Created
August 27, 2021 09:33
-
-
Save lwr/6829cefaab2261cec1cc733f6398b9ac to your computer and use it in GitHub Desktop.
cn.mailtech.maven.resolver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2020 Mailtech.cn, Ltd. All Rights Reserved. | |
*/ | |
package cn.mailtech.maven.resolver; | |
import java.io.*; | |
import java.lang.reflect.Method; | |
import java.net.*; | |
import java.util.*; | |
/** | |
* LaunchHelper. | |
* | |
* @author <a href="mailto:[email protected]">William Leung</a> | |
*/ | |
public abstract class Launcher { | |
private static String mavenHome; | |
@SuppressWarnings("SpellCheckingInspection") | |
private static final String CLASSWORLDS_LAUNCHER = "org.codehaus.plexus.classworlds.launcher.Launcher"; | |
@SuppressWarnings("SpellCheckingInspection") // ${MAVEN_HOME}/boot/plexus-classworlds-*.jar | |
private static ClassLoader classworldsLoader(String mavenHome) throws IOException { | |
List<URL> urls = new ArrayList<>(); | |
String[] names = new File(mavenHome, "boot").list(); | |
if (names == null) { | |
throw new FileNotFoundException(new File(mavenHome, "boot").toString()); | |
} | |
for (String filename : names) { | |
if (filename.toLowerCase().endsWith(".jar")) { | |
urls.add(new File(mavenHome, "boot/" + filename).toURI().toURL()); | |
} | |
} | |
return new URLClassLoader(urls.toArray(new URL[0])); | |
} | |
public static void setMavenHome(String mavenHome) { | |
Launcher.mavenHome = mavenHome; | |
} | |
@SuppressWarnings("SpellCheckingInspection") | |
static void mvnLaunch(Properties props, String... args) throws Exception { | |
ClassLoader loader = classworldsLoader(mavenHome); | |
Method main = loader.loadClass(CLASSWORLDS_LAUNCHER).getDeclaredMethod("mainWithExitCode", String[].class); | |
Properties systemProperties = System.getProperties(); | |
Properties mavenOpts = (Properties) systemProperties.clone(); | |
mavenOpts.putAll(props); | |
if (mavenHome == null) { | |
mavenHome = System.getenv("MAVEN_HOME"); | |
if (mavenHome == null) { | |
throw new Exception("mavenHome not set"); | |
} | |
} | |
// "-Dclassworlds.conf=${MAVEN_HOME}/bin/m2.conf" \ | |
// "-Dmaven.home=${MAVEN_HOME}" \ | |
// "-Dlibrary.jansi.path=${MAVEN_HOME}/lib/jansi-native" \ | |
// "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ | |
mavenOpts.put("classworlds.conf", mavenHome + "/bin/m2.conf"); | |
mavenOpts.put("maven.home", mavenHome); | |
if (new File(mavenHome + "/lib/jansi-native").exists()) { | |
mavenOpts.put("library.jansi.path", mavenHome + "/lib/jansi-native"); | |
} | |
mavenOpts.put("maven.multiModuleProjectDirectory", ""); | |
System.setProperties(mavenOpts); | |
try { | |
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); | |
Thread.currentThread().setContextClassLoader(loader); | |
try { | |
int exitCode = (int) main.invoke(null, new Object[]{args}); | |
if (exitCode != 0) { | |
throw new Exception("maven launcher exit code: " + exitCode); | |
} | |
} finally { | |
Thread.currentThread().setContextClassLoader(contextClassLoader); | |
} | |
} finally { | |
System.setProperties(systemProperties); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2020 Mailtech.cn, Ltd. All Rights Reserved. | |
*/ | |
package cn.mailtech.maven.resolver; | |
import java.io.*; | |
import java.util.*; | |
/** | |
* ResolveParams. | |
* | |
* @author <a href="mailto:[email protected]">William Leung</a> | |
*/ | |
public class ResolveOptions { | |
private Collection<File> pomFiles; | |
private String coords = ""; | |
private String scope = ""; | |
private String mavenCliOpts = ""; | |
private String mavenResolveParams = ""; | |
public Collection<File> getPomFiles() { | |
return pomFiles; | |
} | |
public void setPomFiles(Collection<File> pomFiles) { | |
this.pomFiles = pomFiles; | |
} | |
public String getCoords() { | |
return coords; | |
} | |
public void setCoords(String coords) { | |
this.coords = coords; | |
} | |
public String getScope() { | |
return scope; | |
} | |
public void setScope(String scope) { | |
this.scope = scope; | |
} | |
public String getMavenCliOpts() { | |
return mavenCliOpts; | |
} | |
public void setMavenCliOpts(String mavenCliOpts) { | |
this.mavenCliOpts = mavenCliOpts; | |
} | |
public String getMavenResolveParams() { | |
return mavenResolveParams; | |
} | |
public void setMavenResolveParams(String mavenResolveParams) { | |
this.mavenResolveParams = mavenResolveParams; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2020 Mailtech.cn, Ltd. All Rights Reserved. | |
*/ | |
package cn.mailtech.maven.resolver; | |
import java.io.*; | |
import java.nio.charset.StandardCharsets; | |
import java.nio.file.Files; | |
import java.util.*; | |
/** | |
* Resolver. | |
* | |
* @author <a href="mailto:[email protected]">William Leung</a> | |
*/ | |
public class Resolver { | |
private static String settingsFile; | |
private static Properties mavenProps() { | |
Properties result = new Properties(); | |
// suppress messages: 'canning for projects ...' / 'Building ...' / ... | |
result.put("org.slf4j.simpleLogger.log.org.apache.maven.cli", "WARN"); | |
// suppress messages: 'Not compiling main sources' / 'Not compiling test sources' | |
result.put("org.slf4j.simpleLogger.log.org.apache.maven.plugin.compiler.CompilerMojo", "WARN"); | |
result.put("org.slf4j.simpleLogger.log.org.apache.maven.plugin.compiler.TestCompilerMojo", "WARN"); | |
// The copying of resource files would not be bypassed until [email protected] | |
// see https://maven.apache.org/plugins/maven-resources-plugin/resources-mojo.html#skip | |
// suppress messages: [INFO] Skipping the execution | |
// [INFO] Using ... encoding to copy filtered resources. | |
// [INFO] Copying ... resource(s) | |
// [INFO] Not copying test resources | |
result.put("org.slf4j.simpleLogger.log.org.apache.maven.plugins.resources", "WARN"); // for maven-resources-plugin-3 | |
result.put("org.slf4j.simpleLogger.log.org.apache.maven.plugin.resources", "WARN"); // for maven-resources-plugin-2.6 | |
// suppress messages: [WARNING] Using platform encoding (GB18030 actually) to copy filtered resources, i.e. build is platform dependent! | |
// [INFO] skip non existing resourceDirectory ... | |
result.put("org.slf4j.simpleLogger.log.org.apache.maven.shared.filtering.DefaultMavenResourcesFiltering", "ERROR"); | |
// enable message: 'Downloading from ...' / 'Downloaded from ...' | |
result.put("org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener", "INFO"); | |
// Other properties | |
result.put("java.awt.headless", "true"); | |
result.put("user.language", "en"); | |
return result; | |
} | |
public static void setSettingsFile(String settingsFile) { | |
Resolver.settingsFile = settingsFile; | |
} | |
private Set<String> scopes = Collections.emptySet(); | |
private void generatedTempPom(String coords, File target) throws IOException { | |
String template; | |
try (InputStream in = getClass().getResourceAsStream("template.pom")) { | |
assert in != null; | |
// stupid way but no better choice | |
// see https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java | |
java.util.Scanner s = new java.util.Scanner(in, "utf8").useDelimiter("\\A"); | |
template = s.hasNext() ? s.next() : ""; | |
} | |
// --coords=coords="g1:a1:v1 g2:a2:v2!g:a" | |
StringBuilder builder = new StringBuilder("<dependencies>"); | |
for (String item : coords.split("[\\s;]+")) { | |
String[] withExclusions = item.split("!"); | |
String[] arr = withExclusions[0].split(":", 4); | |
if (arr.length == 3 && arr[0].length() > 0 && arr[1].length() > 0 && arr[2].length() > 0) { | |
builder.append("<dependency>"); | |
builder.append(String.format("" | |
+ "<groupId>%s</groupId>" | |
+ "<artifactId>%s</artifactId>" | |
+ "<version>%s</version>", | |
(Object[]) arr)); | |
for (String exclusion : Arrays.asList(withExclusions).subList(1, withExclusions.length)) { | |
String[] arrExclusion = exclusion.split(":", 3); | |
if (arrExclusion.length == 2 && arrExclusion[0].length() > 0 && arrExclusion[1].length() > 0) { | |
builder.append(String.format("" | |
+ "<exclusions>" | |
+ "<exclusion>" | |
+ "<groupId>%s</groupId>" | |
+ "<artifactId>%s</artifactId>" | |
+ "</exclusion>" | |
+ "</exclusions>", | |
(Object[]) arrExclusion)); | |
} else { | |
throw new IllegalArgumentException("Invalid coords: " + coords); | |
} | |
} | |
builder.append("</dependency>"); | |
} else { | |
throw new IllegalArgumentException("Invalid coords: " + coords); | |
} | |
} | |
builder.append("</dependencies>"); | |
Files.write(target.toPath(), template.replace("<!-- content -->", builder).getBytes(StandardCharsets.UTF_8)); | |
} | |
// scope = "compile" <==> scope = "[compile, provided, system]" (default) | |
// scope = "runtime" <==> scope = "[compile, runtime]" | |
// scope = "test" <==> scope = "[compile, provided, system, runtime, test]" (all) | |
private String parseScope(String scope) { | |
String[] scopes; | |
if (scope.startsWith("[") && scope.endsWith("]")) { | |
scopes = scope.substring(1, scope.length() - 1).split("\\s*,\\s*"); | |
scope = "test"; | |
} else if (scope.equals("compile") || scope.isEmpty()) { | |
scopes = new String[]{"compile", "provided", "system"}; | |
} else if (scope.equals("runtime")) { | |
scopes = new String[]{"compile", "runtime"}; | |
} else if (scope.equals("test")) { | |
scopes = new String[]{"compile", "provided", "system", "runtime", "test"}; | |
} else { | |
scopes = new String[]{scope}; | |
} | |
this.scopes = new HashSet<>(Arrays.asList(scopes)); | |
return scope; | |
} | |
public Map<String, String> execute(ResolveOptions options) throws Exception { | |
Map<String, String> result = new LinkedHashMap<>(); | |
File generatedPomFile = File.createTempFile("generated", ".pom"); | |
File outputFile = File.createTempFile("output", ".txt"); | |
try { | |
Collection<File> pomFiles; | |
if (options.getPomFiles() != null && options.getPomFiles().size() > 0) { | |
pomFiles = options.getPomFiles(); | |
// System.out.printf("Resolving %s", options.getPomFiles()); | |
} else if (options.getCoords().length() > 0) { | |
generatedTempPom(options.getCoords(), generatedPomFile); | |
pomFiles = Collections.singleton(generatedPomFile); | |
// System.out.printf("Resolving %s", options.getCoords()); | |
} else { | |
throw new Exception("Arg not found: pomFile or coords"); | |
} | |
for (File pomFile : pomFiles) { | |
doResolve(options, pomFile, outputFile); | |
for (Map.Entry<String, String> e : parseOutput(outputFile).entrySet()) { | |
result.putIfAbsent(e.getKey(), e.getValue()); | |
} | |
} | |
return result; | |
} finally { | |
// noinspection ResultOfMethodCallIgnored | |
generatedPomFile.delete(); | |
// noinspection ResultOfMethodCallIgnored | |
outputFile.delete(); | |
} | |
} | |
private void doResolve(ResolveOptions options, File pomFile, File outputFile) throws Exception { | |
// Resolve maven dependencies: | |
// - @see https://stackoverflow.com/questions/28835418/mvn-dependency-fails-on-trivial-project/39565742#39565742 | |
// - @see https://stackoverflow.com/questions/15249345/disable-phases-in-maven-lifecycle/15250450#15250450 | |
// | |
// Kotlin compile mojo does not have a skip configuration parameter now (but have for test-compile). | |
// For workaround, we now set parse to none to disable the kotlin compiler (same for resources plugin). | |
List<String> args = new ArrayList<>(Arrays.asList("test-compile", "dependency:resolve")); | |
args.add("-f"); | |
args.add(pomFile.getAbsolutePath()); | |
// MAVEN_CLI_OPTS | |
args.add("--batch-mode"); | |
if (settingsFile != null) { | |
args.add("--settings"); | |
args.add(settingsFile); | |
} | |
// --mavenCliOpts="-am -pl relativePath|:artifactId" | |
if (!options.getMavenCliOpts().matches("-pl |--projects ")) { | |
args.add("-pl"); | |
args.add("."); | |
} | |
if (options.getMavenCliOpts().length() > 0) { | |
Collections.addAll(args, options.getMavenCliOpts().split("\\s+")); | |
} | |
Map<String, String> props = new HashMap<>(); | |
props.put("maven.main.skip", null); | |
props.put("maven.test.skip", null); | |
props.put("maven.resources.skip", null); | |
props.put("kotlin.main.compile.phase", "none"); | |
props.put("kotlin.test.compile.phase", "none"); | |
props.put("includeScope", parseScope(options.getScope())); | |
props.put("outputAbsoluteArtifactFilename", null); | |
props.put("outputFile", outputFile.getAbsolutePath()); | |
for (String param : options.getMavenResolveParams().split("\\s+")) { | |
String[] arr = param.split("=", 2); | |
if (arr[0].length() > 0) { | |
props.putIfAbsent(arr[0], arr.length > 1 ? arr[1] : ""); // dont override | |
} | |
} | |
props.forEach((key, val) -> args.add("-D" + key + (val == null ? "" : "=" + val))); | |
Launcher.mvnLaunch(mavenProps(), args.toArray(new String[0])); | |
} | |
private Map<String, String> parseOutput(File outputFile) throws IOException { | |
Map<String, String> result = new LinkedHashMap<>(); | |
for (String line : Files.readAllLines(outputFile.toPath())) { | |
line = line.trim(); | |
// skip the first line "The following files have been resolved:" | |
if (line.isEmpty() || line.equals("none") || line.startsWith("The following files")) { | |
continue; | |
} | |
// groupId:artifactId:type:version:scope:absoluteArtifactFilename (optional) | |
String[] arr = line.split(":", 6); | |
if (arr.length != 6) { | |
throw new RuntimeException("Unknown output: " + line); | |
} | |
String groupId = arr[0]; | |
String artifactId = arr[1]; | |
String type = arr[2]; | |
// String version = arr[3]; | |
String scope = arr[4]; | |
String value = arr[5]; // absoluteArtifactFilename (optional) | |
if (scopes.contains(scope)) { | |
result.put(groupId + ':' + artifactId + ':' + type, value.endsWith(" (optional)") | |
? value.substring(0, value.length() - " (optional)".length()) | |
: value); | |
} | |
} | |
return result; | |
} | |
} |
usage
// preparing for environments
Launcher.setMavenHome(getProject().getProperty("maven.home"));
Resolver.setSettingsFile(getProject().getProperty("maven.settingsFile"));
ResolveOptions options = new ResolveOptions()
// pomFile="foo.pom; bar.pom; foo/**/bar/pom.xml" // multiple files are also supported
options.setPomFiles(pomFiles);
// coords="g1:a1:v1 g2:a2:v2!g:a" // multiple coords and exclusion are also supported
options.setCoords(coords);
// "compile" | "runtime" | "test"
options.setScope(scope);
// if you liked
options.setMavenCliOpts(...);
// if you liked
options.setMavenResolveParams(...);
// for each entry: {key -> "g1:a1:v1:jar", value -> "/path/to/.m2/repository/g1/a1/v1/a1-v1.jar"}
Map<String, String> properties = new Resolver().execute(options);
and this is the ant task class
/*
* Copyright (c) 2020 Mailtech.cn, Ltd. All Rights Reserved.
*/
package cn.mailtech.maven.resolver.ant.tasks;
import cn.mailtech.maven.resolver.Launcher;
import cn.mailtech.maven.resolver.ResolveOptions;
import cn.mailtech.maven.resolver.Resolver;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.resources.FileResource;
import java.io.*;
import java.util.*;
/**
* Resolve.
*
* @author <a href="mailto:[email protected]">William Leung</a>
*/
public class Resolve extends Task {
private final ResolveOptions options = new ResolveOptions();
private String pomFile;
private String pathRef;
private String propertyPrefix;
public void setPomFile(String pomFile) {
this.pomFile = pomFile;
}
public void setCoords(String coords) {
options.setCoords(coords);
}
public void setScope(String scope) {
options.setScope(scope);
}
public void setMavenCliOpts(String mavenCliOpts) {
options.setMavenCliOpts(mavenCliOpts);
}
public void setMavenResolveParams(String mavenResolveParams) {
options.setMavenResolveParams(mavenResolveParams);
}
public void setPathRef(String pathRef) {
this.pathRef = pathRef;
}
public void setPropertyPrefix(String propertyPrefix) {
this.propertyPrefix = propertyPrefix;
}
private boolean inited;
@Override
public void execute() throws BuildException {
if (!inited) {
Launcher.setMavenHome(getProject().getProperty("maven.home"));
Resolver.setSettingsFile(getProject().getProperty("maven.settingsFile"));
inited = true;
}
// pomFile="foo.pom; bar.pom; foo/**/bar/pom.xml"
Set<File> pomFiles = new LinkedHashSet<>();
if (pomFile != null && pomFile.length() > 0) {
for (String pomFile : pomFile.split("\\s*;\\s")) {
// foo/**/bar/pom.xml -> ['foo', '**/bar/pom.xml']
int asteriskPosition = pomFile.indexOf('*');
if (asteriskPosition != -1 && pomFile.charAt(asteriskPosition - 1) == '/') {
FileSet fileset = (FileSet) getProject().createDataType("fileset");
fileset.setDir(getProject().resolveFile(pomFile.substring(0, asteriskPosition - 1)));
fileset.setIncludes(pomFile.substring(asteriskPosition));
Iterator<Resource> iterator = fileset.iterator();
if (!iterator.hasNext()) {
throw new BuildException("pomFile not found: " + pomFile);
}
while (iterator.hasNext()) {
pomFiles.add(((FileResource) iterator.next()).getFile());
}
} else if (pomFile.length() > 0) {
pomFiles.add(getProject().resolveFile(pomFile));
}
}
}
options.setPomFiles(pomFiles);
Map<String, String> properties;
try {
properties = new Resolver().execute(options);
} catch (Exception e) {
throw new BuildException(e);
}
Path path = new Path(getProject());
String propPrefix = propertyPrefix != null ? propertyPrefix + (propertyPrefix.matches("[./:#]$|^$") ? "" : ".") : null;
properties.forEach((key, value) -> {
if (propPrefix != null) {
getProject().setNewProperty(propPrefix + key, value);
}
path.createPathElement().setPath(value);
});
if (pathRef != null) {
getProject().addReference(pathRef, path);
}
}
}
and examples
<!-- simple pom -->
<resolve pomFile="./pom.xml" scope="provided" pathRef="cp" />
<!-- simple coords -->
<resolve coords="org.apache.ant:ant:1.9.4" pathRef="cp" />
<!-- coords-exclusion -->
<resolve coords="org.apache.ant:ant:1.9.4!org.apache.ant:ant-launcher" pathRef="cp" />
<!-- multiple poms with fileset -->
<resolve pomFile="./resolver/*.pom" pathRef="cp" />
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
replacement of https://issues.apache.org/jira/browse/MRESOLVER
see