Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b07fa70

Browse files
committed
Kotlin/Bazel: provide wrapper for managing versions of kotlinc
By adding `java/kotlinc-extractor/deps/dev` to `PATH`, one gets a `kotlinc` wrapper that takes care of downloading and extracting the desired version of `kotlinc` on demand. The desired version can be selected with `kotlinc --select x.y.z`, or left to the current default of `1.9.0`. Moreover, this default version is integrated with the Bazel build, so that when using this wrapper, changes in the selected version will be picked up to define the default single version kotlin extractor build, without needing to do anything else (like `bazel fetch --force` or similar). Selected and installed version data is stored in `.gitignore`d files in the same directory, and can be cleared with `kotlinc --clear`.
1 parent 9d1901c commit b07fa70

5 files changed

Lines changed: 176 additions & 6 deletions

File tree

java/kotlin-extractor/deps.bzl

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("//java/kotlin-extractor:versions.bzl", "DEFAULT_VERSION", "VERSIONS", "version_less")
1+
load("//java/kotlin-extractor:versions.bzl", "VERSIONS", "version_less")
22
load("//misc/bazel:lfs.bzl", "lfs_smudge")
33

44
_kotlin_dep_build = """
@@ -73,11 +73,22 @@ def _get_default_version(repository_ctx):
7373
default_version = repository_ctx.getenv("CODEQL_KOTLIN_SINGLE_VERSION")
7474
if default_version:
7575
return default_version
76-
if not repository_ctx.which("kotlinc"):
77-
return DEFAULT_VERSION
7876
kotlin_plugin_versions = repository_ctx.path(Label("//java/kotlin-extractor:current_kotlin_version.py"))
7977
python = repository_ctx.which("python3") or repository_ctx.which("python")
80-
res = repository_ctx.execute([python, kotlin_plugin_versions])
78+
env = {}
79+
repository_ctx.watch(Label("//java/kotlin-extractor/deps:dev/.kotlinc_selected_version"))
80+
if not repository_ctx.which("kotlinc"):
81+
# take default from the kotlinc wrapper
82+
path = repository_ctx.getenv("PATH")
83+
path_to_add = repository_ctx.path(Label("//java/kotlin-extractor/deps:dev"))
84+
if not path:
85+
path = str(path_to_add)
86+
elif repository_ctx.os.name == "windows":
87+
path = "%s;%s" % (path, path_to_add)
88+
else:
89+
path = "%s:%s" % (path, path_to_add)
90+
env["PATH"] = path
91+
res = repository_ctx.execute([python, kotlin_plugin_versions], environment = env)
8192
if res.return_code != 0:
8293
fail(res.stderr)
8394
return res.stdout.strip()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.kotlinc_installed
2+
/.kotlinc_installed_version
3+
/.kotlinc_selected_version
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Wrapper script that manages kotlinc versions.
5+
Usage: add this directory to your PATH, then
6+
* `kotlinc --select x.y.z` will select the version for the next invocations, checking it actually exists
7+
* `kotlinc --clear` will remove any state of the wrapper (deselecting a previous version selection)
8+
* `kotlinc -version` will print the selected version information. It will not print `JRE` information as a normal
9+
`kotlinc` invocation would do though. In exchange, the invocation incurs no overhead.
10+
* Any other invocation will forward to the selected kotlinc version, downloading it if necessary. If no version was
11+
previously selected with `--select`, a default will be used (see `DEFAULT_VERSION` below)
12+
13+
In order to install kotlin, ripunzip will be used if installed, or if running on Windows within `semmle-code` (ripunzip
14+
is available in `resources/lib/windows/ripunzip` then).
15+
"""
16+
17+
import pathlib
18+
import os
19+
import urllib
20+
import urllib.request
21+
import urllib.error
22+
import argparse
23+
import sys
24+
import platform
25+
import subprocess
26+
import zipfile
27+
import shutil
28+
import io
29+
30+
DEFAULT_VERSION = "1.9.0"
31+
32+
def options():
33+
parser = argparse.ArgumentParser(add_help=False)
34+
parser.add_argument("--select")
35+
parser.add_argument("--clear", action="store_true")
36+
parser.add_argument("-version", action="store_true")
37+
return parser.parse_known_args()
38+
39+
40+
url_template = 'https://github.com/JetBrains/kotlin/releases/download/v{version}/kotlin-compiler-{version}.zip'
41+
this_dir = pathlib.Path(__file__).resolve().parent
42+
version_file = this_dir / ".kotlinc_selected_version"
43+
installed_version_file = this_dir / ".kotlinc_installed_version"
44+
install_dir = this_dir / ".kotlinc_installed"
45+
windows_ripunzip = this_dir.parents[4] / "resources" / "lib" / "windows" / "ripunzip" / "ripunzip.exe"
46+
47+
48+
class Error(Exception):
49+
pass
50+
51+
52+
class ZipFilePreservingPermissions(zipfile.ZipFile):
53+
def _extract_member(self, member, targetpath, pwd):
54+
if not isinstance(member, zipfile.ZipInfo):
55+
member = self.getinfo(member)
56+
57+
targetpath = super()._extract_member(member, targetpath, pwd)
58+
59+
attr = member.external_attr >> 16
60+
if attr != 0:
61+
os.chmod(targetpath, attr)
62+
return targetpath
63+
64+
65+
def check_version(version: str):
66+
try:
67+
with urllib.request.urlopen(url_template.format(version=version)) as response:
68+
pass
69+
except urllib.error.HTTPError as e:
70+
if e.code == 404:
71+
raise Error(f"Version {version} not found in github.com/JetBrains/kotlin/releases") from e
72+
raise
73+
74+
def get_version(file: pathlib.Path) -> str:
75+
try:
76+
return file.read_text()
77+
except FileNotFoundError:
78+
return None
79+
80+
81+
def install(version: str):
82+
url = url_template.format(version=version)
83+
if install_dir.exists():
84+
shutil.rmtree(install_dir)
85+
install_dir.mkdir()
86+
ripunzip = shutil.which("ripunzip")
87+
if platform.system() == "Windows" and windows_ripunzip.exists():
88+
ripunzip = windows_ripunzip
89+
if ripunzip:
90+
print(f"downloading and extracting {url} using ripunzip", file=sys.stderr)
91+
subprocess.run([ripunzip, "unzip-uri", url], cwd=install_dir, check=True)
92+
return
93+
with io.BytesIO() as buffer:
94+
with urllib.request.urlopen(url) as response:
95+
print(f"downloading {url}", file=sys.stderr)
96+
while True:
97+
bytes = response.read()
98+
if not bytes:
99+
break
100+
buffer.write(bytes)
101+
buffer.seek(0)
102+
print(f"extracting kotlin-compiler-{version}.zip", file=sys.stderr)
103+
with ZipFilePreservingPermissions(buffer) as archive:
104+
archive.extractall(install_dir)
105+
106+
107+
def forward(forwarded_opts):
108+
kotlinc = install_dir / "kotlinc" / "bin" / "kotlinc"
109+
if platform.system() == "Windows":
110+
kotlinc = kotlinc.with_suffix(".bat")
111+
args = [sys.argv[0]]
112+
args.extend(forwarded_opts)
113+
os.execv(kotlinc, args)
114+
115+
def clear():
116+
if install_dir.exists():
117+
print(f"removing {install_dir}", file=sys.stderr)
118+
shutil.rmtree(install_dir)
119+
if installed_version_file.exists():
120+
print(f"removing {installed_version_file}", file=sys.stderr)
121+
installed_version_file.unlink()
122+
if version_file.exists():
123+
print(f"removing {version_file}", file=sys.stderr)
124+
version_file.unlink()
125+
126+
def main(opts, forwarded_opts):
127+
if opts.clear:
128+
clear()
129+
return
130+
if opts.select:
131+
check_version(opts.select)
132+
version_file.write_text(opts.select)
133+
selected_version = opts.select
134+
else:
135+
selected_version = get_version(version_file)
136+
if not selected_version:
137+
selected_version = DEFAULT_VERSION
138+
version_file.write_text(selected_version)
139+
if opts.version:
140+
print(f"info: kotlinc-jvm {selected_version} (codeql dev wrapper)", file=sys.stderr)
141+
return
142+
if opts.select and not forwarded_opts:
143+
print(f"selected {selected_version}", file=sys.stderr)
144+
return
145+
if get_version(installed_version_file) != selected_version:
146+
install(selected_version)
147+
installed_version_file.write_text(selected_version)
148+
forward(forwarded_opts)
149+
150+
151+
if __name__ == "__main__":
152+
try:
153+
main(*options())
154+
except Exception as e:
155+
print(f"{e.__class__.__name__}: {e}", file=sys.stderr)
156+
sys.exit(1)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@echo off
2+
python3 %~dp0/kotlinc

java/kotlin-extractor/versions.bzl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ VERSIONS = [
1414
"2.0.0-RC1",
1515
]
1616

17-
DEFAULT_VERSION = "1.9.0"
18-
1917
def _version_to_tuple(v):
2018
# we ignore the tag when comparing versions, for example 1.9.0-Beta <= 1.9.0
2119
v, _, ignored_tag = v.partition("-")

0 commit comments

Comments
 (0)