Skip to content

Commit 9bca24e

Browse files
Allow to create standalone packages of scala native (#3)
* Rename intergration-test to cli * Make sure linked application can be run * Bootsrrap packing * Clenaup * Make sure created binary is executable * Fix scripts * Fix standalone test build * Fix scripts, ensure it's possible to run created executable can be run in scripted tests * Run scalafmt * fix typos * Dynamically find path for PATH env when setting * Revert changes adding cross Scala versions support * Remove println
1 parent 0807e31 commit 9bca24e

File tree

11 files changed

+322
-6
lines changed

11 files changed

+322
-6
lines changed

build.sbt

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
val crossScalaNative212 = Seq("2.12.13", "2.12.14", "2.12.15")
2+
13
val scalaNativeVersion =
24
settingKey[String]("Version of Scala Native for which to build to CLI")
35

@@ -6,11 +8,14 @@ val cliAssemblyJarName = settingKey[String]("Name of created assembly jar")
68
inThisBuild(
79
Def.settings(
810
organization := "org.scala-native",
9-
scalaVersion := "2.12.15",
11+
scalaVersion := crossScalaNative212.last,
1012
scalaNativeVersion := "0.4.0",
1113
version := scalaNativeVersion.value
1214
)
1315
)
16+
val cliPackLibJars =
17+
taskKey[Seq[File]]("All libraries packed with packed in cliPack")
18+
val cliPack = taskKey[File]("Pack the CLI for the current configuration")
1419

1520
lazy val cli = project
1621
.in(file("cli"))
@@ -36,17 +41,124 @@ lazy val cli = project
3641
Seq(
3742
"-Xmx1024M",
3843
"-Dplugin.version=" + scalaNativeVersion.value,
39-
"-Dscala-native-cli=" + cliPath
44+
"-Dscala-native-cli=" + cliPath,
45+
"-Dscala-native-cli-pack=" + (cliPack / crossTarget).value
4046
)
4147
},
4248
scriptedBufferLog := false,
4349
scriptedDependencies := {
4450
scriptedDependencies
45-
.dependsOn(assembly)
51+
.dependsOn(assembly, cliPack)
4652
.value
47-
}
53+
},
54+
cliPackSettings
4855
)
4956

57+
lazy val cliPackSettings = Def.settings(
58+
cliPackLibJars := {
59+
val s = streams.value
60+
val log = s.log
61+
62+
val scalaNativeOrg = "org.scala-native"
63+
val scalaBinVer = scalaBinaryVersion.value
64+
val snVer = scalaNativeVersion.value
65+
66+
val scalaFullVers = crossScalaNative212
67+
val cliAssemblyJar = assembly.value
68+
69+
// Standard modules needed for linking of Scala Native
70+
val optLib = snVer match {
71+
case "0.4.0" => Nil
72+
case v => "windowslib" :: Nil
73+
}
74+
val stdLibModuleIDs = Seq(
75+
"nativelib",
76+
"clib",
77+
"posixlib",
78+
"javalib",
79+
"auxlib",
80+
"scalalib"
81+
).++(optLib).map { lib =>
82+
val nativeBinVersion = ScalaNativeCrossVersion.binaryVersion(snVer)
83+
scalaNativeOrg % s"${lib}_native${nativeBinVersion}_${scalaBinVer}" % snVer
84+
}
85+
val compilerPluginModuleIDs =
86+
scalaFullVers.map(v => scalaNativeOrg % s"nscplugin_$v" % snVer)
87+
val allModuleIDs = (stdLibModuleIDs ++ compilerPluginModuleIDs).toVector
88+
val allModuleIDsIntransitive = allModuleIDs.map(_.intransitive())
89+
90+
val resolvedLibJars = {
91+
val retrieveDir = s.cacheDirectory / "cli-lib-jars"
92+
val lm = {
93+
import sbt.librarymanagement.ivy._
94+
val ivyConfig = InlineIvyConfiguration().withLog(log)
95+
IvyDependencyResolution(ivyConfig)
96+
}
97+
val dummyModuleName =
98+
s"clilibjars-$snVer-$scalaBinVer-" + scalaFullVers.mkString("-")
99+
val dummyModuleID = scalaNativeOrg % dummyModuleName % version.value
100+
val descriptor =
101+
lm.moduleDescriptor(
102+
dummyModuleID,
103+
allModuleIDsIntransitive,
104+
scalaModuleInfo = None
105+
)
106+
lm
107+
.retrieve(descriptor, retrieveDir, log)
108+
.fold(
109+
{ unresolvedWarn => throw unresolvedWarn.resolveException },
110+
identity
111+
)
112+
.distinct
113+
}
114+
115+
cliAssemblyJar +: resolvedLibJars
116+
},
117+
cliPack / target := crossTarget.value / "pack",
118+
cliPack / moduleName :=
119+
s"scala-native-cli_${scalaBinaryVersion.value}-${scalaNativeVersion.value}",
120+
cliPack / crossTarget := (cliPack / target).value / (cliPack / moduleName).value,
121+
cliPack := {
122+
val scalaBinVer = scalaBinaryVersion.value
123+
val snVer = scalaNativeVersion.value
124+
125+
val trg = (cliPack / crossTarget).value
126+
val trgLib = trg / "lib"
127+
val trgBin = trg / "bin"
128+
129+
if (trg.exists)
130+
IO.delete(trg)
131+
132+
IO.createDirectory(trgLib)
133+
val libJars = cliPackLibJars.value
134+
for (libJar <- libJars) {
135+
IO.copyFile(libJar, trgLib / libJar.getName)
136+
}
137+
138+
IO.createDirectory(trgBin)
139+
val scriptDir = (Compile / sourceDirectory).value.getParentFile / "script"
140+
for {
141+
scriptFile <- IO.listFiles(scriptDir)
142+
if !scriptFile.getPath.endsWith("~")
143+
} {
144+
val content = IO.read(scriptFile)
145+
val processedContent = content
146+
.replaceAllLiterally("@SCALA_BIN_VER@", scalaBinVer)
147+
.replaceAllLiterally("@SCALANATIVE_VER@", snVer)
148+
.replaceAllLiterally(
149+
"@SCALANATIVE_BIN_VER@",
150+
ScalaNativeCrossVersion.binaryVersion(snVer)
151+
)
152+
val dest = trgBin / scriptFile.getName
153+
IO.write(dest, processedContent)
154+
if (scriptFile.canExecute)
155+
dest.setExecutable( /* executable = */ true, /* ownerOnly = */ false)
156+
}
157+
158+
trg
159+
}
160+
)
161+
50162
// To be removed since 0.4.2
51163
lazy val patchSourcesSettings = {
52164
def patchSources(base: File, version: String, subdir: String) = {

cli/src/sbt-test/integration/cli/build.sbt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import scala.collection.JavaConverters._
88
val runCli = inputKey[Unit](
99
"Runs scala-native-cli with classpath and selected other options via cli arguments"
1010
)
11+
val runExec = inputKey[Unit](
12+
"Runs given executable (due to problems with exec on Windows)"
13+
)
1114
runCli := {
1215
val args = spaceDelimited("<arg>").parsed
1316
val classpath = (Compile / fullClasspath).value.map(_.data.toString())
@@ -33,3 +36,17 @@ runCli := {
3336
) with scala.util.control.NoStackTrace
3437
}
3538
}
39+
40+
runExec := {
41+
val args = spaceDelimited("<arg>").parsed
42+
new ProcessBuilder(args: _*)
43+
.inheritIO()
44+
.start()
45+
.waitFor() match {
46+
case 0 => ()
47+
case exitCode =>
48+
throw new RuntimeException(
49+
s"Execution of command failed with code $exitCode"
50+
) with scala.util.control.NoStackTrace
51+
}
52+
}

cli/src/sbt-test/integration/cli/test

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
> runCli --version
55

66
# -- Link and check if outpath exists
7-
> runCli --outpath target/out1 -v -v --main Main$
8-
$ exists target/out1
7+
> runCli --outpath target/out1.exe -v -v --main Main$
8+
$ exists target/out1.exe
9+
> runExec ./target/out1.exe
910

1011
# -- Fail to link without main specified
1112
-> runCli --outpath target/out2 -v -v
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Main {
2+
def main(args: Array[String]): Unit = {
3+
println("Hello world")
4+
}
5+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import sbt._
2+
import sbt.Keys._
3+
import complete.DefaultParsers._
4+
import scala.collection.JavaConverters._
5+
import java.util.Locale
6+
import java.nio.file.Paths
7+
import java.io.File
8+
9+
val runScript = inputKey[Unit](
10+
"Runs scala-native-cli pack script"
11+
)
12+
val runExec = inputKey[Unit](
13+
"Runs given executable (due to problems with exec on Windows)"
14+
)
15+
16+
runScript := {
17+
val scriptName +: args = spaceDelimited("<arg>").parsed.toSeq
18+
val cliPackDir = System.getProperty("scala-native-cli-pack")
19+
val isWindows = System
20+
.getProperty("os.name", "unknown")
21+
.toLowerCase(Locale.ROOT)
22+
.startsWith("windows")
23+
24+
val ver = scalaVersion.value
25+
val cacheDir =
26+
new File(cliPackDir).getParentFile.getParentFile / s"fetchScala-$ver"
27+
val scalaBinDir = cacheDir / s"scala-$ver" / "bin"
28+
29+
FileFunction.cached(
30+
cacheDir / s"fetchScala-$ver",
31+
FilesInfo.lastModified,
32+
FilesInfo.exists
33+
) { _ =>
34+
if (!scalaBinDir.exists) {
35+
IO.unzipURL(
36+
url(s"https://downloads.lightbend.com/scala/${ver}/scala-$ver.zip"),
37+
cacheDir
38+
)
39+
}
40+
// Make sure we can execute scala/scalac from downloaded distro
41+
scalaBinDir
42+
.listFiles()
43+
.foreach(f =>
44+
f.setExecutable( /* executable = */ true, /* ownerOnly = */ false)
45+
)
46+
47+
Set(scalaBinDir)
48+
}(Set(scalaBinDir))
49+
50+
val script = Paths.get(cliPackDir, "bin", scriptName).toString
51+
val proc =
52+
if (isWindows)
53+
new ProcessBuilder(
54+
(Seq("cmd", "/c", script) ++ args): _*
55+
)
56+
else new ProcessBuilder((Seq("sh", script) ++ args): _*)
57+
58+
// Removes fake error notification - expected errors would be
59+
// reported as such, leading to failing CI and confusing output
60+
proc.redirectErrorStream(true)
61+
proc.inheritIO()
62+
63+
// Find name of Path environment, depending on OS/env it might differ in casing
64+
// Update existing path or create a new env variable
65+
proc.environment.keySet.toArray.collectFirst {
66+
case key: String if key.toUpperCase(Locale.ROOT) == "PATH" => key
67+
} match {
68+
case Some(pathKey) =>
69+
val prevPath = proc.environment.get(pathKey)
70+
proc.environment.put(
71+
pathKey,
72+
s"${scalaBinDir}${File.pathSeparator}${prevPath}"
73+
)
74+
case _ =>
75+
proc.environment.put("PATH", scalaBinDir.toString)
76+
}
77+
78+
proc.start().waitFor() match {
79+
case 0 => ()
80+
case exitCode =>
81+
throw new RuntimeException(
82+
s"Execution of command failed with code $exitCode"
83+
) with scala.util.control.NoStackTrace
84+
}
85+
}
86+
87+
runExec := {
88+
val args = spaceDelimited("<arg>").parsed
89+
new ProcessBuilder(args: _*)
90+
.inheritIO()
91+
.start()
92+
.waitFor() match {
93+
case 0 => ()
94+
case exitCode =>
95+
throw new RuntimeException(
96+
s"Execution of command failed with code $exitCode"
97+
) with scala.util.control.NoStackTrace
98+
}
99+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
> runScript scala-native-c Main.scala
2+
$ exists Main$.nir
3+
4+
> runScript scala-native-ld --main Main$ . -o scala-native-out.exe -v -v
5+
$ exists scala-native-out.exe
6+
> runExec ./scala-native-out.exe

cli/src/script/scala-native-c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#! /bin/sh
2+
3+
SCALA_BIN_VER="@SCALA_BIN_VER@"
4+
SCALANATIVE_VER="@SCALANATIVE_VER@"
5+
SCALANATIVE_BIN_VER="@SCALANATIVE_BIN_VER@"
6+
SCALA_VER=$(scalac -version 2>&1 | grep -i scala | grep -o '[0-9]\.[0-9]\+\.[0-9]\+')
7+
8+
if [ "$(echo $SCALA_VER | cut -d '.' -f 1-2)" != "$SCALA_BIN_VER" ]; then
9+
echo "This bundle of Scala Native CLI is for $SCALA_BIN_VER. Your scala version is $SCALA_VER!" >&2
10+
exit 1
11+
fi
12+
13+
14+
BASE="$(dirname $0)/.."
15+
PLUGIN="$BASE/lib/nscplugin_$SCALA_VER-$SCALANATIVE_VER.jar"
16+
NATIVELIB=""
17+
libs="nativelib clib posixlib windowslib auxlib javalib scalalib"
18+
for lib in $libs; do
19+
NATIVELIB="${NATIVELIB}:$BASE/lib/${lib}_native${SCALANATIVE_BIN_VER}_$SCALA_BIN_VER-$SCALANATIVE_VER.jar"
20+
done
21+
22+
scalac -classpath ".:$NATIVELIB" "-Xplugin:$PLUGIN" "$@"

cli/src/script/scala-native-c.bat

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@ECHO OFF
2+
SetLocal
3+
4+
set SCALA_BIN_VER=@SCALA_BIN_VER@
5+
set SCALANATIVE_VER=@SCALANATIVE_VER@
6+
set SCALANATIVE_BIN_VER=@SCALANATIVE_BIN_VER@
7+
8+
for /F "tokens=5" %%i in (' scala -version 2^>^&1 1^>nul ') do set SCALA_VER=%%i
9+
10+
if NOT "%SCALA_VER:~0,4%" == "%SCALA_BIN_VER%" (
11+
echo "This bundle of Scala Native CLI is for %SCALA_BIN_VER%. Your scala version is %SCALA_VER%!" 1>&2
12+
EXIT 1
13+
)
14+
15+
set BASE=%~dp0\..\lib
16+
set SUFFIX=_native%SCALANATIVE_BIN_VER%_%SCALA_BIN_VER%-%SCALANATIVE_VER%.jar
17+
18+
set PLUGIN=%BASE%\nscplugin_%SCALA_VER%-%SCALANATIVE_VER%.jar
19+
set NATIVELIB=%BASE%\nativelib%SUFFIX%;%BASE%\clib%SUFFIX%;%BASE%\posixlib%SUFFIX%;%BASE%\windowslib%SUFFIX%;%BASE%\auxlib%SUFFIX%;%BASE%\javalib%SUFFIX%;%BASE%\scalalib%SUFFIX%
20+
21+
scalac -classpath .;%NATIVELIB% -Xplugin:%PLUGIN% "%*"

cli/src/script/scala-native-ld

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#! /bin/sh
2+
3+
SCALA_BIN_VER="@SCALA_BIN_VER@"
4+
SCALANATIVE_VER="@SCALANATIVE_VER@"
5+
SCALANATIVE_BIN_VER="@SCALANATIVE_BIN_VER@"
6+
7+
BASE="$(dirname $0)/.."
8+
9+
CLILIB="$BASE/lib/scala-native-cli-assembly_$SCALA_BIN_VER-$SCALANATIVE_VER.jar"
10+
NATIVELIB=""
11+
libs="nativelib clib posixlib windowslib auxlib javalib scalalib"
12+
for lib in $libs; do
13+
NATIVELIB="${NATIVELIB} $BASE/lib/${lib}_native${SCALANATIVE_BIN_VER}_$SCALA_BIN_VER-$SCALANATIVE_VER.jar"
14+
done
15+
16+
scala -classpath "$CLILIB" scala.scalanative.cli.ScalaNativeCli "$@" ${NATIVELIB}

cli/src/script/scala-native-ld.bat

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@ECHO OFF
2+
SetLocal
3+
4+
set SCALA_BIN_VER=@SCALA_BIN_VER@
5+
set SCALANATIVE_VER=@SCALANATIVE_VER@
6+
set SCALANATIVE_BIN_VER=@SCALANATIVE_BIN_VER@
7+
8+
set BASE=%~dp0\..\lib
9+
set SUFFIX=_native%SCALANATIVE_BIN_VER%_%SCALA_BIN_VER%-%SCALANATIVE_VER%.jar
10+
11+
set CLILIB=%BASE%\scala-native-cli-assembly_%SCALA_BIN_VER%-%SCALANATIVE_VER%.jar
12+
set PLUGIN=%BASE%\nscplugin_%SCALA_VER%-%SCALANATIVE_VER%.jar
13+
set NATIVELIB=%BASE%\nativelib%SUFFIX% %BASE%\clib%SUFFIX% %BASE%\posixlib%SUFFIX% %BASE%\windowslib%SUFFIX% %BASE%\auxlib%SUFFIX% %BASE%\javalib%SUFFIX% %BASE%\scalalib%SUFFIX%
14+
15+
scala -classpath %CLILIB% scala.scalanative.cli.ScalaNativeCli %* %NATIVELIB%

0 commit comments

Comments
 (0)