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 domain, repository, services 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>4.0.0</modelVersion> <groupId>org.specs2</groupId> <artifactId>parent</artifactId> <version>0.3</version> <packaging>pom</packaging> <properties> ... <specs2.version>1.7</specs2.version> </properties> <dependencies> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>2.9.1</version> </dependency> </dependencies> <modules> <module>org.specs2.spring</module> <module>org.specs2.spring.web</module> <module>org.specs2.spring.documentation</module> <module>org.specs2.spring-example</module> <module>org.specs2.spring.web-example</module> </modules> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> </plugin> <plugin> <groupId>org.scala-tools</groupId> <artifactId>maven-scala-plugin</artifactId> <version>2.15.3-SNAPSHOT</version> </plugin> </plugins> </pluginManagement> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.7.2</version> <configuration> <classesDirectory>target/classes</classesDirectory> <includes> <include>**/*Test.class</include> </includes> <argLine>-Xmx1024M -XX:MaxPermSize=256m</argLine> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.1</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <plugin> <groupId>org.scala-tools</groupId> <artifactId>maven-scala-plugin</artifactId> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>com.agilejava.docbkx</groupId> <artifactId>docbkx-maven-plugin</artifactId> <version>2.0.13</version> <configuration> <xincludeSupported>true</xincludeSupported> <highlightSource>1</highlightSource> <foCustomization> ${project.basedir}/src/docbkx/styles/pdf/custom.xsl </foCustomization> </configuration> <dependencies> <dependency> <groupId>org.docbook</groupId> <artifactId>docbook-xml</artifactId> <version>4.4</version> <scope>runtime</scope> </dependency> <dependency> <groupId>net.sf.xslthl</groupId> <artifactId>xslthl</artifactId> <version>2.0.1</version> <scope>runtime</scope> </dependency> <dependency> <groupId>net.sf.offo</groupId> <artifactId>fop-hyph</artifactId> <version>1.2</version> <scope>runtime</scope> </dependency> </dependencies> <executions> <execution> <phase>pre-site</phase> <goals> <goal>generate-html</goal> <goal>generate-pdf</goal> </goals> </execution> </executions> </plugin> </plugins> </build></project>
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<?xml version="1.0" encoding="UTF-8"?><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"> <parent> <groupId>org.specs2</groupId> <artifactId>parent</artifactId> <version>0.3</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>org.specs2</groupId> <artifactId>spring</artifactId> <packaging>jar</packaging> <version>0.3</version> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> ... </dependencies></project>
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<?xml version="1.0" encoding="UTF-8"?><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"> <parent> <groupId>org.specs2</groupId> <artifactId>parent</artifactId> <version>0.3</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>org.specs2</groupId> <artifactId>spring-example</artifactId> <packaging>jar</packaging> <version>0.3</version> <dependencies> <dependency> <groupId>org.specs2</groupId> <artifactId>spring</artifactId> <version>0.3</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> ... </dependencies></project>
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 .sbt
file 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 scala
directory 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 <<= scalaVersion { scala_version => 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 => 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.sbt
files into a single program that then builds your project. Let's pick the org.specs2.spring-example/build.sbt
as an example:
12345678910111213libraryDependencies <<= scalaVersion { scala_version => 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 compile
, sbt test
, sbt 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 (With all the changes applied.)project
directory) in preparation for the 0.4 release.
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
- Maven to SBT
- Maven SBT
- sbt和maven
- sbt与maven简单对比
- sbt与maven简单对比
- Maven/SBT常用的repositorie一览表
- Maven/SBT常用的repositorie一览表
- 用SBT替代Maven做自动构建
- 我们为什么放弃SBT回归Maven
- 使用国内 maven 源编译 sbt
- Maven和SBT学习日记1
- idea + scala + maven/sbt环境配置
- sbt/maven构建scala项目配置文件
- SBT
- SBT
- SBT!!
- SBT
- SBT
- 2016年1月6号
- Workerman--unable to connect to tcp://0.0.0.0:7272 (Address already in use)
- TortoiseSVN英文版菜单中文翻译
- 写日志类,怎么使用protected 类构造函数创建类实例
- ListView自动滚动到数据集的最后一条
- Maven to SBT
- 管理表与更改数据
- linux usermod命令参数及用法详解(linux修改用户账号信息命令
- java mysql 表中字段是tinyint(1)类型,hibernate自动生成bean对应属性为boolean类型,如何传值
- python 掌握之路(二)
- Trie树详解及其应用
- Node.js
- 周易六十四卦—师卦
- Struts2学习笔记