mirror of
https://github.com/rife2/bld
synced 2026-08-05 07:07:48 +02:00
Compare commits
No commits in common. "f2a568a6545b7aea92a6104f3684f1cf6574f4ee" and "385b0cb2a8ef32867c7054c6d17a623dcf3e7152" have entirely different histories.
f2a568a654
...
385b0cb2a8
2
core
2
core
|
|
@ -1 +1 @@
|
||||||
Subproject commit fb2e5022e5c1d125e45227b49076c2b200763986
|
Subproject commit 5f977636531d1b978b762a615d720097baa37830
|
||||||
|
|
@ -9,4 +9,4 @@ bld.javaOptions=
|
||||||
bld.javacOptions=
|
bld.javacOptions=
|
||||||
bld.repositories=MAVEN_CENTRAL,RIFE2_RELEASES,RIFE2_SNAPSHOTS
|
bld.repositories=MAVEN_CENTRAL,RIFE2_RELEASES,RIFE2_SNAPSHOTS
|
||||||
bld.sourceDirectories=core/src/bld/java
|
bld.sourceDirectories=core/src/bld/java
|
||||||
bld.version=2.4.0-SNAPSHOT
|
bld.version=2.3.1-SNAPSHOT
|
||||||
|
|
@ -31,7 +31,7 @@ public class BaseProject extends BuildExecutor {
|
||||||
/**
|
/**
|
||||||
* The CLI option to trigger automatic dependency download and purge.
|
* The CLI option to trigger automatic dependency download and purge.
|
||||||
*
|
*
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public static final String AUTO_DOWNLOAD_PURGE_OPTION = "--auto-download-purge";
|
public static final String AUTO_DOWNLOAD_PURGE_OPTION = "--auto-download-purge";
|
||||||
|
|
||||||
|
|
@ -395,7 +395,6 @@ public class BaseProject extends BuildExecutor {
|
||||||
private final CompileOperation compileOperation_ = new CompileOperation();
|
private final CompileOperation compileOperation_ = new CompileOperation();
|
||||||
private final DependencyTreeOperation dependencyTreeOperation_ = new DependencyTreeOperation();
|
private final DependencyTreeOperation dependencyTreeOperation_ = new DependencyTreeOperation();
|
||||||
private final DownloadOperation downloadOperation_ = new DownloadOperation();
|
private final DownloadOperation downloadOperation_ = new DownloadOperation();
|
||||||
private final McpOperation mcpOperation_ = new McpOperation();
|
|
||||||
private final PurgeOperation purgeOperation_ = new PurgeOperation();
|
private final PurgeOperation purgeOperation_ = new PurgeOperation();
|
||||||
private final PublishOperation publishOperation_ = new PublishOperation();
|
private final PublishOperation publishOperation_ = new PublishOperation();
|
||||||
private final RunOperation runOperation_ = new RunOperation();
|
private final RunOperation runOperation_ = new RunOperation();
|
||||||
|
|
@ -443,16 +442,6 @@ public class BaseProject extends BuildExecutor {
|
||||||
return downloadOperation_;
|
return downloadOperation_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the project's default MCP operation.
|
|
||||||
*
|
|
||||||
* @return the default MCP operation instance
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public McpOperation mcpOperation() {
|
|
||||||
return mcpOperation_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the project's default publish operation.
|
* Retrieves the project's default publish operation.
|
||||||
*
|
*
|
||||||
|
|
@ -557,18 +546,6 @@ public class BaseProject extends BuildExecutor {
|
||||||
downloadOperation().executeOnce(() -> downloadOperation().fromProject(this));
|
downloadOperation().executeOnce(() -> downloadOperation().fromProject(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard build command, starts an MCP server that exposes the build
|
|
||||||
* commands as tools.
|
|
||||||
*
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
@BuildCommand(help = McpHelp.class)
|
|
||||||
public void mcp()
|
|
||||||
throws Exception {
|
|
||||||
mcpOperation().executeOnce(() -> mcpOperation().fromProject(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard build command, purges all unused artifacts from the project.
|
* Standard build command, purges all unused artifacts from the project.
|
||||||
*
|
*
|
||||||
|
|
@ -933,7 +910,8 @@ public class BaseProject extends BuildExecutor {
|
||||||
*/
|
*/
|
||||||
public LocalDependency local(String path) {
|
public LocalDependency local(String path) {
|
||||||
return new LocalDependency(path);
|
return new LocalDependency(path);
|
||||||
}
|
} /**
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a local dependency instance.
|
* Creates a local dependency instance.
|
||||||
|
|
@ -942,9 +920,8 @@ public class BaseProject extends BuildExecutor {
|
||||||
*
|
*
|
||||||
* @param path the file system path (absolute or relative to the {@link #workDirectory})
|
* @param path the file system path (absolute or relative to the {@link #workDirectory})
|
||||||
* of the local dependency
|
* of the local dependency
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public LocalDependency local(Path path) {
|
public LocalDependency local(Path path) {
|
||||||
return new LocalDependency(path.toString());
|
return new LocalDependency(path.toString());
|
||||||
}
|
}
|
||||||
|
|
@ -954,7 +931,7 @@ public class BaseProject extends BuildExecutor {
|
||||||
* If the local dependency points to a directory, it will be scanned for jar files.
|
* If the local dependency points to a directory, it will be scanned for jar files.
|
||||||
*
|
*
|
||||||
* @param path the file system path of the local dependency
|
* @param path the file system path of the local dependency
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public LocalDependency local(File path) {
|
public LocalDependency local(File path) {
|
||||||
return new LocalDependency(path.getAbsolutePath());
|
return new LocalDependency(path.getAbsolutePath());
|
||||||
|
|
@ -1042,62 +1019,6 @@ public class BaseProject extends BuildExecutor {
|
||||||
return Module.parse(description);
|
return Module.parse(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new bill of materials instance.
|
|
||||||
*
|
|
||||||
* @param groupId the BOM group identifier
|
|
||||||
* @param artifactId the BOM artifact identifier
|
|
||||||
* @return a newly created {@code Bom} instance
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public Bom bom(String groupId, String artifactId) {
|
|
||||||
return new Bom(groupId, artifactId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new bill of materials instance.
|
|
||||||
*
|
|
||||||
* @param groupId the BOM group identifier
|
|
||||||
* @param artifactId the BOM artifact identifier
|
|
||||||
* @param version the BOM version
|
|
||||||
* @return a newly created {@code Bom} instance
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public Bom bom(String groupId, String artifactId, String version) {
|
|
||||||
return new Bom(groupId, artifactId, version(version));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new bill of materials instance.
|
|
||||||
*
|
|
||||||
* @param groupId the BOM group identifier
|
|
||||||
* @param artifactId the BOM artifact identifier
|
|
||||||
* @param version the BOM version
|
|
||||||
* @return a newly created {@code Bom} instance
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public Bom bom(String groupId, String artifactId, Version version) {
|
|
||||||
return new Bom(groupId, artifactId, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new bill of materials instance from a string representation.
|
|
||||||
* The format is {@code groupId:artifactId:version}.
|
|
||||||
* The {@code version} is optional, and an optional {@code @bom} or
|
|
||||||
* {@code @pom} type suffix is accepted. BOMs can't have classifiers,
|
|
||||||
* strings that contain one are rejected.
|
|
||||||
* <p>
|
|
||||||
* If the string can't be successfully parsed, {@code null} will be returned.
|
|
||||||
*
|
|
||||||
* @param description the BOM string to parse
|
|
||||||
* @return a parsed instance of {@code Bom}; or
|
|
||||||
* {@code null} when the string couldn't be parsed
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public Bom bom(String description) {
|
|
||||||
return Bom.parse(description);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a local module instance.
|
* Creates a local module instance.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -1118,7 +1039,7 @@ public class BaseProject extends BuildExecutor {
|
||||||
*
|
*
|
||||||
* @param path the file system path (absolute or relative to the {@link #workDirectory})
|
* @param path the file system path (absolute or relative to the {@link #workDirectory})
|
||||||
* of the local module
|
* of the local module
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public LocalModule localModule(Path path) {
|
public LocalModule localModule(Path path) {
|
||||||
return new LocalModule(path.toString());
|
return new LocalModule(path.toString());
|
||||||
|
|
@ -1130,7 +1051,7 @@ public class BaseProject extends BuildExecutor {
|
||||||
* If the local module points to a directory, it will be scanned for jar files.
|
* If the local module points to a directory, it will be scanned for jar files.
|
||||||
*
|
*
|
||||||
* @param path the file system path of the local module
|
* @param path the file system path of the local module
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public LocalModule localModule(File path) {
|
public LocalModule localModule(File path) {
|
||||||
return new LocalModule(path.getAbsolutePath());
|
return new LocalModule(path.getAbsolutePath());
|
||||||
|
|
@ -2141,22 +2062,6 @@ public class BaseProject extends BuildExecutor {
|
||||||
purge();
|
purge();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs the automatic download and purge of the project
|
|
||||||
* dependencies when it's enabled through {@link #autoDownloadPurge()}
|
|
||||||
* and the project isn't offline.
|
|
||||||
* <p>
|
|
||||||
* The dependencies are only refreshed when the dependency cache is
|
|
||||||
* stale, so that repeated invocations are cheap.
|
|
||||||
*
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public void performAutoDownloadPurgeIfEnabled() {
|
|
||||||
if (!offline() && autoDownloadPurge()) {
|
|
||||||
performAutoDownloadPurge();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void performAutoDownloadPurge() {
|
private void performAutoDownloadPurge() {
|
||||||
var resolution = new VersionResolution(properties());
|
var resolution = new VersionResolution(properties());
|
||||||
var cache = new BldCache(libBldDirectory(), resolution);
|
var cache = new BldCache(libBldDirectory(), resolution);
|
||||||
|
|
@ -2180,12 +2085,6 @@ public class BaseProject extends BuildExecutor {
|
||||||
var remainingArguments = new ArrayList<>(List.of(arguments));
|
var remainingArguments = new ArrayList<>(List.of(arguments));
|
||||||
var auto = remainingArguments.remove(AUTO_DOWNLOAD_PURGE_OPTION);
|
var auto = remainingArguments.remove(AUTO_DOWNLOAD_PURGE_OPTION);
|
||||||
|
|
||||||
if (remainingArguments.contains(BuildExecutor.ARG_USE_STDERR)) {
|
|
||||||
// the build output is sent to standard error so that standard
|
|
||||||
// output stays free for the MCP protocol, done before the
|
|
||||||
// automatic download and purge produces any output
|
|
||||||
System.setOut(System.err);
|
|
||||||
}
|
|
||||||
if (!offline() &&
|
if (!offline() &&
|
||||||
(autoDownloadPurge() || auto)) {
|
(autoDownloadPurge() || auto)) {
|
||||||
performAutoDownloadPurge();
|
performAutoDownloadPurge();
|
||||||
|
|
|
||||||
|
|
@ -271,10 +271,6 @@ public class BldCache {
|
||||||
finger_print.append(entry.getKey());
|
finger_print.append(entry.getKey());
|
||||||
finger_print.append('\n');
|
finger_print.append('\n');
|
||||||
if (entry.getValue() != null) {
|
if (entry.getValue() != null) {
|
||||||
for (var bom : entry.getValue().boms()) {
|
|
||||||
finger_print.append(bom.toString());
|
|
||||||
finger_print.append('\n');
|
|
||||||
}
|
|
||||||
for (var dependency : entry.getValue()) {
|
for (var dependency : entry.getValue()) {
|
||||||
finger_print.append(dependency.toString());
|
finger_print.append(dependency.toString());
|
||||||
finger_print.append('\n');
|
finger_print.append('\n');
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,6 @@ public class BuildExecutor {
|
||||||
public static final String LOCAL_PROPERTIES = "local.properties";
|
public static final String LOCAL_PROPERTIES = "local.properties";
|
||||||
|
|
||||||
private static final String ARG_OFFLINE = "--offline";
|
private static final String ARG_OFFLINE = "--offline";
|
||||||
static final String ARG_USE_STDERR = "--use-stderr";
|
|
||||||
|
|
||||||
private static final String ARG_HELP1 = "--help";
|
private static final String ARG_HELP1 = "--help";
|
||||||
private static final String ARG_HELP2 = "-h";
|
private static final String ARG_HELP2 = "-h";
|
||||||
private static final String ARG_HELP3 = "-?";
|
private static final String ARG_HELP3 = "-?";
|
||||||
|
|
@ -43,7 +41,7 @@ public class BuildExecutor {
|
||||||
private static final String ARG_VERBOSE2 = "-v";
|
private static final String ARG_VERBOSE2 = "-v";
|
||||||
|
|
||||||
private final HierarchicalProperties properties_;
|
private final HierarchicalProperties properties_;
|
||||||
private List<String> arguments_ = new ArrayList<>();
|
private List<String> arguments_ = Collections.emptyList();
|
||||||
private boolean offline_ = false;
|
private boolean offline_ = false;
|
||||||
private boolean verbose_ = false;
|
private boolean verbose_ = false;
|
||||||
private Map<String, CommandDefinition> buildCommands_ = null;
|
private Map<String, CommandDefinition> buildCommands_ = null;
|
||||||
|
|
@ -140,65 +138,18 @@ public class BuildExecutor {
|
||||||
return offline_;
|
return offline_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes whether the bld execution is intended to be offline.
|
|
||||||
*
|
|
||||||
* @param offline {@code true} if the execution is intended to be offline;
|
|
||||||
* or {@code false} otherwise
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public void offline(boolean offline) {
|
|
||||||
offline_ = offline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the bld execution should output detailed information
|
* Returns whether the bld execution should output detailed information
|
||||||
* about the operations it performs.
|
* about the operations it performs.
|
||||||
*
|
*
|
||||||
* @return {@code true} if the execution is verbose;
|
* @return {@code true} if the execution is verbose;
|
||||||
* or {@code false} otherwise
|
* or {@code false} otherwise
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public boolean verbose() {
|
public boolean verbose() {
|
||||||
return verbose_;
|
return verbose_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes whether the bld execution should output detailed information
|
|
||||||
* about the operations it performs.
|
|
||||||
*
|
|
||||||
* @param verbose {@code true} if the execution is verbose;
|
|
||||||
* or {@code false} otherwise
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public void verbose(boolean verbose) {
|
|
||||||
verbose_ = verbose;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the bld execution prints out the stacktrace for
|
|
||||||
* exceptions.
|
|
||||||
*
|
|
||||||
* @return {@code true} if the stacktrace is printed;
|
|
||||||
* or {@code false} otherwise
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public boolean showStacktrace() {
|
|
||||||
return showStacktrace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes whether the bld execution prints out the stacktrace for
|
|
||||||
* exceptions.
|
|
||||||
*
|
|
||||||
* @param showStacktrace {@code true} if the stacktrace is printed;
|
|
||||||
* or {@code false} otherwise
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public void showStacktrace(boolean showStacktrace) {
|
|
||||||
this.showStacktrace = showStacktrace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the properties uses for bld execution.
|
* Returns the properties uses for bld execution.
|
||||||
*
|
*
|
||||||
|
|
@ -292,19 +243,12 @@ public class BuildExecutor {
|
||||||
show_help |= arguments_.removeAll(List.of(ARG_HELP1, ARG_HELP2, ARG_HELP3));
|
show_help |= arguments_.removeAll(List.of(ARG_HELP1, ARG_HELP2, ARG_HELP3));
|
||||||
showStacktrace = arguments_.removeAll(List.of(ARG_STACKTRACE1, ARG_STACKTRACE2));
|
showStacktrace = arguments_.removeAll(List.of(ARG_STACKTRACE1, ARG_STACKTRACE2));
|
||||||
verbose_ = arguments_.removeAll(List.of(ARG_VERBOSE1, ARG_VERBOSE2));
|
verbose_ = arguments_.removeAll(List.of(ARG_VERBOSE1, ARG_VERBOSE2));
|
||||||
if (arguments_.removeAll(List.of(ARG_USE_STDERR))) {
|
|
||||||
// the build output is sent to standard error so that standard
|
|
||||||
// output stays free for the MCP protocol, which the MCP server
|
|
||||||
// relies on to keep its protocol stream clean
|
|
||||||
System.setOut(System.err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (show_help) {
|
if (show_help) {
|
||||||
new HelpOperation(this, Collections.emptyList()).execute();
|
new HelpOperation(this, Collections.emptyList()).execute();
|
||||||
return exitStatus_;
|
return exitStatus_;
|
||||||
}
|
}
|
||||||
|
else if (arguments_.isEmpty()) {
|
||||||
if (arguments_.isEmpty()) {
|
|
||||||
showBldHelp();
|
showBldHelp();
|
||||||
return exitStatus_;
|
return exitStatus_;
|
||||||
}
|
}
|
||||||
|
|
@ -497,14 +441,47 @@ public class BuildExecutor {
|
||||||
*/
|
*/
|
||||||
public boolean executeCommand(String command)
|
public boolean executeCommand(String command)
|
||||||
throws Throwable {
|
throws Throwable {
|
||||||
var matched_command = resolveCommand(command);
|
var matched_command = command;
|
||||||
CommandDefinition definition = null;
|
var definition = buildCommands().get(command);
|
||||||
if (matched_command != null) {
|
|
||||||
if (!matched_command.equals(command) &&
|
// try to find an alias
|
||||||
!matched_command.equals(buildAliases().get(command))) {
|
if (definition == null) {
|
||||||
System.out.println("Executing matched command: " + matched_command);
|
var aliased_command = buildAliases().get(command);
|
||||||
|
if (aliased_command != null) {
|
||||||
|
matched_command = aliased_command;
|
||||||
|
definition = buildCommands().get(aliased_command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to find a match for the provided command amongst
|
||||||
|
// the ones that are known
|
||||||
|
if (definition == null) {
|
||||||
|
// try to find starting matching options
|
||||||
|
var matches = new ArrayList<>(buildCommands().keySet().stream()
|
||||||
|
.filter(c -> c.toLowerCase().startsWith(command.toLowerCase()))
|
||||||
|
.toList());
|
||||||
|
|
||||||
|
if (matches.isEmpty()) {
|
||||||
|
// try to find fuzzy matching options
|
||||||
|
var fuzzy_regexp = new StringBuilder("^.*");
|
||||||
|
for (var ch : command.toCharArray()) {
|
||||||
|
fuzzy_regexp.append("\\Q");
|
||||||
|
fuzzy_regexp.append(ch);
|
||||||
|
fuzzy_regexp.append("\\E.*");
|
||||||
|
}
|
||||||
|
fuzzy_regexp.append('$');
|
||||||
|
var fuzzy_pattern = Pattern.compile(fuzzy_regexp.toString());
|
||||||
|
matches.addAll(buildCommands().keySet().stream()
|
||||||
|
.filter(c -> fuzzy_pattern.matcher(c.toLowerCase()).matches())
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// only proceed if exactly one match was found
|
||||||
|
if (matches.size() == 1) {
|
||||||
|
matched_command = matches.get(0);
|
||||||
|
System.out.println("Executing matched command: " + matched_command);
|
||||||
|
definition = buildCommands().get(matched_command);
|
||||||
}
|
}
|
||||||
definition = buildCommands().get(matched_command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute the command if we found one
|
// execute the command if we found one
|
||||||
|
|
@ -530,54 +507,6 @@ public class BuildExecutor {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves a command name to the name of the build command that would
|
|
||||||
* be executed for it, taking the command aliases, unique name prefixes
|
|
||||||
* and unique fuzzy matches into account.
|
|
||||||
*
|
|
||||||
* @param command the command name to resolve
|
|
||||||
* @return the name of the build command that would be executed; or
|
|
||||||
* {@code null} when the name couldn't be resolved
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public String resolveCommand(String command) {
|
|
||||||
if (buildCommands().containsKey(command)) {
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to find an alias
|
|
||||||
var aliased_command = buildAliases().get(command);
|
|
||||||
if (aliased_command != null) {
|
|
||||||
return aliased_command;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to find starting matching options
|
|
||||||
var matches = new ArrayList<>(buildCommands().keySet().stream()
|
|
||||||
.filter(c -> c.toLowerCase().startsWith(command.toLowerCase()))
|
|
||||||
.toList());
|
|
||||||
|
|
||||||
if (matches.isEmpty()) {
|
|
||||||
// try to find fuzzy matching options
|
|
||||||
var fuzzy_regexp = new StringBuilder("^.*");
|
|
||||||
for (var ch : command.toCharArray()) {
|
|
||||||
fuzzy_regexp.append("\\Q");
|
|
||||||
fuzzy_regexp.append(ch);
|
|
||||||
fuzzy_regexp.append("\\E.*");
|
|
||||||
}
|
|
||||||
fuzzy_regexp.append('$');
|
|
||||||
var fuzzy_pattern = Pattern.compile(fuzzy_regexp.toString());
|
|
||||||
matches.addAll(buildCommands().keySet().stream()
|
|
||||||
.filter(c -> fuzzy_pattern.matcher(c.toLowerCase()).matches())
|
|
||||||
.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
// only resolve if exactly one match was found
|
|
||||||
if (matches.size() == 1) {
|
|
||||||
return matches.get(0);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showBldHelp() {
|
private void showBldHelp() {
|
||||||
var help = new HelpOperation(this, arguments());
|
var help = new HelpOperation(this, arguments());
|
||||||
help.executePrintWelcome();
|
help.executePrintWelcome();
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import java.net.URLConnection;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static rife.tools.HttpUtils.HEADER_AUTHORIZATION;
|
import static rife.tools.HttpUtils.HEADER_AUTHORIZATION;
|
||||||
import static rife.tools.HttpUtils.basicAuthorizationHeader;
|
import static rife.tools.HttpUtils.basicAuthorizationHeader;
|
||||||
|
|
@ -36,8 +35,6 @@ import static rife.tools.StringUtils.encodeHexLower;
|
||||||
* @since 1.5.18
|
* @since 1.5.18
|
||||||
*/
|
*/
|
||||||
public abstract class ArtifactRetriever {
|
public abstract class ArtifactRetriever {
|
||||||
private static final int TRANSFER_CHUNK_SIZE = 128 * 1024;
|
|
||||||
|
|
||||||
private final static ArtifactRetriever UNCACHED = new ArtifactRetriever() {
|
private final static ArtifactRetriever UNCACHED = new ArtifactRetriever() {
|
||||||
String getCached(RepositoryArtifact artifact) {
|
String getCached(RepositoryArtifact artifact) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -45,10 +42,6 @@ public abstract class ArtifactRetriever {
|
||||||
|
|
||||||
void cache(RepositoryArtifact artifact, String content) {
|
void cache(RepositoryArtifact artifact, String content) {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isCaching() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -70,7 +63,7 @@ public abstract class ArtifactRetriever {
|
||||||
*/
|
*/
|
||||||
public static ArtifactRetriever cachingInstance() {
|
public static ArtifactRetriever cachingInstance() {
|
||||||
return new ArtifactRetriever() {
|
return new ArtifactRetriever() {
|
||||||
private final Map<RepositoryArtifact, String> artifactCache = new ConcurrentHashMap<>();
|
private final Map<RepositoryArtifact, String> artifactCache = new HashMap<>();
|
||||||
|
|
||||||
String getCached(RepositoryArtifact artifact) {
|
String getCached(RepositoryArtifact artifact) {
|
||||||
return artifactCache.get(artifact);
|
return artifactCache.get(artifact);
|
||||||
|
|
@ -80,9 +73,6 @@ public abstract class ArtifactRetriever {
|
||||||
artifactCache.put(artifact, content);
|
artifactCache.put(artifact, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isCaching() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,8 +83,6 @@ public abstract class ArtifactRetriever {
|
||||||
|
|
||||||
abstract void cache(RepositoryArtifact artifact, String content);
|
abstract void cache(RepositoryArtifact artifact, String content);
|
||||||
|
|
||||||
abstract boolean isCaching();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the contents of an artifact as a string.
|
* Reads the contents of an artifact as a string.
|
||||||
*
|
*
|
||||||
|
|
@ -153,17 +141,17 @@ public abstract class ArtifactRetriever {
|
||||||
|
|
||||||
var download_filename = artifact.location().substring(artifact.location().lastIndexOf("/") + 1);
|
var download_filename = artifact.location().substring(artifact.location().lastIndexOf("/") + 1);
|
||||||
var download_file = new File(directory, download_filename);
|
var download_file = new File(directory, download_filename);
|
||||||
var transfer = TransferOutput.instance().start(artifact.location());
|
System.out.print("Downloading: " + artifact.location() + " ... ");
|
||||||
var status = "";
|
System.out.flush();
|
||||||
try {
|
try {
|
||||||
if (artifact.repository().isLocal()) {
|
if (artifact.repository().isLocal()) {
|
||||||
var source = new File(artifact.location());
|
var source = new File(artifact.location());
|
||||||
if (source.exists()) {
|
if (source.exists()) {
|
||||||
FileUtils.copy(source, download_file);
|
FileUtils.copy(source, download_file);
|
||||||
status = "done";
|
System.out.print("done");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
status = "not found";
|
System.out.print("not found");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -171,7 +159,7 @@ public abstract class ArtifactRetriever {
|
||||||
if (download_file.exists() && download_file.canRead()) {
|
if (download_file.exists() && download_file.canRead()) {
|
||||||
if (checkHash(artifact, download_file, ".sha256", "SHA-256") ||
|
if (checkHash(artifact, download_file, ".sha256", "SHA-256") ||
|
||||||
checkHash(artifact, download_file, ".md5", "MD5")) {
|
checkHash(artifact, download_file, ".md5", "MD5")) {
|
||||||
status = "exists";
|
System.out.print("exists");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,29 +170,23 @@ public abstract class ArtifactRetriever {
|
||||||
HEADER_AUTHORIZATION,
|
HEADER_AUTHORIZATION,
|
||||||
basicAuthorizationHeader(artifact.repository().username(), artifact.repository().password()));
|
basicAuthorizationHeader(artifact.repository().username(), artifact.repository().password()));
|
||||||
}
|
}
|
||||||
var content_length = connection.getContentLengthLong();
|
|
||||||
try (var input_stream = connection.getInputStream()) {
|
try (var input_stream = connection.getInputStream()) {
|
||||||
var readableByteChannel = Channels.newChannel(input_stream);
|
var readableByteChannel = Channels.newChannel(input_stream);
|
||||||
try (var fileOutputStream = new FileOutputStream(download_file)) {
|
try (var fileOutputStream = new FileOutputStream(download_file)) {
|
||||||
var fileChannel = fileOutputStream.getChannel();
|
var fileChannel = fileOutputStream.getChannel();
|
||||||
var position = 0L;
|
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
|
||||||
long transferred;
|
|
||||||
while ((transferred = fileChannel.transferFrom(readableByteChannel, position, TRANSFER_CHUNK_SIZE)) > 0) {
|
|
||||||
position += transferred;
|
|
||||||
transfer.progress(position, content_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
status = "done";
|
System.out.print("done");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
status = "not found";
|
System.out.print("not found");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
transfer.finish(status);
|
System.out.println();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.dependencies;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains the information required to describe a bill of materials (BOM)
|
|
||||||
* in the build system.
|
|
||||||
* <p>
|
|
||||||
* A BOM is a POM whose dependency management section supplies versions
|
|
||||||
* during dependency resolution: dependencies that are declared without a
|
|
||||||
* version take the BOM's version, and transitive dependencies that match a
|
|
||||||
* BOM entry are pinned to the BOM's version. Versions that are explicitly
|
|
||||||
* declared in the build file, and versions provided through the
|
|
||||||
* {@code bld.override} property, always take precedence over a BOM.
|
|
||||||
* <p>
|
|
||||||
* BOMs don't transfer any artifacts, they only participate in resolution.
|
|
||||||
*
|
|
||||||
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public class Bom extends Dependency {
|
|
||||||
public Bom(String groupId, String artifactId) {
|
|
||||||
this(groupId, artifactId, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bom(String groupId, String artifactId, Version version) {
|
|
||||||
super(groupId, artifactId, version, null, TYPE_BOM);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Pattern BOM_PATTERN = Pattern.compile("^(?<groupId>[^:@]+):(?<artifactId>[^:@]+)(?::(?<version>[^:@]+))?(?:@(?:bom|pom))?$");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a BOM from a string representation.
|
|
||||||
* The format is {@code groupId:artifactId:version}.
|
|
||||||
* The {@code version} is optional, and an optional {@code @bom} or
|
|
||||||
* {@code @pom} type suffix is accepted. BOMs can't have classifiers,
|
|
||||||
* strings that contain one are rejected.
|
|
||||||
* <p>
|
|
||||||
* If the string can't be successfully parsed, {@code null} will be returned.
|
|
||||||
*
|
|
||||||
* @param bom the BOM string to parse
|
|
||||||
* @return a parsed instance of {@code Bom}; or
|
|
||||||
* {@code null} when the string couldn't be parsed
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static Bom parse(String bom) {
|
|
||||||
if (bom == null || bom.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var matcher = BOM_PATTERN.matcher(bom);
|
|
||||||
if (!matcher.matches()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var groupId = matcher.group("groupId");
|
|
||||||
var artifactId = matcher.group("artifactId");
|
|
||||||
var version = Version.parse(matcher.group("version"));
|
|
||||||
|
|
||||||
return new Bom(groupId, artifactId, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -40,14 +40,6 @@ public class Dependency {
|
||||||
// see https://github.com/apache/maven/blob/maven-4.0.0-beta-3/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java
|
// see https://github.com/apache/maven/blob/maven-4.0.0-beta-3/api/maven-api-core/src/main/java/org/apache/maven/api/Type.java
|
||||||
public static final String TYPE_MODULAR_JAR = "modular-jar";
|
public static final String TYPE_MODULAR_JAR = "modular-jar";
|
||||||
|
|
||||||
/**
|
|
||||||
* The dependency type name for a bill of materials POM whose
|
|
||||||
* dependency management supplies versions during resolution.
|
|
||||||
*
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static final String TYPE_BOM = "bom";
|
|
||||||
|
|
||||||
private final String groupId_;
|
private final String groupId_;
|
||||||
private final String artifactId_;
|
private final String artifactId_;
|
||||||
private final Version version_;
|
private final Version version_;
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ public class DependencyResolver {
|
||||||
var pom_dependencies = getMavenPom(dependency_).getDependencies(scopes);
|
var pom_dependencies = getMavenPom(dependency_).getDependencies(scopes);
|
||||||
var result = new DependencySet();
|
var result = new DependencySet();
|
||||||
for (var dependency : pom_dependencies) {
|
for (var dependency : pom_dependencies) {
|
||||||
result.add(resolution_.overrideTransitiveDependency(dependency.convertToDependency()));
|
result.add(resolution_.overrideDependency(dependency.convertToDependency()));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -121,17 +121,8 @@ public class DependencyResolver {
|
||||||
* @since 1.5
|
* @since 1.5
|
||||||
*/
|
*/
|
||||||
public DependencySet getAllDependencies(Scope... scopes) {
|
public DependencySet getAllDependencies(Scope... scopes) {
|
||||||
var prefetcher = new PomPrefetcher(resolution_, retriever_, repositories_);
|
|
||||||
try {
|
|
||||||
return getAllDependencies(prefetcher, scopes);
|
|
||||||
} finally {
|
|
||||||
prefetcher.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DependencySet getAllDependencies(PomPrefetcher prefetcher, Scope... scopes) {
|
|
||||||
var result = new DependencySet();
|
var result = new DependencySet();
|
||||||
var overridden = resolution_.overrideDeclaredDependency(dependency_);
|
var overridden = resolution_.overrideDependency(dependency_);
|
||||||
result.add(overridden);
|
result.add(overridden);
|
||||||
|
|
||||||
var dependency_queue = new ArrayList<PomDependency>();
|
var dependency_queue = new ArrayList<PomDependency>();
|
||||||
|
|
@ -147,11 +138,8 @@ public class DependencyResolver {
|
||||||
next_dependencies.removeIf(it -> matchesExclusions(exclusion_context, it));
|
next_dependencies.removeIf(it -> matchesExclusions(exclusion_context, it));
|
||||||
// add all next dependencies to the queue
|
// add all next dependencies to the queue
|
||||||
dependency_queue.addAll(next_dependencies);
|
dependency_queue.addAll(next_dependencies);
|
||||||
// speculatively retrieve the POMs of the queued dependencies in
|
|
||||||
// parallel so that they are cached when they're processed in order
|
|
||||||
prefetcher.prefetch(next_dependencies);
|
|
||||||
|
|
||||||
// unless we find the next set of dependencies to add, stop resolving
|
// unless we find a next set of dependencies to add, stop resolving
|
||||||
parent = null;
|
parent = null;
|
||||||
next_dependencies = null;
|
next_dependencies = null;
|
||||||
|
|
||||||
|
|
@ -159,7 +147,7 @@ public class DependencyResolver {
|
||||||
// part of the results yet
|
// part of the results yet
|
||||||
while (!dependency_queue.isEmpty()) {
|
while (!dependency_queue.isEmpty()) {
|
||||||
var candidate = dependency_queue.remove(0);
|
var candidate = dependency_queue.remove(0);
|
||||||
var dependency = resolution_.overrideTransitiveDependency(candidate.convertToDependency());
|
var dependency = resolution_.overrideDependency(candidate.convertToDependency());
|
||||||
if (!result.contains(dependency)) {
|
if (!result.contains(dependency)) {
|
||||||
result.add(dependency);
|
result.add(dependency);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ package rife.bld.dependencies;
|
||||||
|
|
||||||
import rife.ioc.HierarchicalProperties;
|
import rife.ioc.HierarchicalProperties;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -39,8 +37,7 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Includes all the dependencies, local dependencies, local modules and
|
* Includes all the dependencies from another dependency scope map.
|
||||||
* BOMs from another dependency scope map.
|
|
||||||
*
|
*
|
||||||
* @param other the other map to include dependencies from
|
* @param other the other map to include dependencies from
|
||||||
* @since 1.5
|
* @since 1.5
|
||||||
|
|
@ -52,7 +49,7 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
||||||
dependencies = new DependencySet();
|
dependencies = new DependencySet();
|
||||||
put(entry.getKey(), dependencies);
|
put(entry.getKey(), dependencies);
|
||||||
}
|
}
|
||||||
dependencies.include(entry.getValue());
|
dependencies.addAll(entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,150 +65,6 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
||||||
return computeIfAbsent(scope, k -> new DependencySet());
|
return computeIfAbsent(scope, k -> new DependencySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the BOMs that apply to the version resolution of a particular
|
|
||||||
* scope.
|
|
||||||
* <p>
|
|
||||||
* BOMs follow the same scope composition as the classpaths: the
|
|
||||||
* {@code compile} scope BOMs also apply to the {@code provided},
|
|
||||||
* {@code runtime} and {@code test} scopes, and the {@code runtime} and
|
|
||||||
* {@code provided} scope BOMs also apply to the {@code test} scope.
|
|
||||||
* The BOMs are returned with the scope's own BOMs first, followed by
|
|
||||||
* the inherited ones in the standard scope order, for the {@code test}
|
|
||||||
* scope that is {@code test}, {@code compile}, {@code provided},
|
|
||||||
* {@code runtime}. This order determines their precedence when several
|
|
||||||
* manage the same dependency: a BOM declared in the scope where a
|
|
||||||
* dependency is used takes precedence over a BOM that is inherited from
|
|
||||||
* another scope, and among the inherited BOMs the more fundamental
|
|
||||||
* scope wins.
|
|
||||||
* <p>
|
|
||||||
* The {@code standalone} scope only uses its own BOMs, and its BOMs
|
|
||||||
* deliberately never apply to any other scope.
|
|
||||||
*
|
|
||||||
* @param scope the scope to retrieve the applicable BOMs for
|
|
||||||
* @return the BOMs that apply to the scope's version resolution
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public List<Bom> effectiveBoms(Scope scope) {
|
|
||||||
var boms = new ArrayList<Bom>();
|
|
||||||
for (var bom_scope : bomScopes(scope)) {
|
|
||||||
var scoped_dependencies = get(bom_scope);
|
|
||||||
if (scoped_dependencies != null) {
|
|
||||||
boms.addAll(scoped_dependencies.boms());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return boms;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Scope[] bomScopes(Scope scope) {
|
|
||||||
return switch (scope) {
|
|
||||||
case provided -> new Scope[]{Scope.provided, Scope.compile};
|
|
||||||
case runtime -> new Scope[]{Scope.runtime, Scope.compile};
|
|
||||||
case test -> new Scope[]{Scope.test, Scope.compile, Scope.provided, Scope.runtime};
|
|
||||||
default -> new Scope[]{scope};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the version-less dependencies that are not covered by the
|
|
||||||
* BOMs that apply to their scope.
|
|
||||||
* <p>
|
|
||||||
* Scopes that no BOMs apply to are not considered, their
|
|
||||||
* version-less dependencies simply resolve to the latest version.
|
|
||||||
* Dependencies whose version is supplied by a {@code bld.override}
|
|
||||||
* property are not reported either.
|
|
||||||
*
|
|
||||||
* @param properties the properties to use to get artifacts
|
|
||||||
* @param retriever the retriever to use to get artifacts
|
|
||||||
* @param repositories the repositories to use for the BOM resolution
|
|
||||||
* @return the version-less dependencies that no applicable BOM covers
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public List<Dependency> versionlessDependenciesWithoutBom(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
|
||||||
var result = new ArrayList<Dependency>();
|
|
||||||
for (var entry : entrySet()) {
|
|
||||||
var effective_boms = effectiveBoms(entry.getKey());
|
|
||||||
if (effective_boms.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var resolution = new VersionResolution(properties, retriever, repositories, effective_boms);
|
|
||||||
for (var dependency : entry.getValue()) {
|
|
||||||
if (dependency.version().equals(VersionNumber.UNKNOWN) &&
|
|
||||||
!resolution.coversDependency(dependency) &&
|
|
||||||
!resolution.versionOverrides().containsKey(dependency.toArtifactString()) &&
|
|
||||||
!result.contains(dependency)) {
|
|
||||||
result.add(dependency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the version conflicts between the BOMs that apply to the
|
|
||||||
* scopes, where more than one applicable BOM manages the same
|
|
||||||
* dependency at a different version.
|
|
||||||
* <p>
|
|
||||||
* Each conflict is reported once, the versions are keyed by their BOM
|
|
||||||
* in precedence order so that the first is the version that is used.
|
|
||||||
*
|
|
||||||
* @param properties the properties to use to get artifacts
|
|
||||||
* @param retriever the retriever to use to get artifacts
|
|
||||||
* @param repositories the repositories to use for the BOM resolution
|
|
||||||
* @return the version conflicts between the applicable BOMs
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public List<VersionResolution.BomVersionConflict> bomVersionConflicts(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
|
||||||
var result = new ArrayList<VersionResolution.BomVersionConflict>();
|
|
||||||
var seen = new HashSet<String>();
|
|
||||||
for (var scope : keySet()) {
|
|
||||||
var effective_boms = effectiveBoms(scope);
|
|
||||||
if (effective_boms.size() < 2) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (var conflict : VersionResolution.resolveBomVersionConflicts(properties, retriever, repositories, effective_boms)) {
|
|
||||||
if (seen.add(conflict.dependency() + conflict.bomVersions())) {
|
|
||||||
result.add(conflict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the declared dependencies whose explicit version differs
|
|
||||||
* from the version that a BOM applying to their scope manages them at.
|
|
||||||
* <p>
|
|
||||||
* The declared version is used for the dependency itself, its
|
|
||||||
* transitive dependencies still resolve to the versions that the BOMs
|
|
||||||
* manage. Dependencies whose version is supplied by a
|
|
||||||
* {@code bld.override} property are not reported. Each difference is
|
|
||||||
* reported once.
|
|
||||||
*
|
|
||||||
* @param properties the properties to use to get artifacts
|
|
||||||
* @param retriever the retriever to use to get artifacts
|
|
||||||
* @param repositories the repositories to use for the BOM resolution
|
|
||||||
* @return the version differences between the declared dependencies
|
|
||||||
* and the applicable BOMs
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public List<VersionResolution.DeclaredVersionConflict> declaredVersionConflicts(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
|
||||||
var result = new ArrayList<VersionResolution.DeclaredVersionConflict>();
|
|
||||||
var seen = new HashSet<String>();
|
|
||||||
for (var entry : entrySet()) {
|
|
||||||
var effective_boms = effectiveBoms(entry.getKey());
|
|
||||||
if (effective_boms.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (var conflict : VersionResolution.resolveDeclaredVersionConflicts(properties, retriever, repositories, effective_boms, entry.getValue())) {
|
|
||||||
if (seen.add(conflict.dependency() + conflict.declaredVersion() + conflict.bom() + conflict.bomVersion())) {
|
|
||||||
result.add(conflict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the transitive set of dependencies that would be used for the compile scope in a project.
|
* Returns the transitive set of dependencies that would be used for the compile scope in a project.
|
||||||
*
|
*
|
||||||
|
|
@ -223,7 +76,6 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
||||||
*/
|
*/
|
||||||
public DependencySet resolveCompileDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
public DependencySet resolveCompileDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
||||||
return resolveScopedDependencies(properties, retriever, repositories,
|
return resolveScopedDependencies(properties, retriever, repositories,
|
||||||
Scope.compile,
|
|
||||||
new Scope[]{Scope.compile},
|
new Scope[]{Scope.compile},
|
||||||
new Scope[]{Scope.compile},
|
new Scope[]{Scope.compile},
|
||||||
null);
|
null);
|
||||||
|
|
@ -240,7 +92,6 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
||||||
*/
|
*/
|
||||||
public DependencySet resolveProvidedDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
public DependencySet resolveProvidedDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
||||||
return resolveScopedDependencies(properties, retriever, repositories,
|
return resolveScopedDependencies(properties, retriever, repositories,
|
||||||
Scope.provided,
|
|
||||||
new Scope[]{Scope.provided},
|
new Scope[]{Scope.provided},
|
||||||
new Scope[]{Scope.compile, Scope.runtime},
|
new Scope[]{Scope.compile, Scope.runtime},
|
||||||
null);
|
null);
|
||||||
|
|
@ -257,7 +108,6 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
||||||
*/
|
*/
|
||||||
public DependencySet resolveRuntimeDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
public DependencySet resolveRuntimeDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
||||||
return resolveScopedDependencies(properties, retriever, repositories,
|
return resolveScopedDependencies(properties, retriever, repositories,
|
||||||
Scope.runtime,
|
|
||||||
new Scope[]{Scope.compile, Scope.runtime},
|
new Scope[]{Scope.compile, Scope.runtime},
|
||||||
new Scope[]{Scope.compile, Scope.runtime},
|
new Scope[]{Scope.compile, Scope.runtime},
|
||||||
resolveCompileDependencies(properties, retriever, repositories));
|
resolveCompileDependencies(properties, retriever, repositories));
|
||||||
|
|
@ -274,7 +124,6 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
||||||
*/
|
*/
|
||||||
public DependencySet resolveStandaloneDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
public DependencySet resolveStandaloneDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
||||||
return resolveScopedDependencies(properties, retriever, repositories,
|
return resolveScopedDependencies(properties, retriever, repositories,
|
||||||
Scope.standalone,
|
|
||||||
new Scope[]{Scope.standalone},
|
new Scope[]{Scope.standalone},
|
||||||
new Scope[]{Scope.compile, Scope.runtime},
|
new Scope[]{Scope.compile, Scope.runtime},
|
||||||
null);
|
null);
|
||||||
|
|
@ -291,22 +140,22 @@ public class DependencyScopes extends LinkedHashMap<Scope, DependencySet> {
|
||||||
*/
|
*/
|
||||||
public DependencySet resolveTestDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
public DependencySet resolveTestDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories) {
|
||||||
return resolveScopedDependencies(properties, retriever, repositories,
|
return resolveScopedDependencies(properties, retriever, repositories,
|
||||||
Scope.test,
|
|
||||||
new Scope[]{Scope.test},
|
new Scope[]{Scope.test},
|
||||||
new Scope[]{Scope.compile, Scope.runtime},
|
new Scope[]{Scope.compile, Scope.runtime},
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DependencySet resolveScopedDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories, Scope scope, Scope[] resolvedScopes, Scope[] transitiveScopes, DependencySet excluded) {
|
private DependencySet resolveScopedDependencies(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories, Scope[] resolvedScopes, Scope[] transitiveScopes, DependencySet excluded) {
|
||||||
var roots = new ArrayList<Dependency>();
|
var resolution = new VersionResolution(properties);
|
||||||
for (var resolved_scope : resolvedScopes) {
|
var dependencies = new DependencySet();
|
||||||
var scoped_dependencies = get(resolved_scope);
|
for (var scope : resolvedScopes) {
|
||||||
|
var scoped_dependencies = get(scope);
|
||||||
if (scoped_dependencies != null) {
|
if (scoped_dependencies != null) {
|
||||||
roots.addAll(scoped_dependencies);
|
for (var dependency : scoped_dependencies) {
|
||||||
|
dependencies.addAll(new DependencyResolver(resolution, retriever, repositories, dependency).getAllDependencies(transitiveScopes));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var resolution = new VersionResolution(properties, retriever, repositories, effectiveBoms(scope));
|
|
||||||
var dependencies = new ParallelDependencyResolver(resolution, retriever, repositories).resolveAllDependencies(roots, transitiveScopes);
|
|
||||||
if (excluded != null) {
|
if (excluded != null) {
|
||||||
dependencies.removeAll(excluded);
|
dependencies.removeAll(excluded);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ public class DependencySet extends AbstractSet<Dependency> implements Set<Depend
|
||||||
private final Map<Dependency, Dependency> dependencies_ = new LinkedHashMap<>();
|
private final Map<Dependency, Dependency> dependencies_ = new LinkedHashMap<>();
|
||||||
private final Set<LocalDependency> localDependencies_ = new LinkedHashSet<>();
|
private final Set<LocalDependency> localDependencies_ = new LinkedHashSet<>();
|
||||||
private final Set<LocalModule> localModules_ = new LinkedHashSet<>();
|
private final Set<LocalModule> localModules_ = new LinkedHashSet<>();
|
||||||
private final Set<Bom> boms_ = new LinkedHashSet<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an empty dependency set.
|
* Creates an empty dependency set.
|
||||||
|
|
@ -41,23 +40,7 @@ public class DependencySet extends AbstractSet<Dependency> implements Set<Depend
|
||||||
* @since 1.5
|
* @since 1.5
|
||||||
*/
|
*/
|
||||||
public DependencySet(DependencySet other) {
|
public DependencySet(DependencySet other) {
|
||||||
include(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Includes all the dependencies, local dependencies, local modules and
|
|
||||||
* BOMs from another dependency set.
|
|
||||||
*
|
|
||||||
* @param other the other set to include
|
|
||||||
* @return this dependency set instance
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public DependencySet include(DependencySet other) {
|
|
||||||
addAll(other);
|
addAll(other);
|
||||||
localDependencies_.addAll(other.localDependencies_);
|
|
||||||
localModules_.addAll(other.localModules_);
|
|
||||||
boms_.addAll(other.boms_);
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -98,32 +81,7 @@ public class DependencySet extends AbstractSet<Dependency> implements Set<Depend
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Includes a bill of materials in the dependency set.
|
* Includes a local module into the dependency set.
|
||||||
* <p>
|
|
||||||
* BOMs aren't transferred, their dependency management sections supply
|
|
||||||
* versions during the resolution of this dependency set.
|
|
||||||
*
|
|
||||||
* @param bom the BOM to include
|
|
||||||
* @return this dependency set instance
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public DependencySet include(Bom bom) {
|
|
||||||
boms_.add(bom);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the bills of materials.
|
|
||||||
*
|
|
||||||
* @return the set of BOMs
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public Set<Bom> boms() {
|
|
||||||
return boms_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Includes a local module in the dependency set.
|
|
||||||
* <p>
|
* <p>
|
||||||
* Local modules aren't resolved and point to a location on
|
* Local modules aren't resolved and point to a location on
|
||||||
* the file system.
|
* the file system.
|
||||||
|
|
@ -170,11 +128,6 @@ public class DependencySet extends AbstractSet<Dependency> implements Set<Depend
|
||||||
* including other classifiers.
|
* including other classifiers.
|
||||||
* <p>
|
* <p>
|
||||||
* The destination directory must exist and be writable.
|
* The destination directory must exist and be writable.
|
||||||
* <p>
|
|
||||||
* The artifacts of different dependencies are transferred in parallel,
|
|
||||||
* the {@value VersionResolution#PROPERTY_TRANSFER_PARALLELISM} property
|
|
||||||
* can be used to change the number of simultaneous transfers, setting it
|
|
||||||
* to {@code 1} makes the transfers sequential.
|
|
||||||
*
|
*
|
||||||
* @param resolution the version resolution state that can be cached
|
* @param resolution the version resolution state that can be cached
|
||||||
* @param retriever the retriever to use to get artifacts
|
* @param retriever the retriever to use to get artifacts
|
||||||
|
|
@ -187,15 +140,48 @@ public class DependencySet extends AbstractSet<Dependency> implements Set<Depend
|
||||||
* @since 2.1
|
* @since 2.1
|
||||||
*/
|
*/
|
||||||
public List<RepositoryArtifact> transferIntoDirectory(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories, File directory, File modulesDirectory, String... classifiers) {
|
public List<RepositoryArtifact> transferIntoDirectory(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories, File directory, File modulesDirectory, String... classifiers) {
|
||||||
return new DependencyTransferBatch()
|
var result = new ArrayList<RepositoryArtifact>();
|
||||||
.add(this, directory, modulesDirectory, classifiers)
|
for (var dependency : this) {
|
||||||
.transfer(resolution, retriever, repositories);
|
var transfer_directory = directory;
|
||||||
|
if (dependency.isModularJar()) {
|
||||||
|
if (modulesDirectory == null) {
|
||||||
|
throw new DependencyTransferException(dependency, "modules directory is not provided");
|
||||||
|
}
|
||||||
|
transfer_directory = modulesDirectory;
|
||||||
|
}
|
||||||
|
else if (directory == null) {
|
||||||
|
throw new DependencyTransferException(dependency, "artifacts directory is not provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transfer_directory.exists()) {
|
||||||
|
if (!transfer_directory.mkdirs()) {
|
||||||
|
throw new DependencyTransferException(dependency, transfer_directory, "couldn't create directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var artifact = new DependencyResolver(resolution, retriever, repositories, dependency).transferIntoDirectory(transfer_directory);
|
||||||
|
if (artifact != null) {
|
||||||
|
result.add(artifact);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (classifiers != null) {
|
||||||
|
for (var classifier : classifiers) {
|
||||||
|
if (classifier != null && !dependency.excludedClassifiers().contains(classifier)) {
|
||||||
|
var classifier_artifact = new DependencyResolver(resolution, retriever, repositories, dependency.withClassifier(classifier)).transferIntoDirectory(transfer_directory);
|
||||||
|
if (classifier_artifact != null) {
|
||||||
|
result.add(classifier_artifact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the dependency that was stored in the set.
|
* Returns the dependency that was stored in the set.
|
||||||
* <p>
|
* <p>
|
||||||
* The version can be different from the dependency passed in, and this
|
* The version can be different from the dependency passed in and this
|
||||||
* method can be used to look up the actual version of the dependency in the set.
|
* method can be used to look up the actual version of the dependency in the set.
|
||||||
*
|
*
|
||||||
* @param dependency the dependency to look for
|
* @param dependency the dependency to look for
|
||||||
|
|
@ -220,8 +206,11 @@ public class DependencySet extends AbstractSet<Dependency> implements Set<Depend
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public String generateTransitiveDependencyTree(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories, Scope... scopes) {
|
public String generateTransitiveDependencyTree(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories, Scope... scopes) {
|
||||||
resolution = resolution.withBoms(retriever, repositories, boms_);
|
var compile_dependencies = new DependencySet();
|
||||||
return new ParallelDependencyResolver(resolution, retriever, repositories).resolveAllDependencies(this, scopes).generateDependencyTree();
|
for (var dependency : this) {
|
||||||
|
compile_dependencies.addAll(new DependencyResolver(resolution, retriever, repositories, dependency).getAllDependencies(scopes));
|
||||||
|
}
|
||||||
|
return compile_dependencies.generateDependencyTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.dependencies;
|
|
||||||
|
|
||||||
import rife.bld.dependencies.exceptions.DependencyTransferException;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collects the artifact transfers of one or more dependency sets so that
|
|
||||||
* they can be transferred together in a single parallel batch, instead of
|
|
||||||
* separate consecutive batches per dependency set.
|
|
||||||
* <p>
|
|
||||||
* The batch itself is a passive collector, the resolution context is only
|
|
||||||
* provided when the transfers are {@linkplain #transfer performed}. The
|
|
||||||
* parallelism is determined by {@link VersionResolution#transferParallelism()},
|
|
||||||
* setting it to {@code 1} makes the transfers sequential. Identical
|
|
||||||
* transfers into the same directory are only performed once.
|
|
||||||
*
|
|
||||||
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public class DependencyTransferBatch {
|
|
||||||
private record TransferRequest(Dependency dependency, File directory, String[] classifiers) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<TransferRequest> requests_ = new ArrayList<>();
|
|
||||||
private final Set<String> transferTargets_ = new HashSet<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the artifact transfers for a dependency set to this batch.
|
|
||||||
* <p>
|
|
||||||
* The destination directory must exist and be writable.
|
|
||||||
*
|
|
||||||
* @param dependencies the dependencies whose artifacts to transfer
|
|
||||||
* @param directory the directory to transfer the artifacts into
|
|
||||||
* @param modulesDirectory the directory to download the modules into
|
|
||||||
* @param classifiers the additional classifiers to transfer
|
|
||||||
* @return this batch instance
|
|
||||||
* @throws DependencyTransferException when the transfer couldn't be prepared
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public DependencyTransferBatch add(DependencySet dependencies, File directory, File modulesDirectory, String... classifiers) {
|
|
||||||
for (var dependency : dependencies) {
|
|
||||||
var transfer_directory = directory;
|
|
||||||
if (dependency.isModularJar()) {
|
|
||||||
if (modulesDirectory == null) {
|
|
||||||
throw new DependencyTransferException(dependency, "modules directory is not provided");
|
|
||||||
}
|
|
||||||
transfer_directory = modulesDirectory;
|
|
||||||
}
|
|
||||||
else if (directory == null) {
|
|
||||||
throw new DependencyTransferException(dependency, "artifacts directory is not provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!transfer_directory.exists()) {
|
|
||||||
if (!transfer_directory.mkdirs()) {
|
|
||||||
throw new DependencyTransferException(dependency, transfer_directory, "couldn't create directory");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip transfers that are already batched for the same directory
|
|
||||||
if (!transferTargets_.add(dependency + " -> " + transfer_directory.getAbsolutePath())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
requests_.add(new TransferRequest(dependency, transfer_directory, classifiers));
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs all the collected artifact transfers in a single parallel
|
|
||||||
* batch, in the order they were added.
|
|
||||||
* <p>
|
|
||||||
* This empties the batch, transfers can be collected and transferred
|
|
||||||
* again with the same instance.
|
|
||||||
*
|
|
||||||
* @param resolution the version resolution state that can be cached
|
|
||||||
* @param retriever the retriever to use to get artifacts
|
|
||||||
* @param repositories the repositories to use for the transfer
|
|
||||||
* @return the list of artifacts that were transferred successfully
|
|
||||||
* @throws DependencyTransferException when an error occurred during the transfer
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public List<RepositoryArtifact> transfer(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories) {
|
|
||||||
final var repos = (repositories == null ? List.<Repository>of() : repositories);
|
|
||||||
try {
|
|
||||||
var transfers = new ArrayList<Supplier<List<RepositoryArtifact>>>(requests_.size());
|
|
||||||
for (var request : requests_) {
|
|
||||||
transfers.add(() -> {
|
|
||||||
var artifacts = new ArrayList<RepositoryArtifact>();
|
|
||||||
var artifact = new DependencyResolver(resolution, retriever, repos, request.dependency()).transferIntoDirectory(request.directory());
|
|
||||||
if (artifact != null) {
|
|
||||||
artifacts.add(artifact);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.classifiers() != null) {
|
|
||||||
for (var classifier : request.classifiers()) {
|
|
||||||
if (classifier != null && !request.dependency().excludedClassifiers().contains(classifier)) {
|
|
||||||
var classifier_artifact = new DependencyResolver(resolution, retriever, repos, request.dependency().withClassifier(classifier)).transferIntoDirectory(request.directory());
|
|
||||||
if (classifier_artifact != null) {
|
|
||||||
artifacts.add(classifier_artifact);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return artifacts;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = new ArrayList<RepositoryArtifact>();
|
|
||||||
for (var artifacts : ParallelExecution.execute(transfers, resolution.transferParallelism())) {
|
|
||||||
result.addAll(artifacts);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} finally {
|
|
||||||
requests_.clear();
|
|
||||||
transferTargets_.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.dependencies;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves multiple dependencies in parallel within a list of
|
|
||||||
* Maven-compatible repositories.
|
|
||||||
* <p>
|
|
||||||
* The parallelism is determined by {@link VersionResolution#resolutionParallelism()},
|
|
||||||
* setting it to {@code 1} makes the resolution sequential. The results are
|
|
||||||
* always identical to resolving each dependency sequentially with a
|
|
||||||
* {@link DependencyResolver}, in the same order.
|
|
||||||
*
|
|
||||||
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public class ParallelDependencyResolver {
|
|
||||||
private final VersionResolution resolution_;
|
|
||||||
private final ArtifactRetriever retriever_;
|
|
||||||
private final List<Repository> repositories_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new parallel resolver.
|
|
||||||
* <p>
|
|
||||||
* The repositories will be checked in the order they're listed.
|
|
||||||
*
|
|
||||||
* @param resolution the version resolution state that can be cached
|
|
||||||
* @param retriever the retriever to use to get artifacts
|
|
||||||
* @param repositories the repositories to use for the resolution
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public ParallelDependencyResolver(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories) {
|
|
||||||
resolution_ = resolution;
|
|
||||||
retriever_ = retriever;
|
|
||||||
if (repositories == null) {
|
|
||||||
repositories = List.of();
|
|
||||||
}
|
|
||||||
repositories_ = repositories;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the transitive dependencies of multiple root dependencies,
|
|
||||||
* merging the results in the order of the provided roots.
|
|
||||||
* <p>
|
|
||||||
* The roots are resolved in parallel while a shared prefetcher
|
|
||||||
* speculatively warms the retriever cache across all of them.
|
|
||||||
*
|
|
||||||
* @param roots the root dependencies to resolve
|
|
||||||
* @param scopes the scopes to return the transitive dependencies for
|
|
||||||
* @return the merged transitive dependencies of all the roots
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public DependencySet resolveAllDependencies(Collection<Dependency> roots, Scope... scopes) {
|
|
||||||
var result = new DependencySet();
|
|
||||||
if (roots.isEmpty()) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
var prefetcher = new PomPrefetcher(resolution_, retriever_, repositories_);
|
|
||||||
try {
|
|
||||||
var resolutions = new ArrayList<Supplier<DependencySet>>(roots.size());
|
|
||||||
for (var root : roots) {
|
|
||||||
resolutions.add(() -> new DependencyResolver(resolution_, retriever_, repositories_, root).getAllDependencies(prefetcher, scopes));
|
|
||||||
}
|
|
||||||
for (var dependencies : ParallelExecution.execute(resolutions, resolution_.resolutionParallelism())) {
|
|
||||||
result.addAll(dependencies);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
prefetcher.shutdown();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the latest versions of multiple dependencies, returning them
|
|
||||||
* in the same order as the provided dependencies.
|
|
||||||
*
|
|
||||||
* @param dependencies the dependencies to resolve the latest versions of
|
|
||||||
* @return the latest versions in the order of the provided dependencies
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public List<Version> resolveLatestVersions(List<Dependency> dependencies) {
|
|
||||||
var resolutions = new ArrayList<Supplier<Version>>(dependencies.size());
|
|
||||||
for (var dependency : dependencies) {
|
|
||||||
resolutions.add(() -> new DependencyResolver(resolution_, retriever_, repositories_, dependency).latestVersion());
|
|
||||||
}
|
|
||||||
return ParallelExecution.execute(resolutions, resolution_.resolutionParallelism());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the version resolution state that can be cached.
|
|
||||||
*
|
|
||||||
* @return the version resolution state
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public VersionResolution resolution() {
|
|
||||||
return resolution_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the repositories that are used by this resolver.
|
|
||||||
*
|
|
||||||
* @return the resolver's repositories
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public List<Repository> repositories() {
|
|
||||||
return repositories_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.dependencies;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes tasks in parallel while preserving the order of their results
|
|
||||||
* and the sequential semantics of failures: the first task that fails in
|
|
||||||
* order will have its exception rethrown.
|
|
||||||
*
|
|
||||||
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
final class ParallelExecution {
|
|
||||||
private ParallelExecution() {
|
|
||||||
}
|
|
||||||
|
|
||||||
static <T> List<T> execute(List<Supplier<T>> tasks, int parallelism) {
|
|
||||||
var result = new ArrayList<T>(tasks.size());
|
|
||||||
|
|
||||||
parallelism = Math.min(tasks.size(), parallelism);
|
|
||||||
if (parallelism <= 1) {
|
|
||||||
for (var task : tasks) {
|
|
||||||
result.add(task.get());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
var executor = Executors.newFixedThreadPool(parallelism);
|
|
||||||
try {
|
|
||||||
var futures = new ArrayList<Future<T>>(tasks.size());
|
|
||||||
for (var task : tasks) {
|
|
||||||
futures.add(executor.submit(task::get));
|
|
||||||
}
|
|
||||||
for (var future : futures) {
|
|
||||||
try {
|
|
||||||
result.add(future.get());
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new IllegalStateException("Parallel execution was interrupted", e);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
if (e.getCause() instanceof RuntimeException runtime) {
|
|
||||||
throw runtime;
|
|
||||||
}
|
|
||||||
if (e.getCause() instanceof Error error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException(e.getCause());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
executor.shutdownNow();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.dependencies;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Speculatively retrieves POMs in parallel so that they are already cached
|
|
||||||
* by the artifact retriever when the sequential dependency resolution
|
|
||||||
* processes them, without influencing the resolution semantics.
|
|
||||||
* <p>
|
|
||||||
* A single instance can be shared by multiple concurrent resolutions, the
|
|
||||||
* POM of each unique dependency will only be prefetched once.
|
|
||||||
* <p>
|
|
||||||
* Prefetching only has benefits when the retrieved POMs are cached for the
|
|
||||||
* sequential resolution that follows, the prefetcher is inert when the
|
|
||||||
* retriever doesn't cache or when the resolution parallelism disables it.
|
|
||||||
*
|
|
||||||
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
class PomPrefetcher {
|
|
||||||
private final VersionResolution resolution_;
|
|
||||||
private final ArtifactRetriever retriever_;
|
|
||||||
private final List<Repository> repositories_;
|
|
||||||
private final ExecutorService executor_;
|
|
||||||
private final Set<Dependency> submitted_ = ConcurrentHashMap.newKeySet();
|
|
||||||
|
|
||||||
PomPrefetcher(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories) {
|
|
||||||
resolution_ = resolution;
|
|
||||||
retriever_ = retriever;
|
|
||||||
repositories_ = repositories;
|
|
||||||
if (retriever.isCaching() && resolution.resolutionParallelism() > 1) {
|
|
||||||
executor_ = Executors.newFixedThreadPool(resolution.resolutionParallelism());
|
|
||||||
} else {
|
|
||||||
executor_ = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void prefetch(Collection<PomDependency> candidates) {
|
|
||||||
if (executor_ == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (var candidate : candidates) {
|
|
||||||
var dependency = resolution_.overrideTransitiveDependency(candidate.convertToDependency());
|
|
||||||
if (submitted_.add(dependency)) {
|
|
||||||
executor_.submit(() -> {
|
|
||||||
try {
|
|
||||||
new DependencyResolver(resolution_, retriever_, repositories_, dependency).getMavenPom(dependency);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
// failures are ignored since they will resurface
|
|
||||||
// with the proper context when the dependency is
|
|
||||||
// resolved in order
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void shutdown() {
|
|
||||||
if (executor_ != null) {
|
|
||||||
executor_.shutdownNow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.dependencies;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Outputs the status of artifact transfers to the console.
|
|
||||||
* <p>
|
|
||||||
* When the console is an interactive terminal, the transfers that are in
|
|
||||||
* progress are displayed in a live updating block at the bottom of the
|
|
||||||
* output, with each transfer reporting its progress as it happens.
|
|
||||||
* Otherwise, a single line is printed when each transfer finishes, keeping
|
|
||||||
* the output stable for CI logs, pipes and IDE consoles, while still never
|
|
||||||
* interleaving the lines of parallel transfers.
|
|
||||||
*
|
|
||||||
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
abstract class TransferOutput {
|
|
||||||
private static final TransferOutput INSTANCE = create();
|
|
||||||
|
|
||||||
static TransferOutput instance() {
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TransferOutput create() {
|
|
||||||
if (System.console() != null && !"dumb".equals(System.getenv("TERM"))) {
|
|
||||||
return new AnsiTransferOutput();
|
|
||||||
}
|
|
||||||
return new PlainTransferOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts tracking a single artifact transfer.
|
|
||||||
*
|
|
||||||
* @param location the location of the artifact that is being transferred
|
|
||||||
* @return the transfer to report progress and completion on
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
abstract Transfer start(String location);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single artifact transfer whose progress is being tracked.
|
|
||||||
*
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
abstract static class Transfer {
|
|
||||||
protected final String location_;
|
|
||||||
|
|
||||||
protected Transfer(String location) {
|
|
||||||
location_ = location;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reports transfer progress.
|
|
||||||
*
|
|
||||||
* @param transferred the number of bytes transferred so far
|
|
||||||
* @param total the total number of bytes; or {@code -1} when unknown
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
abstract void progress(long transferred, long total);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reports that the transfer finished.
|
|
||||||
*
|
|
||||||
* @param status the final status of the transfer, like
|
|
||||||
* {@code done}, {@code exists} or {@code not found}
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
abstract void finish(String status);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String describe(String location, String status) {
|
|
||||||
return "Downloading: " + location + " ... " + status;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class PlainTransferOutput extends TransferOutput {
|
|
||||||
Transfer start(String location) {
|
|
||||||
return new Transfer(location) {
|
|
||||||
void progress(long transferred, long total) {
|
|
||||||
// no-op, only the finished state is printed
|
|
||||||
}
|
|
||||||
|
|
||||||
void finish(String status) {
|
|
||||||
System.out.println(describe(location_, status));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class AnsiTransferOutput extends TransferOutput {
|
|
||||||
private static final long REPAINT_INTERVAL_MS = 100;
|
|
||||||
private static final int MAX_LINE_WIDTH = 79;
|
|
||||||
|
|
||||||
private final List<AnsiTransfer> active_ = new ArrayList<>();
|
|
||||||
private int paintedLines_ = 0;
|
|
||||||
private long lastRepaint_ = 0;
|
|
||||||
|
|
||||||
synchronized Transfer start(String location) {
|
|
||||||
var transfer = new AnsiTransfer(location);
|
|
||||||
active_.add(transfer);
|
|
||||||
repaint(null);
|
|
||||||
return transfer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void reportProgress() {
|
|
||||||
if (System.currentTimeMillis() - lastRepaint_ >= REPAINT_INTERVAL_MS) {
|
|
||||||
repaint(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void reportFinished(AnsiTransfer transfer, String status) {
|
|
||||||
active_.remove(transfer);
|
|
||||||
repaint(describe(transfer.location_, status));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void repaint(String finishedLine) {
|
|
||||||
var output = new StringBuilder();
|
|
||||||
if (paintedLines_ > 0) {
|
|
||||||
// move the cursor to the beginning of the live block
|
|
||||||
// and clear everything below it
|
|
||||||
output.append("\u001B[").append(paintedLines_).append("F\u001B[0J");
|
|
||||||
}
|
|
||||||
if (finishedLine != null) {
|
|
||||||
output.append(finishedLine).append('\n');
|
|
||||||
}
|
|
||||||
for (var transfer : active_) {
|
|
||||||
output.append(transfer.statusLine()).append('\n');
|
|
||||||
}
|
|
||||||
paintedLines_ = active_.size();
|
|
||||||
lastRepaint_ = System.currentTimeMillis();
|
|
||||||
System.out.print(output);
|
|
||||||
System.out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AnsiTransfer extends Transfer {
|
|
||||||
private volatile long transferred_ = 0;
|
|
||||||
private volatile long total_ = -1;
|
|
||||||
|
|
||||||
AnsiTransfer(String location) {
|
|
||||||
super(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
void progress(long transferred, long total) {
|
|
||||||
transferred_ = transferred;
|
|
||||||
total_ = total;
|
|
||||||
reportProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
void finish(String status) {
|
|
||||||
reportFinished(this, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
String statusLine() {
|
|
||||||
var filename = location_.substring(location_.lastIndexOf('/') + 1);
|
|
||||||
var line = new StringBuilder("Downloading: ").append(filename).append(" ... ");
|
|
||||||
if (total_ > 0) {
|
|
||||||
line.append(transferred_ * 100 / total_).append('%');
|
|
||||||
} else if (transferred_ > 0) {
|
|
||||||
line.append(humanBytes(transferred_));
|
|
||||||
}
|
|
||||||
// prevent lines from wrapping since that would break
|
|
||||||
// the cursor repositioning of the repaints
|
|
||||||
if (line.length() > MAX_LINE_WIDTH) {
|
|
||||||
line.setLength(MAX_LINE_WIDTH);
|
|
||||||
}
|
|
||||||
return line.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String humanBytes(long bytes) {
|
|
||||||
if (bytes >= 1024 * 1024) {
|
|
||||||
return (bytes / (1024 * 1024)) + " MB";
|
|
||||||
}
|
|
||||||
if (bytes >= 1024) {
|
|
||||||
return (bytes / 1024) + " KB";
|
|
||||||
}
|
|
||||||
return bytes + " B";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,19 +6,13 @@ package rife.bld.dependencies;
|
||||||
|
|
||||||
import rife.ioc.HierarchicalProperties;
|
import rife.ioc.HierarchicalProperties;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is responsible for managing the versions that are applied
|
* This class is responsible for managing version overrides for dependencies.
|
||||||
* during dependency resolution.
|
|
||||||
* <p>
|
* <p>
|
||||||
* It allows users to specify a property key with the prefix "{@code bld.override}" where the values will be parsed as
|
* It allows users to specify a property keys with the prefix "{@code bld.override}" where the values will be parsed as
|
||||||
* a comma-separated list of dependencies with the versions that should override any other versions that are encountered.
|
* a comma-separated list of dependencies with the versions that should override any other versions that are encountered.
|
||||||
* <p>
|
* <p>
|
||||||
* For instance:
|
* For instance:
|
||||||
|
|
@ -33,20 +27,6 @@ import java.util.logging.Logger;
|
||||||
* bld.override-tests=com.uwyn.rife2:bld-tests-badge:1.4.7
|
* bld.override-tests=com.uwyn.rife2:bld-tests-badge:1.4.7
|
||||||
* bld.override-h2=com.h2database:h2:2.2.222
|
* bld.override-h2=com.h2database:h2:2.2.222
|
||||||
* </pre>
|
* </pre>
|
||||||
* <p>
|
|
||||||
* It can also import versions from the dependency management sections of
|
|
||||||
* bills of materials (BOMs). When resolving versions, the
|
|
||||||
* following precedence applies: a version from a "{@code bld.override}"
|
|
||||||
* property always wins, followed by a version explicitly declared
|
|
||||||
* in the build file, followed by a version from a BOM. Dependencies that
|
|
||||||
* are declared without a version and that are not covered by a BOM,
|
|
||||||
* resolve to their latest version.
|
|
||||||
* <p>
|
|
||||||
* It also captures other dependency resolution preferences, like the number
|
|
||||||
* of parallel artifact transfers through the "{@code bld.transferParallelism}"
|
|
||||||
* property and the number of parallel POM retrievals during transitive
|
|
||||||
* dependency resolution through the "{@code bld.resolutionParallelism}"
|
|
||||||
* property.
|
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public class VersionResolution {
|
public class VersionResolution {
|
||||||
|
|
@ -56,27 +36,7 @@ public class VersionResolution {
|
||||||
*/
|
*/
|
||||||
public static final String PROPERTY_OVERRIDE_PREFIX = "bld.override";
|
public static final String PROPERTY_OVERRIDE_PREFIX = "bld.override";
|
||||||
|
|
||||||
/**
|
|
||||||
* The property key that determines how many artifact transfers are
|
|
||||||
* performed in parallel, {@code 1} makes them sequential.
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static final String PROPERTY_TRANSFER_PARALLELISM = "bld.transferParallelism";
|
|
||||||
private static final int DEFAULT_TRANSFER_PARALLELISM = 6;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The property key that determines how many POMs are speculatively
|
|
||||||
* retrieved in parallel during transitive dependency resolution,
|
|
||||||
* {@code 1} disables the parallel retrieval.
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static final String PROPERTY_RESOLUTION_PARALLELISM = "bld.resolutionParallelism";
|
|
||||||
private static final int DEFAULT_RESOLUTION_PARALLELISM = 6;
|
|
||||||
|
|
||||||
private final Map<String, Version> versionOverrides_ = new HashMap<>();
|
private final Map<String, Version> versionOverrides_ = new HashMap<>();
|
||||||
private final Map<String, Version> bomVersions_;
|
|
||||||
private final int transferParallelism_;
|
|
||||||
private final int resolutionParallelism_;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a dummy {@code VersionResolution} instance that doesn't override anything.
|
* Returns a dummy {@code VersionResolution} instance that doesn't override anything.
|
||||||
|
|
@ -114,223 +74,6 @@ public class VersionResolution {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transferParallelism_ = parseParallelism(properties, PROPERTY_TRANSFER_PARALLELISM, DEFAULT_TRANSFER_PARALLELISM);
|
|
||||||
resolutionParallelism_ = parseParallelism(properties, PROPERTY_RESOLUTION_PARALLELISM, DEFAULT_RESOLUTION_PARALLELISM);
|
|
||||||
bomVersions_ = Map.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
private VersionResolution(VersionResolution base, Map<String, Version> bomVersions) {
|
|
||||||
versionOverrides_.putAll(base.versionOverrides_);
|
|
||||||
transferParallelism_ = base.transferParallelism_;
|
|
||||||
resolutionParallelism_ = base.resolutionParallelism_;
|
|
||||||
bomVersions_ = Map.copyOf(bomVersions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of the {@code VersionResolution} class from
|
|
||||||
* hierarchical properties and bills of materials whose dependency
|
|
||||||
* management sections supply versions during resolution.
|
|
||||||
* <p>
|
|
||||||
* The BOMs are imported in the order they're provided, the first BOM
|
|
||||||
* that manages a particular dependency determines its version. The
|
|
||||||
* version overrides of the properties take precedence over any BOM
|
|
||||||
* version, they also apply when resolving the BOMs themselves.
|
|
||||||
*
|
|
||||||
* @param properties the hierarchical properties that will be used to determine the version overrides
|
|
||||||
* @param retriever the retriever to use to get the BOMs
|
|
||||||
* @param repositories the repositories to resolve the BOMs in
|
|
||||||
* @param boms the BOMs to import
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public VersionResolution(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories, Collection<Bom> boms) {
|
|
||||||
this(new VersionResolution(properties), retriever, repositories, boms);
|
|
||||||
}
|
|
||||||
|
|
||||||
private VersionResolution(VersionResolution base, ArtifactRetriever retriever, List<Repository> repositories, Collection<Bom> boms) {
|
|
||||||
this(base, resolveBomVersions(base, retriever, repositories, boms));
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns a resolution that additionally imports the provided BOMs,
|
|
||||||
// versions that are already imported take precedence over the new ones
|
|
||||||
VersionResolution withBoms(ArtifactRetriever retriever, List<Repository> repositories, Collection<Bom> boms) {
|
|
||||||
if (boms == null || boms.isEmpty()) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
var merged = new HashMap<>(resolveBomVersions(this, retriever, repositories, boms));
|
|
||||||
merged.putAll(bomVersions_);
|
|
||||||
return new VersionResolution(this, merged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, Version> resolveBomVersions(VersionResolution resolution, ArtifactRetriever retriever, List<Repository> repositories, Collection<Bom> boms) {
|
|
||||||
var bom_versions = new HashMap<String, Version>();
|
|
||||||
if (boms != null) {
|
|
||||||
for (var bom : boms) {
|
|
||||||
var pom = new DependencyResolver(resolution, retriever, repositories, bom).getMavenPom(bom);
|
|
||||||
for (var managed : pom.getManagedDependencies()) {
|
|
||||||
if (managed.version() != null && !managed.version().isBlank()) {
|
|
||||||
var dependency = managed.convertToDependency();
|
|
||||||
bom_versions.putIfAbsent(managedKey(dependency), dependency.version());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bom_versions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Describes a version conflict between bills of materials, where more
|
|
||||||
* than one applicable BOM manages the same dependency at a different
|
|
||||||
* version.
|
|
||||||
*
|
|
||||||
* @param dependency the group and artifact identifiers of the
|
|
||||||
* dependency that is managed at conflicting versions
|
|
||||||
* @param bomVersions the versions that the BOMs manage the dependency
|
|
||||||
* at, keyed by the BOM, in precedence order so that
|
|
||||||
* the first entry is the version that is used
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public record BomVersionConflict(String dependency, Map<String, Version> bomVersions) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the version conflicts between the provided bills of
|
|
||||||
* materials, where more than one of them manages the same dependency
|
|
||||||
* at a different version.
|
|
||||||
*
|
|
||||||
* @param properties the properties to use to get artifacts
|
|
||||||
* @param retriever the retriever to use to get the BOMs
|
|
||||||
* @param repositories the repositories to resolve the BOMs in
|
|
||||||
* @param boms the BOMs to check, in precedence order
|
|
||||||
* @return the version conflicts between the BOMs
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static List<BomVersionConflict> resolveBomVersionConflicts(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories, Collection<Bom> boms) {
|
|
||||||
var base = new VersionResolution(properties);
|
|
||||||
var versions_by_key = new LinkedHashMap<String, LinkedHashMap<String, Version>>();
|
|
||||||
var dependency_by_key = new LinkedHashMap<String, String>();
|
|
||||||
if (boms != null) {
|
|
||||||
for (var bom : boms) {
|
|
||||||
var pom = new DependencyResolver(base, retriever, repositories, bom).getMavenPom(bom);
|
|
||||||
for (var managed : pom.getManagedDependencies()) {
|
|
||||||
if (managed.version() != null && !managed.version().isBlank()) {
|
|
||||||
var dependency = managed.convertToDependency();
|
|
||||||
var key = managedKey(dependency);
|
|
||||||
versions_by_key.computeIfAbsent(key, k -> new LinkedHashMap<>())
|
|
||||||
.putIfAbsent(bom.toArtifactString(), dependency.version());
|
|
||||||
dependency_by_key.putIfAbsent(key, dependency.toArtifactString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var conflicts = new ArrayList<BomVersionConflict>();
|
|
||||||
for (var entry : versions_by_key.entrySet()) {
|
|
||||||
var bom_versions = entry.getValue();
|
|
||||||
if (bom_versions.values().stream().distinct().count() > 1) {
|
|
||||||
conflicts.add(new BomVersionConflict(dependency_by_key.get(entry.getKey()), new LinkedHashMap<>(bom_versions)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conflicts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Describes a declared dependency whose explicit version differs from
|
|
||||||
* the version that an applicable bill of materials manages it at.
|
|
||||||
* <p>
|
|
||||||
* The declared version is used for the dependency itself, its
|
|
||||||
* transitive dependencies still resolve to the versions that the BOM
|
|
||||||
* manages.
|
|
||||||
*
|
|
||||||
* @param dependency the group and artifact identifiers of the
|
|
||||||
* declared dependency
|
|
||||||
* @param declaredVersion the version the dependency is declared with
|
|
||||||
* @param bom the BOM that manages the dependency, the one
|
|
||||||
* with the highest precedence when several do
|
|
||||||
* @param bomVersion the version the BOM manages the dependency at
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public record DeclaredVersionConflict(String dependency, Version declaredVersion, String bom, Version bomVersion) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the declared dependencies whose explicit version differs from
|
|
||||||
* the version that the provided bills of materials manage them at.
|
|
||||||
* <p>
|
|
||||||
* Dependencies that are declared without a version or whose version is
|
|
||||||
* supplied by a {@code bld.override} property are not reported.
|
|
||||||
*
|
|
||||||
* @param properties the properties to use to get artifacts
|
|
||||||
* @param retriever the retriever to use to get the BOMs
|
|
||||||
* @param repositories the repositories to resolve the BOMs in
|
|
||||||
* @param boms the BOMs to check, in precedence order
|
|
||||||
* @param declared the declared dependencies to check
|
|
||||||
* @return the version differences between the declared dependencies and the BOMs
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static List<DeclaredVersionConflict> resolveDeclaredVersionConflicts(HierarchicalProperties properties, ArtifactRetriever retriever, List<Repository> repositories, Collection<Bom> boms, Collection<Dependency> declared) {
|
|
||||||
var base = new VersionResolution(properties);
|
|
||||||
var managed_versions = new LinkedHashMap<String, Version>();
|
|
||||||
var managed_boms = new LinkedHashMap<String, String>();
|
|
||||||
if (boms != null) {
|
|
||||||
for (var bom : boms) {
|
|
||||||
var pom = new DependencyResolver(base, retriever, repositories, bom).getMavenPom(bom);
|
|
||||||
for (var managed : pom.getManagedDependencies()) {
|
|
||||||
if (managed.version() != null && !managed.version().isBlank()) {
|
|
||||||
var dependency = managed.convertToDependency();
|
|
||||||
var key = managedKey(dependency);
|
|
||||||
// the first BOM that manages a dependency determines
|
|
||||||
// its version, mirroring the resolution precedence
|
|
||||||
if (managed_versions.putIfAbsent(key, dependency.version()) == null) {
|
|
||||||
managed_boms.put(key, bom.toArtifactString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var conflicts = new ArrayList<DeclaredVersionConflict>();
|
|
||||||
if (declared != null) {
|
|
||||||
for (var dependency : declared) {
|
|
||||||
if (VersionNumber.UNKNOWN.equals(dependency.version()) ||
|
|
||||||
base.versionOverrides_.containsKey(dependency.toArtifactString())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var key = managedKey(dependency);
|
|
||||||
var managed_version = managed_versions.get(key);
|
|
||||||
if (managed_version != null && !managed_version.equals(dependency.version())) {
|
|
||||||
conflicts.add(new DeclaredVersionConflict(dependency.toArtifactString(), dependency.version(), managed_boms.get(key), managed_version));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conflicts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// builds the identity that dependency management entries are matched
|
|
||||||
// on, mirroring Maven this includes the type and the classifier, the
|
|
||||||
// modular and forced-classpath JAR types match the plain jar entries
|
|
||||||
// that BOMs manage
|
|
||||||
private static String managedKey(Dependency dependency) {
|
|
||||||
var type = dependency.type();
|
|
||||||
if (type == null || type.isBlank() ||
|
|
||||||
Dependency.TYPE_MODULAR_JAR.equals(type) ||
|
|
||||||
Dependency.TYPE_CLASSPATH_JAR.equals(type)) {
|
|
||||||
type = Dependency.TYPE_JAR;
|
|
||||||
}
|
|
||||||
return dependency.toArtifactString() + ":" + type + ":" + dependency.classifier();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int parseParallelism(HierarchicalProperties properties, String property, int defaultValue) {
|
|
||||||
if (properties != null) {
|
|
||||||
var parallelism = properties.getValueString(property);
|
|
||||||
if (parallelism != null && !parallelism.isBlank()) {
|
|
||||||
try {
|
|
||||||
return Math.max(1, Integer.parseInt(parallelism.trim()));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
Logger.getLogger("rife.bld").warning("Unable to parse the " + property + " property as an integer: '" + parallelism + "', using " + defaultValue + " instead");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -342,16 +85,10 @@ public class VersionResolution {
|
||||||
*/
|
*/
|
||||||
public Version overrideVersion(Dependency original) {
|
public Version overrideVersion(Dependency original) {
|
||||||
var overridden = versionOverrides_.get(original.toArtifactString());
|
var overridden = versionOverrides_.get(original.toArtifactString());
|
||||||
if (overridden != null) {
|
if (overridden == null) {
|
||||||
return overridden;
|
return original.version();
|
||||||
}
|
}
|
||||||
if (VersionNumber.UNKNOWN.equals(original.version())) {
|
return overridden;
|
||||||
var bom_version = bomVersions_.get(managedKey(original));
|
|
||||||
if (bom_version != null) {
|
|
||||||
return bom_version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return original.version();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -367,63 +104,9 @@ public class VersionResolution {
|
||||||
if (overridden == null) {
|
if (overridden == null) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
return withVersion(original, overridden);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies version overrides to a dependency that was explicitly declared
|
|
||||||
* in the build file.
|
|
||||||
* <p>
|
|
||||||
* A version from the {@code bld.override} property always takes
|
|
||||||
* precedence. A bill of materials version is only applied when the
|
|
||||||
* dependency was declared without a version, an explicitly declared
|
|
||||||
* version is never rewritten by a BOM.
|
|
||||||
*
|
|
||||||
* @param declared the declared dependency to apply overrides to
|
|
||||||
* @return the dependency with the overridden version if one applies; or
|
|
||||||
* the original dependency otherwise
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public Dependency overrideDeclaredDependency(Dependency declared) {
|
|
||||||
var overridden = versionOverrides_.get(declared.toArtifactString());
|
|
||||||
if (overridden == null && VersionNumber.UNKNOWN.equals(declared.version())) {
|
|
||||||
overridden = bomVersions_.get(managedKey(declared));
|
|
||||||
}
|
|
||||||
if (overridden == null) {
|
|
||||||
return declared;
|
|
||||||
}
|
|
||||||
return withVersion(declared, overridden);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies version overrides to a transitive dependency that was
|
|
||||||
* encountered during resolution.
|
|
||||||
* <p>
|
|
||||||
* A version from the {@code bld.override} property always takes
|
|
||||||
* precedence, followed by a matching bill of materials version that
|
|
||||||
* pins the transitive dependency regardless of the version its parent
|
|
||||||
* POM declared.
|
|
||||||
*
|
|
||||||
* @param transitive the transitive dependency to apply overrides to
|
|
||||||
* @return the dependency with the overridden version if one applies; or
|
|
||||||
* the original dependency otherwise
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public Dependency overrideTransitiveDependency(Dependency transitive) {
|
|
||||||
var overridden = versionOverrides_.get(transitive.toArtifactString());
|
|
||||||
if (overridden == null) {
|
|
||||||
overridden = bomVersions_.get(managedKey(transitive));
|
|
||||||
}
|
|
||||||
if (overridden == null) {
|
|
||||||
return transitive;
|
|
||||||
}
|
|
||||||
return withVersion(transitive, overridden);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dependency withVersion(Dependency original, Version version) {
|
|
||||||
return new Dependency(original.groupId(),
|
return new Dependency(original.groupId(),
|
||||||
original.artifactId(),
|
original.artifactId(),
|
||||||
version,
|
overridden,
|
||||||
original.classifier(),
|
original.classifier(),
|
||||||
original.type(),
|
original.type(),
|
||||||
original.exclusions(),
|
original.exclusions(),
|
||||||
|
|
@ -439,56 +122,4 @@ public class VersionResolution {
|
||||||
public Map<String, Version> versionOverrides() {
|
public Map<String, Version> versionOverrides() {
|
||||||
return versionOverrides_;
|
return versionOverrides_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether a bill of materials supplies the version of a
|
|
||||||
* particular dependency.
|
|
||||||
* <p>
|
|
||||||
* The dependency identity takes the group, artifact, type and
|
|
||||||
* classifier into account, mirroring Maven's dependency management.
|
|
||||||
*
|
|
||||||
* @param dependency the dependency to check
|
|
||||||
* @return {@code true} when a BOM manages the dependency's version;
|
|
||||||
* {@code false} otherwise
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public boolean coversDependency(Dependency dependency) {
|
|
||||||
return bomVersions_.containsKey(managedKey(dependency));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the map of versions that were imported from bills of
|
|
||||||
* materials, where the key is the dependency identity composed of the
|
|
||||||
* group, artifact, type and classifier, and the value is the managed
|
|
||||||
* version.
|
|
||||||
*
|
|
||||||
* @return the map of BOM versions
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public Map<String, Version> bomVersions() {
|
|
||||||
return bomVersions_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of artifact transfers that are performed in parallel,
|
|
||||||
* {@code 1} means transfers are sequential.
|
|
||||||
*
|
|
||||||
* @return the number of parallel artifact transfers
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public int transferParallelism() {
|
|
||||||
return transferParallelism_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of POMs that are speculatively retrieved in parallel
|
|
||||||
* during transitive dependency resolution, {@code 1} means the parallel
|
|
||||||
* retrieval is disabled.
|
|
||||||
*
|
|
||||||
* @return the number of parallel POM retrievals
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public int resolutionParallelism() {
|
|
||||||
return resolutionParallelism_;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,17 +129,6 @@ class Xml2MavenPom extends Xml2Data {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<PomDependency> getManagedDependencies() {
|
|
||||||
var result = new LinkedHashSet<PomDependency>();
|
|
||||||
// iterate the values since putting an entry with an equal key
|
|
||||||
// preserves the original key instance while the value reflects
|
|
||||||
// the actual managed dependency
|
|
||||||
for (var managed : dependencyManagement_.values()) {
|
|
||||||
result.add(resolveDependency(managed));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
PomDependency resolveDependency(PomDependency dependency) {
|
PomDependency resolveDependency(PomDependency dependency) {
|
||||||
return new PomDependency(
|
return new PomDependency(
|
||||||
resolveMavenProperties(dependency.groupId()),
|
resolveMavenProperties(dependency.groupId()),
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.help;
|
|
||||||
|
|
||||||
import rife.bld.CommandHelp;
|
|
||||||
import rife.tools.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides help for the MCP command.
|
|
||||||
*
|
|
||||||
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public class McpHelp implements CommandHelp {
|
|
||||||
public String getSummary() {
|
|
||||||
return "Starts an MCP server that exposes the build commands";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription(String topic) {
|
|
||||||
return StringUtils.replace("""
|
|
||||||
Starts a Model Context Protocol (MCP) server that exposes the
|
|
||||||
build commands as tools, so that AI agents can drive the build.
|
|
||||||
This is an experimental feature and may still change.
|
|
||||||
|
|
||||||
The server communicates over standard input and output with the
|
|
||||||
MCP stdio transport and runs until its input stream ends. Launch
|
|
||||||
bld with the --use-stderr option to keep standard output free
|
|
||||||
for the protocol while the build starts up.
|
|
||||||
|
|
||||||
The install argument doesn't start the server, it registers the
|
|
||||||
project with an MCP client by writing the standard configuration
|
|
||||||
file inside the project directory:
|
|
||||||
|
|
||||||
install writes .mcp.json (Claude Code and compatible)
|
|
||||||
install cursor writes .cursor/mcp.json
|
|
||||||
install vscode writes .vscode/mcp.json
|
|
||||||
install --print prints the configuration instead of writing it
|
|
||||||
|
|
||||||
Existing configuration files are merged with, other servers are
|
|
||||||
preserved. The registered command launches the wrapper directly
|
|
||||||
through java, so that the same file works on every platform.
|
|
||||||
|
|
||||||
Usage : ${topic} [install [claude | cursor | vscode] [--print]]""", "${topic}", topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -179,7 +179,7 @@ public abstract class AbstractCreateOperation<T extends AbstractCreateOperation<
|
||||||
* parents, outputting the location when the operation is {@link #verbose()}.
|
* parents, outputting the location when the operation is {@link #verbose()}.
|
||||||
*
|
*
|
||||||
* @param directory the directory to create
|
* @param directory the directory to create
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
protected void executeCreateDirectory(File directory) {
|
protected void executeCreateDirectory(File directory) {
|
||||||
if (verbose()) {
|
if (verbose()) {
|
||||||
|
|
@ -194,7 +194,7 @@ public abstract class AbstractCreateOperation<T extends AbstractCreateOperation<
|
||||||
*
|
*
|
||||||
* @param content the content to write
|
* @param content the content to write
|
||||||
* @param file the file to write the content into
|
* @param file the file to write the content into
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
protected void executeWriteProjectFile(String content, File file)
|
protected void executeWriteProjectFile(String content, File file)
|
||||||
throws FileUtilsErrorException {
|
throws FileUtilsErrorException {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ public abstract class AbstractOperation<T extends AbstractOperation<T>> {
|
||||||
* @param verbose {@code true} if the operation should be verbose;
|
* @param verbose {@code true} if the operation should be verbose;
|
||||||
* {@code false} otherwise
|
* {@code false} otherwise
|
||||||
* @return this operation instance
|
* @return this operation instance
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public T verbose(boolean verbose) {
|
public T verbose(boolean verbose) {
|
||||||
verbose_ = verbose;
|
verbose_ = verbose;
|
||||||
|
|
@ -68,7 +68,7 @@ public abstract class AbstractOperation<T extends AbstractOperation<T>> {
|
||||||
*
|
*
|
||||||
* @return {@code true} if the operation should be verbose;
|
* @return {@code true} if the operation should be verbose;
|
||||||
* {@code false} otherwise
|
* {@code false} otherwise
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public boolean verbose() {
|
public boolean verbose() {
|
||||||
return verbose_;
|
return verbose_;
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ public abstract class AbstractProcessOperation<T extends AbstractProcessOperatio
|
||||||
*
|
*
|
||||||
* @param classpath classpath entries for the operation
|
* @param classpath classpath entries for the operation
|
||||||
* @return this operation instance
|
* @return this operation instance
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public T classpath(File... classpath) {
|
public T classpath(File... classpath) {
|
||||||
|
|
@ -269,7 +269,7 @@ public abstract class AbstractProcessOperation<T extends AbstractProcessOperatio
|
||||||
*
|
*
|
||||||
* @param classpath a list of classpath entries for the operation
|
* @param classpath a list of classpath entries for the operation
|
||||||
* @return this operation instance
|
* @return this operation instance
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public T classpath(Collection<File> classpath) {
|
public T classpath(Collection<File> classpath) {
|
||||||
classpath_.addAll(classpath.stream().map(File::getAbsolutePath).toList());
|
classpath_.addAll(classpath.stream().map(File::getAbsolutePath).toList());
|
||||||
|
|
@ -293,7 +293,7 @@ public abstract class AbstractProcessOperation<T extends AbstractProcessOperatio
|
||||||
*
|
*
|
||||||
* @param modulePath module path entries for the operation
|
* @param modulePath module path entries for the operation
|
||||||
* @return this operation instance
|
* @return this operation instance
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public T modulePath(File... modulePath) {
|
public T modulePath(File... modulePath) {
|
||||||
return modulePath(List.of(modulePath));
|
return modulePath(List.of(modulePath));
|
||||||
|
|
@ -320,7 +320,7 @@ public abstract class AbstractProcessOperation<T extends AbstractProcessOperatio
|
||||||
*
|
*
|
||||||
* @param modulePath a list of module path entries for the operation
|
* @param modulePath a list of module path entries for the operation
|
||||||
* @return this operation instance
|
* @return this operation instance
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public T modulePath(Collection<File> modulePath) {
|
public T modulePath(Collection<File> modulePath) {
|
||||||
modulePath_.addAll(modulePath.stream().map(File::getAbsolutePath).toList());
|
modulePath_.addAll(modulePath.stream().map(File::getAbsolutePath).toList());
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ public class DependencyTreeOperation extends AbstractOperation<DependencyTreeOpe
|
||||||
* @since 1.5.21
|
* @since 1.5.21
|
||||||
*/
|
*/
|
||||||
protected String executeGenerateCompileDependencies() {
|
protected String executeGenerateCompileDependencies() {
|
||||||
var compile_tree = dependencies().scope(compile).generateTransitiveDependencyTree(new VersionResolution(properties(), artifactRetriever(), repositories(), dependencies().effectiveBoms(compile)), artifactRetriever(), repositories(), compile);
|
var compile_tree = dependencies().scope(compile).generateTransitiveDependencyTree(new VersionResolution(properties()), artifactRetriever(), repositories(), compile);
|
||||||
if (compile_tree.isEmpty()) {
|
if (compile_tree.isEmpty()) {
|
||||||
compile_tree = "no dependencies" + System.lineSeparator();
|
compile_tree = "no dependencies" + System.lineSeparator();
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +191,7 @@ public class DependencyTreeOperation extends AbstractOperation<DependencyTreeOpe
|
||||||
* @since 1.7.3
|
* @since 1.7.3
|
||||||
*/
|
*/
|
||||||
protected String executeGenerateProvidedDependencies() {
|
protected String executeGenerateProvidedDependencies() {
|
||||||
var provided_tree = dependencies().scope(provided).generateTransitiveDependencyTree(new VersionResolution(properties(), artifactRetriever(), repositories(), dependencies().effectiveBoms(provided)), artifactRetriever(), repositories(), compile, runtime);
|
var provided_tree = dependencies().scope(provided).generateTransitiveDependencyTree(new VersionResolution(properties()), artifactRetriever(), repositories(), compile, runtime);
|
||||||
if (provided_tree.isEmpty()) {
|
if (provided_tree.isEmpty()) {
|
||||||
provided_tree = "no dependencies" + System.lineSeparator();
|
provided_tree = "no dependencies" + System.lineSeparator();
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +204,7 @@ public class DependencyTreeOperation extends AbstractOperation<DependencyTreeOpe
|
||||||
* @since 1.5.21
|
* @since 1.5.21
|
||||||
*/
|
*/
|
||||||
protected String executeGenerateRuntimeDependencies() {
|
protected String executeGenerateRuntimeDependencies() {
|
||||||
var runtime_tree = dependencies().scope(runtime).generateTransitiveDependencyTree(new VersionResolution(properties(), artifactRetriever(), repositories(), dependencies().effectiveBoms(runtime)), artifactRetriever(), repositories(), compile, runtime);
|
var runtime_tree = dependencies().scope(runtime).generateTransitiveDependencyTree(new VersionResolution(properties()), artifactRetriever(), repositories(), compile, runtime);
|
||||||
if (runtime_tree.isEmpty()) {
|
if (runtime_tree.isEmpty()) {
|
||||||
runtime_tree = "no dependencies" + System.lineSeparator();
|
runtime_tree = "no dependencies" + System.lineSeparator();
|
||||||
}
|
}
|
||||||
|
|
@ -217,7 +217,7 @@ public class DependencyTreeOperation extends AbstractOperation<DependencyTreeOpe
|
||||||
* @since 1.7.3
|
* @since 1.7.3
|
||||||
*/
|
*/
|
||||||
protected String executeGenerateTestDependencies() {
|
protected String executeGenerateTestDependencies() {
|
||||||
var test_tree = dependencies().scope(test).generateTransitiveDependencyTree(new VersionResolution(properties(), artifactRetriever(), repositories(), dependencies().effectiveBoms(test)), artifactRetriever(), repositories(), compile, runtime);
|
var test_tree = dependencies().scope(test).generateTransitiveDependencyTree(new VersionResolution(properties()), artifactRetriever(), repositories(), compile, runtime);
|
||||||
if (test_tree.isEmpty()) {
|
if (test_tree.isEmpty()) {
|
||||||
test_tree = "no dependencies" + System.lineSeparator();
|
test_tree = "no dependencies" + System.lineSeparator();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,13 +42,9 @@ public class DownloadOperation extends AbstractOperation<DownloadOperation> {
|
||||||
private File libTestModulesDirectory_;
|
private File libTestModulesDirectory_;
|
||||||
private boolean downloadSources_ = false;
|
private boolean downloadSources_ = false;
|
||||||
private boolean downloadJavadoc_ = false;
|
private boolean downloadJavadoc_ = false;
|
||||||
private final DependencyTransferBatch transfers_ = new DependencyTransferBatch();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the download operation.
|
* Performs the download operation.
|
||||||
* <p>
|
|
||||||
* The artifact transfers of all the scopes are collected first and then
|
|
||||||
* performed together in a single parallel batch.
|
|
||||||
*
|
*
|
||||||
* @since 1.5
|
* @since 1.5
|
||||||
*/
|
*/
|
||||||
|
|
@ -58,75 +54,16 @@ public class DownloadOperation extends AbstractOperation<DownloadOperation> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!silent()) {
|
|
||||||
System.out.println("Analyzing dependencies...");
|
|
||||||
}
|
|
||||||
executeReportUncoveredDependencies();
|
|
||||||
executeDownloadCompileDependencies();
|
executeDownloadCompileDependencies();
|
||||||
executeDownloadProvidedDependencies();
|
executeDownloadProvidedDependencies();
|
||||||
executeDownloadRuntimeDependencies();
|
executeDownloadRuntimeDependencies();
|
||||||
executeDownloadStandaloneDependencies();
|
executeDownloadStandaloneDependencies();
|
||||||
executeDownloadTestDependencies();
|
executeDownloadTestDependencies();
|
||||||
executeTransferDependencies();
|
|
||||||
if (!silent()) {
|
if (!silent()) {
|
||||||
System.out.println("Downloading finished successfully.");
|
System.out.println("Downloading finished successfully.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of the {@link #execute} operation, warns about version-less
|
|
||||||
* dependencies that are not covered by a BOM in their scope.
|
|
||||||
*
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
protected void executeReportUncoveredDependencies() {
|
|
||||||
if (silent()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (var dependency : dependencies().versionlessDependenciesWithoutBom(properties(), artifactRetriever(), repositories())) {
|
|
||||||
System.out.println("Warning: '" + dependency.toArtifactString() + "' isn't covered by a BOM, its latest version will be used");
|
|
||||||
}
|
|
||||||
for (var conflict : dependencies().bomVersionConflicts(properties(), artifactRetriever(), repositories())) {
|
|
||||||
System.out.println(formatBomVersionConflict(conflict));
|
|
||||||
}
|
|
||||||
for (var conflict : dependencies().declaredVersionConflicts(properties(), artifactRetriever(), repositories())) {
|
|
||||||
System.out.println(formatDeclaredVersionConflict(conflict));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats a warning message for a BOM version conflict.
|
|
||||||
*
|
|
||||||
* @param conflict the BOM version conflict to format
|
|
||||||
* @return the formatted warning message
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
protected static String formatBomVersionConflict(rife.bld.dependencies.VersionResolution.BomVersionConflict conflict) {
|
|
||||||
var entries = conflict.bomVersions().entrySet().iterator();
|
|
||||||
var used = entries.next();
|
|
||||||
var message = new StringBuilder("Warning: '" + conflict.dependency() + "' is managed by multiple BOMs, using " +
|
|
||||||
used.getValue() + " from '" + used.getKey() + "'");
|
|
||||||
while (entries.hasNext()) {
|
|
||||||
var other = entries.next();
|
|
||||||
message.append(", not ").append(other.getValue()).append(" from '").append(other.getKey()).append('\'');
|
|
||||||
}
|
|
||||||
return message.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats a warning message for a declared dependency whose version
|
|
||||||
* differs from the version that a BOM manages it at.
|
|
||||||
*
|
|
||||||
* @param conflict the declared version conflict to format
|
|
||||||
* @return the formatted warning message
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
protected static String formatDeclaredVersionConflict(rife.bld.dependencies.VersionResolution.DeclaredVersionConflict conflict) {
|
|
||||||
return "Warning: '" + conflict.dependency() + "' is declared with version " + conflict.declaredVersion() +
|
|
||||||
" while BOM '" + conflict.bom() + "' manages it at " + conflict.bomVersion() +
|
|
||||||
", the declared version is used but transitive dependencies still follow the BOM";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Part of the {@link #execute} operation, download the {@code compile} scope artifacts.
|
* Part of the {@link #execute} operation, download the {@code compile} scope artifacts.
|
||||||
*
|
*
|
||||||
|
|
@ -173,11 +110,7 @@ public class DownloadOperation extends AbstractOperation<DownloadOperation> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Part of the {@link #execute} operation, collect the artifact transfers
|
* Part of the {@link #execute} operation, download the artifacts for a particular dependency scope.
|
||||||
* for a particular dependency scope into the {@linkplain #transfers()
|
|
||||||
* transfer batch}.
|
|
||||||
* <p>
|
|
||||||
* The transfers are performed by {@link #executeTransferDependencies}.
|
|
||||||
*
|
*
|
||||||
* @param destinationDirectory the directory in which the artifacts should be downloaded
|
* @param destinationDirectory the directory in which the artifacts should be downloaded
|
||||||
* @param modulesDirectory the directory in which the modules should be downloaded
|
* @param modulesDirectory the directory in which the modules should be downloaded
|
||||||
|
|
@ -195,29 +128,7 @@ public class DownloadOperation extends AbstractOperation<DownloadOperation> {
|
||||||
additional_classifiers = classifiers.toArray(new String[0]);
|
additional_classifiers = classifiers.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
transfers().add(dependencies, destinationDirectory, modulesDirectory, additional_classifiers);
|
dependencies.transferIntoDirectory(new VersionResolution(properties()), artifactRetriever(), repositories(), destinationDirectory, modulesDirectory, additional_classifiers);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of the {@link #execute} operation, perform all the collected
|
|
||||||
* artifact transfers of the {@linkplain #transfers() transfer batch}
|
|
||||||
* together in parallel.
|
|
||||||
*
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
protected void executeTransferDependencies() {
|
|
||||||
transfers().transfer(new VersionResolution(properties()), artifactRetriever(), repositories());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the batch that collects the artifact transfers of this
|
|
||||||
* operation.
|
|
||||||
*
|
|
||||||
* @return the artifact transfer batch of this operation
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
protected DependencyTransferBatch transfers() {
|
|
||||||
return transfers_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,6 @@ public class HelpOperation {
|
||||||
The following bld arguments are supported:
|
The following bld arguments are supported:
|
||||||
|
|
||||||
--offline Works without Internet (only as first argument)
|
--offline Works without Internet (only as first argument)
|
||||||
--use-stderr Sends the build output to standard error
|
|
||||||
-?, -h, --help Shows the help
|
-?, -h, --help Shows the help
|
||||||
-D<name>=<value> Sets a JVM system property
|
-D<name>=<value> Sets a JVM system property
|
||||||
-s, --stacktrace Prints out the stacktrace for exceptions
|
-s, --stacktrace Prints out the stacktrace for exceptions
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* of other-module is ALL-UNNAMED
|
* of other-module is ALL-UNNAMED
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions addExports(String... modules) {
|
public JavacOptions addExports(String... modules) {
|
||||||
if (isNotEmpty(modules)) {
|
if (isNotEmpty(modules)) {
|
||||||
|
|
@ -151,7 +151,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* of other-module is ALL-UNNAMED
|
* of other-module is ALL-UNNAMED
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions addExports(Collection<String> modules) {
|
public JavacOptions addExports(Collection<String> modules) {
|
||||||
return addCommaSeparatedOption("--add-exports", modules);
|
return addCommaSeparatedOption("--add-exports", modules);
|
||||||
|
|
@ -161,7 +161,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specifies additional modules to be considered as required by a given module
|
* Specifies additional modules to be considered as required by a given module
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions addReads(String... modules) {
|
public JavacOptions addReads(String... modules) {
|
||||||
if (isNotEmpty(modules)) {
|
if (isNotEmpty(modules)) {
|
||||||
|
|
@ -174,7 +174,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specifies additional modules to be considered as required by a given module
|
* Specifies additional modules to be considered as required by a given module
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions addReads(Collection<String> modules) {
|
public JavacOptions addReads(Collection<String> modules) {
|
||||||
return addCommaSeparatedOption("--add-reads", modules);
|
return addCommaSeparatedOption("--add-reads", modules);
|
||||||
|
|
@ -222,7 +222,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* if none specified or inferred
|
* if none specified or inferred
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions defaultModuleForCreatedFiles(String module) {
|
public JavacOptions defaultModuleForCreatedFiles(String module) {
|
||||||
add("--default-module-for-created-files");
|
add("--default-module-for-created-files");
|
||||||
|
|
@ -417,7 +417,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Overrides or augments a module with classes and resources in JAR files or directories
|
* Overrides or augments a module with classes and resources in JAR files or directories
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions patchModule(String module) {
|
public JavacOptions patchModule(String module) {
|
||||||
add("--patch-module");
|
add("--patch-module");
|
||||||
|
|
@ -441,7 +441,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Provide source compatibility with the specified Java SE release
|
* Provide source compatibility with the specified Java SE release
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions source(int version) {
|
public JavacOptions source(int version) {
|
||||||
add("--source");
|
add("--source");
|
||||||
|
|
@ -453,7 +453,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Generate class files suitable for the specified Java SE release
|
* Generate class files suitable for the specified Java SE release
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions target(int version) {
|
public JavacOptions target(int version) {
|
||||||
add("--target");
|
add("--target");
|
||||||
|
|
@ -645,7 +645,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specify where to find input source files for multiple modules
|
* Specify where to find input source files for multiple modules
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions moduleSourcePathStrings(Collection<String> paths) {
|
public JavacOptions moduleSourcePathStrings(Collection<String> paths) {
|
||||||
return addPathOption("--module-source-path", paths);
|
return addPathOption("--module-source-path", paths);
|
||||||
|
|
@ -655,7 +655,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specify where to find input source files for multiple modules
|
* Specify where to find input source files for multiple modules
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions moduleSourcePathPaths(Collection<Path> paths) {
|
public JavacOptions moduleSourcePathPaths(Collection<Path> paths) {
|
||||||
if (isNotEmpty(paths)) {
|
if (isNotEmpty(paths)) {
|
||||||
|
|
@ -668,7 +668,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specify where to find input source files for multiple modules
|
* Specify where to find input source files for multiple modules
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions moduleSourcePath(Collection<File> paths) {
|
public JavacOptions moduleSourcePath(Collection<File> paths) {
|
||||||
if (isNotEmpty(paths)) {
|
if (isNotEmpty(paths)) {
|
||||||
|
|
@ -827,7 +827,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specify a module path where to find annotation processors
|
* Specify a module path where to find annotation processors
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions processorModulePathPaths(Collection<Path> paths) {
|
public JavacOptions processorModulePathPaths(Collection<Path> paths) {
|
||||||
if (isNotEmpty(paths)) {
|
if (isNotEmpty(paths)) {
|
||||||
|
|
@ -840,7 +840,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specify a module path where to find annotation processors
|
* Specify a module path where to find annotation processors
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions processorModulePathStrings(Collection<String> paths) {
|
public JavacOptions processorModulePathStrings(Collection<String> paths) {
|
||||||
return addPathOption("--processor-module-path", paths);
|
return addPathOption("--processor-module-path", paths);
|
||||||
|
|
@ -850,7 +850,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specify a module path where to find annotation processors
|
* Specify a module path where to find annotation processors
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions processorModulePath(Collection<File> paths) {
|
public JavacOptions processorModulePath(Collection<File> paths) {
|
||||||
if (isNotEmpty(paths)) {
|
if (isNotEmpty(paths)) {
|
||||||
|
|
@ -902,7 +902,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specify where to find annotation processors
|
* Specify where to find annotation processors
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions processorPathStrings(Collection<String> paths) {
|
public JavacOptions processorPathStrings(Collection<String> paths) {
|
||||||
return addPathOption("--processor-path", paths);
|
return addPathOption("--processor-path", paths);
|
||||||
|
|
@ -912,7 +912,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specify where to find annotation processors
|
* Specify where to find annotation processors
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions processorPath(Collection<File> paths) {
|
public JavacOptions processorPath(Collection<File> paths) {
|
||||||
if (isNotEmpty(paths)) {
|
if (isNotEmpty(paths)) {
|
||||||
|
|
@ -925,7 +925,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Specify where to find annotation processors
|
* Specify where to find annotation processors
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions processorPathPaths(Collection<Path> paths) {
|
public JavacOptions processorPathPaths(Collection<Path> paths) {
|
||||||
if (isNotEmpty(paths)) {
|
if (isNotEmpty(paths)) {
|
||||||
|
|
@ -1036,7 +1036,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Override location of upgradeable modules
|
* Override location of upgradeable modules
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions upgradeModulePathStrings(Collection<String> paths) {
|
public JavacOptions upgradeModulePathStrings(Collection<String> paths) {
|
||||||
return addPathOption("--upgrade-module-path", paths);
|
return addPathOption("--upgrade-module-path", paths);
|
||||||
|
|
@ -1046,7 +1046,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Override location of upgradeable modules
|
* Override location of upgradeable modules
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions upgradeModulePath(Collection<File> paths) {
|
public JavacOptions upgradeModulePath(Collection<File> paths) {
|
||||||
if (isNotEmpty(paths)) {
|
if (isNotEmpty(paths)) {
|
||||||
|
|
@ -1059,7 +1059,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Override location of upgradeable modules
|
* Override location of upgradeable modules
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions upgradeModulePathPaths(Collection<Path> paths) {
|
public JavacOptions upgradeModulePathPaths(Collection<Path> paths) {
|
||||||
if (isNotEmpty(paths)) {
|
if (isNotEmpty(paths)) {
|
||||||
|
|
@ -1083,7 +1083,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Enable recommended warning categories
|
* Enable recommended warning categories
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions xLint() {
|
public JavacOptions xLint() {
|
||||||
add("-Xlint");
|
add("-Xlint");
|
||||||
|
|
@ -1094,7 +1094,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Warning categories to enable
|
* Warning categories to enable
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions xLint(XLintKey... keys) {
|
public JavacOptions xLint(XLintKey... keys) {
|
||||||
if (isNotEmpty(keys)) {
|
if (isNotEmpty(keys)) {
|
||||||
|
|
@ -1107,7 +1107,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Warning categories to enable
|
* Warning categories to enable
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions xLint(Collection<XLintKey> keys) {
|
public JavacOptions xLint(Collection<XLintKey> keys) {
|
||||||
if (isNotEmpty(keys)) {
|
if (isNotEmpty(keys)) {
|
||||||
|
|
@ -1120,7 +1120,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Warning categories to disable
|
* Warning categories to disable
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions xLintDisable(XLintKey... keys) {
|
public JavacOptions xLintDisable(XLintKey... keys) {
|
||||||
if (isNotEmpty(keys)) {
|
if (isNotEmpty(keys)) {
|
||||||
|
|
@ -1133,7 +1133,7 @@ public class JavacOptions extends ArrayList<String> {
|
||||||
* Warning categories to disable
|
* Warning categories to disable
|
||||||
*
|
*
|
||||||
* @return this list of options
|
* @return this list of options
|
||||||
* @since 2.4.0
|
* @since 2.3.1
|
||||||
*/
|
*/
|
||||||
public JavacOptions xLintDisable(Collection<XLintKey> keys) {
|
public JavacOptions xLintDisable(Collection<XLintKey> keys) {
|
||||||
if (isNotEmpty(keys)) {
|
if (isNotEmpty(keys)) {
|
||||||
|
|
|
||||||
|
|
@ -561,7 +561,7 @@ public class JpackageOptions extends LinkedHashMap<String, String> {
|
||||||
/**
|
/**
|
||||||
* Creates a shortcut for the application.
|
* Creates a shortcut for the application.
|
||||||
*
|
*
|
||||||
* @param shortcut {@code true} to create a shortcut, {@code false} otherwise
|
* @param shortcut {@code true| to create a shortcut, {@code false} otherwise
|
||||||
* @return this map of options
|
* @return this map of options
|
||||||
*/
|
*/
|
||||||
public JpackageOptions linuxShortcut(boolean shortcut) {
|
public JpackageOptions linuxShortcut(boolean shortcut) {
|
||||||
|
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.operations;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The details that a single MCP tool call hands to the build process it
|
|
||||||
* launches.
|
|
||||||
* <p>
|
|
||||||
* The command, its arguments, the flags and the excluded commands are
|
|
||||||
* written to a control file instead of the command line, so they can never
|
|
||||||
* be mistaken for the command's own arguments. The values are escaped so
|
|
||||||
* that any character, including newlines, comes through intact. Only the
|
|
||||||
* MCP code uses this file, the build itself knows nothing about it.
|
|
||||||
*
|
|
||||||
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
final class McpControl {
|
|
||||||
final String command;
|
|
||||||
final List<String> arguments;
|
|
||||||
final boolean offline;
|
|
||||||
final boolean verbose;
|
|
||||||
final boolean stacktrace;
|
|
||||||
final Set<String> exclusions;
|
|
||||||
|
|
||||||
private McpControl(String command, List<String> arguments, boolean offline, boolean verbose, boolean stacktrace, Set<String> exclusions) {
|
|
||||||
this.command = command;
|
|
||||||
this.arguments = arguments;
|
|
||||||
this.offline = offline;
|
|
||||||
this.verbose = verbose;
|
|
||||||
this.stacktrace = stacktrace;
|
|
||||||
this.exclusions = exclusions;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void write(File file, String command, List<String> arguments,
|
|
||||||
boolean offline, boolean verbose, boolean stacktrace,
|
|
||||||
Collection<String> exclusions)
|
|
||||||
throws IOException {
|
|
||||||
var lines = new ArrayList<String>();
|
|
||||||
lines.add("command " + escape(command));
|
|
||||||
for (var argument : arguments) {
|
|
||||||
lines.add("arg " + escape(argument));
|
|
||||||
}
|
|
||||||
if (offline) {
|
|
||||||
lines.add("offline");
|
|
||||||
}
|
|
||||||
if (verbose) {
|
|
||||||
lines.add("verbose");
|
|
||||||
}
|
|
||||||
if (stacktrace) {
|
|
||||||
lines.add("stacktrace");
|
|
||||||
}
|
|
||||||
for (var exclusion : exclusions) {
|
|
||||||
lines.add("exclude " + escape(exclusion));
|
|
||||||
}
|
|
||||||
Files.write(file.toPath(), lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
static McpControl read(File file)
|
|
||||||
throws IOException {
|
|
||||||
String command = null;
|
|
||||||
var arguments = new ArrayList<String>();
|
|
||||||
var offline = false;
|
|
||||||
var verbose = false;
|
|
||||||
var stacktrace = false;
|
|
||||||
var exclusions = new LinkedHashSet<String>();
|
|
||||||
for (var line : Files.readAllLines(file.toPath())) {
|
|
||||||
if (line.equals("offline")) {
|
|
||||||
offline = true;
|
|
||||||
} else if (line.equals("verbose")) {
|
|
||||||
verbose = true;
|
|
||||||
} else if (line.equals("stacktrace")) {
|
|
||||||
stacktrace = true;
|
|
||||||
} else if (line.startsWith("command ")) {
|
|
||||||
command = unescape(line.substring("command ".length()));
|
|
||||||
} else if (line.startsWith("arg ")) {
|
|
||||||
arguments.add(unescape(line.substring("arg ".length())));
|
|
||||||
} else if (line.startsWith("exclude ")) {
|
|
||||||
exclusions.add(unescape(line.substring("exclude ".length())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (command == null) {
|
|
||||||
throw new IOException("the control file doesn't specify a command");
|
|
||||||
}
|
|
||||||
return new McpControl(command, arguments, offline, verbose, stacktrace, exclusions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String escape(String value) {
|
|
||||||
return value.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String unescape(String value) {
|
|
||||||
var result = new StringBuilder();
|
|
||||||
for (var i = 0; i < value.length(); ++i) {
|
|
||||||
var c = value.charAt(i);
|
|
||||||
if (c == '\\' && i + 1 < value.length()) {
|
|
||||||
var next = value.charAt(++i);
|
|
||||||
switch (next) {
|
|
||||||
case 'n' -> result.append('\n');
|
|
||||||
case 'r' -> result.append('\r');
|
|
||||||
case '\\' -> result.append('\\');
|
|
||||||
default -> result.append(next);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,180 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.operations;
|
|
||||||
|
|
||||||
import rife.bld.BaseProject;
|
|
||||||
import rife.bld.BuildExecutor;
|
|
||||||
import rife.bld.operations.exceptions.ExitStatusException;
|
|
||||||
import rife.tools.ExceptionUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The entry point that runs exactly one build command for an MCP tool
|
|
||||||
* call, in a separate build process.
|
|
||||||
* <p>
|
|
||||||
* It reads the command, its arguments, the flags and the excluded commands
|
|
||||||
* from the control file, creates the build executor, and runs the single
|
|
||||||
* command through its public API. All of this single command handling lives
|
|
||||||
* here in the MCP feature, the build itself stays unaware of it. The runner
|
|
||||||
* is launched with the build executor's class name as its last argument,
|
|
||||||
* any earlier arguments, like the wrapper's {@code --offline} option, are
|
|
||||||
* ignored.
|
|
||||||
*
|
|
||||||
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public final class McpToolRunner {
|
|
||||||
/**
|
|
||||||
* The environment variable with the path of the control file that
|
|
||||||
* requests the execution of exactly one command.
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static final String ENV_CONTROL_FILE = "BLD_MCP_CONTROL_FILE";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The environment variable with the path of the file that the runner
|
|
||||||
* writes its result to.
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static final String ENV_STATUS_FILE = "BLD_MCP_STATUS_FILE";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The outcome that is written when the command couldn't be resolved.
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static final String STATUS_UNKNOWN_COMMAND = "unknown-command";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The outcome that is written when the command is excluded.
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static final String STATUS_EXCLUDED_COMMAND = "excluded-command";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The outcome that is written when the runner itself couldn't be set
|
|
||||||
* up, for instance when the build executor class couldn't be
|
|
||||||
* instantiated, this is a server failure rather than a tool failure.
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static final String STATUS_RUNNER_ERROR = "runner-error";
|
|
||||||
|
|
||||||
private McpToolRunner() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs a single build command for an MCP tool call.
|
|
||||||
*
|
|
||||||
* @param arguments the build executor class name as the last argument
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public static void main(String[] arguments) {
|
|
||||||
System.exit(run(arguments));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int run(String[] arguments) {
|
|
||||||
var control_path = System.getenv(ENV_CONTROL_FILE);
|
|
||||||
if (control_path == null) {
|
|
||||||
writeStatus(STATUS_RUNNER_ERROR);
|
|
||||||
System.err.println("ERROR: no MCP control file provided");
|
|
||||||
return ExitStatusException.EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
var control_file = new File(control_path);
|
|
||||||
McpControl control;
|
|
||||||
try {
|
|
||||||
control = McpControl.read(control_file);
|
|
||||||
} catch (IOException e) {
|
|
||||||
writeStatus(STATUS_RUNNER_ERROR);
|
|
||||||
System.err.println("ERROR: the MCP control file couldn't be read");
|
|
||||||
return ExitStatusException.EXIT_FAILURE;
|
|
||||||
} finally {
|
|
||||||
// the control file is consumed immediately, so that nested
|
|
||||||
// processes that inherit the environment don't re-enter the
|
|
||||||
// single command execution and don't overwrite its status
|
|
||||||
control_file.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arguments.length == 0) {
|
|
||||||
writeStatus(STATUS_RUNNER_ERROR);
|
|
||||||
System.err.println("ERROR: no build executor class provided");
|
|
||||||
return ExitStatusException.EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
// the build executor class is instantiated directly through its
|
|
||||||
// no-arg constructor and driven through its public API, its main
|
|
||||||
// method is not invoked, so that a tool call runs exactly the
|
|
||||||
// requested command and nothing else
|
|
||||||
BuildExecutor executor;
|
|
||||||
try {
|
|
||||||
var executor_class = Class.forName(arguments[arguments.length - 1]);
|
|
||||||
executor = (BuildExecutor) executor_class.getDeclaredConstructor().newInstance();
|
|
||||||
} catch (ReflectiveOperationException | ClassCastException e) {
|
|
||||||
writeStatus(STATUS_RUNNER_ERROR);
|
|
||||||
System.err.println("ERROR: the build executor couldn't be instantiated: " + e);
|
|
||||||
return ExitStatusException.EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
executor.offline(control.offline);
|
|
||||||
executor.verbose(control.verbose);
|
|
||||||
executor.showStacktrace(control.stacktrace);
|
|
||||||
|
|
||||||
var resolved = executor.resolveCommand(control.command);
|
|
||||||
if (resolved == null) {
|
|
||||||
// the result goes into the status file so that it can never be
|
|
||||||
// confused with a regular exit status of a build command
|
|
||||||
writeStatus(STATUS_UNKNOWN_COMMAND);
|
|
||||||
System.err.println("ERROR: Unknown command '" + control.command + "'");
|
|
||||||
return ExitStatusException.EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
// the exclusions are enforced here, in the freshly compiled build,
|
|
||||||
// so that aliases or commands added to the build sources can't
|
|
||||||
// reach an excluded command either
|
|
||||||
if (control.exclusions.contains(control.command) || control.exclusions.contains(resolved)) {
|
|
||||||
writeStatus(STATUS_EXCLUDED_COMMAND);
|
|
||||||
System.err.println("ERROR: Command '" + control.command + "' is not available");
|
|
||||||
return ExitStatusException.EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// an automatic dependency download and purge that the project is
|
|
||||||
// configured for runs before the command, exactly like it would on
|
|
||||||
// the command line
|
|
||||||
if (executor instanceof BaseProject project) {
|
|
||||||
project.performAutoDownloadPurgeIfEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
// the command's arguments come from the control file, they are
|
|
||||||
// never interpreted as additional commands or as global flags
|
|
||||||
executor.arguments().clear();
|
|
||||||
executor.arguments().addAll(control.arguments);
|
|
||||||
try {
|
|
||||||
executor.executeCommand(resolved);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
executor.exitStatus(ExitStatusException.EXIT_FAILURE);
|
|
||||||
System.err.println();
|
|
||||||
if (control.stacktrace) {
|
|
||||||
System.err.println(ExceptionUtils.getExceptionStackTrace(e));
|
|
||||||
} else if (e.getMessage() != null) {
|
|
||||||
System.err.println(e.getMessage());
|
|
||||||
} else {
|
|
||||||
System.err.println(e.getClass().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return executor.exitStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void writeStatus(String status) {
|
|
||||||
var status_file = System.getenv(ENV_STATUS_FILE);
|
|
||||||
if (status_file != null) {
|
|
||||||
try {
|
|
||||||
Files.writeString(Path.of(status_file), status);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// the caller falls back to the exit status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,6 @@ import java.security.*;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
@ -53,7 +52,6 @@ public class PublishOperation extends AbstractOperation<PublishOperation> {
|
||||||
|
|
||||||
private ZonedDateTime moment_ = null;
|
private ZonedDateTime moment_ = null;
|
||||||
private final List<Repository> repositories_ = new ArrayList<>();
|
private final List<Repository> repositories_ = new ArrayList<>();
|
||||||
private final List<Repository> dependencyRepositories_ = new ArrayList<>();
|
|
||||||
private final DependencyScopes dependencies_ = new DependencyScopes();
|
private final DependencyScopes dependencies_ = new DependencyScopes();
|
||||||
private PublishInfo info_ = new PublishInfo();
|
private PublishInfo info_ = new PublishInfo();
|
||||||
private PublishProperties publishProperties_ = new PublishProperties();
|
private PublishProperties publishProperties_ = new PublishProperties();
|
||||||
|
|
@ -80,7 +78,6 @@ public class PublishOperation extends AbstractOperation<PublishOperation> {
|
||||||
}
|
}
|
||||||
|
|
||||||
executeValidateArtifacts();
|
executeValidateArtifacts();
|
||||||
executeResolveVersionlessDependencies();
|
|
||||||
|
|
||||||
var actual_version = info().version();
|
var actual_version = info().version();
|
||||||
|
|
||||||
|
|
@ -106,101 +103,6 @@ public class PublishOperation extends AbstractOperation<PublishOperation> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of the {@link #execute} operation, freezes the versions of
|
|
||||||
* dependencies that were declared without one, so that the published
|
|
||||||
* POM is always complete.
|
|
||||||
* <p>
|
|
||||||
* Dependencies that are covered by a BOM that applies to their scope
|
|
||||||
* stay version-less, the dependency management imports of the POM
|
|
||||||
* supply their versions. They are frozen anyway when the flattened
|
|
||||||
* dependency management of the POM would supply a different version
|
|
||||||
* than the BOMs that apply to their scope, or when a
|
|
||||||
* {@code bld.override} property supplies their version, since the POM
|
|
||||||
* can't reflect either. Uncovered dependencies are frozen to their
|
|
||||||
* latest version.
|
|
||||||
* <p>
|
|
||||||
* BOMs that were declared without a version are frozen too, to their
|
|
||||||
* {@code bld.override} version or to their latest version.
|
|
||||||
*
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
protected void executeResolveVersionlessDependencies() {
|
|
||||||
// freeze the versions of the declared BOMs, a version from a
|
|
||||||
// bld.override property is used by the build itself and takes
|
|
||||||
// precedence, version-less BOMs resolve to their latest version
|
|
||||||
var base_resolution = new VersionResolution(properties());
|
|
||||||
for (var scope : List.of(Scope.compile, Scope.runtime, Scope.provided)) {
|
|
||||||
var scoped_dependencies = dependencies().get(scope);
|
|
||||||
if (scoped_dependencies == null || scoped_dependencies.boms().isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var declared_boms = List.copyOf(scoped_dependencies.boms());
|
|
||||||
scoped_dependencies.boms().clear();
|
|
||||||
for (var bom : declared_boms) {
|
|
||||||
var version = base_resolution.overrideVersion(bom);
|
|
||||||
if (version.equals(VersionNumber.UNKNOWN)) {
|
|
||||||
version = new DependencyResolver(base_resolution, artifactRetriever(), dependencyRepositories(), bom).latestVersion();
|
|
||||||
}
|
|
||||||
if (!version.equals(bom.version())) {
|
|
||||||
bom = new Bom(bom.groupId(), bom.artifactId(), version);
|
|
||||||
}
|
|
||||||
scoped_dependencies.boms().add(bom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the published POM flattens all the BOM imports into a single
|
|
||||||
// dependency management list, compute the versions that this list
|
|
||||||
// supplies so that scoped differences can be frozen explicitly
|
|
||||||
var flattened_boms = new LinkedHashMap<String, Bom>();
|
|
||||||
for (var scope : List.of(Scope.compile, Scope.runtime, Scope.provided)) {
|
|
||||||
var scoped_dependencies = dependencies().get(scope);
|
|
||||||
if (scoped_dependencies != null) {
|
|
||||||
for (var bom : scoped_dependencies.boms()) {
|
|
||||||
flattened_boms.putIfAbsent(bom.toArtifactString(), bom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var flattened_resolution = new VersionResolution(properties(), artifactRetriever(), dependencyRepositories(), flattened_boms.values());
|
|
||||||
|
|
||||||
// freeze the versions of version-less dependencies that no
|
|
||||||
// applicable BOM covers
|
|
||||||
for (var scope : List.of(Scope.compile, Scope.runtime, Scope.provided)) {
|
|
||||||
var scoped_dependencies = dependencies().get(scope);
|
|
||||||
if (scoped_dependencies == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var resolution = new VersionResolution(properties(), artifactRetriever(), dependencyRepositories(), dependencies().effectiveBoms(scope));
|
|
||||||
for (var dependency : List.copyOf(scoped_dependencies)) {
|
|
||||||
if (!dependency.version().equals(VersionNumber.UNKNOWN)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// a version from a bld.override property is used by the
|
|
||||||
// build itself, it's frozen into the POM even when a BOM
|
|
||||||
// covers the dependency since the import can't reflect it
|
|
||||||
var version = resolution.versionOverrides().get(dependency.toArtifactString());
|
|
||||||
if (version == null) {
|
|
||||||
if (resolution.coversDependency(dependency)) {
|
|
||||||
// the dependency can only stay version-less when the
|
|
||||||
// flattened dependency management of the POM supplies
|
|
||||||
// the same version as the BOMs that apply to its scope
|
|
||||||
var scoped_version = resolution.overrideVersion(dependency);
|
|
||||||
if (scoped_version.equals(flattened_resolution.overrideVersion(dependency))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
version = scoped_version;
|
|
||||||
} else {
|
|
||||||
version = new DependencyResolver(resolution, artifactRetriever(), dependencyRepositories(), dependency).latestVersion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scoped_dependencies.add(new Dependency(dependency.groupId(), dependency.artifactId(), version,
|
|
||||||
dependency.classifier(), dependency.type(), dependency.exclusions(), dependency.parent()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Part of the {@link #execute} operation, validates the publishing artifacts.
|
* Part of the {@link #execute} operation, validates the publishing artifacts.
|
||||||
*
|
*
|
||||||
|
|
@ -723,7 +625,6 @@ public class PublishOperation extends AbstractOperation<PublishOperation> {
|
||||||
offline(project.offline());
|
offline(project.offline());
|
||||||
properties(project.properties());
|
properties(project.properties());
|
||||||
artifactRetriever(project.artifactRetriever());
|
artifactRetriever(project.artifactRetriever());
|
||||||
dependencyRepositories(project.repositories());
|
|
||||||
dependencies().include(project.dependencies());
|
dependencies().include(project.dependencies());
|
||||||
artifacts(List.of(
|
artifacts(List.of(
|
||||||
new PublishArtifact(new File(project.buildDistDirectory(), project.jarFileName()), "", TYPE_JAR),
|
new PublishArtifact(new File(project.buildDistDirectory(), project.jarFileName()), "", TYPE_JAR),
|
||||||
|
|
@ -832,33 +733,6 @@ public class PublishOperation extends AbstractOperation<PublishOperation> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a list of repositories to resolve the dependencies against.
|
|
||||||
* <p>
|
|
||||||
* These are used to resolve the versions of dependencies that were
|
|
||||||
* declared without one, they are not publication targets.
|
|
||||||
*
|
|
||||||
* @param repositories a list of repositories against which dependencies will be resolved
|
|
||||||
* @return this operation instance
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public PublishOperation dependencyRepositories(List<Repository> repositories) {
|
|
||||||
dependencyRepositories_.addAll(repositories);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the repositories that dependencies will be resolved against.
|
|
||||||
* <p>
|
|
||||||
* This is a modifiable list that can be retrieved and changed.
|
|
||||||
*
|
|
||||||
* @return the repositories used for dependency resolution
|
|
||||||
* @since 2.4.0
|
|
||||||
*/
|
|
||||||
public List<Repository> dependencyRepositories() {
|
|
||||||
return dependencyRepositories_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides scoped dependencies to reference in the publication.
|
* Provides scoped dependencies to reference in the publication.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -31,47 +31,15 @@ public class UpdatesOperation extends AbstractOperation<UpdatesOperation> {
|
||||||
*/
|
*/
|
||||||
public void execute() {
|
public void execute() {
|
||||||
var resolution = new VersionResolution(properties());
|
var resolution = new VersionResolution(properties());
|
||||||
|
|
||||||
var scopes = new ArrayList<Scope>();
|
|
||||||
var dependencies = new ArrayList<Dependency>();
|
|
||||||
for (var entry : dependencies_.entrySet()) {
|
|
||||||
var scoped_dependencies = entry.getValue();
|
|
||||||
for (var bom : scoped_dependencies.boms()) {
|
|
||||||
scopes.add(entry.getKey());
|
|
||||||
dependencies.add(bom);
|
|
||||||
}
|
|
||||||
|
|
||||||
// dependencies that are declared without a version and that are
|
|
||||||
// covered by a BOM that applies to their scope have their version
|
|
||||||
// pinned by that BOM, the BOM update itself carries their update
|
|
||||||
// signal, unless a bld.override supplies the version and takes
|
|
||||||
// that control away from the BOM
|
|
||||||
var scope_resolution = new VersionResolution(properties(), artifactRetriever(), repositories(), dependencies_.effectiveBoms(entry.getKey()));
|
|
||||||
for (var dependency : scoped_dependencies) {
|
|
||||||
if (VersionNumber.UNKNOWN.equals(dependency.version()) &&
|
|
||||||
scope_resolution.coversDependency(dependency) &&
|
|
||||||
!scope_resolution.versionOverrides().containsKey(dependency.toArtifactString())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
scopes.add(entry.getKey());
|
|
||||||
// an overridden version is the one the build uses, it's the
|
|
||||||
// baseline that update checks compare against
|
|
||||||
dependencies.add(scope_resolution.overrideDependency(dependency));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var latest_versions = new ParallelDependencyResolver(resolution, artifactRetriever(), repositories()).resolveLatestVersions(dependencies);
|
|
||||||
|
|
||||||
var result = new DependencyScopes();
|
var result = new DependencyScopes();
|
||||||
for (var i = 0; i < dependencies.size(); ++i) {
|
for (var entry : dependencies_.entrySet()) {
|
||||||
var dependency = dependencies.get(i);
|
var scope = entry.getKey();
|
||||||
var latest = latest_versions.get(i);
|
for (var dependency : entry.getValue()) {
|
||||||
if (latest.compareTo(dependency.version()) > 0) {
|
var latest = new DependencyResolver(resolution, artifactRetriever(), repositories(), dependency).latestVersion();
|
||||||
if (dependency instanceof Bom) {
|
if (latest.compareTo(dependency.version()) > 0) {
|
||||||
result.scope(scopes.get(i)).include(new Bom(dependency.groupId(), dependency.artifactId(), latest));
|
var latest_dependency = new Dependency(dependency.groupId(), dependency.artifactId(), latest,
|
||||||
} else {
|
dependency.classifier(), dependency.type());
|
||||||
result.scope(scopes.get(i)).include(new Dependency(dependency.groupId(), dependency.artifactId(), latest,
|
result.scope(scope).include(latest_dependency);
|
||||||
dependency.classifier(), dependency.type()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,9 +53,6 @@ public class UpdatesOperation extends AbstractOperation<UpdatesOperation> {
|
||||||
for (var entry : result.entrySet()) {
|
for (var entry : result.entrySet()) {
|
||||||
var scope = entry.getKey();
|
var scope = entry.getKey();
|
||||||
System.out.println(scope + ":");
|
System.out.println(scope + ":");
|
||||||
for (var bom : entry.getValue().boms()) {
|
|
||||||
System.out.println(" " + bom);
|
|
||||||
}
|
|
||||||
for (var dependency : entry.getValue()) {
|
for (var dependency : entry.getValue()) {
|
||||||
System.out.println(" " + dependency);
|
System.out.println(" " + dependency);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ import rife.tools.StringUtils;
|
||||||
import rife.tools.exceptions.FileUtilsErrorException;
|
import rife.tools.exceptions.FileUtilsErrorException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static rife.bld.dependencies.Dependency.TYPE_JAR;
|
import static rife.bld.dependencies.Dependency.TYPE_JAR;
|
||||||
|
|
@ -154,7 +152,6 @@ public class PomBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dependencies() != null && !dependencies().isEmpty()) {
|
if (dependencies() != null && !dependencies().isEmpty()) {
|
||||||
addDependencyManagement(t);
|
|
||||||
addDependencies(t, Scope.compile);
|
addDependencies(t, Scope.compile);
|
||||||
addDependencies(t, Scope.runtime);
|
addDependencies(t, Scope.runtime);
|
||||||
addDependencies(t, Scope.provided);
|
addDependencies(t, Scope.provided);
|
||||||
|
|
@ -175,32 +172,6 @@ public class PomBuilder {
|
||||||
FileUtils.writeString(pomBuilder.build(), file);
|
FileUtils.writeString(pomBuilder.build(), file);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDependencyManagement(Template t) {
|
|
||||||
var boms = new LinkedHashMap<String, Bom>();
|
|
||||||
for (var scope : List.of(Scope.compile, Scope.runtime, Scope.provided)) {
|
|
||||||
var scoped_dependencies = dependencies().get(scope);
|
|
||||||
if (scoped_dependencies == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (var bom : scoped_dependencies.boms()) {
|
|
||||||
if (!bom.version().equals(VersionNumber.UNKNOWN)) {
|
|
||||||
boms.putIfAbsent(bom.toArtifactString(), bom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (boms.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var bom : boms.values()) {
|
|
||||||
t.setValueEncoded("dependency-management-groupId", bom.groupId());
|
|
||||||
t.setValueEncoded("dependency-management-artifactId", bom.artifactId());
|
|
||||||
t.setValueEncoded("dependency-management-version", bom.version());
|
|
||||||
t.appendBlock("dependency-management", "dependency-management-entry");
|
|
||||||
}
|
|
||||||
t.setBlock("dependency-management-tag");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addDependencies(Template t, Scope scope) {
|
private void addDependencies(Template t, Scope scope) {
|
||||||
var scoped_dependencies = dependencies().scope(scope);
|
var scoped_dependencies = dependencies().scope(scope);
|
||||||
if (!scoped_dependencies.isEmpty()) {
|
if (!scoped_dependencies.isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ public class Wrapper {
|
||||||
|
|
||||||
public static final String BUILD_ARGUMENT = "--build";
|
public static final String BUILD_ARGUMENT = "--build";
|
||||||
public static final String OFFLINE_ARGUMENT = "--offline";
|
public static final String OFFLINE_ARGUMENT = "--offline";
|
||||||
public static final String USE_STDERR_ARGUMENT = "--use-stderr";
|
|
||||||
|
|
||||||
public static final String WRAPPER_PREFIX = "bld-wrapper";
|
public static final String WRAPPER_PREFIX = "bld-wrapper";
|
||||||
public static final String WRAPPER_PROPERTIES = WRAPPER_PREFIX + ".properties";
|
public static final String WRAPPER_PROPERTIES = WRAPPER_PREFIX + ".properties";
|
||||||
|
|
@ -98,11 +97,6 @@ public class Wrapper {
|
||||||
* @since 1.5
|
* @since 1.5
|
||||||
*/
|
*/
|
||||||
public static void main(String[] arguments) {
|
public static void main(String[] arguments) {
|
||||||
if (Arrays.asList(arguments).contains(USE_STDERR_ARGUMENT)) {
|
|
||||||
// the wrapper diagnostics are sent to standard error so that
|
|
||||||
// standard output stays free for the MCP protocol
|
|
||||||
System.setOut(System.err);
|
|
||||||
}
|
|
||||||
System.exit(new Wrapper().installAndLaunch(new ArrayList<>(Arrays.asList(arguments))));
|
System.exit(new Wrapper().installAndLaunch(new ArrayList<>(Arrays.asList(arguments))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,16 +89,12 @@ public class WrapperExtensionResolver {
|
||||||
|
|
||||||
private Set<String> transferExtensionDependencies() {
|
private Set<String> transferExtensionDependencies() {
|
||||||
var filenames = new HashSet<String>();
|
var filenames = new HashSet<String>();
|
||||||
var roots = new ArrayList<Dependency>();
|
var dependencies = new DependencySet();
|
||||||
for (var d : dependencies_) {
|
for (var d : dependencies_) {
|
||||||
if (d != null) {
|
if (d != null) {
|
||||||
roots.add(d);
|
dependencies.addAll(new DependencyResolver(resolution_, retriever_, repositories_, d).getAllDependencies(Scope.compile, Scope.runtime));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!roots.isEmpty()) {
|
|
||||||
System.out.println("Analyzing bld extension dependencies...");
|
|
||||||
}
|
|
||||||
var dependencies = new ParallelDependencyResolver(resolution_, retriever_, repositories_).resolveAllDependencies(roots, Scope.compile, Scope.runtime);
|
|
||||||
if (!dependencies.isEmpty()) {
|
if (!dependencies.isEmpty()) {
|
||||||
ensurePrintedHeader();
|
ensurePrintedHeader();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
2.4.0-SNAPSHOT
|
2.3.1-SNAPSHOT
|
||||||
|
|
@ -32,24 +32,6 @@
|
||||||
</properties>
|
</properties>
|
||||||
<!--/b-->
|
<!--/b-->
|
||||||
|
|
||||||
<!--v dependency-management-tag--><!--/v-->
|
|
||||||
<!--b dependency-management-tag-->
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<!--v dependency-management--><!--/v-->
|
|
||||||
<!--b dependency-management-entry-->
|
|
||||||
<dependency>
|
|
||||||
<groupId><!--v dependency-management-groupId--><!--/v--></groupId>
|
|
||||||
<artifactId><!--v dependency-management-artifactId--><!--/v--></artifactId>
|
|
||||||
<version><!--v dependency-management-version--><!--/v--></version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
<!--/b-->
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
<!--/b-->
|
|
||||||
|
|
||||||
<!--v dependencies-tag--><!--/v-->
|
<!--v dependencies-tag--><!--/v-->
|
||||||
<!--b dependencies-tag-->
|
<!--b dependencies-tag-->
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
||||||
|
|
@ -1,319 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2026 Erik C. Thauvin (https://erik.thauvin.net/)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.*;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
import rife.bld.dependencies.Scope;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class BaseProjectTest {
|
|
||||||
private static Method ADD_LOCAL_JARS;
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
private BaseProject project;
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
static void initReflection() throws Exception {
|
|
||||||
ADD_LOCAL_JARS = BaseProject.class.getDeclaredMethod("addLocalJars", List.class, String.class);
|
|
||||||
ADD_LOCAL_JARS.setAccessible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void beforeEach() {
|
|
||||||
project = new BaseProject();
|
|
||||||
project.workDirectory = tempDir.toFile();
|
|
||||||
project.createProjectStructure();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void invokeAddLocalJars(List<File> jars, String path) throws Exception {
|
|
||||||
ADD_LOCAL_JARS.invoke(project, jars, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nested
|
|
||||||
@DisplayName("local(Path) and local(File)")
|
|
||||||
class LocalDependencyTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("local(Path) relative should resolve via workDirectory")
|
|
||||||
void localPathRelative() throws Exception {
|
|
||||||
var jar = Files.createFile(tempDir.resolve("a.jar"));
|
|
||||||
var dep = project.local(Path.of("a.jar"));
|
|
||||||
|
|
||||||
assertAll(
|
|
||||||
() -> assertNotNull(dep, "local(Path) should not return null"),
|
|
||||||
() -> assertEquals("a.jar", dep.path(), "relative Path should be kept as-is")
|
|
||||||
);
|
|
||||||
|
|
||||||
project.dependencies().scope(Scope.compile).include(dep);
|
|
||||||
assertTrue(project.compileClasspathJars().stream()
|
|
||||||
.anyMatch(f -> f.getAbsolutePath().equals(jar.toFile().getAbsolutePath())),
|
|
||||||
"compileClasspathJars should resolve relative path against workDirectory");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("local(Path) absolute should be kept absolute")
|
|
||||||
void localPathAbsolute() throws Exception {
|
|
||||||
var jar = Files.createFile(tempDir.resolve("abs.jar"));
|
|
||||||
var dep = project.local(jar);
|
|
||||||
|
|
||||||
assertAll(
|
|
||||||
() -> assertNotNull(dep, "local(Path) absolute should not return null"),
|
|
||||||
() -> assertEquals(jar.toString(), dep.path(), "absolute Path should be kept absolute")
|
|
||||||
);
|
|
||||||
|
|
||||||
project.dependencies().scope(Scope.compile).include(dep);
|
|
||||||
assertTrue(project.compileClasspathJars().stream()
|
|
||||||
.anyMatch(f -> f.getName().equals("abs.jar")),
|
|
||||||
"compileClasspathJars should include absolute Path dependency");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("local(File) relative file")
|
|
||||||
void localFileRelative() {
|
|
||||||
var dep = project.local(new File("b.jar"));
|
|
||||||
|
|
||||||
assertAll(
|
|
||||||
() -> assertNotNull(dep, "local(File) should not return null"),
|
|
||||||
() -> assertTrue(dep.path().endsWith("b.jar"), "should end with file name"),
|
|
||||||
() -> assertTrue(Path.of(dep.path()).isAbsolute(), "File overload uses getAbsolutePath()")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("local(File) absolute file")
|
|
||||||
void localFileAbsolute() throws Exception {
|
|
||||||
var jar = Files.createFile(tempDir.resolve("c.jar"));
|
|
||||||
var dep = project.local(jar.toFile());
|
|
||||||
|
|
||||||
assertAll(
|
|
||||||
() -> assertNotNull(dep, "local(File) absolute should not return null"),
|
|
||||||
() -> assertEquals(jar.toFile().getAbsolutePath(), dep.path(),
|
|
||||||
"should use File.getAbsolutePath()")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("local(File) directory should scan jars excluding sources/javadoc")
|
|
||||||
void localFileDirectory() throws Exception {
|
|
||||||
var dir = Files.createDirectory(tempDir.resolve("libs-dep-filter"));
|
|
||||||
Files.createFile(dir.resolve("one.jar"));
|
|
||||||
Files.createFile(dir.resolve("one-sources.jar"));
|
|
||||||
Files.createFile(dir.resolve("one-JAVADOC.jar"));
|
|
||||||
|
|
||||||
var dep = project.local(dir.toFile());
|
|
||||||
project.dependencies().scope(Scope.compile).include(dep);
|
|
||||||
|
|
||||||
var cp = project.compileClasspathJars();
|
|
||||||
assertAll(
|
|
||||||
() -> assertTrue(cp.stream().anyMatch(
|
|
||||||
f -> f.getName().equals("one.jar")), "regular jar should be included"),
|
|
||||||
() -> assertFalse(cp.stream().anyMatch(f -> f.getName().equals("one-sources.jar")),
|
|
||||||
"sources jar should be excluded (case-insensitive)"),
|
|
||||||
() -> assertFalse(cp.stream().anyMatch(
|
|
||||||
f -> f.getName().toLowerCase().contains("javadoc")),
|
|
||||||
"javadoc jar should be excluded")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("local(Path) and local(String) should behave same")
|
|
||||||
void parityWithStringOverload() throws Exception {
|
|
||||||
Files.createFile(tempDir.resolve("parity.jar"));
|
|
||||||
|
|
||||||
var fromString = project.local("parity.jar");
|
|
||||||
var fromPath = project.local(Path.of("parity.jar"));
|
|
||||||
var fromFile = project.local(new File("parity.jar"));
|
|
||||||
|
|
||||||
assertAll(
|
|
||||||
() -> assertEquals(fromString.path(), fromPath.path(),
|
|
||||||
"String and Path overloads should have same path()"),
|
|
||||||
() -> assertTrue(fromFile.path().endsWith("parity.jar"),
|
|
||||||
"File overload is absolute but ends with same name"),
|
|
||||||
() -> assertTrue(Path.of(fromFile.path()).isAbsolute(),
|
|
||||||
"File overload should be absolute")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nested
|
|
||||||
@DisplayName("addLocalJars - private implementation")
|
|
||||||
class LocalJarsTest {
|
|
||||||
@Test
|
|
||||||
@DisplayName("should add relative file path")
|
|
||||||
void relativeFile() throws Exception {
|
|
||||||
Files.createFile(tempDir.resolve("my.jar"));
|
|
||||||
var jars = new ArrayList<File>();
|
|
||||||
invokeAddLocalJars(jars, "my.jar");
|
|
||||||
assertEquals(1, jars.size(), "relative file that exists should be added");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("should add absolute file path")
|
|
||||||
void absoluteFile() throws Exception {
|
|
||||||
var jar = Files.createFile(tempDir.resolve("abs2.jar"));
|
|
||||||
var jars = new ArrayList<File>();
|
|
||||||
invokeAddLocalJars(jars, jar.toAbsolutePath().toString());
|
|
||||||
assertEquals(1, jars.size(), "absolute file that exists should be added");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("should resolve relative to workDirectory")
|
|
||||||
void relativeToWorkDirectory() throws Exception {
|
|
||||||
var sub = Files.createDirectory(tempDir.resolve("sub"));
|
|
||||||
var jar = Files.createFile(sub.resolve("x.jar"));
|
|
||||||
var jars = new ArrayList<File>();
|
|
||||||
invokeAddLocalJars(jars, "sub/x.jar");
|
|
||||||
assertEquals(jar.toFile().getAbsoluteFile(), jars.get(0).getAbsoluteFile(),
|
|
||||||
"should resolve against workDirectory");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("should scan directory for jars only")
|
|
||||||
void directoryScansJars() throws Exception {
|
|
||||||
var libDir = Files.createDirectory(tempDir.resolve("libs-jars-scan"));
|
|
||||||
Files.createFile(libDir.resolve("a.jar"));
|
|
||||||
Files.createFile(libDir.resolve("b.jar"));
|
|
||||||
Files.createFile(libDir.resolve("not-a-jar.txt"));
|
|
||||||
var jars = new ArrayList<File>();
|
|
||||||
invokeAddLocalJars(jars, "libs-jars-scan");
|
|
||||||
assertEquals(2, jars.size(), "should only include .jar files");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("should exclude -sources and -javadoc jars in directory")
|
|
||||||
void directoryExcludesSourcesAndJavadoc() throws Exception {
|
|
||||||
var libDir = Files.createDirectory(tempDir.resolve("libs2-filter"));
|
|
||||||
Files.createFile(libDir.resolve("foo.jar"));
|
|
||||||
Files.createFile(libDir.resolve("foo-sources.jar"));
|
|
||||||
Files.createFile(libDir.resolve("foo-javadoc.jar"));
|
|
||||||
var jars = new ArrayList<File>();
|
|
||||||
invokeAddLocalJars(jars, "libs2-filter");
|
|
||||||
assertAll(
|
|
||||||
() -> assertEquals(1, jars.size(), "should filter out -sources and -javadoc"),
|
|
||||||
() -> assertEquals("foo.jar", jars.get(0).getName(), "only foo.jar should remain")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nested
|
|
||||||
@DisplayName("integration via public classpath methods")
|
|
||||||
class Integration {
|
|
||||||
@Test
|
|
||||||
@DisplayName("compileClasspathJars should include local file dependency")
|
|
||||||
void viaCompileClasspath() throws Exception {
|
|
||||||
var myJar = Files.createFile(tempDir.resolve("custom.jar"));
|
|
||||||
project.dependencies().scope(Scope.compile).include(project.local("custom.jar"));
|
|
||||||
var classpath = project.compileClasspathJars();
|
|
||||||
assertTrue(classpath.stream().anyMatch(
|
|
||||||
f -> f.getAbsolutePath().equals(myJar.toFile().getAbsolutePath())),
|
|
||||||
"compileClasspathJars should include local file dependency");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nested
|
|
||||||
@DisplayName("verbose branch")
|
|
||||||
class VerboseBranch {
|
|
||||||
@BeforeEach
|
|
||||||
void enableVerbose() {
|
|
||||||
project = new BaseProject() {
|
|
||||||
@Override
|
|
||||||
public boolean verbose() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
project.workDirectory = tempDir.toFile();
|
|
||||||
project.createProjectStructure();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("should skip non-existent path without throwing, even verbose")
|
|
||||||
void nonExistentPathSkipped() {
|
|
||||||
var jars = new ArrayList<File>();
|
|
||||||
assertDoesNotThrow(() -> invokeAddLocalJars(jars, "does-not-exist-verbose"),
|
|
||||||
"should not throw for missing path");
|
|
||||||
assertTrue(jars.isEmpty(), "non-existent path should be skipped");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nested
|
|
||||||
@DisplayName("localModule(Path) and localModule(File)")
|
|
||||||
class LocalModuleTest {
|
|
||||||
@Test
|
|
||||||
@DisplayName("localModule(Path) relative")
|
|
||||||
void localModulePathRelative() {
|
|
||||||
var mod = project.localModule(Path.of("mod.jar"));
|
|
||||||
assertAll(
|
|
||||||
() -> assertNotNull(mod, "should not return null"),
|
|
||||||
() -> assertEquals("mod.jar", mod.path(), "relative path kept as-is")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("localModule(Path) absolute")
|
|
||||||
void localModulePathAbsolute() throws Exception {
|
|
||||||
var jar = Files.createFile(tempDir.resolve("modAbs.jar"));
|
|
||||||
var mod = project.localModule(jar);
|
|
||||||
assertAll(
|
|
||||||
() -> assertNotNull(mod, "should not return null"),
|
|
||||||
() -> assertEquals(jar.toString(), mod.path(), "absolute path kept absolute")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("localModule(File) absolute")
|
|
||||||
void localModuleFileAbsolute() throws Exception {
|
|
||||||
var jar = Files.createFile(tempDir.resolve("mod2.jar"));
|
|
||||||
var mod = project.localModule(jar.toFile());
|
|
||||||
assertAll(
|
|
||||||
() -> assertNotNull(mod, "should not return null"),
|
|
||||||
() -> assertEquals(jar.toFile().getAbsolutePath(), mod.path(),
|
|
||||||
"should use getAbsolutePath()")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("localModule(File) directory scan")
|
|
||||||
void localModuleFileDirectory() throws Exception {
|
|
||||||
var dir = Files.createDirectory(tempDir.resolve("mods-filter"));
|
|
||||||
Files.createFile(dir.resolve("m1.jar"));
|
|
||||||
Files.createFile(dir.resolve("m1-javadoc.jar"));
|
|
||||||
|
|
||||||
var mod = project.localModule(dir.toFile());
|
|
||||||
project.dependencies().scope(Scope.compile).include(mod);
|
|
||||||
|
|
||||||
var mp = project.compileModulePathJars();
|
|
||||||
assertAll(
|
|
||||||
() -> assertTrue(mp.stream().anyMatch(f -> f.getName().equals("m1.jar")),
|
|
||||||
"regular jar should be in module path"),
|
|
||||||
() -> assertFalse(mp.stream().anyMatch(f -> f.getName().contains("javadoc")),
|
|
||||||
"javadoc jar should be excluded")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("all three overloads are consistent")
|
|
||||||
void overloadsConsistent() throws Exception {
|
|
||||||
Files.createFile(tempDir.resolve("cons.jar"));
|
|
||||||
|
|
||||||
var s = project.localModule("cons.jar");
|
|
||||||
var p = project.localModule(Path.of("cons.jar"));
|
|
||||||
var f = project.localModule(new File("cons.jar"));
|
|
||||||
|
|
||||||
assertAll(
|
|
||||||
() -> assertEquals(s.path(), p.path(), "String and Path overloads equal"),
|
|
||||||
() -> assertTrue(f.path().endsWith("cons.jar"), "File overload ends with same name")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -4,20 +4,15 @@
|
||||||
*/
|
*/
|
||||||
package rife.bld.dependencies;
|
package rife.bld.dependencies;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpServer;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import rife.ioc.HierarchicalProperties;
|
import rife.ioc.HierarchicalProperties;
|
||||||
import rife.tools.FileUtils;
|
import rife.tools.FileUtils;
|
||||||
import rife.tools.StringUtils;
|
import rife.tools.StringUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static rife.bld.dependencies.Dependency.CLASSIFIER_JAVADOC;
|
import static rife.bld.dependencies.Dependency.CLASSIFIER_JAVADOC;
|
||||||
|
|
@ -2346,98 +2341,4 @@ public class TestDependencyResolver {
|
||||||
FileUtils.deleteDirectory(tmp2);
|
FileUtils.deleteDirectory(tmp2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetAllDependenciesParallelPomPrefetching() throws Exception {
|
|
||||||
var max_concurrent_retrievals = new AtomicInteger();
|
|
||||||
var server = createPomServer(max_concurrent_retrievals);
|
|
||||||
server.start();
|
|
||||||
try {
|
|
||||||
var repositories = List.of(new Repository("http://localhost:" + server.getAddress().getPort() + "/"));
|
|
||||||
var root = new Dependency("com.example", "root", new VersionNumber(1, 0, 0));
|
|
||||||
|
|
||||||
// the caching retriever enables parallel POM prefetching
|
|
||||||
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.cachingInstance(), repositories, root);
|
|
||||||
assertEquals(StringUtils.convertLineSeparator("""
|
|
||||||
com.example:root:1.0.0
|
|
||||||
com.example:child1:1.0.0
|
|
||||||
com.example:child2:1.0.0
|
|
||||||
com.example:child3:1.0.0
|
|
||||||
com.example:child4:1.0.0
|
|
||||||
com.example:child5:1.0.0
|
|
||||||
com.example:child6:1.0.0
|
|
||||||
com.example:shared:1.0.0"""), StringUtils.join(resolver.getAllDependencies(compile), System.lineSeparator()));
|
|
||||||
assertTrue(max_concurrent_retrievals.get() > 1, "expected concurrent POM retrievals, max was " + max_concurrent_retrievals.get());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetAllDependenciesSequentialWithoutCachingRetriever() throws Exception {
|
|
||||||
var max_concurrent_retrievals = new AtomicInteger();
|
|
||||||
var server = createPomServer(max_concurrent_retrievals);
|
|
||||||
server.start();
|
|
||||||
try {
|
|
||||||
var repositories = List.of(new Repository("http://localhost:" + server.getAddress().getPort() + "/"));
|
|
||||||
var root = new Dependency("com.example", "root", new VersionNumber(1, 0, 0));
|
|
||||||
|
|
||||||
// the uncached retriever disables prefetching, retrievals stay sequential
|
|
||||||
var resolver = new DependencyResolver(VersionResolution.dummy(), ArtifactRetriever.instance(), repositories, root);
|
|
||||||
assertEquals(8, resolver.getAllDependencies(compile).size());
|
|
||||||
assertEquals(1, max_concurrent_retrievals.get(), "expected sequential POM retrievals, max was " + max_concurrent_retrievals.get());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HttpServer createPomServer(AtomicInteger maxConcurrentRetrievals)
|
|
||||||
throws IOException {
|
|
||||||
var graph = new HashMap<String, List<String>>();
|
|
||||||
graph.put("root", List.of("child1", "child2", "child3", "child4", "child5", "child6"));
|
|
||||||
graph.put("child1", List.of("shared"));
|
|
||||||
|
|
||||||
var active_retrievals = new AtomicInteger();
|
|
||||||
var server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
|
|
||||||
server.createContext("/", exchange -> {
|
|
||||||
var active = active_retrievals.incrementAndGet();
|
|
||||||
maxConcurrentRetrievals.accumulateAndGet(active, Math::max);
|
|
||||||
try {
|
|
||||||
// delay the response so that parallel retrievals overlap
|
|
||||||
Thread.sleep(100);
|
|
||||||
|
|
||||||
// serve the POM of the artifact in the request path
|
|
||||||
var segments = exchange.getRequestURI().getPath().split("/");
|
|
||||||
var artifact = segments[segments.length - 3];
|
|
||||||
var body = buildPom(artifact, graph.getOrDefault(artifact, List.of())).getBytes();
|
|
||||||
exchange.sendResponseHeaders(200, body.length);
|
|
||||||
exchange.getResponseBody().write(body);
|
|
||||||
exchange.close();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
} finally {
|
|
||||||
active_retrievals.decrementAndGet();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
server.setExecutor(Executors.newCachedThreadPool());
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String buildPom(String artifact, List<String> children) {
|
|
||||||
var dependencies = new StringBuilder();
|
|
||||||
for (var child : children) {
|
|
||||||
dependencies.append("<dependency><groupId>com.example</groupId><artifactId>")
|
|
||||||
.append(child)
|
|
||||||
.append("</artifactId><version>1.0.0</version></dependency>");
|
|
||||||
}
|
|
||||||
return """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>%s</artifactId>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
<dependencies>%s</dependencies>
|
|
||||||
</project>""".formatted(artifact, dependencies);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,11 @@
|
||||||
package rife.bld.dependencies;
|
package rife.bld.dependencies;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import rife.ioc.HierarchicalProperties;
|
|
||||||
import rife.tools.FileUtils;
|
|
||||||
import rife.tools.StringUtils;
|
import rife.tools.StringUtils;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static rife.bld.dependencies.TransferTestHelper.*;
|
|
||||||
import static rife.bld.dependencies.RepositoryTestHelper.getNextRepository;
|
import static rife.bld.dependencies.RepositoryTestHelper.getNextRepository;
|
||||||
import static rife.bld.dependencies.Scope.compile;
|
import static rife.bld.dependencies.Scope.compile;
|
||||||
import static rife.bld.dependencies.Scope.runtime;
|
import static rife.bld.dependencies.Scope.runtime;
|
||||||
|
|
@ -378,83 +373,4 @@ public class TestDependencySet {
|
||||||
└─ org.json:json:20250107
|
└─ org.json:json:20250107
|
||||||
"""), dependencies.generateTransitiveDependencyTree(VersionResolution.dummy(), ArtifactRetriever.instance(), RepositoryTestHelper.getNextRepositories(), compile, runtime));
|
"""), dependencies.generateTransitiveDependencyTree(VersionResolution.dummy(), ArtifactRetriever.instance(), RepositoryTestHelper.getNextRepositories(), compile, runtime));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCopyAndIncludePreserveAllContents() {
|
|
||||||
var set = new DependencySet()
|
|
||||||
.include(new Dependency("com.uwyn.rife2", "rife2", new VersionNumber(1, 9, 1)))
|
|
||||||
.include(new Bom("io.vertx", "vertx-stack-depchain", new VersionNumber(4, 5, 12)))
|
|
||||||
.include(new LocalDependency("lib/local.jar"))
|
|
||||||
.include(new LocalModule("lib/module.jar"));
|
|
||||||
|
|
||||||
var copy = new DependencySet(set);
|
|
||||||
assertEquals(set, copy);
|
|
||||||
assertEquals(set.boms(), copy.boms());
|
|
||||||
assertEquals(set.localDependencies(), copy.localDependencies());
|
|
||||||
assertEquals(set.localModules(), copy.localModules());
|
|
||||||
|
|
||||||
var included = new DependencySet().include(set);
|
|
||||||
assertEquals(set, included);
|
|
||||||
assertEquals(set.boms(), included.boms());
|
|
||||||
assertEquals(set.localDependencies(), included.localDependencies());
|
|
||||||
assertEquals(set.localModules(), included.localModules());
|
|
||||||
|
|
||||||
var scopes = new DependencyScopes();
|
|
||||||
scopes.scope(Scope.compile).include(set);
|
|
||||||
var scopes_copy = new DependencyScopes();
|
|
||||||
scopes_copy.include(scopes);
|
|
||||||
assertEquals(set.boms(), scopes_copy.scope(Scope.compile).boms());
|
|
||||||
assertEquals(set.localDependencies(), scopes_copy.scope(Scope.compile).localDependencies());
|
|
||||||
|
|
||||||
var scopes_ctor_copy = new DependencyScopes(scopes);
|
|
||||||
assertEquals(set.boms(), scopes_ctor_copy.scope(Scope.compile).boms());
|
|
||||||
assertEquals(set.localDependencies(), scopes_ctor_copy.scope(Scope.compile).localDependencies());
|
|
||||||
assertEquals(set.localModules(), scopes_ctor_copy.scope(Scope.compile).localModules());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testTransferIntoDirectoryParallel() throws Exception {
|
|
||||||
var max_concurrent_transfers = new AtomicInteger();
|
|
||||||
var server = createTransferServer(max_concurrent_transfers);
|
|
||||||
server.start();
|
|
||||||
var tmp = Files.createTempDirectory("transfers").toFile();
|
|
||||||
try {
|
|
||||||
var dependencies = createTransferDependencies(1, 6);
|
|
||||||
var repositories = List.of(transferRepository(server));
|
|
||||||
|
|
||||||
var artifacts = dependencies.transferIntoDirectory(new VersionResolution(null), ArtifactRetriever.instance(), repositories, tmp, tmp);
|
|
||||||
|
|
||||||
assertTransferredArtifacts(dependencies, artifacts, tmp);
|
|
||||||
assertTrue(max_concurrent_transfers.get() > 1, "expected concurrent transfers, max was " + max_concurrent_transfers.get());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
FileUtils.deleteDirectory(tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testTransferIntoDirectorySequential() throws Exception {
|
|
||||||
var max_concurrent_transfers = new AtomicInteger();
|
|
||||||
var server = createTransferServer(max_concurrent_transfers);
|
|
||||||
server.start();
|
|
||||||
var tmp = Files.createTempDirectory("transfers").toFile();
|
|
||||||
try {
|
|
||||||
var properties = new HierarchicalProperties();
|
|
||||||
properties.put(VersionResolution.PROPERTY_TRANSFER_PARALLELISM, "1");
|
|
||||||
var resolution = new VersionResolution(properties);
|
|
||||||
assertEquals(1, resolution.transferParallelism());
|
|
||||||
|
|
||||||
var dependencies = createTransferDependencies(1, 6);
|
|
||||||
var repositories = List.of(transferRepository(server));
|
|
||||||
|
|
||||||
var artifacts = dependencies.transferIntoDirectory(resolution, ArtifactRetriever.instance(), repositories, tmp, tmp);
|
|
||||||
|
|
||||||
assertTransferredArtifacts(dependencies, artifacts, tmp);
|
|
||||||
assertEquals(1, max_concurrent_transfers.get(), "expected sequential transfers, max was " + max_concurrent_transfers.get());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
FileUtils.deleteDirectory(tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.dependencies;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import rife.bld.dependencies.exceptions.DependencyTransferException;
|
|
||||||
import rife.tools.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
import static rife.bld.dependencies.TransferTestHelper.*;
|
|
||||||
|
|
||||||
public class TestDependencyTransferBatch {
|
|
||||||
@Test
|
|
||||||
void testTransferAcrossSets() throws Exception {
|
|
||||||
var max_concurrent_transfers = new AtomicInteger();
|
|
||||||
var server = createTransferServer(max_concurrent_transfers);
|
|
||||||
server.start();
|
|
||||||
var tmp1 = Files.createTempDirectory("transfers1").toFile();
|
|
||||||
var tmp2 = Files.createTempDirectory("transfers2").toFile();
|
|
||||||
try {
|
|
||||||
var set1 = createTransferDependencies(1, 3);
|
|
||||||
var set2 = createTransferDependencies(4, 6);
|
|
||||||
var repositories = List.of(transferRepository(server));
|
|
||||||
|
|
||||||
var batch = new DependencyTransferBatch();
|
|
||||||
batch.add(set1, tmp1, tmp1)
|
|
||||||
.add(set2, tmp2, tmp2)
|
|
||||||
// identical transfers into the same directory are only performed once
|
|
||||||
.add(set1, tmp1, tmp1);
|
|
||||||
var artifacts = batch.transfer(new VersionResolution(null), ArtifactRetriever.instance(), repositories);
|
|
||||||
|
|
||||||
assertEquals(6, artifacts.size());
|
|
||||||
assertTransferredArtifacts(set1, artifacts.subList(0, 3), tmp1);
|
|
||||||
assertTransferredArtifacts(set2, artifacts.subList(3, 6), tmp2);
|
|
||||||
assertTrue(max_concurrent_transfers.get() > 1, "expected concurrent transfers, max was " + max_concurrent_transfers.get());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
FileUtils.deleteDirectory(tmp1);
|
|
||||||
FileUtils.deleteDirectory(tmp2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testTransferEmptiesTheBatch() throws Exception {
|
|
||||||
var server = createTransferServer(new AtomicInteger());
|
|
||||||
server.start();
|
|
||||||
var tmp = Files.createTempDirectory("transfers").toFile();
|
|
||||||
try {
|
|
||||||
var repositories = List.of(transferRepository(server));
|
|
||||||
var resolution = new VersionResolution(null);
|
|
||||||
var batch = new DependencyTransferBatch();
|
|
||||||
|
|
||||||
var set1 = createTransferDependencies(1, 3);
|
|
||||||
batch.add(set1, tmp, tmp);
|
|
||||||
assertTransferredArtifacts(set1, batch.transfer(resolution, ArtifactRetriever.instance(), repositories), tmp);
|
|
||||||
|
|
||||||
// the batch was emptied, nothing is transferred again
|
|
||||||
assertTrue(batch.transfer(resolution, ArtifactRetriever.instance(), repositories).isEmpty());
|
|
||||||
|
|
||||||
// the same instance can collect and transfer again,
|
|
||||||
// including targets that were transferred before
|
|
||||||
var set2 = createTransferDependencies(3, 5);
|
|
||||||
batch.add(set2, tmp, tmp);
|
|
||||||
assertTransferredArtifacts(set2, batch.transfer(resolution, ArtifactRetriever.instance(), repositories), tmp);
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
FileUtils.deleteDirectory(tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testTransferModularJarsIntoModulesDirectory() throws Exception {
|
|
||||||
var server = createTransferServer(new AtomicInteger());
|
|
||||||
server.start();
|
|
||||||
var artifacts_dir = Files.createTempDirectory("artifacts").toFile();
|
|
||||||
var modules_dir = Files.createTempDirectory("modules").toFile();
|
|
||||||
try {
|
|
||||||
var dependencies = new DependencySet()
|
|
||||||
.include(new Dependency("com.example", "artifact1", new VersionNumber(1, 0, 0)))
|
|
||||||
.include(new Dependency("com.example", "module1", new VersionNumber(1, 0, 0), "", Dependency.TYPE_MODULAR_JAR));
|
|
||||||
|
|
||||||
var artifacts = new DependencyTransferBatch()
|
|
||||||
.add(dependencies, artifacts_dir, modules_dir)
|
|
||||||
.transfer(new VersionResolution(null), ArtifactRetriever.instance(), List.of(transferRepository(server)));
|
|
||||||
|
|
||||||
assertEquals(2, artifacts.size());
|
|
||||||
assertTrue(new File(artifacts_dir, "artifact1-1.0.0.jar").exists());
|
|
||||||
assertTrue(new File(modules_dir, "module1-1.0.0.jar").exists());
|
|
||||||
assertFalse(new File(artifacts_dir, "module1-1.0.0.jar").exists());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
FileUtils.deleteDirectory(artifacts_dir);
|
|
||||||
FileUtils.deleteDirectory(modules_dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testTransferClassifiers() throws Exception {
|
|
||||||
var server = createTransferServer(new AtomicInteger());
|
|
||||||
server.start();
|
|
||||||
var tmp = Files.createTempDirectory("transfers").toFile();
|
|
||||||
try {
|
|
||||||
var dependencies = createTransferDependencies(1, 2);
|
|
||||||
var artifacts = new DependencyTransferBatch()
|
|
||||||
.add(dependencies, tmp, tmp, "sources")
|
|
||||||
.transfer(new VersionResolution(null), ArtifactRetriever.instance(), List.of(transferRepository(server)));
|
|
||||||
|
|
||||||
assertEquals(4, artifacts.size());
|
|
||||||
for (var i = 1; i <= 2; i++) {
|
|
||||||
assertTrue(new File(tmp, "artifact" + i + "-1.0.0.jar").exists());
|
|
||||||
assertTrue(new File(tmp, "artifact" + i + "-1.0.0-sources.jar").exists());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
FileUtils.deleteDirectory(tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testMissingDirectories() {
|
|
||||||
var regular = new DependencySet()
|
|
||||||
.include(new Dependency("com.example", "artifact1", new VersionNumber(1, 0, 0)));
|
|
||||||
var modular = new DependencySet()
|
|
||||||
.include(new Dependency("com.example", "module1", new VersionNumber(1, 0, 0), "", Dependency.TYPE_MODULAR_JAR));
|
|
||||||
|
|
||||||
assertThrows(DependencyTransferException.class, () -> new DependencyTransferBatch().add(regular, null, null));
|
|
||||||
assertThrows(DependencyTransferException.class, () -> new DependencyTransferBatch().add(modular, Files.createTempDirectory("artifacts").toFile(), null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testEmptyBatch() {
|
|
||||||
var artifacts = new DependencyTransferBatch()
|
|
||||||
.transfer(new VersionResolution(null), ArtifactRetriever.instance(), List.of());
|
|
||||||
assertTrue(artifacts.isEmpty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001-2026 Geert Bevin (gbevin[remove] at uwyn dot com)
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
|
||||||
*/
|
|
||||||
package rife.bld.dependencies;
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpServer;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a local artifact server and dependency fixtures for testing
|
|
||||||
* transfers without relying on remote repositories.
|
|
||||||
*/
|
|
||||||
abstract class TransferTestHelper {
|
|
||||||
static HttpServer createTransferServer(AtomicInteger maxConcurrentTransfers)
|
|
||||||
throws IOException {
|
|
||||||
var active_transfers = new AtomicInteger();
|
|
||||||
var server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
|
|
||||||
server.createContext("/", exchange -> {
|
|
||||||
var active = active_transfers.incrementAndGet();
|
|
||||||
maxConcurrentTransfers.accumulateAndGet(active, Math::max);
|
|
||||||
try {
|
|
||||||
// delay the response so that parallel transfers overlap
|
|
||||||
Thread.sleep(200);
|
|
||||||
|
|
||||||
var body = exchange.getRequestURI().getPath().getBytes();
|
|
||||||
exchange.sendResponseHeaders(200, body.length);
|
|
||||||
exchange.getResponseBody().write(body);
|
|
||||||
exchange.close();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
} finally {
|
|
||||||
active_transfers.decrementAndGet();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
server.setExecutor(Executors.newCachedThreadPool());
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Repository transferRepository(HttpServer server) {
|
|
||||||
return new Repository("http://localhost:" + server.getAddress().getPort() + "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
static DependencySet createTransferDependencies(int from, int to) {
|
|
||||||
var dependencies = new DependencySet();
|
|
||||||
for (var i = from; i <= to; i++) {
|
|
||||||
dependencies.include(new Dependency("com.example", "artifact" + i, new VersionNumber(1, 0, 0)));
|
|
||||||
}
|
|
||||||
return dependencies;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void assertTransferredArtifacts(DependencySet dependencies, List<RepositoryArtifact> artifacts, File directory) {
|
|
||||||
assertEquals(dependencies.size(), artifacts.size());
|
|
||||||
var index = 0;
|
|
||||||
for (var dependency : dependencies) {
|
|
||||||
var filename = dependency.artifactId() + "-" + dependency.version() + ".jar";
|
|
||||||
assertTrue(artifacts.get(index).location().endsWith(filename), "expected artifact " + filename + " at index " + index);
|
|
||||||
assertTrue(new File(directory, filename).exists(), "expected file " + filename + " to be transferred");
|
|
||||||
++index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -11,11 +11,9 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||||
import org.junit.jupiter.api.condition.OS;
|
import org.junit.jupiter.api.condition.OS;
|
||||||
import rife.bld.Project;
|
import rife.bld.Project;
|
||||||
import rife.bld.WebProject;
|
|
||||||
import rife.bld.blueprints.AppProjectBlueprint;
|
import rife.bld.blueprints.AppProjectBlueprint;
|
||||||
import rife.bld.dependencies.*;
|
import rife.bld.dependencies.*;
|
||||||
import rife.bld.publish.PublishArtifact;
|
import rife.bld.publish.PublishArtifact;
|
||||||
import rife.bld.publish.PublishInfo;
|
|
||||||
import rife.tools.FileUtils;
|
import rife.tools.FileUtils;
|
||||||
import rife.tools.exceptions.FileUtilsErrorException;
|
import rife.tools.exceptions.FileUtilsErrorException;
|
||||||
|
|
||||||
|
|
@ -706,428 +704,4 @@ public class TestPublishOperation {
|
||||||
FileUtils.deleteDirectory(tmp1);
|
FileUtils.deleteDirectory(tmp1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testPublishLocalWithBom()
|
|
||||||
throws Exception {
|
|
||||||
var poms = java.util.Map.of(
|
|
||||||
"bom1:1.0.0", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>bom1</artifactId>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency><groupId>com.example</groupId><artifactId>a</artifactId><version>1.4.0</version></dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
</project>""");
|
|
||||||
var metadata = java.util.Map.of(
|
|
||||||
"b", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<metadata>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>b</artifactId>
|
|
||||||
<versioning>
|
|
||||||
<latest>2.2.0</latest>
|
|
||||||
<release>2.2.0</release>
|
|
||||||
<versions><version>2.2.0</version></versions>
|
|
||||||
</versioning>
|
|
||||||
</metadata>""");
|
|
||||||
var server = com.sun.net.httpserver.HttpServer.create(new java.net.InetSocketAddress("localhost", 0), 0);
|
|
||||||
server.createContext("/", exchange -> {
|
|
||||||
var segments = exchange.getRequestURI().getPath().split("/");
|
|
||||||
var filename = segments[segments.length - 1];
|
|
||||||
String content = null;
|
|
||||||
if (filename.endsWith(".pom") && segments.length >= 3) {
|
|
||||||
content = poms.get(segments[segments.length - 3] + ":" + segments[segments.length - 2]);
|
|
||||||
} else if (filename.equals("maven-metadata.xml") && segments.length >= 2) {
|
|
||||||
content = metadata.get(segments[segments.length - 2]);
|
|
||||||
}
|
|
||||||
if (content == null) {
|
|
||||||
exchange.sendResponseHeaders(404, -1);
|
|
||||||
} else {
|
|
||||||
var body = content.getBytes();
|
|
||||||
exchange.sendResponseHeaders(200, body.length);
|
|
||||||
exchange.getResponseBody().write(body);
|
|
||||||
}
|
|
||||||
exchange.close();
|
|
||||||
});
|
|
||||||
server.start();
|
|
||||||
|
|
||||||
var tmp_local = Files.createTempDirectory("bomlocal").toFile();
|
|
||||||
var artifact_file = File.createTempFile("myapp", ".jar");
|
|
||||||
try {
|
|
||||||
var operation = new PublishOperation()
|
|
||||||
.artifactRetriever(ArtifactRetriever.cachingInstance())
|
|
||||||
.repositories(new Repository(tmp_local.getAbsolutePath()))
|
|
||||||
.dependencyRepositories(List.of(new Repository("http://localhost:" + server.getAddress().getPort() + "/")))
|
|
||||||
.info(new PublishInfo()
|
|
||||||
.groupId("test.pkg")
|
|
||||||
.artifactId("myapp")
|
|
||||||
.version(new VersionNumber(3, 3, 3)))
|
|
||||||
.artifacts(List.of(new PublishArtifact(artifact_file, "", "jar")));
|
|
||||||
operation.dependencies().scope(Scope.compile)
|
|
||||||
.include(new Bom("com.example", "bom1", new VersionNumber(1, 0, 0)))
|
|
||||||
.include(new Dependency("com.example", "a"))
|
|
||||||
.include(new Dependency("com.example", "b"));
|
|
||||||
operation.execute();
|
|
||||||
|
|
||||||
var pom = FileUtils.readString(Path.of(tmp_local.getAbsolutePath(), "test", "pkg", "myapp", "3.3.3", "myapp-3.3.3.pom").toFile());
|
|
||||||
|
|
||||||
// the BOM is imported through dependency management
|
|
||||||
assertTrue(pom.contains("<dependencyManagement>"), pom);
|
|
||||||
assertTrue(pom.contains("<artifactId>bom1</artifactId>"), pom);
|
|
||||||
assertTrue(pom.contains("<version>1.0.0</version>"), pom);
|
|
||||||
assertTrue(pom.contains("<type>pom</type>"), pom);
|
|
||||||
assertTrue(pom.contains("<scope>import</scope>"), pom);
|
|
||||||
|
|
||||||
// the covered dependency stays version-less,
|
|
||||||
// the BOM import provides its version downstream
|
|
||||||
assertTrue(pom.contains("<artifactId>a</artifactId>"), pom);
|
|
||||||
assertFalse(java.util.regex.Pattern.compile("<artifactId>a</artifactId>\\s*<version>").matcher(pom).find(), pom);
|
|
||||||
|
|
||||||
// the uncovered dependency is frozen to its resolved version
|
|
||||||
assertTrue(java.util.regex.Pattern.compile("<artifactId>b</artifactId>\\s*<version>2\\.2\\.0</version>").matcher(pom).find(), pom);
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
artifact_file.delete();
|
|
||||||
FileUtils.deleteDirectory(tmp_local);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testResolveVersionlessDependenciesWithOverrides()
|
|
||||||
throws Exception {
|
|
||||||
var poms = java.util.Map.of(
|
|
||||||
"bom1:1.0.0", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>bom1</artifactId>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency><groupId>com.example</groupId><artifactId>a</artifactId><version>1.4.0</version></dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
</project>""",
|
|
||||||
"bom2:2.5.0", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>bom2</artifactId>
|
|
||||||
<version>2.5.0</version>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency><groupId>com.example</groupId><artifactId>d</artifactId><version>4.0.0</version></dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
</project>""");
|
|
||||||
var metadata = java.util.Map.of(
|
|
||||||
"b", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<metadata>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>b</artifactId>
|
|
||||||
<versioning>
|
|
||||||
<latest>2.2.0</latest>
|
|
||||||
<release>2.2.0</release>
|
|
||||||
<versions><version>2.2.0</version></versions>
|
|
||||||
</versioning>
|
|
||||||
</metadata>""");
|
|
||||||
var server = com.sun.net.httpserver.HttpServer.create(new java.net.InetSocketAddress("localhost", 0), 0);
|
|
||||||
server.createContext("/", exchange -> {
|
|
||||||
var segments = exchange.getRequestURI().getPath().split("/");
|
|
||||||
var filename = segments[segments.length - 1];
|
|
||||||
String content = null;
|
|
||||||
if (filename.endsWith(".pom") && segments.length >= 3) {
|
|
||||||
content = poms.get(segments[segments.length - 3] + ":" + segments[segments.length - 2]);
|
|
||||||
} else if (filename.equals("maven-metadata.xml") && segments.length >= 2) {
|
|
||||||
content = metadata.get(segments[segments.length - 2]);
|
|
||||||
}
|
|
||||||
if (content == null) {
|
|
||||||
exchange.sendResponseHeaders(404, -1);
|
|
||||||
} else {
|
|
||||||
var body = content.getBytes();
|
|
||||||
exchange.sendResponseHeaders(200, body.length);
|
|
||||||
exchange.getResponseBody().write(body);
|
|
||||||
}
|
|
||||||
exchange.close();
|
|
||||||
});
|
|
||||||
server.start();
|
|
||||||
try {
|
|
||||||
var properties = new rife.ioc.HierarchicalProperties();
|
|
||||||
properties.put(VersionResolution.PROPERTY_OVERRIDE_PREFIX,
|
|
||||||
"com.example:a:1.9.9,com.example:b:1.1.1,com.example:bom2:2.5.0");
|
|
||||||
|
|
||||||
var scopes = new DependencyScopes();
|
|
||||||
scopes.scope(Scope.compile)
|
|
||||||
.include(new Bom("com.example", "bom1", new VersionNumber(1, 0, 0)))
|
|
||||||
.include(new Dependency("com.example", "a"))
|
|
||||||
.include(new Dependency("com.example", "b"));
|
|
||||||
scopes.scope(Scope.runtime)
|
|
||||||
.include(new Bom("com.example", "bom2"));
|
|
||||||
|
|
||||||
var operation = new PublishOperation()
|
|
||||||
.properties(properties)
|
|
||||||
.artifactRetriever(ArtifactRetriever.cachingInstance())
|
|
||||||
.dependencyRepositories(List.of(new Repository("http://localhost:" + server.getAddress().getPort() + "/")))
|
|
||||||
.dependencies(scopes);
|
|
||||||
operation.executeResolveVersionlessDependencies();
|
|
||||||
|
|
||||||
var compile_scope = operation.dependencies().scope(Scope.compile);
|
|
||||||
// the override is what the build resolves, it's frozen into the
|
|
||||||
// POM even though the BOM covers the dependency
|
|
||||||
assertEquals(new VersionNumber(1, 9, 9), compile_scope.get(new Dependency("com.example", "a")).version());
|
|
||||||
// an uncovered dependency freezes to its override instead of
|
|
||||||
// the latest version
|
|
||||||
assertEquals(new VersionNumber(1, 1, 1), compile_scope.get(new Dependency("com.example", "b")).version());
|
|
||||||
// a version-less BOM freezes to its override
|
|
||||||
var runtime_boms = operation.dependencies().scope(Scope.runtime).boms();
|
|
||||||
assertEquals(new Bom("com.example", "bom2", new VersionNumber(2, 5, 0)), runtime_boms.iterator().next());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testResolveVersionlessDependenciesFreezesFlattenedConflicts()
|
|
||||||
throws Exception {
|
|
||||||
var poms = java.util.Map.of(
|
|
||||||
"bomr:1.0.0", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>bomr</artifactId>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency><groupId>com.example</groupId><artifactId>a</artifactId><version>1.0.0</version></dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
</project>""",
|
|
||||||
"bomp:1.0.0", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>bomp</artifactId>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency><groupId>com.example</groupId><artifactId>a</artifactId><version>2.0.0</version></dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
</project>""");
|
|
||||||
var server = com.sun.net.httpserver.HttpServer.create(new java.net.InetSocketAddress("localhost", 0), 0);
|
|
||||||
server.createContext("/", exchange -> {
|
|
||||||
var segments = exchange.getRequestURI().getPath().split("/");
|
|
||||||
var filename = segments[segments.length - 1];
|
|
||||||
String content = null;
|
|
||||||
if (filename.endsWith(".pom") && segments.length >= 3) {
|
|
||||||
content = poms.get(segments[segments.length - 3] + ":" + segments[segments.length - 2]);
|
|
||||||
}
|
|
||||||
if (content == null) {
|
|
||||||
exchange.sendResponseHeaders(404, -1);
|
|
||||||
} else {
|
|
||||||
var body = content.getBytes();
|
|
||||||
exchange.sendResponseHeaders(200, body.length);
|
|
||||||
exchange.getResponseBody().write(body);
|
|
||||||
}
|
|
||||||
exchange.close();
|
|
||||||
});
|
|
||||||
server.start();
|
|
||||||
try {
|
|
||||||
var scopes = new DependencyScopes();
|
|
||||||
scopes.scope(Scope.runtime)
|
|
||||||
.include(new Bom("com.example", "bomr", new VersionNumber(1, 0, 0)))
|
|
||||||
.include(new Dependency("com.example", "a"));
|
|
||||||
scopes.scope(Scope.provided)
|
|
||||||
.include(new Bom("com.example", "bomp", new VersionNumber(1, 0, 0)))
|
|
||||||
.include(new Dependency("com.example", "a"));
|
|
||||||
|
|
||||||
var operation = new PublishOperation()
|
|
||||||
.artifactRetriever(ArtifactRetriever.cachingInstance())
|
|
||||||
.dependencyRepositories(List.of(new Repository("http://localhost:" + server.getAddress().getPort() + "/")))
|
|
||||||
.dependencies(scopes);
|
|
||||||
operation.executeResolveVersionlessDependencies();
|
|
||||||
|
|
||||||
// the flattened dependency management of the POM imports the
|
|
||||||
// runtime BOM first and would supply 1.0.0, the runtime scope
|
|
||||||
// agrees with that so its dependency stays version-less
|
|
||||||
assertEquals(VersionNumber.UNKNOWN, operation.dependencies().scope(Scope.runtime).get(new Dependency("com.example", "a")).version());
|
|
||||||
// the provided scope resolves 2.0.0 through its own BOM, that
|
|
||||||
// conflicts with the flattened list so the version is frozen
|
|
||||||
assertEquals(new VersionNumber(2, 0, 0), operation.dependencies().scope(Scope.provided).get(new Dependency("com.example", "a")).version());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testResolveVersionlessDependenciesUntouchedScopes() {
|
|
||||||
// no network access is needed when nothing has to be resolved :
|
|
||||||
// explicitly versioned dependencies are untouched and the test
|
|
||||||
// scope isn't part of the publication
|
|
||||||
var scopes = new DependencyScopes();
|
|
||||||
scopes.scope(Scope.compile)
|
|
||||||
.include(new Dependency("com.example", "c", new VersionNumber(5, 0, 0)));
|
|
||||||
scopes.scope(Scope.test)
|
|
||||||
.include(new Dependency("com.example", "t"));
|
|
||||||
|
|
||||||
var operation = new PublishOperation()
|
|
||||||
.dependencies(scopes);
|
|
||||||
operation.executeResolveVersionlessDependencies();
|
|
||||||
|
|
||||||
assertEquals(new VersionNumber(5, 0, 0), operation.dependencies().scope(Scope.compile).get(new Dependency("com.example", "c")).version());
|
|
||||||
assertEquals(VersionNumber.UNKNOWN, operation.dependencies().scope(Scope.test).get(new Dependency("com.example", "t")).version());
|
|
||||||
}
|
|
||||||
|
|
||||||
static class BomPublishProject extends WebProject {
|
|
||||||
BomPublishProject(File tmp) {
|
|
||||||
workDirectory = tmp;
|
|
||||||
pkg = "test.pkg";
|
|
||||||
name = "myapp";
|
|
||||||
version = new VersionNumber(0, 0, 1);
|
|
||||||
repositories = List.of(new Repository("https://example.com/resolve"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testFromProjectDependencyRepositories()
|
|
||||||
throws Exception {
|
|
||||||
var tmp = Files.createTempDirectory("test").toFile();
|
|
||||||
try {
|
|
||||||
var project = new BomPublishProject(tmp);
|
|
||||||
var operation = new PublishOperation()
|
|
||||||
.fromProject(project);
|
|
||||||
assertEquals(project.repositories(), operation.dependencyRepositories());
|
|
||||||
} finally {
|
|
||||||
FileUtils.deleteDirectory(tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testResolveVersionlessDependencies()
|
|
||||||
throws Exception {
|
|
||||||
var poms = java.util.Map.of(
|
|
||||||
"bom1:1.0.0", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>bom1</artifactId>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency><groupId>com.example</groupId><artifactId>a</artifactId><version>1.4.0</version></dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
</project>""",
|
|
||||||
"bom2:3.0.0", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>bom2</artifactId>
|
|
||||||
<version>3.0.0</version>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency><groupId>com.example</groupId><artifactId>d</artifactId><version>4.0.0</version></dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
</project>""");
|
|
||||||
var metadata = java.util.Map.of(
|
|
||||||
"b", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<metadata>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>b</artifactId>
|
|
||||||
<versioning>
|
|
||||||
<latest>2.2.0</latest>
|
|
||||||
<release>2.2.0</release>
|
|
||||||
<versions><version>2.2.0</version></versions>
|
|
||||||
</versioning>
|
|
||||||
</metadata>""",
|
|
||||||
"bom2", """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<metadata>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>bom2</artifactId>
|
|
||||||
<versioning>
|
|
||||||
<latest>3.0.0</latest>
|
|
||||||
<release>3.0.0</release>
|
|
||||||
<versions><version>3.0.0</version></versions>
|
|
||||||
</versioning>
|
|
||||||
</metadata>""");
|
|
||||||
var server = com.sun.net.httpserver.HttpServer.create(new java.net.InetSocketAddress("localhost", 0), 0);
|
|
||||||
server.createContext("/", exchange -> {
|
|
||||||
var segments = exchange.getRequestURI().getPath().split("/");
|
|
||||||
var filename = segments[segments.length - 1];
|
|
||||||
String content = null;
|
|
||||||
if (filename.endsWith(".pom") && segments.length >= 3) {
|
|
||||||
content = poms.get(segments[segments.length - 3] + ":" + segments[segments.length - 2]);
|
|
||||||
} else if (filename.equals("maven-metadata.xml") && segments.length >= 2) {
|
|
||||||
content = metadata.get(segments[segments.length - 2]);
|
|
||||||
}
|
|
||||||
if (content == null) {
|
|
||||||
exchange.sendResponseHeaders(404, -1);
|
|
||||||
} else {
|
|
||||||
var body = content.getBytes();
|
|
||||||
exchange.sendResponseHeaders(200, body.length);
|
|
||||||
exchange.getResponseBody().write(body);
|
|
||||||
}
|
|
||||||
exchange.close();
|
|
||||||
});
|
|
||||||
server.start();
|
|
||||||
try {
|
|
||||||
var scopes = new DependencyScopes();
|
|
||||||
scopes.scope(Scope.compile)
|
|
||||||
.include(new Bom("com.example", "bom1", new VersionNumber(1, 0, 0)))
|
|
||||||
.include(new Dependency("com.example", "a"))
|
|
||||||
.include(new Dependency("com.example", "b"))
|
|
||||||
.include(new Dependency("com.example", "c", new VersionNumber(5, 0, 0)));
|
|
||||||
scopes.scope(Scope.runtime)
|
|
||||||
.include(new Bom("com.example", "bom2"));
|
|
||||||
scopes.scope(Scope.provided)
|
|
||||||
// covered by the compile scope BOM through scope inheritance
|
|
||||||
.include(new Dependency("com.example", "a"));
|
|
||||||
|
|
||||||
var operation = new PublishOperation()
|
|
||||||
.artifactRetriever(ArtifactRetriever.cachingInstance())
|
|
||||||
.dependencyRepositories(List.of(new Repository("http://localhost:" + server.getAddress().getPort() + "/")))
|
|
||||||
.dependencies(scopes);
|
|
||||||
operation.executeResolveVersionlessDependencies();
|
|
||||||
|
|
||||||
var compile_scope = operation.dependencies().scope(Scope.compile);
|
|
||||||
// covered by the BOM : stays version-less for the POM,
|
|
||||||
// the dependency management import provides the version
|
|
||||||
assertEquals(VersionNumber.UNKNOWN, compile_scope.get(new Dependency("com.example", "a")).version());
|
|
||||||
// not covered : resolved to its latest version
|
|
||||||
assertEquals(new VersionNumber(2, 2, 0), compile_scope.get(new Dependency("com.example", "b")).version());
|
|
||||||
// explicitly versioned : untouched
|
|
||||||
assertEquals(new VersionNumber(5, 0, 0), compile_scope.get(new Dependency("com.example", "c")).version());
|
|
||||||
// a version-less BOM is resolved to its latest version
|
|
||||||
var runtime_boms = operation.dependencies().scope(Scope.runtime).boms();
|
|
||||||
assertEquals(new Bom("com.example", "bom2", new VersionNumber(3, 0, 0)), runtime_boms.iterator().next());
|
|
||||||
// the compile scope BOM also covers the provided scope,
|
|
||||||
// its version-less dependency stays version-less
|
|
||||||
assertEquals(VersionNumber.UNKNOWN, operation.dependencies().scope(Scope.provided).get(new Dependency("com.example", "a")).version());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,14 @@
|
||||||
*/
|
*/
|
||||||
package rife.bld.operations;
|
package rife.bld.operations;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpServer;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import rife.bld.WebProject;
|
import rife.bld.WebProject;
|
||||||
import rife.bld.dependencies.*;
|
import rife.bld.dependencies.*;
|
||||||
import rife.tools.FileUtils;
|
import rife.tools.FileUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
|
@ -172,123 +168,4 @@ public class TestUpdatesOperation {
|
||||||
FileUtils.deleteDirectory(tmp);
|
FileUtils.deleteDirectory(tmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testBomUpdateReported()
|
|
||||||
throws Exception {
|
|
||||||
var server = createArtifactServer(
|
|
||||||
Map.of("bom1:1.0.0", bomPom("bom1", "1.0.0", "<dependency><groupId>com.example</groupId><artifactId>a</artifactId><version>1.4.0</version></dependency>")),
|
|
||||||
Map.of("bom1", metadata("bom1", "2.0.0", "1.0.0", "2.0.0"),
|
|
||||||
"a", metadata("a", "9.9.9", "1.4.0", "9.9.9")));
|
|
||||||
server.start();
|
|
||||||
try {
|
|
||||||
var operation = new UpdatesOperation()
|
|
||||||
.artifactRetriever(ArtifactRetriever.cachingInstance())
|
|
||||||
.repositories(List.of(new Repository("http://localhost:" + server.getAddress().getPort() + "/")));
|
|
||||||
operation.dependencies().scope(Scope.compile)
|
|
||||||
.include(new Bom("com.example", "bom1", new VersionNumber(1, 0, 0)))
|
|
||||||
.include(new Dependency("com.example", "a"));
|
|
||||||
|
|
||||||
operation.execute();
|
|
||||||
|
|
||||||
// the newer BOM version is reported
|
|
||||||
var boms = operation.updates().scope(Scope.compile).boms();
|
|
||||||
assertEquals(1, boms.size());
|
|
||||||
assertEquals(new Bom("com.example", "bom1", new VersionNumber(2, 0, 0)), boms.iterator().next());
|
|
||||||
|
|
||||||
// the version-less dependency covered by the BOM is not reported,
|
|
||||||
// even though newer versions exist in the metadata
|
|
||||||
assertTrue(operation.updates().scope(Scope.compile).isEmpty());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testExplicitVersionCheckedDespiteBom()
|
|
||||||
throws Exception {
|
|
||||||
var server = createArtifactServer(
|
|
||||||
Map.of("bom1:1.0.0", bomPom("bom1", "1.0.0", "<dependency><groupId>com.example</groupId><artifactId>a</artifactId><version>1.4.0</version></dependency>")),
|
|
||||||
Map.of("bom1", metadata("bom1", "1.0.0", "1.0.0"),
|
|
||||||
"a", metadata("a", "2.0.0", "1.0.0", "2.0.0")));
|
|
||||||
server.start();
|
|
||||||
try {
|
|
||||||
var operation = new UpdatesOperation()
|
|
||||||
.artifactRetriever(ArtifactRetriever.cachingInstance())
|
|
||||||
.repositories(List.of(new Repository("http://localhost:" + server.getAddress().getPort() + "/")));
|
|
||||||
operation.dependencies().scope(Scope.compile)
|
|
||||||
.include(new Bom("com.example", "bom1", new VersionNumber(1, 0, 0)))
|
|
||||||
.include(new Dependency("com.example", "a", new VersionNumber(1, 0, 0)));
|
|
||||||
|
|
||||||
operation.execute();
|
|
||||||
|
|
||||||
// the BOM is up to date and not reported
|
|
||||||
assertTrue(operation.updates().scope(Scope.compile).boms().isEmpty());
|
|
||||||
|
|
||||||
// the explicitly versioned dependency is still checked for
|
|
||||||
// updates, even though the BOM covers it
|
|
||||||
var dependencies = operation.updates().scope(Scope.compile);
|
|
||||||
assertEquals(1, dependencies.size());
|
|
||||||
assertEquals(new VersionNumber(2, 0, 0), dependencies.iterator().next().version());
|
|
||||||
} finally {
|
|
||||||
server.stop(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HttpServer createArtifactServer(Map<String, String> poms, Map<String, String> metadata)
|
|
||||||
throws IOException {
|
|
||||||
var server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
|
|
||||||
server.createContext("/", exchange -> {
|
|
||||||
var segments = exchange.getRequestURI().getPath().split("/");
|
|
||||||
var filename = segments[segments.length - 1];
|
|
||||||
String content = null;
|
|
||||||
if (filename.endsWith(".pom") && segments.length >= 3) {
|
|
||||||
content = poms.get(segments[segments.length - 3] + ":" + segments[segments.length - 2]);
|
|
||||||
} else if (filename.equals("maven-metadata.xml") && segments.length >= 2) {
|
|
||||||
content = metadata.get(segments[segments.length - 2]);
|
|
||||||
}
|
|
||||||
if (content == null) {
|
|
||||||
exchange.sendResponseHeaders(404, -1);
|
|
||||||
} else {
|
|
||||||
var body = content.getBytes();
|
|
||||||
exchange.sendResponseHeaders(200, body.length);
|
|
||||||
exchange.getResponseBody().write(body);
|
|
||||||
}
|
|
||||||
exchange.close();
|
|
||||||
});
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String bomPom(String artifact, String version, String managedDependencies) {
|
|
||||||
return """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>%s</artifactId>
|
|
||||||
<version>%s</version>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>%s</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
</project>""".formatted(artifact, version, managedDependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String metadata(String artifact, String latest, String... versions) {
|
|
||||||
var versions_xml = new StringBuilder();
|
|
||||||
for (var version : versions) {
|
|
||||||
versions_xml.append("<version>").append(version).append("</version>");
|
|
||||||
}
|
|
||||||
return """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<metadata>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>%s</artifactId>
|
|
||||||
<versioning>
|
|
||||||
<latest>%s</latest>
|
|
||||||
<release>%s</release>
|
|
||||||
<versions>%s</versions>
|
|
||||||
</versioning>
|
|
||||||
</metadata>""".formatted(artifact, latest, latest, versions_xml);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -652,93 +652,4 @@ public class TestPomBuilder {
|
||||||
</project>
|
</project>
|
||||||
""", builder.build());
|
""", builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDependencyManagementScopeBoundaries() {
|
|
||||||
var builder = new PomBuilder();
|
|
||||||
builder.dependencies().scope(Scope.provided)
|
|
||||||
.include(new Bom("org.eclipse.jetty", "jetty-bom", new VersionNumber(12, 0, 16)));
|
|
||||||
builder.dependencies().scope(Scope.test)
|
|
||||||
// the test scope isn't published, its BOMs are not emitted
|
|
||||||
.include(new Bom("org.junit", "junit-bom", new VersionNumber(6, 1, 1)));
|
|
||||||
assertEquals("""
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId></groupId>
|
|
||||||
<artifactId></artifactId>
|
|
||||||
<version></version>
|
|
||||||
<name></name>
|
|
||||||
<description></description>
|
|
||||||
<url></url>
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
|
||||||
<artifactId>jetty-bom</artifactId>
|
|
||||||
<version>12.0.16</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
||||||
""", builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDependencyManagement() {
|
|
||||||
var builder = new PomBuilder();
|
|
||||||
builder.dependencies().scope(Scope.compile)
|
|
||||||
.include(new Bom("io.vertx", "vertx-stack-depchain", new VersionNumber(4, 5, 12)))
|
|
||||||
.include(new Dependency("io.vertx", "vertx-core"));
|
|
||||||
builder.dependencies().scope(Scope.runtime)
|
|
||||||
// duplicate BOMs are only emitted once
|
|
||||||
.include(new Bom("io.vertx", "vertx-stack-depchain", new VersionNumber(4, 5, 12)))
|
|
||||||
.include(new Bom("org.junit", "junit-bom", new VersionNumber(6, 1, 1)));
|
|
||||||
builder.dependencies().scope(Scope.provided)
|
|
||||||
// BOMs without a version are not emitted
|
|
||||||
.include(new Bom("com.example", "versionless-bom"));
|
|
||||||
assertEquals("""
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId></groupId>
|
|
||||||
<artifactId></artifactId>
|
|
||||||
<version></version>
|
|
||||||
<name></name>
|
|
||||||
<description></description>
|
|
||||||
<url></url>
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.vertx</groupId>
|
|
||||||
<artifactId>vertx-stack-depchain</artifactId>
|
|
||||||
<version>4.5.12</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit</groupId>
|
|
||||||
<artifactId>junit-bom</artifactId>
|
|
||||||
<version>6.1.1</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.vertx</groupId>
|
|
||||||
<artifactId>vertx-core</artifactId>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
||||||
""", builder.build());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue