I'm a complete beginner in sbt/scala. I have a multi module sbt project that I build with sbt assembly, which creates jar files of my modules. When I tried sbt module2/publish I noticed the output jar file in my directory is super slim compared to the jar files created by sbt-assembly and its throwing error when I tried to run:
Error: Unable to initialize main class
com.example.module2.service.Server
Caused by: java.lang.NoClassDefFoundError: scala/Function0
What do I need to include in my build.sbt file to make sbt publishing possible?
my-proj
├── Build.scala
├── common
│ ├── build.sbt
│ └── src
├── module1
│ ├── build.sbt
│ └── src
├── module2
│ ├── build.sbt
│ └── src
└── project
├── build.properties
└── plugins.sbt
Build.scala
lazy val my_proj = (project in file("."))
.aggregate(common, module1, module2)
lazy val common = project
lazy val common = (project in file("commons"))
lazy val module1 = (project in file("module1"))
.dependsOn(common, module2)
lazy val module1 = (project in file("module1"))
.dependsOn(common)
module1/build.sbt
name := "module1"
version := "0.1"
organization := "com.example"
scalaVersion := "2.11.12"
val akkaVersion = "2.5.16"
scalacOptions ++= Seq("-unchecked", "-deprecation", "-Xcheckinit", "-encoding", "utf8")
fork := true
/** Dependencies */
resolvers ++= Seq("Akka Repository" at "http://repo.akka.io/releases/",
"Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
)
publishTo := Some(Resolver.file("file", new File("/tmp/my/artifactory")))
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-library" % scalaVersion
, "org.scala-lang" % "scala-compiler" % scalaVersion
, "org.scala-lang" % "scala-reflect" % scalaVersion
, "com.typesafe.akka" %% "akka-actor" % akkaVersion
)
assemblyMergeStrategy in assembly := {
case "META-INF\\io.netty.versions.properties" => MergeStrategy.first
case m if m.toLowerCase.endsWith("manifest.mf") => MergeStrategy.discard
case m if m.toLowerCase.matches("meta-inf.*\\.sf$") => MergeStrategy.discard
case m if m.toLowerCase.startsWith("meta-inf/services/") => MergeStrategy.filterDistinctLines
case "reference.conf" => MergeStrategy.concat
case _ => MergeStrategy.first
}
To publish a "fat" jar to a repository, you have to add the following to build.sbt of each sub-project you want to publish.
Compile / assembly / artifact ~= { art =>
art.withClassifier(Some("fat"))
}
addArtifact(Compile / assembly / artifact, assembly).settings
Related
I am using sbt 1.2.8 with assembly plugin. This is my sbt file:
name := "my-project"
version := "0.1"
scalaVersion := "2.11.8"
libraryDependencies ++= Seq(
... some dependency ...
)
mainClass in (Compile, assembly) := Some("some.package.MyMainClass")
assemblyMergeStrategy in assembly := {
case PathList("META-INF", xs # _*) => MergeStrategy.discard
case x => MergeStrategy.first
}
After I run the command sbt assembly configured in assembly.sbt with:
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.7")
I get the following content from the extracted file:
scala-2.11 $ cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Implementation-Title: my-project
Implementation-Version: 0.1
Specification-Vendor: default
Specification-Title: my-project
Implementation-Vendor-Id: default
Specification-Version: 0.1
Implementation-Vendor: default
but I cannot see where my main class is specified. Any idea?
It works for me. The only change I did is rename assembly.sbt to plugins.sbt. Try it out.
I want to use the ScalaPB plugin in my SBT project. But when I try to compile the project I get an error that states the "object gen is not a member of package scalapb". So how do I configure this plugin to work with my project?
I followed the instructions on the Github page and it didn't work.
My project has the following structure, in other words the standard Maven project structure:
.
├── build.sbt
├── ci
│ └── checkstyle
├── LICENSE
├── project
│ ├── build.properties
│ ├── Dependencies.scala
│ ├── plugins.sbt
│ ├── project
│ └── target
├── src
│ ├── main
│ └── test
└── version.sbt
This is the Dependencies.scala file:
import sbt._
object Dependencies {
lazy val scalatestVersion = "3.0.5"
lazy val scalamockVersion = "4.1.0"
lazy val scalaPbcVersion = "0.8.3"
// Libraries for Protobuf
val scalaPbc = "com.thesamet.scalapb" %% "compilerplugin" % scalaPbcVersion
// Libraries for Testing
val scalatest = "org.scalatest" %% "scalatest" % scalatestVersion % Test
val scalamock = "org.scalamock" %% "scalamock" % scalamockVersion % Test
// Projects
val groupBackendDependencies = Seq(
scalatest, scalamock, scalaPbc)
}
This is the plugins.sbt file:
// The Typesafe repository
resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/maven-releases/"
// for autoplugins
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.6" withSources())
dependencyOverrides += "com.puppycrawl.tools" % "checkstyle" % "8.12"
// Scala checkstyle
addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0")
// Scala Protobuf
addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.19")
This is the build.sbt file. It is where the error occurs.
/** ****************************************************************************
* <REDACTED>
* ****************************************************************************
*/
enablePlugins(UniversalPlugin)
/** ****************************************************************************
* Application related configurations
* ****************************************************************************
*/
organization := "<REDACTED>"
name := "<REDACTED>"
packageName := "<REDACTED>"
/** ****************************************************************************
* Compilation related
* ****************************************************************************
*/
scalaVersion := "2.12.7"
scalacOptions ++= Seq("-target:jvm-1.8",
"-unchecked",
"-deprecation",
"-encoding", "utf8",
"-feature",
"-Ywarn-adapted-args",
"-Ywarn-dead-code")
javacOptions in(Compile, compile) ++= Seq("-source", "11",
"-target", "11",
"-g:lines")
logLevel := sbt.Level.Warn
exportJars := true
libraryDependencies ++= Dependencies.groupBackendDependencies
/** ****************************************************************************
* Packaging related configurations
* ****************************************************************************
*/
packageName in Universal := s"${packageName.value}-${version.value}"
exportJars := true
//By default, the dist task will include the API documentation in the generated package.
//Below instruction will exclude them/
sources in(Compile, doc) := Seq.empty
publishArtifact in(Compile, packageDoc) := false
/** ****************************************************************************
* CI : Scala Checkstyle
* Ref: http://www.scalastyle.org/sbt.html
* Usage: sbt scalastyle
* ****************************************************************************
*/
lazy val scalaCheckstyle = "ci/checkstyle/scala/scalastyle-config.xml"
scalastyleConfig := baseDirectory(_ / scalaCheckstyle).value
scalastyleFailOnWarning := true
/** ****************************************************************************
* CI : Pipeline Simulation
* Usage: sbt pipeline-ci
* ****************************************************************************
*/
commands += Command.command("pipeline-ci") { state =>
"clean" ::
"compile" ::
"test" ::
state
}
/**
* The error occurs here.
*/
PB.targets in Compile := Seq(
scalapb.gen() -> (sourceManaged in Compile).value
)
I expect that this project should compile with out any errors when running
sbt clean compile
But instead I get this stacktrace
/home/my-project/build.sbt:73: error: object gen is not a member of package scalapb
scalapb.gen() -> (sourceManaged in Compile).value
^
[error] Type error in expression
You are including ScalaPB's compiler plugin ("com.thesamet.scalapb" %% "compilerplugin" % scalaPbcVersion) as a library dependency of your project. The compiler plugin needs to be a dependency of your build project. To accomplish that, it needs to be add as a library dependency in your project/plugins.sbt.
I've isolated this to a very simple test project that has no purpose other than simple log4j2test configuration + usage. The file directory structure:
.
├── build.sbt
└── src
└── main
├── resources
│ └── log4j2.xml
└── scala
└── mycompany
└── myapp
└── SimpleMain.scala
Build.sbt:
name := """log4j2test"""
version := "1.0.0-SNAPSHOT"
scalaVersion := "2.12.1"
scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8")
libraryDependencies ++= {
Seq(
"org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.8",
"org.apache.logging.log4j" % "log4j-api" % "2.8",
"org.apache.logging.log4j" % "log4j-core" % "2.8",
"com.fasterxml.jackson.dataformat" % "jackson-dataformat-yaml" % "2.8.6"
)
}
log4j2.xml contents are copy/pasted from example in official documentation: https://logging.apache.org/log4j/2.x/manual/configuration.html
SimpleMain.scala:
package mycompany.myapp
import org.slf4j.LoggerFactory
object SimpleMain {
val logger = LoggerFactory.getLogger(getClass())
def main(args: Array[String]): Unit = {
println("simple println test")
logger.info("this is a logger.info message")
logger.debug("this is a logger.debug message")
logger.error("this is a logger.error message")
val stream = this.getClass.getClassLoader.getResourceAsStream("log4j2.xml")
if (stream == null) {
println("failed to open log4j2.xml on classpath")
} else {
println("successfully opened log4j2.xml on classpath")
stream.close()
}
}
}
I run with
sbt "run-main mycompany.myapp.SimpleMain"
output:
[info] Running mycompany.myapp.SimpleMain
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.
simple println test
14:12:14.508 [run-main-0] ERROR mycompany.myapp.SimpleMain$ - this is a logger.error message
successfully opened log4j2.xml on classpath
[success] Total time: 4 s, completed Feb 10, 2017 2:12:14 PM
Is it possible that the classloader for the SimpleMain application is different from the classloader for slf4j? Please try debugging the Log4j2 initialization by setting system property org.apache.logging.log4j.simplelog.StatusLogger.level to TRACE.
Is log4j2.xml being placed at the root of your jar? I am surprised getResourceAsStream is working. You have it specified as a relative path so it should be finding /mycompany/myapp/log4j2.xml.
The following build.sbt file works, but it defines the dependencies of all subprojects:
name := "myproject"
version := "1.0"
scalaVersion := "2.11.8"
libraryDependencies ++= Seq(
"org.scalafx" %% "scalafx" % "8.0.60-R9"
)
lazy val aLib = (project in file("lib/a"))
lazy val bLib = (project in file("lib/b"))
.dependsOn(aLib)
.dependsOn(cLib)
lazy val cLib = (project in file("lib/c"))
.dependsOn(aLib)
lazy val myApp = (project in file("myapp"))
.dependsOn(aLib)
.dependsOn(bLib)
.dependsOn(cLib)
.aggregate(aLib, bLib, cLib)
Since each subproject (directories lib/a, lib/b, lib/c, myapp) has its own build.sbt file, I would like to use those build files to define the individual dependencies of each project.
I tried to move the dependsOn/aggregate statements to the subprojects' build files, but I am not able to make it work that way. What is the recommended way?
I have an sbt (0.13.1) project with a bunch of subprojects. I am generating eclipse project configurations using sbteclipse. My projects only have scala source files, so I want to remove the generated src/java folders.
I can achieve that by (redundantly) adding the following to the build.sbt of each subproject:
unmanagedSourceDirectories in Compile := (scalaSource in Compile).value :: Nil
unmanagedSourceDirectories in Test := (scalaSource in Test).value :: Nil
I tried just adding the above configuration to the root build.sbt but the eclipse command still generated the java source folders.
Is there any way to specify a configuration like this once (in the root build.sbt) and have it flow down to each subproject?
You could define the settings unscoped and then reuse them
val onlyScalaSources = Seq(
unmanagedSourceDirectories in Compile := Seq((scalaSource in Compile).value),
unmanagedSourceDirectories in Test := Seq((scalaSource in Test).value)
)
val project1 = project.in( file( "project1" )
.settings(onlyScalaSources: _*)
val project2 = project.in( file( "project2" )
.settings(onlyScalaSources: _*)
You could also create a simple plugin (untested code)
object OnlyScalaSources extends AutoPlugin {
override def trigger = allRequirements
override lazy val projectSettings = Seq(
unmanagedSourceDirectories in Compile := Seq((scalaSource in Compile).value),
unmanagedSourceDirectories in Test := Seq((scalaSource in Test).value)
)
}
More details about creating plugins in the plugins documentation