8
votes

I have a multi-project build configuration in SBT that consists of two distinct modules that do not depend on each other. They just (happen to) belong to the same product.

The project layout is as follows:

myLib
  + build.sbt
  + myProject_1
  |    + build.sbt
  |    + src
  |        + ...
  + myProject_2
  |    + build.sbt
  |    + src
  |        + ...
  + project
       + Build.scala

project/Build.scala contains common settings and looks like this:

import sbt._
import Keys._

object ApplicationBuild extends Build {

  val appVersion = "1.0-SNAPSHOT"

  val defaultScalacOptions = Seq(
    "-unchecked", "-deprecation", "-feature", "-language:reflectiveCalls",
    "-language:implicitConversions", "-language:postfixOps",
    "-language:dynamics", "-language:higherKinds", "-language:existentials",
    "-language:experimental.macros", "-Xmax-classfile-name", "140")

  val defaultResolvers = Seq(
    "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
  )

  val defaultLibraryDependencies = Seq(
    "org.specs2" %% "specs2" % "1.14" % "test",
    "org.slf4j" % "slf4j-nop" % "1.7.5" % "test"
  )

  val defaultSettings = Defaults.defaultSettings ++ Seq(
    scalacOptions ++= defaultScalacOptions,
    resolvers ++= defaultResolvers,
    libraryDependencies ++= defaultLibraryDependencies
  )
}

The root build file build.sbt is just needed to put all together [I also tried to remove it.. but then the sub-projects don't get compiled anymore]:

lazy val myProject_1 = project.in(file("myProject_1"))

lazy val myProject_2 = project.in(file("myProject_2"))

And finally here is myProject_1/build.sbt [I have just omitted myProject_2/build.sbt because it is very similar and does not provide any added value for the topic]:

name := "myProject_1"

version := ApplicationBuild.appVersion

ApplicationBuild.defaultSettings

libraryDependencies ++= Seq(
  "commons-codec" % "commons-codec" % "1.8"
)

The project compiles successfully... but when I issue the command sbt package, then an empty jar is generated in the root target directory:

j3d@gonzo:~/myLib/$ ll target/scala-2.10
drwxrwxr-x 2 j3d j3d 4096 Dez 23 17:13 ./
drwxrwxr-x 5 j3d j3d 4096 Dez 23 17:13 ../
-rw-rw-r-- 1 j3d j3d  273 Dez 23 17:13 brix_2.10-0.1-SNAPSHOT.jar

Am I missing something? How can I prevent SBT from generating this empty and useless jar?

5

5 Answers

10
votes

This is ridiculously difficult to achieve. The following set of options appears to achieve the goal. Subsets of these options have a tendency to burn you by suppressing the root jar via some execution paths but not others, so if you want to find the minimum cover make sure neither 'sbt package' and 'sbt publishLocal' creates the jar. It took me plenty long to find this set and I am disinclined to iterate further.

      Keys.`package` :=  file(""),
packageBin in Global :=  file(""),
   packagedArtifacts :=  Map(),

sbt: making simple things borderline impossible since 2008.

6
votes

I propose a workaround with the following (re)definition of package in build.sbt:

Keys.`package` := {
    (Keys.`package` in (a, Compile)).value
    (Keys.`package` in (b, Compile)).value
}

where a and b are (sub)modules.

lazy val a = project

lazy val b = project

Since package is a keyword in Scala it needs quotes to be resolvable.

It also needs to be fully-qualified since package is imported by Keys._ and sbt._ imports that are by default in .sbt build files.

/Users/jacek/sandbox/so/multi-packageBin/build.sbt:5: error: reference to package is ambiguous;
it is imported twice in the same scope by
import Keys._
and import sbt._
`package` := {
^
[error] Type error in expression

Please also note that I'm using SBT 0.13.2-SNAPSHOT (built from the sources) so use with caution (yet I doubt it will make a difference in any version of SBT 0.13+).

[multi-packagebin]> */*:sbtVersion
[info] 0.13.2-SNAPSHOT
[multi-packagebin]> projects
[info] In file:/Users/jacek/sandbox/so/multi-packageBin/
[info]     a
[info]     b
[info]   * multi-packagebin
[multi-packagebin]> package
[info] Updating {file:/Users/jacek/sandbox/so/multi-packageBin/}a...
[info] Updating {file:/Users/jacek/sandbox/so/multi-packageBin/}b...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Resolving org.scala-lang#scala-reflect;2.10.3 ...
[info] Packaging /Users/jacek/sandbox/so/multi-packageBin/b/target/scala-2.10/b_2.10-0.1-SNAPSHOT.jar ...
[info] Done packaging.
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Packaging /Users/jacek/sandbox/so/multi-packageBin/a/target/scala-2.10/a_2.10-0.1-SNAPSHOT.jar ...
[info] Done packaging.
[success] Total time: 1 s, completed Feb 23, 2014 10:12:41 AM
1
votes

I use the following

lazy val root: Project = Project(
  id        = "root",
  base      = file("."),
  aggregate = Seq(proj1, proj2),
  settings  = Project.defaultSettings ++ Seq(
    publishArtifact in (Compile, packageBin) := false, // there are no binaries
    publishArtifact in (Compile, packageDoc) := false, // there are no javadocs
    publishArtifact in (Compile, packageSrc) := false  // there are no sources
  )
)

That still generates the empty packaged jar, but it will not export it when publishing.

1
votes

I'm not entirely sure on all the implications of this as I am new to SBT but the following seems to do the job:

compile := sbt.inc.Analysis.Empty

Overriding the packaging tasks didn't seem right to me. So I used inspect (plus the source as I didn't know what to look for at first) to find where exactly these mappings were coming from and found these dependencies:

compile:package -> compile:packageBin -> compile:packageBin::packageConfiguration -> compile:packageBin::mappings -> compile:packageBin::products (provided by compile:products) -> compile:compile

I don't know why it has to be this difficult. Most of my problems are coming from trying to have a multi project build.

0
votes

I add the following settings to the project root:

Compile / packageBin := { file("") }
Compile / packageSrc := { file("") }
Compile / packageDoc := { file("") }

By changing tasks at Compile scope, the default package task is also suppressed.