|
#!/usr/bin/env python3 |
|
import sys |
|
import hashlib |
|
import os |
|
import subprocess |
|
from datetime import datetime |
|
import itertools |
|
|
|
filename = sys.argv[-1] |
|
with open(filename, 'r') as f: |
|
all_code = f.read() |
|
|
|
content_hash = hashlib.sha256(all_code.encode()).hexdigest() |
|
scala_code = all_code[1:] |
|
|
|
cache_path = os.path.expanduser(f"~/.ivy2/local/com.abdulradi.scala-scripts-launcher/generated/{content_hash}") |
|
launcher_path = f'{cache_path}/launcher' |
|
|
|
if not os.path.exists(launcher_path): |
|
dependencies = list(itertools.takewhile(lambda x: not x.startswith('-'), sys.argv[1:-1])) |
|
bootstrap_flags = ' '.join(itertools.dropwhile(lambda x: not x.startswith('-'), sys.argv[1:-1])) |
|
now = datetime.now().timestamp() |
|
|
|
os.makedirs(f'{cache_path}/jars', exist_ok=True) |
|
os.makedirs(f'{cache_path}/ivys', exist_ok=True) |
|
|
|
# Copy of scala code with shebang commented out as it confuses scalac. TODO: Find a better place for this file |
|
scalacode_path = f'{cache_path}/jars/generated.scala' |
|
with open(scalacode_path, "w") as f: |
|
f.write(f'//{all_code}') |
|
|
|
jar_path = f'{cache_path}/jars/generated.jar' |
|
class_path = subprocess.check_output(['coursier', 'fetch', '--classpath'] + dependencies).decode('utf-8') |
|
|
|
os.system(f'coursier launch scalac -- {scalacode_path} -d {jar_path} -classpath {class_path} ') |
|
scala_version = subprocess.check_output(['coursier', 'launch', 'scalac', '--', '-version']).decode('utf-8')[23:-55] # Drop the copyrights and extract version number, probably very fragile and might break with dotty |
|
[scala_version_major, scala_version_minor, scala_version_patch] = scala_version.split('.') |
|
with open(f'{cache_path}/ivys/ivy.xml', "w") as f: |
|
f.write('<?xml version="1.0" encoding="UTF-8"?>\n') |
|
f.write('<ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">\n') |
|
f.write(f' <info organisation="com.abdulradi.scala-scripts-launcher" module="generated" revision="{content_hash}" status="integration" publication="{now}"></info>\n') |
|
f.write(' <configurations>\n') |
|
f.write(' <conf name="compile" visibility="public" description=""/>\n') |
|
f.write(' <conf name="optional" visibility="public" description=""/>\n') |
|
f.write(' <conf name="scala-tool" visibility="private" description=""/>\n') |
|
f.write(' </configurations>\n') |
|
f.write(' <dependencies>\n') |
|
f.write(f' <dependency org="org.scala-lang" name="scala-compiler" rev="{scala_version}" conf="scala-tool->default,optional(default)"/>\n') |
|
f.write(f' <dependency org="org.scala-lang" name="scala-library" rev="{scala_version}" conf="scala-tool->default,optional(default);compile->default(compile)"/>\n') |
|
for dependency in dependencies: |
|
if '::' in dependency: |
|
[org, name_rev] = dependency.split('::') |
|
[name_unversioned, rev] = name_rev.split(':') |
|
name = f'{name_unversioned}_{scala_version_major}.{scala_version_minor}' |
|
else: |
|
[org, name, rev] = dependency.split(':') |
|
|
|
f.write(f' <dependency org="{org}" name="{name}" rev="{rev}" conf="compile->default(compile)"/>\n') |
|
|
|
f.write(' </dependencies>\n') |
|
f.write('</ivy-module>\n') |
|
|
|
os.system(f'coursier bootstrap com.abdulradi.scala-scripts-launcher:generated:{content_hash} {bootstrap_flags} -o {launcher_path} --quiet') |
|
|
|
|
|
os.system(launcher_path) |
@tabdulradi, I think that longer-term the hack/solution might be better off not relying on Coursier's native image generation because one may run into troubles in case of dependency exclusions. One would need to construct the Ivy module descriptor (or Maven pom) very carefully in order not to end up with an unwanted version of a dependency conflict.
So in TSK I plan to use Coursier just for fetching GraalVM (and possibly also for running
gu
to install thenative-image
tool) but to use thenative-image
on my own, supplying the classpath resolved by Coursier basing on all the exclusions I care about./cc @alexarchambault