mirror of
https://github.com/rife2/bld
synced 2026-08-03 14:37:47 +02:00
Compare commits
2 commits
2177fcb57c
...
dffe1cf6df
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dffe1cf6df | ||
|
|
3ced706fa7 |
|
|
@ -1669,6 +1669,9 @@ public class BaseProject extends BuildExecutor {
|
|||
return dependencies;
|
||||
}
|
||||
|
||||
private final Map<String, List<File>> classpathJarsMemo_ = new HashMap<>();
|
||||
private DependencySet resolvedExtensions_ = null;
|
||||
|
||||
/**
|
||||
* Creates the version resolution for a dependency scope, applying the
|
||||
* version overrides from the properties and the BOMs that are
|
||||
|
|
@ -1708,16 +1711,39 @@ public class BaseProject extends BuildExecutor {
|
|||
* @since 2.4.0
|
||||
*/
|
||||
public List<File> dependencyClasspathJars(Scope scope, String groupId, String artifactId) {
|
||||
var coordinate = groupId + ":" + artifactId;
|
||||
var memo_key = "dependency." + scope + "." + coordinate;
|
||||
var memoized = classpathJarsMemo_.get(memo_key);
|
||||
if (memoized != null) {
|
||||
return memoized;
|
||||
}
|
||||
|
||||
var declared = dependencies().scope(scope).get(new Dependency(groupId, artifactId));
|
||||
if (declared == null) {
|
||||
throw new IllegalArgumentException("Dependency '" + groupId + ":" + artifactId + "' isn't declared in the " + scope + " scope.");
|
||||
throw new IllegalArgumentException("Dependency '" + coordinate + "' isn't declared in the " + scope + " scope.");
|
||||
}
|
||||
|
||||
var dir = scopeLibDirectory(scope);
|
||||
var cache = new BldCache(libBldDirectory(), new VersionResolution(properties()));
|
||||
cache.cacheDependenciesHash(repositories(), dependencies());
|
||||
if (cache.isDependenciesHashValid()) {
|
||||
var cached = cache.getCachedDependencyClasspath(scope, coordinate);
|
||||
if (cached != null) {
|
||||
var jars = cached.stream().map(name -> new File(dir, name)).toList();
|
||||
classpathJarsMemo_.put(memo_key, jars);
|
||||
return jars;
|
||||
}
|
||||
}
|
||||
|
||||
var resolver = new DependencyResolver(versionResolution(scope), artifactRetriever(), repositories(), declared);
|
||||
var dir = scopeLibDirectory(scope);
|
||||
return resolver.getAllDependencies(Scope.compile, Scope.runtime).stream()
|
||||
var jars = resolver.getAllDependencies(Scope.compile, Scope.runtime).stream()
|
||||
.map(dependency -> new File(dir, dependency.toFileName()))
|
||||
.toList();
|
||||
|
||||
cache.cacheDependencyClasspath(scope, coordinate, jars.stream().map(File::getName).toList());
|
||||
cache.writeCache();
|
||||
classpathJarsMemo_.put(memo_key, jars);
|
||||
return jars;
|
||||
}
|
||||
|
||||
private File scopeLibDirectory(Scope scope) {
|
||||
|
|
@ -1751,6 +1777,13 @@ public class BaseProject extends BuildExecutor {
|
|||
* @since 2.4.0
|
||||
*/
|
||||
public List<File> extensionClasspathJars(String groupId, String artifactId) {
|
||||
var coordinate = groupId + ":" + artifactId;
|
||||
var memo_key = "extension." + coordinate;
|
||||
var memoized = classpathJarsMemo_.get(memo_key);
|
||||
if (memoized != null) {
|
||||
return memoized;
|
||||
}
|
||||
|
||||
var wrapper = new Wrapper();
|
||||
wrapper.currentDir(workDirectory());
|
||||
try {
|
||||
|
|
@ -1762,25 +1795,45 @@ public class BaseProject extends BuildExecutor {
|
|||
var properties = new HierarchicalProperties().parent(BuildExecutor.setupProperties(workDirectory()));
|
||||
properties.putAll(wrapper.wrapperProperties());
|
||||
|
||||
var resolution = new VersionResolution(properties);
|
||||
var cache = new BldCache(libBldDirectory(), resolution);
|
||||
cache.cacheExtensionsHash(wrapper.repositories(), wrapper.extensions());
|
||||
if (cache.isExtensionsHashValid()) {
|
||||
var cached = cache.getCachedExtensionClasspath(coordinate);
|
||||
if (cached != null) {
|
||||
var jars = cached.stream().map(name -> new File(libBldDirectory(), name)).toList();
|
||||
classpathJarsMemo_.put(memo_key, jars);
|
||||
return jars;
|
||||
}
|
||||
}
|
||||
|
||||
var repositories = new ArrayList<Repository>();
|
||||
for (var repository : wrapper.repositories()) {
|
||||
repositories.add(Repository.resolveRepository(properties, repository));
|
||||
}
|
||||
|
||||
var resolution = new VersionResolution(properties);
|
||||
var extensions = wrapper.extensions().stream().map(Dependency::parse).toList();
|
||||
var resolved = new ParallelDependencyResolver(resolution, artifactRetriever(), repositories)
|
||||
.resolveAllDependencies(extensions, Scope.compile, Scope.runtime);
|
||||
// the resolved set of all the extensions is reused across
|
||||
// lookups of different dependencies in the same build
|
||||
if (resolvedExtensions_ == null) {
|
||||
var extensions = wrapper.extensions().stream().map(Dependency::parse).toList();
|
||||
resolvedExtensions_ = new ParallelDependencyResolver(resolution, artifactRetriever(), repositories)
|
||||
.resolveAllDependencies(extensions, Scope.compile, Scope.runtime);
|
||||
}
|
||||
|
||||
var dependency = resolved.get(new Dependency(groupId, artifactId));
|
||||
var dependency = resolvedExtensions_.get(new Dependency(groupId, artifactId));
|
||||
if (dependency == null) {
|
||||
throw new IllegalArgumentException("Dependency '" + groupId + ":" + artifactId + "' isn't part of the extensions of this project.");
|
||||
throw new IllegalArgumentException("Dependency '" + coordinate + "' isn't part of the extensions of this project.");
|
||||
}
|
||||
|
||||
var resolver = new DependencyResolver(resolution, artifactRetriever(), repositories, dependency);
|
||||
return resolver.getAllDependencies(Scope.compile, Scope.runtime).stream()
|
||||
var jars = resolver.getAllDependencies(Scope.compile, Scope.runtime).stream()
|
||||
.map(d -> new File(libBldDirectory(), d.toFileName()))
|
||||
.toList();
|
||||
|
||||
cache.cacheExtensionClasspath(coordinate, jars.stream().map(File::getName).toList());
|
||||
cache.writeCache();
|
||||
classpathJarsMemo_.put(memo_key, jars);
|
||||
return jars;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
package rife.bld;
|
||||
|
||||
import rife.bld.dependencies.DependencyScopes;
|
||||
import rife.bld.dependencies.Scope;
|
||||
import rife.bld.dependencies.Repository;
|
||||
import rife.bld.dependencies.VersionResolution;
|
||||
import rife.bld.wrapper.Wrapper;
|
||||
|
|
@ -40,12 +41,15 @@ public class BldCache {
|
|||
private static final String WRAPPER_PROPERTIES_HASH = Wrapper.WRAPPER_PROPERTIES + PROPERTY_SUFFIX_HASH;
|
||||
private static final String BLD_BUILD_HASH = "bld-build" + PROPERTY_SUFFIX_HASH;
|
||||
|
||||
private static final String PROPERTY_SUFFIX_CLASSPATH = ".classpath.";
|
||||
|
||||
private static final String PROPERTY_EXTENSIONS_PREFIX = "bld.extensions";
|
||||
private static final String PROPERTY_EXTENSIONS_HASH = PROPERTY_EXTENSIONS_PREFIX + PROPERTY_SUFFIX_HASH;
|
||||
private static final String PROPERTY_EXTENSIONS_LOCAL = PROPERTY_EXTENSIONS_PREFIX + PROPERTY_SUFFIX_LOCAL;
|
||||
private static final String PROPERTY_EXTENSIONS_DOWNLOAD_SOURCES = PROPERTY_EXTENSIONS_PREFIX + PROPERTY_SUFFIX_DOWNLOAD_SOURCES;
|
||||
private static final String PROPERTY_EXTENSIONS_DOWNLOAD_JAVADOC = PROPERTY_EXTENSIONS_PREFIX + PROPERTY_SUFFIX_DOWNLOAD_JAVADOC;
|
||||
private static final String PROPERTY_EXTENSIONS_DEPENDENCY_TREE = PROPERTY_EXTENSIONS_PREFIX + PROPERTY_SUFFIX_DEPENDENCY_TREE;
|
||||
private static final String PROPERTY_EXTENSIONS_CLASSPATH_PREFIX = PROPERTY_EXTENSIONS_PREFIX + PROPERTY_SUFFIX_CLASSPATH;
|
||||
|
||||
private static final String PROPERTY_DEPENDENCIES_PREFIX = "bld.dependencies";
|
||||
private static final String PROPERTY_DEPENDENCIES_HASH = PROPERTY_DEPENDENCIES_PREFIX + PROPERTY_SUFFIX_HASH;
|
||||
|
|
@ -55,6 +59,7 @@ public class BldCache {
|
|||
private static final String PROPERTY_DEPENDENCIES_PROVIDED_DEPENDENCY_TREE = PROPERTY_DEPENDENCIES_PREFIX + ".provided" + PROPERTY_SUFFIX_DEPENDENCY_TREE;
|
||||
private static final String PROPERTY_DEPENDENCIES_RUNTIME_DEPENDENCY_TREE = PROPERTY_DEPENDENCIES_PREFIX + ".runtime" + PROPERTY_SUFFIX_DEPENDENCY_TREE;
|
||||
private static final String PROPERTY_DEPENDENCIES_TEST_DEPENDENCY_TREE = PROPERTY_DEPENDENCIES_PREFIX + ".test" + PROPERTY_SUFFIX_DEPENDENCY_TREE;
|
||||
private static final String PROPERTY_DEPENDENCIES_CLASSPATH_PREFIX = PROPERTY_DEPENDENCIES_PREFIX + PROPERTY_SUFFIX_CLASSPATH;
|
||||
|
||||
private final File cacheDir_;
|
||||
private final VersionResolution resolution_;
|
||||
|
|
@ -64,6 +69,8 @@ public class BldCache {
|
|||
private List<File> extensionsLocalArtifacts_;
|
||||
private String extensionsDependencyTree_;
|
||||
private String dependenciesHash_;
|
||||
private final java.util.Map<String, String> extensionClasspaths_ = new java.util.LinkedHashMap<>();
|
||||
private final java.util.Map<String, String> dependencyClasspaths_ = new java.util.LinkedHashMap<>();
|
||||
private Boolean dependenciesDownloadSources_;
|
||||
private Boolean dependenciesDownloadJavadocs_;
|
||||
private String dependenciesCompileDependencyTree_;
|
||||
|
|
@ -477,6 +484,76 @@ public class BldCache {
|
|||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resolved classpath jar names of a single extension
|
||||
* dependency, to store with {@link #writeCache()}.
|
||||
* <p>
|
||||
* The names are only meaningful while the extensions hash is valid,
|
||||
* check {@link #isExtensionsHashValid()} before using them.
|
||||
*
|
||||
* @param coordinate the {@code groupId:artifactId} of the dependency
|
||||
* @param jarNames the jar file names of its transitive classpath
|
||||
* @see #getCachedExtensionClasspath
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public void cacheExtensionClasspath(String coordinate, List<String> jarNames) {
|
||||
extensionClasspaths_.put(coordinate, String.join("\n", jarNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the previously stored classpath jar names of a single
|
||||
* extension dependency.
|
||||
*
|
||||
* @param coordinate the {@code groupId:artifactId} of the dependency
|
||||
* @return the stored jar file names; or {@code null} when nothing was
|
||||
* stored for the coordinate
|
||||
* @since 2.4.0
|
||||
* @see #cacheExtensionClasspath
|
||||
*/
|
||||
public List<String> getCachedExtensionClasspath(String coordinate) {
|
||||
var stored = hashProperties().getProperty(PROPERTY_EXTENSIONS_CLASSPATH_PREFIX + coordinate);
|
||||
if (stored == null || stored.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return List.of(stored.split("\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resolved classpath jar names of a single dependency in a
|
||||
* scope, to store with {@link #writeCache()}.
|
||||
* <p>
|
||||
* The names are only meaningful while the dependencies hash is valid,
|
||||
* check {@link #isDependenciesHashValid()} before using them.
|
||||
*
|
||||
* @param scope the scope the dependency is declared in
|
||||
* @param coordinate the {@code groupId:artifactId} of the dependency
|
||||
* @param jarNames the jar file names of its transitive classpath
|
||||
* @since 2.4.0
|
||||
* @see #getCachedDependencyClasspath
|
||||
*/
|
||||
public void cacheDependencyClasspath(Scope scope, String coordinate, List<String> jarNames) {
|
||||
dependencyClasspaths_.put(scope + "." + coordinate, String.join("\n", jarNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the previously stored classpath jar names of a single
|
||||
* dependency in a scope.
|
||||
*
|
||||
* @param scope the scope the dependency is declared in
|
||||
* @param coordinate the {@code groupId:artifactId} of the dependency
|
||||
* @return the stored jar file names; or {@code null} when nothing was
|
||||
* stored for the coordinate
|
||||
* @since 2.4.0
|
||||
* @see #cacheDependencyClasspath
|
||||
*/
|
||||
public List<String> getCachedDependencyClasspath(Scope scope, String coordinate) {
|
||||
var stored = hashProperties().getProperty(PROPERTY_DEPENDENCIES_CLASSPATH_PREFIX + scope + "." + coordinate);
|
||||
if (stored == null || stored.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return List.of(stored.split("\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the state of this {@code BldCache} instance to disk.
|
||||
*
|
||||
|
|
@ -490,7 +567,9 @@ public class BldCache {
|
|||
if (!extensionsHash_.equals(properties.get(PROPERTY_EXTENSIONS_HASH))) {
|
||||
properties.put(PROPERTY_EXTENSIONS_HASH, extensionsHash_);
|
||||
properties.remove(PROPERTY_EXTENSIONS_DEPENDENCY_TREE);
|
||||
properties.keySet().removeIf(key -> key.toString().startsWith(PROPERTY_EXTENSIONS_CLASSPATH_PREFIX));
|
||||
}
|
||||
extensionClasspaths_.forEach((coordinate, jars) -> properties.put(PROPERTY_EXTENSIONS_CLASSPATH_PREFIX + coordinate, jars));
|
||||
|
||||
if (extensionsDependencyTree_ != null) {
|
||||
properties.put(PROPERTY_EXTENSIONS_DEPENDENCY_TREE, extensionsDependencyTree_);
|
||||
|
|
@ -525,7 +604,9 @@ public class BldCache {
|
|||
properties.remove(PROPERTY_DEPENDENCIES_PROVIDED_DEPENDENCY_TREE);
|
||||
properties.remove(PROPERTY_DEPENDENCIES_RUNTIME_DEPENDENCY_TREE);
|
||||
properties.remove(PROPERTY_DEPENDENCIES_TEST_DEPENDENCY_TREE);
|
||||
properties.keySet().removeIf(key -> key.toString().startsWith(PROPERTY_DEPENDENCIES_CLASSPATH_PREFIX));
|
||||
}
|
||||
dependencyClasspaths_.forEach((coordinate, jars) -> properties.put(PROPERTY_DEPENDENCIES_CLASSPATH_PREFIX + coordinate, jars));
|
||||
|
||||
if (dependenciesCompileDependencyTree_ != null) {
|
||||
properties.put(PROPERTY_DEPENDENCIES_COMPILE_DEPENDENCY_TREE, dependenciesCompileDependencyTree_);
|
||||
|
|
|
|||
|
|
@ -426,14 +426,324 @@ public class TestClasspathJars {
|
|||
FileUtils.writeString(properties, new File(lib_bld, "bld-wrapper.properties"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testDependencyClasspathJarsCaching() throws Exception {
|
||||
var requests = new java.util.concurrent.atomic.AtomicInteger();
|
||||
var server = createArtifactServer(Map.of(
|
||||
"tool:1.0.0", pom("tool", "1.0.0", dependency("liba", "1.1.0")),
|
||||
"liba:1.1.0", pom("liba", "1.1.0", ""),
|
||||
"tool:2.0.0", pom("tool", "2.0.0", "")), requests);
|
||||
server.start();
|
||||
var tmp = Files.createTempDirectory("classpathjars").toFile();
|
||||
try {
|
||||
var project = new JarsProject(tmp, serverRepository(server));
|
||||
project.dependencies().scope(provided)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
|
||||
var jars = project.dependencyClasspathJars(provided, "com.example", "tool");
|
||||
var resolution_requests = settledRequests(requests);
|
||||
assertTrue(resolution_requests > 0);
|
||||
|
||||
// a repeated call in the same build resolves nothing
|
||||
assertEquals(jars, project.dependencyClasspathJars(provided, "com.example", "tool"));
|
||||
assertEquals(resolution_requests, requests.get());
|
||||
|
||||
// a fresh project instance reads the persisted cache instead
|
||||
// of resolving, like a new build invocation would
|
||||
var fresh = new JarsProject(tmp, serverRepository(server));
|
||||
fresh.dependencies().scope(provided)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
assertEquals(jars, fresh.dependencyClasspathJars(provided, "com.example", "tool"));
|
||||
assertEquals(resolution_requests, requests.get());
|
||||
|
||||
// changing the declared version invalidates the cache
|
||||
var changed = new JarsProject(tmp, serverRepository(server));
|
||||
changed.dependencies().scope(provided)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(2, 0, 0)));
|
||||
assertEquals(List.of("tool-2.0.0.jar"),
|
||||
changed.dependencyClasspathJars(provided, "com.example", "tool").stream().map(File::getName).toList());
|
||||
assertTrue(requests.get() > resolution_requests);
|
||||
} finally {
|
||||
server.stop(0);
|
||||
FileUtils.deleteDirectory(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExtensionClasspathJarsCaching() throws Exception {
|
||||
var requests = new java.util.concurrent.atomic.AtomicInteger();
|
||||
var server = createArtifactServer(Map.of(
|
||||
"ext:1.0.0", pom("ext", "1.0.0", dependency("tool", "1.0.0")),
|
||||
"tool:1.0.0", pom("tool", "1.0.0", dependency("liba", "1.1.0")),
|
||||
"liba:1.1.0", pom("liba", "1.1.0", "")), requests);
|
||||
server.start();
|
||||
var tmp = Files.createTempDirectory("classpathjars").toFile();
|
||||
try {
|
||||
var project = new JarsProject(tmp, serverRepository(server));
|
||||
writeWrapperProperties(project, server, "com.example:ext:1.0.0");
|
||||
|
||||
var jars = project.extensionClasspathJars("com.example", "tool");
|
||||
var resolution_requests = settledRequests(requests);
|
||||
assertTrue(resolution_requests > 0);
|
||||
|
||||
// repeated lookups in the same build resolve nothing, also
|
||||
// for another dependency out of the same extension universe
|
||||
assertEquals(jars, project.extensionClasspathJars("com.example", "tool"));
|
||||
assertEquals(resolution_requests, requests.get());
|
||||
var liba_jars = project.extensionClasspathJars("com.example", "liba");
|
||||
assertEquals(List.of("liba-1.1.0.jar"), liba_jars.stream().map(File::getName).toList());
|
||||
var universe_requests = settledRequests(requests);
|
||||
assertEquals(resolution_requests, universe_requests);
|
||||
|
||||
// a fresh project instance reads the persisted cache
|
||||
var fresh = new JarsProject(tmp, serverRepository(server));
|
||||
assertEquals(jars, fresh.extensionClasspathJars("com.example", "tool"));
|
||||
assertEquals(universe_requests, requests.get());
|
||||
|
||||
// changing the declared extensions invalidates the cache
|
||||
writeWrapperProperties(project, server, "com.example:tool:1.0.0");
|
||||
var changed = new JarsProject(tmp, serverRepository(server));
|
||||
assertEquals(List.of("tool-1.0.0.jar", "liba-1.1.0.jar"),
|
||||
changed.extensionClasspathJars("com.example", "tool").stream().map(File::getName).toList());
|
||||
assertTrue(requests.get() > universe_requests);
|
||||
} finally {
|
||||
server.stop(0);
|
||||
FileUtils.deleteDirectory(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testDependencyClasspathJarsBomChangeInvalidates() throws Exception {
|
||||
var requests = new java.util.concurrent.atomic.AtomicInteger();
|
||||
var server = createArtifactServer(Map.of(
|
||||
"bom1:1.0.0", bomPom("bom1", "1.0.0", dependency("libb", "1.5.0")),
|
||||
"bom1:2.0.0", bomPom("bom1", "2.0.0", dependency("libb", "2.0.0")),
|
||||
"tool:1.0.0", pom("tool", "1.0.0", dependency("libb", "3.0.0")),
|
||||
"libb:1.5.0", pom("libb", "1.5.0", ""),
|
||||
"libb:2.0.0", pom("libb", "2.0.0", ""),
|
||||
"libb:3.0.0", pom("libb", "3.0.0", "")), requests);
|
||||
server.start();
|
||||
var tmp = Files.createTempDirectory("classpathjars").toFile();
|
||||
try {
|
||||
var project = new JarsProject(tmp, serverRepository(server));
|
||||
project.dependencies().scope(test)
|
||||
.include(new Bom("com.example", "bom1", new VersionNumber(1, 0, 0)))
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
assertEquals(List.of("tool-1.0.0.jar", "libb-1.5.0.jar"),
|
||||
project.dependencyClasspathJars(test, "com.example", "tool").stream().map(File::getName).toList());
|
||||
var warm_requests = requests.get();
|
||||
|
||||
// a different BOM version changes the hash and the pinned jar
|
||||
var changed = new JarsProject(tmp, serverRepository(server));
|
||||
changed.dependencies().scope(test)
|
||||
.include(new Bom("com.example", "bom1", new VersionNumber(2, 0, 0)))
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
assertEquals(List.of("tool-1.0.0.jar", "libb-2.0.0.jar"),
|
||||
changed.dependencyClasspathJars(test, "com.example", "tool").stream().map(File::getName).toList());
|
||||
assertTrue(requests.get() > warm_requests);
|
||||
} finally {
|
||||
server.stop(0);
|
||||
FileUtils.deleteDirectory(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDependencyClasspathJarsOverrideChangeInvalidates() throws Exception {
|
||||
var requests = new java.util.concurrent.atomic.AtomicInteger();
|
||||
var server = createArtifactServer(Map.of(
|
||||
"tool:1.0.0", pom("tool", "1.0.0", dependency("libb", "2.0.0")),
|
||||
"libb:2.0.0", pom("libb", "2.0.0", ""),
|
||||
"libb:2.5.0", pom("libb", "2.5.0", "")), requests);
|
||||
server.start();
|
||||
var tmp = Files.createTempDirectory("classpathjars").toFile();
|
||||
try {
|
||||
var project = new JarsProject(tmp, serverRepository(server));
|
||||
project.dependencies().scope(test)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
assertEquals(List.of("tool-1.0.0.jar", "libb-2.0.0.jar"),
|
||||
project.dependencyClasspathJars(test, "com.example", "tool").stream().map(File::getName).toList());
|
||||
var warm_requests = requests.get();
|
||||
|
||||
// a version override changes the hash and the resolved jar
|
||||
var overridden = new JarsProject(tmp, serverRepository(server));
|
||||
overridden.properties().put(VersionResolution.PROPERTY_OVERRIDE_PREFIX, "com.example:libb:2.5.0");
|
||||
overridden.dependencies().scope(test)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
assertEquals(List.of("tool-1.0.0.jar", "libb-2.5.0.jar"),
|
||||
overridden.dependencyClasspathJars(test, "com.example", "tool").stream().map(File::getName).toList());
|
||||
assertTrue(requests.get() > warm_requests);
|
||||
} finally {
|
||||
server.stop(0);
|
||||
FileUtils.deleteDirectory(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDependencyClasspathJarsRepositoryChangeInvalidates() throws Exception {
|
||||
var requests1 = new java.util.concurrent.atomic.AtomicInteger();
|
||||
var requests2 = new java.util.concurrent.atomic.AtomicInteger();
|
||||
var poms = Map.of("tool:1.0.0", pom("tool", "1.0.0", ""));
|
||||
var server1 = createArtifactServer(poms, requests1);
|
||||
var server2 = createArtifactServer(poms, requests2);
|
||||
server1.start();
|
||||
server2.start();
|
||||
var tmp = Files.createTempDirectory("classpathjars").toFile();
|
||||
try {
|
||||
var project = new JarsProject(tmp, serverRepository(server1));
|
||||
project.dependencies().scope(test)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
project.dependencyClasspathJars(test, "com.example", "tool");
|
||||
assertTrue(requests1.get() > 0);
|
||||
|
||||
// a different repository list changes the hash, the second
|
||||
// server sees the resolution instead of the cache answering
|
||||
var moved = new JarsProject(tmp, serverRepository(server2));
|
||||
moved.dependencies().scope(test)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
moved.dependencyClasspathJars(test, "com.example", "tool");
|
||||
assertTrue(requests2.get() > 0);
|
||||
} finally {
|
||||
server1.stop(0);
|
||||
server2.stop(0);
|
||||
FileUtils.deleteDirectory(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDependencyClasspathJarsScopeKeysIsolated() throws Exception {
|
||||
var requests = new java.util.concurrent.atomic.AtomicInteger();
|
||||
var server = createArtifactServer(Map.of(
|
||||
"tool:1.0.0", pom("tool", "1.0.0", ""),
|
||||
"tool:2.0.0", pom("tool", "2.0.0", "")), requests);
|
||||
server.start();
|
||||
var tmp = Files.createTempDirectory("classpathjars").toFile();
|
||||
try {
|
||||
var project = new JarsProject(tmp, serverRepository(server));
|
||||
project.dependencies().scope(provided)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
project.dependencies().scope(test)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(2, 0, 0)));
|
||||
|
||||
// the same coordinate is cached per scope, with its own version
|
||||
assertEquals(List.of("tool-1.0.0.jar"),
|
||||
project.dependencyClasspathJars(provided, "com.example", "tool").stream().map(File::getName).toList());
|
||||
assertEquals(List.of("tool-2.0.0.jar"),
|
||||
project.dependencyClasspathJars(test, "com.example", "tool").stream().map(File::getName).toList());
|
||||
var warm_requests = settledRequests(requests);
|
||||
|
||||
var fresh = new JarsProject(tmp, serverRepository(server));
|
||||
fresh.dependencies().scope(provided)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
fresh.dependencies().scope(test)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(2, 0, 0)));
|
||||
assertEquals(List.of("tool-1.0.0.jar"),
|
||||
fresh.dependencyClasspathJars(provided, "com.example", "tool").stream().map(File::getName).toList());
|
||||
assertEquals(List.of("tool-2.0.0.jar"),
|
||||
fresh.dependencyClasspathJars(test, "com.example", "tool").stream().map(File::getName).toList());
|
||||
assertEquals(warm_requests, requests.get());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
FileUtils.deleteDirectory(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClasspathCacheCoexistsWithDependencyTrees() throws Exception {
|
||||
var requests = new java.util.concurrent.atomic.AtomicInteger();
|
||||
var server = createArtifactServer(Map.of(
|
||||
"tool:1.0.0", pom("tool", "1.0.0", "")), requests);
|
||||
server.start();
|
||||
var tmp = Files.createTempDirectory("classpathjars").toFile();
|
||||
try {
|
||||
var project = new JarsProject(tmp, serverRepository(server));
|
||||
project.dependencies().scope(test)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
project.dependencyClasspathJars(test, "com.example", "tool");
|
||||
var warm_requests = settledRequests(requests);
|
||||
|
||||
// another BldCache user writing to the same file with the same
|
||||
// hash preserves the classpath entries
|
||||
var cache = new BldCache(project.libBldDirectory(), new VersionResolution(project.properties()));
|
||||
cache.cacheDependenciesHash(project.repositories(), project.dependencies());
|
||||
assertTrue(cache.isDependenciesHashValid());
|
||||
cache.cacheDependenciesTestDependencyTree("tree placeholder");
|
||||
cache.writeCache();
|
||||
|
||||
assertEquals("tree placeholder", cache.getCachedDependenciesTestDependencyTree());
|
||||
var fresh = new JarsProject(tmp, serverRepository(server));
|
||||
fresh.dependencies().scope(test)
|
||||
.include(new Dependency("com.example", "tool", new VersionNumber(1, 0, 0)));
|
||||
fresh.dependencyClasspathJars(test, "com.example", "tool");
|
||||
assertEquals(warm_requests, requests.get());
|
||||
|
||||
// and a classpath write with the same hash preserves the tree
|
||||
fresh.dependencyClasspathJars(test, "com.example", "tool");
|
||||
assertEquals("tree placeholder", cache.getCachedDependenciesTestDependencyTree());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
FileUtils.deleteDirectory(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExtensionClasspathJarsUndeclaredAfterCached() throws Exception {
|
||||
var server = createArtifactServer(Map.of(
|
||||
"ext:1.0.0", pom("ext", "1.0.0", dependency("tool", "1.0.0")),
|
||||
"tool:1.0.0", pom("tool", "1.0.0", ""),
|
||||
"other:1.0.0", pom("other", "1.0.0", "")));
|
||||
server.start();
|
||||
var tmp = Files.createTempDirectory("classpathjars").toFile();
|
||||
try {
|
||||
var project = new JarsProject(tmp, serverRepository(server));
|
||||
writeWrapperProperties(project, server, "com.example:ext:1.0.0");
|
||||
assertEquals(1, project.extensionClasspathJars("com.example", "tool").size());
|
||||
|
||||
// the extension that brought the tool in is replaced, a stale
|
||||
// cache entry must not keep answering for it
|
||||
writeWrapperProperties(project, server, "com.example:other:1.0.0");
|
||||
var changed = new JarsProject(tmp, serverRepository(server));
|
||||
var exception = assertThrows(IllegalArgumentException.class,
|
||||
() -> changed.extensionClasspathJars("com.example", "tool"));
|
||||
assertTrue(exception.getMessage().contains("isn't part of the extensions"));
|
||||
} finally {
|
||||
server.stop(0);
|
||||
FileUtils.deleteDirectory(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// the POM prefetcher can still have a speculative request in flight
|
||||
// when resolution returns, count requests only after they settle
|
||||
private static int settledRequests(java.util.concurrent.atomic.AtomicInteger requests)
|
||||
throws InterruptedException {
|
||||
var last = requests.get();
|
||||
var stable_since = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - stable_since < 300) {
|
||||
Thread.sleep(50);
|
||||
var current = requests.get();
|
||||
if (current != last) {
|
||||
last = current;
|
||||
stable_since = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
private static Repository serverRepository(HttpServer server) {
|
||||
return new Repository("http://localhost:" + server.getAddress().getPort() + "/");
|
||||
}
|
||||
|
||||
private static HttpServer createArtifactServer(Map<String, String> poms)
|
||||
throws IOException {
|
||||
return createArtifactServer(poms, new java.util.concurrent.atomic.AtomicInteger());
|
||||
}
|
||||
|
||||
private static HttpServer createArtifactServer(Map<String, String> poms, java.util.concurrent.atomic.AtomicInteger requests)
|
||||
throws IOException {
|
||||
var server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
|
||||
server.createContext("/", exchange -> {
|
||||
requests.incrementAndGet();
|
||||
var segments = exchange.getRequestURI().getPath().split("/");
|
||||
var filename = segments[segments.length - 1];
|
||||
byte[] body = null;
|
||||
|
|
|
|||
Loading…
Reference in a new issue