Chapter 3. Repositories

Repositories are where quokka stores artifacts such a .jar files. Artifacts include quokka extensions such as plugins, dependency sets and xml catalogues as well as 3rd party libraries and any artifacts installed from projects directly. In short, for a project to use an artifact, it needs to be in a repository.

Repositories don't only store artifacts, they also store information on the dependencies between artifacts. This is how quokka can process transitive dependencies (dependencies of dependencies). Each artifact may store zero or more paths that refer to other dependencies within the repository. This is implemented in exactly the same way as for projects, using paths, dependencies and path specifications. See the relevant section in the project reference for more information.

There are multiple types of repositories and an SPI so that you can write your own implementation.

At startup, quokka looks for a repository named project. If it doesn't exist it looks for a repository named shared. Project repositories allow for projects to store their dependencies locally if desired. As such, project repositories should only be defined within a particular project's properties.

The shared repository is intended to be shared amongst all projects. The actual implementation of the project and shared repositories can vary depending on the project and/or installation. Quokka determines the installation from the standard properties, those starting with q.repo.<repositoryName> controlling repository configuration.

The default installation of quokka creates the following configuration in quokka.properties within the preferences directory:

# Gets any repositories defined by the current project, or an empty string otherwise
q.project.reposElseBlank=${@ifdef q.project.repos ? q.project.repos : ''}

# Defines shared repositories across all projects (used unless q.repo.project is defined)
q.repo.shared.url=delegate:core,snapshot,release

# Snapshot: Local snapshots
q.repo.snapshot.url=file:${q.preferencesDir}/snapshots;snapshots=true;\
    parents=${q.project.reposElseBlank}

# Release: Local cache of the global repository and where local releases are installed
q.repo.release.url=file:${q.preferencesDir}/releases;\
    parents=pending-uploads,quokka,${q.project.reposElseBlank}

# Global: the global quokka repository
q.repo.quokka.url=url:http://quokka.ws/repository/0.2

# Upload repository: Used when you wish to upload artifacts to the quokka global repository
q.repo.uploads.url=file:${q.preferencesDir}/uploads;hierarchical=false;parents=quokka
q.repo.pending-uploads.url=file:${q.preferencesDir}/uploads;hierarchical=false

The above configuration defines that the shared repository delegate requests to the core, snapshot and release repositories (the core repository includes the core quokka libraries found in the installation directory). Furthermore, the release repository will attempt to retrieve artifacts from it's parent (the quokka global repository) if an artifact cannot be found.

In addition, the snapshot and release repositories are configured to include any repositories defined in q.project.repos as parents. This allows projects to add additional repositories to the standard shared configuration.

Finally, the uploads and pending-uploads repositories are configured to ease the process of uploading new artifacts to the global quokka repository.

While this is a reasonable configuration to get you up and running it is strongly recommended that you change it to store your releases under version control. See Release Configurations below.

The syntax used above is a short hand URL notation, that is automatically converted into properties. The format is <class>[:root][;property1=value1]*.

So q.repo.release.url=file:${user.home}/.quokka/releases;parents=quokka;confirmImport=false could be rewritten as:

q.repo.release.class=file
q.repo.release.root=${user.home}/.quokka/releases
q.repo.release.parents=quokka
q.repo.release.confirmImport=false

When initialising a repository quokka looks for a property matching the name with a q.repo.<name>.class. The class property can be the fully qualified class name of your custom repository implementation, or one of the following built-in repository implementations:

file

Stores artifacts on your local file system

url

Read-only. Retrieves artifacts from a remote url. Supports an index to retrieve metadata about available artifacts without retrieving them.

checksum

Stores dependency information and checksums locally, but the actual artifacts in held in a parent repository

delegate

Delegating repository. All requests are forwarded to the named repositories in order, returning after one of the repositories completes the request.

indexed

Indexed repository. Wraps another repository, maintaining an index of artifacts used.

bundle

Bundled repository. A bundled repository is basically a zipped up file repository. It is primarily aimed at minimised the number of individual dependencies a project has when storing dependencies under version control with the source code.

The following properties are common to all of the repository implementations:

root

Mandtory. The value required depends on the class of repository.

file, checksum

The root directory of the repository

url

The root url of the repository

delegate

A comma separated list of names of repositories to delegate to

indexed

The name of the repository to index

bundle

The artifact id of the repository bundle

The file and checksum repositories have the following additional properties:

hierarchical

Optional. If true, the repository will store artifacts in directories based on group. If false, artifacts will all be stored in a single directory. Defaults to true.

parents

Optional. A comma separated list of parent repository names. Parents are used if artifact cannot be resolved or installed in the current repository.

confirmImport

Optional. If true, the user will be prompted to confirm importing an artifact from a parent repository. It is designed to prevent accidental pollution of the repository where the user is unaware of additional dependencies. Defaults to true.

snapshots

Optional. If true, the repository supports snapshot artifact resolution (reading). Defaults to false.

installSnapshots

Optional. If true, the repository supports snapshot artifact installation (writing). Defaults to the value of snapshots.

releases

Optional. If true, the repository supports release artifact resolution. Defaults to !snapshots.

installReleases

Optional. If true, the repository supports release artifact installation. Defaults to the value of releases.

supports

Optional. A list of regular expressions of artifacts that are supported. This allows for example a particular repository to only contain apache.* artifacts.

The url repository has the following additional properties:

user

Optional. The user if the repository requires authentication

password

Optional. The password if the repository requires authentication

The indexed repository has the following additional properties:

repository

Mandatory. The name of the repository to index

indexRoot

Optional. The root directory of the index. If the indexed repository is a file repository, it defaults to a directory named _index at the root of the indexed repository.

Using the above information, we can now explore the default configuration in detail. The q.repo.share.url property is set to delegate:core,snapshot,release. So assuming your project has not defined a q.repo.project.url property, the shared repository will be used. The shared repository is a delegating repository, so will forward requests to the core, snapshot and release repositories, stopping as soon as the request is satisfied by a repository.

Releases and snapshots have been separated into different repositories to simply the migration of releases to a better location later. Snapshots do not need to be as tightly controlled as releases as they vary over time. Specifically, a release of a product should never depend on a snapshot artifact.

First, consider the case of a project attempting to resolve a snapshot artifact (one with a version ending in -ss). The delegating repository asks the core repository if it supports the artifact. It doesn't, so it moves on to snapshot repository and attempts to resolve it. If it cannot resolve it, it will try the releases repository. As releases doesn't support it, it will throw an exception.

In the case of a release artifact (anything that doesn't end in -ss), the delegating repository finds the first supporting repository, releases. If releases contains the artifact, it returns it and the request ends. However, if it is not found, the parent repositories are searched, in this case quokka. If the artifact is found in quokka, it will be imported into the releases repository (without confirmation). Subsequent requests for the artifact will now find it in the releases repository.

3.1. Release Repository Configurations

One of the main drivers behind quokka is to enable reproducible builds. For example, you should be confident that you can go back to any release in your version control system and be able to build it, generating exactly the same artifact as was released originally.

To achieve this, one essential requirement is that your repository does not change over time. Or more precisely, that once an artifact is in the repository, it or its metadata regarding dependencies never changes (additions to the repository are acceptable). As stated earlier, you need not worry about snapshots as a product should never be released with snapshot dependencies (indeed, the release plugin ensures this is so).

So how do you ensure that your repository never changes? There are several options listed in the order of safety:

  1. Store your repository under version control along side your project source, with artifacts. This is the safest option and guarantees reproducibility as all dependencies are local. Even if they do inadvertently change over time, the build will still be reproducible as the repository is in the same branch as your source and will be checked out with it.

    To implement this, you would use a project repository with the following properties:

    q.repo.project.url=delegate:core,local,shared
    q.repo.local.url=file:${basedir}/lib;hierarchical=false;parents=shared

    This will store all releases locally in your project's build/repository directory, automatically importing them from the shared repository as required. Snapshots will be delegated to the shared repository.

    You can reduce the dependencies stored in the lib directory further by using a bundled repository as follows:

    q.repo.project.url=delegate:core,bundle,local,shared
    q.repo.bundle.url=bundle:quokka.bundle.extensions:2008.1;\
        snapshots=true;repository=shared
    q.repo.local.url=file:${basedir}/lib;hierarchical=false;parents=shared
    

    Pros: Guaranteed reproducibility. You are aware when dependencies are added to your project as they have to be checked in. The project local repository is easy to manage as it only needs to contain the artifacts required to build the current revision of the project. You are fully protected should someone inadvertently update an artifact in the corporate or global repositories.

    Cons: Artifacts such as .jar files will be stored in version control for every project. Overhead of maintaining the project local repository.

  2. Store your repository under version control along side your project source, without artifacts. In this option, the structure of your repository, including all dependency information is stored locally with your project, like option above. However, instead of storing the actual artifacts such as .jar files, a checksum is stored instead and the artifact itself is retrieved from a parent.

    To implement this, you would use a project repository with the following properties:

    q.repo.project.url=delegate:core,local,shared
    q.repo.local.url=checksum:lib;parents=shared

    Pros: Reproducibility is not guaranteed, but it can detect if a build is not reproducible. The only thing that can vary is someone inadvertently replacing an artifact with a different version in the corporate repository. As a checksum is stored, this can be detected allowing manual intervention to fix the problem. You are aware when dependencies are added to your project as they have to be checked in. The project local repository is easy to manage as it only needs to contain the artifacts required to build the current revision of the project.

    Cons: Overhead of maintaining the project local repository.

  3. Use a shared corporate repository under version control. In this option, the releases repository root should point to a directory that is under version control. All projects would share this repository.

    To implement this, you change the root to the working copy of the shared repository. Set confirmImport to true to be informed any time an artifact is imported into the shared repository. At this point, update your working copy to see if it has been added by another developer. If it has CTRL+C to quit and re-run to pick up the new dependency. If not, confirm the import and commit it to the shared repository.

    Pros: There is an inbuilt review mechansim in the process as all developers will be aware that dependencies have changed when they update working copies. It enforces release repositories to be consistent amongst developers. There is an inbuilt audit facility - the version control system can confirm if any artifacts or metadata has been modified over time.

    Cons: Reproducibility is not guaranteed.

Naturally, there are many other options available. Pick the option that best suits your requirements.

3.2. Snapshot Repository Configurations

Less care has to be taken with snapshot dependencies as they do not impact reproducibility - a build is simply not reproducible if it has a snapshot dependency.

Snapshot repositories are configured like any others in that you can have parent/child relationships etc. The main consideration with them is that once a snapshot version is in the lowest level repository, any updates in parents will be ignored. This can be a good thing as it is annoying to suddenly have a new snapshot appear that breaks your build. This happens with maven as it uses an expiry period to check repositories for updates.

Quokka does not implement expiry for this very reason. Instead, the repository plugin has an update-snapshots target that will traverse all repositories to find the latest version available on demand.

It is recommended that you leave the default snapshots configuration where it is, but add a parent repository that points to your continuous integration (CI) server (assuming you have one). Then any snapshots from the CI server will be picked up automatically and anything you generate locally will override them. Whenever you want to pick up the latest snapshots from the CI server, use update-snapshots as described earlier.

3.3. Installing 3rd Party Libraries

If you require a 3rd party library (one that you don't build and install yourself), the chances are it won't be in the global repository. Therefore, you'll need to add it to one of your local release repositories. If the artifact exists in the maven repository, you can use the quokka maven plugin to import the artifact. This is the best option as it will import the artifact along with any dependencies it has automatically. See Importing from Maven below. If you intend to upload the libraries to the quokka global repository, see Uploading to the Global Repository below.

Otherwise, you can install it using the repo:install target from repository plugin as follows. Firstly, add the repository plugin to your project (within the dependency-set element):

<plugin group="quokka.plugin.repository" version="<version>">

Note: you can determine what version of the plugin to use via: quokka list-plugins -Dplugin=quokka.plugin.repository

Decide on a group, name and version for the artifact you require, taking into account the naming standards below. Then install the artifact using the following command:

$ quokka repo:install -Did=<group>:[name]:[type]:<version> \
     -Dfile=<path of file to install>

For example, to install the hibernate annotations 3.3 jar file, you would use:

$ quokka repo:install -Did=hibernate.annotations:::3.3 \
    -Dfile=~/Downloads/hibernate-annotaions.jar

Note: in this case the name will default to annotations, and the type to jar, so there's no need to specifiy them.

If your library has no dependencies, then that's it.

If your library has dependencies then you need to add metadata to the repository declaring the dependencies (and add then as well if necessary). Note: the location of the repository file was displayed on the console when the artifact was installed.

The repository file is essentially a subset of the dependency-set element described in the project reference chapter, limited to path, path-specification, dependency and override elements, plus description and license elements. Below is an example for the Cobertura plugin:

<?xml version="1.0"?>
<!DOCTYPE artifact PUBLIC "quokka.ws/dtd/repository-0.2"
                   "http://quokka.ws/dtd/repository-0.2.dtd">

<artifact group="quokka.plugin.cobertura" name="cobertura" type="jar" version="0.1">
    <description>Cobertura plugin for quokka</description>
    
    <paths>
        <path id="instrument" description="Path required for instrumenting classes"/>
        <path id="coverage" description="Path required collecting coverage data"/>
        <path id="runtime" description="Runtime class path"/>
        <path id="report" description="Path required for generating the coverage report"/>
    </paths>

    <dependencies>
        <dependency group="apache.oro" version="2.0.8" paths="instrument"/>
        <dependency group="apache.ant" version="1.7" paths="runtime"/>
        <dependency group="quokka.plugin.lifecycle" version="0.1" paths="runtime"/>
        <dependency group="apache.log4j" version="1.2.15" paths="instrument, report"/>
        <dependency group="quokka.core.bootstrap-util" version="0.1"
                    paths="runtime"/>
        <dependency group="sf.cobertura" version="1.8" paths="runtime, coverage"/>
        <dependency group="quokka.plugin.devreport" version="0.1" paths="runtime"/>
        <dependency group="quokka.core.plugin-spi" version="0.1" paths="runtime"/>
        <dependency group="objectweb.asm" version="2.2.1" paths="instrument"/>
    </dependencies>

    <licenses>
        <license group="license.apache" version="2.0"/>
    </licenses>
</artifact>

From the example, you can see that a dependency can have multiple paths (that correspond to different uses). In this case different Cobertura tasks require different libraries.

Most libraries usually have a single path and by convention this is called runtime. It is highly recommended that you follow this convention as it means users that depend on your library don't need to specify the path as runtime is assumed. Also, if you just as a single runtime path, you can omit the paths declaration completely.

3.3.1. Naming Standards

If you ever intend to submit libraries to the global repository, or wish to migrate to the global repository versions when they become available, you should be aware of the following naming standards.

  • The group name can be the name of your project only if you own a matching domain. It should be without the top level domain. e.g. hibernate.org would have a group of hibernate. Otherwise, the group should be prefixed by the name of the project host, such as apache for all apache projects and sf for sourceforge projects.

  • The group should uniquely identify a root under version control. Put another way, all artifacts under a given group are released at the same time with the same version. e.g. Ant is released with a number of optional jars. All of these jars have the same version and are from the same source tree. Therefore they have the same group, apache.ant. However, for hibernate it's modules have their own life cycles, so each belongs in it's own group, such as hibernate.core, hibernate.annotations and so on.

  • Where there is only one artifact for a given group (the general case), the name should be the last segment of the group. e.g. group=hibernate.core, name=core. This is the default and saves everyone from naming the artifact in this case.

  • All runtime dependencies are properly described and marked as optional as appropriate

  • Generally, a single path of runtime should be supplied, but other paths can and should be supplied if the library is commonly deployed in different configurations with different paths

  • The version should match the standard format where possible to allow comparisons between versions.

3.3.2. Importing from Maven

If the dependency you need exists in the Maven2 repository, then importing it can be a be the easiest option. Importing has the advantage that any transitive runtime dependencies can be imported automatically. Unfortunately, to perform the import it is necessary to have maven installed (versions greater than 2.0.8 are recommended as prior versions may generate incorrect paths). Note: If you intend to upload to the global repository, import to the uploads repository (see the next section).

The following will walk through the process of importing Cobertura 1.9 into your local releases repository.

Firstly, add the maven and repository plugins to your project's dependency set if you have not already done so as follows:

<plugin group="quokka.plugin.maven" version="?"/>
<plugin group="quokka.plugin.repository" version="?"/>

Tip: If you don't know what version to add, use the quokka list-plugins command. This will list the versions of plugins that are compatible with your installed version of quokka.

You can now use the maven:import target to import artifacts from Maven. The import process will automatically import any transitive dependencies, including optional dependencies.

$ quokka maven:import -Did=net.sourceforge.cobertura:cobertura:jar::1.9

If mappings between Maven and Quokka ids have been defined for Cobertura and all of it's dependencies, the import will just work. In this case, it failed with the following output:

<--- Importing artifact: net.sourceforge.cobertura:cobertura:jar::1.9 --->
Description: Cobertura is a Java tool that calculates the percentage of code accessed... 
dependencies:
    oro:oro:jar::2.0.8 -> apache.oro:oro:jar:2.0.8
    asm:asm:jar::2.2.1 -> ow2.asm:asm:jar:2.2.1
    asm:asm-tree:jar::2.2.1 -> No mapping
    log4j:log4j:jar::1.2.9 -> apache.log4j:log4j:jar:1.2.9
    org.apache.ant:ant:jar::1.7.0 -> apache.ant:ant:jar:1.7
Transitive runtime path:
    net.sourceforge.cobertura:cobertura:jar::1.9 -> No mapping
        org.apache.ant:ant:jar::1.7.0 -> apache.ant:ant:jar:1.7
            org.apache.ant:ant-launcher:jar::1.7.0 -> apache.ant:launcher:jar:1.7
        asm:asm-tree:jar::2.2.1 -> No mapping
            asm:asm:jar::2.2.1 -> ow2.asm:asm:jar:2.2.1, omittedForDuplicate
        asm:asm:jar::2.2.1 -> ow2.asm:asm:jar:2.2.1
        oro:oro:jar::2.0.8 -> apache.oro:oro:jar:2.0.8
        log4j:log4j:jar::1.2.9 -> apache.log4j:log4j:jar:1.2.9
Licenses:
   The GNU General Public License, Version 2 -> license.gnu-public:gnu-public:license:2.0


BUILD FAILED
Mappings (listed above) need to be defined from maven to quokka ids.
Edit /Users/andrew/Library/Preferences/Quokka/mappings.xml to define mappings...

Tip: If the import command failed in some other way, it may be due to an incorrectly configured Maven installation. Use the debug command line option (-d) to see what is going on. It will display the command executed and will not delete the temporay project created so you can try running it directly with Maven.

Examining the output above, it shows that two Maven ids do not have any mappings to their Quokka counterparts, namely net.sourceforge.cobertura:cobertura:jar::1.9 and asm:asm-tree:jar::2.2.1. This means that the gloabl mapping file does not contain any relevant mappings and you will need to define your own local mappings. For the import to proceed, we need to define them in the local mappings file (the location of the file is displayed on the console).

We'll add the following entries:

<id-mapping from="net.sourceforge.cobertura:cobertura" to="sf.cobertura"/> 
<id-mapping from="asm:asm-tree" to="ow2.asm:tree"/>

Tip: To check if there's another version of the same artifact in the repository, you can use repository plugin's list target. e.g. quokka repo:list | grep cobertura

Note that the mapping for Cobertura maps the Maven groupId and artifactId to a single Quokka group, but for asm, it maps it to a group of o2w.asm and a name of tree. This is following the naming standards - artifacts released at the same time with the same version number belong in the same group, otherwise the artifact belongs in it's own group and the name may be omitted.

Now we can try running the import again. The exact results depends on what artifacts already exist the repository and what mappings are defined globally and locally. In this particularly case, the import succeeded, importing net.sourceforge.cobertura:cobertura:jar::1.9 and asm:asm-tree:jar::2.2.1, org.apache.ant:ant-launcher:jar::1.7.0 and org.apache.ant:ant:jar::1.7.0 as transitive dependencies.

You should carefully review the dependencies imported from Maven, as the metadata in the Maven repository is often incorrect or incomplete. For more information on import options see the maven plugin help.

3.3.3. Uploading to the Global Repository

If your product is a library that will be incorporated into other products, it is highly recommended that you upload it and its dependencies to the global repository. This allows users to combine your library with other libraries, managing any shared dependencies and detecting conflicts.

To upload to the global repository, the artifacts have to comply with a number of conditions:

  • If the artifact exists in the Maven repository, it must be imported from Maven. This condition is in place so that a lot of the verification steps can be automated. It also makes it easier to import a new version of an artifact when released to the Maven repository. The Maven repository can be difficult to search - I recommend using http://www.mvnrepository.com/ to search for the artifact first.

  • The artifacts must adhere to the naming standards.

  • The artifacts must pass various verification tests using the maven:verify target. This includes having valid descriptions and licenses.

This section will continue the example of importing Cobertura 1.9 with the purpose of uploading to the global repository. Importing is essentially exactly the same as above. However, instead of importing into your releases repository, we'll import into a dedicated uploads repository (which is defined by default). The import command becomes:

$ quokka maven:import -Did=net.sourceforge.cobertura:cobertura:jar::1.9\
     -Drepository=uploads

Assuming all mappings are present and correct (as described above), Cobertura and any transitive dependencies not already in the global repository will now by installed in the uploads repository.

The next step is to run the maven:verify target to check that the imported artifacts are consistent. It is similar to lint in that it will flag things that are false positives. Verification is performed with the following command:

$ quokka maven:verify -Drepository=uploads

And it produces the following output:

No license: ow2.asm:tree:jar:2.2.1 ...
Name mismatches group: ow2.asm:tree:jar:2.2.1
No description: ow2.asm:tree:jar:2.2.1 ...
Dependencies and path specs do not match previous: sf.cobertura:cobertura:jar:1.9 ...
Resolved path does not match previous: sf.cobertura:cobertura:jar:1.9 runtime

The no license and description and straight forward and can be fixed by editing the relevant repository files (the file locations are on the console).

"Name mismatches group" refers to the fact that for the asm artifact, the name (tree) does not match the last segment of the group (asm). In this case, it is a false positive, as asm-tree is released together with asm. Most projects produce a single artifact, so this warning is there to prevent accidentally copying the Maven style of naming. However, in our case we want to get rid of the false positive. To do this, edit the verified issues file (use quokka help to find it's location - by default it's called verifiedIssues.txt and is in your preferences directory). The file should contain regular expressions to ignore, one per line. In this case we'll add:

Name mismatches group: ow2\.asm:.*

Running verification again will now ignore the false positve.

The next errors are stating that the dependencies defined for Cobertura are not consistent with the previous version of Cobertura in the repository. To see why, use the list target as follows:

$ quokka list -Did=sf.cobertura -Drepository=uploads -DincludeReferenced=true

This produces the following output:

sf.cobertura 1.8
    cobertura       jar        Cobertura is a free Java tool ...
          licenses: license.gnu-public:gnu-public:license:2.0
      dependencies:
            apache.log4j log4j 1.2.9: runtime?<
            apache.oro   oro   2.0.8: runtime?<
            ow2.asm      asm   2.2.1: runtime?<

sf.cobertura 1.9
    cobertura       jar        Cobertura is a free Java tool ...
          licenses: license.gnu-public:gnu-public:license:2.0
      dependencies:
            apache.ant   ant   1.7  : runtime!<
            apache.log4j log4j 1.2.9: runtime!<
            apache.oro   oro   2.0.8: runtime!<
            ow2.asm      asm   2.2.1: runtime!<
            ow2.asm      tree  2.2.1: runtime!<

Looking at the output we can see that the 1.9 version has two new dependencies, namely apache.ant:ant and ow2.asm:tree. Also, all the dependencies for 1.8 are optional (the question mark after the path), while the 1.9 dependencies are all mandatory (exclamation mark after path). If the 1.9 version is correct, we can just add the issues as false positives to the verified issues file.

However, in this case the 1.9 definition is not correct. Firstly, all of the Cobertura dependencies should be optional as they are not required for some of the Cobertura tasks, such as instrumenting and merging. Secondly, although Ant is technically an optional runtime dependency, it is only required for the Ant tasks and in that case it is more like Cobertura is an optional dependency of Ant. For consistency, it will be removed. That leaves ow2.asm:tree as the only discrepancy. This is more difficult to resolve. In this case, both binary packages were downloaded and compared - it turns out that ow2.asm:tree is a geniunely new dependency added in 1.9. So, after editing the repository.xml file we end up with:

sf.cobertura 1.8
    cobertura       jar        Cobertura is a free Java tool ...
          licenses: license.gnu-public:gnu-public:license:2.0
      dependencies:
            apache.log4j log4j 1.2.9: runtime?<
            apache.oro   oro   2.0.8: runtime?<
            ow2.asm      asm   2.2.1: runtime?<

sf.cobertura 1.9
    cobertura       jar        Cobertura is a free Java tool ...
          licenses: license.gnu-public:gnu-public:license:2.0
      dependencies:
            apache.log4j log4j 1.2.9: runtime?<
            apache.oro   oro   2.0.8: runtime?<
            ow2.asm      asm   2.2.1: runtime?<
            ow2.asm      tree  2.2.1: runtime?<

Now that we've fixed all the issues, we should run verification again. This time, we'll enable the Maven path and checksum comparisons:

$ quokka maven:verify -Drepository=uploads -DcompareMavenPaths=true\
     -DcompareMavenHash=true
...
Dependencies and path specs do not match previous: sf.cobertura:cobertura:jar:1.9 ...
Maven and quokka runtime paths do not match: sf.cobertura:cobertura:jar:1.9 ...
    quokka: sf.cobertura:cobertura:jar:1.9
     maven: apache.ant:ant:jar:1.7, apache.ant:launcher:jar:1.7, 
            apache.log4j:log4j:jar:1.2.9, apache.oro:oro:jar:2.0.8, 
            ow2.asm:asm:jar:2.2.1, ow2.asm:tree:jar:2.2.1, sf.cobertura:cobertura:jar:1.9

Both of these issues are fine - the mismatch with previous is geniune, and the paths don't match as we've just edited it so that it doesn't. So we'll add them to the verified issues file:

# asm:tree added in 1.9
Dependencies and path specs do not match previous: sf\.cobertura:cobertura:jar:1\.9.*

# Made dependencies optional, removed Ant dependency
Maven and quokka runtime paths do not match: sf\.cobertura:cobertura:jar:1\.9.*

For the final cleanup, we can remove the imported Ant libraries from the uploads repository as we removed that dependency. Now we're ready to upload.

To request an upload, create an issue in the quokka issue tracker. Set the component to global-repo. Add a meaningful description and paste in any verified issues required for the import to pass verification. Zip up the uploads repository dir and make it available via a URL for downloading, including the URL in the issue tracker description. It will then be verfied and merged into the global repository. At that point you can delete your uploads repository directory use the global version instead.