mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 01:21:33 +03:00
9182f91e35
* Use moduleDependencies instead of modulePath * Fix compilation of editions * Fix compilation of distribution-manager * polyglot-api needs to explicitly compile module-info * Fix compilationOrder in library-manager and edition-updater * engine-runner-common is module * JPMSPlugin provides default implementation of compileModuleInfo * Remove unused setting key from JPMSUtils.compileModuleInfo * JPMSPlugin has internalModuleDependencies and exportedModule tasks. * Use BuildVersion instead of buildInfo * Manual compilation of module-info.java is reported as warning * Define org.enso.scalalibs.wrapper meta project. * Fix module check in JPMSPlugin. This is a fix for projects that declare `Compile /exportJars := true` * version-output is a module * ydoc-server uses internalModuleDependencies * persistance is module * engine-common uses internalModuleDependencies * polyglot-api does not override compileModuleInfo task * runtime-parser uses internalModuleDependencies * edition-updater is module * Update moduleDependencies for distribution-manager * editions is module * Fix some dependencies of modules * scala-yaml is a module * Add scala-compiler to scala-libs-wrapper * cli depends on scala-library module * Add dependencies for distribution-manager module * Add some scala-library dependencies in some modules * engine-runner uses internalModuleDependencies * Fix module dependencies of library-manager * Rename org.enso.scalalibs.wrapper to org.enso.scala.wrapper * Add jsoniter-scala-macros to org.enso.scala.wrapper fat module * Fix dependencies of some projects * polyglot-api does not depend on truffle-api * Fix dependencies of some projects * runtime does not use com.google.common * runtime is a module * text-buffer is a module * refactoring-utils is a module * runtime-compiler is a module * runtime-instrument-common is a module * connected-lock-manager is a module * JPMSUtils reports project name in some error messages * Modularize some instruments * module-info compilation is cached * runtime-instrument-runtime-server is module * runtime-language-epb is module * Remove runtime-fat-jar * engine-runner is not a fat jar * JPMSPlugin defines exportedModuleBin task * Redefine componentModulesPaths task * interpreter-dsl is module * Redefine componentModulesPaths task * fmt sbt * scala-libs-wrapper is a modular fat jar * Add some module deps to org.enso.runtime * engine-runner is not a fat jar * Rename package in logging-config * Rename package in logging-service * Rename package in logging-service-logback * Fix dependencies of exportedModuleBin task * Mixed projects have own compileJava task this task does not compile only module-info.java but all the java sources. So that we can see errors more easily. When only module-info.java is compiled, the only errors that we can see are that we did not include some modules on module-path. * Fix definition of exportedModule task. * Remove usages of non-existing buildInfo and replace it with BuildVersion * Fix some dependencies of org.enso.runtime module * module-info compilation is handled directly by FrgaalCompiler * module-info compilation is forced for projects that has only Scala sources with single module-info.java * Fix compilation of org.enso.runtime * manual module-info compilation is not a warning * Rename packages in logging-utils-akka * Create org.enso.language.server.deps.wrapper module * language-server is module * Creat akka-wrapper modular fat jar * fmt * Define common settings for modularFatJarWrapper * Fix compilation of json-rpc-server * Use akka and zio wrappers * language-server depends on org.eclipse.jgit * Fix some dependencies - update library manifests works now! * update library manifests invokes runner directly * buildEngineDistribution does not copy runner.jar * Remove EngineRunnerBootLoader * Fix compilation of std libs * --patch-module and --add-exports are also passed to javac * Rename package in runtime-integration-tests. The package name org.enso.compiler clashes with the package from the module * Remove usage of buildInfo * FrgaalJavaCompiler can deal with non-existing module-info.java when shouldCompileModuleInfo is true. It just generates a warning in such case as it suggests that there is something wrong with the project configuration. * Revert AliasAnalysisTest.scala * Fix dependencies and java cmdline options for runtime-integration-tests * Rename test package * runtime-integration-test depends on logging-service-logback/Test/compile * Rename package in logging-service-logback/Test * Fix FrgaalJavaCompiler creation for projects * Sanitize Test/javaOptions arguments * Sanitize Test/javaOptions arguments * All the JPMSPlugin settings are scoped * Remove unused sbt tasks * modularFatJarWrapperSettings do not override javacOptions * Resolve issue "Cannot find TestLoggerProvider" in runtime-integration-tests * org.enso.runtime module is open * Test that test classes are unconditionally opened and exported * polyglot-api-macros is a module * JPMSPlugin handles --add-opens cmdline option * RuntimeServerTest ensures instruments are initialized * Add some exports to org.enso.runtime.compiler * Add instruments on module-path to runtime-integration-tests * Replace TestLogProviderOnClassPath with TestLogProviderOnModulePath * Replace buildInfo with BuildVersion * Add jpms-wrapper-scalatest * ReportLogsOnFailure is in non-modular testkit project * Add necessary dependencies to testkit project * Revert "Add jpms-wrapper-scalatest" This reverts commit732b3427a2
. * modularize filewatcher and wrap its dependencies * Initial fix for language-server/test * frgaal compiler setting are scoped for Compile and Test * Rename package in language-server/test * Exclude com.sun.jna from wrapper jars * Rename package in library-manager-test * testkit is an automatic module * process-utils is module * akka-wrapper contains akka-http * Some fixes for library-manager-test * Fix dependencies for akka-wrapper * scala-libs-wrapper exports shapeless * lang server deps wrapper exports pureconfig * json-rpc-server requires org.slf4j * Add some dependencies * lang server deps wrapper exports pureconfig.generic * language server test requires bouncycastle provider * language server depends on cli * directory-watcher wrapper requires org.slf4j * WatcherAdapter logs unsuccessful initialization errors * Fix error reporting in WatcherAdapter * Fix rest of the language-server tests * language-server-deps-wrapper depends on scala-libs-wrapper * Fix rest of the language-server tests * Missing module-info.class in an internal project is a warning, not an error * Rename jpms-methvin-directory-watcher-wrapper to a simpler name * compileOrder has to be specified before libraryDependencies * exclude module-info.java from polyglot-api-macros * Remove temporary logging in customFrgaalCompilerSettings * Fix compilation of logging-service-logback * Fix compilation of runtime-benchmarks * Fix runtime-benchmarks/run * HostClassLoader delegates to org.graalvm.polyglot class loader if org.enso.runtime is not on boot layer * org.enso.runtime.lnaguage.epb module must be opened to allow it to be used by annnotation processor * fmt * Fix afetr merge * Add module deps after merge * Print stack trace of the uncaught exception from the annotation processor * Remove akka-actor-typed from akka-wrapper * runtime-instrument-common depends on slf4j * Fix module-path for runtime-instrument-repl-debugger * runtime-benchmarks depends on runtime-language-arrow * --module-path is passed directly to frgaal * Fix some module-related cmd line options for std-benchmarks * Revert "--module-path is passed directly to frgaal" This reverts commitda63f66a0e
. * Avoid closing of System.err when closing Context * Avoid processing altogether when requested annotations are empty * Pass shouldNotLimitModules opt to frgaal * Pass module-path and add-modules options with -J prefix to frgaal * BenchProcessor annotation processor creates its own truffle module layer * bench-processor and benchmarks-common are modules * fmt * Fix after mege * Enable JMH annotation processor * Fix compileOrder in some projects * Insert TruffleBoundary to QualifiedName. This is a revert * Fix building of engine-runner native image * Add more deps to the native image * Force module-info compilation in instruments. This fixes some weird sbt bug * Don't run engine-runner/assembly from Rust build script * Update docs of JPMSPlugin * fmt * runtime-benchmarks depends on benchmarks-common module * Fix benchmark report writing * std-benchmarks annot processing does not take settings from runtime-benchmarks * Suppress interpreter only warning in annotation processor * Runtime version manager does not expect runtime.jar fat jar * fmt * Fix module entry point * Move some polyglot tests to runtime-integration-tests. Also make their output silent * pkg has no dependency on org.graalvm.truffle * Fix compiler dependencies test * Rename all runtime.jar in fake releases * Add language-server with dependencies to component dir * No module-info.class in target dir is warning not error * language-server does not depend on netbeans lookup uitl * Declare LanguageServerApi service provider in module-info * connected-lock-manager-server is JPMS module * task-progress-notifications is module * Add fansi-wrapper module * Fix compilation of connected-lock-manager-server * Define correct Test/internalModuleDependencies for project-manager * fmt * Fix LauncherRunnerSpec - no runtime.jar * Add fansi-wrapper to runtime-integration-tests and runtime-benchmarks * Fix engine-runner native image build * Use newer JNA version - fixes running of hyperd * DRY * scala-compiler DRY * fmt * More build.sbt refactoring * Include runtime-instrument-id-execution in engine-runner native image * TruffleBoundary for QualifiedName.toString * Finding a needle in a haystack 🤦 * More scala-library DRY * more mixed-java/scala goodies * Fix compilation of syntax-rust-definition * Test that engine-runner does not depend on language-server * Append rather than assign `moduleDependencies` `++=` is less error prone than `:=`. Also discovered some unnecessary dependencies. * Replace : with File.pathSeparator * [WIP] Make logging in ProjectService more verbose * language-server/test didn't start because of missing lookup and fansi modules * Formatting * org.enso.cli.task.notifications needs Akka and Circe to link * project-manager/test depends on buildEngineDistribution * [WIP] Even more verbose logging for creating projects * [WIP] Even more verbose logging for creating projects * Revert "[WIP] Even more verbose logging for creating projects" This reverts commita7067c8472
. * Revert "[WIP] Even more verbose logging for creating projects" This reverts commitfc6f53d4f1
. * Revert "[WIP] Make logging in ProjectService more verbose" This reverts commit427428e142
. * All the project with JPMSPlugin has stripped artifact names * Revert all placeholder fake release components to runtime.jar without version * Eliminate a cross version hack We shouldn't be specifying Scala dependencies with a Scala cross version in the suffix. * Address SBT lint warnings * Revert "Eliminate a cross version hack" This reverts commit8861dab288
. * logging-service-logback is mixedJavaScalaProject * fmt * Stripped artifact name contains classifier. This fixes tests as those were named like `artifact-tests.jar`. * Don't use LocalProject unless really needed * Add more logging when BenchProcessor fails * logging-service-logback is not mixed project * Work with java.io.File.getPath to avoid mixing slash and backslash on Windows * Reapply "Eliminate a cross version hack" This reverts commitedaa436ee8
. * Pass scalaBinaryVersion correctly * Remove scala-compiler from the distribution * Fix IllegalAccessErrors from serde * typos * License review * fmt * Move testLogProviderOnModulePath to TestJPMSConfiguration * logging-service-logback is not a mixed project --------- Co-authored-by: Jaroslav Tulach <jaroslav.tulach@enso.org> Co-authored-by: Hubert Plociniczak <hubert.plociniczak@gmail.com>
1058 lines
32 KiB
Scala
1058 lines
32 KiB
Scala
import io.circe.yaml
|
|
import io.circe.syntax._
|
|
import org.apache.commons.io.IOUtils
|
|
import sbt.internal.util.ManagedLogger
|
|
import sbt._
|
|
import sbt.io.syntax.fileToRichFile
|
|
import sbt.util.{CacheStore, CacheStoreFactory, FileInfo, Tracked}
|
|
|
|
import scala.sys.process._
|
|
import org.enso.build.WithDebugCommand
|
|
|
|
import java.nio.file.Paths
|
|
import scala.util.Try
|
|
|
|
object DistributionPackage {
|
|
|
|
/** File extensions. */
|
|
implicit class FileExtensions(file: File) {
|
|
|
|
/** Get the outermost directory of this file. For absolute paths this
|
|
* function always returns root.
|
|
*
|
|
* == Example ==
|
|
* Get top directory of the relative path.
|
|
* {{{
|
|
* file("foo/bar/baz").getTopDirectory == file("foo")
|
|
* }}}
|
|
*
|
|
* Get top directory of the absolute path.
|
|
* {{{
|
|
* file(/foo/bar/baz").getTopDirectory == file("/")
|
|
* }}}
|
|
*
|
|
* @return the outermost directory of this file.
|
|
*/
|
|
def getTopDirectory: File = {
|
|
@scala.annotation.tailrec
|
|
def go(path: File): File = {
|
|
val parent = path.getParentFile
|
|
if (parent == null) path else go(parent)
|
|
}
|
|
go(file)
|
|
}
|
|
}
|
|
|
|
/** Conditional copying, based on the contents of cache and timestamps of files.
|
|
*
|
|
* @param source source directory
|
|
* @param destination target directory
|
|
* @param cache cache used for persisting the cached information
|
|
* @return true, if copying was necessary, false if no change was detected between the directories
|
|
*/
|
|
def copyDirectoryIncremental(
|
|
source: File,
|
|
destination: File,
|
|
cache: CacheStore
|
|
): Boolean = {
|
|
val allFiles = source.allPaths.get().toSet
|
|
Tracked.diffInputs(cache, FileInfo.lastModified)(allFiles) { diff =>
|
|
val missing = diff.unmodified.exists { f =>
|
|
val relativePath = f.relativeTo(source).get
|
|
val destinationFile =
|
|
destination.toPath.resolve(relativePath.toPath).toFile
|
|
!destinationFile.exists()
|
|
}
|
|
|
|
if (diff.modified.nonEmpty || diff.removed.nonEmpty || missing) {
|
|
IO.delete(destination)
|
|
IO.copyDirectory(source, destination)
|
|
true
|
|
} else false
|
|
}
|
|
}
|
|
|
|
def copyFilesIncremental(
|
|
sources: Seq[File],
|
|
destinationDirectory: File,
|
|
cache: CacheStore
|
|
): Unit = {
|
|
val allFiles = sources.toSet
|
|
IO.createDirectory(destinationDirectory)
|
|
Tracked.diffInputs(cache, FileInfo.lastModified)(allFiles) { diff =>
|
|
for (f <- diff.removed) {
|
|
IO.delete(destinationDirectory / f.getName)
|
|
}
|
|
for (f <- diff.modified -- diff.removed) {
|
|
IO.copyFile(f, destinationDirectory / f.getName)
|
|
}
|
|
for (f <- diff.unmodified) {
|
|
val destinationFile = destinationDirectory / f.getName
|
|
if (!destinationFile.exists()) {
|
|
IO.copyFile(f, destinationDirectory / f.getName)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def executableName(baseName: String): String =
|
|
if (Platform.isWindows) baseName + ".exe" else baseName
|
|
|
|
private def batName(baseName: String): String =
|
|
if (Platform.isWindows) baseName + ".bat" else baseName
|
|
|
|
def createProjectManagerPackage(
|
|
distributionRoot: File,
|
|
cacheFactory: CacheStoreFactory
|
|
): Unit = {
|
|
copyDirectoryIncremental(
|
|
file("distribution/project-manager/THIRD-PARTY"),
|
|
distributionRoot / "THIRD-PARTY",
|
|
cacheFactory.make("project-manager-third-party")
|
|
)
|
|
|
|
copyFilesIncremental(
|
|
Seq(file(executableName("project-manager"))),
|
|
distributionRoot / "bin",
|
|
cacheFactory.make("project-manager-exe")
|
|
)
|
|
}
|
|
|
|
def createEnginePackage(
|
|
distributionRoot: File,
|
|
cacheFactory: CacheStoreFactory,
|
|
log: Logger,
|
|
jarModulesToCopy: Seq[File],
|
|
graalVersion: String,
|
|
javaVersion: String,
|
|
ensoVersion: String,
|
|
editionName: String,
|
|
sourceStdlibVersion: String,
|
|
targetStdlibVersion: String,
|
|
targetDir: File,
|
|
generateIndex: Boolean
|
|
): Unit = {
|
|
copyDirectoryIncremental(
|
|
file("distribution/engine/THIRD-PARTY"),
|
|
distributionRoot / "THIRD-PARTY",
|
|
cacheFactory.make("engine-third-party")
|
|
)
|
|
|
|
copyFilesIncremental(
|
|
jarModulesToCopy,
|
|
distributionRoot / "component",
|
|
cacheFactory.make("module jars")
|
|
)
|
|
|
|
val parser = targetDir / Platform.dynamicLibraryFileName("enso_parser")
|
|
copyFilesIncremental(
|
|
Seq(parser),
|
|
distributionRoot / "component",
|
|
cacheFactory.make("engine-parser-library")
|
|
)
|
|
|
|
(distributionRoot / "editions").mkdirs()
|
|
Editions.writeEditionConfig(
|
|
editionsRoot = distributionRoot / "editions",
|
|
ensoVersion = ensoVersion,
|
|
editionName = editionName,
|
|
libraryVersion = targetStdlibVersion,
|
|
log = log
|
|
)
|
|
|
|
copyLibraryCacheIncremental(
|
|
sourceRoot = file("distribution/lib"),
|
|
destinationRoot = distributionRoot / "lib",
|
|
sourceVersion = sourceStdlibVersion,
|
|
targetVersion = targetStdlibVersion,
|
|
cacheFactory = cacheFactory.sub("engine-libraries"),
|
|
log = log
|
|
)
|
|
|
|
copyDirectoryIncremental(
|
|
file("distribution/bin"),
|
|
distributionRoot / "bin",
|
|
cacheFactory.make("engine-bin")
|
|
)
|
|
|
|
buildEngineManifest(
|
|
template = file("distribution/manifest.template.yaml"),
|
|
destination = distributionRoot / "manifest.yaml",
|
|
graalVersion = graalVersion,
|
|
javaVersion = javaVersion
|
|
)
|
|
|
|
if (generateIndex) {
|
|
indexStdLibs(
|
|
stdLibVersion = targetStdlibVersion,
|
|
ensoVersion = ensoVersion,
|
|
stdLibRoot = distributionRoot / "lib",
|
|
ensoExecutable = distributionRoot / "bin" / "enso",
|
|
cacheFactory = cacheFactory.sub("stdlib"),
|
|
log = log
|
|
)
|
|
}
|
|
}
|
|
|
|
def indexStdLibs(
|
|
stdLibVersion: String,
|
|
ensoVersion: String,
|
|
stdLibRoot: File,
|
|
ensoExecutable: File,
|
|
cacheFactory: CacheStoreFactory,
|
|
log: Logger
|
|
): Unit = {
|
|
for {
|
|
libMajor <- stdLibRoot.listFiles()
|
|
libName <- (stdLibRoot / libMajor.getName).listFiles()
|
|
} yield {
|
|
indexStdLib(
|
|
libName,
|
|
stdLibVersion,
|
|
ensoVersion,
|
|
ensoExecutable,
|
|
cacheFactory,
|
|
log
|
|
)
|
|
}
|
|
}
|
|
|
|
def indexStdLib(
|
|
libName: File,
|
|
stdLibVersion: String,
|
|
ensoVersion: String,
|
|
ensoExecutable: File,
|
|
cacheFactory: CacheStoreFactory,
|
|
log: Logger
|
|
): Unit = {
|
|
object FileOnlyFilter extends sbt.io.FileFilter {
|
|
def accept(arg: File): Boolean = arg.isFile
|
|
}
|
|
val cache = cacheFactory.make(s"$libName.$ensoVersion")
|
|
val path = libName / ensoVersion
|
|
Tracked.diffInputs(cache, FileInfo.lastModified)(
|
|
path.globRecursive("*.enso" && FileOnlyFilter).get().toSet
|
|
) { diff =>
|
|
if (diff.modified.nonEmpty) {
|
|
log.info(s"Generating index for $libName ")
|
|
val command = Seq(
|
|
Platform.executableFile(ensoExecutable.getAbsoluteFile),
|
|
"--no-compile-dependencies",
|
|
"--no-global-cache",
|
|
"--compile",
|
|
path.getAbsolutePath
|
|
)
|
|
log.debug(command.mkString(" "))
|
|
val runningProcess = Process(
|
|
command,
|
|
Some(path.getAbsoluteFile.getParentFile),
|
|
"JAVA_OPTS" -> "-Dorg.jline.terminal.dumb=true"
|
|
).run
|
|
// Poor man's solution to stuck index generation
|
|
val GENERATING_INDEX_TIMEOUT = 60 * 2 // 2 minutes
|
|
var current = 0
|
|
var timeout = false
|
|
while (runningProcess.isAlive() && !timeout) {
|
|
if (current > GENERATING_INDEX_TIMEOUT) {
|
|
java.lang.System.err
|
|
.println("Reached timeout when generating index. Terminating...")
|
|
try {
|
|
val pidOfProcess = pid(runningProcess)
|
|
val javaHome = System.getProperty("java.home")
|
|
val jstack =
|
|
if (javaHome == null) "jstack"
|
|
else
|
|
Paths.get(javaHome, "bin", "jstack").toAbsolutePath.toString
|
|
val in = java.lang.Runtime.getRuntime
|
|
.exec(Array(jstack, pidOfProcess.toString))
|
|
.getInputStream
|
|
|
|
System.err.println(IOUtils.toString(in, "UTF-8"))
|
|
} catch {
|
|
case e: Throwable =>
|
|
java.lang.System.err
|
|
.println("Failed to get threaddump of a stuck process", e);
|
|
} finally {
|
|
timeout = true
|
|
runningProcess.destroy()
|
|
}
|
|
} else {
|
|
Thread.sleep(1000)
|
|
current += 1
|
|
}
|
|
}
|
|
if (timeout) {
|
|
throw new RuntimeException(
|
|
s"TIMEOUT: Failed to compile $libName in $GENERATING_INDEX_TIMEOUT seconds"
|
|
)
|
|
}
|
|
if (runningProcess.exitValue() != 0) {
|
|
throw new RuntimeException(s"Cannot compile $libName.")
|
|
}
|
|
} else {
|
|
log.debug(s"No modified files. Not generating index for $libName.")
|
|
}
|
|
}
|
|
}
|
|
|
|
def runEnginePackage(
|
|
distributionRoot: File,
|
|
args: Seq[String],
|
|
log: Logger
|
|
): Boolean = {
|
|
import scala.collection.JavaConverters._
|
|
|
|
val enso = distributionRoot / "bin" / batName("enso")
|
|
val pb = new java.lang.ProcessBuilder()
|
|
val all = new java.util.ArrayList[String]()
|
|
val runArgumentIndex = locateRunArgument(args)
|
|
val runArgument = runArgumentIndex.map(args)
|
|
val disablePrivateCheck = runArgument match {
|
|
case Some(whatToRun) =>
|
|
if (whatToRun.startsWith("test/") && whatToRun.endsWith("_Tests")) {
|
|
whatToRun.contains("_Internal_")
|
|
} else {
|
|
false
|
|
}
|
|
case None => false
|
|
}
|
|
|
|
val runArgumentAsFile = runArgument.flatMap(createFileIfValidPath)
|
|
val projectDirectory = runArgumentAsFile.flatMap(findProjectRoot)
|
|
val cwdOverride: Option[File] =
|
|
projectDirectory.flatMap(findParentFile).map(_.getAbsoluteFile)
|
|
|
|
all.add(enso.getAbsolutePath)
|
|
all.addAll(args.asJava)
|
|
// Override the working directory of new process to be the parent of the project directory.
|
|
cwdOverride.foreach { c =>
|
|
pb.directory(c)
|
|
}
|
|
if (cwdOverride.isDefined) {
|
|
// If the working directory is changed, we need to translate the path - make it absolute.
|
|
all.set(runArgumentIndex.get + 1, runArgumentAsFile.get.getAbsolutePath)
|
|
}
|
|
if (args.contains("--debug")) {
|
|
all.remove("--debug")
|
|
pb.environment().put("JAVA_OPTS", "-ea " + WithDebugCommand.DEBUG_OPTION)
|
|
} else {
|
|
pb.environment().put("JAVA_OPTS", "-ea")
|
|
}
|
|
if (disablePrivateCheck) {
|
|
all.add("--disable-private-check")
|
|
}
|
|
pb.command(all)
|
|
pb.inheritIO()
|
|
log.info(s"Executing ${all.asScala.mkString(" ")}")
|
|
val p = pb.start()
|
|
val exitCode = p.waitFor()
|
|
if (exitCode != 0) {
|
|
log.warn(enso + " finished with exit code " + exitCode)
|
|
}
|
|
exitCode == 0
|
|
}
|
|
|
|
// https://stackoverflow.com/questions/23279898/get-process-id-of-scala-sys-process-process
|
|
def pid(p: Process): Long = {
|
|
val procField = p.getClass.getDeclaredField("p")
|
|
procField.synchronized {
|
|
procField.setAccessible(true)
|
|
val proc = procField.get(p)
|
|
try {
|
|
proc match {
|
|
case unixProc
|
|
if unixProc.getClass.getName == "java.lang.UNIXProcess" =>
|
|
val pidField = unixProc.getClass.getDeclaredField("pid")
|
|
pidField.synchronized {
|
|
pidField.setAccessible(true)
|
|
try {
|
|
pidField.getLong(unixProc)
|
|
} finally {
|
|
pidField.setAccessible(false)
|
|
}
|
|
}
|
|
case javaProc: java.lang.Process =>
|
|
javaProc.pid()
|
|
case other =>
|
|
throw new RuntimeException(
|
|
"Cannot get PID of a " + proc.getClass.getName
|
|
)
|
|
}
|
|
} finally {
|
|
procField.setAccessible(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns the index of the next argument after `--run`, if it exists. */
|
|
private def locateRunArgument(args: Seq[String]): Option[Int] = {
|
|
val findRun = args.indexOf("--run")
|
|
if (findRun >= 0 && findRun + 1 < args.size) {
|
|
Some(findRun + 1)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/** Returns a file, only if the provided string represented a valid path. */
|
|
private def createFileIfValidPath(path: String): Option[File] =
|
|
Try(new File(path)).toOption
|
|
|
|
/** Looks for a parent directory that contains `package.yaml`. */
|
|
private def findProjectRoot(file: File): Option[File] =
|
|
if (file.isDirectory && (file / "package.yaml").exists()) {
|
|
Some(file)
|
|
} else {
|
|
findParentFile(file).flatMap(findProjectRoot)
|
|
}
|
|
|
|
private def findParentFile(file: File): Option[File] =
|
|
Option(file.getParentFile)
|
|
|
|
def runProjectManagerPackage(
|
|
engineRoot: File,
|
|
distributionRoot: File,
|
|
args: Seq[String],
|
|
log: Logger
|
|
): Boolean = {
|
|
import scala.collection.JavaConverters._
|
|
|
|
val enso = distributionRoot / "bin" / "project-manager"
|
|
log.info(s"Executing $enso ${args.mkString(" ")}")
|
|
val pb = new java.lang.ProcessBuilder()
|
|
val all = new java.util.ArrayList[String]()
|
|
all.add(enso.getAbsolutePath())
|
|
all.addAll(args.asJava)
|
|
pb.command(all)
|
|
pb.environment().put("ENSO_ENGINE_PATH", engineRoot.toString())
|
|
pb.environment().put("ENSO_JVM_PATH", System.getProperty("java.home"))
|
|
if (args.contains("--debug")) {
|
|
all.remove("--debug")
|
|
pb.environment().put("ENSO_JVM_OPTS", WithDebugCommand.DEBUG_OPTION)
|
|
}
|
|
pb.inheritIO()
|
|
val p = pb.start()
|
|
val exitCode = p.waitFor()
|
|
if (exitCode != 0) {
|
|
log.warn(enso + " finished with exit code " + exitCode)
|
|
}
|
|
exitCode == 0
|
|
}
|
|
|
|
def fixLibraryManifest(
|
|
packageRoot: File,
|
|
targetVersion: String,
|
|
log: Logger
|
|
): Unit = {
|
|
val packageConfig = packageRoot / "package.yaml"
|
|
val originalContent = IO.read(packageConfig)
|
|
yaml.parser.parse(originalContent) match {
|
|
case Left(error) =>
|
|
log.error(s"Failed to parse $packageConfig: $error")
|
|
throw error
|
|
case Right(parsed) =>
|
|
val obj = parsed.asObject.getOrElse {
|
|
throw new IllegalStateException(s"Incorrect format of $packageConfig")
|
|
}
|
|
|
|
val key = "version"
|
|
val updated = obj.remove(key).add(key, targetVersion.asJson)
|
|
val serialized = yaml.printer.print(updated.asJson)
|
|
if (serialized == originalContent) {
|
|
log.info(
|
|
s"No need to update $packageConfig, already in correct version."
|
|
)
|
|
} else {
|
|
IO.write(packageConfig, serialized)
|
|
log.debug(s"Updated $packageConfig to $targetVersion")
|
|
}
|
|
}
|
|
}
|
|
|
|
def copyLibraryCacheIncremental(
|
|
sourceRoot: File,
|
|
destinationRoot: File,
|
|
sourceVersion: String,
|
|
targetVersion: String,
|
|
cacheFactory: CacheStoreFactory,
|
|
log: Logger
|
|
): Unit = {
|
|
val existingLibraries =
|
|
collection.mutable.ArrayBuffer.empty[(String, String)]
|
|
for (prefix <- sourceRoot.list()) {
|
|
for (libName <- (sourceRoot / prefix).list()) {
|
|
val targetPackageRoot =
|
|
destinationRoot / prefix / libName / targetVersion
|
|
copyDirectoryIncremental(
|
|
source = sourceRoot / prefix / libName / sourceVersion,
|
|
destination = targetPackageRoot,
|
|
cache = cacheFactory.make(s"$prefix.$libName")
|
|
)
|
|
fixLibraryManifest(targetPackageRoot, targetVersion, log)
|
|
existingLibraries.append((prefix, libName))
|
|
}
|
|
}
|
|
|
|
val existingLibrariesSet = existingLibraries.toSet
|
|
for (prefix <- destinationRoot.list()) {
|
|
for (libName <- (destinationRoot / prefix).list()) {
|
|
if (!existingLibrariesSet.contains((prefix, libName))) {
|
|
log.info(
|
|
s"Removing a library $prefix.$libName from the distribution, " +
|
|
s"because it does not exist in the sources anymore."
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private def buildEngineManifest(
|
|
template: File,
|
|
destination: File,
|
|
graalVersion: String,
|
|
javaVersion: String
|
|
): Unit = {
|
|
val base = IO.read(template)
|
|
val extensions =
|
|
s"""graal-vm-version: $graalVersion
|
|
|graal-java-version: $javaVersion
|
|
|""".stripMargin
|
|
IO.write(destination, base + extensions)
|
|
}
|
|
|
|
def createLauncherPackage(
|
|
distributionRoot: File,
|
|
cacheFactory: CacheStoreFactory
|
|
): Unit = {
|
|
copyDirectoryIncremental(
|
|
file("distribution/launcher/THIRD-PARTY"),
|
|
distributionRoot / "THIRD-PARTY",
|
|
cacheFactory.make("launcher-third-party")
|
|
)
|
|
|
|
copyFilesIncremental(
|
|
Seq(file(executableName("ensoup"))),
|
|
distributionRoot / "bin",
|
|
cacheFactory.make("launcher-exe")
|
|
)
|
|
|
|
IO.createDirectory(distributionRoot / "dist")
|
|
IO.createDirectory(distributionRoot / "runtime")
|
|
|
|
copyFilesIncremental(
|
|
Seq(
|
|
file("distribution/launcher/.enso.portable"),
|
|
file("distribution/launcher/README.md")
|
|
),
|
|
distributionRoot,
|
|
cacheFactory.make("launcher-rootfiles")
|
|
)
|
|
}
|
|
|
|
sealed trait OS {
|
|
def name: String
|
|
def hasSupportForSulong: Boolean
|
|
def executableName(base: String): String = base
|
|
def archiveExt: String = ".tar.gz"
|
|
def isUNIX: Boolean = true
|
|
def archs: Seq[Architecture]
|
|
}
|
|
object OS {
|
|
case object Linux extends OS {
|
|
override val name: String = "linux"
|
|
override val hasSupportForSulong: Boolean = true
|
|
override val archs = Seq(Architecture.X64)
|
|
}
|
|
trait MacOS extends OS {
|
|
override val name: String = "macos"
|
|
}
|
|
case object MacOSAmd extends MacOS {
|
|
override val hasSupportForSulong: Boolean = true
|
|
override val archs = Seq(Architecture.X64)
|
|
}
|
|
|
|
case object MacOSArm extends MacOS {
|
|
override val hasSupportForSulong: Boolean = true
|
|
override val archs = Seq(Architecture.AarchX64)
|
|
}
|
|
case object Windows extends OS {
|
|
override val name: String = "windows"
|
|
override val hasSupportForSulong: Boolean = false
|
|
override def executableName(base: String): String = base + ".exe"
|
|
override def archiveExt: String = ".zip"
|
|
override def isUNIX: Boolean = false
|
|
override val archs = Seq(Architecture.X64)
|
|
}
|
|
|
|
val platforms = Seq(Linux, MacOSArm, MacOSAmd, Windows)
|
|
|
|
def apply(name: String, arch: Option[String]): Option[OS] =
|
|
name.toLowerCase match {
|
|
case Linux.`name` => Some(Linux)
|
|
case MacOSAmd.`name` =>
|
|
arch match {
|
|
case Some(Architecture.X64.`name`) =>
|
|
Some(MacOSAmd)
|
|
case Some(Architecture.AarchX64.`name`) =>
|
|
Some(MacOSArm)
|
|
case _ =>
|
|
None
|
|
}
|
|
case MacOSArm.`name` => Some(MacOSArm)
|
|
case Windows.`name` => Some(Windows)
|
|
case _ => None
|
|
}
|
|
}
|
|
|
|
sealed trait Architecture {
|
|
def name: String
|
|
|
|
/** Name of the architecture for GraalVM releases
|
|
*/
|
|
def graalName: String
|
|
}
|
|
object Architecture {
|
|
case object X64 extends Architecture {
|
|
override val name: String = "amd64"
|
|
override def graalName: String = "x64"
|
|
}
|
|
|
|
case object AarchX64 extends Architecture {
|
|
override val name: String = "aarch64"
|
|
override def graalName: String = "x64"
|
|
}
|
|
|
|
}
|
|
|
|
/** A helper class that manages building distribution artifacts. */
|
|
class Builder(
|
|
ensoVersion: String,
|
|
graalVersion: String,
|
|
graalJavaVersion: String,
|
|
artifactRoot: File
|
|
) {
|
|
|
|
def artifactName(
|
|
component: String,
|
|
os: OS,
|
|
architecture: Architecture
|
|
): String =
|
|
s"enso-$component-$ensoVersion-${os.name}-${architecture.name}"
|
|
|
|
def graalInPackageName: String =
|
|
s"graalvm-ce-java$graalJavaVersion-$graalVersion"
|
|
|
|
private def extractZip(archive: File, root: File): Unit = {
|
|
IO.createDirectory(root)
|
|
val exitCode = Process(
|
|
Seq("unzip", "-q", archive.toPath.toAbsolutePath.normalize.toString),
|
|
cwd = Some(root)
|
|
).!
|
|
if (exitCode != 0) {
|
|
throw new RuntimeException(s"Cannot extract $archive.")
|
|
}
|
|
}
|
|
|
|
private def listZip(archive: File): Seq[File] = {
|
|
val suppressStdErr = ProcessLogger(_ => ())
|
|
val zipList = Process(
|
|
Seq("zip", "-l", archive.toPath.toAbsolutePath.normalize.toString)
|
|
)
|
|
zipList.lineStream(suppressStdErr).map(file)
|
|
}
|
|
|
|
private def extractTarGz(archive: File, root: File): Unit = {
|
|
IO.createDirectory(root)
|
|
val exitCode = Process(
|
|
Seq(
|
|
"tar",
|
|
"xf",
|
|
archive.toPath.toAbsolutePath.toString
|
|
),
|
|
cwd = Some(root)
|
|
).!
|
|
if (exitCode != 0) {
|
|
throw new RuntimeException(s"Cannot extract $archive.")
|
|
}
|
|
}
|
|
|
|
private def listTarGz(archive: File): Seq[File] = {
|
|
val suppressStdErr = ProcessLogger(_ => ())
|
|
val tarList =
|
|
Process(Seq("tar", "tf", archive.toPath.toAbsolutePath.toString))
|
|
tarList.lineStream(suppressStdErr).map(file)
|
|
}
|
|
|
|
private def extract(archive: File, root: File): Unit = {
|
|
if (archive.getName.endsWith("zip")) {
|
|
extractZip(archive, root)
|
|
} else {
|
|
extractTarGz(archive, root)
|
|
}
|
|
}
|
|
|
|
private def list(archive: File): Seq[File] = {
|
|
if (archive.getName.endsWith("zip")) {
|
|
listZip(archive)
|
|
} else {
|
|
listTarGz(archive)
|
|
}
|
|
}
|
|
|
|
private def graalArchive(os: OS, architecture: Architecture): File = {
|
|
val packageDir =
|
|
artifactRoot / s"graalvm-$graalVersion-${os.name}-${architecture.name}"
|
|
if (!packageDir.exists()) {
|
|
IO.createDirectory(packageDir)
|
|
}
|
|
val archiveName =
|
|
s"graalvm-${os.name}-${architecture.name}-$graalVersion-$graalJavaVersion"
|
|
packageDir / (archiveName + os.archiveExt)
|
|
}
|
|
|
|
private def downloadGraal(
|
|
log: ManagedLogger,
|
|
os: OS,
|
|
architecture: Architecture
|
|
): File = {
|
|
val archive = graalArchive(os, architecture)
|
|
if (!archive.exists()) {
|
|
log.info(
|
|
s"Downloading GraalVM $graalVersion Java $graalJavaVersion " +
|
|
s"for $os $architecture"
|
|
)
|
|
val graalUrl =
|
|
s"https://github.com/graalvm/graalvm-ce-builds/releases/download/" +
|
|
s"jdk-$graalJavaVersion/" +
|
|
s"graalvm-community-jdk-${graalJavaVersion}_${os.name}-" +
|
|
s"${architecture.graalName}_bin${os.archiveExt}"
|
|
val exitCode = (url(graalUrl) #> archive).!
|
|
if (exitCode != 0) {
|
|
throw new RuntimeException(s"Graal download from $graalUrl failed.")
|
|
}
|
|
}
|
|
|
|
archive
|
|
}
|
|
|
|
private def copyGraal(
|
|
os: OS,
|
|
architecture: Architecture,
|
|
runtimeDir: File
|
|
): Unit = {
|
|
val archive = graalArchive(os, architecture)
|
|
extract(archive, runtimeDir)
|
|
}
|
|
|
|
/** Prepare the GraalVM package.
|
|
*
|
|
* @param log the logger
|
|
* @param os the system type
|
|
* @param architecture the architecture type
|
|
* @return the path to the created GraalVM package
|
|
*/
|
|
def createGraalPackage(
|
|
log: ManagedLogger,
|
|
os: OS,
|
|
architecture: Architecture
|
|
): File = {
|
|
log.info("Building GraalVM distribution")
|
|
val archive = downloadGraal(log, os, architecture)
|
|
|
|
if (os.hasSupportForSulong) {
|
|
log.info("Building GraalVM distribution2")
|
|
val packageDir = archive.getParentFile
|
|
val archiveRootDir = list(archive).head.getTopDirectory.getName
|
|
val extractedGraalDir0 = packageDir / archiveRootDir
|
|
val graalRuntimeDir =
|
|
s"graalvm-ce-java${graalJavaVersion}-${graalVersion}"
|
|
val extractedGraalDir = packageDir / graalRuntimeDir
|
|
|
|
if (extractedGraalDir0.exists()) {
|
|
IO.delete(extractedGraalDir0)
|
|
}
|
|
if (extractedGraalDir.exists()) {
|
|
IO.delete(extractedGraalDir)
|
|
}
|
|
|
|
log.info(s"Extracting $archive to $packageDir")
|
|
extract(archive, packageDir)
|
|
|
|
if (extractedGraalDir0 != extractedGraalDir) {
|
|
log.info(s"Standardizing GraalVM directory name")
|
|
IO.move(extractedGraalDir0, extractedGraalDir)
|
|
}
|
|
|
|
log.info("Installing components")
|
|
gu(log, os, extractedGraalDir, "install", "python")
|
|
|
|
log.info(s"Re-creating $archive")
|
|
IO.delete(archive)
|
|
makeArchive(packageDir, graalRuntimeDir, archive)
|
|
|
|
log.info(s"Cleaning up $extractedGraalDir")
|
|
IO.delete(extractedGraalDir)
|
|
}
|
|
archive
|
|
}
|
|
|
|
/** Run the `gu` executable from the GraalVM distribution.
|
|
*
|
|
* @param log the logger
|
|
* @param os the system type
|
|
* @param graalDir the directory with a GraalVM distribution
|
|
* @param arguments the command arguments
|
|
* @return Stdout from the `gu` command.
|
|
*/
|
|
def gu(
|
|
log: ManagedLogger,
|
|
os: OS,
|
|
graalDir: File,
|
|
arguments: String*
|
|
): String = {
|
|
val shallowFile = graalDir / "bin" / "gu"
|
|
val deepFile = graalDir / "Contents" / "Home" / "bin" / "gu"
|
|
val executableFile = os match {
|
|
case OS.Linux =>
|
|
shallowFile
|
|
case _: OS.MacOS =>
|
|
if (deepFile.exists) {
|
|
deepFile
|
|
} else {
|
|
shallowFile
|
|
}
|
|
case OS.Windows =>
|
|
graalDir / "bin" / "gu.cmd"
|
|
}
|
|
val javaHomeFile = executableFile.getParentFile.getParentFile
|
|
val javaHome = javaHomeFile.toPath.toAbsolutePath
|
|
val command =
|
|
executableFile.toPath.toAbsolutePath.toString +: arguments
|
|
|
|
log.debug(
|
|
s"Running $command in $graalDir with JAVA_HOME=${javaHome.toString}"
|
|
)
|
|
|
|
try {
|
|
Process(
|
|
command,
|
|
Some(graalDir),
|
|
("JAVA_HOME", javaHome.toString),
|
|
("GRAALVM_HOME", javaHome.toString)
|
|
).!!
|
|
} catch {
|
|
case _: RuntimeException =>
|
|
throw new RuntimeException(
|
|
s"Failed to run '${command.mkString(" ")}'"
|
|
)
|
|
}
|
|
}
|
|
|
|
def copyEngine(os: OS, architecture: Architecture, distDir: File): Unit = {
|
|
val engine = builtArtifact("engine", os, architecture)
|
|
if (!engine.exists()) {
|
|
throw new IllegalStateException(
|
|
s"Cannot create bundle for $os / $architecture because corresponding " +
|
|
s"engine has not been built."
|
|
)
|
|
}
|
|
|
|
IO.copyDirectory(engine / s"enso-$ensoVersion", distDir / ensoVersion)
|
|
}
|
|
|
|
def makeExecutable(file: File): Unit = {
|
|
val ownerOnly = false
|
|
file.setExecutable(true, ownerOnly)
|
|
}
|
|
|
|
def fixLauncher(root: File, os: OS): Unit = {
|
|
makeExecutable(root / "enso" / "bin" / os.executableName("enso"))
|
|
IO.createDirectories(
|
|
Seq("dist", "config", "runtime").map(root / "enso" / _)
|
|
)
|
|
}
|
|
|
|
def makeArchive(root: File, rootDir: String, target: File): Unit = {
|
|
val exitCode = if (target.getName.endsWith("zip")) {
|
|
Process(
|
|
Seq(
|
|
"zip",
|
|
"-9",
|
|
"-q",
|
|
"-r",
|
|
target.toPath.toAbsolutePath.normalize.toString,
|
|
rootDir
|
|
),
|
|
cwd = Some(root)
|
|
).!
|
|
} else {
|
|
Process(
|
|
Seq(
|
|
"tar",
|
|
"--use-compress-program=gzip -9",
|
|
"-cf",
|
|
target.toPath.toAbsolutePath.normalize.toString,
|
|
rootDir
|
|
),
|
|
cwd = Some(root)
|
|
).!
|
|
}
|
|
if (exitCode != 0) {
|
|
throw new RuntimeException(s"Failed to create archive $target")
|
|
}
|
|
}
|
|
|
|
/** Path to an arbitrary built artifact. */
|
|
def builtArtifact(
|
|
component: String,
|
|
os: OS,
|
|
architecture: Architecture
|
|
): File = artifactRoot / artifactName(component, os, architecture)
|
|
|
|
/** Path to the artifact that is built on this local machine. */
|
|
def localArtifact(component: String): File = {
|
|
val os =
|
|
if (Platform.isWindows) OS.Windows
|
|
else if (Platform.isLinux) OS.Linux
|
|
else if (Platform.isMacOS) {
|
|
if (Platform.isAmd64) OS.MacOSAmd
|
|
else if (Platform.isArm64) OS.MacOSArm
|
|
else
|
|
throw new IllegalStateException(
|
|
"Unknown Arch: " + sys.props("os.arch")
|
|
)
|
|
} else
|
|
throw new IllegalStateException("Unknown OS: " + sys.props("os.name"))
|
|
artifactRoot / artifactName(component, os, os.archs.head)
|
|
}
|
|
|
|
/** Path to a built archive.
|
|
*
|
|
* These archives are built by [[makePackages]] and [[makeBundles]].
|
|
*/
|
|
def builtArchive(
|
|
component: String,
|
|
os: OS,
|
|
architecture: Architecture
|
|
): File =
|
|
artifactRoot / (artifactName(
|
|
component,
|
|
os,
|
|
architecture
|
|
) + os.archiveExt)
|
|
|
|
private def cleanDirectory(dir: File): Unit = {
|
|
for (f <- IO.listFiles(dir)) {
|
|
IO.delete(f)
|
|
}
|
|
}
|
|
|
|
/** Creates compressed and ready for release packages for the launcher and
|
|
* engine.
|
|
*
|
|
* A project manager package is not created, as we release only its bundle.
|
|
* See [[makeBundles]].
|
|
*
|
|
* It does not trigger any builds. Instead, it uses available artifacts
|
|
* placed in `artifactRoot`. These artifacts may be created using the
|
|
* `enso/build*Distribution` tasks or they may come from other workers (as
|
|
* is the case in the release CI where the artifacts are downloaded from
|
|
* other jobs).
|
|
*/
|
|
def makePackages = Command.command("makePackages") { state =>
|
|
val log = state.log
|
|
for {
|
|
os <- OS.platforms
|
|
arch <- os.archs
|
|
} {
|
|
val launcher = builtArtifact("launcher", os, arch)
|
|
if (launcher.exists()) {
|
|
fixLauncher(launcher, os)
|
|
val archive = builtArchive("launcher", os, arch)
|
|
makeArchive(launcher, "enso", archive)
|
|
log.info(s"Created $archive")
|
|
}
|
|
|
|
val engine = builtArtifact("engine", os, arch)
|
|
if (engine.exists()) {
|
|
if (os.isUNIX) {
|
|
makeExecutable(engine / s"enso-$ensoVersion" / "bin" / "enso")
|
|
}
|
|
val archive = builtArchive("engine", os, arch)
|
|
makeArchive(engine, s"enso-$ensoVersion", archive)
|
|
log.info(s"Created $archive")
|
|
}
|
|
}
|
|
state
|
|
}
|
|
|
|
/** Creates launcher and project-manager bundles that include the component
|
|
* itself, the engine and a Graal runtime.
|
|
*
|
|
* It will download the GraalVM runtime and cache it in `artifactRoot` so
|
|
* further invocations for the same version will not need to download it.
|
|
*
|
|
* It does not trigger any builds. Instead, it uses available artifacts
|
|
* placed in `artifactRoot`. These artifacts may be created using the
|
|
* `enso/build*Distribution` tasks or they may come from other workers (as
|
|
* is the case in the release CI where the artifacts are downloaded from
|
|
* other jobs).
|
|
*/
|
|
def makeBundles = Command.command("makeBundles") { state =>
|
|
val log = state.log
|
|
for {
|
|
os <- OS.platforms
|
|
arch <- os.archs
|
|
} {
|
|
val launcher = builtArtifact("launcher", os, arch)
|
|
if (launcher.exists()) {
|
|
fixLauncher(launcher, os)
|
|
copyEngine(os, arch, launcher / "enso" / "dist")
|
|
copyGraal(
|
|
os,
|
|
arch,
|
|
launcher / "enso" / "runtime" / s"graalvm-ce-java$graalJavaVersion-$graalVersion/"
|
|
)
|
|
|
|
val archive = builtArchive("bundle", os, arch)
|
|
makeArchive(launcher, "enso", archive)
|
|
|
|
cleanDirectory(launcher / "enso" / "dist")
|
|
cleanDirectory(launcher / "enso" / "runtime")
|
|
|
|
log.info(s"Created $archive")
|
|
}
|
|
|
|
val pm = builtArtifact("project-manager", os, arch)
|
|
if (pm.exists()) {
|
|
if (os.isUNIX) {
|
|
makeExecutable(pm / "enso" / "bin" / "project-manager")
|
|
}
|
|
|
|
copyEngine(os, arch, pm / "enso" / "dist")
|
|
copyGraal(
|
|
os,
|
|
arch,
|
|
pm / "enso" / "runtime" / s"graalvm-ce-java$graalJavaVersion-$graalVersion/"
|
|
)
|
|
|
|
IO.copyFile(
|
|
file("distribution/enso.bundle.template"),
|
|
pm / "enso" / ".enso.bundle"
|
|
)
|
|
|
|
val archive = builtArchive("project-manager", os, arch)
|
|
makeArchive(pm, "enso", archive)
|
|
|
|
cleanDirectory(pm / "enso" / "dist")
|
|
cleanDirectory(pm / "enso" / "runtime")
|
|
|
|
log.info(s"Created $archive")
|
|
}
|
|
}
|
|
state
|
|
}
|
|
}
|
|
}
|