diff --git a/modules/cli/src/main/scala/scala/cli/commands/doc/Doc.scala b/modules/cli/src/main/scala/scala/cli/commands/doc/Doc.scala index abb5f5834e..f44de7c5e0 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/doc/Doc.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/doc/Doc.scala @@ -114,7 +114,7 @@ object Doc extends ScalaCommand[DocOptions] { value(alreadyExistsCheck()) - val docJarPath = value(generateScaladocDirPath(build, logger, extraArgs)) + val docJarPath = value(generateScaladocDirPath(Seq(build), logger, extraArgs)) value(alreadyExistsCheck()) if (force) os.copy.over(docJarPath, destPath) else os.copy(docJarPath, destPath) @@ -136,84 +136,77 @@ object Doc extends ScalaCommand[DocOptions] { ) def generateScaladocDirPath( - build: Build.Successful, + builds: Seq[Build.Successful], logger: Logger, extraArgs: Seq[String] ): Either[BuildException, os.Path] = either { - val docContentDir = build.scalaParams match { + val docContentDir = builds.head.scalaParams match { case Some(scalaParams) if scalaParams.scalaVersion.startsWith("2.") => - build.project.scaladocDir + builds.head.project.scaladocDir case Some(scalaParams) => val res = value { Artifacts.fetchAnyDependencies( Seq(Positioned.none(dep"org.scala-lang::scaladoc:${scalaParams.scalaVersion}")), - value(build.options.finalRepositories), + value(builds.head.options.finalRepositories), Some(scalaParams), logger, - build.options.finalCache, + builds.head.options.finalCache, None ) } - val destDir = build.project.scaladocDir + val destDir = builds.head.project.scaladocDir os.makeDir.all(destDir) - val ext = if (Properties.isWin) ".exe" else "" + val ext = if Properties.isWin then ".exe" else "" val baseArgs = Seq( "-classpath", - build.fullClassPath.map(_.toString).mkString(File.pathSeparator), + builds.flatMap(_.fullClassPath).distinct.map(_.toString).mkString(File.pathSeparator), "-d", destDir.toString ) val defaultArgs = - if ( - build.options.notForBloopOptions.packageOptions.useDefaultScaladocOptions.getOrElse( - true - ) - ) - defaultScaladocArgs - else - Nil + if builds.head.options.notForBloopOptions.packageOptions.useDefaultScaladocOptions + .getOrElse(true) + then defaultScaladocArgs + else Nil val args = baseArgs ++ - build.project.scalaCompiler.map(_.scalacOptions).getOrElse(Nil) ++ + builds.head.project.scalaCompiler.map(_.scalacOptions).getOrElse(Nil) ++ extraArgs ++ defaultArgs ++ - Seq(build.output.toString) + builds.map(_.output.toString) val retCode = Runner.runJvm( - (build.options.javaHomeLocation().value / "bin" / s"java$ext").toString, + (builds.head.options.javaHomeLocation().value / "bin" / s"java$ext").toString, Nil, // FIXME Allow to customize that? res.files.map(os.Path(_, os.pwd)), "dotty.tools.scaladoc.Main", args, logger, - cwd = Some(build.inputs.workspace) + cwd = Some(builds.head.inputs.workspace) ).waitFor() - if (retCode == 0) - destDir - else - value(Left(new ScaladocGenerationFailedError(retCode))) + if retCode == 0 then destDir + else value(Left(new ScaladocGenerationFailedError(retCode))) case None => - val destDir = build.project.scaladocDir + val destDir = builds.head.project.scaladocDir os.makeDir.all(destDir) val ext = if (Properties.isWin) ".exe" else "" val javaSources = - (build.sources.paths.map(_._1) ++ build.generatedSources.map(_.generated)) + builds + .flatMap(b => b.sources.paths.map(_._1) ++ b.generatedSources.map(_.generated)) + .distinct .filter(_.last.endsWith(".java")) val command = Seq( - (build.options.javaHomeLocation().value / "bin" / s"javadoc$ext").toString, + (builds.head.options.javaHomeLocation().value / "bin" / s"javadoc$ext").toString, "-d", destDir.toString, "-classpath", - build.fullClassPath.map(_.toString).mkString(File.pathSeparator) - ) ++ - javaSources.map(_.toString) + builds.flatMap(_.fullClassPath).distinct.map(_.toString).mkString(File.pathSeparator) + ) ++ javaSources.map(_.toString) val retCode = Runner.run( command, logger, - cwd = Some(build.inputs.workspace) + cwd = Some(builds.head.inputs.workspace) ).waitFor() - if (retCode == 0) - destDir - else - value(Left(new ScaladocGenerationFailedError(retCode))) + if retCode == 0 then destDir + else value(Left(new ScaladocGenerationFailedError(retCode))) } docContentDir } diff --git a/modules/cli/src/main/scala/scala/cli/commands/package0/Package.scala b/modules/cli/src/main/scala/scala/cli/commands/package0/Package.scala index e785d3b1a2..ea47550103 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/package0/Package.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/package0/Package.scala @@ -5,7 +5,7 @@ import caseapp.* import caseapp.core.help.HelpFormat import coursier.launcher.* import dependency.* -import os.Path +import os.{BasePathImpl, FilePath, Path, SegmentedPath} import packager.config.* import packager.deb.DebianPackage import packager.docker.DockerPackage @@ -28,7 +28,7 @@ import scala.build.internal.Util.* import scala.build.internal.resource.NativeResourceMapper import scala.build.internal.{Runner, ScalaJsLinkerConfig} import scala.build.options.PackageType.Native -import scala.build.options.{BuildOptions, JavaOpt, PackageType, Platform, ScalaNativeTarget} +import scala.build.options.{BuildOptions, JavaOpt, PackageType, Platform, ScalaNativeTarget, Scope} import scala.cli.CurrentParams import scala.cli.commands.OptionsHelper.* import scala.cli.commands.doc.Doc @@ -96,20 +96,21 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { docCompilerMakerOpt, logger, crossBuilds = cross, - buildTests = false, + buildTests = options.scope.test, partial = None, actionableDiagnostics = actionableDiagnostics, postAction = () => WatchUtil.printWatchMessage() ) { res => - res.orReport(logger).map(_.main).foreach { - case s: Build.Successful => - s.copyOutput(options.shared) + res.orReport(logger).map(_.builds).foreach { + case b if b.forall(_.success) => + val successfulBuilds = b.collect { case s: Build.Successful => s } + successfulBuilds.foreach(_.copyOutput(options.shared)) val mtimeDestPath = doPackage( logger = logger, outputOpt = options.output.filter(_.nonEmpty), force = options.force, forcedPackageTypeOpt = options.forcedPackageTypeOpt, - build = s, + builds = successfulBuilds, extraArgs = args.unparsed, expectedModifyEpochSecondOpt = expectedModifyEpochSecondOpt, allowTerminate = !options.watch.watchMode, @@ -118,52 +119,50 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { .orReport(logger) for (valueOpt <- mtimeDestPath) expectedModifyEpochSecondOpt = valueOpt - case _: Build.Failed => + case b if b.exists(bb => !bb.success && !bb.cancelled) => System.err.println("Compilation failed") - case _: Build.Cancelled => - System.err.println("Build cancelled") + case _ => System.err.println("Build cancelled") } } try WatchUtil.waitForCtrlC(() => watcher.schedule()) finally watcher.dispose() } - else { - val builds = - Build.build( - inputs, - initialBuildOptions, - compilerMaker, - docCompilerMakerOpt, - logger, - crossBuilds = cross, - buildTests = false, - partial = None, - actionableDiagnostics = actionableDiagnostics - ) - .orExit(logger) - builds.main match { - case s: Build.Successful => - s.copyOutput(options.shared) + else + Build.build( + inputs, + initialBuildOptions, + compilerMaker, + docCompilerMakerOpt, + logger, + crossBuilds = cross, + buildTests = options.scope.test, + partial = None, + actionableDiagnostics = actionableDiagnostics + ) + .orExit(logger) + .builds match { + case b if b.forall(_.success) => + val successfulBuilds = b.collect { case s: Build.Successful => s } + successfulBuilds.foreach(_.copyOutput(options.shared)) val res0 = doPackage( logger = logger, outputOpt = options.output.filter(_.nonEmpty), force = options.force, forcedPackageTypeOpt = options.forcedPackageTypeOpt, - build = s, + builds = successfulBuilds, extraArgs = args.unparsed, expectedModifyEpochSecondOpt = None, allowTerminate = !options.watch.watchMode, mainClassOptions = options.mainClass ) res0.orExit(logger) - case _: Build.Failed => + case b if b.exists(bb => !bb.success && !bb.cancelled) => System.err.println("Compilation failed") sys.exit(1) - case _: Build.Cancelled => + case _ => System.err.println("Build cancelled") sys.exit(1) } - } } def finalBuildOptions(options: PackageOptions): BuildOptions = { @@ -186,20 +185,23 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { outputOpt: Option[String], force: Boolean, forcedPackageTypeOpt: Option[PackageType], - build: Build.Successful, + builds: Seq[Build.Successful], extraArgs: Seq[String], expectedModifyEpochSecondOpt: Option[Long], allowTerminate: Boolean, mainClassOptions: MainClassOptions ): Either[BuildException, Option[Long]] = either { - if (mainClassOptions.mainClassLs.contains(true)) + if mainClassOptions.mainClassLs.contains(true) then value { mainClassOptions - .maybePrintMainClasses(build.foundMainClasses(), shouldExit = allowTerminate) + .maybePrintMainClasses( + builds.flatMap(_.foundMainClasses()).distinct, + shouldExit = allowTerminate + ) .map(_ => None) } else { - val packageType: PackageType = value(resolvePackageType(build, forcedPackageTypeOpt)) + val packageType: PackageType = value(resolvePackageType(builds, forcedPackageTypeOpt)) // TODO When possible, call alreadyExistsCheck() before compiling stuff def extension = packageType match { @@ -263,86 +265,103 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { case path => path } - val packageOutput = build.options.notForBloopOptions.packageOptions.output + val packageOutput = builds.head.options.notForBloopOptions.packageOptions.output val dest = output.orElse(packageOutput) .orElse { - build.sources.defaultMainClass + builds.flatMap(_.sources.defaultMainClass) + .headOption .map(n => n.drop(n.lastIndexOf('.') + 1)) .map(_.stripSuffix("_sc")) .map(_ + extension) } - .orElse(build.retainedMainClass(logger).map( - _.stripSuffix("_sc") + extension - ).toOption) - .orElse(build.sources.paths.collectFirst(_._1.baseName + extension)) + .orElse { + builds.flatMap(_.retainedMainClass(logger).toOption) + .headOption + .map(_.stripSuffix("_sc") + extension) + } + .orElse(builds.flatMap(_.sources.paths).collectFirst(_._1.baseName + extension)) .getOrElse(defaultName) val destPath = os.Path(dest, Os.pwd) val printableDest = CommandUtils.printablePath(destPath) - def alreadyExistsCheck(): Either[BuildException, Unit] = { - val alreadyExists = !force && + def alreadyExistsCheck(): Either[BuildException, Unit] = + if !force && os.exists(destPath) && !expectedModifyEpochSecondOpt.contains(os.mtime(destPath)) - if (alreadyExists) - build.options.interactive.map { interactive => + then + builds.head.options.interactive.map { interactive => InteractiveFileOps.erasingPath(interactive, printableDest, destPath) { () => val errorMsg = - if (expectedModifyEpochSecondOpt.isEmpty) s"$printableDest already exists" + if expectedModifyEpochSecondOpt.isEmpty then s"$printableDest already exists" else s"$printableDest was overwritten by another process" System.err.println(s"Error: $errorMsg. Pass -f or --force to force erasing it.") sys.exit(1) } } - else - Right(()) - } + else Right(()) value(alreadyExistsCheck()) def mainClass: Either[BuildException, String] = - build.options.mainClass match { + builds.head.options.mainClass.filter(_.nonEmpty) match { case Some(cls) => Right(cls) - case None => build.retainedMainClass(logger) - } + case None => + val potentialMainClasses = builds.flatMap(_.foundMainClasses()).distinct + builds + .map { build => + build.retainedMainClass(logger, potentialMainClasses) + .map(mainClass => build.scope -> mainClass) + } + .sequence + .left + .map(CompositeBuildException(_)) + .map(_.toMap) + .map { retainedMainClassesByScope => + if retainedMainClassesByScope.size == 1 then retainedMainClassesByScope.head._2 + else + retainedMainClassesByScope + .get(Scope.Main) + .orElse(retainedMainClassesByScope.get(Scope.Test)) + .get + } - def mainClassOpt: Option[String] = - build.options.mainClass.orElse { - build.retainedMainClassOpt(build.foundMainClasses(), logger) } - val packageOptions = build.options.notForBloopOptions.packageOptions + def mainClassOpt: Option[String] = mainClass.toOption + + val packageOptions = builds.head.options.notForBloopOptions.packageOptions val outputPath = packageType match { case PackageType.Bootstrap => - value(bootstrap(build, destPath, value(mainClass), () => alreadyExistsCheck(), logger)) + value(bootstrap(builds, destPath, value(mainClass), () => alreadyExistsCheck(), logger)) destPath case PackageType.LibraryJar => - val libraryJar = Library.libraryJar(Seq(build)) + val libraryJar = Library.libraryJar(builds) value(alreadyExistsCheck()) - if (force) os.copy.over(libraryJar, destPath, createFolders = true) + if force then os.copy.over(libraryJar, destPath, createFolders = true) else os.copy(libraryJar, destPath, createFolders = true) destPath case PackageType.SourceJar => val now = System.currentTimeMillis() - val content = sourceJar(build, now) + val content = sourceJar(builds, now) value(alreadyExistsCheck()) - if (force) os.write.over(destPath, content, createFolders = true) + if force then os.write.over(destPath, content, createFolders = true) else os.write(destPath, content, createFolders = true) destPath case PackageType.DocJar => - val docJarPath = value(docJar(build, logger, extraArgs)) + val docJarPath = value(docJar(builds, logger, extraArgs)) value(alreadyExistsCheck()) - if (force) os.copy.over(docJarPath, destPath, createFolders = true) + if force then os.copy.over(docJarPath, destPath, createFolders = true) else os.copy(docJarPath, destPath, createFolders = true) destPath case a: PackageType.Assembly => value { assembly( - Seq(build), - destPath, - a.mainClassInManifest match { + builds = builds, + destPath = destPath, + mainClassOpt = a.mainClassInManifest match { case None => - if (a.addPreamble) { + if a.addPreamble then { val clsName = value { mainClass.left.map { case e: NoMainClassFoundError => @@ -358,17 +377,17 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { case Some(false) => None case Some(true) => Some(value(mainClass)) }, - Nil, + extraProvided = Nil, withPreamble = a.addPreamble, - () => alreadyExistsCheck(), - logger + alreadyExistsCheck = () => alreadyExistsCheck(), + logger = logger ) } destPath case PackageType.Spark => value { assembly( - Seq(build), + builds, destPath, mainClassOpt, // The Spark modules are assumed to be already on the class path, @@ -384,7 +403,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { destPath case PackageType.Js => - value(buildJs(build, destPath, mainClassOpt, logger)) + value(buildJs(builds, destPath, mainClassOpt, logger)) case tpe: PackageType.Native => import PackageType.Native.* @@ -394,22 +413,22 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { case _ => None val cachedDest = value(buildNative( - builds = Seq(build), + builds = builds, mainClass = mainClassO, targetType = tpe, destPath = Some(destPath), logger = logger )) - if (force) os.copy.over(cachedDest, destPath, createFolders = true) + if force then os.copy.over(cachedDest, destPath, createFolders = true) else os.copy(cachedDest, destPath, createFolders = true) destPath case PackageType.GraalVMNativeImage => NativeImage.buildNativeImage( - build, + builds, value(mainClass), destPath, - build.inputs.nativeImageWorkDir, + builds.head.inputs.nativeImageWorkDir, extraArgs, logger ) @@ -419,7 +438,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { val bootstrapPath = os.temp.dir(prefix = "scala-packager") / "app" value { bootstrap( - build, + builds, bootstrapPath, value(mainClass), () => alreadyExistsCheck(), @@ -502,63 +521,60 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { } destPath case PackageType.Docker => - value(docker(build, value(mainClass), logger)) + value(docker(builds, value(mainClass), logger)) destPath } val printableOutput = CommandUtils.printablePath(outputPath) - if (packageType.runnable.nonEmpty) + if packageType.runnable.nonEmpty then logger.message { - if (packageType.runnable.contains(true)) + if packageType.runnable.contains(true) then s"Wrote $outputPath, run it with" + System.lineSeparator() + " " + printableOutput - else if (packageType == PackageType.Js) + else if packageType == PackageType.Js then s"Wrote $outputPath, run it with" + System.lineSeparator() + " node " + printableOutput - else - s"Wrote $outputPath" + else s"Wrote $outputPath" } - val mTimeDestPathOpt = if (packageType.runnable.isEmpty) None else Some(os.mtime(destPath)) + val mTimeDestPathOpt = if packageType.runnable.isEmpty then None else Some(os.mtime(destPath)) mTimeDestPathOpt } // end of doPackage } def docJar( - build: Build.Successful, + builds: Seq[Build.Successful], logger: Logger, extraArgs: Seq[String] ): Either[BuildException, os.Path] = either { - val workDir = build.inputs.docJarWorkDir + val workDir = builds.head.inputs.docJarWorkDir val dest = workDir / "doc.jar" val cacheData = CachedBinary.getCacheData( - builds = Seq(build), + builds = builds, config = extraArgs.toList, dest = dest, workDir = workDir ) - if (cacheData.changed) { + if cacheData.changed then { - val contentDir = value(Doc.generateScaladocDirPath(build, logger, extraArgs)) + val contentDir = value(Doc.generateScaladocDirPath(builds, logger, extraArgs)) var outputStream: OutputStream = null try { outputStream = os.write.outputStream(dest, createFolders = true) Library.writeLibraryJarTo( outputStream, - Seq(build), + builds, hasActualManifest = false, contentDirOverride = Some(contentDir) ) } - finally - if (outputStream != null) - outputStream.close() + finally if outputStream != null then outputStream.close() CachedBinary.updateProjectAndOutputSha(dest, workDir, cacheData.projectSha) } @@ -567,38 +583,40 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { } private val generatedSourcesPrefix = os.rel / "META-INF" / "generated" - def sourceJar(build: Build.Successful, defaultLastModified: Long): Array[Byte] = { + def sourceJar(builds: Seq[Build.Successful], defaultLastModified: Long): Array[Byte] = { val baos = new ByteArrayOutputStream var zos: ZipOutputStream = null - def fromSimpleSources = build.sources.paths.iterator.map { + def fromSimpleSources = builds.flatMap(_.sources.paths).distinct.iterator.map { case (path, relPath) => val lastModified = os.mtime(path) val content = os.read.bytes(path) (relPath, content, lastModified) } - def fromGeneratedSources = build.sources.inMemory.iterator.flatMap { inMemSource => - val lastModified = inMemSource.originalPath match { - case Right((_, origPath)) => os.mtime(origPath) - case Left(_) => defaultLastModified - } - val originalOpt = inMemSource.originalPath.toOption.collect { - case (subPath, origPath) if subPath != inMemSource.generatedRelPath => - val origContent = os.read.bytes(origPath) - (subPath, origContent, lastModified) + def fromGeneratedSources = + builds.flatMap(_.sources.inMemory).distinct.iterator.flatMap { inMemSource => + val lastModified = inMemSource.originalPath match { + case Right((_, origPath)) => os.mtime(origPath) + case Left(_) => defaultLastModified + } + val originalOpt = inMemSource.originalPath.toOption.collect { + case (subPath, origPath) if subPath != inMemSource.generatedRelPath => + val origContent = os.read.bytes(origPath) + (subPath, origContent, lastModified) + } + val prefix = if (originalOpt.isEmpty) os.rel else generatedSourcesPrefix + val generated = ( + prefix / inMemSource.generatedRelPath, + inMemSource.content, + lastModified + ) + Iterator(generated) ++ originalOpt.iterator } - val prefix = if (originalOpt.isEmpty) os.rel else generatedSourcesPrefix - val generated = ( - prefix / inMemSource.generatedRelPath, - inMemSource.content, - lastModified - ) - Iterator(generated) ++ originalOpt.iterator - } - def paths = fromSimpleSources ++ fromGeneratedSources + def paths: Iterator[(FilePath & BasePathImpl & SegmentedPath, Array[Byte], Long)] = + fromSimpleSources ++ fromGeneratedSources try { zos = new ZipOutputStream(baos) @@ -613,19 +631,20 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { zos.closeEntry() } } - finally if (zos != null) zos.close() + finally if zos != null then zos.close() baos.toByteArray } private def docker( - build: Build.Successful, + builds: Seq[Build.Successful], mainClass: String, logger: Logger ): Either[BuildException, Unit] = either { - val packageOptions = build.options.notForBloopOptions.packageOptions + val packageOptions = builds.head.options.notForBloopOptions.packageOptions - if (build.options.platform.value == Platform.Native && (Properties.isMac || Properties.isWin)) { + if builds.head.options.platform.value == Platform.Native && (Properties.isMac || Properties.isWin) + then { System.err.println( "Package scala native application to docker image is not supported on MacOs and Windows" ) @@ -633,14 +652,14 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { } val exec = packageOptions.dockerOptions.cmd.orElse { - build.options.platform.value match { + builds.head.options.platform.value match { case Platform.JVM => Some("sh") case Platform.JS => Some("node") case Platform.Native => None } } val from = packageOptions.dockerOptions.from.getOrElse { - build.options.platform.value match { + builds.head.options.platform.value match { case Platform.JVM => "openjdk:17-slim" case Platform.JS => "node" case Platform.Native => "debian:stable-slim" @@ -662,13 +681,13 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { ) val appPath = os.temp.dir(prefix = "scala-cli-docker") / "app" - build.options.platform.value match { - case Platform.JVM => value(bootstrap(build, appPath, mainClass, () => Right(()), logger)) - case Platform.JS => buildJs(build, appPath, Some(mainClass), logger) + builds.head.options.platform.value match { + case Platform.JVM => value(bootstrap(builds, appPath, mainClass, () => Right(()), logger)) + case Platform.JS => buildJs(builds, appPath, Some(mainClass), logger) case Platform.Native => val dest = value(buildNative( - builds = Seq(build), + builds = builds, mainClass = Some(mainClass), targetType = PackageType.Native.Application, destPath = None, @@ -677,9 +696,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { os.copy(dest, appPath) } - logger.message( - "Started building docker image with your application, it might take some time" - ) + logger.message("Started building docker image with your application, it might take some time") DockerPackage(appPath, dockerSettings).build() @@ -690,43 +707,45 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { } private def buildJs( - build: Build.Successful, + builds: Seq[Build.Successful], destPath: os.Path, mainClass: Option[String], logger: Logger ): Either[BuildException, os.Path] = for { - isFullOpt <- build.options.scalaJsOptions.fullOpt - linkerConfig = build.options.scalaJsOptions.linkerConfig(logger) + isFullOpt <- builds.head.options.scalaJsOptions.fullOpt + linkerConfig = builds.head.options.scalaJsOptions.linkerConfig(logger) linkResult <- linkJs( - Seq(build), + builds, destPath, mainClass, addTestInitializer = false, linkerConfig, isFullOpt, - build.options.scalaJsOptions.noOpt.getOrElse(false), + builds.head.options.scalaJsOptions.noOpt.getOrElse(false), logger ) } yield linkResult private def bootstrap( - build: Build.Successful, + builds: Seq[Build.Successful], destPath: os.Path, mainClass: String, alreadyExistsCheck: () => Either[BuildException, Unit], logger: Logger ): Either[BuildException, Unit] = either { - val byteCodeZipEntries = os.walk(build.output) - .filter(os.isFile(_)) - .map { path => - val name = path.relativeTo(build.output).toString - val content = os.read.bytes(path) - val lastModified = os.mtime(path) - val ent = new ZipEntry(name) - ent.setLastModifiedTime(FileTime.fromMillis(lastModified)) - ent.setSize(content.length) - (ent, content) - } + val byteCodeZipEntries = builds.flatMap { build => + os.walk(build.output) + .filter(os.isFile(_)) + .map { path => + val name = path.relativeTo(build.output).toString + val content = os.read.bytes(path) + val lastModified = os.mtime(path) + val entry = new ZipEntry(name) + entry.setLastModifiedTime(FileTime.fromMillis(lastModified)) + entry.setSize(content.length) + (entry, content) + } + } // TODO Generate that in memory val tmpJar = os.temp(prefix = destPath.last.stripSuffix(".jar"), suffix = ".jar") @@ -737,16 +756,15 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { val tmpJarContent = os.read.bytes(tmpJar) os.remove(tmpJar) - def dependencyEntries = - build.artifacts.artifacts.map { + def dependencyEntries: Seq[ClassPathEntry] = + builds.flatMap(_.artifacts.artifacts).distinct.map { case (url, path) => - if (build.options.notForBloopOptions.packageOptions.isStandalone) + if builds.head.options.notForBloopOptions.packageOptions.isStandalone then ClassPathEntry.Resource(path.last, os.mtime(path), os.read.bytes(path)) - else - ClassPathEntry.Url(url) + else ClassPathEntry.Url(url) } val byteCodeEntry = ClassPathEntry.Resource(s"${destPath.last}-content.jar", 0L, tmpJarContent) - val extraClassPath = build.options.classPathOptions.extraClassPath.map { classPath => + val extraClassPath = builds.head.options.classPathOptions.extraClassPath.map { classPath => ClassPathEntry.Resource(classPath.last, os.mtime(classPath), os.read.bytes(classPath)) } @@ -755,13 +773,13 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { val preamble = Preamble() .withOsKind(Properties.isWin) .callsItself(Properties.isWin) - .withJavaOpts(build.options.javaOptions.javaOpts.toSeq.map(_.value.value)) + .withJavaOpts(builds.head.options.javaOptions.javaOpts.toSeq.map(_.value.value)) val baseParams = Parameters.Bootstrap(Seq(loaderContent), mainClass) .withDeterministic(true) .withPreamble(preamble) - val params = - if (build.options.notForBloopOptions.doSetupPython.getOrElse(false)) { + val params: Parameters.Bootstrap = + if builds.head.options.notForBloopOptions.doSetupPython.getOrElse(false) then { val res = value { Artifacts.fetchAnyDependencies( Seq(Positioned.none( @@ -770,7 +788,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { Nil, None, logger, - build.options.finalCache, + builds.head.options.finalCache, None, Some(_) ) @@ -778,18 +796,14 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { val entries = res.artifacts.map { case (a, f) => val path = os.Path(f) - if (build.options.notForBloopOptions.packageOptions.isStandalone) + if builds.head.options.notForBloopOptions.packageOptions.isStandalone then ClassPathEntry.Resource(path.last, os.mtime(path), os.read.bytes(path)) - else - ClassPathEntry.Url(a.url) + else ClassPathEntry.Url(a.url) } - val pythonContent = Seq( - ClassLoaderContent(entries) - ) + val pythonContent = Seq(ClassLoaderContent(entries)) baseParams.addExtraContent("python", pythonContent).withPython(true) } - else - baseParams + else baseParams value(alreadyExistsCheck()) BootstrapGenerator.generate(params, destPath.toNIO) @@ -863,15 +877,17 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { extraClassesFolders.flatMap(os.walk(_)).filter(os.isFile(_)).map(builds.head.output -> _) val byteCodeZipEntries = - (compiledClassesByOutputDir ++ extraClassesByDefaultOutputDir).map { (outputDir, path) => - val name = path.relativeTo(outputDir).toString - val content = os.read.bytes(path) - val lastModified = os.mtime(path) - val ent = new ZipEntry(name) - ent.setLastModifiedTime(FileTime.fromMillis(lastModified)) - ent.setSize(content.length) - (ent, content) - } + (compiledClassesByOutputDir ++ extraClassesByDefaultOutputDir) + .distinct + .map { (outputDir, path) => + val name = path.relativeTo(outputDir).toString + val content = os.read.bytes(path) + val lastModified = os.mtime(path) + val ent = new ZipEntry(name) + ent.setLastModifiedTime(FileTime.fromMillis(lastModified)) + ent.setSize(content.length) + (ent, content) + } val provided = builds.head.options.notForBloopOptions.packageOptions.provided ++ extraProvided val allJars = @@ -906,17 +922,6 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { cause = cause ) - def withSourceJar[T]( - build: Build.Successful, - defaultLastModified: Long, - fileName: String = "library" - )(f: os.Path => T): T = { - val jarContent = sourceJar(build, defaultLastModified) - val jar = os.temp(jarContent, prefix = fileName.stripSuffix(".jar"), suffix = "-sources.jar") - try f(jar) - finally os.remove(jar) - } - private object LinkingDir { case class Input(linkJsInput: ScalaJsLinker.LinkJSInput, scratchDirOpt: Option[os.Path]) private var currentInput: Option[Input] = None @@ -1127,10 +1132,10 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { dest } def resolvePackageType( - build: Build.Successful, + builds: Seq[Build.Successful], forcedPackageTypeOpt: Option[PackageType] ): Either[BuildException, PackageType] = { - val basePackageTypeOpt = build.options.notForBloopOptions.packageOptions.packageTypeOpt + val basePackageTypeOpt = builds.head.options.notForBloopOptions.packageOptions.packageTypeOpt lazy val validPackageScalaJS = Seq(PackageType.Js, PackageType.LibraryJar, PackageType.SourceJar, PackageType.DocJar) lazy val validPackageScalaNative = @@ -1143,43 +1148,40 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { PackageType.Native.LibraryStatic ) - forcedPackageTypeOpt -> build.options.platform.value match { + forcedPackageTypeOpt -> builds.head.options.platform.value match { case (Some(forcedPackageType), _) => Right(forcedPackageType) - case (_, _) if build.options.notForBloopOptions.packageOptions.isDockerEnabled => + case (_, _) if builds.head.options.notForBloopOptions.packageOptions.isDockerEnabled => basePackageTypeOpt match { case Some(PackageType.Docker) | None => Right(PackageType.Docker) case Some(packageType) => Left(new MalformedCliInputError( s"Unsupported package type: $packageType for Docker." )) } - case (_, Platform.JS) => - val validatedPackageType = + case (_, Platform.JS) => { for (basePackageType <- basePackageTypeOpt) yield - if (validPackageScalaJS.contains(basePackageType)) Right(basePackageType) - else Left(new MalformedCliInputError( - s"Unsupported package type: $basePackageType for Scala.js." - )) - validatedPackageType.getOrElse(Right(PackageType.Js)) - case (_, Platform.Native) => - val specificNativePackageType = - import ScalaNativeTarget.* - build.options.scalaNativeOptions.buildTargetStr.flatMap(fromString).map { - case Application => PackageType.Native.Application - case LibraryDynamic => PackageType.Native.LibraryDynamic - case LibraryStatic => PackageType.Native.LibraryStatic - } - - val validatedPackageType = - for - basePackageType <- specificNativePackageType orElse basePackageTypeOpt + if validPackageScalaJS.contains(basePackageType) then Right(basePackageType) + else + Left(new MalformedCliInputError( + s"Unsupported package type: $basePackageType for Scala.js." + )) + }.getOrElse(Right(PackageType.Js)) + case (_, Platform.Native) => { + val specificNativePackageType: Option[Native] = + import ScalaNativeTarget.* + builds.head.options.scalaNativeOptions.buildTargetStr.flatMap(fromString).map { + case Application => PackageType.Native.Application + case LibraryDynamic => PackageType.Native.LibraryDynamic + case LibraryStatic => PackageType.Native.LibraryStatic + } + for basePackageType <- specificNativePackageType orElse basePackageTypeOpt yield - if (validPackageScalaNative.contains(basePackageType)) Right(basePackageType) - else Left(new MalformedCliInputError( - s"Unsupported package type: $basePackageType for Scala Native." - )) - - validatedPackageType.getOrElse(Right(PackageType.Native.Application)) + if validPackageScalaNative.contains(basePackageType) then Right(basePackageType) + else + Left(new MalformedCliInputError( + s"Unsupported package type: $basePackageType for Scala Native." + )) + }.getOrElse(Right(PackageType.Native.Application)) case _ => Right(basePackageTypeOpt.getOrElse(PackageType.Bootstrap)) } } diff --git a/modules/cli/src/main/scala/scala/cli/commands/package0/PackageOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/package0/PackageOptions.scala index d42760ebc7..3feccaa8ec 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/package0/PackageOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/package0/PackageOptions.scala @@ -141,7 +141,10 @@ final case class PackageOptions( @ExtraName("graal") @Tag(tags.restricted) @Tag(tags.inShortHelp) - nativeImage: Boolean = false + nativeImage: Boolean = false, + + @Recurse + scope: ScopeOptions = ScopeOptions() ) extends HasSharedOptions { // format: on diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala index 528b9a8d69..219efb3ef7 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/Publish.scala @@ -572,27 +572,25 @@ object Publish extends ScalaCommand[PublishOptions] with BuildCommandHelpers { } val sourceJarOpt = - if (publishOptions.contextual(isCi).sourceJar.getOrElse(true)) { - val content = PackageCmd.sourceJar(build, now.toEpochMilli) + if publishOptions.contextual(isCi).sourceJar.getOrElse(true) then { + val content = PackageCmd.sourceJar(Seq(build), now.toEpochMilli) val sourceJar = workingDir / org / s"$moduleName-$ver-sources.jar" os.write.over(sourceJar, content, createFolders = true) Some(sourceJar) } - else - None + else None val docJarOpt = - if (publishOptions.contextual(isCi).docJar.getOrElse(true)) + if publishOptions.contextual(isCi).docJar.getOrElse(true) then docBuildOpt match { case None => None case Some(docBuild) => - val docJarPath = value(PackageCmd.docJar(docBuild, logger, Nil)) + val docJarPath = value(PackageCmd.docJar(Seq(docBuild), logger, Nil)) val docJar = workingDir / org / s"$moduleName-$ver-javadoc.jar" os.copy.over(docJarPath, docJar, createFolders = true) Some(docJar) } - else - None + else None val dependencies = build.artifacts.userDependencies .map(_.toCs(build.artifacts.scalaOpt.map(_.params))) diff --git a/modules/cli/src/main/scala/scala/cli/packaging/NativeImage.scala b/modules/cli/src/main/scala/scala/cli/packaging/NativeImage.scala index 8ce5c8dc10..fe6f621407 100644 --- a/modules/cli/src/main/scala/scala/cli/packaging/NativeImage.scala +++ b/modules/cli/src/main/scala/scala/cli/packaging/NativeImage.scala @@ -168,7 +168,7 @@ object NativeImage { f(currentHome) def buildNativeImage( - build: Build.Successful, + builds: Seq[Build.Successful], mainClass: String, dest: os.Path, nativeImageWorkDir: os.Path, @@ -178,9 +178,9 @@ object NativeImage { os.makeDir.all(nativeImageWorkDir) - val jvmId = build.options.notForBloopOptions.packageOptions.nativeImageOptions.jvmId - val options = build.options.copy( - javaOptions = build.options.javaOptions.copy( + val jvmId = builds.head.options.notForBloopOptions.packageOptions.nativeImageOptions.jvmId + val options = builds.head.options.copy( + javaOptions = builds.head.options.javaOptions.copy( jvmIdOpt = Some(Positioned.none(jvmId)) ) ) @@ -190,15 +190,15 @@ object NativeImage { options.notForBloopOptions.packageOptions.nativeImageOptions.graalvmArgs.map(_.value) val cacheData = CachedBinary.getCacheData( - Seq(build), + builds, s"--java-home=${javaHome.javaHome.toString}" :: "--" :: extraOptions.toList ++ nativeImageArgs, dest, nativeImageWorkDir ) - if (cacheData.changed) { - val mainJar = Library.libraryJar(Seq(build)) - val originalClassPath = mainJar +: build.dependencyClassPath + if cacheData.changed then { + val mainJar = Library.libraryJar(builds) + val originalClassPath = mainJar +: builds.flatMap(_.dependencyClassPath).distinct ManifestJar.maybeWithManifestClassPath( createManifest = Properties.isWin, @@ -206,9 +206,9 @@ object NativeImage { // seems native-image doesn't correctly parse paths in manifests - this is especially a problem on Windows wrongSimplePathsInManifest = true ) { processedClassPath => - val needsProcessing = build.scalaParams.exists(_.scalaVersion.startsWith("3.")) + val needsProcessing = builds.head.scalaParams.exists(_.scalaVersion.startsWith("3.")) val (classPath, toClean, scala3extraOptions) = - if (needsProcessing) { + if needsProcessing then { val cpString = processedClassPath.mkString(File.pathSeparator) val processed = BytecodeProcessor.processClassPath(cpString, TempCache).toSeq val nativeConfigFile = os.temp(suffix = ".json") @@ -230,8 +230,7 @@ object NativeImage { (cp, nativeConfigFile +: BytecodeProcessor.toClean(processed), options) } - else - (processedClassPath, Seq[os.Path](), Seq[String]()) + else (processedClassPath, Seq[os.Path](), Seq[String]()) try { val args = extraOptions ++ scala3extraOptions ++ Seq( @@ -248,30 +247,25 @@ object NativeImage { val command = nativeImageCommand.toString +: args val exitCode = - if (Properties.isWin) + if Properties.isWin then vcvarsOpt match { - case Some(vcvars) => - runFromVcvarsBat(command, vcvars, nativeImageWorkDir, logger) - case None => - Runner.run(command, logger).waitFor() + case Some(vcvars) => runFromVcvarsBat(command, vcvars, nativeImageWorkDir, logger) + case None => Runner.run(command, logger).waitFor() } - else - Runner.run(command, logger).waitFor() - if (exitCode == 0) { + else Runner.run(command, logger).waitFor() + if exitCode == 0 then { val actualDest = - if (Properties.isWin) - if (dest.last.endsWith(".exe")) dest + if Properties.isWin then + if dest.last.endsWith(".exe") then dest else dest / os.up / s"${dest.last}.exe" - else - dest + else dest CachedBinary.updateProjectAndOutputSha( actualDest, nativeImageWorkDir, cacheData.projectSha ) } - else - throw new GraalVMNativeImageError + else throw new GraalVMNativeImageError } } finally util.Try(toClean.foreach(os.remove.all)) diff --git a/modules/cli/src/test/scala/cli/tests/PackageTests.scala b/modules/cli/src/test/scala/cli/tests/PackageTests.scala index 254f48ceda..c07f573f97 100644 --- a/modules/cli/src/test/scala/cli/tests/PackageTests.scala +++ b/modules/cli/src/test/scala/cli/tests/PackageTests.scala @@ -91,7 +91,7 @@ class PackageTests extends munit.FunSuite { (_, _, maybeFirstBuild) => val build = maybeFirstBuild.orThrow.successfulOpt.get - val packageType = Package.resolvePackageType(build, None).orThrow + val packageType = Package.resolvePackageType(Seq(build), None).orThrow expect(packageType == PackageType.Native.Application) } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala index 653f8e85cf..f8a73270fc 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala @@ -15,6 +15,7 @@ import scala.util.{Properties, Using} abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersionArgs { _: TestScalaVersion => protected lazy val extraOptions: Seq[String] = scalaVersionArgs ++ TestUtil.extraOptions + protected lazy val node: String = TestUtil.fromPath("node").getOrElse("node") test("simple script") { val fileName = "simple.sc" @@ -190,7 +191,7 @@ abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersio val launcher = root / destName expect(os.isFile(launcher)) - val nodePath = TestUtil.fromPath("node").getOrElse("node") + val nodePath = node val output = os.proc(nodePath, launcher.toString).call(cwd = root).out.trim() expect(output == message) } @@ -268,7 +269,7 @@ abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersio ) val launcher = root / destDir / "main.js" - val nodePath = TestUtil.fromPath("node").getOrElse("node") + val nodePath = node os.write(root / "package.json", "{\n\n \"type\": \"module\"\n\n}") // enable es module val output = os.proc(nodePath, launcher.toString).call(cwd = root).out.trim() expect(output == message) @@ -313,7 +314,7 @@ abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersio ) val launcher = root / destDir / "main.js" - val nodePath = TestUtil.fromPath("node").getOrElse("node") + val nodePath = node os.write(root / "package.json", "{\n\n \"type\": \"module\"\n\n}") // enable es module val output = os.proc(nodePath, launcher.toString).call(cwd = root).out.trim() expect(output == message) @@ -392,7 +393,7 @@ abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersio ) val runHelloWorld = root / "runHello.js" - val nodePath = TestUtil.fromPath("node").getOrElse("node") + val nodePath = node val output = os.proc(nodePath, runHelloWorld.toString).call(cwd = root).out.trim() expect(output == msg) } @@ -1337,4 +1338,55 @@ abstract class PackageTestDefinitions extends ScalaCliSuite with TestScalaVersio expect(res.out.trim() == child) } } + + { + val libraryArg = "--library" + val jsArg = "--js" + for { + (packageOpts, extension) <- Seq( + Seq("--native") -> (if (Properties.isWin) ".exe" else ""), + Nil -> (if (Properties.isWin) ".bat" else ""), + Seq(libraryArg) -> ".jar" + ) ++ (if (!TestUtil.isNativeCli || !Properties.isWin) Seq( + Seq("--assembly") -> ".jar", + Seq("--native-image") -> (if (Properties.isWin) ".exe" else ""), + Seq(jsArg) -> ".js" + ) + else Nil) + packageDescription = packageOpts.headOption.getOrElse("bootstrap") + } + test(s"package with main method in test scope ($packageDescription)") { + val mainClass = "TestScopeMain" + val testScopeFileName = s"$mainClass.test.scala" + val message = "Hello" + val outputFile = mainClass + extension + TestInputs( + os.rel / "Messages.scala" -> s"""object Messages { val msg = "$message" }""", + os.rel / testScopeFileName -> s"""object $mainClass extends App { println(Messages.msg) }""" + ).fromRoot { root => + os.proc( + TestUtil.cli, + "--power", + "package", + "--test", + extraOptions, + ".", + packageOpts + ) + .call(cwd = root) + val outputFilePath = root / outputFile + expect(os.isFile(outputFilePath)) + val output = + if (packageDescription == libraryArg) + os.proc(TestUtil.cli, "run", outputFilePath).call(cwd = root).out.trim() + else if (packageDescription == jsArg) + os.proc(node, outputFilePath).call(cwd = root).out.trim() + else { + expect(Files.isExecutable(outputFilePath.toNIO)) + TestUtil.maybeUseBash(outputFilePath)(cwd = root).out.trim() + } + expect(output == message) + } + } + } } diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index baec3df207..0e876d5097 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -1534,7 +1534,7 @@ Run scalafix rule(s) explicitly, overriding the configuration file default. Available in commands: -[`compile`](./commands.md#compile), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`shebang`](./commands.md#shebang) +[`compile`](./commands.md#compile), [`package`](./commands.md#package), [`repl` , `console`](./commands.md#repl), [`run`](./commands.md#run), [`shebang`](./commands.md#shebang) diff --git a/website/docs/reference/commands.md b/website/docs/reference/commands.md index bc4737270d..7009e0e92f 100644 --- a/website/docs/reference/commands.md +++ b/website/docs/reference/commands.md @@ -229,7 +229,7 @@ All supported types of inputs can be mixed with each other. For detailed documentation refer to our website: https://scala-cli.virtuslab.org/docs/commands/package -Accepts option groups: [benchmarking](./cli-options.md#benchmarking-options), [compilation server](./cli-options.md#compilation-server-options), [coursier](./cli-options.md#coursier-options), [cross](./cli-options.md#cross-options), [debug](./cli-options.md#debug-options), [dependency](./cli-options.md#dependency-options), [global suppress warning](./cli-options.md#global-suppress-warning-options), [help group](./cli-options.md#help-group-options), [input](./cli-options.md#input-options), [java](./cli-options.md#java-options), [java prop](./cli-options.md#java-prop-options), [jvm](./cli-options.md#jvm-options), [logging](./cli-options.md#logging-options), [main class](./cli-options.md#main-class-options), [markdown](./cli-options.md#markdown-options), [package](./cli-options.md#package-options), [packager](./cli-options.md#packager-options), [power](./cli-options.md#power-options), [python](./cli-options.md#python-options), [Scala.js](./cli-options.md#scalajs-options), [Scala Native](./cli-options.md#scala-native-options), [scalac](./cli-options.md#scalac-options), [scalac extra](./cli-options.md#scalac-extra-options), [semantic db](./cli-options.md#semantic-db-options), [shared](./cli-options.md#shared-options), [snippet](./cli-options.md#snippet-options), [source generator](./cli-options.md#source-generator-options), [suppress warning](./cli-options.md#suppress-warning-options), [verbosity](./cli-options.md#verbosity-options), [version](./cli-options.md#version-options), [watch](./cli-options.md#watch-options), [workspace](./cli-options.md#workspace-options) +Accepts option groups: [benchmarking](./cli-options.md#benchmarking-options), [compilation server](./cli-options.md#compilation-server-options), [coursier](./cli-options.md#coursier-options), [cross](./cli-options.md#cross-options), [debug](./cli-options.md#debug-options), [dependency](./cli-options.md#dependency-options), [global suppress warning](./cli-options.md#global-suppress-warning-options), [help group](./cli-options.md#help-group-options), [input](./cli-options.md#input-options), [java](./cli-options.md#java-options), [java prop](./cli-options.md#java-prop-options), [jvm](./cli-options.md#jvm-options), [logging](./cli-options.md#logging-options), [main class](./cli-options.md#main-class-options), [markdown](./cli-options.md#markdown-options), [package](./cli-options.md#package-options), [packager](./cli-options.md#packager-options), [power](./cli-options.md#power-options), [python](./cli-options.md#python-options), [Scala.js](./cli-options.md#scalajs-options), [Scala Native](./cli-options.md#scala-native-options), [scalac](./cli-options.md#scalac-options), [scalac extra](./cli-options.md#scalac-extra-options), [scope](./cli-options.md#scope-options), [semantic db](./cli-options.md#semantic-db-options), [shared](./cli-options.md#shared-options), [snippet](./cli-options.md#snippet-options), [source generator](./cli-options.md#source-generator-options), [suppress warning](./cli-options.md#suppress-warning-options), [verbosity](./cli-options.md#verbosity-options), [version](./cli-options.md#version-options), [watch](./cli-options.md#watch-options), [workspace](./cli-options.md#workspace-options) ## publish