From 85127640b72077fd4b7e4f62bce53e0ac2479ac1 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Thu, 2 Jun 2022 10:52:51 +0200 Subject: [PATCH] Initial commit --- .github/workflows/ci.yml | 125 ++++++++ .gitignore | 1 + .mill-version | 1 + Foo.java | 5 + README.md | 7 + build.sc | 269 ++++++++++++++++++ .../javaclassname/JavaClassNameTests.scala | 40 +++ .../java-class-name/reflect-config.json | 9 + .../cli/javaclassname/JavaClassName.scala | 32 +++ .../scala/cli/javaclassname/JavaParser.scala | 39 +++ mill | 171 +++++++++++ mill.bat | 115 ++++++++ musl-image/Dockerfile | 4 + musl-image/setup.sh | 11 + .../src/CoursierCacheProcessor.scala | 18 ++ 15 files changed, 847 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .mill-version create mode 100644 Foo.java create mode 100644 README.md create mode 100644 build.sc create mode 100644 java-class-name-tests/test/src/scala/cli/javaclassname/JavaClassNameTests.scala create mode 100644 java-class-name/resources/META-INF/native-image/org.virtuslab.scala-cli/java-class-name/reflect-config.json create mode 100644 java-class-name/src/scala/cli/javaclassname/JavaClassName.scala create mode 100644 java-class-name/src/scala/cli/javaclassname/JavaParser.scala create mode 100755 mill create mode 100644 mill.bat create mode 100644 musl-image/Dockerfile create mode 100755 musl-image/setup.sh create mode 100644 scala3-graal-processor/src/CoursierCacheProcessor.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6fb7201 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,125 @@ +name: CI +on: + push: + branches: + - main + tags: + - "v*" + pull_request: +jobs: + generate-launchers: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: true + - uses: coursier/cache-action@v6.3 + - uses: coursier/setup-action@v1.2.0-M3 + with: + jvm: temurin:17 + - run: | + ./mill -i "java-class-name.writeNativeImageScript" generate.sh "" && \ + ./generate.sh && \ + ./mill -i "java-class-name.copyToArtifacts" artifacts/ + if: runner.os != 'Windows' + - run: | + @call ./mill.bat -i "java-class-name.writeNativeImageScript" generate.bat "" + @call generate.bat + @call ./mill.bat -i "java-class-name.copyToArtifacts" artifacts/ + shell: cmd + if: runner.os == 'Windows' + - name: Test + run: ./mill -i java-class-name-tests.test + - uses: actions/upload-artifact@v2.2.4 + with: + name: launcher-${{ matrix.os }} + path: artifacts/ + if-no-files-found: error + retention-days: 1 + - run: ./mill -i ci.upload artifacts/ + if: github.event_name == 'push' + env: + UPLOAD_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + generate-static-launcher: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: true + - uses: coursier/cache-action@v6.3 + - uses: coursier/setup-action@v1.2.0-M3 + with: + jvm: temurin:17 + - run: | + ./mill -i "java-class-name.static.writeNativeImageScript" generate.sh "" && \ + ./generate.sh && \ + ./mill -i "java-class-name.static.copyToArtifacts" artifacts/ + - uses: actions/upload-artifact@v2.2.4 + with: + name: launcher-linux-static + path: artifacts/ + if-no-files-found: error + retention-days: 1 + - name: Test + run: ./mill -i java-class-name-tests.static.test + - run: ./mill -i ci.upload artifacts/ + if: github.event_name == 'push' + env: + UPLOAD_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + generate-mostly-static-launcher: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: true + - uses: coursier/cache-action@v6.3 + - uses: coursier/setup-action@v1.2.0-M3 + with: + jvm: temurin:17 + - run: | + ./mill -i "java-class-name.mostly-static.writeNativeImageScript" generate.sh "" && \ + ./generate.sh && \ + ./mill -i "java-class-name.mostly-static.copyToArtifacts" artifacts/ + - uses: actions/upload-artifact@v2.2.4 + with: + name: launcher-linux-mostly-static + path: artifacts/ + if-no-files-found: error + retention-days: 1 + - name: Test + run: ./mill -i java-class-name-tests.mostly-static.test + - run: ./mill -i ci.upload artifacts/ + if: github.event_name == 'push' + env: + UPLOAD_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish: + needs: generate-launchers + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: true + - uses: coursier/cache-action@v6.3 + - uses: coursier/setup-action@v1.2.0-M3 + with: + jvm: temurin:17 + - run: .github/scripts/gpg-setup.sh + env: + PGP_SECRET: ${{ secrets.PUBLISH_SECRET_KEY }} + - name: Publish + run: ./mill -i ci.publishSonatype __.publishArtifacts + env: + PGP_PASSWORD: ${{ secrets.PUBLISH_SECRET_KEY_PASSWORD }} + SONATYPE_PASSWORD: ${{ secrets.PUBLISH_USER }} + SONATYPE_USERNAME: ${{ secrets.PUBLISH_PASSWORD }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89f9ac0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/.mill-version b/.mill-version new file mode 100644 index 0000000..9b40aa6 --- /dev/null +++ b/.mill-version @@ -0,0 +1 @@ +0.10.4 diff --git a/Foo.java b/Foo.java new file mode 100644 index 0000000..bdb1239 --- /dev/null +++ b/Foo.java @@ -0,0 +1,5 @@ +package a.b; + +public class Foo { + +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..8733376 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# java-class-name + +A small library and application to extract class names from Java sources. + +Can be used as a library and via a binary (generated with GraalVM native image). + +Mainly meant to be used by [Scala CLI](https://github.com/VirtusLab/scala-cli) diff --git a/build.sc b/build.sc new file mode 100644 index 0000000..f162de6 --- /dev/null +++ b/build.sc @@ -0,0 +1,269 @@ +import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.1.4` +import $ivy.`io.github.alexarchambault.mill::mill-native-image::0.1.21` +import $ivy.`io.github.alexarchambault.mill::mill-native-image-upload:0.1.21` + +import de.tobiasroeser.mill.vcs.version._ +import io.github.alexarchambault.millnativeimage.NativeImage +import io.github.alexarchambault.millnativeimage.upload.Upload +import mill._ +import mill.scalalib._ +import coursier.core.Version +import scala.concurrent.duration.DurationInt + +import java.io.File + +trait JavaMainClassNativeImage extends NativeImage { + + def nativeImageOptions = T{ + super.nativeImageOptions() ++ Seq( + "--no-fallback" + ) + } + def nativeImagePersist = System.getenv("CI") != null + def graalVmVersion = "22.1.0" + def nativeImageGraalVmJvmId = s"graalvm-java17:$graalVmVersion" + def nativeImageName = "java-class-name" + def nativeImageMainClass = "scala.cli.javaclassname.JavaClassName" + + def nameSuffix = "" + def copyToArtifacts(directory: String = "artifacts/") = T.command { + val _ = Upload.copyLauncher( + nativeImage().path, + directory, + "java-class-name", + compress = true, + suffix = nameSuffix + ) + } +} + +object `scala3-graal-processor` extends ScalaModule { + def scalaVersion = "3.1.2" + def mainClass = Some("scala.cli.graal.CoursierCacheProcessor") + def ivyDeps = Agg( + ivy"org.virtuslab.scala-cli::scala3-graal:0.1.6" + ) +} + +object `java-class-name` extends ScalaModule with JavaMainClassNativeImage with JavaClassNamePublishModule { + def scalaVersion = "3.1.2" + + def nativeImageClassPath = T { + // adapted from https://github.com/VirtusLab/scala-cli/blob/b19086697401827a6f8185040ceb248d8865bf21/build.sc#L732-L744 + + val classpath = runClasspath().map(_.path).mkString(File.pathSeparator) + val cache = T.dest / "native-cp" + // `scala3-graal-processor`.run() do not give me output and I cannot pass dynamically computed values like classpath + System.err.println("Calling scala3 graal processor on") + for (f <- classpath.split(File.pathSeparator)) + System.err.println(s" $f") + val res = mill.modules.Jvm.callSubprocess( + mainClass = `scala3-graal-processor`.finalMainClass(), + classPath = `scala3-graal-processor`.runClasspath().map(_.path), + mainArgs = Seq(cache.toNIO.toString, classpath), + workingDir = os.pwd + ) + val cp = res.out.text.trim + System.err.println("Processed class path:") + for (f <- cp.split(File.pathSeparator)) + System.err.println(s" $f") + cp.split(File.pathSeparator).toSeq.map(p => mill.PathRef(os.Path(p))) + } + def ivyDeps = super.ivyDeps() ++ Seq( + ivy"org.scala-lang::scala3-compiler:${scalaVersion()}" + ) + def compileIvyDeps = super.compileIvyDeps() ++ Seq( + ivy"org.graalvm.nativeimage:svm:$graalVmVersion" + ) + + object static extends JavaMainClassNativeImage { + def nameSuffix = "-static" + def nativeImageClassPath = T{ + `java-class-name`.nativeImageClassPath() + } + def buildHelperImage = T { + os.proc("docker", "build", "-t", "scala-cli-base-musl:latest", ".") + .call(cwd = os.pwd / "musl-image", stdout = os.Inherit) + () + } + def nativeImageDockerParams = T{ + buildHelperImage() + Some( + NativeImage.linuxStaticParams( + "scala-cli-base-musl:latest", + s"https://github.com/coursier/coursier/releases/download/v$csDockerVersion/cs-x86_64-pc-linux.gz" + ) + ) + } + def writeNativeImageScript(scriptDest: String, imageDest: String = "") = T.command { + buildHelperImage() + super.writeNativeImageScript(scriptDest, imageDest)() + } + } + + object `mostly-static` extends JavaMainClassNativeImage { + def nameSuffix = "-mostly-static" + def nativeImageClassPath = T{ + `java-class-name`.nativeImageClassPath() + } + def nativeImageDockerParams = Some( + NativeImage.linuxMostlyStaticParams( + "ubuntu:18.04", // TODO Pin that? + s"https://github.com/coursier/coursier/releases/download/v$csDockerVersion/cs-x86_64-pc-linux.gz" + ) + ) + } +} + +object `java-class-name-tests` extends ScalaModule { + def scalaVersion = "3.1.2" + trait Tests extends super.Tests { + def launcher: T[PathRef] + def ivyDeps = super.ivyDeps() ++ Seq( + ivy"com.lihaoyi::os-lib:0.8.1", + ivy"com.lihaoyi::utest:0.7.11" + ) + def testFramework = "utest.runner.Framework" + def forkEnv = super.forkEnv() ++ Seq( + "JAVA_CLASS_NAME_CLI" -> launcher().path.toString + ) + } + object test extends Tests { + def launcher = `java-class-name`.nativeImage() + } + object static extends Tests { + def sources = T.sources(`java-class-name-tests`.test.sources()) + def launcher = `java-class-name`.static.nativeImage() + } + object `mostly-static` extends Tests { + def sources = T.sources(`java-class-name-tests`.test.sources()) + def launcher = `java-class-name`.`mostly-static`.nativeImage() + } +} + +def csDockerVersion = "2.1.0-M5-18-gfebf9838c" + +def publishVersion0 = T { + val state = VcsVersion.vcsState() + if (state.commitsSinceLastTag > 0) { + val versionOrEmpty = state.lastTag + .filter(_ != "latest") + .map(_.stripPrefix("v")) + .flatMap { tag => + val idx = tag.lastIndexOf(".") + if (idx >= 0) Some(tag.take(idx + 1) + (tag.drop(idx + 1).toInt + 1).toString + "-SNAPSHOT") + else None + } + .getOrElse("0.0.1-SNAPSHOT") + Some(versionOrEmpty) + .filter(_.nonEmpty) + .getOrElse(state.format()) + } else + state + .lastTag + .getOrElse(state.format()) + .stripPrefix("v") +} + +def ghOrg = "scala-cli" +def ghName = "java-class-name" +trait JavaClassNamePublishModule extends PublishModule { + import mill.scalalib.publish._ + def pomSettings = PomSettings( + description = artifactName(), + organization = "io.github.alexarchambault.scala-cli", + url = s"https://github.com/$ghOrg/$ghName", + licenses = Seq(License.`Apache-2.0`), + versionControl = VersionControl.github(ghOrg, ghName), + developers = Seq( + Developer( + "Gedochao", + "Piotr Chabelski", + "https://github.com/Gedochao", + None + ), + Developer( + "alexarchambault", + "Alex Archambault", + "https://github.com/alexarchambault", + None + ) + ) + ) + def publishVersion = + publishVersion0() +} + +object ci extends Module { + def publishSonatype(tasks: mill.main.Tasks[PublishModule.PublishData]) = T.command { + publishSonatype0( + data = define.Target.sequence(tasks.value)(), + log = T.ctx().log + ) + } + + private def publishSonatype0( + data: Seq[PublishModule.PublishData], + log: mill.api.Logger + ): Unit = { + + val credentials = sys.env("SONATYPE_USERNAME") + ":" + sys.env("SONATYPE_PASSWORD") + val pgpPassword = sys.env("PGP_PASSWORD") + val timeout = 10.minutes + + val artifacts = data.map { case PublishModule.PublishData(a, s) => + (s.map { case (p, f) => (p.path, f) }, a) + } + + val isRelease = { + val versions = artifacts.map(_._2.version).toSet + val set = versions.map(!_.endsWith("-SNAPSHOT")) + assert( + set.size == 1, + s"Found both snapshot and non-snapshot versions: ${versions.toVector.sorted.mkString(", ")}" + ) + set.head + } + val publisher = new scalalib.publish.SonatypePublisher( + uri = "https://s01.oss.sonatype.org/service/local", + snapshotUri = "https://s01.oss.sonatype.org/content/repositories/snapshots", + credentials = credentials, + signed = true, + // format: off + gpgArgs = Seq( + "--detach-sign", + "--batch=true", + "--yes", + "--pinentry-mode", "loopback", + "--passphrase", pgpPassword, + "--armor", + "--use-agent" + ), + // format: on + readTimeout = timeout.toMillis.toInt, + connectTimeout = timeout.toMillis.toInt, + log = log, + awaitTimeout = timeout.toMillis.toInt, + stagingRelease = isRelease + ) + + publisher.publishAll(isRelease, artifacts: _*) + } + + def upload(directory: String = "artifacts/") = T.command { + val version = publishVersion0() + + val path = os.Path(directory, os.pwd) + val launchers = os.list(path).filter(os.isFile(_)).map { path => + path -> path.last + } + val ghToken = Option(System.getenv("UPLOAD_GH_TOKEN")).getOrElse { + sys.error("UPLOAD_GH_TOKEN not set") + } + val (tag, overwriteAssets) = + if (version.endsWith("-SNAPSHOT")) ("launchers", true) + else ("v" + version, false) + + Upload.upload("scala-cli", "java-class-name", ghToken, tag, dryRun = false, overwrite = overwriteAssets)(launchers: _*) + } +} diff --git a/java-class-name-tests/test/src/scala/cli/javaclassname/JavaClassNameTests.scala b/java-class-name-tests/test/src/scala/cli/javaclassname/JavaClassNameTests.scala new file mode 100644 index 0000000..4978aeb --- /dev/null +++ b/java-class-name-tests/test/src/scala/cli/javaclassname/JavaClassNameTests.scala @@ -0,0 +1,40 @@ +package scala.cli.javaclassname + +import utest._ + +object JavaClassNameTests extends TestSuite { + + val launcher = Option(System.getenv("JAVA_CLASS_NAME_CLI")) + .map(os.Path(_, os.pwd)) + .getOrElse { + sys.error("JAVA_CLASS_NAME_CLI not set") + } + + val tests = Tests { + test("simple") { + val expectedClassName = "Foo" + val content = + s"""package a.b.c; + | + |public class $expectedClassName { + | private int n = 2; + | public String getThing() { + | return "a"; + | } + |} + |""".stripMargin + val tmpDir = os.temp.dir() + try { + os.write(tmpDir / "Foo.java", content) + val res = os.proc(launcher, "Foo.java") + .call(cwd = tmpDir) + val className = res.out.text().trim + assert(className == expectedClassName) + } + finally { + os.remove.all(tmpDir) + } + } + } + +} diff --git a/java-class-name/resources/META-INF/native-image/org.virtuslab.scala-cli/java-class-name/reflect-config.json b/java-class-name/resources/META-INF/native-image/org.virtuslab.scala-cli/java-class-name/reflect-config.json new file mode 100644 index 0000000..6719e45 --- /dev/null +++ b/java-class-name/resources/META-INF/native-image/org.virtuslab.scala-cli/java-class-name/reflect-config.json @@ -0,0 +1,9 @@ +[ + { + "name": "sun.misc.Unsafe", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + } +] diff --git a/java-class-name/src/scala/cli/javaclassname/JavaClassName.scala b/java-class-name/src/scala/cli/javaclassname/JavaClassName.scala new file mode 100644 index 0000000..c383c3a --- /dev/null +++ b/java-class-name/src/scala/cli/javaclassname/JavaClassName.scala @@ -0,0 +1,32 @@ +package scala.cli.javaclassname + +import java.nio.file.{Files, Paths} + +object JavaClassName { + + private def printHelp(): Unit = { + System.err.println( + """Extracts class names out of Java sources + | + |Usage: java-class-name java-source-path + |""".stripMargin + ) + } + + def main(args: Array[String]): Unit = { + val p = args match { + case Array("--help" | "-h" | "-help") => + printHelp() + sys.exit(0) + case Array(path) => + Paths.get(path) + case _ => + printHelp() + sys.exit(1) + } + val content = Files.readAllBytes(p) + val classNameOpt = JavaParser.parseRootPublicClassName(content) + for (className <- classNameOpt) + println(className) + } +} diff --git a/java-class-name/src/scala/cli/javaclassname/JavaParser.scala b/java-class-name/src/scala/cli/javaclassname/JavaParser.scala new file mode 100644 index 0000000..cd50077 --- /dev/null +++ b/java-class-name/src/scala/cli/javaclassname/JavaParser.scala @@ -0,0 +1,39 @@ +package scala.cli.javaclassname + +import dotty.tools.dotc.ast.{Trees, untpd} +import dotty.tools.dotc.core.Contexts.{Context, ContextBase} +import dotty.tools.dotc.parsing.JavaParsers.OutlineJavaParser +import dotty.tools.dotc.util.SourceFile +import dotty.tools.io.VirtualFile +import dotty.tools.dotc.ast.untpd.{ModuleDef, PackageDef, Tree, TypeDef} +import dotty.tools.dotc.core.Symbols.ClassSymbol +import dotty.tools.dotc.core.{SymbolLoaders, Flags} +import dotty.tools.dotc.ast.untpd.Modifiers +import scala.io.Codec + +object JavaParser { + private def parseOutline(byteContent: Array[Byte]): untpd.Tree = { + given Context = ContextBase().initialCtx.fresh + val virtualFile = VirtualFile("placeholder.java", byteContent) + val sourceFile = SourceFile(virtualFile, Codec.UTF8) + val outlineParser = OutlineJavaParser(sourceFile) + outlineParser.parse() + } + + extension(mdef: untpd.DefTree) { + def nonPackagePrivate: Boolean = mdef.mods.privateWithin.toTermName.toString != "" + def isPrivate: Boolean = mdef.mods.flags.is(Flags.Private) + def isProtected: Boolean = mdef.mods.flags.is(Flags.Protected) + } + + def parseRootPublicClassName(byteContent: Array[Byte]): Option[String] = + Option(parseOutline(byteContent)) + .flatMap { + case pd: Trees.PackageDef[_] => Some(pd.stats) + case _ => None + } + .flatMap(_.collectFirst { + case mdef: ModuleDef if mdef.nonPackagePrivate && !mdef.isPrivate && !mdef.isProtected => + mdef.name.toString + }) +} diff --git a/mill b/mill new file mode 100755 index 0000000..62e5e18 --- /dev/null +++ b/mill @@ -0,0 +1,171 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Project page: https://github.com/lefou/millw +# Script Version: 0.4.2 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + + +DEFAULT_MILL_VERSION=0.10.0 + +set -e + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" + fi +fi + +if [ -n "${XDG_CACHE_HOME}" ] ; then + MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download" +else + MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" + +try_to_use_system_mill() { + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return + fi + + UNIVERSAL_SCRIPT_MAGIC="@ 2>/dev/null # 2>nul & echo off & goto BOF" + + if ! head -c 128 "${MILL_IN_PATH}" | grep -qF "${UNIVERSAL_SCRIPT_MAGIC}"; then + if [ -n "${MILLW_VERBOSE}" ]; then + echo "Could not determine mill version of ${MILL_IN_PATH}, as it does not start with the universal script magic2" 1>&2 + fi + return + fi + + # Roughly the size of the universal script. + MILL_VERSION_SEARCH_RANGE="2403" + MILL_IN_PATH_VERSION=$(head -c "${MILL_VERSION_SEARCH_RANGE}" "${MILL_IN_PATH}" |\ + sed -n 's/^.*-DMILL_VERSION=\([^\s]*\) .*$/\1/p' |\ + head -n 1) + + if [ -z "${MILL_IN_PATH_VERSION}" ]; then + echo "Could not determine mill version, even though ${MILL_IN_PATH} has the universal script magic" 1>&2 + return + fi + + if [ "${MILL_IN_PATH_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${MILL_IN_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + VERSION_PREFIX="$(echo $MILL_VERSION | cut -b -4)" + case $VERSION_PREFIX in + 0.0. | 0.1. | 0.2. | 0.3. | 0.4. ) + DOWNLOAD_SUFFIX="" + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + ;; + esac + unset VERSION_PREFIX + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${MILL_REPO_URL}/releases ..." 1>&2 + MILL_VERSION_TAG=$(echo $MILL_VERSION | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_VERSION_TAG +unset MILL_REPO_URL + +exec "${MILL}" "$@" diff --git a/mill.bat b/mill.bat new file mode 100644 index 0000000..2ea5c03 --- /dev/null +++ b/mill.bat @@ -0,0 +1,115 @@ +@echo off + +rem This is a wrapper script, that automatically download mill from GitHub release pages +rem You can give the required mill version with --mill-version parameter +rem If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +rem +rem Project page: https://github.com/lefou/millw +rem Script Version: 0.4.2 +rem +rem If you want to improve this script, please also contribute your changes back! +rem +rem Licensed under the Apache License, Version 2.0 + +rem setlocal seems to be unavailable on Windows 95/98/ME +rem but I don't think we need to support them in 2019 +setlocal enabledelayedexpansion + +set "DEFAULT_MILL_VERSION=0.10.0" + +set "MILL_REPO_URL=https://github.com/com-lihaoyi/mill" + +rem %~1% removes surrounding quotes +if [%~1%]==[--mill-version] ( + rem shift command doesn't work within parentheses + if not [%~2%]==[] ( + set MILL_VERSION=%~2% + set "STRIP_VERSION_PARAMS=true" + ) else ( + echo You specified --mill-version without a version. 1>&2 + echo Please provide a version that matches one provided on 1>&2 + echo %MILL_REPO_URL%/releases 1>&2 + exit /b 1 + ) +) + +if [!MILL_VERSION!]==[] ( + if exist .mill-version ( + set /p MILL_VERSION=<.mill-version + ) +) + +if [!MILL_VERSION!]==[] ( + set MILL_VERSION=%DEFAULT_MILL_VERSION% +) + +set MILL_DOWNLOAD_PATH=%USERPROFILE%\.mill\download + +rem without bat file extension, cmd doesn't seem to be able to run it +set MILL=%MILL_DOWNLOAD_PATH%\!MILL_VERSION!.bat + +if not exist "%MILL%" ( + set VERSION_PREFIX=%MILL_VERSION:~0,4% + set DOWNLOAD_SUFFIX=-assembly + if [!VERSION_PREFIX!]==[0.0.] set DOWNLOAD_SUFFIX= + if [!VERSION_PREFIX!]==[0.1.] set DOWNLOAD_SUFFIX= + if [!VERSION_PREFIX!]==[0.2.] set DOWNLOAD_SUFFIX= + if [!VERSION_PREFIX!]==[0.3.] set DOWNLOAD_SUFFIX= + if [!VERSION_PREFIX!]==[0.4.] set DOWNLOAD_SUFFIX= + set VERSION_PREFIX= + + for /F "delims=- tokens=1" %%A in ("!MILL_VERSION!") do set MILL_VERSION_BASE=%%A + for /F "delims=- tokens=2" %%A in ("!MILL_VERSION!") do set MILL_VERSION_MILESTONE=%%A + set VERSION_MILESTONE_START=!MILL_VERSION_MILESTONE:~0,1! + if [!VERSION_MILESTONE_START!]==[M] ( + set MILL_VERSION_TAG="!MILL_VERSION_BASE!-!MILL_VERSION_MILESTONE!" + ) else ( + set MILL_VERSION_TAG=!MILL_VERSION_BASE! + ) + + rem there seems to be no way to generate a unique temporary file path (on native Windows) + set DOWNLOAD_FILE=%MILL%.tmp + + set DOWNLOAD_URL=%MILL_REPO_URL%/releases/download/!MILL_VERSION_TAG!/!MILL_VERSION!!DOWNLOAD_SUFFIX! + + echo Downloading mill %MILL_VERSION% from %MILL_REPO_URL%/releases ... 1>&2 + + if not exist "%MILL_DOWNLOAD_PATH%" mkdir "%MILL_DOWNLOAD_PATH%" + rem curl is bundled with recent Windows 10 + rem but I don't think we can expect all the users to have it in 2019 + where /Q curl + if %ERRORLEVEL% EQU 0 ( + curl -f -L "!DOWNLOAD_URL!" -o "!DOWNLOAD_FILE!" + ) else ( + rem bitsadmin seems to be available on Windows 7 + rem without /dynamic, github returns 403 + rem bitsadmin is sometimes needlessly slow but it looks better with /priority foreground + bitsadmin /transfer millDownloadJob /dynamic /priority foreground "!DOWNLOAD_URL!" "!DOWNLOAD_FILE!" + ) + if not exist "!DOWNLOAD_FILE!" ( + echo Could not download mill %MILL_VERSION% 1>&2 + exit /b 1 + ) + + move /y "!DOWNLOAD_FILE!" "%MILL%" + + set DOWNLOAD_FILE= + set DOWNLOAD_SUFFIX= +) + +set MILL_DOWNLOAD_PATH= +set MILL_VERSION= +set MILL_REPO_URL= + +set MILL_PARAMS=%* + +if defined STRIP_VERSION_PARAMS ( + for /f "tokens=1-2*" %%a in ("%*") do ( + rem strip %%a - It's the "--mill-version" option. + rem strip %%b - it's the version number that comes after the option. + rem keep %%c - It's the remaining options. + set MILL_PARAMS=%%c + ) +) + +"%MILL%" %MILL_PARAMS% diff --git a/musl-image/Dockerfile b/musl-image/Dockerfile new file mode 100644 index 0000000..6a0416c --- /dev/null +++ b/musl-image/Dockerfile @@ -0,0 +1,4 @@ +# copied from https://github.com/VirtusLab/scala-cli/blob/b73b3e612eeba09c3231da9a51720cb8ddff1874/project/musl-image/Dockerfile +FROM messense/rust-musl-cross@sha256:47a3721b3e186abfd705feb1e03bf1d5212357ea26762cceef11530e0a2f2c7c +ADD setup.sh /setup.sh +RUN /setup.sh diff --git a/musl-image/setup.sh b/musl-image/setup.sh new file mode 100755 index 0000000..217e05f --- /dev/null +++ b/musl-image/setup.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +# copied from https://github.com/VirtusLab/scala-cli/blob/b73b3e612eeba09c3231da9a51720cb8ddff1874/project/musl-image/setup.sh + +cd /usr/local/musl/bin + +for i in x86_64-unknown-linux-musl-*; do + dest="$(echo "$i" | sed 's/-unknown//')" + ln -s "$i" "$dest" +done diff --git a/scala3-graal-processor/src/CoursierCacheProcessor.scala b/scala3-graal-processor/src/CoursierCacheProcessor.scala new file mode 100644 index 0000000..ad34e1a --- /dev/null +++ b/scala3-graal-processor/src/CoursierCacheProcessor.scala @@ -0,0 +1,18 @@ +// copied from https://github.com/VirtusLab/scala-cli/blob/b19086697401827a6f8185040ceb248d8865bf21/modules/scala3-graal-processor/src/scala/cli/graal/CoursierCacheProcessor.scala +// remove once the scala3-graal-processor module of Scala CLI is published, and can be used from here + +package scala.cli.graal + +import java.io.File +import java.nio.channels.FileChannel + +object CoursierCacheProcessor { + def main(args: Array[String]) = { + val List(cacheDir, classpath) = args.toList + val cache = DirCache(os.Path(cacheDir, os.pwd)) + + val newCp = BytecodeProcessor.processClassPath(classpath, cache).map(_.nioPath) + + println(newCp.mkString(File.pathSeparator)) + } +}