Packaging
A jar_package builds a jar. The other Java archives, a war, an ear or a Spring Boot executable
jar, are jars with a different layout: the same classes and dependency jars, placed under
different directories. Fabr builds the war and ear layouts with war_package and ear_package,
a Spring Boot executable jar with spring_boot_jar, and any other layout with a rule you write
yourself out of the same pieces.
Coming from Maven or Gradle
Section titled “Coming from Maven or Gradle”| With Maven or Gradle | In fabr |
|---|---|
maven-jar-plugin / jar { } |
A jar_package. |
<manifestEntries> / jar.manifest.attributes |
A jar_package’s manifest. |
<packaging>war</packaging> / the war plugin |
A war_package. |
<packaging>ear</packaging> / the ear plugin |
An ear_package. |
maven-dependency-plugin:copy-dependencies |
A java_installation. |
spring-boot-maven-plugin / the Spring Boot Gradle plugin |
A spring_boot_jar. |
mvn javadoc:javadoc / gradle javadoc |
fabr doc app; see Javadoc and sources. |
maven-source-plugin / withSourcesJar() |
fabr sources app; a sync publishes it with auto_attach. |
target/app.jar / build/libs/app.jar |
Kept in fabr’s cache; fabr cp app ./out copies it out. |
mvn deploy |
A sync target; see Publishing. |
The jar
Section titled “The jar”A jar_package builds one jar, named after the target: greeter.jar, or slf4j-api.jar for a
target named org.slf4j/slf4j-api. It holds:
- the classes the
srcscompile to; - the
resources, each under the name it has in the target (resources = src/main/resources:**putssrc/main/resources/application.propertiesatapplication.properties); META-INF/MANIFEST.MF.
A jar or zip among the resources is stored uncompressed, so that a nested jar can be read in
place, as a Spring Boot loader reads one. The jar holds no pom.xml or META-INF/maven files.
The jar doesn’t carry its dependencies: whatever depends on the target gets the jar and its
deps, as Maven’s consumers get a jar and its POM. To put them in an archive, see
The jars a program needs.
The manifest
Section titled “The manifest”The manifest has Manifest-Version and Created-By: fabr, then Main-Class where the target has
a main_class, then the target’s manifest entries in the order they are written:
jar_package app { srcs = src/main/java:**/*.java; main_class = com.example.Main; manifest = { Implementation-Version = 1.4.0; Automatic-Module-Name = com.example.app; };}main_class must be one of the jar’s classes or a class of a package it requires, which is
checked when the jar is built: app's main_class 'com.example.Mian' is no class of its sources or of what it requires. A Main-Class written in manifest instead is not checked, which is how a
jar can name a launcher that is among its resources; a jar can’t name its main class both ways.
Javadoc and sources
Section titled “Javadoc and sources”fabr doc app generates a jar_package’s or a war_package’s javadoc, and fabr sources app
gives its sources: its srcs, the sources its annotation processors generate, and its
resources (a war’s webapp is not among them), laid out as a sources jar holds them. A sync
can publish both beside the artifact; see
Sources and javadoc jars. Both include the generated sources, such as AutoValue’s AutoValue_Point.java,
because they are part of the jar’s classes; the jar itself holds no sources. Each is a tree of files, which you
look at as you would a build’s:
fabr doc appfabr cp 'app<BUILD_OPERATION=doc>' ./out # ./out/app/index.html, …fabr ls 'app<BUILD_OPERATION=sources>' # com/example/Main.java, …The javadoc is generated by JAVADOC, from the JDK release
unless you set it, for the release the jar is compiled for, against the same classpath,
or module path, as the compile. Its warnings, such as warning: no comment, are shown with the
step; an error in a doc comment fails it, as it fails javadoc.
The javadoc covers the sources the jar’s annotation processors generate too, such as AutoValue’s
AutoValue_Point. What Lombok adds is missing: Lombok changes the classes as they are compiled
rather than writing sources, and javadoc reads sources. That is so in Maven and Gradle too, where
a project documents Lombok code by running Lombok’s delombok command first and giving javadoc
its output.
A javadoc target generates javadoc for any Java sources, against the packages its deps name.
Javadoc documents the .java files among its srcs and reads the rest as it would from a source
directory: a package’s package.html, an overview.html, and a doc-files directory, which it
copies into the output. One javadoc target over several jars’ sources gives one site for all of
them, with links between them, as Maven’s javadoc:aggregate does:
javadoc api_docs { srcs = api<BUILD_OPERATION=sources> impl<BUILD_OPERATION=sources>; deps = api impl;}srcs are the jars’ sources, generated sources included, and deps puts the jars, with
everything they require, on the classpath.
A dependency’s own javadoc and sources are read the same way; see Dependencies.
The jars a program needs
Section titled “The jars a program needs”A java_installation is the jars of everything its deps require, each under its own file name,
in one directory: what Maven’s copy-dependencies produces.
java_installation libs { deps = @mvn:org.apache.commons:commons-text:1.11.0;}builds commons-text-1.11.0.jar and commons-lang3-3.13.0.jar. Loose files among its deps are
kept as they are. It is the piece the archive layouts below are made of: placed under a directory
of an archive, with a rename, it is that archive’s library directory.
A package that something requires only as a provided_deps is not installed: whatever runs the
installation supplies it, as Maven and Gradle leave provided and compileOnly dependencies out.
An installation’s own provided_deps name what that is. They are resolved with its deps, so the
two use one version of each package, and left out together with everything they require, however
the deps reach them, as Gradle’s war plugin leaves out providedCompile:
java_installation libs { deps = app; provided_deps = @mvn:jakarta.platform:jakarta.jakartaee-api:10.0.0;}A war_package builds <name>.war:
| In the war | From |
|---|---|
WEB-INF/classes/ |
The classes the srcs compile to, and the resources. |
WEB-INF/lib/ |
The jars of everything deps require. |
| The root | The webapp files: pages, static content, WEB-INF/web.xml. |
war_package shop { srcs = src/main/java:**/*.java; resources = src/main/resources:**; webapp = src/main/webapp:**; deps = @mvn:org.apache.commons:commons-text:1.11.0; provided_deps = @mvn:jakarta.servlet:jakarta.servlet-api:6.0.0;}provided_deps are compiled against and left out of WEB-INF/lib, with everything they require,
for the servlet API and anything else the container supplies. A package a jar_package among the
deps requires as a provided_deps is left out too, unless the war’s deps require it. With the Maven layout above, src/main/webapp/WEB-INF/web.xml
is the war’s WEB-INF/web.xml; a descriptor kept elsewhere is placed with a rename:
webapp = src/main/webapp:** config:web.xml -> WEB-INF/web.xml;The application’s own code can also come from a jar_package among the deps, with no srcs:
its jar then goes in WEB-INF/lib beside its dependencies.
An ear_package builds <name>.ear, holding its modules, the wars and jars it deploys, at the
root, the jars of everything deps require under lib/, and its resources at the root:
ear_package store { modules = shop; deps = @mvn:org.apache.commons:commons-lang3:3.14.0; resources = src/main/application:**;}modules = shop puts shop.war in the ear. Fabr doesn’t write a META-INF/application.xml, which
Java EE 5 and later don’t require; to give one, put it among the resources.
Spring Boot executable jars
Section titled “Spring Boot executable jars”A Spring Boot executable jar holds an application and every jar it needs, and starts with
java -jar: the jars, uncompressed, under BOOT-INF/lib/, and the Spring Boot loader’s classes at
the root, whose JarLauncher starts the class the manifest names as Start-Class. A
spring_boot_jar builds one from a jar_package. Its target type comes in an optional file of the
Java plugin, which you include:
include @fabr-build/java:lib/spring-boot.fabr;
jar_package server { srcs = src/main/java:**/*.java; resources = src/main/resources:**; deps = @mvn:org.springframework.boot:spring-boot-dependencies:4.1.2 @mvn:org.springframework.boot:spring-boot-starter-webmvc java21; main_class = com.example.shop.ShopApplication;}
spring_boot_jar shop { app = server; main_class = com.example.shop.ShopApplication; loader = @mvn:org.springframework.boot:spring-boot-loader:4.1.2;}fabr build shop builds shop.jar, holding server.jar and the jar of everything it requires
under BOOT-INF/lib/. fabr run server runs the same application from its jars, for development.
| Property | What it is |
|---|---|
app |
The application: a jar_package, with everything it requires. |
main_class |
The class the application starts in. |
loader |
spring-boot-loader, of the application’s Spring Boot release. |
launcher |
The loader’s class that starts the jar. The default, org.springframework.boot.loader.launch.JarLauncher, is Spring Boot 3.2 and later’s; before 3.2 it is org.springframework.boot.loader.JarLauncher. |
The type and its rule are written in .fabr, in the Java plugin’s lib/spring-boot.fabr, out of
a java_installation and a jar_package; A layout of your own explains
how such a rule is written. The jar differs from the one Spring Boot’s own plugins write in three
ways:
- The application’s classes are in its jar under
BOOT-INF/lib/, not underBOOT-INF/classes/. Spring Boot finds them either way; a tool that reads the layout, such asjava -Djarmode=tools, expectsBOOT-INF/classes/. The layout below puts them there. - There is no
BOOT-INF/classpath.idx, so Spring Boot puts the jars on the classpath in the order they are stored in, which is by name. This matters only where two jars hold the same class. - There is no
layers.idxfor layered container images.
A layout of your own
Section titled “A layout of your own”A layout fabr doesn’t build is a rule you write in your build file. As an example, this one
builds a Spring Boot executable jar from the application’s sources, with its classes under
BOOT-INF/classes/ as Spring Boot’s own plugins put them. Declare a target type for it, and a rule
that builds it from a java_compile, a java_installation and a jar_package:
targetdef boot_jar { # The application's Java sources. srcs = FILES; # The packages the application uses, and `java<release>` flags. deps = PACKAGES; # Files on the application's classpath, such as `application.properties`. resources = FILES; # The application's main class. main_class = REQUIRED STRING; # The Spring Boot loader: `spring-boot-loader`, of the application's Spring Boot release. loader = PACKAGES;}
rule boot_jar b<BUILD_OPERATION=build> = jar { java_compile classes { srcs = b.srcs; deps = b.deps; } java_installation libs { deps = b.deps; } jar_package jar { resources = classes:classes/** -> BOOT-INF/classes/** b.resources:** -> BOOT-INF/classes/** libs:** -> BOOT-INF/lib/** b.loader:*.jar:org/**; manifest = { Main-Class = org.springframework.boot.loader.JarLauncher; Start-Class = ${b.main_class}; Spring-Boot-Classes = BOOT-INF/classes/; Spring-Boot-Lib = BOOT-INF/lib/; }; }}
boot_jar demo { srcs = src/main/java:**/*.java; resources = src/main/resources:**; deps = @mvn:org.springframework.boot:spring-boot-dependencies:2.7.18 @mvn:org.springframework.boot:spring-boot-starter java17; main_class = demo.DemoApplication; loader = @mvn:org.springframework.boot:spring-boot-loader:2.7.18;}fabr build demo builds demo.jar, and java -jar demo.jar starts the application.
- A
java_compileproduces the classes underclasses/and any sources its annotation processors generate undergenerated/, soclasses:classes/** -> BOOT-INF/classes/**places the classes underBOOT-INF/classes/;libs:** -> BOOT-INF/lib/**does the same for the dependency jars, whichjar_packagestores uncompressed because they are jars. b.loader:*.jar:org/**reads inside the loader’s jar and takes its classes, leaving out its own manifest, so they sit at the root ofdemo.jar.Main-Classis written inmanifestrather than asmain_class, because the launcher is one of the jar’s resources, not one of its classes.- From Spring Boot 3.2 the launcher is
org.springframework.boot.loader.launch.JarLauncher.
The jar has no BOOT-INF/classpath.idx, which fixes the order of the jars on the classpath, and
no layers.idx for layered container images.
Writing your own rules explains the rule’s parts.
Not supported
Section titled “Not supported”- The javadoc doesn’t link to the JDK’s documentation or to the dependencies’ javadoc.