aboutsummaryrefslogtreecommitdiff
path: root/src/gradlecomp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gradlecomp')
-rw-r--r--src/gradlecomp/java/com/amadornes/artifactural/gradle/DependencyResolver.java83
-rw-r--r--src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleArtifact.java26
-rw-r--r--src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java303
-rw-r--r--src/gradlecomp/java/com/amadornes/artifactural/gradle/ReflectionUtils.java52
4 files changed, 464 insertions, 0 deletions
diff --git a/src/gradlecomp/java/com/amadornes/artifactural/gradle/DependencyResolver.java b/src/gradlecomp/java/com/amadornes/artifactural/gradle/DependencyResolver.java
new file mode 100644
index 0000000..12364d7
--- /dev/null
+++ b/src/gradlecomp/java/com/amadornes/artifactural/gradle/DependencyResolver.java
@@ -0,0 +1,83 @@
+package com.amadornes.artifactural.gradle;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.*;
+import org.gradle.internal.impldep.com.google.common.cache.Cache;
+import org.gradle.internal.impldep.com.google.common.cache.CacheBuilder;
+
+import java.io.File;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class DependencyResolver {
+
+ private final Project project;
+ private final AtomicInteger counter = new AtomicInteger(0);
+ private final Cache<String, CompletableFuture<Set<File>>> resolved = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build();
+
+ public DependencyResolver(Project project) {
+ this.project = project;
+ }
+
+ /**
+ * Resolves a dependency, downloading the file and its transitives
+ * if not cached and returns the set of files.
+ */
+ public Set<File> resolveDependency(Dependency dependency) {
+ if (dependency instanceof FileCollectionDependency) {
+ return ((FileCollectionDependency) dependency).getFiles().getFiles();
+ }
+ String name = dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion();
+ if (dependency instanceof ModuleDependency) {
+ Set<DependencyArtifact> artifacts = ((ModuleDependency) dependency).getArtifacts();
+ if (!artifacts.isEmpty()) {
+ DependencyArtifact artifact = artifacts.iterator().next();
+ name += ":" + artifact.getClassifier() + "@" + artifact.getExtension();
+ }
+ }
+
+ // If this dep is being resolved on another thread, let it do it
+ CompletableFuture<Set<File>> future;
+ boolean found = true;
+ synchronized (resolved) {
+ future = resolved.getIfPresent(name);
+ if (future == null) {
+ resolved.put(name, future = new CompletableFuture<>());
+ found = false;
+ }
+ }
+
+ if (found) {
+ try {
+ return future.get();
+ } catch (InterruptedException | ExecutionException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ // No other thread is resolving this dep and we've claimed it, so let's go!
+ int currentID = counter.getAndIncrement();
+ Configuration cfg = project.getConfigurations().maybeCreate("resolve_dep_" + currentID);
+ cfg.getDependencies().add(dependency);
+ Set<File> files = cfg.resolve();
+ project.getConfigurations().remove(cfg);
+ future.complete(files);
+ return files;
+ }
+
+ /**
+ * Resolves a dependency, downloading the file and its transitives
+ * if not cached and returns the set of files.
+ */
+ public Set<File> resolveDependency(Object dependency, boolean transitive) {
+ Dependency dep = project.getDependencies().create(dependency);
+ if (dep instanceof ClientModule) {
+ dep = ((ClientModule) dep).copy().setTransitive(transitive);
+ }
+ return resolveDependency(dep);
+ }
+
+}
diff --git a/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleArtifact.java b/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleArtifact.java
new file mode 100644
index 0000000..2e742ed
--- /dev/null
+++ b/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleArtifact.java
@@ -0,0 +1,26 @@
+package com.amadornes.artifactural.gradle;
+
+import com.amadornes.artifactural.api.artifact.Artifact;
+import com.amadornes.artifactural.api.artifact.ArtifactIdentifier;
+import com.amadornes.artifactural.api.artifact.ArtifactType;
+import com.amadornes.artifactural.base.artifact.StreamableArtifact;
+
+import java.io.File;
+import java.util.Set;
+
+public class GradleArtifact {
+
+ public static Artifact maven(DependencyResolver resolver, ArtifactIdentifier identifier, ArtifactType type) {
+ Set<File> files = resolver.resolveDependency(
+ identifier.getGroup()
+ + ":" + identifier.getName()
+ + ":" + identifier.getVersion()
+ + (identifier.getClassifier().isEmpty() ? "" : ":" + identifier.getClassifier())
+ + (identifier.getExtension().isEmpty() ? "" : "@" + identifier.getExtension()),
+ false
+ );
+ if (files.isEmpty()) return Artifact.none();
+ return StreamableArtifact.ofJar(identifier, type, files.iterator().next());
+ }
+
+}
diff --git a/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java b/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java
new file mode 100644
index 0000000..a601a1e
--- /dev/null
+++ b/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java
@@ -0,0 +1,303 @@
+package com.amadornes.artifactural.gradle;
+
+import com.amadornes.artifactural.api.artifact.Artifact;
+import com.amadornes.artifactural.api.artifact.ArtifactIdentifier;
+import com.amadornes.artifactural.api.repository.Repository;
+import com.amadornes.artifactural.base.artifact.ArtifactIdentifierImpl;
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectCollection;
+import org.gradle.api.Transformer;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleComponentRepository;
+import org.gradle.api.internal.artifacts.repositories.AbstractArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.DefaultMavenArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
+import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceArtifactResolver;
+import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
+import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver;
+import org.gradle.api.resources.ResourceException;
+import org.gradle.internal.impldep.com.google.common.io.CountingInputStream;
+import org.gradle.internal.impldep.org.apache.commons.io.IOUtils;
+import org.gradle.internal.resource.*;
+import org.gradle.internal.resource.metadata.DefaultExternalResourceMetaData;
+import org.gradle.internal.resource.metadata.ExternalResourceMetaData;
+
+import javax.annotation.Nullable;
+import java.io.*;
+import java.net.URI;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GradleRepositoryAdapter extends AbstractArtifactRepository implements ResolutionAwareRepository {
+
+ private static final Pattern URL_PATTERN = Pattern.compile(
+ "^/(?<group>\\S+(?:/\\S+)*)/(?<name>\\S+)/(?<version>\\S+)/" +
+ "\\2-\\3(?:-(?<classifier>[^.\\s]+))?\\.(?<extension>\\S+)$");
+
+ public static GradleRepositoryAdapter add(RepositoryHandler handler, String name, Object url, Repository repository) {
+ // Create the real maven test we'll be using and remove it
+ MavenArtifactRepository maven = handler.maven($ -> {
+ $.setName(name);
+ $.setUrl(url);
+ });
+ handler.remove(maven);
+
+ // Add our own custom test instead, using the real one in the background
+ GradleRepositoryAdapter repo = new GradleRepositoryAdapter((DefaultMavenArtifactRepository) maven, repository);
+ handler.add(repo);
+ return repo;
+ }
+
+ private final DefaultMavenArtifactRepository maven;
+ private final Repository repository;
+
+ private GradleRepositoryAdapter(DefaultMavenArtifactRepository maven, Repository repository) {
+ this.maven = maven;
+ this.repository = repository;
+ }
+
+ @Override
+ public String getName() {
+ return maven.getName(); // Proxy to the real repo
+ }
+
+ @Override
+ public void setName(String name) {
+ maven.setName(name); // Proxy to the real repo
+ }
+
+ @Override
+ public String getDisplayName() {
+ return maven.getDisplayName(); // Proxy to the real repo
+ }
+
+ @Override
+ public void onAddToContainer(NamedDomainObjectCollection<ArtifactRepository> container) {
+ // No-op. The real repo will get this already
+ }
+
+ @Override
+ public ConfiguredModuleComponentRepository createResolver() {
+ MavenResolver resolver = (MavenResolver) maven.createResolver();
+ ExternalResourceRepository repo = new StreamingRepo();
+
+ ExternalResourceArtifactResolver artifactResolver = ReflectionUtils.invoke(resolver, ExternalResourceResolver.class, "createArtifactResolver");
+ ReflectionUtils.alter(resolver, "repository", prev -> repo);
+ ReflectionUtils.alter(resolver, "mavenMetaDataLoader.cacheAwareExternalResourceAccessor.delegate", prev -> repo);
+ ReflectionUtils.alter(artifactResolver, "repository", prev -> repo);
+
+ return resolver;
+ }
+
+ private class StreamingRepo implements ExternalResourceRepository {
+
+ @Override
+ public ExternalResourceRepository withProgressLogging() {
+ return this;
+ }
+
+ @Override
+ public ExternalResource resource(ExternalResourceName name, boolean revalidate) {
+ URI uri = name.getUri();
+ Matcher matcher = URL_PATTERN.matcher(uri.getPath());
+ if (!matcher.matches()) return new NullExternalResource(uri);
+ ArtifactIdentifier identifier = new ArtifactIdentifierImpl(
+ matcher.group("group").replace('/', '.'),
+ matcher.group("name"),
+ matcher.group("version"),
+ matcher.group("classifier"),
+ matcher.group("extension"));
+ Artifact artifact = repository.getArtifact(identifier);
+ if (!artifact.isPresent()) return new NullExternalResource(uri);
+ return new CustomArtifactExternalResource(uri, artifact);
+ }
+
+ @Override
+ public ExternalResource resource(ExternalResourceName name) {
+ return resource(name, false);
+ }
+
+ }
+
+ private class CustomArtifactExternalResource extends AbstractExternalResource {
+
+ private final URI uri;
+ private final Artifact artifact;
+
+ private CustomArtifactExternalResource(URI uri, Artifact artifact) {
+ this.uri = uri;
+ this.artifact = artifact;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return uri.toString();
+ }
+
+ @Override
+ public URI getURI() {
+ return uri;
+ }
+
+ @Nullable
+ @Override
+ public ExternalResourceReadResult<Void> writeToIfPresent(File file) {
+ try {
+ if (!artifact.isPresent()) return null;
+ FileOutputStream out = new FileOutputStream(file);
+ ExternalResourceReadResult<Void> result = writeTo(out);
+ out.close();
+ return result;
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
+ @Override
+ public ExternalResourceReadResult<Void> writeTo(OutputStream out) throws ResourceException {
+ return withContent(in -> {
+ try {
+ IOUtils.copy(in, out);
+ } catch (IOException ex) {
+ throw ResourceExceptions.failure(uri, "Failed to write resource!", ex);
+ }
+ });
+ }
+
+ @Override
+ public ExternalResourceReadResult<Void> withContent(Action<? super InputStream> action) throws ResourceException {
+ try {
+ if (!artifact.isPresent()) throw ResourceExceptions.getMissing(uri);
+ CountingInputStream in = new CountingInputStream(artifact.openStream());
+ action.execute(in);
+ in.close();
+ return ExternalResourceReadResult.of(in.getCount());
+ } catch (IOException ex) {
+ throw ResourceExceptions.failure(uri, "Failed to write resource!", ex);
+ }
+ }
+
+ @Nullable
+ @Override
+ public <T> ExternalResourceReadResult<T> withContentIfPresent(Transformer<? extends T, ? super InputStream> transformer) {
+ try {
+ if (!artifact.isPresent()) return null;
+ CountingInputStream in = new CountingInputStream(artifact.openStream());
+ T result = transformer.transform(in);
+ in.close();
+ return ExternalResourceReadResult.of(in.getCount(), result);
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
+ @Nullable
+ @Override
+ public <T> ExternalResourceReadResult<T> withContentIfPresent(ContentAction<? extends T> contentAction) {
+ try {
+ if (!artifact.isPresent()) return null;
+ CountingInputStream in = new CountingInputStream(artifact.openStream());
+ T result = contentAction.execute(in, getMetaData());
+ in.close();
+ return ExternalResourceReadResult.of(in.getCount(), result);
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
+ @Override
+ public ExternalResourceWriteResult put(ReadableContent readableContent) throws ResourceException {
+ throw ResourceExceptions.putFailed(uri, null);
+ }
+
+ @Nullable
+ @Override
+ public List<String> list() throws ResourceException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ExternalResourceMetaData getMetaData() {
+ try {
+ if (!artifact.isPresent()) return null;
+ InputStream stream = artifact.openStream();
+ int length = stream.available();
+ stream.close();
+ return new DefaultExternalResourceMetaData(uri, 0, length);
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
+ }
+
+ private class NullExternalResource extends AbstractExternalResource {
+
+ private final URI uri;
+
+ private NullExternalResource(URI uri) {
+ this.uri = uri;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return uri.toString();
+ }
+
+ @Override
+ public URI getURI() {
+ return uri;
+ }
+
+ @Nullable
+ @Override
+ public ExternalResourceReadResult<Void> writeToIfPresent(File destination) throws ResourceException {
+ return null;
+ }
+
+ @Override
+ public ExternalResourceReadResult<Void> writeTo(OutputStream destination) throws ResourceException {
+ throw ResourceExceptions.getMissing(uri);
+ }
+
+ @Override
+ public ExternalResourceReadResult<Void> withContent(Action<? super InputStream> readAction) throws ResourceException {
+ throw ResourceExceptions.getMissing(uri);
+ }
+
+ @Nullable
+ @Override
+ public <T> ExternalResourceReadResult<T> withContentIfPresent(Transformer<? extends T, ? super InputStream> readAction) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public <T> ExternalResourceReadResult<T> withContentIfPresent(ContentAction<? extends T> readAction) {
+ return null;
+ }
+
+ @Override
+ public ExternalResourceWriteResult put(ReadableContent source) throws ResourceException {
+ throw ResourceExceptions.getMissing(uri);
+ }
+
+ @Nullable
+ @Override
+ public List<String> list() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ExternalResourceMetaData getMetaData() {
+ return null;
+ }
+
+ }
+
+}
diff --git a/src/gradlecomp/java/com/amadornes/artifactural/gradle/ReflectionUtils.java b/src/gradlecomp/java/com/amadornes/artifactural/gradle/ReflectionUtils.java
new file mode 100644
index 0000000..4fbe925
--- /dev/null
+++ b/src/gradlecomp/java/com/amadornes/artifactural/gradle/ReflectionUtils.java
@@ -0,0 +1,52 @@
+package com.amadornes.artifactural.gradle;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.function.UnaryOperator;
+
+public class ReflectionUtils {
+
+ public static <T> void alter(Object target, String name, UnaryOperator<T> operator) {
+ try {
+ Object prev = target;
+ Field f = null;
+ for (String n : name.split("\\.")) {
+ f = findField(target.getClass(), n);
+ if(f == null) throw new IllegalStateException("Could not find '" + name + "'");
+ f.setAccessible(true);
+ prev = target;
+ target = f.get(target);
+ }
+ if(f == null) throw new IllegalStateException("Could not find '" + name + "'");
+ f.set(prev, operator.apply((T) target));
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static Field findField(Class<?> clazz, String name) {
+ while (clazz != Object.class) {
+ for (Field f : clazz.getDeclaredFields()) {
+ if (f.getName().equals(name)) {
+ return f;
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * Invokes a method (can be private).
+ */
+ public static <T> T invoke(Object target, Class<?> type, String name, Object... args) {
+ try {
+ Method method = type.getDeclaredMethod(name);
+ method.setAccessible(true);
+ return (T) method.invoke(target, args);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+}