Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Scala Native build target #2898

Merged
merged 4 commits into from
Nov 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 34 additions & 22 deletions scalanativelib/src/mill/scalanativelib/ScalaNativeModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -210,30 +210,35 @@ trait ScalaNativeModule extends ScalaModule { outer =>

def nativeOptimize: Target[Boolean] = T { nativeOptimizeInput().getOrElse(true) }

/** Build target for current compilation */
def nativeBuildTarget: Target[BuildTarget] = T { BuildTarget.Application }

private def nativeConfig: Task[NativeConfig] = T.task {
val classpath = runClasspath().map(_.path).filter(_.toIO.exists).toList

NativeConfig(
scalaNativeBridge().config(
finalMainClass(),
classpath.map(_.toIO),
nativeWorkdir().toIO,
nativeClang().toIO,
nativeClangPP().toIO,
nativeTarget(),
nativeCompileOptions(),
nativeLinkingOptions(),
nativeGC(),
nativeLinkStubs(),
nativeLTO().value,
releaseMode().value,
nativeOptimize(),
nativeEmbedResources(),
nativeIncrementalCompilation(),
nativeDump(),
toWorkerApi(logLevel())
)
)
scalaNativeBridge().config(
finalMainClassOpt(),
classpath.map(_.toIO),
nativeWorkdir().toIO,
nativeClang().toIO,
nativeClangPP().toIO,
nativeTarget(),
nativeCompileOptions(),
nativeLinkingOptions(),
nativeGC(),
nativeLinkStubs(),
nativeLTO().value,
releaseMode().value,
nativeOptimize(),
nativeEmbedResources(),
nativeIncrementalCompilation(),
nativeDump(),
toWorkerApi(logLevel()),
toWorkerApi(nativeBuildTarget())
) match {
case Right(config) => Result.Success(NativeConfig(config))
case Left(error) => Result.Failure(error)
}
}

private[scalanativelib] def toWorkerApi(logLevel: api.NativeLogLevel): workerApi.NativeLogLevel =
Expand All @@ -245,11 +250,18 @@ trait ScalaNativeModule extends ScalaModule { outer =>
case api.NativeLogLevel.Trace => workerApi.NativeLogLevel.Trace
}

private[scalanativelib] def toWorkerApi(buildTarget: api.BuildTarget): workerApi.BuildTarget =
buildTarget match {
case api.BuildTarget.Application => workerApi.BuildTarget.Application
case api.BuildTarget.LibraryDynamic => workerApi.BuildTarget.LibraryDynamic
case api.BuildTarget.LibraryStatic => workerApi.BuildTarget.LibraryStatic
}

// Generates native binary
def nativeLink = T {
os.Path(scalaNativeBridge().nativeLink(
nativeConfig().config,
(T.dest / "out").toIO
T.dest.toIO
))
}

Expand Down
18 changes: 18 additions & 0 deletions scalanativelib/src/mill/scalanativelib/api/ScalaNativeApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,21 @@ object NativeConfig {
def apply(config: Object): NativeConfig =
new NativeConfig(config)
}

sealed trait BuildTarget
object BuildTarget {

/** Link code as application */
case object Application extends BuildTarget

/** Link code as shared/dynamic library */
case object LibraryDynamic extends BuildTarget

/** Link code as static library */
case object LibraryStatic extends BuildTarget

implicit val rwApplication: ReadWriter[Application.type] = macroRW[Application.type]
implicit val rwLibraryDynamic: ReadWriter[LibraryDynamic.type] = macroRW[LibraryDynamic.type]
implicit val rwLibraryStatic: ReadWriter[LibraryStatic.type] = macroRW[LibraryStatic.type]
implicit val rw: ReadWriter[BuildTarget] = macroRW[BuildTarget]
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ private[scalanativelib] trait ScalaNativeWorkerApi {
def defaultGarbageCollector(): String

def config(
mainClass: String,
mainClass: Either[String, String],
classpath: Seq[File],
nativeWorkdir: File,
nativeClang: File,
Expand All @@ -27,8 +27,9 @@ private[scalanativelib] trait ScalaNativeWorkerApi {
nativeEmbedResources: Boolean,
nativeIncrementalCompilation: Boolean,
nativeDump: Boolean,
logLevel: NativeLogLevel
): Object
logLevel: NativeLogLevel,
buildTarget: BuildTarget
): Either[String, Object]

def nativeLink(nativeConfig: Object, outPath: File): File

Expand All @@ -48,3 +49,10 @@ private[scalanativelib] object NativeLogLevel {
case object Debug extends NativeLogLevel(500)
case object Trace extends NativeLogLevel(600)
}

private[scalanativelib] sealed trait BuildTarget
private[scalanativelib] object BuildTarget {
case object Application extends BuildTarget
case object LibraryDynamic extends BuildTarget
case object LibraryStatic extends BuildTarget
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import mill.scalanativelib.worker.api._
import scala.scalanative.util.Scope
import scala.scalanative.build.{
Build,
BuildTarget => ScalaNativeBuildTarget,
Config,
Discover,
GC,
Logger,
LTO,
Mode,
NativeConfig => ScalaNativeNativeConfig
NativeConfig => ScalaNativeNativeConfig,
MillUtils
}
import scala.scalanative.nir.Versions
import scala.scalanative.testinterface.adapter.TestAdapter
Expand Down Expand Up @@ -45,7 +47,7 @@ class ScalaNativeWorkerImpl extends mill.scalanativelib.worker.api.ScalaNativeWo
def defaultGarbageCollector(): String = GC.default.name

def config(
mainClass: String,
mainClass: Either[String, String],
classpath: Seq[File],
nativeWorkdir: File,
nativeClang: File,
Expand All @@ -61,9 +63,9 @@ class ScalaNativeWorkerImpl extends mill.scalanativelib.worker.api.ScalaNativeWo
nativeEmbedResources: Boolean,
nativeIncrementalCompilation: Boolean,
nativeDump: Boolean,
logLevel: NativeLogLevel
): Object = {
val entry = if (patchIsGreaterThanOrEqual(3)) mainClass else mainClass + "$"
logLevel: NativeLogLevel,
buildTarget: BuildTarget
): Either[String, Config] = {
var nativeConfig =
ScalaNativeNativeConfig.empty
.withClang(nativeClang.toPath)
Expand All @@ -77,24 +79,71 @@ class ScalaNativeWorkerImpl extends mill.scalanativelib.worker.api.ScalaNativeWo
.withOptimize(nativeOptimize)
.withLTO(LTO(nativeLTO))
.withDump(nativeDump)
if (patchIsGreaterThanOrEqual(8)) {
val nativeBuildTarget = buildTarget match {
case BuildTarget.Application => ScalaNativeBuildTarget.application
case BuildTarget.LibraryDynamic => ScalaNativeBuildTarget.libraryDynamic
case BuildTarget.LibraryStatic => ScalaNativeBuildTarget.libraryStatic
}
nativeConfig = nativeConfig.withBuildTarget(nativeBuildTarget)
} else {
if (buildTarget != BuildTarget.Application) {
return Left("nativeBuildTarget not supported. Please update to Scala Native 0.4.8+")
}
}
if (patchIsGreaterThanOrEqual(4)) {
nativeConfig = nativeConfig.withEmbedResources(nativeEmbedResources)
}
if (patchIsGreaterThanOrEqual(9)) {
nativeConfig = nativeConfig.withIncrementalCompilation(nativeIncrementalCompilation)
}
val config =
Config.empty
.withMainClass(entry)
.withClassPath(classpath.map(_.toPath))
.withWorkdir(nativeWorkdir.toPath)
.withCompilerConfig(nativeConfig)
.withLogger(logger(logLevel))
config
var config = Config.empty
.withClassPath(classpath.map(_.toPath))
.withWorkdir(nativeWorkdir.toPath)
.withCompilerConfig(nativeConfig)
.withLogger(logger(logLevel))

if (buildTarget == BuildTarget.Application) {
mainClass match {
case Left(error) => return Left(error)
case Right(mainClass) =>
val entry = if (patchIsGreaterThanOrEqual(3)) mainClass else mainClass + "$"
config = config.withMainClass(entry)
}
}

Right(config)
}

def nativeLink(nativeConfig: Object, outPath: File): File = {
def nativeLink(nativeConfig: Object, outDirectory: File): File = {
val config = nativeConfig.asInstanceOf[Config]
val compilerConfig = config.compilerConfig

val name = if (patchIsGreaterThanOrEqual(8)) {
val isWindows = MillUtils.targetsWindows(config)
val isMac = MillUtils.targetsMac(config)

val ext = if (compilerConfig.buildTarget == ScalaNativeBuildTarget.application) {
if (MillUtils.targetsWindows(config)) ".exe" else ""
} else if (compilerConfig.buildTarget == ScalaNativeBuildTarget.libraryDynamic) {
if (isWindows) ".dll"
else if (isMac) ".dylib"
else ".so"
} else if (compilerConfig.buildTarget == ScalaNativeBuildTarget.libraryStatic) {
if (isWindows) ".lib"
else ".a"
} else {
throw new RuntimeException(s"Unknown buildTarget ${compilerConfig.buildTarget}")
}

val namePrefix = if (compilerConfig.buildTarget == ScalaNativeBuildTarget.application) ""
else {
if (isWindows) "" else "lib"
}
s"${namePrefix}out${ext}"
} else "out"

val outPath = new File(outDirectory, name)
Build.build(config, outPath.toPath)(Scope.unsafe())
outPath
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package scala.scalanative.build

object MillUtils {
def targetsWindows(config: Config): Boolean = config.targetsWindows
def targetsMac(config: Config): Boolean = config.targetsMac
}