diff --git a/build.sc b/build.sc
index 7e145f05c6..528ebf9788 100644
--- a/build.sc
+++ b/build.sc
@@ -491,6 +491,13 @@ trait Core extends ScalaCliCrossSbtModule
| def giter8Organization = "${Deps.giter8.dep.module.organization.value}"
| def giter8Name = "${Deps.giter8.dep.module.name.value}"
| def giter8Version = "${Deps.giter8.dep.version}"
+ |
+ | def mavenVersion = "${Deps.Versions.mavenVersion}"
+ | def mavenScalaCompilerPluginVersion = "${Deps.Versions.mavenScalaCompilerPluginVersion}"
+ | def mavenExecPluginVersion = "${Deps.Versions.mavenExecPluginVersion}"
+ | def mavenAppArtifactId = "${Deps.Versions.mavenAppArtifactId}"
+ | def mavenAppGroupId = "${Deps.Versions.mavenAppGroupId}"
+ | def mavenAppVersion = "${Deps.Versions.mavenAppVersion}"
|}
|""".stripMargin
if (!os.isFile(dest) || os.read(dest) != code)
diff --git a/modules/cli/src/main/scala/scala/cli/commands/export0/Export.scala b/modules/cli/src/main/scala/scala/cli/commands/export0/Export.scala
index 2bae7b863b..ddd62921c0 100644
--- a/modules/cli/src/main/scala/scala/cli/commands/export0/Export.scala
+++ b/modules/cli/src/main/scala/scala/cli/commands/export0/Export.scala
@@ -79,6 +79,28 @@ object Export extends ScalaCommand[ExportOptions] {
logger: Logger
): SbtProjectDescriptor =
SbtProjectDescriptor(sbtVersion, extraSettings, logger)
+
+ def mavenProjectDescriptor(
+ mavenPluginVersion: String,
+ mavenScalaPluginVersion: String,
+ mavenExecPluginVersion: String,
+ extraSettings: Seq[String],
+ mavenGroupId: String,
+ mavenArtifactId: String,
+ mavenVersion: String,
+ logger: Logger
+ ): MavenProjectDescriptor =
+ MavenProjectDescriptor(
+ mavenPluginVersion,
+ mavenScalaPluginVersion,
+ mavenExecPluginVersion,
+ extraSettings,
+ mavenGroupId,
+ mavenArtifactId,
+ mavenVersion,
+ logger
+ )
+
def millProjectDescriptor(
cache: FileCache[Task],
projectName: Option[String],
@@ -129,9 +151,13 @@ object Export extends ScalaCommand[ExportOptions] {
sys.exit(1)
}
- val shouldExportToMill = options.mill.getOrElse(false)
- val shouldExportToSbt = options.sbt.getOrElse(false)
- if (shouldExportToMill && shouldExportToSbt) {
+ val shouldExportToMill = options.mill.getOrElse(false)
+ val shouldExportToSbt = options.sbt.getOrElse(false)
+ val shouldExportToMaven = options.maven.getOrElse(false)
+
+ val exportOptions = List(shouldExportToMill, shouldExportToSbt, shouldExportToMaven)
+
+ if (exportOptions.count(identity) > 1) {
logger.error(
s"Error: Cannot export to both mill and sbt. Please pick one build tool to export."
)
@@ -139,7 +165,8 @@ object Export extends ScalaCommand[ExportOptions] {
}
if (!shouldExportToJson) {
- val buildToolName = if (shouldExportToMill) "mill" else "sbt"
+ val buildToolName =
+ if (shouldExportToMill) "mill" else if (shouldExportToMaven) "maven" else "sbt"
logger.message(s"Exporting to a $buildToolName project...")
}
else if (!shouldExportJsonToStdout)
@@ -211,7 +238,18 @@ object Export extends ScalaCommand[ExportOptions] {
project.print(System.out)
}
else {
- val sbtVersion = options.sbtVersion.getOrElse("1.10.1")
+ val sbtVersion = options.sbtVersion.getOrElse("1.10.1")
+ val defaultMavenCompilerVersion = options.mvnVersion.getOrElse(Constants.mavenVersion)
+ val defaultScalaMavenCompilerVersion =
+ options.mvnScalaVersion.getOrElse(Constants.mavenScalaCompilerPluginVersion)
+ val defaultMavenExecPluginVersion =
+ options.mvnExecPluginVersion.getOrElse(Constants.mavenExecPluginVersion)
+ val defaultMavenArtifactId =
+ options.mvnAppArtifactId.getOrElse(Constants.mavenAppArtifactId)
+ val defaultMavenGroupId =
+ options.mvnAppGroupId.getOrElse(Constants.mavenAppGroupId)
+ val defaultMavenVersion =
+ options.mvnAppVersion.getOrElse(Constants.mavenAppVersion)
def sbtProjectDescriptor0 =
sbtProjectDescriptor(options.sbtSetting.map(_.trim).filter(_.nonEmpty), sbtVersion, logger)
@@ -219,6 +257,17 @@ object Export extends ScalaCommand[ExportOptions] {
val projectDescriptor =
if (shouldExportToMill)
millProjectDescriptor(options.shared.coursierCache, options.project, logger)
+ else if (shouldExportToMaven)
+ mavenProjectDescriptor(
+ defaultMavenCompilerVersion,
+ defaultScalaMavenCompilerVersion,
+ defaultMavenExecPluginVersion,
+ Nil,
+ defaultMavenGroupId,
+ defaultMavenArtifactId,
+ defaultMavenVersion,
+ logger
+ )
else if (shouldExportToJson)
jsonProjectDescriptor(options.project, inputs.workspace, logger)
else // shouldExportToSbt isn't checked, as it's treated as default
diff --git a/modules/cli/src/main/scala/scala/cli/commands/export0/ExportOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/export0/ExportOptions.scala
index a38c3b4d6e..b5effa4edb 100644
--- a/modules/cli/src/main/scala/scala/cli/commands/export0/ExportOptions.scala
+++ b/modules/cli/src/main/scala/scala/cli/commands/export0/ExportOptions.scala
@@ -27,6 +27,12 @@ final case class ExportOptions(
@HelpMessage("Sets the export format to SBT")
sbt: Option[Boolean] = None,
@Group(HelpGroup.BuildToolExport.toString)
+ @Tag(tags.experimental)
+ @Tag(tags.inShortHelp)
+ @HelpMessage("Sets the export format to Maven")
+ @Name("mvn")
+ maven: Option[Boolean] = None,
+ @Group(HelpGroup.BuildToolExport.toString)
@Tag(tags.restricted)
@Tag(tags.inShortHelp)
@HelpMessage("Sets the export format to Mill")
@@ -50,6 +56,30 @@ final case class ExportOptions(
@Tag(tags.restricted)
@HelpMessage("Version of SBT to be used for the export")
sbtVersion: Option[String] = None,
+ @Group(HelpGroup.BuildToolExport.toString)
+ @Tag(tags.experimental)
+ @HelpMessage("Version of Maven Compiler Plugin to be used for the export")
+ mvnVersion: Option[String] = None,
+ @Group(HelpGroup.BuildToolExport.toString)
+ @Tag(tags.experimental)
+ @HelpMessage("Version of Maven Scala Plugin to be used for the export")
+ mvnScalaVersion: Option[String] = None,
+ @Group(HelpGroup.BuildToolExport.toString)
+ @Tag(tags.experimental)
+ @HelpMessage("Version of Maven Exec Plugin to be used for the export")
+ mvnExecPluginVersion: Option[String] = None,
+ @Group(HelpGroup.BuildToolExport.toString)
+ @Tag(tags.experimental)
+ @HelpMessage("ArtifactId to be used for the maven export")
+ mvnAppArtifactId: Option[String] = None,
+ @Group(HelpGroup.BuildToolExport.toString)
+ @Tag(tags.experimental)
+ @HelpMessage("GroupId to be used for the maven export")
+ mvnAppGroupId: Option[String] = None,
+ @Group(HelpGroup.BuildToolExport.toString)
+ @Tag(tags.experimental)
+ @HelpMessage("Version to be used for the maven export")
+ mvnAppVersion: Option[String] = None,
@Name("o")
@Group(HelpGroup.BuildToolExport.toString)
@Tag(tags.restricted)
diff --git a/modules/cli/src/main/scala/scala/cli/exportCmd/MavenProject.scala b/modules/cli/src/main/scala/scala/cli/exportCmd/MavenProject.scala
new file mode 100644
index 0000000000..aa1de44786
--- /dev/null
+++ b/modules/cli/src/main/scala/scala/cli/exportCmd/MavenProject.scala
@@ -0,0 +1,144 @@
+package scala.cli.exportCmd
+
+import os.RelPath
+
+import java.nio.charset.StandardCharsets
+
+import scala.build.options.{ConfigMonoid, Scope}
+import scala.xml.{Elem, NodeSeq, PrettyPrinter, XML}
+
+final case class MavenProject(
+ groupId: Option[String] = None,
+ artifactId: Option[String] = None,
+ version: Option[String] = None,
+ plugins: Seq[MavenPlugin] = Nil,
+ imports: Seq[String] = Nil,
+ settings: Seq[Seq[String]] = Nil,
+ dependencies: Seq[MavenLibraryDependency] = Nil,
+ mainSources: Seq[(os.SubPath, String, Array[Byte])] = Nil,
+ testSources: Seq[(os.SubPath, String, Array[Byte])] = Nil,
+ resourceDirectories: Seq[String] = Nil
+) extends Project {
+
+ def +(other: MavenProject): MavenProject =
+ MavenProject.monoid.orElse(this, other)
+
+ def writeTo(dir: os.Path): Unit = {
+
+ val nl = System.lineSeparator()
+ val charset = StandardCharsets.UTF_8
+
+ val buildMavenContent = MavenModel(
+ "4.0.0",
+ groupId.getOrElse("groupId"),
+ artifactId.getOrElse("artifactId"),
+ version.getOrElse("0.1-SNAPSHOT"),
+ dependencies,
+ plugins,
+ resourceDirectories
+ )
+
+ val prettyPrinter = new PrettyPrinter(width = 80, step = 2)
+ val formattedXml = prettyPrinter.format(buildMavenContent.toXml)
+
+ os.write(
+ dir / "pom.xml",
+ formattedXml.getBytes(charset)
+ )
+
+ for ((path, language, content) <- mainSources) {
+ val path0 = dir / "src" / "main" / language / path
+ os.write(path0, content, createFolders = true)
+ }
+ for ((path, language, content) <- testSources) {
+ val path0 = dir / "src" / "test" / language / path
+ os.write(path0, content, createFolders = true)
+ }
+
+ }
+}
+
+object MavenProject {
+ implicit val monoid: ConfigMonoid[MavenProject] = ConfigMonoid.derive
+}
+
+final case class MavenModel(
+ model: String,
+ groupId: String,
+ artifactId: String,
+ version: String,
+ dependencies: Seq[MavenLibraryDependency],
+ plugins: Seq[MavenPlugin],
+ resourceDirectories: Seq[String]
+) {
+
+ private def resourceNodes: NodeSeq =
+ if (resourceDirectories.isEmpty)
+ NodeSeq.Empty
+ else {
+ val resourceNodes = resourceDirectories.map { path =>
+
+
+ {path}
+
+
+ }
+
+ {resourceNodes}
+
+ }
+
+ def toXml: Elem =
+
+ {model}
+ {groupId}
+ {artifactId}
+ {version}
+
+
+ {dependencies.map(_.toXml)}
+
+
+ {resourceNodes}
+
+ {plugins.map(_.toXml)}
+
+
+
+}
+
+final case class MavenLibraryDependency(
+ groupId: String,
+ artifactId: String,
+ version: String,
+ scope: MavenScopes
+) {
+
+ private val scopeParam =
+ if scope == MavenScopes.Main then scala.xml.Null else {scope.name}
+
+ def toXml: Elem =
+
+ {groupId}
+ {artifactId}
+ {version}
+ {scopeParam}
+
+}
+
+final case class MavenPlugin(
+ groupId: String,
+ artifactId: String,
+ version: String,
+ jdk: String,
+ additionalNode: Elem
+) {
+
+ def toXml: Elem =
+
+ {groupId}
+ {artifactId}
+ {version}
+ {additionalNode}
+
+}
diff --git a/modules/cli/src/main/scala/scala/cli/exportCmd/MavenProjectDescriptor.scala b/modules/cli/src/main/scala/scala/cli/exportCmd/MavenProjectDescriptor.scala
new file mode 100644
index 0000000000..dba708e287
--- /dev/null
+++ b/modules/cli/src/main/scala/scala/cli/exportCmd/MavenProjectDescriptor.scala
@@ -0,0 +1,324 @@
+package scala.cli.exportCmd
+
+import coursier.ivy.IvyRepository
+import coursier.maven.MavenRepository
+import coursier.parse.RepositoryParser
+import dependency.{AnyDependency, NoAttributes, ScalaNameAttributes}
+
+import java.nio.charset.StandardCharsets
+import java.nio.file.Path
+
+import scala.build.errors.BuildException
+import scala.build.internal.Constants
+import scala.build.internal.Runner.frameworkName
+import scala.build.options.{BuildOptions, Platform, Scope, ShadowingSeq}
+import scala.build.testrunner.AsmTestRunner
+import scala.build.{Logger, Positioned, Sources}
+import scala.cli.ScalaCli
+import scala.cli.commands.export0.ExportOptions
+import scala.cli.exportCmd.POMBuilderHelper.*
+import scala.xml.{Elem, XML}
+
+object POMBuilderHelper {
+ def buildNode(name: String, value: String): Elem =
+ new Elem(
+ null,
+ name,
+ scala.xml.Null,
+ scala.xml.TopScope,
+ minimizeEmpty = false,
+ scala.xml.Text(value)
+ )
+}
+
+final case class MavenProjectDescriptor(
+ mavenPluginVersion: String,
+ mavenScalaPluginVersion: String,
+ mavenExecPluginVersion: String,
+ extraSettings: Seq[String],
+ mavenAppGroupId: String,
+ mavenAppArtifactId: String,
+ mavenAppVersion: String,
+ logger: Logger
+) extends ProjectDescriptor {
+ private val q = "\""
+ private val nl = System.lineSeparator()
+
+ private def sources(sourcesMain: Sources, sourcesTest: Sources): MavenProject = {
+ val mainSources = ProjectDescriptor.sources(sourcesMain)
+ val testSources = ProjectDescriptor.sources(sourcesTest)
+ MavenProject(
+ mainSources = mainSources,
+ testSources = testSources
+ )
+ }
+
+ // todo: fill this - to be done in separate issue to reduce scope for maven export
+ private def javaOptionsSettings(options: BuildOptions): MavenProject =
+ MavenProject(
+ settings = Nil
+ )
+
+ private def javacOptionsSettings(options: BuildOptions): List[String] = {
+
+ val javacOptionsSettings =
+ if (options.javaOptions.javacOptions.toSeq.isEmpty) Nil
+ else {
+ val options0 = options
+ .javaOptions
+ .javacOptions
+ .toSeq
+ .map(_.value)
+ .map(o => "\"" + o.replace("\"", "\\\"") + "\"")
+ options0
+ }
+
+ javacOptionsSettings.toList
+ }
+
+ private def projectArtifactSettings(
+ mavenAppGroupId: String,
+ mavenAppArtifactId: String,
+ mavenAppVersion: String
+ ): MavenProject =
+ MavenProject(
+ groupId = Some(mavenAppGroupId),
+ artifactId = Some(mavenAppArtifactId),
+ version = Some(mavenAppVersion)
+ )
+
+ private def dependencySettings(
+ options: BuildOptions,
+ testOptions: BuildOptions,
+ scope: Scope,
+ sources: Sources
+ ): MavenProject = {
+
+ val scalaV = getScalaVersion(options)
+ def getScalaPrefix =
+ if scalaV.startsWith("3") then "3"
+ else if scalaV.startsWith("2.13") then "2.13"
+ else "2.12"
+
+ def buildMavenDepModels(
+ mainDeps: ShadowingSeq[Positioned[AnyDependency]],
+ isCompileOnly: Boolean
+ ) =
+ mainDeps.toSeq.toList.map(_.value).map { dep =>
+ val org = dep.organization
+ val name = dep.name
+ val ver = dep.version
+ // TODO dep.userParams
+ // TODO dep.exclude
+ // TODO dep.attributes
+ val artNameWithPrefix = dep.nameAttributes match {
+ case NoAttributes => name
+ case s: ScalaNameAttributes => s"${name}_$getScalaPrefix"
+ }
+ val scope0 =
+ if (scope == Scope.Test) MavenScopes.Test
+ else if (isCompileOnly) {
+ System.err.println(
+ s"Warning: Maven seems to support either test or provided, not both. So falling back to use Provided scope."
+ )
+ MavenScopes.Provided
+ }
+ else MavenScopes.Main
+
+ MavenLibraryDependency(org, artNameWithPrefix, ver, scope0)
+ }
+
+ val depSettings = {
+ def toDependencies(
+ mainDeps: ShadowingSeq[Positioned[AnyDependency]],
+ testDeps: ShadowingSeq[Positioned[AnyDependency]],
+ isCompileOnly: Boolean
+ ): Seq[MavenLibraryDependency] = {
+ val scopePriorities = List()
+ val mainDependenciesMaven = buildMavenDepModels(mainDeps, isCompileOnly)
+ val testDependenciesMaven = buildMavenDepModels(testDeps, isCompileOnly)
+ val resolvedDeps = (mainDependenciesMaven ++ testDependenciesMaven).groupBy(k =>
+ k.groupId + k.artifactId + k.version
+ ).map { (_, list) =>
+ val highestScope = MavenScopes.getHighestPriorityScope(list.map(_.scope))
+ list.head.copy(scope = highestScope)
+ }.toList
+
+ val scalaDep = if (!ProjectDescriptor.isPureJavaProject(options, sources)) {
+ val scalaDep = if scalaV.startsWith("3") then "scala3-library_3" else "scala-library"
+ val scalaCompilerDep =
+ if scalaV.startsWith("3") then "scala3-compiler_3" else "scala-compiler"
+ List(
+ MavenLibraryDependency("org.scala-lang", scalaDep, scalaV, MavenScopes.Main),
+ MavenLibraryDependency("org.scala-lang", scalaCompilerDep, scalaV, MavenScopes.Main)
+ )
+ }
+ else Nil
+
+ resolvedDeps ++ scalaDep
+ }
+
+ toDependencies(
+ options.classPathOptions.allExtraDependencies,
+ testOptions.classPathOptions.allExtraDependencies,
+ true
+ )
+ }
+
+ MavenProject(
+ dependencies = depSettings
+ )
+ }
+
+ private def getScalaVersion(options: BuildOptions): String =
+ options.scalaParams.toOption.flatten.map(_.scalaVersion).getOrElse(
+ ScalaCli.getDefaultScalaVersion
+ )
+
+ private def plugins(
+ options: BuildOptions,
+ scope: Scope,
+ jdkVersion: String,
+ sourcesMain: Sources
+ ): MavenProject = {
+
+ val pureJava = ProjectDescriptor.isPureJavaProject(options, sourcesMain)
+
+ val javacOptions = javacOptionsSettings(options)
+
+ val javaOptions = javaOptionsSettings(options)
+
+ val mavenJavaPlugin = buildJavaCompilerPlugin(javacOptions, jdkVersion)
+ val mavenExecPlugin = buildJavaExecPlugin(javacOptions, jdkVersion)
+ val scalaPlugin = buildScalaPlugin(javacOptions, jdkVersion, getScalaVersion(options))
+
+ val reqdPlugins =
+ if (pureJava) Seq(mavenJavaPlugin, mavenExecPlugin) else Seq(mavenJavaPlugin, scalaPlugin)
+
+ MavenProject(
+ plugins = reqdPlugins
+ )
+ }
+
+ private def buildScalaPlugin(
+ javacOptions: Seq[String],
+ jdkVersion: String,
+ scalaVersion: String
+ ): MavenPlugin = {
+
+ val scalaVersionNode = buildNode("scalaVersion", scalaVersion)
+ val javacOptionsElem = {
+ val opts = javacOptions.map { opt =>
+ buildNode("javacArg", opt)
+ }
+
+ {opts}
+
+ }
+
+ val execElements =
+
+
+
+ compile
+ testCompile
+
+
+
+
+ MavenPlugin(
+ "net.alchim31.maven",
+ "scala-maven-plugin",
+ mavenScalaPluginVersion,
+ jdkVersion,
+ execElements
+ )
+ }
+
+ private def buildJavaCompilerPlugin(
+ javacOptions: Seq[String],
+ jdkVersion: String
+ ): MavenPlugin = {
+ val javacOptionsElem = {
+ val opts = javacOptions.map { opt =>
+ buildNode("arg", opt)
+ }
+
+ {opts}
+
+ }
+
+ val sourceArg = buildNode("source", jdkVersion)
+ val targetArg = buildNode("target", jdkVersion)
+ val configNode =
+
+ {javacOptionsElem}
+ {sourceArg}
+ {targetArg}
+
+
+ MavenPlugin(
+ "org.apache.maven.plugins",
+ "maven-compiler-plugin",
+ mavenPluginVersion,
+ jdkVersion,
+ configNode
+ )
+ }
+
+ private def buildJavaExecPlugin(
+ javacOptions: Seq[String],
+ jdkVersion: String
+ ): MavenPlugin =
+ MavenPlugin(
+ "org.codehaus.mojo",
+ "exec-maven-plugin",
+ mavenExecPluginVersion,
+ jdkVersion,
+
+ )
+
+ private def customResourcesSettings(options: BuildOptions): MavenProject = {
+ val resourceDirs =
+ if (options.classPathOptions.resourcesDir.isEmpty) Nil
+ else
+ options.classPathOptions.resourcesDir.map(_.toNIO.toAbsolutePath.toString)
+ MavenProject(
+ resourceDirectories = resourceDirs
+ )
+ }
+
+ def `export`(
+ optionsMain: BuildOptions,
+ optionsTest: BuildOptions,
+ sourcesMain: Sources,
+ sourcesTest: Sources
+ ): Either[BuildException, MavenProject] = {
+ val jdk =
+ optionsMain.javaOptions.jvmIdOpt.map(_.value).getOrElse(
+ "17"
+ ) // todo: get from constants for default jdk
+ val projectChunks = Seq(
+ sources(sourcesMain, sourcesTest),
+ javaOptionsSettings(optionsMain),
+ dependencySettings(optionsMain, optionsTest, Scope.Main, sourcesMain),
+ customResourcesSettings(optionsMain),
+ plugins(optionsMain, Scope.Main, jdk, sourcesMain),
+ projectArtifactSettings(mavenAppGroupId, mavenAppArtifactId, mavenAppVersion)
+ )
+ Right(projectChunks.foldLeft(MavenProject())(_ + _))
+ }
+
+}
+
+enum MavenScopes(val priority: Int, val name: String) {
+ case Main extends MavenScopes(1, "main")
+ case Test extends MavenScopes(2, "test")
+ case Provided extends MavenScopes(3, "provided")
+}
+
+object MavenScopes {
+ def getHighestPriorityScope(scopes: Seq[MavenScopes]): MavenScopes =
+ // if scope is empty return Main Scope, depending on priority, with 1 being highest
+ scopes.minByOption(_.priority).getOrElse(Main)
+}
diff --git a/modules/cli/src/main/scala/scala/cli/exportCmd/MillProjectDescriptor.scala b/modules/cli/src/main/scala/scala/cli/exportCmd/MillProjectDescriptor.scala
index daa684a1e0..b731e86d64 100644
--- a/modules/cli/src/main/scala/scala/cli/exportCmd/MillProjectDescriptor.scala
+++ b/modules/cli/src/main/scala/scala/cli/exportCmd/MillProjectDescriptor.scala
@@ -31,15 +31,10 @@ final case class MillProjectDescriptor(
private def scalaVersionSettings(options: BuildOptions, sources: Sources): MillProject = {
- val pureJava = !options.scalaOptions.addScalaLibrary.contains(true) &&
- !options.scalaOptions.addScalaCompiler.contains(true) &&
- sources.hasJava &&
- !sources.hasScala &&
- options.classPathOptions.allExtraDependencies.toSeq
- .forall(_.value.nameAttributes == NoAttributes)
+ val pureJava = ProjectDescriptor.isPureJavaProject(options, sources)
val sv = options.scalaParams.toOption.flatten.map(_.scalaVersion).getOrElse(
- ScalaCli.getDefaultScalaVersion // FIXME account for pure Java projects, where Scala version isn't defined
+ ScalaCli.getDefaultScalaVersion
)
if (pureJava)
diff --git a/modules/cli/src/main/scala/scala/cli/exportCmd/ProjectDescriptor.scala b/modules/cli/src/main/scala/scala/cli/exportCmd/ProjectDescriptor.scala
index 27a5d8031a..33f31dd7a8 100644
--- a/modules/cli/src/main/scala/scala/cli/exportCmd/ProjectDescriptor.scala
+++ b/modules/cli/src/main/scala/scala/cli/exportCmd/ProjectDescriptor.scala
@@ -1,5 +1,7 @@
package scala.cli.exportCmd
+import dependency.NoAttributes
+
import java.nio.charset.Charset
import scala.build.errors.BuildException
@@ -63,4 +65,12 @@ object ProjectDescriptor {
calls
}
+ def isPureJavaProject(options: BuildOptions, sources: Sources): Boolean =
+ !options.scalaOptions.addScalaLibrary.contains(true) &&
+ !options.scalaOptions.addScalaCompiler.contains(true) &&
+ sources.hasJava &&
+ !sources.hasScala &&
+ options.classPathOptions.allExtraDependencies.toSeq
+ .forall(_.value.nameAttributes == NoAttributes)
+
}
diff --git a/modules/cli/src/main/scala/scala/cli/exportCmd/SbtProjectDescriptor.scala b/modules/cli/src/main/scala/scala/cli/exportCmd/SbtProjectDescriptor.scala
index 3e1f46ec73..64b36f00a5 100644
--- a/modules/cli/src/main/scala/scala/cli/exportCmd/SbtProjectDescriptor.scala
+++ b/modules/cli/src/main/scala/scala/cli/exportCmd/SbtProjectDescriptor.scala
@@ -45,12 +45,7 @@ final case class SbtProjectDescriptor(
private def pureJavaSettings(options: BuildOptions, sources: Sources): SbtProject = {
- val pureJava = !options.scalaOptions.addScalaLibrary.contains(true) &&
- !options.scalaOptions.addScalaCompiler.contains(true) &&
- sources.paths.forall(_._1.last.endsWith(".java")) &&
- sources.inMemory.forall(_.generatedRelPath.last.endsWith(".java")) &&
- options.classPathOptions.allExtraDependencies.toSeq
- .forall(_.value.nameAttributes == NoAttributes)
+ val pureJava = ProjectDescriptor.isPureJavaProject(options, sources)
val settings =
if (pureJava)
@@ -123,7 +118,7 @@ final case class SbtProjectDescriptor(
val scalaVerSetting = {
val sv = options.scalaParams.toOption.flatten.map(_.scalaVersion).getOrElse(
- ScalaCli.getDefaultScalaVersion // FIXME account for pure Java projects, where Scala version isn't defined
+ ScalaCli.getDefaultScalaVersion
)
s"""scalaVersion := "$sv""""
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportCommonTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportCommonTestDefinitions.scala
index 92575c4caf..8f1ddba25b 100644
--- a/modules/integration/src/test/scala/scala/cli/integration/ExportCommonTestDefinitions.scala
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportCommonTestDefinitions.scala
@@ -13,95 +13,82 @@ trait ExportCommonTestDefinitions { _: ScalaCliSuite & TestScalaVersionArgs =>
protected def runExportTests: Boolean = Properties.isMac
protected def exportCommand(args: String*): os.proc
- protected def buildToolCommand(root: os.Path, args: String*): os.proc
+ protected def buildToolCommand(root: os.Path, mainClass: Option[String], args: String*): os.proc
- protected def runMainArgs: Seq[String]
- protected def runTestsArgs: Seq[String]
+ protected def runMainArgs(mainClass: Option[String]): Seq[String]
+ protected def runTestsArgs(mainClass: Option[String]): Seq[String]
protected val prepareTestInputs: TestInputs => TestInputs = identity
protected val outputDir: os.RelPath = os.rel / "output-project"
- protected def simpleTest(inputs: TestInputs, extraExportArgs: Seq[String] = Nil): Unit =
+ protected def simpleTest(
+ inputs: TestInputs,
+ mainClass: Option[String],
+ extraExportArgs: Seq[String] = Nil
+ ): Unit =
prepareTestInputs(inputs).fromRoot { root =>
val exportArgs = "." +: extraExportArgs
exportCommand(exportArgs*).call(cwd = root, stdout = os.Inherit)
val res =
- buildToolCommand(root, runMainArgs*).call(cwd = root / outputDir)
+ buildToolCommand(root, mainClass, runMainArgs(mainClass)*).call(cwd = root / outputDir)
val output = res.out.text(Charset.defaultCharset())
expect(output.contains("Hello from exported Scala CLI project"))
}
protected def jvmTest(
- mainArgs: Seq[String] = runMainArgs,
- testArgs: Seq[String] = runTestsArgs,
- extraExportArgs: Seq[String] = Nil
+ mainArgs: Seq[String],
+ testArgs: Seq[String],
+ extraExportArgs: Seq[String] = Nil,
+ mainClassName: String
): Unit =
- prepareTestInputs(ExportTestProjects.jvmTest(actualScalaVersion)).fromRoot { root =>
- val exportArgs = "." +: extraExportArgs
- exportCommand(exportArgs*).call(cwd = root, stdout = os.Inherit)
- // main
- val res = buildToolCommand(root, mainArgs*).call(cwd = root / outputDir)
- val output = res.out.text(Charset.defaultCharset())
- expect(output.contains("Hello from " + actualScalaVersion))
- // resource
- expect(output.contains("resource:1,2"))
- // test
- val testRes = buildToolCommand(root, testArgs*).call(cwd = root / outputDir)
- val testOutput = testRes.out.text(Charset.defaultCharset())
- expect(testOutput.contains("1 succeeded"))
- }
-
- protected def logbackBugCase(): Unit =
- prepareTestInputs(ExportTestProjects.logbackBugCase(actualScalaVersion)).fromRoot { root =>
- exportCommand(".").call(cwd = root, stdout = os.Inherit)
- val res = buildToolCommand(root, runMainArgs*)
- .call(cwd = root / outputDir)
- val output = res.out.text(Charset.defaultCharset())
- expect(output.contains("Hello"))
+ prepareTestInputs(ExportTestProjects.jvmTest(actualScalaVersion, mainClassName)).fromRoot {
+ root =>
+ val exportArgs = "." +: extraExportArgs
+ exportCommand(exportArgs*).call(cwd = root, stdout = os.Inherit)
+ // main
+ val res =
+ buildToolCommand(root, Some(mainClassName), mainArgs*).call(cwd = root / outputDir)
+ val output = res.out.text(Charset.defaultCharset())
+ expect(output.contains("Hello from " + actualScalaVersion))
+ // resource
+ expect(output.contains("resource:1,2"))
+ // test
+ val testRes =
+ buildToolCommand(root, Some(mainClassName), testArgs*).call(cwd = root / outputDir)
+ val testOutput = testRes.out.text(Charset.defaultCharset())
+ expect(
+ testOutput.contains("1 succeeded") || testOutput.contains("BUILD SUCCESS")
+ ) // maven returns 'BUILD SUCCESS'
}
- protected def scalaVersionTest(scalaVersion: String): Unit =
- prepareTestInputs(ExportTestProjects.scalaVersionTest(scalaVersion)).fromRoot {
+ protected def scalaVersionTest(scalaVersion: String, mainClass: String): Unit =
+ prepareTestInputs(ExportTestProjects.scalaVersionTest(scalaVersion, mainClass)).fromRoot {
root =>
exportCommand(".").call(cwd = root, stdout = os.Inherit)
- val res = buildToolCommand(root, runMainArgs*)
+ val res = buildToolCommand(root, Some(mainClass), runMainArgs(Some(mainClass))*)
.call(cwd = root / outputDir)
val output = res.out.text(Charset.defaultCharset())
expect(output.contains("Hello"))
}
- def extraSourceFromDirectiveWithExtraDependency(inputs: String*): Unit =
+ def extraSourceFromDirectiveWithExtraDependency(mainClass: String, inputs: String*): Unit =
prepareTestInputs(
- ExportTestProjects.extraSourceFromDirectiveWithExtraDependency(actualScalaVersion)
+ ExportTestProjects.extraSourceFromDirectiveWithExtraDependency(actualScalaVersion, mainClass)
).fromRoot { root =>
exportCommand(inputs*).call(cwd = root, stdout = os.Inherit)
- val res = buildToolCommand(root, runMainArgs*)
+ val res = buildToolCommand(root, Some(mainClass), runMainArgs(Some(mainClass))*)
.call(cwd = root / outputDir)
val output = res.out.trim(Charset.defaultCharset())
expect(output.contains(root.toString))
}
- def compileOnlyTest(): Unit = {
- val userName = "John"
- prepareTestInputs(
- ExportTestProjects.compileOnlySource(actualScalaVersion, userName = userName)
- ).fromRoot { root =>
- exportCommand(".").call(cwd = root, stdout = os.Inherit)
- val res = buildToolCommand(root, runMainArgs*)
- .call(cwd = root / outputDir)
- val output = res.out.trim(Charset.defaultCharset())
- expect(output.contains(userName))
- expect(!output.contains("jsoniter-scala-macros"))
- }
- }
-
- def justTestScope(): Unit = {
+ def justTestScope(mainClass: String): Unit = {
val expectedMessage = "exporting just the test scope actually works!"
- prepareTestInputs(ExportTestProjects.justTestScope(expectedMessage))
+ prepareTestInputs(ExportTestProjects.justTestScope(mainClass, expectedMessage))
.fromRoot { root =>
exportCommand(".").call(cwd = root)
- val testRes = buildToolCommand(root, runTestsArgs*)
+ val testRes = buildToolCommand(root, Some(mainClass), runTestsArgs(Some(mainClass))*)
.call(cwd = root / outputDir)
val testOutput = testRes.out.text()
expect(testOutput.contains(expectedMessage))
@@ -111,32 +98,24 @@ trait ExportCommonTestDefinitions { _: ScalaCliSuite & TestScalaVersionArgs =>
private val scalaVersionsInDir: Seq[String] = Seq("2.12", "2.13", "2", "3", "3.lts")
if (runExportTests) {
- test("compile-time only for jsoniter macros") {
- compileOnlyTest()
- }
test("JVM") {
- jvmTest()
- }
- test("Scala.js") {
- simpleTest(ExportTestProjects.jsTest(actualScalaVersion))
- }
- test("Ensure test framework NPE is not thrown when depending on logback") {
- logbackBugCase()
+ jvmTest(runMainArgs(Some("Main")), runTestsArgs(Some("Main")), mainClassName = "Main")
}
test("extra source from a directive introducing a dependency") {
- extraSourceFromDirectiveWithExtraDependency("Main.scala")
+ extraSourceFromDirectiveWithExtraDependency("Main", "Main.scala")
}
test("extra source passed both via directive and from command line") {
- extraSourceFromDirectiveWithExtraDependency(".")
+ extraSourceFromDirectiveWithExtraDependency("Main", ".")
}
scalaVersionsInDir.foreach { scalaV =>
test(s"check export for project with scala version in directive as $scalaV") {
- scalaVersionTest(scalaV)
+ scalaVersionTest(scalaV, "Main")
}
}
test("just test scope") {
- justTestScope()
+ // Keeping the test name ends with Test to support maven convention
+ justTestScope("MyTest")
}
}
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTest3NextRc.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTest3NextRc.scala
new file mode 100644
index 0000000000..3cd1e6d452
--- /dev/null
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTest3NextRc.scala
@@ -0,0 +1,3 @@
+package scala.cli.integration
+
+class ExportMavenTest3NextRc extends ExportMavenTestDefinitions with Test3NextRc with MavenScala {}
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTestDefinitions.scala
new file mode 100644
index 0000000000..7c3b3f7e83
--- /dev/null
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTestDefinitions.scala
@@ -0,0 +1,47 @@
+package scala.cli.integration
+
+abstract class ExportMavenTestDefinitions extends ScalaCliSuite
+ with TestScalaVersionArgs with ExportCommonTestDefinitions with MavenTestHelper {
+ _: TestScalaVersion & MavenLanguageMode =>
+ override def exportCommand(args: String*): os.proc =
+ os.proc(
+ TestUtil.cli,
+ "--power",
+ "export",
+ extraOptions,
+ "--mvn",
+ "-o",
+ outputDir.toString,
+ args
+ )
+
+ override def buildToolCommand(root: os.Path, mainClass: Option[String], args: String*): os.proc =
+ mavenCommand(args*)
+
+ override def runMainArgs(mainClass: Option[String]): Seq[String] = {
+ require(mainClass.nonEmpty, "Main class or Test class is mandatory to build in maven")
+ if (language == JAVA) Seq("exec:java", s"-Dexec.mainClass=${mainClass.get}")
+ else Seq("scala:run", s"-DmainClass=${mainClass.get}")
+ }
+
+ override def runTestsArgs(mainClass: Option[String]): Seq[String] =
+ if (language == JAVA) Seq("test")
+ else Seq("test")
+
+}
+
+sealed trait Language
+case object JAVA extends Language
+case object SCALA extends Language
+
+sealed trait MavenLanguageMode {
+ def language: Language
+}
+
+trait MavenJava extends MavenLanguageMode {
+ final override def language: Language = JAVA
+}
+
+trait MavenScala extends MavenLanguageMode {
+ final override def language: Language = SCALA
+}
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTestJava.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTestJava.scala
new file mode 100644
index 0000000000..dcef3c0b34
--- /dev/null
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTestJava.scala
@@ -0,0 +1,14 @@
+package scala.cli.integration
+
+import scala.util.Properties
+
+class ExportMavenTestJava extends ExportMavenTestDefinitions with Test3Lts with MavenJava {
+ // disable running scala tests in java maven export
+ override def runExportTests: Boolean = false
+ if (!Properties.isWin) test("pure java") {
+ simpleTest(
+ ExportTestProjects.pureJavaTest("ScalaCliJavaTest"),
+ mainClass = Some("ScalaCliJavaTest")
+ )
+ }
+}
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTests212.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTests212.scala
new file mode 100644
index 0000000000..250dd492a6
--- /dev/null
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTests212.scala
@@ -0,0 +1,3 @@
+package scala.cli.integration
+
+class ExportMavenTests212 extends ExportMavenTestDefinitions with Test212 with MavenScala
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTests213.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTests213.scala
new file mode 100644
index 0000000000..0ac5d3389b
--- /dev/null
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTests213.scala
@@ -0,0 +1,3 @@
+package scala.cli.integration
+
+class ExportMavenTests213 extends ExportMavenTestDefinitions with Test213 with MavenScala
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTests3Lts.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTests3Lts.scala
new file mode 100644
index 0000000000..a880746c12
--- /dev/null
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportMavenTests3Lts.scala
@@ -0,0 +1,3 @@
+package scala.cli.integration
+
+class ExportMavenTests3Lts extends ExportMavenTestDefinitions with Test3Lts with MavenScala
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportMillTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportMillTestDefinitions.scala
index 2281603d6c..c0628b2a1b 100644
--- a/modules/integration/src/test/scala/scala/cli/integration/ExportMillTestDefinitions.scala
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportMillTestDefinitions.scala
@@ -8,6 +8,7 @@ import java.nio.charset.Charset
abstract class ExportMillTestDefinitions extends ScalaCliSuite
with TestScalaVersionArgs
with ExportCommonTestDefinitions
+ with ExportScalaOrientedBuildToolsTestDefinitions
with MillTestHelper { _: TestScalaVersion =>
override val prepareTestInputs: TestInputs => TestInputs = _.withMillJvmOpts
@@ -24,31 +25,40 @@ abstract class ExportMillTestDefinitions extends ScalaCliSuite
args
)
- override def buildToolCommand(root: os.Path, args: String*): os.proc =
+ override def buildToolCommand(root: os.Path, mainClass: Option[String], args: String*): os.proc =
millCommand(root, args*)
- override val runMainArgs: Seq[String] = Seq(s"$millDefaultProjectName.run")
+ override def runMainArgs(mainClass: Option[String]): Seq[String] =
+ Seq(s"$millDefaultProjectName.run")
- override val runTestsArgs: Seq[String] = Seq(s"$millDefaultProjectName.test")
+ override def runTestsArgs(mainClass: Option[String]): Seq[String] =
+ Seq(s"$millDefaultProjectName.test")
- def jvmTestScalacOptions(): Unit =
- ExportTestProjects.jvmTest(actualScalaVersion).withMillJvmOpts.fromRoot { root =>
+ def jvmTestScalacOptions(className: String): Unit =
+ ExportTestProjects.jvmTest(actualScalaVersion, className).withMillJvmOpts.fromRoot { root =>
exportCommand(".").call(cwd = root, stdout = os.Inherit)
val res =
- buildToolCommand(root, "--disable-ticker", "show", s"$millDefaultProjectName.scalacOptions")
+ buildToolCommand(
+ root,
+ Some(className),
+ "--disable-ticker",
+ "show",
+ s"$millDefaultProjectName.scalacOptions"
+ )
.call(cwd = root / outputDir)
val output = res.out.text(Charset.defaultCharset())
expect(output.filterNot(_.isWhitespace) == "[\"-deprecation\"]")
}
- def jvmTestCompilerPlugin(): Unit =
- ExportTestProjects.jvmTest(actualScalaVersion).withMillJvmOpts.fromRoot { root =>
+ def jvmTestCompilerPlugin(mainClass: String): Unit =
+ ExportTestProjects.jvmTest(actualScalaVersion, mainClass).withMillJvmOpts.fromRoot { root =>
exportCommand(".").call(cwd = root, stdout = os.Inherit)
locally {
// scalacPluginIvyDeps
val res =
buildToolCommand(
root,
+ Some(mainClass),
"--disable-ticker",
"show",
s"$millDefaultProjectName.scalacPluginIvyDeps"
@@ -61,7 +71,9 @@ abstract class ExportMillTestDefinitions extends ScalaCliSuite
locally {
// test
val res =
- buildToolCommand(root, s"$millDefaultProjectName.test").call(cwd = root / outputDir)
+ buildToolCommand(root, Some(mainClass), s"$millDefaultProjectName.test").call(cwd =
+ root / outputDir
+ )
val output = res.out.text(Charset.defaultCharset())
expect(output.contains("1 succeeded"))
}
@@ -73,20 +85,24 @@ abstract class ExportMillTestDefinitions extends ScalaCliSuite
jvmTest(
mainArgs = Seq(s"$customProjectName.run"),
testArgs = Seq(s"$customProjectName.test"),
- extraExportArgs = Seq("-p", customProjectName)
+ extraExportArgs = Seq("-p", customProjectName),
+ mainClassName = "Hello"
)
}
test("JVM scalac options") {
- jvmTestScalacOptions()
+ jvmTestScalacOptions("Hello")
}
}
if (runExportTests && !actualScalaVersion.startsWith("3."))
test("JVM with compiler plugin") {
- jvmTestCompilerPlugin()
+ jvmTestCompilerPlugin("Hello")
}
test("Scala Native") {
// FIXME this should be adjusted to Scala Native 0.5.x syntax once Mill gets support for it
- simpleTest(ExportTestProjects.nativeTest(actualScalaVersion, useNative04Syntax = true))
+ simpleTest(
+ ExportTestProjects.nativeTest(actualScalaVersion, useNative04Syntax = true),
+ mainClass = None
+ )
}
}
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportMillTests213.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportMillTests213.scala
index 64e648710a..243a1d3f23 100644
--- a/modules/integration/src/test/scala/scala/cli/integration/ExportMillTests213.scala
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportMillTests213.scala
@@ -3,13 +3,13 @@ package scala.cli.integration
class ExportMillTests213 extends ExportMillTestDefinitions with Test213 {
if (runExportTests) {
test("scalac options") {
- simpleTest(ExportTestProjects.scalacOptionsScala2Test(actualScalaVersion))
+ simpleTest(ExportTestProjects.scalacOptionsScala2Test(actualScalaVersion), mainClass = None)
}
test("pure java") {
- simpleTest(ExportTestProjects.pureJavaTest)
+ simpleTest(ExportTestProjects.pureJavaTest("ScalaCliJavaTest"), mainClass = None)
}
test("custom JAR") {
- simpleTest(ExportTestProjects.customJarTest(actualScalaVersion))
+ simpleTest(ExportTestProjects.customJarTest(actualScalaVersion), mainClass = None)
}
}
}
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportSbtTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportSbtTestDefinitions.scala
index 99f77a0d1f..a1ebdd32a1 100644
--- a/modules/integration/src/test/scala/scala/cli/integration/ExportSbtTestDefinitions.scala
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportSbtTestDefinitions.scala
@@ -1,7 +1,8 @@
package scala.cli.integration
abstract class ExportSbtTestDefinitions extends ScalaCliSuite
- with TestScalaVersionArgs with ExportCommonTestDefinitions with SbtTestHelper {
+ with TestScalaVersionArgs with ExportCommonTestDefinitions
+ with ExportScalaOrientedBuildToolsTestDefinitions with SbtTestHelper {
_: TestScalaVersion =>
override def exportCommand(args: String*): os.proc =
os.proc(
@@ -15,12 +16,13 @@ abstract class ExportSbtTestDefinitions extends ScalaCliSuite
args
)
- override def buildToolCommand(root: os.Path, args: String*): os.proc = sbtCommand(args*)
+ override def buildToolCommand(root: os.Path, mainClass: Option[String], args: String*): os.proc =
+ sbtCommand(args*)
- override val runMainArgs: Seq[String] = Seq("run")
- override val runTestsArgs: Seq[String] = Seq("test")
+ override def runMainArgs(mainClass: Option[String]): Seq[String] = Seq("run")
+ override def runTestsArgs(mainClass: Option[String]): Seq[String] = Seq("test")
test("Scala Native") {
- simpleTest(ExportTestProjects.nativeTest(actualScalaVersion))
+ simpleTest(ExportTestProjects.nativeTest(actualScalaVersion), mainClass = None)
}
}
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportSbtTests213.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportSbtTests213.scala
index 9430c91486..baa6ce4e84 100644
--- a/modules/integration/src/test/scala/scala/cli/integration/ExportSbtTests213.scala
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportSbtTests213.scala
@@ -3,16 +3,17 @@ package scala.cli.integration
class ExportSbtTests213 extends ExportSbtTestDefinitions with Test213 {
if (runExportTests) {
test("scalac options") {
- simpleTest(ExportTestProjects.scalacOptionsScala2Test(actualScalaVersion))
+ simpleTest(ExportTestProjects.scalacOptionsScala2Test(actualScalaVersion), mainClass = None)
}
test("pure java") {
simpleTest(
- ExportTestProjects.pureJavaTest,
+ ExportTestProjects.pureJavaTest("ScalaCliJavaTest"),
+ mainClass = None,
extraExportArgs = Seq("--sbt-setting=fork := true")
)
}
test("custom JAR") {
- simpleTest(ExportTestProjects.customJarTest(actualScalaVersion))
+ simpleTest(ExportTestProjects.customJarTest(actualScalaVersion), mainClass = None)
}
}
}
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportScalaOrientedBuildToolsTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportScalaOrientedBuildToolsTestDefinitions.scala
new file mode 100644
index 0000000000..f98090a0de
--- /dev/null
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportScalaOrientedBuildToolsTestDefinitions.scala
@@ -0,0 +1,88 @@
+package scala.cli.integration
+
+import com.eed3si9n.expecty.Expecty.expect
+
+import java.nio.charset.Charset
+
+/** This is a trait that defined test definitions for scala-oriented build tools like sbt and mill.
+ * The build tools like maven doesn't support some of the features like scalaJs, ScalaNative or
+ * compile-only dependencies.
+ */
+trait ExportScalaOrientedBuildToolsTestDefinitions {
+ _: ExportCommonTestDefinitions & ScalaCliSuite & TestScalaVersionArgs =>
+
+ def compileOnlyTest(mainClass: String): Unit = {
+ val userName = "John"
+ prepareTestInputs(
+ ExportTestProjects.compileOnlySource(actualScalaVersion, userName = userName)
+ ).fromRoot { root =>
+ exportCommand(".").call(cwd = root, stdout = os.Inherit)
+ val res = buildToolCommand(root, None, runMainArgs(Some(mainClass))*)
+ .call(cwd = root / outputDir)
+ val output = res.out.trim(Charset.defaultCharset())
+ expect(output.contains(userName))
+ expect(!output.contains("jsoniter-scala-macros"))
+ }
+ }
+
+ def testZioTest(testClassName: String, testArgs: Seq[String] = Nil): Unit = {
+
+ val testInput = TestInputs(
+ // todo: remove this hack after the PR https://github.com/VirtusLab/scala-cli/pull/3046 is merged
+ os.rel / "Hello.scala" -> """object Hello extends App""",
+ os.rel / "Zio.test.scala" ->
+ s"""|//> using dep "dev.zio::zio::1.0.8"
+ |//> using dep "dev.zio::zio-test-sbt::1.0.8"
+ |
+ |import zio._
+ |import zio.test._
+ |import zio.test.Assertion.equalTo
+ |
+ |object $testClassName extends DefaultRunnableSpec {
+ | def spec = suite("associativity")(
+ | testM("associativity") {
+ | check(Gen.anyInt, Gen.anyInt, Gen.anyInt) { (x, y, z) =>
+ | assert((x + y) + z)(equalTo(x + (y + z)))
+ | }
+ | }
+ | )
+ |}
+ |""".stripMargin,
+ os.rel / "input" / "input" ->
+ """|1
+ |2""".stripMargin
+ )
+
+ prepareTestInputs(testInput).fromRoot { root =>
+ val exportArgs = Seq(".")
+ val testArgsToPass = runTestsArgs(None)
+ exportCommand(exportArgs*).call(cwd = root, stdout = os.Inherit)
+ val testRes = buildToolCommand(root, None, testArgsToPass*).call(cwd = root / outputDir)
+ val testOutput = testRes.out.text(Charset.defaultCharset())
+ expect(testOutput.contains("1 succeeded"))
+ }
+ }
+ protected def logbackBugCase(mainClass: String): Unit =
+ prepareTestInputs(ExportTestProjects.logbackBugCase(actualScalaVersion)).fromRoot { root =>
+ exportCommand(".").call(cwd = root, stdout = os.Inherit)
+ val res = buildToolCommand(root, Some(mainClass), runMainArgs(Some(mainClass))*)
+ .call(cwd = root / outputDir)
+ val output = res.out.text(Charset.defaultCharset())
+ expect(output.contains("Hello"))
+ }
+
+ if (runExportTests) {
+ test("compile-time only for jsoniter macros") {
+ compileOnlyTest("main")
+ }
+ test("Scala.js") {
+ simpleTest(ExportTestProjects.jsTest(actualScalaVersion), mainClass = None)
+ }
+ test("zio test") {
+ testZioTest("ZioSpec")
+ }
+ test("Ensure test framework NPE is not thrown when depending on logback") {
+ logbackBugCase("main")
+ }
+ }
+}
diff --git a/modules/integration/src/test/scala/scala/cli/integration/ExportTestProjects.scala b/modules/integration/src/test/scala/scala/cli/integration/ExportTestProjects.scala
index 28c6508df6..7a280a3b54 100644
--- a/modules/integration/src/test/scala/scala/cli/integration/ExportTestProjects.scala
+++ b/modules/integration/src/test/scala/scala/cli/integration/ExportTestProjects.scala
@@ -5,7 +5,7 @@ import com.eed3si9n.expecty.Expecty.expect
import scala.cli.integration.Constants.munitVersion
object ExportTestProjects {
- def jvmTest(scalaVersion: String): TestInputs = {
+ def jvmTest(scalaVersion: String, mainClassName: String): TestInputs = {
val mainFile =
if (scalaVersion.startsWith("3."))
@@ -16,7 +16,7 @@ object ExportTestProjects {
|
|import scala.io.Source
|
- |object Hello {
+ |object $mainClassName {
| def main(args: Array[String]): Unit = {
| val message = "Hello from " + dotty.tools.dotc.config.Properties.simpleVersionString
| println(message)
@@ -33,7 +33,7 @@ object ExportTestProjects {
|
|import scala.io.Source
|
- |object Hello {
+ |object $mainClassName {
| def main(args: Array[String]): Unit = {
| val message = "Hello from " + scala.util.Properties.versionNumberString
| println(message)
@@ -42,8 +42,9 @@ object ExportTestProjects {
| }
|}
|""".stripMargin
+
TestInputs(
- os.rel / "Hello.scala" -> mainFile,
+ os.rel / s"$mainClassName.scala" -> mainFile,
os.rel / "Zio.test.scala" ->
"""|//> using dep "dev.zio::zio::1.0.8"
|//> using dep "dev.zio::zio-test-sbt::1.0.8"
@@ -197,9 +198,9 @@ object ExportTestProjects {
TestInputs(os.rel / "Test.scala" -> testFile)
}
- def pureJavaTest: TestInputs = {
+ def pureJavaTest(mainClass: String): TestInputs = {
val testFile =
- s"""public class ScalaCliJavaTest {
+ s"""public class $mainClass {
| public static void main(String[] args) {
| String className = "scala.concurrent.ExecutionContext";
| ClassLoader cl = Thread.currentThread().getContextClassLoader();
@@ -281,12 +282,15 @@ object ExportTestProjects {
|println("Hello")
|""".stripMargin)
- def extraSourceFromDirectiveWithExtraDependency(scalaVersion: String): TestInputs =
+ def extraSourceFromDirectiveWithExtraDependency(
+ scalaVersion: String,
+ mainClass: String
+ ): TestInputs =
TestInputs(
- os.rel / "Main.scala" ->
+ os.rel / s"$mainClass.scala" ->
s"""//> using scala "$scalaVersion"
|//> using file "Message.scala"
- |object Main extends App {
+ |object $mainClass extends App {
| println(Message(value = os.pwd.toString).value)
|}
|""".stripMargin,
@@ -317,21 +321,21 @@ object ExportTestProjects {
|""".stripMargin
)
- def scalaVersionTest(scalaVersion: String): TestInputs =
+ def scalaVersionTest(scalaVersion: String, mainClass: String): TestInputs =
TestInputs(
os.rel / "Hello.scala" ->
s"""//> using scala $scalaVersion
- |object Main extends App {
+ |object $mainClass extends App {
| println("Hello")
|}
|""".stripMargin
)
- def justTestScope(msg: String): TestInputs = TestInputs(
+ def justTestScope(testClass: String, msg: String): TestInputs = TestInputs(
os.rel / "MyTests.test.scala" ->
s"""//> using dep org.scalameta::munit::$munitVersion
|
- |class MyTests extends munit.FunSuite {
+ |class $testClass extends munit.FunSuite {
| test("foo") {
| assert(2 + 2 == 4)
| println("$msg")
diff --git a/modules/integration/src/test/scala/scala/cli/integration/MavenTestHelper.scala b/modules/integration/src/test/scala/scala/cli/integration/MavenTestHelper.scala
new file mode 100644
index 0000000000..7b6f99acf1
--- /dev/null
+++ b/modules/integration/src/test/scala/scala/cli/integration/MavenTestHelper.scala
@@ -0,0 +1,13 @@
+package scala.cli.integration
+
+trait MavenTestHelper {
+
+ protected def mavenCommand(args: String*): os.proc = os.proc(maven, args)
+
+ protected lazy val maven: os.Shellable =
+ Seq[os.Shellable](
+ "mvn",
+ "clean",
+ "compile"
+ )
+}
diff --git a/project/deps.sc b/project/deps.sc
index e6eb5c14f3..a01535d9e9 100644
--- a/project/deps.sc
+++ b/project/deps.sc
@@ -112,6 +112,12 @@ object Deps {
def javaSemanticdb = "0.10.0"
def javaClassName = "0.1.3"
def bloop = "1.5.17-sc-2"
+ def mavenVersion = "3.8.1"
+ def mavenScalaCompilerPluginVersion = "4.9.1"
+ def mavenExecPluginVersion = "3.3.0"
+ def mavenAppArtifactId = "maven-app"
+ def mavenAppGroupId = "com.example"
+ def mavenAppVersion = "0.1-SNAPSHOT"
}
// DO NOT hardcode a Scala version in this dependency string
// This dependency is used to ensure that Ammonite is available for Scala versions
diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md
index d6707d6d3b..2591db76cf 100644
--- a/website/docs/reference/cli-options.md
+++ b/website/docs/reference/cli-options.md
@@ -358,6 +358,12 @@ Available in commands:
Sets the export format to SBT
+### `--maven`
+
+Aliases: `--mvn`
+
+Sets the export format to Maven
+
### `--mill`
Sets the export format to Mill
@@ -380,6 +386,30 @@ Project name to be used on Mill build file
Version of SBT to be used for the export
+### `--mvn-version`
+
+Version of Maven Compiler Plugin to be used for the export
+
+### `--mvn-scala-version`
+
+Version of Maven Scala Plugin to be used for the export
+
+### `--mvn-exec-plugin-version`
+
+Version of Maven Exec Plugin to be used for the export
+
+### `--mvn-app-artifact-id`
+
+ArtifactId to be used for the maven export
+
+### `--mvn-app-group-id`
+
+GroupId to be used for the maven export
+
+### `--mvn-app-version`
+
+Version to be used for the maven export
+
### `--output`
Aliases: `-o`