Skip to content

Commit 8b3d34c

Browse files
committed
configure Sonatype deployment
Signed-off-by: Peter Kirschner <peter@klib.io>
1 parent 791f48e commit 8b3d34c

File tree

8 files changed

+217
-77
lines changed

8 files changed

+217
-77
lines changed

.github/workflows/cibuild.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ env:
3838
P2_SIGN: true
3939
P2_SIGN_KEY: ${{ secrets.GPG_KEY_ID }}
4040
P2_SIGN_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
41-
P2_PUB_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
42-
SONATYPE_BEARER: ${{ secrets.SONATYPE_BEARER }}
41+
P2_PUB_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
42+
SONATYPE_BEARER: ${{ secrets.SONATYPE_BEARER }}
4343

4444
defaults:
4545
run:
@@ -123,6 +123,9 @@ jobs:
123123
./.github/scripts/ci-publish.sh
124124
env:
125125
CANONICAL: ${{ matrix.canonical }}
126+
GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
127+
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
128+
SONATYPE_BEARER: ${{ secrets.SONATYPE_BEARER }}
126129
JFROG_USERNAME: ${{ secrets.JFROG_USERNAME }}
127130
JFROG_PASSWORD: ${{ secrets.JFROG_PASSWORD }}
128131
- name: Upload Test Reports

biz.aQute.bnd.transform/bnd.bnd

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ Bundle-Description: Class file and Manifest header Transformation Support
2323

2424
-sources: false
2525

26-
-maven-release: pom;path=JAR,\
27-
sources;-sourcepath="${project.allsourcepath}",\
28-
javadoc;-classpath="${project.buildpath}"
29-
3026
-maven-scope: provided
3127

3228
-buildpath: \

biz.aQute.repository/src/aQute/maven/provider/MavenFileRepository.java

Lines changed: 159 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
import java.net.URI;
88
import java.nio.file.Files;
99
import java.nio.file.Path;
10+
import java.util.ArrayList;
11+
import java.util.LinkedHashMap;
12+
import java.util.List;
13+
import java.util.Map;
1014
import java.util.zip.ZipEntry;
1115
import java.util.zip.ZipOutputStream;
1216

@@ -19,7 +23,7 @@
1923

2024
public class MavenFileRepository extends MavenBackingRepository {
2125

22-
private final File remote;
26+
private final File remote;
2327
private HttpClient client = null;
2428

2529
public MavenFileRepository(File localRepo, File remote, Reporter reporter) throws Exception {
@@ -89,62 +93,160 @@ public boolean isRemote() {
8993
return false;
9094
}
9195

92-
/**
93-
* Creates a ZIP archive containing all files from the remote repository directory.
94-
*
95-
* @param outputFile the destination file for the ZIP archive
96-
* @return the created ZIP file
97-
* @throws IOException if an error occurs during ZIP creation
98-
*/
99-
public File createZipArchive(File outputFile) throws IOException {
100-
if (!remote.exists() || !remote.isDirectory()) {
101-
throw new IllegalStateException("Remote directory does not exist or is not a directory: " + remote);
102-
}
103-
104-
IO.mkdirs(outputFile.getParentFile());
105-
106-
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputFile))) {
107-
Path remotePath = remote.toPath();
108-
109-
Files.walk(remotePath)
110-
.filter(path -> !Files.isDirectory(path))
111-
.forEach(path -> {
112-
try {
113-
Path relativePath = remotePath.relativize(path);
114-
ZipEntry zipEntry = new ZipEntry(relativePath.toString().replace("\\", "/"));
115-
116-
zos.putNextEntry(zipEntry);
117-
118-
try (FileInputStream fis = new FileInputStream(path.toFile())) {
119-
byte[] buffer = new byte[4096];
120-
int len;
121-
while ((len = fis.read(buffer)) > 0) {
122-
zos.write(buffer, 0, len);
123-
}
124-
}
125-
126-
zos.closeEntry();
127-
} catch (IOException e) {
128-
reporter.error("Error adding file %s to ZIP archive: %s", path, e.getMessage());
129-
}
130-
});
131-
}
132-
133-
return outputFile;
134-
}
135-
136-
/**
137-
* Creates a ZIP archive containing all files from the remote repository directory.
138-
* The ZIP file will be created in the system temp directory with a generated name.
139-
*
140-
* @return the created ZIP file
141-
* @throws IOException if an error occurs during ZIP creation
142-
*/
143-
public File createZipArchive() throws IOException {
144-
File tempFile = File.createTempFile("sonatype-bundle-", ".zip");
145-
tempFile.deleteOnExit();
146-
return createZipArchive(tempFile);
147-
}
96+
/**
97+
* Discovers all groupIds in the Maven repository by analyzing the directory
98+
* structure.
99+
*
100+
* @return map of groupId to its directory path
101+
* @throws IOException if an error occurs reading the directory
102+
*/
103+
private Map<String, File> discoverGroupIds() throws IOException {
104+
Map<String, File> groupIds = new LinkedHashMap<>();
105+
if (!remote.exists() || !remote.isDirectory()) {
106+
return groupIds;
107+
}
108+
109+
// Walk the directory tree to find all groupIds
110+
Files.walk(remote.toPath())
111+
.filter(Files::isDirectory)
112+
.forEach(dir -> {
113+
File dirFile = dir.toFile();
114+
File[] children = dirFile.listFiles();
115+
if (children != null && children.length > 0) {
116+
// Look for artifact directories (contain version
117+
// subdirectories with artifacts)
118+
for (File child : children) {
119+
if (child.isDirectory()) {
120+
File[] versionDirs = child.listFiles();
121+
if (versionDirs != null) {
122+
for (File versionDir : versionDirs) {
123+
if (versionDir.isDirectory() && containsArtifacts(versionDir)) {
124+
// Found a groupId directory
125+
Path relativePath = remote.toPath()
126+
.relativize(dir);
127+
String groupId = relativePath.toString()
128+
.replace(File.separatorChar, '.');
129+
if (!groupId.isEmpty() && !groupIds.containsKey(groupId)) {
130+
groupIds.put(groupId, dirFile);
131+
}
132+
return;
133+
}
134+
}
135+
}
136+
}
137+
}
138+
}
139+
});
140+
141+
return groupIds;
142+
}
143+
144+
/**
145+
* Checks if a directory contains Maven artifacts (JAR, POM, WAR, AAR
146+
* files).
147+
*/
148+
private boolean containsArtifacts(File dir) {
149+
File[] files = dir.listFiles();
150+
if (files == null) {
151+
return false;
152+
}
153+
for (File file : files) {
154+
if (file.isFile()) {
155+
String name = file.getName();
156+
if (name.endsWith(".jar") || name.endsWith(".pom") || name.endsWith(".war") || name.endsWith(".aar")) {
157+
return true;
158+
}
159+
}
160+
}
161+
return false;
162+
}
163+
164+
/**
165+
* Creates ZIP archives, one for each Maven namespace/groupId in the
166+
* repository. Each archive contains all files for that specific groupId.
167+
*
168+
* @return list of created ZIP files with groupId information
169+
* @throws IOException if an error occurs during ZIP creation
170+
*/
171+
public List<GroupIdArchive> createZipArchive() throws IOException {
172+
if (!remote.exists() || !remote.isDirectory()) {
173+
throw new IllegalStateException("Remote directory does not exist or is not a directory: " + remote);
174+
}
175+
176+
List<GroupIdArchive> archives = new ArrayList<>();
177+
Map<String, File> groupIds = discoverGroupIds();
178+
179+
if (groupIds.isEmpty()) {
180+
reporter.warning("No groupIds found in repository: %s", remote);
181+
return archives;
182+
}
183+
184+
reporter.trace("Found %d groupId(s) in repository: %s", groupIds.size(), groupIds.keySet());
185+
186+
// Create a separate ZIP archive for each groupId
187+
for (Map.Entry<String, File> entry : groupIds.entrySet()) {
188+
String groupId = entry.getKey();
189+
File groupDir = entry.getValue();
190+
191+
String sanitizedGroupId = groupId.replace('.', '_');
192+
File tempFile = File.createTempFile("sonatype-bundle-" + sanitizedGroupId + "-", ".zip");
193+
tempFile.deleteOnExit();
194+
195+
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tempFile))) {
196+
Path groupDirPath = groupDir.toPath();
197+
Path remotePath = remote.toPath();
198+
199+
Files.walk(groupDirPath)
200+
.filter(path -> !Files.isDirectory(path))
201+
.forEach(path -> {
202+
try {
203+
// Use path relative to remote root (includes
204+
// groupId path)
205+
Path relativePath = remotePath.relativize(path);
206+
ZipEntry zipEntry = new ZipEntry(relativePath.toString()
207+
.replace("\\", "/"));
208+
209+
zos.putNextEntry(zipEntry);
210+
211+
try (FileInputStream fis = new FileInputStream(path.toFile())) {
212+
byte[] buffer = new byte[4096];
213+
int len;
214+
while ((len = fis.read(buffer)) > 0) {
215+
zos.write(buffer, 0, len);
216+
}
217+
}
218+
219+
zos.closeEntry();
220+
} catch (IOException e) {
221+
reporter.error("Error adding file %s to ZIP archive for groupId %s: %s", path, groupId,
222+
e.getMessage());
223+
}
224+
});
225+
}
226+
227+
archives.add(new GroupIdArchive(groupId, tempFile));
228+
reporter.trace("Created ZIP archive for groupId %s: %s", groupId, tempFile);
229+
}
230+
231+
return archives;
232+
}
233+
234+
/**
235+
* Container class for a groupId and its corresponding archive file.
236+
*/
237+
public static class GroupIdArchive {
238+
public final String groupId;
239+
public final File archiveFile;
240+
241+
public GroupIdArchive(String groupId, File archiveFile) {
242+
this.groupId = groupId;
243+
this.archiveFile = archiveFile;
244+
}
245+
246+
public String getSanitizedGroupId() {
247+
return groupId.replace('.', '_');
248+
}
249+
}
148250

149251
protected HttpClient getClient() {
150252
return client;

biz.aQute.repository/src/aQute/maven/provider/Releaser.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,16 +115,34 @@ private void prepareSonatypeUpload() throws IOException, Exception {
115115
throw new IllegalStateException("No release repository configured for Sonatype upload");
116116
}
117117
}
118-
logger.info("Creating and uploading deployment bundle for Sonatype Central Portal");
118+
logger.info("Creating and uploading deployment bundles for Sonatype Central Portal");
119119
MavenFileRepository mfr = (MavenFileRepository) mbr;
120120
client = mfr.getClient();
121-
File deploymentBundle = mfr.createZipArchive();
122-
uploadToPortal(deploymentBundle);
123-
File deploymentIdFile = Files.createTempFile("deploymentid", ".txt")
124-
.toFile();
125-
Files.writeString(deploymentIdFile.toPath(), deploymentId, StandardOpenOption.CREATE);
126-
deploymentIdFile.deleteOnExit();
127-
mbr.store(deploymentIdFile, MavenBndRepository.SONATYPE_DEPLOYMENTID_FILE);
121+
122+
// Create archives for each groupId
123+
List<MavenFileRepository.GroupIdArchive> archives = mfr.createZipArchive();
124+
if (archives.isEmpty()) {
125+
throw new IllegalStateException("No groupIds found in staging repository");
126+
}
127+
128+
logger.info("Found {} groupId(s) to upload", archives.size());
129+
130+
// Upload each archive separately
131+
for (MavenFileRepository.GroupIdArchive archive : archives) {
132+
logger.info("Processing groupId: {}", archive.groupId);
133+
uploadToPortal(archive.archiveFile);
134+
135+
// Store deployment ID file with groupId in filename
136+
String sanitizedGroupId = archive.getSanitizedGroupId();
137+
File deploymentIdFile = Files.createTempFile(sanitizedGroupId + "_deploymentid", ".txt")
138+
.toFile();
139+
Files.writeString(deploymentIdFile.toPath(), deploymentId, StandardOpenOption.CREATE);
140+
deploymentIdFile.deleteOnExit();
141+
142+
String deploymentIdPath = sanitizedGroupId + "_" + MavenBndRepository.SONATYPE_DEPLOYMENTID_FILE;
143+
mbr.store(deploymentIdFile, deploymentIdPath);
144+
logger.info("Completed upload for groupId: {} with deployment ID: {}", archive.groupId, deploymentId);
145+
}
128146
}
129147

130148
private String normalize(String sonatypePublisherUrl) {

bndtools.m2e.debug.fragment/bnd.bnd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Bundle-SymbolicName: ${p};singleton:=true
77
Fragment-Host: org.eclipse.m2e.maven.runtime
88

9-
-buildpath: org.eclipse.m2e.maven.runtime
9+
-buildpath: org.eclipse.m2e.maven.runtime;maven-optional=true
1010

1111
Import-Package: \
1212
javax.crypto,\

bndtools.m2e/bnd.bnd

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
bndtools.api;version=latest,\
1919
bndtools.core;version=latest,\
2020
slf4j.api;version=latest,\
21-
org.eclipse.m2e.maven.runtime,\
21+
org.eclipse.m2e.maven.runtime;maven-optional=true,\
22+
org.eclipse.m2e.core;maven-optional=true,\
23+
org.eclipse.m2e.jdt;maven-optional=true,\
2224
org.apache.maven:maven-artifact,\
2325
org.apache.maven:maven-core,\
2426
org.apache.maven:maven-model,\

cnf/build.bnd

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@ Bundle-Version: ${base.version}.${tstamp}-SNAPSHOT
4343
"org.osgi.service.*.annotations";~version=latest
4444

4545
# Maven info. The maven artifactId defaults to Bundle-SymbolicName
46+
# signing is only active if GPG_KEY_ID and GPG_PASSPHRASE are set
4647
-groupid: biz.aQute.bnd
4748
-pom: version=${if;${def;-snapshot};${versionmask;===;${@version}}-${def;-snapshot};${versionmask;===s;${@version}}}
48-
-maven-release: pom;path=JAR,javadoc;-classpath="${project.buildpath}"
49+
-maven-release: \
50+
pom;path=JAR,\
51+
javadoc;-classpath="${project.buildpath}",\
52+
${if;${env;GPG_KEY_ID};${if;${env;GPG_PASSPHRASE};'sign;keyname=${env;GPG_KEY_ID};passphrase=${env;GPG_PASSPHRASE}'}}
4953

5054
gpg: gpg --homedir ${def;USERHOME;~}/.gnupg --pinentry-mode loopback
5155

@@ -89,7 +93,22 @@ Bundle-Developers: \
8993
organization="Liferay Inc."; \
9094
organizationUrl="https://www.liferay.com"; \
9195
roles="developer"; \
92-
timezone="America/New_York"
96+
timezone="America/New_York",\
97+
chrisrueger; \
98+
name="Christoph Rueger"; \
99+
email="chrisrueger@gmail.com"; \
100+
organization="Synesty GmbH"; \
101+
organizationUrl="https://synesty.com/"; \
102+
roles="developer"; \
103+
timezone="Europe/Berlin",\
104+
peterkir; \
105+
name="Peter Kirschner"; \
106+
email="peter@klib.io"; \
107+
url="https://peterkir.github.io"; \
108+
organization="Kirschners GmbH"; \
109+
organizationUrl="https://peterkir.github.io/"; \
110+
roles="developer"; \
111+
timezone="Europe/Berlin"
93112

94113
-make: (*).(jar);type=bnd; recipe="bnd/$1.bnd"
95114
-reproducible: true

cnf/ext/repositories.bnd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ sonatype_bearer: '${env;SONATYPE_BEARER}'
4848
snapshotUrl = ${sonatype_snapshots}; \
4949
index = ${.}/release.maven; \
5050
name = "Sonatype"; \
51-
sonatypeMode = manual
52-
51+
sonatypeMode = manual;\
52+
noupdateOnRelease=true
5353

5454
# Eclipse Release repository
5555
#-plugin.1.Eclipse:\

0 commit comments

Comments
 (0)