diff --git a/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/Configuration.java b/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/Configuration.java index 19766e9ac3..14fa4a320f 100644 --- a/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/Configuration.java +++ b/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/Configuration.java @@ -10,6 +10,11 @@ public interface Configuration { */ String releaseUrl(); + /** + * The url of the staging release repository. + */ + String stagingUrl(); + /** * The urls to the remote snapshot repository. */ diff --git a/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/MavenBndRepository.java b/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/MavenBndRepository.java index 13fe742ad9..c28ba7d0b9 100644 --- a/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/MavenBndRepository.java +++ b/biz.aQute.repository/src/aQute/bnd/repository/maven/provider/MavenBndRepository.java @@ -646,6 +646,13 @@ synchronized boolean init() { List snapshot = MavenBackingRepository.create(configuration.snapshotUrl(), reporter, localRepo, client); + MavenBackingRepository staging = null; + + if (configuration.stagingUrl() != null) { + staging = MavenBackingRepository.getBackingRepository(configuration.stagingUrl(), + reporter, localRepo, client); + } + for (MavenBackingRepository mbr : release) { if (mbr.isRemote()) { remote = true; @@ -660,7 +667,8 @@ synchronized boolean init() { } } - storage = new MavenRepository(localRepo, name, release, snapshot, client.promiseFactory() + storage = new MavenRepository(localRepo, name, release, staging, snapshot, + client.promiseFactory() .executor(), reporter); File indexFile = getIndexFile(); diff --git a/biz.aQute.repository/src/aQute/maven/provider/MavenRepository.java b/biz.aQute.repository/src/aQute/maven/provider/MavenRepository.java index 716ddfb5cc..81026a7601 100644 --- a/biz.aQute.repository/src/aQute/maven/provider/MavenRepository.java +++ b/biz.aQute.repository/src/aQute/maven/provider/MavenRepository.java @@ -40,6 +40,7 @@ public class MavenRepository implements IMavenRepo, Closeable { private final File base; private final String id; private final List release = new ArrayList<>(); + private final MavenBackingRepository stagingRepository; private final List snapshot = new ArrayList<>(); private final PromiseFactory promiseFactory; private final boolean localOnly; @@ -48,8 +49,15 @@ public class MavenRepository implements IMavenRepo, Closeable { public MavenRepository(File base, String id, List release, List snapshot, Executor executor, Reporter reporter) throws Exception { + this(base, id, release, null, snapshot, executor, reporter); + } + + public MavenRepository(File base, String id, List release, + MavenBackingRepository stagingRepository, List snapshot, Executor executor, + Reporter reporter) throws Exception { this.base = base; this.id = id; + this.stagingRepository = stagingRepository; if (release != null) this.release.addAll(release); if (snapshot != null) @@ -114,7 +122,13 @@ public Release release(final Revision revision, final Properties context) throws if (revision.isSnapshot()) { return new SnapshotReleaser(this, revision, snapshot.isEmpty() ? null : snapshot.get(0), context); } - return new Releaser(this, revision, release.isEmpty() ? null : release.get(0), context); + + MavenBackingRepository releaseRepo = stagingRepository; + if (releaseRepo == null) { + releaseRepo = release.isEmpty() ? null : release.get(0); + } + + return new Releaser(this, revision, releaseRepo, context); } @Override diff --git a/biz.aQute.repository/src/aQute/maven/provider/packageinfo b/biz.aQute.repository/src/aQute/maven/provider/packageinfo index 2d3a9f6c7a..fd56132965 100644 --- a/biz.aQute.repository/src/aQute/maven/provider/packageinfo +++ b/biz.aQute.repository/src/aQute/maven/provider/packageinfo @@ -1 +1 @@ -version 2.5 +version 2.6 diff --git a/biz.aQute.repository/test/aQute/bnd/repository/maven/provider/MavenBndRepoTest.java b/biz.aQute.repository/test/aQute/bnd/repository/maven/provider/MavenBndRepoTest.java index 6e0fbaed22..56d9b53b60 100644 --- a/biz.aQute.repository/test/aQute/bnd/repository/maven/provider/MavenBndRepoTest.java +++ b/biz.aQute.repository/test/aQute/bnd/repository/maven/provider/MavenBndRepoTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -73,6 +74,7 @@ public class MavenBndRepoTest { File tmp; File local; File remote; + File staging; File index; private MavenBndRepository repo; @@ -83,9 +85,11 @@ public class MavenBndRepoTest { public void setUp() throws Exception { local = IO.getFile(tmp, "local"); remote = IO.getFile(tmp, "remote"); + staging = IO.getFile(tmp, "staging"); index = IO.getFile(tmp, "index"); remote.mkdirs(); local.mkdirs(); + staging.mkdirs(); IO.copy(IO.getFile("testresources/mavenrepo"), remote); IO.copy(IO.getFile("testresources/mavenrepo/index.maven"), index); @@ -1239,4 +1243,45 @@ public void testPutBundledWithMetaInfMaven() throws Exception { assertNotNull(file); } + @Test + public void testPutReleaseWithStaging() throws Exception { + Workspace ws = new Workspace(IO.getFile("testdata/releasews")); + Project p1 = ws.getProject("p1"); + Project indexProject = ws.getProject("index"); + + Map map = new HashMap<>(); + map.put("releaseUrl", remote.toURI() + .toString()); + map.put("stagingUrl", staging.toURI() + .toString()); + p1.set("-pom", "true"); + config(ws, map); + + repo.begin(indexProject); + File jar = IO.getFile("testresources/mavenrepo/org/osgi/org.osgi.dto/1.0.0/org.osgi.dto-1.0.0.jar"); + PutOptions po = new PutOptions(); + po.context = p1; + PutResult put = repo.put(new FileInputStream(jar), po); + assertTrue(put.alreadyReleased); + + File demoJar = IO.getFile("testresources/demo.jar"); + PutOptions indexPo = new PutOptions(); + indexPo.context = indexProject; + put = repo.put(new FileInputStream(demoJar), indexPo); + assertFalse(put.alreadyReleased); + + repo.end(indexProject); + + assertTrue(indexProject.check()); + assertFalse(IO.getFile(remote, "biz/aQute/bnd/demo/1.0.0/demo-1.0.0-index.xml") + .isFile()); + assertTrue(IO.getFile(staging, "biz/aQute/bnd/demo/1.0.0/demo-1.0.0-index.xml") + .isFile()); + + assertTrue(IO.getFile(remote, "org/osgi/org.osgi.dto/1.0.0/org.osgi.dto-1.0.0.jar") + .isFile()); + assertFalse(IO.getFile(staging, "org/osgi/org.osgi.dto/1.0.0/org.osgi.dto-1.0.0.jar") + .isFile()); + } + } diff --git a/biz.aQute.repository/testresources/mavenrepo/org/osgi/org.osgi.dto/1.0.0/org.osgi.dto-1.0.0.pom b/biz.aQute.repository/testresources/mavenrepo/org/osgi/org.osgi.dto/1.0.0/org.osgi.dto-1.0.0.pom new file mode 100644 index 0000000000..114f955341 --- /dev/null +++ b/biz.aQute.repository/testresources/mavenrepo/org/osgi/org.osgi.dto/1.0.0/org.osgi.dto-1.0.0.pom @@ -0,0 +1,34 @@ + + + 4.0.0 + org.osgi + org.osgi.dto + 1.0.0 + OSGi Companion Code for org.osgi.dto Version 1.0.0. + org.osgi:org.osgi.dto + http://www.osgi.org/ + + OSGi Alliance + http://www.osgi.org/ + + + + Apache License, Version 2.0 + http://opensource.org/licenses/apache2.0.php + repo + + + + https://osgi.org/git/build.git + scm:git:https://osgi.org/git/build.git + scm:git:https://osgi.org/git/build.git + + + + osgi + info@osgi.org + OSGi Alliance + OSGi Alliance + + + diff --git a/biz.aQute.repository/testresources/mavenrepo/org/osgi/org.osgi.dto/1.0.0/org.osgi.dto-1.0.0.pom.sha1 b/biz.aQute.repository/testresources/mavenrepo/org/osgi/org.osgi.dto/1.0.0/org.osgi.dto-1.0.0.pom.sha1 new file mode 100644 index 0000000000..997c02b4aa --- /dev/null +++ b/biz.aQute.repository/testresources/mavenrepo/org/osgi/org.osgi.dto/1.0.0/org.osgi.dto-1.0.0.pom.sha1 @@ -0,0 +1 @@ +a1638f7dbb775f2dd9389a6e14a60b5ca90e932c \ No newline at end of file diff --git a/docs/_instructions/maven-release.md b/docs/_instructions/maven-release.md index 31f84a5374..672682174a 100644 --- a/docs/_instructions/maven-release.md +++ b/docs/_instructions/maven-release.md @@ -35,13 +35,7 @@ If `sources` or `javadoc` has the attribute `force=true`, either one will be rel The `aQute.maven.bnd.MavenBndRepository` is a bnd plugin that represent the local and a remote Maven repository. The locations of both repositories can be configured. The local repository is always used as a cache for the remote repository. -The repository has the following parameters: - -* `url` – The remote repository URL. Credentials and proxy can be set with the [Http Client options]. The url should be the base url of a Maven repository. If no URL is configured, there is no remote repository. -* `local` – (`~/.m2/repository`) The location of the local repository. By default this is `~/.m2/repository`. It is not possible to not have a local repository because it acts as the cache. -* `generate` – (`JAVADOC,SOURCES`) A combination of `JAVADOC` and/or `SOURCES`. If no `-maven-release` instruction is found and the released artifact contains source code, then the given classifiers are used to generate them. -* `readOnly` – (`false`) Indicates if this is a read only repository -* `name` – The name of the repository +For a detailed configuration of the [Maven Bnd Repository Plugin][1], please look at the documentation page. If the Maven Bnd Repository is asked to put a file, it will look up the `-maven-release` instruction using merged properties. The property is looked up from the bnd file that built the artifact. However, it should in general be possible to define this header in the workspace using macros like `${project}` to specify relative paths. @@ -58,7 +52,7 @@ For example: archive;\ path=files/feature.json; classifier=feature - + # Signing If the instruction contains the sign attribute and release build is detected the repository tries to apply [gnupg](https://gnupg.org/) via a command process to create `.asc` files for all deployed artifacts. This requires a Version of [gnupg](https://gnupg.org/) installed on your build system. By default it uses the `gpg` command. If the `passphrase` is configured, it will hand it over to the command as standard input. The command will be constructed as follows: `gpg --batch --passphrase-fd 0 --output .asc --detach-sign --armor `. Some newer gnupg versions will ignore the passphrase via standard input for the first try and ask again with password screen. This will crash the process. Have a look [here](https://stackoverflow.com/questions/19895122/how-to-use-gnupgs-passphrase-fd-argument) to teach gnupg otherwise. The command can be exchanged or amended with additional options by defining a property named `gpg` in your workspace (e.g. `build.bnd` or somewhere in the ext directory). diff --git a/docs/_plugins/maven.md b/docs/_plugins/maven.md index ee8745bfb7..36c99fd8f8 100644 --- a/docs/_plugins/maven.md +++ b/docs/_plugins/maven.md @@ -18,6 +18,23 @@ To access Maven Central use the following configuration: You can add `Group:Artifact:Version` coordinates in the `central.maven` file. The file can contain comments, empty lines, and can use macros per line. That is, you cannot create a macro with a load of GAV's. +#### Release to Maven Central + +Releasing to maven Central requires usually a couple of steps. For one you usually go through a staging repositroy like the staging nexus of sonartype. They perform a couple of checks and you manually have to clear the release via their web frontend. + +In case you version your bundles individually, e.g. sonartype will not complain if a Version of one Artifact in your staging repo already exists. The release will simply not work. Thus you can define a staging repository. The Release process will check against the releaseUrl if something already exists, but will upload to the staging URL. + +A configuration can look like this: + + -plugin.release = \ + aQute.bnd.repository.maven.provider.MavenBndRepository; \ + releaseUrl=https://repo.maven.apache.org/maven2/; \ + stagingUrl=https://oss.sonatype.org/service/local/staging/deploy/maven2/; \ + index=${.}/release.maven; \ + name="Release" + + + ### Use of .m2 Local Repository To use your local Maven repository (`~/.m2/repository`) you can define the following plugin: @@ -63,6 +80,7 @@ The class name of the plugin is `aQute.bnd.repository.maven.provider.MavenBndRep |------------------|-------|---------|-------------| | `releaseUrl` | `URI` | | Comma separated list of URLs to the repositories of released artifacts.| | `snapshotUrl` | `URI` | | Comma separated list of URLs to the repositories of snapshot artifacts.| +| `stagingUrl` | `URI` | | A single URL to the repositories staging repository. THis is required, e.g. for a release to maven central, which usually goes through a staging repository.| | `local` | `PATH`| `~/.m2/repository` | The file path to the local Maven repository. | | | | | If specified, it should use forward slashes. If the directory does not exist, the plugin will attempt to create it.| | | | | The default can be overridden with the `maven.repo.local` System property.| @@ -76,7 +94,7 @@ The class name of the plugin is `aQute.bnd.repository.maven.provider.MavenBndRep If no `releaseUrl` nor a `snapshotUrl` are specified then the repository is _local only_. -For finding archives, both URLs are used, first `releaseUrl`. +For finding archives, both URLs are used. For releasing, only the first or the `stagingUrl` is used. The `index` file specifies a view on the remote repository, it _scopes_ it. Since we use the bnd repositories to resolve against, it is impossible to resolve against the world. The index file falls under source control, it is stored in the source control management system. This guarantees that at any time the project is checked out it has the same views on its repository. This is paramount to prevent build breackages due to changes in repositories.