Skip to content

Commit

Permalink
Add export --json option
Browse files Browse the repository at this point in the history
  • Loading branch information
MaciejG604 committed Feb 13, 2023
1 parent f0e6e9b commit 7f6a5fa
Show file tree
Hide file tree
Showing 10 changed files with 402 additions and 21 deletions.
44 changes: 33 additions & 11 deletions modules/cli/src/main/scala/scala/cli/commands/export/Export.scala
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
package scala.cli.commands.export0

import caseapp.*
import com.github.plokhotnyuk.jsoniter_scala.core.*
import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker
import com.google.gson.{Gson, GsonBuilder}
import coursier.cache.FileCache
import coursier.util.{Artifact, Task}

import java.io.{OutputStreamWriter, PrintStream}
import java.nio.charset.{Charset, StandardCharsets}

import scala.build.EitherCps.{either, value}
import scala.build.*
import scala.build.errors.BuildException
import scala.build.input.Inputs
import scala.build.internal.{Constants, CustomCodeWrapper}
import scala.build.options.{BuildOptions, Scope}
import scala.build.options.{BuildOptions, Platform, Scope}
import scala.cli.CurrentParams
import scala.cli.commands.ScalaCommand
import scala.cli.commands.shared.SharedOptions
import scala.cli.exportCmd.*
import scala.util.Using

object Export extends ScalaCommand[ExportOptions] {
override def scalaSpecificationLevel = SpecificationLevel.RESTRICTED
Expand Down Expand Up @@ -53,9 +60,13 @@ object Export extends ScalaCommand[ExportOptions] {
}

// FIXME Auto-update those
def sbtBuildTool(extraSettings: Seq[String], sbtVersion: String, logger: Logger): Sbt =
def sbtProjectDescriptor(extraSettings: Seq[String], sbtVersion: String, logger: Logger): Sbt =
Sbt(sbtVersion, extraSettings, logger)
def millBuildTool(cache: FileCache[Task], projectName: Option[String], logger: Logger): Mill = {
def millProjectDescriptor(
cache: FileCache[Task],
projectName: Option[String],
logger: Logger
): Mill = {
val launcherArtifacts = Seq(
os.rel / "mill" -> s"https://github.com/lefou/millw/raw/${Constants.lefouMillwRef}/millw",
os.rel / "mill.bat" -> s"https://github.com/lefou/millw/raw/${Constants.lefouMillwRef}/millw.bat"
Expand All @@ -76,6 +87,9 @@ object Export extends ScalaCommand[ExportOptions] {
Mill(Constants.millVersion, projectName, launchers, logger)
}

def jsonProjectDescriptor(projectName: Option[String], logger: Logger): Json =
Json(projectName, logger)

override def sharedOptions(opts: ExportOptions): Option[SharedOptions] = Some(opts.shared)

override def runCommand(options: ExportOptions, args: RemainingArgs, logger: Logger): Unit = {
Expand All @@ -91,6 +105,7 @@ object Export extends ScalaCommand[ExportOptions] {
sys.exit(1)
}

val shouldExportToJson = options.json.getOrElse(false)
val shouldExportToMill = options.mill.getOrElse(false)
val shouldExportToSbt = options.sbt.getOrElse(false)
if (shouldExportToMill && shouldExportToSbt) {
Expand All @@ -100,8 +115,12 @@ object Export extends ScalaCommand[ExportOptions] {
sys.exit(1)
}

val buildToolName = if (shouldExportToMill) "mill" else "sbt"
System.out.println(s"Exporting to a $buildToolName project...")
if (!shouldExportToJson) {
val buildToolName = if (shouldExportToMill) "mill" else "sbt"
println(s"Exporting to a $buildToolName project...")
}
else
println(s"Exporting to JSON...")

val inputs = options.shared.inputs(args.all).orExit(logger)
CurrentParams.workspaceOpt = Some(inputs.workspace)
Expand Down Expand Up @@ -149,16 +168,19 @@ object Export extends ScalaCommand[ExportOptions] {
}

val sbtVersion = options.sbtVersion.getOrElse("1.6.1")
def sbtBuildTool0 =
sbtBuildTool(options.sbtSetting.map(_.trim).filter(_.nonEmpty), sbtVersion, logger)

val buildTool =
def sbtProjectDescriptor0 =
sbtProjectDescriptor(options.sbtSetting.map(_.trim).filter(_.nonEmpty), sbtVersion, logger)

val projectDescriptor =
if (shouldExportToMill)
millBuildTool(options.shared.coursierCache, options.project, logger)
millProjectDescriptor(options.shared.coursierCache, options.project, logger)
else if (shouldExportToJson)
jsonProjectDescriptor(options.project, logger)
else // shouldExportToSbt isn't checked, as it's treated as default
sbtBuildTool0
sbtProjectDescriptor0

val project = buildTool.`export`(optionsMain0, optionsTest0, sourcesMain, sourcesTest)
val project = projectDescriptor.`export`(optionsMain0, optionsTest0, sourcesMain, sourcesTest)

os.makeDir.all(dest)
project.writeTo(dest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ final case class ExportOptions(
sbt: Option[Boolean] = None,
@Group("Build Tool export options")
mill: Option[Boolean] = None,
@Group("Build Tool export options")
json: Option[Boolean] = None,

@Name("setting")
@Group("Build Tool export options")
Expand All @@ -29,7 +31,7 @@ final case class ExportOptions(
sbtVersion: Option[String] = None,
@Name("o")
@Group("Build Tool export options")
output: Option[String] = None
output: Option[String] = None,
) extends HasSharedOptions
// format: on
object ExportOptions {
Expand Down
186 changes: 186 additions & 0 deletions modules/cli/src/main/scala/scala/cli/exportCmd/Json.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package scala.cli.exportCmd

import com.github.plokhotnyuk.jsoniter_scala.core.WriterConfig
import coursier.ivy.IvyRepository
import coursier.maven.MavenRepository
import coursier.parse.RepositoryParser
import coursier.{LocalRepositories, Repositories}
import dependency.NoAttributes

import java.nio.charset.StandardCharsets
import java.nio.file.Path

import scala.build.internal.Constants
import scala.build.internal.Runner.frameworkName
import scala.build.options.{BuildOptions, Platform, ScalaJsOptions, ScalaNativeOptions, Scope}
import scala.build.testrunner.AsmTestRunner
import scala.build.{Logger, Sources}
import scala.cli.util.SeqHelpers.*

final case class Json(
projectName: Option[String] = None,
logger: Logger
) extends ProjectDescriptor {
private val charSet = StandardCharsets.UTF_8

private def sourcesSettings(mainSources: Sources, testSources: Sources): JsonProject = {
val mainSubPaths = ProjectDescriptor.sources(mainSources, charSet).map(_._1.toNIO.toString)
val testSubPaths = ProjectDescriptor.sources(testSources, charSet).map(_._1.toNIO.toString)
JsonProject(mainSources = mainSubPaths, testSources = testSubPaths)
}

private def scalaVersionSettings(options: BuildOptions): JsonProject = {
val sv = options.scalaOptions.scalaVersion
.flatMap(_.versionOpt) // FIXME If versionOpt is empty, the project is pure Java
.getOrElse(Constants.defaultScalaVersion)

JsonProject(scalaVersion = Some(sv))
}

private def scalaCompilerPlugins(buildOptions: BuildOptions): JsonProject =
JsonProject(scalaCompilerPlugins =
buildOptions.scalaOptions.compilerPlugins.toSeq.map(_.value.render)
)

private def scalacOptionsSettings(buildOptions: BuildOptions): JsonProject =
JsonProject(scalacOptions = buildOptions.scalaOptions.scalacOptions.toSeq.map(_.value.value))

private def scalaJsSettings(options: ScalaJsOptions): JsonProject = {

val scalaJsVersion = Some(options.version.getOrElse(Constants.scalaJsVersion))

val moduleKindDecls =
if (options.moduleKindStr.isEmpty) Nil
else
Seq(options.moduleKind(logger))

JsonProject(
scalaJsVersion = scalaJsVersion,
extraDecls = moduleKindDecls
)
}

private def scalaNativeSettings(options: ScalaNativeOptions): JsonProject = {
val scalaNativeVersion = Some(options.version.getOrElse(Constants.scalaNativeVersion))
JsonProject(scalaNativeVersion = scalaNativeVersion)
}

private def dependencySettings(
mainOptions: BuildOptions,
testOptions: BuildOptions
): JsonProject = {
def getExportDependencyFormat(
options: BuildOptions,
scope: Scope
): Seq[ExportDependencyFormat] = {
val artifacts = options.artifacts(Logger.nop, scope).toSeq

val allDeps = for {
artifact <- artifacts
detailedArtifact <- artifact.detailedArtifacts
exportDep = ExportDependencyFormat(detailedArtifact._1)
} yield exportDep

val directDeps = options.classPathOptions.extraDependencies.toSeq.map(_.value)

allDeps
.filter(exportDep =>
directDeps.exists(anyDep =>
anyDep.module.organization == exportDep.groupId &&
(anyDep.module.name == exportDep.artifactId.name
|| anyDep.module.name == exportDep.artifactId.name) &&
anyDep.version == exportDep.version
)
)
.distinct
.sorted
}

val mainDeps = getExportDependencyFormat(mainOptions, Scope.Main)
val testDeps = getExportDependencyFormat(testOptions, Scope.Test)

JsonProject(mainDeps = mainDeps, testDeps = testDeps)
}

private def repositorySettings(options: BuildOptions): JsonProject = {
val resolvers = options.finalRepositories
.getOrElse(Nil)
.appended(Repositories.central)
.appended(LocalRepositories.ivy2Local)
.collect {
case repo: MavenRepository =>
ExportResolverFormat(
name = "MavenRepository",
location = Some(repo.root)
) // TODO repo.authentication?
case repo: IvyRepository =>
ExportResolverFormat(
name = "IvyRepository",
location = repo.pattern.chunks.headOption.map(_.string)
) // TODO repo.authentication?
}
.distinct
.sortBy(_.name)

JsonProject(resolvers = resolvers)
}

private def customResourcesSettings(options: BuildOptions): JsonProject =
JsonProject(
resourcesDirs = options.classPathOptions.resourcesDir.map(_.toNIO.toString)
)

private def customJarsSettings(options: BuildOptions): JsonProject = {

val customCompileOnlyJarsDecls =
options.classPathOptions.extraCompileOnlyJars.map(_.toNIO.toString)

val customJarsDecls = options.classPathOptions.extraClassPath.map(_.toNIO.toString)

JsonProject(
extraDecls = customCompileOnlyJarsDecls ++ customJarsDecls
)
}

private def platformSettings(options: BuildOptions): JsonProject = {
val platform = options.scalaOptions.platform.map(_.value.repr)
.orElse(Some(Platform.JVM.repr))

JsonProject(platform = platform)
}

def `export`(
optionsMain: BuildOptions,
optionsTest: BuildOptions,
sourcesMain: Sources,
sourcesTest: Sources
): JsonProject = {

// FIXME Put a sensible value in JsonProject.nameOpt

val baseSettings = JsonProject(
projectName = projectName,
mainClass = optionsMain.mainClass
)

val settings = Seq(
baseSettings,
sourcesSettings(sourcesMain, sourcesTest),
scalaVersionSettings(optionsMain),
platformSettings(optionsMain),
scalacOptionsSettings(optionsMain),
scalaCompilerPlugins(optionsMain),
dependencySettings(optionsMain, optionsTest),
repositorySettings(optionsMain),
if (optionsMain.platform.value == Platform.JS) scalaJsSettings(optionsMain.scalaJsOptions)
else JsonProject(),
if (optionsMain.platform.value == Platform.Native)
scalaNativeSettings(optionsMain.scalaNativeOptions)
else JsonProject(),
customResourcesSettings(optionsMain),
customJarsSettings(optionsMain)
)

settings.foldLeft(JsonProject())(_ + _)
}
}
Loading

0 comments on commit 7f6a5fa

Please sign in to comment.