1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.tools.jlink.internal.plugins; 26 27 import java.io.BufferedReader; 28 import java.io.ByteArrayInputStream; 29 import java.io.IOException; 30 import java.io.InputStreamReader; 31 import java.io.UncheckedIOException; 32 import java.nio.charset.StandardCharsets; 33 import java.util.Collections; 34 import java.util.Comparator; 35 import java.util.HashSet; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.TreeSet; 40 import java.util.function.Predicate; 41 import java.util.stream.Collectors; 42 import jdk.tools.jlink.plugin.TransformerPlugin; 43 import jdk.tools.jlink.plugin.Pool; 44 import jdk.tools.jlink.plugin.Pool.ModuleDataType; 45 import jdk.tools.jlink.internal.Utils; 46 import jdk.tools.jlink.plugin.PluginException; 47 48 /** 49 * 50 * Exclude VM plugin 51 */ 52 public final class ExcludeVMPlugin implements TransformerPlugin { 53 54 private static final class JvmComparator implements Comparator<Jvm> { 55 56 @Override 57 public int compare(Jvm o1, Jvm o2) { 58 return o1.getEfficience() - o2.getEfficience(); 59 } 60 } 61 62 private enum Jvm { 63 // The efficience order server - client - minimal. 64 SERVER("server", 3), CLIENT("client", 2), MINIMAL("minimal", 1); 65 private final String name; 66 private final int efficience; 67 68 Jvm(String name, int efficience) { 69 this.name = name; 70 this.efficience = efficience; 71 } 72 73 private String getName() { 74 return name; 75 } 76 77 private int getEfficience() { 78 return efficience; 79 } 80 } 81 82 private static final String JVM_CFG = "jvm.cfg"; 83 84 public static final String NAME = "vm"; 85 private static final String ALL = "all"; 86 private static final String CLIENT = "client"; 87 private static final String SERVER = "server"; 88 private static final String MINIMAL = "minimal"; 89 90 private Predicate<String> predicate; 91 private Jvm target; 92 private boolean keepAll; 93 94 @Override 95 public String getName() { 96 return NAME; 97 } 98 99 /** 100 * VM paths: 101 * /java.base/native/{architecture}/{server|client|minimal}/{shared lib} 102 * e.g.: /java.base/native/amd64/server/libjvm.so 103 * /java.base/native/server/libjvm.dylib 104 */ 105 private List<Pool.ModuleData> getVMs(Pool in) { 106 String jvmlib = jvmlib(); 107 List<Pool.ModuleData> ret = in.getModule("java.base").getContent().stream().filter((t) -> { 108 return t.getPath().endsWith("/" + jvmlib); 109 }).collect(Collectors.toList()); 110 return ret; 111 } 112 113 @Override 114 public void visit(Pool in, Pool out) { 115 String jvmlib = jvmlib(); 116 TreeSet<Jvm> existing = new TreeSet<>(new JvmComparator()); 117 TreeSet<Jvm> removed = new TreeSet<>(new JvmComparator()); 118 if (!keepAll) { 119 // First retrieve all available VM names and removed VM 120 List<Pool.ModuleData> jvms = getVMs(in); 121 for (Jvm jvm : Jvm.values()) { 122 for (Pool.ModuleData md : jvms) { 123 if (md.getPath().endsWith("/" + jvm.getName() + "/" + jvmlib)) { 124 existing.add(jvm); 125 if (isRemoved(md)) { 126 removed.add(jvm); 127 } 128 } 129 } 130 } 131 } 132 // Check that target exists 133 if (!keepAll) { 134 if (!existing.contains(target)) { 135 throw new PluginException("Selected VM " + target.getName() + " doesn't exist."); 136 } 137 } 138 139 // Rewrite the jvm.cfg file. 140 in.visit((file) -> { 141 if (!keepAll) { 142 if (file.getType().equals(ModuleDataType.NATIVE_LIB)) { 143 if (file.getPath().endsWith(JVM_CFG)) { 144 try { 145 file = handleJvmCfgFile(file, existing, removed); 146 } catch (IOException ex) { 147 throw new UncheckedIOException(ex); 148 } 149 } 150 } 151 file = isRemoved(file) ? null : file; 152 } 153 return file; 154 }, out); 155 156 } 157 158 private boolean isRemoved(Pool.ModuleData file) { 159 return !predicate.test(file.getPath()); 160 } 161 162 @Override 163 public Set<PluginType> getType() { 164 Set<PluginType> set = new HashSet<>(); 165 set.add(CATEGORY.FILTER); 166 return Collections.unmodifiableSet(set); 167 } 168 169 @Override 170 public String getDescription() { 171 return PluginsResourceBundle.getDescription(NAME); 172 } 173 174 @Override 175 public boolean hasArguments() { 176 return true; 177 } 178 179 @Override 180 public String getArgumentsDescription() { 181 return PluginsResourceBundle.getArgument(NAME); 182 } 183 184 @Override 185 public void configure(Map<String, String> config) { 186 try { 187 String value = config.get(NAME); 188 String exclude = ""; 189 switch (value) { 190 case ALL: { 191 // no filter. 192 keepAll = true; 193 break; 194 } 195 case CLIENT: { 196 target = Jvm.CLIENT; 197 exclude = "/java.base/native*server/*,/java.base/native*minimal/*"; 198 break; 199 } 200 case SERVER: { 201 target = Jvm.SERVER; 202 exclude = "/java.base/native*client/*,/java.base/native*minimal/*"; 203 break; 204 } 205 case MINIMAL: { 206 target = Jvm.MINIMAL; 207 exclude = "/java.base/native*server/*,/java.base/native*client/*"; 208 break; 209 } 210 default: { 211 throw new PluginException("Unknown option " + value); 212 } 213 } 214 predicate = new ResourceFilter(Utils.listParser.apply(exclude), true); 215 } catch (IOException ex) { 216 throw new UncheckedIOException(ex); 217 } 218 } 219 220 private Pool.ModuleData handleJvmCfgFile(Pool.ModuleData orig, 221 TreeSet<Jvm> existing, 222 TreeSet<Jvm> removed) throws IOException { 223 if (keepAll) { 224 return orig; 225 } 226 StringBuilder builder = new StringBuilder(); 227 // Keep comments 228 try (BufferedReader reader 229 = new BufferedReader(new InputStreamReader(orig.stream(), 230 StandardCharsets.UTF_8))) { 231 reader.lines().forEach((s) -> { 232 if (s.startsWith("#")) { 233 builder.append(s).append("\n"); 234 } 235 }); 236 } 237 TreeSet<Jvm> remaining = new TreeSet<>(new JvmComparator()); 238 // Add entry in jvm.cfg file from the more efficient to less efficient. 239 for (Jvm platform : existing) { 240 if (!removed.contains(platform)) { 241 remaining.add(platform); 242 builder.append("-").append(platform.getName()).append(" KNOWN\n"); 243 } 244 } 245 246 // removed JVM are aliased to the most efficient remaining JVM (last one). 247 // The order in the file is from most to less efficient platform 248 for (Jvm platform : removed.descendingSet()) { 249 builder.append("-").append(platform.getName()). 250 append(" ALIASED_TO -"). 251 append(remaining.last().getName()).append("\n"); 252 } 253 254 byte[] content = builder.toString().getBytes(StandardCharsets.UTF_8); 255 256 return Pool.newImageFile(orig.getModule(), 257 orig.getPath(), 258 orig.getType(), 259 new ByteArrayInputStream(content), content.length); 260 } 261 262 private static String jvmlib() { 263 String lib = "libjvm.so"; 264 if (isWindows()) { 265 lib = "jvm.dll"; 266 } else if (isMac()) { 267 lib = "libjvm.dylib"; 268 } 269 return lib; 270 } 271 272 private static boolean isWindows() { 273 return System.getProperty("os.name").startsWith("Windows"); 274 } 275 276 private static boolean isMac() { 277 return System.getProperty("os.name").startsWith("Mac OS"); 278 } 279 }