41 import java.lang.module.ModuleDescriptor;
42 import java.nio.charset.StandardCharsets;
43 import java.nio.file.FileAlreadyExistsException;
44 import java.nio.file.Files;
45 import java.nio.file.Path;
46 import java.nio.file.Paths;
47 import java.nio.file.StandardOpenOption;
48 import java.nio.file.attribute.PosixFilePermission;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Objects;
55 import java.util.Optional;
56 import java.util.Properties;
57 import java.util.Set;
58 import static java.util.stream.Collectors.*;
59
60 import jdk.tools.jlink.internal.BasicImageWriter;
61 import jdk.tools.jlink.internal.plugins.FileCopierPlugin.SymImageFile;
62 import jdk.tools.jlink.internal.ExecutableImage;
63 import jdk.tools.jlink.plugin.ResourcePool;
64 import jdk.tools.jlink.plugin.ResourcePoolEntry;
65 import jdk.tools.jlink.plugin.ResourcePoolEntry.Type;
66 import jdk.tools.jlink.plugin.ResourcePoolModule;
67 import jdk.tools.jlink.plugin.PluginException;
68
69 /**
70 *
71 * Default Image Builder. This builder creates the default runtime image layout.
72 */
73 public final class DefaultImageBuilder implements ImageBuilder {
74 // Top-level directory names in a modular runtime image
75 public static final String BIN_DIRNAME = "bin";
76 public static final String CONF_DIRNAME = "conf";
77 public static final String INCLUDE_DIRNAME = "include";
78 public static final String LIB_DIRNAME = "lib";
79 public static final String LEGAL_DIRNAME = "legal";
80 public static final String MAN_DIRNAME = "man";
81
132
133 private final Path root;
134 private final Path mdir;
135 private final Set<String> modules = new HashSet<>();
136 private String targetOsName;
137
138 /**
139 * Default image builder constructor.
140 *
141 * @param root The image root directory.
142 * @throws IOException
143 */
144 public DefaultImageBuilder(Path root) throws IOException {
145 Objects.requireNonNull(root);
146
147 this.root = root;
148 this.mdir = root.resolve("lib");
149 Files.createDirectories(mdir);
150 }
151
152 private void storeRelease(ResourcePool pool) throws IOException {
153 Properties props = new Properties();
154 Optional<ResourcePoolEntry> release = pool.findEntry("/java.base/release");
155 if (release.isPresent()) {
156 try (InputStream is = release.get().content()) {
157 props.load(is);
158 }
159 }
160 File r = new File(root.toFile(), "release");
161 try (FileOutputStream fo = new FileOutputStream(r)) {
162 props.store(fo, null);
163 }
164 }
165
166 @Override
167 public void storeFiles(ResourcePool files) {
168 try {
169 // populate targetOsName field up-front because it's used elsewhere.
170 Optional<ResourcePoolModule> javaBase = files.moduleView().findModule("java.base");
171 javaBase.ifPresent(mod -> {
172 // fill release information available from transformed "java.base" module!
173 ModuleDescriptor desc = mod.descriptor();
174 desc.osName().ifPresent(s -> {
175 this.targetOsName = s;
176 });
177 });
178
179 if (this.targetOsName == null) {
180 throw new PluginException("TargetPlatform attribute is missing for java.base module");
181 }
182
183 // store 'release' file
184 storeRelease(files);
185
186 Path bin = root.resolve(BIN_DIRNAME);
187
188 // check any duplicated resource files
189 Map<Path, Set<String>> duplicates = new HashMap<>();
190 files.entries()
191 .filter(f -> f.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
192 .collect(groupingBy(this::entryToImagePath,
193 mapping(ResourcePoolEntry::moduleName, toSet())))
194 .entrySet()
195 .stream()
196 .filter(e -> e.getValue().size() > 1)
197 .forEach(e -> duplicates.put(e.getKey(), e.getValue()));
198
199 // write non-classes resource files to the image
200 files.entries()
201 .filter(f -> f.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
202 .forEach(f -> {
203 try {
204 accept(f);
205 } catch (FileAlreadyExistsException e) {
356 /**
357 * Returns the path of the given entry to be written in the image
358 */
359 private Path entryToImagePath(ResourcePoolEntry entry) {
360 switch (entry.type()) {
361 case NATIVE_LIB:
362 String filename = entryToFileName(entry);
363 return Paths.get(nativeDir(filename), filename);
364 case NATIVE_CMD:
365 return Paths.get(BIN_DIRNAME, entryToFileName(entry));
366 case CONFIG:
367 return Paths.get(CONF_DIRNAME, entryToFileName(entry));
368 case HEADER_FILE:
369 return Paths.get(INCLUDE_DIRNAME, entryToFileName(entry));
370 case MAN_PAGE:
371 return Paths.get(MAN_DIRNAME, entryToFileName(entry));
372 case LEGAL_NOTICE:
373 return Paths.get(LEGAL_DIRNAME, entryToFileName(entry));
374 case TOP:
375 return Paths.get(entryToFileName(entry));
376 case OTHER:
377 return Paths.get("other", entryToFileName(entry));
378 default:
379 throw new IllegalArgumentException("invalid type: " + entry);
380 }
381 }
382
383 private void accept(ResourcePoolEntry file) throws IOException {
384 if (file.linkedTarget() != null && file.type() != Type.LEGAL_NOTICE) {
385 throw new UnsupportedOperationException("symbolic link not implemented: " + file);
386 }
387
388 try (InputStream in = file.content()) {
389 switch (file.type()) {
390 case NATIVE_LIB:
391 Path dest = root.resolve(entryToImagePath(file));
392 writeEntry(in, dest);
393 break;
394 case NATIVE_CMD:
395 Path p = root.resolve(entryToImagePath(file));
396 writeEntry(in, p);
397 p.toFile().setExecutable(true);
398 break;
399 case CONFIG:
400 case HEADER_FILE:
401 case MAN_PAGE:
402 writeEntry(in, root.resolve(entryToImagePath(file)));
403 break;
404 case LEGAL_NOTICE:
405 Path source = entryToImagePath(file);
406 if (file.linkedTarget() == null) {
407 writeEntry(in, root.resolve(source));
408 } else {
409 Path target = entryToImagePath(file.linkedTarget());
410 Path relPath = source.getParent().relativize(target);
411 writeSymLinkEntry(root.resolve(source), relPath);
412 }
413 break;
414 case TOP:
415 break;
416 case OTHER:
417 String filename = entryToFileName(file);
418 if (file instanceof SymImageFile) {
419 SymImageFile sym = (SymImageFile) file;
420 Path target = root.resolve(sym.getTargetPath());
421 if (!Files.exists(target)) {
422 throw new IOException("Sym link target " + target
423 + " doesn't exist");
424 }
425 writeSymEntry(root.resolve(filename), target);
426 } else {
427 writeEntry(in, root.resolve(filename));
428 }
429 break;
430 default:
431 throw new InternalError("unexpected entry: " + file.path());
432 }
433 }
434 }
435
436 private void writeEntry(InputStream in, Path dstFile) throws IOException {
437 Objects.requireNonNull(in);
438 Objects.requireNonNull(dstFile);
439 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
440 Files.copy(in, dstFile);
441 }
442
443 private void writeSymEntry(Path dstFile, Path target) throws IOException {
444 Objects.requireNonNull(dstFile);
445 Objects.requireNonNull(target);
446 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
447 Files.createLink(dstFile, target);
|
41 import java.lang.module.ModuleDescriptor;
42 import java.nio.charset.StandardCharsets;
43 import java.nio.file.FileAlreadyExistsException;
44 import java.nio.file.Files;
45 import java.nio.file.Path;
46 import java.nio.file.Paths;
47 import java.nio.file.StandardOpenOption;
48 import java.nio.file.attribute.PosixFilePermission;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Objects;
55 import java.util.Optional;
56 import java.util.Properties;
57 import java.util.Set;
58 import static java.util.stream.Collectors.*;
59
60 import jdk.tools.jlink.internal.BasicImageWriter;
61 import jdk.tools.jlink.internal.ExecutableImage;
62 import jdk.tools.jlink.plugin.ResourcePool;
63 import jdk.tools.jlink.plugin.ResourcePoolEntry;
64 import jdk.tools.jlink.plugin.ResourcePoolEntry.Type;
65 import jdk.tools.jlink.plugin.ResourcePoolModule;
66 import jdk.tools.jlink.plugin.PluginException;
67
68 /**
69 *
70 * Default Image Builder. This builder creates the default runtime image layout.
71 */
72 public final class DefaultImageBuilder implements ImageBuilder {
73 // Top-level directory names in a modular runtime image
74 public static final String BIN_DIRNAME = "bin";
75 public static final String CONF_DIRNAME = "conf";
76 public static final String INCLUDE_DIRNAME = "include";
77 public static final String LIB_DIRNAME = "lib";
78 public static final String LEGAL_DIRNAME = "legal";
79 public static final String MAN_DIRNAME = "man";
80
131
132 private final Path root;
133 private final Path mdir;
134 private final Set<String> modules = new HashSet<>();
135 private String targetOsName;
136
137 /**
138 * Default image builder constructor.
139 *
140 * @param root The image root directory.
141 * @throws IOException
142 */
143 public DefaultImageBuilder(Path root) throws IOException {
144 Objects.requireNonNull(root);
145
146 this.root = root;
147 this.mdir = root.resolve("lib");
148 Files.createDirectories(mdir);
149 }
150
151 @Override
152 public void storeFiles(ResourcePool files) {
153 try {
154 // populate targetOsName field up-front because it's used elsewhere.
155 Optional<ResourcePoolModule> javaBase = files.moduleView().findModule("java.base");
156 javaBase.ifPresent(mod -> {
157 // fill release information available from transformed "java.base" module!
158 ModuleDescriptor desc = mod.descriptor();
159 desc.osName().ifPresent(s -> {
160 this.targetOsName = s;
161 });
162 });
163
164 if (this.targetOsName == null) {
165 throw new PluginException("TargetPlatform attribute is missing for java.base module");
166 }
167
168 Path bin = root.resolve(BIN_DIRNAME);
169
170 // check any duplicated resource files
171 Map<Path, Set<String>> duplicates = new HashMap<>();
172 files.entries()
173 .filter(f -> f.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
174 .collect(groupingBy(this::entryToImagePath,
175 mapping(ResourcePoolEntry::moduleName, toSet())))
176 .entrySet()
177 .stream()
178 .filter(e -> e.getValue().size() > 1)
179 .forEach(e -> duplicates.put(e.getKey(), e.getValue()));
180
181 // write non-classes resource files to the image
182 files.entries()
183 .filter(f -> f.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
184 .forEach(f -> {
185 try {
186 accept(f);
187 } catch (FileAlreadyExistsException e) {
338 /**
339 * Returns the path of the given entry to be written in the image
340 */
341 private Path entryToImagePath(ResourcePoolEntry entry) {
342 switch (entry.type()) {
343 case NATIVE_LIB:
344 String filename = entryToFileName(entry);
345 return Paths.get(nativeDir(filename), filename);
346 case NATIVE_CMD:
347 return Paths.get(BIN_DIRNAME, entryToFileName(entry));
348 case CONFIG:
349 return Paths.get(CONF_DIRNAME, entryToFileName(entry));
350 case HEADER_FILE:
351 return Paths.get(INCLUDE_DIRNAME, entryToFileName(entry));
352 case MAN_PAGE:
353 return Paths.get(MAN_DIRNAME, entryToFileName(entry));
354 case LEGAL_NOTICE:
355 return Paths.get(LEGAL_DIRNAME, entryToFileName(entry));
356 case TOP:
357 return Paths.get(entryToFileName(entry));
358 default:
359 throw new IllegalArgumentException("invalid type: " + entry);
360 }
361 }
362
363 private void accept(ResourcePoolEntry file) throws IOException {
364 if (file.linkedTarget() != null && file.type() != Type.LEGAL_NOTICE) {
365 throw new UnsupportedOperationException("symbolic link not implemented: " + file);
366 }
367
368 try (InputStream in = file.content()) {
369 switch (file.type()) {
370 case NATIVE_LIB:
371 Path dest = root.resolve(entryToImagePath(file));
372 writeEntry(in, dest);
373 break;
374 case NATIVE_CMD:
375 Path p = root.resolve(entryToImagePath(file));
376 writeEntry(in, p);
377 p.toFile().setExecutable(true);
378 break;
379 case CONFIG:
380 case HEADER_FILE:
381 case MAN_PAGE:
382 writeEntry(in, root.resolve(entryToImagePath(file)));
383 break;
384 case LEGAL_NOTICE:
385 Path source = entryToImagePath(file);
386 if (file.linkedTarget() == null) {
387 writeEntry(in, root.resolve(source));
388 } else {
389 Path target = entryToImagePath(file.linkedTarget());
390 Path relPath = source.getParent().relativize(target);
391 writeSymLinkEntry(root.resolve(source), relPath);
392 }
393 break;
394 case TOP:
395 // Copy TOP files of the "java.base" module (only)
396 if ("java.base".equals(file.moduleName())) {
397 writeEntry(in, root.resolve(entryToImagePath(file)));
398 }
399 break;
400 default:
401 throw new InternalError("unexpected entry: " + file.path());
402 }
403 }
404 }
405
406 private void writeEntry(InputStream in, Path dstFile) throws IOException {
407 Objects.requireNonNull(in);
408 Objects.requireNonNull(dstFile);
409 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
410 Files.copy(in, dstFile);
411 }
412
413 private void writeSymEntry(Path dstFile, Path target) throws IOException {
414 Objects.requireNonNull(dstFile);
415 Objects.requireNonNull(target);
416 Files.createDirectories(Objects.requireNonNull(dstFile.getParent()));
417 Files.createLink(dstFile, target);
|