Skip to content

Commit f8f1e39

Browse files
Copilotlaeubi
andcommitted
Support multi-release source folders in tycho-source-plugin (#5631)
* Initial plan * Initial exploration of tycho-source plugin multi-release support Co-authored-by: laeubi <[email protected]> * Implement multi-release source folder support in tycho-source-plugin Co-authored-by: laeubi <[email protected]> * Add integration test for multi-release source bundle support Co-authored-by: laeubi <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: laeubi <[email protected]> (cherry picked from commit 7a6238f)
1 parent 172be08 commit f8f1e39

File tree

12 files changed

+352
-0
lines changed

12 files changed

+352
-0
lines changed

demo/multi-release-jar-classpath/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
<version>${tycho-version}</version>
2727
<extensions>true</extensions>
2828
</plugin>
29+
<plugin>
30+
<groupId>org.eclipse.tycho</groupId>
31+
<artifactId>tycho-source-plugin</artifactId>
32+
<version>${tycho-version}</version>
33+
<executions>
34+
<execution>
35+
<id>plugin-source</id>
36+
<goals>
37+
<goal>plugin-source</goal>
38+
</goals>
39+
</execution>
40+
</executions>
41+
</plugin>
2942
</plugins>
3043
</build>
3144
</project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<classpath>
3+
<classpathentry kind="src" path="src"/>
4+
<classpathentry kind="src" path="src9">
5+
<attributes>
6+
<attribute name="release" value="9"/>
7+
</attributes>
8+
</classpathentry>
9+
<classpathentry kind="src" path="src11">
10+
<attributes>
11+
<attribute name="release" value="11"/>
12+
</attributes>
13+
</classpathentry>
14+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
15+
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
16+
<classpathentry kind="output" path="bin"/>
17+
</classpath>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Manifest-Version: 1.0
2+
Bundle-ManifestVersion: 2
3+
Bundle-Name: Multi-Release Source Bundle Test
4+
Bundle-SymbolicName: bundle.multiRelease
5+
Bundle-Version: 1.0.0.qualifier
6+
Bundle-Vendor: Eclipse Tycho
7+
Export-Package: tycho.mr.example;version="0.0.1"
8+
Multi-Release: true
9+
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
10+
Automatic-Module-Name: bundle.multiRelease
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Building Multi-Release-Jar with Classpath Attributes
2+
3+
This sample shows how to build a [Multi-Release-Jar](https://openjdk.org/jeps/238) with Tycho using the JDT classpath attribute approach.
4+
5+
This approach requires the `Multi-Release: true` manifest header but simplifies the build by using Eclipse JDT's `release` classpath attribute to mark source folders for specific Java releases, without requiring special directory structures or supplemental manifests.
6+
7+
## Structure
8+
9+
- `src` - contains the main sources (Java 8)
10+
- `src9` - contains the source for release 9 (marked with `release="9"` in `.classpath`)
11+
- `src11` - contains the source for release 11 (marked with `release="11"` in `.classpath`)
12+
- `META-INF/MANIFEST.MF` - the manifest with `Multi-Release: true` header
13+
14+
Note: Source folders can be named anything (e.g., `src_java9`, `java9-src`), not just `src9` or `src11`.
15+
16+
## Classpath Configuration
17+
18+
The `.classpath` file contains entries like:
19+
20+
```xml
21+
<classpathentry kind="src" path="src9">
22+
<attributes>
23+
<attribute name="release" value="9"/>
24+
</attributes>
25+
</classpathentry>
26+
```
27+
28+
This tells Tycho to compile the sources in `src9` for Java 9 and place them in `META-INF/versions/9/` in the resulting JAR.
29+
30+
## Comparison with Manifest-First Approach
31+
32+
This approach is more flexible than the manifest-first approach because:
33+
- Source folders can be named flexibly (derived from `.classpath`, not fixed naming convention)
34+
- No supplemental manifests required in `META-INF/versions/N/OSGI-INF/`
35+
- Follows Eclipse JDT conventions more closely
36+
- Easier integration with Eclipse IDE
37+
38+
Both approaches require the `Multi-Release: true` manifest header.
39+
40+
See the `multi-release-jar` demo for the traditional manifest-first approach with fixed directory naming.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source.. = src/
2+
output.. = bin/
3+
bin.includes = META-INF/,\
4+
.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>tycho.its.source.multiRelease</groupId>
6+
<artifactId>bundle.multiRelease</artifactId>
7+
<version>1.0.0-SNAPSHOT</version>
8+
<packaging>eclipse-plugin</packaging>
9+
<properties>
10+
<tycho-version>${tycho-version}</tycho-version>
11+
</properties>
12+
<build>
13+
<plugins>
14+
<plugin>
15+
<groupId>org.eclipse.tycho</groupId>
16+
<artifactId>tycho-maven-plugin</artifactId>
17+
<version>${tycho-version}</version>
18+
<extensions>true</extensions>
19+
</plugin>
20+
<plugin>
21+
<groupId>org.eclipse.tycho</groupId>
22+
<artifactId>tycho-source-plugin</artifactId>
23+
<version>${tycho-version}</version>
24+
<executions>
25+
<execution>
26+
<id>plugin-source</id>
27+
<goals>
28+
<goal>plugin-source</goal>
29+
</goals>
30+
</execution>
31+
</executions>
32+
</plugin>
33+
</plugins>
34+
</build>
35+
</project>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package tycho.mr.example;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.net.URL;
6+
7+
public class HttpClient {
8+
9+
public byte[] fetchBytes(URL url) throws IOException {
10+
try (InputStream stream = url.openStream()) {
11+
// For Java < 9 we need to manually read the stream
12+
return readAllBytes(stream);
13+
}
14+
}
15+
16+
private byte[] readAllBytes(InputStream stream) throws IOException {
17+
byte[] buffer = new byte[8192];
18+
int bytesRead;
19+
java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();
20+
while ((bytesRead = stream.read(buffer)) != -1) {
21+
output.write(buffer, 0, bytesRead);
22+
}
23+
return output.toByteArray();
24+
}
25+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package tycho.mr.example;
2+
3+
import java.io.IOException;
4+
import java.net.URL;
5+
6+
public class Main {
7+
public static void main(String[] args) throws IOException {
8+
if (args.length == 0) {
9+
System.err.println("Please specify at laest one file to fetch!");
10+
System.exit(1);
11+
}
12+
HttpClient client = new HttpClient();
13+
for (String arg : args) {
14+
byte[] bytes = client.fetchBytes(new URL(arg));
15+
System.out.println("URL " + arg + " has provided " + bytes.length + " bytes!");
16+
}
17+
}
18+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package tycho.mr.example;
2+
3+
import java.io.FileNotFoundException;
4+
import java.io.IOException;
5+
import java.io.InterruptedIOException;
6+
import java.net.HttpURLConnection;
7+
import java.net.URISyntaxException;
8+
import java.net.URL;
9+
import java.net.http.HttpClient.Redirect;
10+
import java.net.http.HttpRequest;
11+
import java.net.http.HttpResponse;
12+
import java.net.http.HttpResponse.BodyHandlers;
13+
import java.time.Duration;
14+
15+
public class HttpClient {
16+
17+
public byte[] fetchBytes(URL url) throws IOException {
18+
// From Java 11 we can even use a true client with HTTP/2 support!
19+
java.net.http.HttpClient client = java.net.http.HttpClient.newBuilder()
20+
.followRedirects(Redirect.NORMAL)
21+
.connectTimeout(Duration.ofSeconds(20))
22+
.build();
23+
try {
24+
HttpRequest request = HttpRequest.newBuilder().uri(url.toURI()).build();
25+
HttpResponse<byte[]> response = client.send(request, BodyHandlers.ofByteArray());
26+
if (response.statusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
27+
throw new FileNotFoundException(url.toString());
28+
}
29+
return response.body();
30+
} catch (URISyntaxException e) {
31+
throw new IOException("invalid: " + url, e);
32+
} catch (InterruptedException e) {
33+
throw new InterruptedIOException();
34+
}
35+
}
36+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package tycho.mr.example;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.net.URL;
6+
7+
public class HttpClient {
8+
9+
public byte[] fetchBytes(URL url) throws IOException {
10+
try (InputStream stream = url.openStream()) {
11+
// For Java >= 9 we can use the built-in readAllBytes
12+
return stream.readAllBytes();
13+
}
14+
}
15+
}

0 commit comments

Comments
 (0)