Skip to content

Commit 3f7e097

Browse files
Copilotlaeubi
authored andcommitted
Add multi-release JAR support via classpath attributes and new demo
1 parent 657e896 commit 3f7e097

File tree

14 files changed

+468
-16
lines changed

14 files changed

+468
-16
lines changed
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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Manifest-Version: 1.0
2+
Bundle-ManifestVersion: 2
3+
Bundle-Name: MR-JAR Classpath TestCase
4+
Bundle-SymbolicName: mr.classpath
5+
Bundle-Version: 1.0.0.qualifier
6+
Export-Package: tycho.mr.example;version="0.0.1"
7+
Import-Package: org.apache.commons.io
8+
Multi-Release: true
9+
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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.compiler.mr.classpath</groupId>
6+
<artifactId>mr.classpath</artifactId>
7+
<version>1.0.0-SNAPSHOT</version>
8+
<packaging>eclipse-plugin</packaging>
9+
<properties>
10+
<!-- This values are overridden by the test execution but are usefull if running as a standalone project (e.g. debugging) -->
11+
<tycho-version>4.0.0-SNAPSHOT</tycho-version>
12+
<target-platform>https://download.eclipse.org/releases/2022-12/</target-platform>
13+
</properties>
14+
<repositories>
15+
<repository>
16+
<id>platform</id>
17+
<url>${target-platform}</url>
18+
<layout>p2</layout>
19+
</repository>
20+
</repositories>
21+
<build>
22+
<plugins>
23+
<plugin>
24+
<groupId>org.eclipse.tycho</groupId>
25+
<artifactId>tycho-maven-plugin</artifactId>
26+
<version>${tycho-version}</version>
27+
<extensions>true</extensions>
28+
</plugin>
29+
</plugins>
30+
</build>
31+
</project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package tycho.mr.example;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.net.URL;
6+
7+
import org.apache.commons.io.IOUtils;
8+
9+
public class HttpClient {
10+
11+
public byte[] fetchBytes(URL url) throws IOException {
12+
try (InputStream stream = url.openStream()) {
13+
// For Java < 9 we need to use an external library!
14+
return IOUtils.toByteArray(stream);
15+
}
16+
}
17+
}
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+
// For From Java 11 we can even you 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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package tycho.mr.example;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.net.URL;
6+
7+
import org.apache.commons.io.IOUtils;
8+
9+
public class HttpClient {
10+
11+
public byte[] fetchBytes(URL url) throws IOException {
12+
try (InputStream stream = url.openStream()) {
13+
// For Java >= 9 we can use the build-in
14+
return stream.readAllBytes();
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)