Cross platform build with SBT

2019-01-26 20:10发布

问题:

I'm currently testing out SBT Native Packager and my desired result is to have a native installer for each supported platform. Obviously to do this the platform specific SBT tasks will need to run on that platform.

The build will either be done with Atlassian's Bamboo or JetBrains' Team City.

Ideally I would only do the compilation and tests once and reuse the same artifacts for the packaging tasks.

What is a good way to approach this with SBT?

One way I can think of is to do the compile and test on any platform and then publish these to a local repository. Then the package would use these somehow. However this would also require changing the packaging so that it doesn't depend on the compile task etc.

回答1:

TL;DR version: use separate sbt projects.

As you probably noted, the JDKPackager plugin creates native packages on the various platforms. The trick to building and testing the primary artifacts once is to publish them to an artifact server, and then have a separate project for creating the installer.

If you your project only needs one installer per platform, then all you should need to do is add the dependency, set the mainClass key, and add enablePlugins(JDKPackagerPlugin):

enablePlugins(JDKPackagerPlugin)

name := "JDKPackagerPlugin Example"

version := "0.1.0"

organization := "com.foo.bar"

libraryDependencies += "com.foo.bar" %% "myProject" % "0.1.0"

mainClass in Compile := Some("com.foo.bar.ExampleApp")

// Optional: provide application file associations
jdkPackagerAssociations := Seq(
    FileAssociation("foobar", "application/foobar", "Foobar file type"),
    FileAssociation("barbaz", "application/barbaz", "Barbaz file type", jdkAppIcon.value)
)

If you have a scenario where you need multiple installers per platform (e.g. command-line tools vs. GUI), I typically structure a project to have a subdirectory called "packaging" with a stand-alone build.xml file, aggregating separate sub-projects defining each installer configuration:

// Settings common across subprojects. Could also do this with a 
// project-specific `AutoPlugin`
val baseSettings = Seq(
    libraryDependencies += "com.foo.bar" %% "myProject" % "0.1.0"
)

// The packaging aggregation project
lazy val packaging = project
   .in(file("."))
   .aggregate(a, b)

// Project with CLI configuration
lazy val a = Project(id = "my-project-cli", base = file("my-project-cli"))
  .settings(baseSettings: _*)

// Project with GUI configuration    
lazy val b = Project(id = "my-project-gui", base = file("my-project-gui"))
  .settings(baseSettings: _*)

// Create a task for invoking the sub-projects as needed
val packageSubs = taskKey[Seq[File]]("Build packages in subprojects")
(packageSubs in packaging) := {
  Seq(
    (packageBin.in(a, Universal)).value,
    (packageBin.in(b, JDKPackager)).value
  ) 
}

I find breaking up the installer configurations like this helps keep straight the dependencies, and effects of specific customizations.