Publishing
A build ends at artifacts under target/. Publishing is what makes them available to somebody else: the
artifacts laid out as a repository, carrying the metadata a repository demands, and finally uploaded and
signed. Publishing to Maven Central is really two jobs - produce a correct, complete bundle and upload
it - and Jenesis owns the first while deliberately leaving the signed upload to a dedicated release tool.
Staging the release tree
The stage target materialises the full release tree in Maven repository layout under
target/stage/maven/output/: the main jar, the POM, and - when you ask for them - the -sources.jar and
-javadoc.jar that Central demands. You enable those and set the version from the command line, or in code
on Project (new Project().version("1.0.0")):
java -Djenesis.project.version=1.0.0 \
-Djenesis.project.sources=true \
-Djenesis.project.documentation=true \
build/jenesis/Project.java stage
Central also requires the POM to carry name, description, url, <licenses>, <developers>, and
<scm>. Jenesis folds two channels into each POM. Everything it can derive from the source comes first: the
coordinate and description from the module name and its Javadoc, or from the source pom.xml. A
project.properties file, pointed at with -Djenesis.project.metadata=project.properties, carries only what
a module declaration cannot express:
# project.properties
url=https://github.com/raphw/jenesis
license.apache-2_0.name=Apache-2.0
license.apache-2_0.url=https://www.apache.org/licenses/LICENSE-2.0.txt
developer.raphw.name=Rafael Winterhalter
developer.raphw.email=rafael.wth@gmail.com
scm.connection=scm:git:https://github.com/raphw/jenesis.git
scm.url=https://github.com/raphw/jenesis
-notimestamp, so two independent builds of the same sources hash bit-for-bit
identically - a consumer can verify the bytes on Central were built from the published sources.
Publishing locally with export
export is a genuine publish, into a local repository:
java build/jenesis/Project.java export
It copies the staged tree into the repositories your layout publishes to: the local Maven repository
(~/.m2) for maven, the local module repository (~/.jenesis) for modular, and both for
modular_to_maven. Another project on the same machine then resolves the artifact immediately. That is the
whole loop for a library you are developing alongside its consumer, with no remote involved.
Publishing a bill of materials
A module can also publish the pin set of its own resolved closure, so a downstream project imports the versions this one was built and tested against instead of curating its own. It is the emitting counterpart of the bills of materials you already know how to consume.
A bom.properties file in the configuration folder switches it on - the file may be empty, presence is the
switch. The modular layouts then render the module's closure as a properties file that export publishes
into the local module repository beside the module jar:
~/.jenesis/demo.bom/1.0.0/demo.bom.properties
Another project consumes it with @jenesis.bom demo.bom, exactly the way it consumes a hand-written file. The
BOM travels through the module layout only; the Maven export never carries it.
The last mile: signing and uploading
The remote upload and GPG signing are not Jenesis's job. Point JReleaser at
target/stage/maven/output/ and it signs every artifact and uploads the bundle to Central. Jenesis stops at
the unsigned, validated bundle, so credentials and signing keys never enter the build.
This split is deliberate. Most people building a project never release it: releasing is a rare, tightly controlled job for CI or a hardened environment that holds the keys. And the way you release evolves independently of how the build produces artifacts.
Driving the release tool from the build
You can still reach that tool through the same selector vocabulary as everything else. release is a target on
every layout, and it depends on stage, so what a release tool uploads is always the tree this build just
produced:
java build/jenesis/Project.java release
The target exists whether or not anything is configured; the tool is what a file activates. A jreleaser.yml
(or .yaml, .toml, .json) at the project root adds a release/jreleaser step - a project-root lookup
rather than the usual per-module configuration folder, because JReleaser resolves every path in its
configuration against one base directory. -Djenesis.jreleaser.config=<path> names a different file.
It contributes two steps. The first writes a jreleaser.properties holding JRELEASER_PROJECT_VERSION, the
version this build stamped, so the version is stated once rather than passed to two tools that can then
disagree. Point a configuration at it with
environment: { variables: target/release/jreleaser/environment/output/jreleaser.properties }. The second runs
the jreleaser executable found in the environment, forwarding the process environment unchanged. Every
JRELEASER_* credential is therefore read by JReleaser itself and never touched, logged, or stored by the
build.
release is a dry run by default: every local phase runs and every remote one is
skipped. A real release needs -Djenesis.jreleaser.dryRun=false, the single switch that separates
a rehearsal from a publication - so no combination of selectors alone can publish.
native-image is. Every tool that shapes an artifact - a compiler, a linter - is pinned,
because reproducing a build means reproducing it exactly. A release tool shapes nothing: it transmits a
finished, already-reproducible tree. What it needs instead is credentials, network access, and a git identity,
which belong to the release environment.
release target is for what they do not cover: rehearsing a release
locally, and releasing from a pipeline that has no such integration.