diff --git a/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/MavenBundleWrapper.java b/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/MavenBundleWrapper.java index 89375aefcc..74b469278c 100644 --- a/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/MavenBundleWrapper.java +++ b/tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/MavenBundleWrapper.java @@ -13,6 +13,8 @@ package org.eclipse.m2e.pde.target.shared; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -27,10 +29,16 @@ import java.util.Set; import java.util.function.Function; import java.util.jar.Attributes; +import java.util.jar.Attributes.Name; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.zip.ZipEntry; import org.apache.commons.codec.digest.DigestUtils; import org.apache.maven.model.Model; @@ -70,6 +78,9 @@ * */ public class MavenBundleWrapper { + + public static final String ECLIPSE_SOURCE_BUNDLE_HEADER = "Eclipse-SourceBundle"; + private MavenBundleWrapper() { } @@ -318,4 +329,49 @@ public static boolean isOutdated(Path cacheFile, Path sourceFile) throws IOExcep } return true; } + + private static boolean isExcludedFromWrapping(String name) { + return name.equals(JarFile.MANIFEST_NAME) || name.startsWith("META-INF/SIG-") || name.startsWith("META-INF/") + && (name.endsWith(".SF") || name.endsWith(".RSA") || name.endsWith(".DSA")); + } + + public static void addSourceBundleMetadata(Manifest manifest, String symbolicName, String version) { + + Attributes attr = manifest.getMainAttributes(); + if (attr.isEmpty()) { + attr.put(Name.MANIFEST_VERSION, "1.0"); + } + attr.putValue(ECLIPSE_SOURCE_BUNDLE_HEADER, symbolicName + ";version=\"" + version + "\";roots:=\".\""); + attr.putValue(Constants.BUNDLE_MANIFESTVERSION, "2"); + attr.putValue(Constants.BUNDLE_NAME, "Source Bundle for " + symbolicName + ":" + version); + attr.putValue(Constants.BUNDLE_SYMBOLICNAME, getSourceBundleName(symbolicName)); + attr.putValue(Constants.BUNDLE_VERSION, version); + } + + public static String getSourceBundleName(String symbolicName) { + return symbolicName + ".source"; + } + + public static void transferJarEntries(File source, Manifest manifest, File target) throws IOException { + Map manifestEntries = manifest.getEntries(); + if (manifestEntries != null) { + // need to clear out signature infos + manifestEntries.clear(); + } + try (var output = new JarOutputStream(new FileOutputStream(target), manifest); + var input = new JarInputStream(new FileInputStream(source));) { + for (JarEntry entry; (entry = input.getNextJarEntry()) != null;) { + if (MavenBundleWrapper.isExcludedFromWrapping(entry.getName())) { + // Exclude manifest and signatures + continue; + } + output.putNextEntry(new ZipEntry(entry.getName())); + input.transferTo(output); + } + } + } + + public static boolean isValidSourceManifest(Manifest manifest) { + return manifest != null && manifest.getMainAttributes().getValue(ECLIPSE_SOURCE_BUNDLE_HEADER) != null; + } } diff --git a/tycho-core/src/main/java/org/eclipse/tycho/core/resolver/MavenTargetDefinitionContent.java b/tycho-core/src/main/java/org/eclipse/tycho/core/resolver/MavenTargetDefinitionContent.java index 011eab3393..38b58c3d80 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/core/resolver/MavenTargetDefinitionContent.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/core/resolver/MavenTargetDefinitionContent.java @@ -12,14 +12,10 @@ *******************************************************************************/ package org.eclipse.tycho.core.resolver; -import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; -import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -29,14 +25,9 @@ import java.util.Properties; import java.util.function.Function; import java.util.function.Predicate; -import java.util.jar.Attributes; -import java.util.jar.Attributes.Name; -import java.util.jar.JarEntry; import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.stream.Collectors; -import java.util.zip.ZipEntry; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; @@ -94,13 +85,11 @@ import org.eclipse.tycho.targetplatform.TargetDefinitionContent; import org.eclipse.tycho.targetplatform.TargetDefinitionResolutionException; import org.osgi.framework.BundleException; -import org.osgi.framework.Constants; import org.w3c.dom.Element; import org.xml.sax.SAXException; public class MavenTargetDefinitionContent implements TargetDefinitionContent { private static final String POM_PACKAGING_TYPE = "pom"; - public static final String ECLIPSE_SOURCE_BUNDLE_HEADER = "Eclipse-SourceBundle"; private final Map repositoryContent = new HashMap<>(); private SupplierMetadataRepository metadataRepository; private FileArtifactRepository artifactRepository; @@ -293,7 +282,7 @@ public MavenTargetDefinitionContent(MavenGAVLocation location, MavenDependencies manifest = Objects.requireNonNullElseGet(jar.getManifest(), Manifest::new); } IInstallableUnit unit; - if (isValidSourceManifest(manifest)) { + if (MavenBundleWrapper.isValidSourceManifest(manifest)) { unit = publish(BundlesAction.createBundleDescription(sourceFile), sourceFile, sourceArtifact); } else { @@ -365,32 +354,8 @@ private IInstallableUnit generateSourceBundle(String symbolicName, String bundle File tempFile = File.createTempFile("tycho_wrapped_source", ".jar"); tempFile.deleteOnExit(); - Attributes attr = manifest.getMainAttributes(); - if (attr.isEmpty()) { - attr.put(Name.MANIFEST_VERSION, "1.0"); - } - attr.putValue(ECLIPSE_SOURCE_BUNDLE_HEADER, symbolicName + ";version=\"" + bundleVersion + "\";roots:=\".\""); - attr.putValue(Constants.BUNDLE_MANIFESTVERSION, "2"); - attr.putValue(Constants.BUNDLE_NAME, "Source Bundle for " + symbolicName + ":" + bundleVersion); - attr.putValue(Constants.BUNDLE_SYMBOLICNAME, symbolicName + ".source"); - attr.putValue(Constants.BUNDLE_VERSION, bundleVersion); - try (JarOutputStream stream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(tempFile)), - manifest)) { - try (JarFile jar = new JarFile(sourceFile)) { - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry jarEntry = entries.nextElement(); - if (jarEntry.getName().equals(JarFile.MANIFEST_NAME)) { - continue; - } - try (InputStream is = jar.getInputStream(jarEntry)) { - stream.putNextEntry(new ZipEntry(jarEntry.getName())); - is.transferTo(stream); - stream.closeEntry(); - } - } - } - } + MavenBundleWrapper.addSourceBundleMetadata(manifest, symbolicName, bundleVersion); + MavenBundleWrapper.transferJarEntries(sourceFile, manifest, tempFile); return publish(BundlesAction.createBundleDescription(tempFile), tempFile, sourceArtifact); } @@ -441,11 +406,4 @@ private static String getKey(Artifact artifact) { return key; } - private static boolean isValidSourceManifest(Manifest manifest) { - if (manifest != null) { - return manifest.getMainAttributes().getValue(ECLIPSE_SOURCE_BUNDLE_HEADER) != null; - } - return false; - } - } diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java index 5df3b892e2..028b8a5866 100644 --- a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java +++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/AbstractMavenTargetTest.java @@ -32,7 +32,9 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.jar.Attributes; +import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.jar.JarInputStream; import java.util.stream.Collectors; import org.eclipse.core.runtime.IStatus; @@ -176,6 +178,17 @@ static void assertTargetBundles(ITargetLocation target, List exp tb -> tb.getBundleInfo().getSymbolicName() + ":" + tb.getBundleInfo().getVersion()); } + static void assertValidSignature(TargetBundle targetBundle) throws IOException { + try (JarInputStream stream = new JarInputStream( + targetBundle.getBundleInfo().getLocation().toURL().openStream())) { + for (JarEntry entry = stream.getNextJarEntry(); entry != null; entry = stream.getNextJarEntry()) { + for (byte[] drain = new byte[4096]; stream.read(drain, 0, drain.length) != -1;) { + // nothing we just want to trigger the signature verification + } + } + } + } + // --- assertion utilities for Features in a target --- static ExpectedFeature originalFeature(String id, String version, String groupArtifact, diff --git a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/OSGiMetadataGenerationTest.java b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/OSGiMetadataGenerationTest.java index 81332cb770..aa12947b60 100644 --- a/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/OSGiMetadataGenerationTest.java +++ b/tycho-core/src/test/java/org/eclipse/m2e/pde/target/tests/OSGiMetadataGenerationTest.java @@ -72,6 +72,39 @@ public void testBadSymbolicName() throws Exception { assertStatusOk(getTargetStatus(target)); } + @Test + public void testSourceWithSignature() throws Exception { + ITargetLocation target = resolveMavenTarget( + """ + + + + org.eclipse.lemminx + org.eclipse.lemminx + 0.29.0 + jar + + + + + lemminx-releases + https://repo.eclipse.org/content/repositories/lemminx-releases/ + + + + """); + assertStatusOk(getTargetStatus(target)); + TargetBundle[] allBundles = target.getBundles(); + boolean sourcesFound = false; + for (TargetBundle targetBundle : allBundles) { + if (targetBundle.isSourceBundle()) { + sourcesFound = true; + assertValidSignature(targetBundle); + } + } + assertTrue("No source bundle generated!", sourcesFound); + } + @Test public void testBadDependencyDirect() throws Exception { ITargetLocation target = resolveMavenTarget("""