Skip to content

Latest commit

 

History

History
389 lines (284 loc) · 15.3 KB

DEVELOPING.md

File metadata and controls

389 lines (284 loc) · 15.3 KB

Table of Contents

Prerequisites

  • Java 11 or later (to build, it requires 1.8 or later to run)
  • Maven 3.3.9 or later (to build, it requires 3.1 or later to run)

Coding

Exception Handling

The three built in Exceptions all support SLF4J style {} substitution parameters

ManipulationException
RestException
ManipulationUncheckedException

For example

throw new ManipulationException( "Unable to detect charset for file {}", jsonFile, exceptionObject );
throw new ManipulationException( "Internal project dependency resolution failure ; replaced {} by {}", old, d );
throw new ManipulationException( "Unable to parse groovyScripts", exceptionObject );

Configuration

Any configuration property value must have a static String together with the internal configuration annotation e.g.

    @ConfigValue( docIndex = "project-version-manip.html#manual-version-suffix" )
    public static final String VERSION_SUFFIX_SYSPROP= "versionSuffix";

The docIndex value represents the property index for the gh-pages. This is automatically generated upon build inside root/target/property-index-subset.md. A new class org.commonjava.maven.ext.core.ConfigList is also generated that contains a HashMap of all properties ; this is used for validation input checking within the CLI / EXT.

Style & Copyright

Eclipse compatible codestyle.xml and eclipse.importorder files are supplied inside the ide-config directory which may also be imported into IntelliJ via the EclipseCodeFormatter. There is also an IntelliJ compatible copyright template suitable for use when the project is imported into IntelliJ.

The .idea folder contains a copyright template suitable for use when the project is imported into IntelliJ.

IntelliJ

The following plugin is required:

It is also recommended to install:

Documentation

In order to edit the website at https://release-engineering.github.io/pom-manipulation-ext checkout the gh-pages branch. It is possible to use Jekyll (https://help.github.com/articles/using-jekyll-with-pages) to preview the changes. Jekyll can be run with jekyll serve --watch -V and may be installed in Fedora via the rubygem-jekyll package.

This README's table of contents is generated by tocdown using the following command line:

ruby .../tocdown/toc.rb -b -r -t -d 3 -s -m <markdown-file>

Compiling

mvn clean install will compile and run all of the unit tests. For further details on testing (including running the integration tests) see below.

The system is setup via GitHub Actions to build all pull requests in CI. Further, it will build main branch to deploy to the Sonatype snapshot repository from GitHub. See the .github/workflows/maven.yml for details.

Testing

The tool has both unit tests and integration tests. The integration tests will only be run if the specified profile has been activated e.g.

mvn clean install -Prun-its

The unit tests make use of the system-rules library e.g.

@Rule
public final SystemOutRule systemOutRule = new SystemOutRule().enableLog().muteForSuccessfulTests();

@Rule
public final TestRule restoreSystemProperties = new RestoreSystemProperties();

See https://stefanbirkner.github.io/system-rules for further information on this.

For the CLI/Integration tests, it is possible to run a specific one by passing e.g.

A subset of the integration tests may be run by using the following example commands:

mvn -DfailIfNoTests=false -Prun-its clean install
    -Dtest=CircularIntegrationTest -DselectedTest=circular-dependencies-test-second

mvn -DfailIfNoTests=false -Prun-its clean install
    -Dtest=DefaultCliIntegrationTest -DselectedTest=<test name e.g. depmgmt-strict-mode-exact>

The main test is the DefaultCliIntegrationTest. Adding -Dmaven.javadoc.skip=true -Denforcer.skip=true will also skip some time consuming plugins.

Code Coverage

JaCoCo is used to produce code coverage reports for the test runs. An aggregate report is produced in HTML and XML. The HTML report can be found at coverage-reporting/target/site/jacoco-aggregate/index.html and the XML report can be found at coverage-reporting/target/site/jacoco-aggregate/jacoco.xml. The XML report is uploaded to Codecov via GitHub CI for use with GitHub pull requests.

Release Process

This document describes how to setup and perform a new POM Manipulation Extension (PME) release.

Conventions

We use some conventions and policies in the projects we develop under the organization. For the most part, these help projects build uniformly with the fewest surprises in store for developers switching codebases. Some of them are aimed at improving reproducibility and reducing the confusion of a variable build environment.

Integration/functional tests are not run in the default build. To run them, you can use one of the following profiles:

  • run-its
  • release (this is used when we release projects)

Dependency versions are kept in the root POM for the project inside the dependencyManagement section (not in the normal dependencies section). Declare the most commonly used scope for the dependency inside the project if it's not a compile-time dependency, e.g., when declaring a dependency on JUnit as <scope>test</scope>. Override this scope as needed inside modules that have specific needs, e.g., re-declaring the JUnit dependency as <scope>compile</scope> for a module containing common test fixtures. We use the license-maven-plugin to format license headers. We declare it in the shared parent POM, inside the formatting profile. The release profile will use the check goal from this plugin to verify that all files have appropriate license headers. The POM's <inceptionYear> element is used as the copyright date in license headers. The POM's projectOwner and projectEmail properties are used to format the copyright ownership declaration. These properties can be overridden on a per-project basis as needed.

We enforce some things about POMs by default:

  • All dependencies must have versions declared in dependencyManagement, and not overridden in the direct dependency.
  • Any artifact that contains a class directly imported by one or more classes in our project should have a direct dependency in that project.
  • All plugins used in the POM must have a version declared.
  • All modules in a project must share the same version.
    • It's easiest to just declare this version in each module's <parent> declaration.
  • Don't declare repositories in project POMs.

Some other things aren't enforced, but are preferred:

  • Declare your project modules in the root POM's dependencyManagement section, to make module interrelationship simpler.
  • Avoid using ${project.groupId} and ${project.artifactId} in dependency declarations.
  • Only use version properties for dependencies when:
    • It's referencing another codebase that we actively develop.
    • There is more than one dependency belonging to the same project and you always want them to use the same version (which is normally the case).

Release Setup

Create a GPG Keypair

If you don't already have a GPG keypair for the email address you use for contributing, you can generate one using

$ gpg --gen-key
$ gpg --send-keys <keyname>

If you need to find the <keyname> later, you can use

$ gpg --list-keys

to find it. If you're using more than one key to perform releases, you can set it during the release process by passing -Dgpg.keyname=<keyname> to the mvn command or placing this property in your Maven settings file.

For more information, see Working with PGP Signatures.

Create a Sonatype JIRA Account

Sonatype is used for publishing both releases and snapshots. To be able to perform a release, you will need a Sonatype OSSRH (OSS Repository Hosting) account.

Sonatype uses JIRA to manage requests.

For more information, see OSSRH Guide.

Store GPG and Sonatype passwords

If you don't already have a mvn password, create one by using

mvn --encrypt-master-password

This command will produce an encrypted version of the password which looks similar to

{jSMOWnoPFgsHVpMvz5VrIt5kRbzGpI8u+9EF1iFQyJQ=}

Store this password in the file ${user.home}/.m2/settings-security.xml like so

<settingsSecurity>
  <master>{jSMOWnoPFgsHVpMvz5VrIt5kRbzGpI8u+9EF1iFQyJQ=}</master>
</settingsSecurity>

Alternatively, you can point to a different settings-security.xml file like so

<settingsSecurity>
  <relocation>/volumeUSB1/settings-security.xml</relocation>
</settingsSecurity>

After you have a password, you then need to produce encrytped versions of your GPG and Sonatype passwords using the following, one time for each password

mvn --encrypt-password

This command produces an encrypted version of of the password which looks similar to

{COQLCE6DU6GtcS5P=}

Place the encpted passwords into the appropriate servers section of your ${user.home}/.m2/settings.xml file. This will look similar to

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      https://maven.apache.org/xsd/settings-1.0.0.xsd">
  <profiles>
    <profile>
      <id>ossrh</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <repositories>
        <repository>
          <id>ossrh</id>
          <url>https://oss.sonatype.org/content/repositories/snapshots</url>
          <releases>
            <enabled>false</enabled>
          </releases>
          <snapshots>
            <enabled>true</enabled>
            <updatePolicy>always</updatePolicy>
          </snapshots>
        </repository>
      </repositories>
    </profile>
  </profiles>

  <servers>
    <server>
      <id>ossrh</id>
      <username>user</username>
      <password>{COQLCE6DU6GtcS5P=}</password>
    </server>
    <server>
      <id>gpg.passphrase</id>
      <passphrase>{COQLCE6DU6GtcS5P=}</passphrase>
    </server>
  </servers>
</settings>

The Sonatype username and password should go into the appropriate server section. In the example, this server has the identifier ossrh. The GPG password should go in the server section with the identifier gpg.passphrase which is the default value for the passphraseServerId property. Alternatively, you can set the GPG paramters using properties

<properties>
  <gpg.executable>gpg</gpg.executable>
  <gpg.keyname>AB12C3D4</gpg.keyname>
</properties>

For more information, see Deploying to OSSRH with Apache Maven.

Pre-release Checks

Stabilize All BOM References

Maven plugins are essentially blind to the existence of BOMs, owing to how Maven processes BOM information. BOMs are basically dependencyManagement sections that get inlined into your project's POM (with some de-duplication). The implication of this is that plugins like the maven-release-plugin cannot see the original BOM reference when they execute. They only see the contents of that BOM. So, if your POM contains a reference to a SNAPSHOT version of a BOM when you do a release, that SNASPHOT reference will be in your released POM file, and nobody will be able to use your libraries without somehow gaining access to that SNAPSHOT BOM.

Check for Improperly Formatted License Headers

Each source file, along with pom.xml and test resource files, should have a header comment that reflects the license of the project. We're using the license-maven-plugin for this purpose. It reformats license headers when you use the formatting profile (-Pformatting) and verifies their presence and correctness during the project release as part of the release build profile. If any of the license headers are missing or incorrect, the verification step will fail the build leaving you with a release to rollback before you can correct the problem and try again.

So, before you try to run the actual release, perform the following steps:

#!/bin/bash

# We don't need tests, but may need module dependency assemblies, etc.
mvn -Pformatting clean install -DskipTests=true

# Check whether any files were updated
git status | grep 'modified:'
changed=$?

# Only necessary if files changed
if [ $changed != 0 ]; then
    git add .
    git commit -am "Fix license headers"
fi

Run the Release

mvn clean release:prepare release:perform -Prelease

Or, if you have a GPG profile in your $HOME/.m2/settings.xml:

mvn clean release:prepare release:perform -Prelease -Pgpg

Note: Due to quirks with the Maven release plugin, to avoid running the integration tests for both prepare and perform goals, the pom is configured to run the tests in the perform goal using the releaseProfiles configuration. This does mean that if an error occurs the developer may have to roll back changes from GitHub and drop the Sonatype staging repositories.

Roll Back a Release

The rollback process below is ONLY safe if you haven't distributed the binaries. If you have, then you must not rollback. Instead, you have to release a new version.

To revert the changes from the release plugin, you can use the release plugin. Run mvn release:rollback release:clean to revert the POM version changes and clean up the release-plugin accounting (temporary) files. Check the git tags. If the release tag exists, you'll have to remove it (locally and remotely). The tag format is: <parent-pom-artifactId>-<version>. Run git tag -d <tag-name> to delete the tag locally and run git push --delete upstream <tag-name> to delete the tag on the canonical GitHub repository. You may also need to remove a staging repository from the Sonatype OSS Reposirory.

Log in and select Staging Repositories from the menu on the left. Select your staging repository. The details should mention your username and the content tab should contain whatever binaries you managed to upload before the build failed. Be very careful not to modify someone else's repository. Finally, click on the Drop button. Confirm the operation if it asks you to.