61 import jdk.tools.jlink.plugin.ResourcePool;
62 import jdk.tools.jlink.plugin.ResourcePoolEntry;
63 import jdk.tools.jlink.plugin.ResourcePoolModule;
64 import jdk.tools.jlink.plugin.PluginException;
65
66 /**
67 *
68 * Default Image Builder. This builder creates the default runtime image layout.
69 */
70 public final class DefaultImageBuilder implements ImageBuilder {
71
72 /**
73 * The default java executable Image.
74 */
75 static final class DefaultExecutableImage implements ExecutableImage {
76
77 private final Path home;
78 private final List<String> args;
79 private final Set<String> modules;
80
81 public DefaultExecutableImage(Path home, Set<String> modules) {
82 this(home, modules, createArgs(home));
83 }
84
85 private DefaultExecutableImage(Path home, Set<String> modules,
86 List<String> args) {
87 Objects.requireNonNull(home);
88 Objects.requireNonNull(args);
89 if (!Files.exists(home)) {
90 throw new IllegalArgumentException("Invalid image home");
91 }
92 this.home = home;
93 this.modules = Collections.unmodifiableSet(modules);
94 this.args = Collections.unmodifiableList(args);
95 }
96
97 private static List<String> createArgs(Path home) {
98 Objects.requireNonNull(home);
99 List<String> javaArgs = new ArrayList<>();
100 javaArgs.add(home.resolve("bin").
101 resolve(getJavaProcessName()).toString());
102 return javaArgs;
103 }
104
105 @Override
106 public Path getHome() {
107 return home;
108 }
109
110 @Override
111 public Set<String> getModules() {
112 return modules;
113 }
114
115 @Override
116 public List<String> getExecutionArgs() {
117 return args;
118 }
119
120 @Override
121 public void storeLaunchArgs(List<String> args) {
122 try {
123 patchScripts(this, args);
124 } catch (IOException ex) {
125 throw new UncheckedIOException(ex);
126 }
127 }
128 }
129
130 private final Path root;
131 private final Path mdir;
132 private final Set<String> modules = new HashSet<>();
133
134 /**
135 * Default image builder constructor.
136 *
137 * @param root The image root directory.
138 * @throws IOException
139 */
140 public DefaultImageBuilder(Path root) throws IOException {
141 Objects.requireNonNull(root);
142
143 this.root = root;
144 this.mdir = root.resolve("lib");
145 Files.createDirectories(mdir);
146 }
147
148 private void storeFiles(Set<String> modules, Properties release) throws IOException {
149 if (release != null) {
150 addModules(release, modules);
151 File r = new File(root.toFile(), "release");
152 try (FileOutputStream fo = new FileOutputStream(r)) {
154 }
155 }
156 }
157
158 private void addModules(Properties props, Set<String> modules) throws IOException {
159 StringBuilder builder = new StringBuilder();
160 int i = 0;
161 for (String m : modules) {
162 builder.append(m);
163 if (i < modules.size() - 1) {
164 builder.append(",");
165 }
166 i++;
167 }
168 props.setProperty("MODULES", builder.toString());
169 }
170
171 @Override
172 public void storeFiles(ResourcePool files) {
173 try {
174 files.entries().forEach(f -> {
175 if (!f.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) {
176 try {
177 accept(f);
178 } catch (IOException ioExp) {
179 throw new UncheckedIOException(ioExp);
180 }
181 }
182 });
183 files.moduleView().modules().forEach(m -> {
184 // Only add modules that contain packages
185 if (!m.packages().isEmpty()) {
186 modules.add(m.name());
187 }
188 });
189 storeFiles(modules, releaseProperties(files));
190
191 if (Files.getFileStore(root).supportsFileAttributeView(PosixFileAttributeView.class)) {
192 // launchers in the bin directory need execute permission
193 Path bin = root.resolve("bin");
194 if (Files.isDirectory(bin)) {
195 Files.list(bin)
196 .filter(f -> !f.toString().endsWith(".diz"))
197 .filter(f -> Files.isRegularFile(f))
198 .forEach(this::setExecutable);
199 }
200
201 // jspawnhelper is in lib or lib/<arch>
202 Path lib = root.resolve("lib");
203 if (Files.isDirectory(lib)) {
204 Files.find(lib, 2, (path, attrs) -> {
205 return path.getFileName().toString().equals("jspawnhelper")
206 || path.getFileName().toString().equals("jexec");
207 }).forEach(this::setExecutable);
208 }
209 }
210
211 prepareApplicationFiles(files, modules);
212 } catch (IOException ex) {
213 throw new PluginException(ex);
214 }
215 }
216
217 private Properties releaseProperties(ResourcePool pool) throws IOException {
218 Properties props = new Properties();
219 Optional<ResourcePoolModule> javaBase = pool.moduleView().findModule("java.base");
220 javaBase.ifPresent(mod -> {
221 // fill release information available from transformed "java.base" module!
222 ModuleDescriptor desc = mod.descriptor();
223 desc.osName().ifPresent(s -> props.setProperty("OS_NAME", s));
224 desc.osVersion().ifPresent(s -> props.setProperty("OS_VERSION", s));
225 desc.osArch().ifPresent(s -> props.setProperty("OS_ARCH", s));
226 props.setProperty("JAVA_VERSION", System.getProperty("java.version"));
227 });
228
229 Optional<ResourcePoolEntry> release = pool.findEntry("/java.base/release");
230 if (release.isPresent()) {
231 try (InputStream is = release.get().content()) {
232 props.load(is);
233 }
234 }
235
236 return props;
237 }
238
239 /**
240 * Generates launcher scripts.
241 *
242 * @param imageContent The image content.
243 * @param modules The set of modules that the runtime image contains.
244 * @throws IOException
245 */
246 protected void prepareApplicationFiles(ResourcePool imageContent, Set<String> modules) throws IOException {
247 // generate launch scripts for the modules with a main class
248 for (String module : modules) {
356 }
357
358 private Path destFile(String dir, String filename) {
359 return root.resolve(dir).resolve(filename);
360 }
361
362 private void writeEntry(InputStream in, Path dstFile) throws IOException {
363 Objects.requireNonNull(in);
364 Objects.requireNonNull(dstFile);
365 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
366 Files.copy(in, dstFile);
367 }
368
369 private void writeSymEntry(Path dstFile, Path target) throws IOException {
370 Objects.requireNonNull(dstFile);
371 Objects.requireNonNull(target);
372 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
373 Files.createLink(dstFile, target);
374 }
375
376 private static String nativeDir(String filename) {
377 if (isWindows()) {
378 if (filename.endsWith(".dll") || filename.endsWith(".diz")
379 || filename.endsWith(".pdb") || filename.endsWith(".map")) {
380 return "bin";
381 } else {
382 return "lib";
383 }
384 } else {
385 return "lib";
386 }
387 }
388
389 private static boolean isWindows() {
390 return System.getProperty("os.name").startsWith("Windows");
391 }
392
393 /**
394 * chmod ugo+x file
395 */
396 private void setExecutable(Path file) {
397 try {
398 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
399 perms.add(PosixFilePermission.OWNER_EXECUTE);
400 perms.add(PosixFilePermission.GROUP_EXECUTE);
401 perms.add(PosixFilePermission.OTHERS_EXECUTE);
402 Files.setPosixFilePermissions(file, perms);
403 } catch (IOException ioe) {
404 throw new UncheckedIOException(ioe);
405 }
406 }
407
408 private static void createUtf8File(File file, String content) throws IOException {
409 try (OutputStream fout = new FileOutputStream(file);
410 Writer output = new OutputStreamWriter(fout, "UTF-8")) {
435 append(pattern);
436 for (String s : args) {
437 builder.append(s).append(" ");
438 }
439 String remain = str.substring(index + pattern.length());
440 builder.append(remain);
441 str = builder.toString();
442 try (BufferedWriter writer = Files.newBufferedWriter(p,
443 StandardCharsets.ISO_8859_1,
444 StandardOpenOption.WRITE)) {
445 writer.write(str);
446 }
447 }
448 } catch (IOException ex) {
449 throw new RuntimeException(ex);
450 }
451 });
452 }
453 }
454
455 private static String getJavaProcessName() {
456 return isWindows() ? "java.exe" : "java";
457 }
458
459 public static ExecutableImage getExecutableImage(Path root) {
460 if (Files.exists(root.resolve("bin").resolve(getJavaProcessName()))) {
461 return new DefaultExecutableImage(root, retrieveModules(root));
462 }
463 return null;
464 }
465
466 private static Set<String> retrieveModules(Path root) {
467 Path releaseFile = root.resolve("release");
468 Set<String> modules = new HashSet<>();
469 if (Files.exists(releaseFile)) {
470 Properties release = new Properties();
471 try (FileInputStream fi = new FileInputStream(releaseFile.toFile())) {
472 release.load(fi);
473 } catch (IOException ex) {
474 System.err.println("Can't read release file " + ex);
475 }
476 String mods = release.getProperty("MODULES");
477 if (mods != null) {
478 String[] arr = mods.split(",");
479 for (String m : arr) {
480 modules.add(m.trim());
|
61 import jdk.tools.jlink.plugin.ResourcePool;
62 import jdk.tools.jlink.plugin.ResourcePoolEntry;
63 import jdk.tools.jlink.plugin.ResourcePoolModule;
64 import jdk.tools.jlink.plugin.PluginException;
65
66 /**
67 *
68 * Default Image Builder. This builder creates the default runtime image layout.
69 */
70 public final class DefaultImageBuilder implements ImageBuilder {
71
72 /**
73 * The default java executable Image.
74 */
75 static final class DefaultExecutableImage implements ExecutableImage {
76
77 private final Path home;
78 private final List<String> args;
79 private final Set<String> modules;
80
81 DefaultExecutableImage(Path home, Set<String> modules) {
82 Objects.requireNonNull(home);
83 if (!Files.exists(home)) {
84 throw new IllegalArgumentException("Invalid image home");
85 }
86 this.home = home;
87 this.modules = Collections.unmodifiableSet(modules);
88 this.args = createArgs(home);
89 }
90
91 private static List<String> createArgs(Path home) {
92 Objects.requireNonNull(home);
93 List<String> javaArgs = new ArrayList<>();
94 Path binDir = home.resolve("bin");
95 String java = Files.exists(binDir.resolve("java"))? "java" : "java.exe";
96 javaArgs.add(binDir.resolve(java).toString());
97 return Collections.unmodifiableList(javaArgs);
98 }
99
100 @Override
101 public Path getHome() {
102 return home;
103 }
104
105 @Override
106 public Set<String> getModules() {
107 return modules;
108 }
109
110 @Override
111 public List<String> getExecutionArgs() {
112 return args;
113 }
114
115 @Override
116 public void storeLaunchArgs(List<String> args) {
117 try {
118 patchScripts(this, args);
119 } catch (IOException ex) {
120 throw new UncheckedIOException(ex);
121 }
122 }
123 }
124
125 private final Path root;
126 private final Path mdir;
127 private final Set<String> modules = new HashSet<>();
128 private String targetOsName;
129
130 /**
131 * Default image builder constructor.
132 *
133 * @param root The image root directory.
134 * @throws IOException
135 */
136 public DefaultImageBuilder(Path root) throws IOException {
137 Objects.requireNonNull(root);
138
139 this.root = root;
140 this.mdir = root.resolve("lib");
141 Files.createDirectories(mdir);
142 }
143
144 private void storeFiles(Set<String> modules, Properties release) throws IOException {
145 if (release != null) {
146 addModules(release, modules);
147 File r = new File(root.toFile(), "release");
148 try (FileOutputStream fo = new FileOutputStream(r)) {
150 }
151 }
152 }
153
154 private void addModules(Properties props, Set<String> modules) throws IOException {
155 StringBuilder builder = new StringBuilder();
156 int i = 0;
157 for (String m : modules) {
158 builder.append(m);
159 if (i < modules.size() - 1) {
160 builder.append(",");
161 }
162 i++;
163 }
164 props.setProperty("MODULES", builder.toString());
165 }
166
167 @Override
168 public void storeFiles(ResourcePool files) {
169 try {
170 // populate release properties up-front. targetOsName
171 // field is assigned from there and used elsewhere.
172 Properties release = releaseProperties(files);
173
174 files.entries().forEach(f -> {
175 if (!f.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) {
176 try {
177 accept(f);
178 } catch (IOException ioExp) {
179 throw new UncheckedIOException(ioExp);
180 }
181 }
182 });
183 files.moduleView().modules().forEach(m -> {
184 // Only add modules that contain packages
185 if (!m.packages().isEmpty()) {
186 modules.add(m.name());
187 }
188 });
189
190 storeFiles(modules, release);
191
192 if (Files.getFileStore(root).supportsFileAttributeView(PosixFileAttributeView.class)) {
193 // launchers in the bin directory need execute permission
194 Path bin = root.resolve("bin");
195 if (Files.isDirectory(bin)) {
196 Files.list(bin)
197 .filter(f -> !f.toString().endsWith(".diz"))
198 .filter(f -> Files.isRegularFile(f))
199 .forEach(this::setExecutable);
200 }
201
202 // jspawnhelper is in lib or lib/<arch>
203 Path lib = root.resolve("lib");
204 if (Files.isDirectory(lib)) {
205 Files.find(lib, 2, (path, attrs) -> {
206 return path.getFileName().toString().equals("jspawnhelper")
207 || path.getFileName().toString().equals("jexec");
208 }).forEach(this::setExecutable);
209 }
210 }
211
212 prepareApplicationFiles(files, modules);
213 } catch (IOException ex) {
214 throw new PluginException(ex);
215 }
216 }
217
218 private Properties releaseProperties(ResourcePool pool) throws IOException {
219 Properties props = new Properties();
220 Optional<ResourcePoolModule> javaBase = pool.moduleView().findModule("java.base");
221 javaBase.ifPresent(mod -> {
222 // fill release information available from transformed "java.base" module!
223 ModuleDescriptor desc = mod.descriptor();
224 desc.osName().ifPresent(s -> props.setProperty("OS_NAME", s));
225 desc.osVersion().ifPresent(s -> props.setProperty("OS_VERSION", s));
226 desc.osArch().ifPresent(s -> props.setProperty("OS_ARCH", s));
227 props.setProperty("JAVA_VERSION", System.getProperty("java.version"));
228 });
229
230 this.targetOsName = props.getProperty("OS_NAME");
231 if (this.targetOsName == null) {
232 throw new RuntimeException("can't determine target OS from java.base descriptor");
233 }
234
235 Optional<ResourcePoolEntry> release = pool.findEntry("/java.base/release");
236 if (release.isPresent()) {
237 try (InputStream is = release.get().content()) {
238 props.load(is);
239 }
240 }
241
242 return props;
243 }
244
245 /**
246 * Generates launcher scripts.
247 *
248 * @param imageContent The image content.
249 * @param modules The set of modules that the runtime image contains.
250 * @throws IOException
251 */
252 protected void prepareApplicationFiles(ResourcePool imageContent, Set<String> modules) throws IOException {
253 // generate launch scripts for the modules with a main class
254 for (String module : modules) {
362 }
363
364 private Path destFile(String dir, String filename) {
365 return root.resolve(dir).resolve(filename);
366 }
367
368 private void writeEntry(InputStream in, Path dstFile) throws IOException {
369 Objects.requireNonNull(in);
370 Objects.requireNonNull(dstFile);
371 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
372 Files.copy(in, dstFile);
373 }
374
375 private void writeSymEntry(Path dstFile, Path target) throws IOException {
376 Objects.requireNonNull(dstFile);
377 Objects.requireNonNull(target);
378 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
379 Files.createLink(dstFile, target);
380 }
381
382 private String nativeDir(String filename) {
383 if (isWindows()) {
384 if (filename.endsWith(".dll") || filename.endsWith(".diz")
385 || filename.endsWith(".pdb") || filename.endsWith(".map")) {
386 return "bin";
387 } else {
388 return "lib";
389 }
390 } else {
391 return "lib";
392 }
393 }
394
395 private boolean isWindows() {
396 return targetOsName.startsWith("Windows");
397 }
398
399 /**
400 * chmod ugo+x file
401 */
402 private void setExecutable(Path file) {
403 try {
404 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
405 perms.add(PosixFilePermission.OWNER_EXECUTE);
406 perms.add(PosixFilePermission.GROUP_EXECUTE);
407 perms.add(PosixFilePermission.OTHERS_EXECUTE);
408 Files.setPosixFilePermissions(file, perms);
409 } catch (IOException ioe) {
410 throw new UncheckedIOException(ioe);
411 }
412 }
413
414 private static void createUtf8File(File file, String content) throws IOException {
415 try (OutputStream fout = new FileOutputStream(file);
416 Writer output = new OutputStreamWriter(fout, "UTF-8")) {
441 append(pattern);
442 for (String s : args) {
443 builder.append(s).append(" ");
444 }
445 String remain = str.substring(index + pattern.length());
446 builder.append(remain);
447 str = builder.toString();
448 try (BufferedWriter writer = Files.newBufferedWriter(p,
449 StandardCharsets.ISO_8859_1,
450 StandardOpenOption.WRITE)) {
451 writer.write(str);
452 }
453 }
454 } catch (IOException ex) {
455 throw new RuntimeException(ex);
456 }
457 });
458 }
459 }
460
461 public static ExecutableImage getExecutableImage(Path root) {
462 Path binDir = root.resolve("bin");
463 if (Files.exists(binDir.resolve("java")) ||
464 Files.exists(binDir.resolve("java.exe"))) {
465 return new DefaultExecutableImage(root, retrieveModules(root));
466 }
467 return null;
468 }
469
470 private static Set<String> retrieveModules(Path root) {
471 Path releaseFile = root.resolve("release");
472 Set<String> modules = new HashSet<>();
473 if (Files.exists(releaseFile)) {
474 Properties release = new Properties();
475 try (FileInputStream fi = new FileInputStream(releaseFile.toFile())) {
476 release.load(fi);
477 } catch (IOException ex) {
478 System.err.println("Can't read release file " + ex);
479 }
480 String mods = release.getProperty("MODULES");
481 if (mods != null) {
482 String[] arr = mods.split(",");
483 for (String m : arr) {
484 modules.add(m.trim());
|