Skip to content

Commit

Permalink
Support Scala Native build target (#2898)
Browse files Browse the repository at this point in the history
This allows to build static and dynamic libraries other than binaries.

Pull Request: #2898
  • Loading branch information
lolgab authored Nov 25, 2023
1 parent ea2ae16 commit b15576f
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 39 deletions.
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
}

0 comments on commit b15576f

Please sign in to comment.