Skip to content

Commit bfa308d

Browse files
maxandersenclaude
andcommitted
feat: add support for Add-Reads and SplashScreen-Image manifest attributes
Extends manifest attribute support for jars beyond PR jbangdev#2439 to handle Add-Reads module dependencies and SplashScreen-Image extraction. Changes: - Add Project.ATTR_ADD_READS and Project.ATTR_SPLASH_SCREEN_IMAGE constants - Import Add-Reads and SplashScreen-Image attributes from jar manifests - Process Add-Reads: converts to --add-reads JVM flags (Java 9+) - Process SplashScreen-Image: extracts image and passes -splash flag - Fix KeyValue.of() to support values containing '=' signs Add-Reads implementation: - Parses space-separated module dependencies (e.g., "mod1=mod2 mod3=mod4") - Generates --add-reads flags for each dependency pair - Version-gated for Java 9+ with runAsModule check SplashScreen-Image implementation: - Extracts splash image from jar to <jarPath>.splash.<ext> in cache - Smart caching: only re-extracts if jar is newer than cached image - Fails gracefully with warnings (never breaks the build) - Adds -splash:<path> flag before other JVM options KeyValue parser fix: - Changed split("=") to split("=", 2) to handle values with '=' - Enables manifest directives like //MANIFEST Add-Reads=mod1=mod2 This builds on PR jbangdev#2439's copyManifestAttribute() and addAllUnnamedManifestOptions() patterns. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 8ab21ec commit bfa308d

File tree

4 files changed

+66
-1
lines changed

4 files changed

+66
-1
lines changed

src/main/java/dev/jbang/source/Project.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public class Project {
5757
public static final String ATTR_ADD_EXPORTS = "Add-Exports";
5858
public static final String ATTR_ADD_OPENS = "Add-Opens";
5959
public static final String ATTR_ENABLE_NATIVE_ACCESS = "Enable-Native-Access";
60+
public static final String ATTR_ADD_READS = "Add-Reads";
61+
public static final String ATTR_SPLASH_SCREEN_IMAGE = "SplashScreen-Image";
6062

6163
public enum BuildFile {
6264
jbang("build.jbang");

src/main/java/dev/jbang/source/ProjectBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,8 @@ private Project importJarMetadata(Project prj, boolean importModuleName) {
447447
copyManifestAttribute(attrs, prj, Project.ATTR_ADD_EXPORTS);
448448
copyManifestAttribute(attrs, prj, Project.ATTR_ADD_OPENS);
449449
copyManifestAttribute(attrs, prj, Project.ATTR_ENABLE_NATIVE_ACCESS);
450+
copyManifestAttribute(attrs, prj, Project.ATTR_ADD_READS);
451+
copyManifestAttribute(attrs, prj, Project.ATTR_SPLASH_SCREEN_IMAGE);
450452

451453
}
452454

src/main/java/dev/jbang/source/generators/JarCmdGenerator.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.net.ServerSocket;
66
import java.nio.file.Files;
77
import java.nio.file.Path;
8+
import java.nio.file.StandardCopyOption;
89
import java.util.ArrayList;
910
import java.util.Arrays;
1011
import java.util.Collection;
@@ -98,11 +99,32 @@ protected List<String> generateCommandLineList() throws IOException {
9899
Jdk jdk = project.projectJdk();
99100
String javacmd = JavaUtil.resolveInJavaHome("java", jdk);
100101

102+
// Handle splash screen - extract image from jar and pass -splash:path
103+
String splashImage = project.getManifestAttributes().get(Project.ATTR_SPLASH_SCREEN_IMAGE);
104+
if (splashImage != null) {
105+
Path jarPath = ctx.getJarFile();
106+
if (jarPath != null && Files.exists(jarPath)) {
107+
Path splashPath = extractSplashImage(jarPath, splashImage);
108+
if (splashPath != null) {
109+
optionalArgs.add("-splash:" + splashPath.toAbsolutePath());
110+
}
111+
}
112+
}
113+
101114
if (!runAsModule && jdk.majorVersion() >= 9) {
102115
addAllUnnamedManifestOptions(optionalArgs, project.getManifestAttributes().get(Project.ATTR_ADD_OPENS),
103116
"--add-opens=");
104117
addAllUnnamedManifestOptions(optionalArgs, project.getManifestAttributes().get(Project.ATTR_ADD_EXPORTS),
105118
"--add-exports=");
119+
120+
// Add-Reads: module1=module2 module3=module4 → --add-reads=module1=module2
121+
// --add-reads=module3=module4
122+
String addReads = project.getManifestAttributes().get(Project.ATTR_ADD_READS);
123+
if (addReads != null) {
124+
Arrays.stream(addReads.trim().split("\\s+"))
125+
.filter(val -> !val.isEmpty())
126+
.forEach(val -> optionalArgs.add("--add-reads=" + val));
127+
}
106128
}
107129

108130
if (!runAsModule && jdk.majorVersion() >= 22) {
@@ -330,4 +352,43 @@ private static void addPropertyFlags(Map<String, String> properties, String def,
330352
properties.forEach((k, e) -> result.add(def + k + "=" + e));
331353
}
332354

355+
/**
356+
* Extract splash screen image from jar to cache directory for use with -splash
357+
* flag.
358+
*
359+
* @param jarPath Path to the jar file
360+
* @param imagePath Path to image inside jar (from manifest SplashScreen-Image
361+
* attribute)
362+
* @return Path to extracted image, or null if extraction failed
363+
*/
364+
private static Path extractSplashImage(Path jarPath, String imagePath) {
365+
try (java.util.jar.JarFile jar = new java.util.jar.JarFile(jarPath.toFile())) {
366+
java.util.jar.JarEntry entry = jar.getJarEntry(imagePath);
367+
if (entry == null) {
368+
Util.warnMsg("Splash screen image not found in jar: " + imagePath);
369+
return null;
370+
}
371+
372+
// Extract to jar's directory with unique name
373+
String jarName = jarPath.getFileName().toString();
374+
int extIndex = imagePath.lastIndexOf('.');
375+
String imageExt = (extIndex > 0) ? imagePath.substring(extIndex) : "";
376+
Path targetPath = jarPath.getParent().resolve(jarName + ".splash" + imageExt);
377+
378+
// Extract if not cached or jar is newer
379+
if (!Files.exists(targetPath) ||
380+
Files.getLastModifiedTime(jarPath).compareTo(Files.getLastModifiedTime(targetPath)) > 0) {
381+
try (InputStream is = jar.getInputStream(entry)) {
382+
Files.copy(is, targetPath, StandardCopyOption.REPLACE_EXISTING);
383+
}
384+
Util.verboseMsg("Extracted splash screen to: " + targetPath);
385+
}
386+
387+
return targetPath;
388+
} catch (IOException e) {
389+
Util.warnMsg("Failed to extract splash screen: " + e.getMessage());
390+
return null;
391+
}
392+
}
393+
333394
}

src/main/java/dev/jbang/source/parser/KeyValue.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public String getValue() {
1818
}
1919

2020
public static KeyValue of(String line) {
21-
String[] split = line.split("=");
21+
String[] split = line.split("=", 2);
2222
String key;
2323
String value = null;
2424

0 commit comments

Comments
 (0)