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`