Skip to content

Commit 6a83bf9

Browse files
authored
Merge pull request #16208 from github/redsun82/kotlin-wrapper
Kotlin/Bazel: provide wrapper for managing versions of `kotlinc`
2 parents 9d1901c + 1b5675e commit 6a83bf9

5 files changed

Lines changed: 183 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: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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 urllib
19+
import urllib.request
20+
import urllib.error
21+
import argparse
22+
import sys
23+
import platform
24+
import subprocess
25+
import zipfile
26+
import shutil
27+
import io
28+
import os
29+
30+
DEFAULT_VERSION = "1.9.0"
31+
32+
33+
def options():
34+
parser = argparse.ArgumentParser(add_help=False)
35+
parser.add_argument("--select")
36+
parser.add_argument("--clear", action="store_true")
37+
parser.add_argument("-version", action="store_true")
38+
return parser.parse_known_args()
39+
40+
41+
url_template = 'https://github.com/JetBrains/kotlin/releases/download/v{version}/kotlin-compiler-{version}.zip'
42+
this_dir = pathlib.Path(__file__).resolve().parent
43+
version_file = this_dir / ".kotlinc_selected_version"
44+
installed_version_file = this_dir / ".kotlinc_installed_version"
45+
install_dir = this_dir / ".kotlinc_installed"
46+
windows_ripunzip = this_dir.parents[4] / "resources" / "lib" / "windows" / "ripunzip" / "ripunzip.exe"
47+
48+
49+
class Error(Exception):
50+
pass
51+
52+
53+
class ZipFilePreservingPermissions(zipfile.ZipFile):
54+
def _extract_member(self, member, targetpath, pwd):
55+
if not isinstance(member, zipfile.ZipInfo):
56+
member = self.getinfo(member)
57+
58+
targetpath = super()._extract_member(member, targetpath, pwd)
59+
60+
attr = member.external_attr >> 16
61+
if attr != 0:
62+
os.chmod(targetpath, attr)
63+
return targetpath
64+
65+
66+
def check_version(version: str):
67+
try:
68+
with urllib.request.urlopen(url_template.format(version=version)) as response:
69+
pass
70+
except urllib.error.HTTPError as e:
71+
if e.code == 404:
72+
raise Error(f"Version {version} not found in github.com/JetBrains/kotlin/releases") from e
73+
raise
74+
75+
76+
def get_version(file: pathlib.Path) -> str:
77+
try:
78+
return file.read_text()
79+
except FileNotFoundError:
80+
return None
81+
82+
83+
def install(version: str):
84+
url = url_template.format(version=version)
85+
if install_dir.exists():
86+
shutil.rmtree(install_dir)
87+
install_dir.mkdir()
88+
ripunzip = shutil.which("ripunzip")
89+
if ripunzip is None and platform.system() == "Windows" and windows_ripunzip.exists():
90+
ripunzip = windows_ripunzip
91+
if ripunzip:
92+
print(f"downloading and extracting {url} using ripunzip", file=sys.stderr)
93+
subprocess.run([ripunzip, "unzip-uri", url], cwd=install_dir, check=True)
94+
return
95+
with io.BytesIO() as buffer:
96+
print(f"downloading {url}", file=sys.stderr)
97+
with urllib.request.urlopen(url) as response:
98+
while True:
99+
bytes = response.read()
100+
if not bytes:
101+
break
102+
buffer.write(bytes)
103+
buffer.seek(0)
104+
print(f"extracting kotlin-compiler-{version}.zip", file=sys.stderr)
105+
with ZipFilePreservingPermissions(buffer) as archive:
106+
archive.extractall(install_dir)
107+
108+
109+
def forward(forwarded_opts):
110+
kotlinc = install_dir / "kotlinc" / "bin" / "kotlinc"
111+
if platform.system() == "Windows":
112+
kotlinc = kotlinc.with_suffix(".bat")
113+
assert kotlinc.exists(), f"{kotlinc} not found"
114+
args = [kotlinc]
115+
args.extend(forwarded_opts)
116+
ret = subprocess.run(args).returncode
117+
sys.exit(ret)
118+
119+
120+
def clear():
121+
if install_dir.exists():
122+
print(f"removing {install_dir}", file=sys.stderr)
123+
shutil.rmtree(install_dir)
124+
if installed_version_file.exists():
125+
print(f"removing {installed_version_file}", file=sys.stderr)
126+
installed_version_file.unlink()
127+
if version_file.exists():
128+
print(f"removing {version_file}", file=sys.stderr)
129+
version_file.unlink()
130+
131+
132+
def main(opts, forwarded_opts):
133+
if opts.clear:
134+
clear()
135+
return
136+
if opts.select:
137+
check_version(opts.select)
138+
version_file.write_text(opts.select)
139+
selected_version = opts.select
140+
else:
141+
selected_version = get_version(version_file)
142+
if not selected_version:
143+
selected_version = DEFAULT_VERSION
144+
version_file.write_text(selected_version)
145+
if get_version(installed_version_file) != selected_version:
146+
install(selected_version)
147+
installed_version_file.write_text(selected_version)
148+
if opts.version or (opts.select and not forwarded_opts):
149+
print(f"info: kotlinc-jvm {selected_version} (codeql dev wrapper)", file=sys.stderr)
150+
return
151+
forward(forwarded_opts)
152+
153+
154+
if __name__ == "__main__":
155+
try:
156+
main(*options())
157+
except Exception as e:
158+
print(f"{e.__class__.__name__}: {e}", file=sys.stderr)
159+
sys.exit(1)
160+
except KeyboardInterrupt:
161+
sys.exit(1)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@echo off
2+
3+
python "%~dp0/kotlinc" %*
4+
exit /b %ERRORLEVEL%

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)