Maven to SBT

来源:互联网 发布:北京淘宝城地址在哪里 编辑:程序博客网 时间:2024/06/05 09:25

    Typical [enterprise] Java projects use multi-module Maven configuration. You have the parent pom.xml file at the root of your project and you refer to the modules from the parent pom.xml. The motivation for this structure is that you want to configure the compiler, testing and perhaps the reporting components once and apply them to all modules; also, the modules depend on each other and you need to use the multi-module project to compile the modules in the right order.

Think about a typical JEE application: you have the domainrepositoryservices and web app; the dependencies between the modules are that:

  • domain does not have any dependencies
  • repository depends on domain
  • services depends on domain and repository
  • web app depends on domain and services

In this post, I am not going to be talking about Spring Java EE application. I will show you how I have moved Specs2 Spring from Maven to SBT.

Specs2 Spring was a multi-module Maven project, with five modules:

  • org.specs2.spring with no dependencies
  • org.specs2.spring-example depends on org.specs2.spring
  • org.specs2.spring.web depends on org.specs2.spring
  • org.specs2.spring.web-example depends on org.specs2.spring.web
  • org.specs2.spring.documentation with no dependencies

In addition to expressing the structure in the Maven poms, I needed to configure the Scala compiler to run during the compile and testCompile phases. Also, I wanted to generate DocBooks as part of the build process. All this made the parent pom.xml rather verbose, reaching 215 lines of XML; the most notable ones being:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  <modelVersion&gt;4.0.0&lt;/modelVersion>  <groupId&gt;org.specs2&lt;/groupId>  <artifactId>parent</artifactId>  <version>0.3</version>  <packaging>pom</packaging>  &lt;properties&gt;      ...    &lt;specs2.version&gt;1.7&lt;/specs2.version&gt;  &lt;/properties&gt;  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.scala-lang&lt;/groupId&gt;      &lt;artifactId&gt;scala-library&lt;/artifactId&gt;      &lt;version&gt;2.9.1&lt;/version&gt;    &lt;/dependency&gt;  &lt;/dependencies&gt;  &lt;modules&gt;    &lt;module&gt;org.specs2.spring&lt;/module&gt;    &lt;module&gt;org.specs2.spring.web&lt;/module&gt;    &lt;module&gt;org.specs2.spring.documentation&lt;/module&gt;    &lt;module&gt;org.specs2.spring-example&lt;/module&gt;    &lt;module&gt;org.specs2.spring.web-example&lt;/module&gt;  &lt;/modules&gt;  &lt;build&gt;    &lt;pluginManagement&gt;      &lt;plugins&gt;        &lt;plugin&gt;          &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;          &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;          &lt;version&gt;2.0.2&lt;/version&gt;        &lt;/plugin&gt;        &lt;plugin&gt;          &lt;groupId&gt;org.scala-tools&lt;/groupId&gt;          &lt;artifactId&gt;maven-scala-plugin&lt;/artifactId&gt;          &lt;version&gt;2.15.3-SNAPSHOT&lt;/version&gt;        &lt;/plugin&gt;      &lt;/plugins&gt;    &lt;/pluginManagement&gt;    &lt;plugins&gt;      &lt;plugin&gt;        &lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;        &lt;version&gt;2.7.2&lt;/version&gt;        &lt;configuration&gt;          &lt;classesDirectory&gt;target/classes&lt;/classesDirectory&gt;          &lt;includes&gt;            &lt;include&gt;**/*Test.class&lt;/include&gt;          &lt;/includes&gt;          &lt;argLine&gt;-Xmx1024M -XX:MaxPermSize=256m&lt;/argLine&gt;        &lt;/configuration&gt;      &lt;/plugin&gt;      &lt;plugin&gt;        &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;        &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;        &lt;configuration&gt;          &lt;source&gt;1.6&lt;/source&gt;          &lt;target&gt;1.6&lt;/target&gt;        &lt;/configuration&gt;      &lt;/plugin&gt;      &lt;plugin&gt;        &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;        &lt;artifactId&gt;maven-war-plugin&lt;/artifactId&gt;        &lt;version&gt;2.1&lt;/version&gt;        &lt;configuration&gt;          &lt;failOnMissingWebXml&gt;false&lt;/failOnMissingWebXml&gt;        &lt;/configuration&gt;      &lt;/plugin&gt;      &lt;plugin&gt;        &lt;groupId&gt;org.scala-tools&lt;/groupId&gt;        &lt;artifactId&gt;maven-scala-plugin&lt;/artifactId&gt;        &lt;executions&gt;          &lt;execution&gt;            &lt;goals&gt;              &lt;goal&gt;compile&lt;/goal&gt;              &lt;goal&gt;testCompile&lt;/goal&gt;            &lt;/goals&gt;          &lt;/execution&gt;        &lt;/executions&gt;      &lt;/plugin&gt;      &lt;plugin&gt;        &lt;groupId&gt;com.agilejava.docbkx&lt;/groupId&gt;        &lt;artifactId&gt;docbkx-maven-plugin&lt;/artifactId&gt;        &lt;version&gt;2.0.13&lt;/version&gt;        &lt;configuration&gt;          &lt;xincludeSupported&gt;true&lt;/xincludeSupported&gt;          &lt;highlightSource&gt;1&lt;/highlightSource&gt;          &lt;foCustomization&gt;            ${project.basedir}/src/docbkx/styles/pdf/custom.xsl          &lt;/foCustomization&gt;        &lt;/configuration&gt;        &lt;dependencies&gt;          &lt;dependency&gt;            &lt;groupId&gt;org.docbook&lt;/groupId&gt;            &lt;artifactId&gt;docbook-xml&lt;/artifactId&gt;            &lt;version&gt;4.4&lt;/version&gt;            &lt;scope&gt;runtime&lt;/scope&gt;          &lt;/dependency&gt;          &lt;dependency&gt;            &lt;groupId&gt;net.sf.xslthl&lt;/groupId&gt;            &lt;artifactId&gt;xslthl&lt;/artifactId&gt;            &lt;version&gt;2.0.1&lt;/version&gt;            &lt;scope&gt;runtime&lt;/scope&gt;          &lt;/dependency&gt;          &lt;dependency&gt;            &lt;groupId&gt;net.sf.offo&lt;/groupId&gt;            &lt;artifactId&gt;fop-hyph&lt;/artifactId&gt;            &lt;version&gt;1.2&lt;/version&gt;            &lt;scope&gt;runtime&lt;/scope&gt;          &lt;/dependency&gt;        &lt;/dependencies&gt;        &lt;executions&gt;          &lt;execution&gt;            &lt;phase&gt;pre-site&lt;/phase&gt;            &lt;goals&gt;              &lt;goal&gt;generate-html&lt;/goal&gt;              &lt;goal&gt;generate-pdf&lt;/goal&gt;            &lt;/goals&gt;          &lt;/execution&gt;        &lt;/executions&gt;      &lt;/plugin&gt;    &lt;/plugins&gt;  &lt;/build&gt;&lt;/project&gt;

This parent pom.xml refers to modules, which must be sub-directories with another pom.xml file in them. The org.specs2.spring/pom.xml with no dependencies amongst the project modules simply listed all other dependencies:

123456789101112131415161718192021222324252627&lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;  &lt;parent&gt;    &lt;groupId&gt;org.specs2&lt;/groupId&gt;    &lt;artifactId&gt;parent&lt;/artifactId&gt;    &lt;version&gt;0.3&lt;/version&gt;  &lt;/parent&gt;  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  &lt;groupId&gt;org.specs2&lt;/groupId&gt;  &lt;artifactId&gt;spring&lt;/artifactId&gt;  &lt;packaging&gt;jar&lt;/packaging&gt;  &lt;version&gt;0.3&lt;/version&gt;  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.springframework&lt;/groupId&gt;      &lt;artifactId&gt;spring-core&lt;/artifactId&gt;      &lt;version&gt;${spring.version}&lt;/version&gt;    &lt;/dependency&gt;    ...  &lt;/dependencies&gt;&lt;/project&gt;

The org.specs2.spring-example module depends on the org.specs2.spring module, so its pom.xml had to include the org.specs2.spring-example module like so:

1234567891011121314151617181920212223242526272829303132&lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;  &lt;parent&gt;    &lt;groupId&gt;org.specs2&lt;/groupId&gt;    &lt;artifactId&gt;parent&lt;/artifactId&gt;    &lt;version&gt;0.3&lt;/version&gt;  &lt;/parent&gt;  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;  &lt;groupId&gt;org.specs2&lt;/groupId&gt;  &lt;artifactId&gt;spring-example&lt;/artifactId&gt;  &lt;packaging&gt;jar&lt;/packaging&gt;  &lt;version&gt;0.3&lt;/version&gt;  &lt;dependencies&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.specs2&lt;/groupId&gt;      &lt;artifactId&gt;spring&lt;/artifactId&gt;      &lt;version&gt;0.3&lt;/version&gt;    &lt;/dependency&gt;    &lt;dependency&gt;      &lt;groupId&gt;org.springframework&lt;/groupId&gt;      &lt;artifactId&gt;spring-core&lt;/artifactId&gt;      &lt;version&gt;${spring.version}&lt;/version&gt;    &lt;/dependency&gt;    ...  &lt;/dependencies&gt;&lt;/project&gt;

Oh, the humanity!

Quite. All this XML became a bit too uncomfortable to navigate around. Furthermore, most of the Scala projects out there use SBT, which promises to be as powerful, but much more concise build tool. So, I set out to replace Maven's verbose XML with SBT's... call it scripts.

SBT Scripts?

SBT is essentially a domain-specific language for building projects. SBT (the tool) then runs the Scala program that is assembled from the various script files as well as full-blown Scala sources. To make life easier, SBT maintains two sets of files: the .sbt files are decorated into the full Scala syntax and then compiled together with the grown-up Scala code. <ins>SBT decorates the body of the .sbt file to become a compilation unit (put simply, a class with all imports and functions resolved). Every block in the .sbt file then becomes a function in the resulting compilation unit. SBT uses empty lines to demarcate what is to become the functions, which is why every "statement" in the .sbtfile needs to be on its own line and why you cannot have empty lines in multi-line "statements". (Many thanks to @plalloni for clarification and comments!)</ins> SBT then executes the resulting Scala program to build your project. There is much more detail at SBT's documentation at https://github.com/harrah/xsbt/wiki. Let's take a look at how I've transformed the multi-module Maven beast into SBT.

Multi-module SBT

First, let's take a look at a typical SBT project. It contains the build.sbt file (that gets rewritten into the grown-up Scala program that then compiles your code, but you already knew that!). A SBT project also needs the source files, which are in the usual Maven structure. So, a single SBT project typically looks like this:

1234567891011src  main    java    scala    resources  test    java    scala    resourcesbuild.sbt

SBT is smart enough to work out that the files in the java directory are to be compiled using the Java compiler; that the files in the scaladirectory need to be compiled using the Scala compiler and that the files in resources are not to be compiled, simply copied to the output.

Now, in Specs2 Spring, I have five projects, so the first approach was to include the build.sbt in every sub-project:

1234567891011121314151617181920212223org.specs2.spring  src    ...  build.sbtorg.specs2.spring-example  src    ...  build.sbtorg.specs2.spring.web  src    ...  build.sbtorg.specs2.spring.web-example  src    ...  build.sbtorg.specs2.spring.documentation  src    ...  build.sbtbuild.sbt

The structure is clear[-ish]: we have five modules, each module's build.sbt describes how it is to be built; and there is a main build.sbt, which should build all modules in the right sequence.

The situation is slightly more complicated. There is no provision for project dependencies in the simplified syntax of the .sbt files. (Recall that they are transformed into Scala by SBT.)
In order to have multi-module SBT projects, we need to define a Scala source that represents the project build. We do so by creating the project directory at the same level as the modules and adding the Build.scala file, which contains object that extends sbt.Build. So, we have:

12345678910111213141516org.specs2.spring  build.sbtorg.specs2.spring-example  build.sbtorg.specs2.spring.web  build.sbtorg.specs2.spring.web-example  build.sbtorg.specs2.spring.documentation  build.sbtproject  Build.scalabuild.sbt

The interesting file is the Build.scala, which defines the "modules" that make up the project and that sets the dependencies between the modules. It is surprisingly simple:

12345678910111213141516171819202122232425import sbt._object Specs2Spring extends Build {  lazy val root =    Project("specs2-spring", file("."))            aggregate(core, coreExample, documentation, web, webExample)  lazy val core =    Project("org.specs2.spring", file("org.specs2.spring"))  lazy val coreExample =    Project("org.specs2.spring-example",            file("org.specs2.spring-example")) dependsOn(core)  lazy val web =    Project("org.specs2.spring.web",            file("org.specs2.spring.web")) dependsOn(core)  lazy val webExample =    Project("org.specs2.spring.web-example",            file("org.specs2.spring.web-example")) dependsOn(web)  lazy val documentation =    Project("org.specs2.spring.documentation",            file("org.specs2.spring.documentation"))}

Notice that the object Specs2Spring extends sbt.Build and defines the variables that represent the projects. We have the root project, which is simply an aggregate of the remaining projects, which are each defined in their own variable. Projects can define dependencies using the dependsOn function, specifying the project variable of the dependency. How simple!

Now that we have the Specs2Spring project build source out of the way, let's take a look at the smaller build.sbt files that complete the picture. By far the most complex is the org.specs2.spring/build.sbt:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354/** Project */name := "specs2-spring"version := "0.3"organization := "org.specs2"scalaVersion := "2.9.1"crossScalaVersions := Seq("2.9.0")/** Dependencies */resolvers ++= Seq("snapshots-repo" at "http://scala-tools.org/repo-snapshots")libraryDependencies &lt;&lt;= scalaVersion { scala_version =&gt; Seq(  "org.specs2" %% "specs2" % "1.7.1",  "junit" % "junit" % "4.7" % "optional",  "org.springframework" % "spring-core" % "3.1.0.RELEASE",  "org.springframework" % "spring-beans" % "3.1.0.RELEASE",  "org.springframework" % "spring-jdbc" % "3.1.0.RELEASE",  "org.springframework" % "spring-tx" % "3.1.0.RELEASE",  "org.springframework" % "spring-orm" % "3.1.0.RELEASE",  "org.springframework" % "spring-test" % "3.1.0.RELEASE",  "org.hibernate" % "hibernate-core" % "3.6.0.CR1",  "javax.mail" % "mail" % "1.4.1",  "javax.transaction" % "jta" % "1.1",  "com.atomikos" % "transactions-jta" % "3.7.0",  "com.atomikos" % "transactions-jdbc" % "3.7.0",  "org.apache.activemq" % "activemq-core" % "5.4.1"  )}/** Compilation */javacOptions ++= Seq()javaOptions += "-Xmx2G"scalacOptions ++= Seq("-deprecation", "-unchecked")maxErrors := 20 pollInterval := 1000logBuffered := falsecancelable := truetestOptions := Seq(Tests.Filter(s =&gt;  Seq("Spec", "Suite", "Unit", "all").exists(s.endsWith(_)) &&    !s.endsWith("FeaturesSpec") ||    s.contains("UserGuide") ||    s.contains("index") ||    s.matches("org.specs2.guide.*")))

In the build.sbt file, you can see that I specify some common settings (name, version, Scala version); the managed libraries (dependencies in Maven speak) and I configure the parameters of the compiler. But that's all I need to do!

The remaining build.sbt files can be much simpler, because SBT compiles the project/Build.scala and all transformed build.sbtfiles into a single program that then builds your project. Let's pick the org.specs2.spring-example/build.sbt as an example:

12345678910111213libraryDependencies &lt;&lt;= scalaVersion { scala_version =&gt; Seq(  "org.springframework" % "spring-core" % "3.1.0.RELEASE",  "org.springframework" % "spring-beans" % "3.1.0.RELEASE",  "org.springframework" % "spring-jdbc" % "3.1.0.RELEASE",  "org.springframework" % "spring-tx" % "3.1.0.RELEASE",  "org.springframework" % "spring-orm" % "3.1.0.RELEASE",  "org.hibernate" % "hibernate-core" % "3.6.0.CR1",  "org.hibernate" % "hibernate-validator" % "4.0.2.GA",  "javassist" % "javassist" % "3.4.GA",  "org.hsqldb" % "hsqldb" % "2.2.4"  )}

That is all!; you can now run sbt compilesbt testsbt publish-local and many others. sbt is a shell script; one example is at https://github.com/harrah/xsbt/wiki/Getting-Started-Setup, but Paul Phillips created far more powerful version, which you can download at https://github.com/paulp/sbt-extras.

The devil in the detail

Now, we have a successful multi-module project in SBT. Everything builds, all libraries are downloaded and the dependencies between the projects work as well. But what about DocBook? In Maven, I used to use the com.agilejava.docbkx:docbkx-maven-plugin plugin, which took care of generating the HTMLs and PDFs.

Luckily, SBT supports similar plugin infrastructure. All we need to do is to include the appropriate plugins in our project and we can run tasks that the plugins expose. To do what I just described, we need to create the project/plugins.sbt file that lists the required plugins in the project that requires the plugin. So, taking our org.specs2.spring.documentation module (or sub-project, if you like), we need to create the following structure:

123456789101112131415org.specs2.spring...org.specs2.spring.documentation  project    plugins.sbt  src    main      docbook        ...  build.sbt...project  Build.scalabuild.sbt

The interesting file is the plugins.sbt, which defines the plugins our project requires. The org.specs2.spring.documentation/project/plugins.sbt contains just a single line:

12addSbtPlugin("de.undercouch" % "sbt-docbook-plugin" % "0.2-SNAPSHOT")

The "de.undercouch" % "sbt-docbook-plugin" % "0.2-SNAPSHOT" plugin requires some properties to be set in the build.sbt, namely the sourceFilter and, because we have our own XSL for the PDF, the docBookXslFoStyleSheet. So, in the org.specs2.spring.documentation/build.sbt, we have:

1234sourceFilter := "**/*.xml"docBookXslFoStyleSheet in DocBook:= "src/docbkx/styles/pdf/custom.xsl"

I updated the plugin to the latest version of Scala and SBT and sent a pull request to the original author; my sources are at https://github.com/janm399/sbt-docbook-plugin (for the future of the plugin, see the open issues).

Summary

So, what can you achieve? Put simply, the same build functionality in approximately quarter lines of code. Even if you do not use any of the advanced features of SBT, you have gained a more intuitive way of building Java and Scala applications; you can also release your libraries for the correct version of the Scala compiler (no more appending _2.9.1_2.9.0_1 and similar to your Maven dependencies!). The artefacts that SBT produces fit directly into the Scala repositories without additional effort.

With this structure, the project will build just like the Maven monster it replaced! As usual, the devil was in the details, so keep reading.

P.S. All SBT goodness is at the SBT master branch of https://github.com/janm399/specs2-spring I will make some final modifications (namely get rid of the now superfluous root project directory) in preparation for the 0.4 release. (With all the changes applied.)

P.P.S. All signs point to Specs2 Spring being available on the scala-tools.org repo in the next few days!

Come from: http://www.cakesolutions.net/teamblogs/2012/01/04/maven-to-sbt

0 0
原创粉丝点击