Skip to content

Commit c9a335b

Browse files
Copiloteclipse-tycho-bot
authored andcommitted
Cache source bundle generation in local Maven repository (#5813)
Fixes #5812 (cherry picked from commit 9fff1d6)
1 parent 208b0a9 commit c9a335b

File tree

3 files changed

+218
-5
lines changed

3 files changed

+218
-5
lines changed

tycho-core/src/main/java/org/eclipse/m2e/pde/target/shared/MavenBundleWrapper.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,4 +397,50 @@ public static void transferJarEntries(File source, Manifest manifest, File targe
397397
public static boolean isValidSourceManifest(Manifest manifest) {
398398
return manifest != null && manifest.getMainAttributes().getValue(ECLIPSE_SOURCE_BUNDLE_HEADER) != null;
399399
}
400+
401+
/**
402+
* Creates or returns a cached Eclipse source bundle from a source JAR file. The resulting
403+
* bundle will have proper Eclipse-SourceBundle manifest headers. The cached file is stored
404+
* alongside the original source file with "-eclipse-source" suffix.
405+
*
406+
* @param sourceFile
407+
* the original source JAR file (e.g., xyz-source.jar)
408+
* @param manifest
409+
* the manifest to use (will be modified with source bundle metadata)
410+
* @param symbolicName
411+
* the bundle symbolic name of the host bundle
412+
* @param bundleVersion
413+
* the version of the host bundle
414+
* @return the cached eclipse source bundle file
415+
* @throws IOException
416+
* if reading/writing files fails
417+
*/
418+
public static File getEclipseSourceBundle(File sourceFile, Manifest manifest, String symbolicName,
419+
String bundleVersion) throws IOException {
420+
// Create the cached file name: xyz-source.jar -> xyz-eclipse-source.jar
421+
String sourceName = sourceFile.getName();
422+
String eclipseSourceName;
423+
if (sourceName.endsWith(".jar")) {
424+
eclipseSourceName = sourceName.substring(0, sourceName.length() - 4) + "-eclipse-source.jar";
425+
} else {
426+
eclipseSourceName = sourceName + "-eclipse-source";
427+
}
428+
File eclipseSourceFile = new File(sourceFile.getParentFile(), eclipseSourceName);
429+
Path eclipseSourcePath = eclipseSourceFile.toPath();
430+
Path sourceFilePath = sourceFile.toPath();
431+
432+
// Check if cached file exists and is up-to-date
433+
if (!isOutdated(eclipseSourcePath, sourceFilePath)) {
434+
return eclipseSourceFile;
435+
}
436+
437+
// Generate new eclipse source bundle
438+
addSourceBundleMetadata(manifest, symbolicName, bundleVersion);
439+
transferJarEntries(sourceFile, manifest, eclipseSourceFile);
440+
441+
// Set the last modified time to match source file for cache validation
442+
Files.setLastModifiedTime(eclipseSourcePath, Files.getLastModifiedTime(sourceFilePath));
443+
444+
return eclipseSourceFile;
445+
}
400446
}

tycho-core/src/main/java/org/eclipse/tycho/core/resolver/MavenTargetDefinitionContent.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,9 @@ private static void stripRuntimeOSGiHeaders(Manifest manifest, String symbolicNa
432432
private IInstallableUnit generateSourceBundle(String symbolicName, String bundleVersion, Manifest manifest,
433433
File sourceFile, IArtifactFacade sourceArtifact, MavenLogger logger) throws IOException, BundleException {
434434
stripRuntimeOSGiHeaders(manifest, symbolicName, bundleVersion, logger);
435-
File tempFile = File.createTempFile("tycho_wrapped_source", ".jar");
436-
tempFile.deleteOnExit();
437-
MavenBundleWrapper.addSourceBundleMetadata(manifest, symbolicName, bundleVersion);
438-
MavenBundleWrapper.transferJarEntries(sourceFile, manifest, tempFile);
439-
return publish(BundlesAction.createBundleDescription(tempFile), tempFile, sourceArtifact);
435+
File eclipseSourceFile = MavenBundleWrapper.getEclipseSourceBundle(sourceFile, manifest, symbolicName,
436+
bundleVersion);
437+
return publish(BundlesAction.createBundleDescription(eclipseSourceFile), eclipseSourceFile, sourceArtifact);
440438

441439
}
442440

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Christoph Läubrich and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* https://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Christoph Läubrich - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.m2e.pde.target.shared;
14+
15+
import static org.junit.Assert.assertEquals;
16+
import static org.junit.Assert.assertFalse;
17+
import static org.junit.Assert.assertNotNull;
18+
import static org.junit.Assert.assertTrue;
19+
20+
import java.io.File;
21+
import java.io.FileOutputStream;
22+
import java.io.IOException;
23+
import java.nio.file.Files;
24+
import java.util.jar.Attributes;
25+
import java.util.jar.JarFile;
26+
import java.util.jar.JarOutputStream;
27+
import java.util.jar.Manifest;
28+
import java.util.zip.ZipEntry;
29+
30+
import org.junit.Rule;
31+
import org.junit.Test;
32+
import org.junit.rules.TemporaryFolder;
33+
34+
/**
35+
* Tests for {@link MavenBundleWrapper} functionality.
36+
*/
37+
public class MavenBundleWrapperTest {
38+
39+
@Rule
40+
public TemporaryFolder temporaryFolder = new TemporaryFolder();
41+
42+
@Test
43+
public void testGetEclipseSourceBundle_createsNewBundle() throws Exception {
44+
// Create a source JAR file
45+
File sourceFile = temporaryFolder.newFile("test-source.jar");
46+
createSourceJar(sourceFile);
47+
48+
Manifest manifest = new Manifest();
49+
File result = MavenBundleWrapper.getEclipseSourceBundle(sourceFile, manifest, "com.example.bundle", "1.0.0");
50+
51+
assertNotNull(result);
52+
assertTrue("Eclipse source bundle should exist", result.exists());
53+
assertEquals("test-source-eclipse-source.jar", result.getName());
54+
assertEquals(sourceFile.getParentFile(), result.getParentFile());
55+
56+
// Verify manifest headers
57+
try (JarFile jar = new JarFile(result)) {
58+
Manifest resultManifest = jar.getManifest();
59+
assertNotNull(resultManifest);
60+
Attributes attrs = resultManifest.getMainAttributes();
61+
assertEquals("com.example.bundle.source", attrs.getValue("Bundle-SymbolicName"));
62+
assertEquals("1.0.0", attrs.getValue("Bundle-Version"));
63+
assertTrue(attrs.getValue("Eclipse-SourceBundle").contains("com.example.bundle"));
64+
}
65+
}
66+
67+
@Test
68+
public void testGetEclipseSourceBundle_returnsCache() throws Exception {
69+
// Create a source JAR file
70+
File sourceFile = temporaryFolder.newFile("cached-source.jar");
71+
createSourceJar(sourceFile);
72+
73+
Manifest manifest1 = new Manifest();
74+
File result1 = MavenBundleWrapper.getEclipseSourceBundle(sourceFile, manifest1, "com.example.bundle", "1.0.0");
75+
76+
// Remember modification time
77+
long firstModTime = result1.lastModified();
78+
79+
// Wait a bit to ensure timestamp would be different if regenerated
80+
Thread.sleep(100);
81+
82+
// Call again - should return cached version
83+
Manifest manifest2 = new Manifest();
84+
File result2 = MavenBundleWrapper.getEclipseSourceBundle(sourceFile, manifest2, "com.example.bundle", "1.0.0");
85+
86+
assertEquals(result1.getAbsolutePath(), result2.getAbsolutePath());
87+
assertEquals("Cache should be reused, modification time should be unchanged", firstModTime,
88+
result2.lastModified());
89+
}
90+
91+
@Test
92+
public void testGetEclipseSourceBundle_regeneratesWhenSourceChanged() throws Exception {
93+
// Create a source JAR file
94+
File sourceFile = temporaryFolder.newFile("changing-source.jar");
95+
createSourceJar(sourceFile);
96+
97+
Manifest manifest1 = new Manifest();
98+
File result1 = MavenBundleWrapper.getEclipseSourceBundle(sourceFile, manifest1, "com.example.bundle", "1.0.0");
99+
long firstSize = result1.length();
100+
101+
// Modify the source file
102+
Thread.sleep(100); // Ensure different timestamp
103+
createSourceJar(sourceFile, "additional content");
104+
105+
// Update source file timestamp to be newer
106+
sourceFile.setLastModified(System.currentTimeMillis());
107+
108+
Manifest manifest2 = new Manifest();
109+
File result2 = MavenBundleWrapper.getEclipseSourceBundle(sourceFile, manifest2, "com.example.bundle", "1.0.0");
110+
111+
assertEquals(result1.getAbsolutePath(), result2.getAbsolutePath());
112+
// The file should be regenerated (different size due to different content)
113+
assertTrue("Regenerated bundle should have different size", result2.length() != firstSize);
114+
}
115+
116+
@Test
117+
public void testIsOutdated_returnsTrueForMissingCache() throws Exception {
118+
File sourceFile = temporaryFolder.newFile("source.jar");
119+
File cacheFile = new File(temporaryFolder.getRoot(), "cache.jar");
120+
121+
assertTrue("Missing cache file should be outdated",
122+
MavenBundleWrapper.isOutdated(cacheFile.toPath(), sourceFile.toPath()));
123+
}
124+
125+
@Test
126+
public void testIsOutdated_returnsFalseForUpToDateCache() throws Exception {
127+
File sourceFile = temporaryFolder.newFile("source.jar");
128+
File cacheFile = temporaryFolder.newFile("cache.jar");
129+
130+
// Set cache to be same timestamp as source
131+
cacheFile.setLastModified(sourceFile.lastModified());
132+
133+
assertFalse("Cache with same timestamp should not be outdated",
134+
MavenBundleWrapper.isOutdated(cacheFile.toPath(), sourceFile.toPath()));
135+
}
136+
137+
@Test
138+
public void testIsOutdated_returnsTrueWhenSourceNewer() throws Exception {
139+
File sourceFile = temporaryFolder.newFile("source.jar");
140+
File cacheFile = temporaryFolder.newFile("cache.jar");
141+
142+
// Set source to be newer than cache
143+
Thread.sleep(100);
144+
sourceFile.setLastModified(System.currentTimeMillis());
145+
cacheFile.setLastModified(sourceFile.lastModified() - 1000);
146+
147+
assertTrue("Cache should be outdated when source is newer",
148+
MavenBundleWrapper.isOutdated(cacheFile.toPath(), sourceFile.toPath()));
149+
}
150+
151+
private void createSourceJar(File file) throws IOException {
152+
createSourceJar(file, null);
153+
}
154+
155+
private void createSourceJar(File file, String additionalContent) throws IOException {
156+
Manifest manifest = new Manifest();
157+
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
158+
try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(file), manifest)) {
159+
// Add a dummy source file
160+
jos.putNextEntry(new ZipEntry("com/example/Test.java"));
161+
String content = "package com.example;\npublic class Test {}";
162+
if (additionalContent != null) {
163+
content += "\n// " + additionalContent;
164+
}
165+
jos.write(content.getBytes());
166+
jos.closeEntry();
167+
}
168+
}
169+
}

0 commit comments

Comments
 (0)