io.quarkus.bootstrap.resolver.maven.options.BootstrapMavenOptions Maven / Gradle / Ivy
package io.quarkus.bootstrap.resolver.maven.options;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.shared.utils.cli.CommandLineException;
import org.apache.maven.shared.utils.cli.CommandLineUtils;
import io.quarkus.bootstrap.util.PropertyUtils;
/**
* This class resolves relevant Maven command line options in case it's called
* from a Maven build process. Maven internally uses org.apache.commons.cli.* API
* besides Maven-specific API. This class locates the Maven's lib directory that
* was used to launch the build process and loads the necessary classes from that
* lib directory.
*/
public class BootstrapMavenOptions {
public static final String QUARKUS_INTERNAL_MAVEN_CMD_LINE_ARGS = "quarkus-internal.maven-cmd-line-args";
private static final String MAVEN_CMD_LINE_ARGS = "MAVEN_CMD_LINE_ARGS";
private static final String ACTIVATE_PROFILES = "P";
public static final String ALTERNATE_USER_SETTINGS = "s";
public static final String ALTERNATE_GLOBAL_SETTINGS = "gs";
public static final String ALTERNATE_POM_FILE = "f";
public static final String OFFLINE = "o";
public static final String SUPRESS_SNAPSHOT_UPDATES = "nsu";
public static final String UPDATE_SNAPSHOTS = "U";
public static final String CHECKSUM_FAILURE_POLICY = "C";
public static final String CHECKSUM_WARNING_POLICY = "c";
public static final String BATCH_MODE = "B";
public static final String NO_TRANSFER_PROGRESS = "ntp";
public static final String SYSTEM_PROPERTY = "D";
public static Map parse(String cmdLine) {
if (cmdLine == null) {
return Collections.emptyMap();
}
final String[] args;
try {
args = CommandLineUtils.translateCommandline(cmdLine);
} catch (CommandLineException e) {
throw new IllegalArgumentException("Invalid command line: " + cmdLine, e);
}
if (args.length == 0) {
return Collections.emptyMap();
}
final String mavenHome = PropertyUtils.getProperty("maven.home");
if (mavenHome == null) {
try {
return invokeParser(Thread.currentThread().getContextClassLoader(), args);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Failed to load parser", e);
}
}
final Path mvnLib = Paths.get(mavenHome).resolve("lib");
if (!Files.exists(mvnLib)) {
throw new IllegalStateException("Maven lib dir does not exist: " + mvnLib);
}
final URL[] urls;
try (Stream files = Files.list(mvnLib)) {
final List list = files.map(p -> {
try {
return p.toUri().toURL();
} catch (MalformedURLException e) {
throw new IllegalStateException("Failed to translate " + p + " to URL", e);
}
}).collect(Collectors.toCollection(ArrayList::new));
list.add(getClassOrigin(BootstrapMavenOptions.class).toUri().toURL());
urls = list.toArray(new URL[list.size()]);
} catch (Exception e) {
throw new IllegalStateException("Failed to create a URL list out of " + mvnLib + " content", e);
}
final ClassLoader originalCl = Thread.currentThread().getContextClassLoader();
try (URLClassLoader ucl = new URLClassLoader(urls, null)) {
Thread.currentThread().setContextClassLoader(ucl);
try {
return invokeParser(ucl, args);
} catch (ClassNotFoundException e) {
Thread.currentThread().setContextClassLoader(originalCl);
try {
return invokeParser(originalCl, args);
} catch (ClassNotFoundException classNotFoundException) {
throw new RuntimeException("Failed to load parser", e);
}
}
} catch (IOException e) {
throw new IllegalStateException("Failed to close URL classloader", e);
} finally {
Thread.currentThread().setContextClassLoader(originalCl);
}
}
public static BootstrapMavenOptions newInstance() {
return newInstance(getMavenCmdLine());
}
public static BootstrapMavenOptions newInstance(String cmdLine) {
return new BootstrapMavenOptions(parse(cmdLine));
}
public static String getMavenCmdLine() {
final String mvnCmd = PropertyUtils.getProperty(QUARKUS_INTERNAL_MAVEN_CMD_LINE_ARGS);
if (mvnCmd != null) {
return mvnCmd;
}
final String mvnLine = System.getenv(MAVEN_CMD_LINE_ARGS);
if (mvnLine == null) {
// if the maven command line isn't available we are not checking for MAVEN_OPTS
return null;
}
String mvnOpts = System.getenv("MAVEN_OPTS");
if (mvnOpts == null) {
return mvnLine;
}
// only retain system properties from MAVEN_OPTS, not something like --add-opens
try {
mvnOpts = Arrays.stream(CommandLineUtils.translateCommandline(mvnOpts))
.filter(part -> part.startsWith("-" + SYSTEM_PROPERTY))
.collect(Collectors.joining(" "));
} catch (CommandLineException e) {
throw new IllegalStateException("Failed to parse MAVEN_OPTS=" + mvnOpts, e);
}
if (mvnOpts.isEmpty()) {
return mvnLine;
}
final StringBuilder buf = new StringBuilder(mvnLine.length() + mvnOpts.length() + 1);
buf.append(mvnOpts);
if (!Character.isWhitespace(mvnLine.charAt(0))) {
buf.append(' ');
}
return buf.append(mvnLine).toString();
}
private final Map options;
private List activeProfileIds;
private List inactiveProfileIds;
private BootstrapMavenOptions(Map options) {
this.options = options;
}
public boolean hasOption(String name) {
return options.containsKey(name);
}
public String getOptionValue(String name) {
final Object o = options.get(name);
return o == null ? null : o.toString();
}
public String[] getOptionValues(String name) {
final Object o = options.get(name);
if (o == null) {
return null;
}
if (o instanceof String[]) {
return (String[]) o;
}
return new String[] { o.toString() };
}
public Properties getSystemProperties() {
return (Properties) options.get(SYSTEM_PROPERTY);
}
public boolean isEmpty() {
return options.isEmpty();
}
public List getActiveProfileIds() {
if (activeProfileIds == null) {
parseProfileArgs();
}
return activeProfileIds;
}
public List getInactiveProfileIds() {
if (inactiveProfileIds == null) {
parseProfileArgs();
}
return inactiveProfileIds;
}
private void parseProfileArgs() {
final String[] profileOptionValues = getOptionValues(ACTIVATE_PROFILES);
if (profileOptionValues != null && profileOptionValues.length > 0) {
final List activeProfiles = new ArrayList<>(0);
final List inactiveProfiles = new ArrayList<>(0);
for (String profileOptionValue : profileOptionValues) {
final StringTokenizer profileTokens = new StringTokenizer(profileOptionValue, ",");
while (profileTokens.hasMoreTokens()) {
final String profileAction = profileTokens.nextToken().trim();
if (profileAction.isEmpty()) {
continue;
}
final char c = profileAction.charAt(0);
if (c == '-' || c == '!') {
inactiveProfiles.add(profileAction.substring(1));
} else if (c == '+') {
activeProfiles.add(profileAction.substring(1));
} else {
activeProfiles.add(profileAction);
}
}
}
this.activeProfileIds = activeProfiles;
this.inactiveProfileIds = inactiveProfiles;
} else {
activeProfileIds = Collections.emptyList();
inactiveProfileIds = Collections.emptyList();
}
}
@SuppressWarnings("unchecked")
private static Map invokeParser(ClassLoader cl, String[] args) throws ClassNotFoundException {
try {
final Class> parserCls = cl
.loadClass("io.quarkus.bootstrap.resolver.maven.options.BootstrapMavenOptionsParser");
final Method parseMethod = parserCls.getMethod("parse", String[].class);
return (Map) parseMethod.invoke(null, (Object) args);
} catch (ClassNotFoundException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException("Failed to parse command line arguments " + Arrays.asList(args), e);
}
}
/**
* Returns the JAR or the root directory that contains the class file that is on the
* classpath of the context classloader
*/
public static Path getClassOrigin(Class> cls) throws IOException {
return getResourceOrigin(cls.getClassLoader(), cls.getName().replace('.', '/') + ".class");
}
public static Path getResourceOrigin(ClassLoader cl, final String name) throws IOException {
URL url = cl.getResource(name);
if (url == null) {
throw new IOException("Failed to locate the origin of " + name);
}
String classLocation = url.toExternalForm();
if (url.getProtocol().equals("jar")) {
classLocation = classLocation.substring(4, classLocation.length() - name.length() - 2);
} else {
classLocation = classLocation.substring(0, classLocation.length() - name.length());
}
return urlSpecToPath(classLocation);
}
private static Path urlSpecToPath(String urlSpec) throws IOException {
try {
return Paths.get(new URL(urlSpec).toURI());
} catch (Throwable e) {
throw new IOException(
"Failed to create an instance of " + Path.class.getName() + " from " + urlSpec, e);
}
}
}