Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: optimize resolving manifests #293

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.options.Option;

Expand Down Expand Up @@ -64,7 +66,7 @@ public void resolveAutodocManifest() {
getProject().getConfigurations()
.stream().flatMap(config -> config.getDependencies().stream())
.distinct()
.filter(this::dependencyFilter)
.filter(this::includeDependency)
.filter(dep -> !getExclusions().contains(dep.getName()))
.map(this::createSource)
.filter(Optional::isPresent)
Expand All @@ -76,7 +78,20 @@ public void setOutput(String output) {
this.outputDirectoryOverride = new File(output);
}

protected abstract boolean dependencyFilter(Dependency dependency);
@OutputDirectory
public File getOutputFile() {
return downloadDirectory.toFile();
}

/**
* Whether to consider a particular dependency for manifest resolution.
*
* @param dependency The dependency in question
* @return true if it should be considered, false otherwise.
*/
protected boolean includeDependency(Dependency dependency) {
return true;
}

/**
* Returns an {@link InputStream} that points to the physical location of the autodoc manifest file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,69 @@

import org.gradle.api.artifacts.Dependency;

import java.io.InputStream;
import java.net.URI;
import java.util.Objects;

import static java.lang.String.format;

/**
* Represents the combination of a dependency and a pointer (URL) to its physical location.
*
* @param dependency the dependency in question
* @param uri the location where the physical file exists
* @param classifier what type of dependency we have, e.g. sources, sources, manifest etc
* @param type file extension
*/
public record DependencySource(Dependency dependency, URI uri, String classifier, String type) {
@Override
public String toString() {
return "{" +
"dependency=" + dependency +
", uri=" + uri +
'}';
public abstract class DependencySource {
Fixed Show fixed Hide fixed
private final Dependency dependency;
private final URI uri;
private final String classifier;
private final String type;

/**
* @param dependency the dependency in question
* @param uri the location where the physical file exists
* @param classifier what type of dependency we have, e.g. sources, sources, manifest etc
* @param type file extension
*/
public DependencySource(Dependency dependency, URI uri, String classifier, String type) {
this.dependency = dependency;
this.uri = uri;
this.classifier = classifier;
this.type = type;
}

/**
* constructs the filename NAME-VERSION-CLASSIFIER.TYPE
*/
String filename() {
return format("%s-%s-%s.%s", dependency.getName(), dependency.getVersion(), classifier, type);
}

/**
* Checks whether a dependency exists. In some implementations this may involve remote calls, so use this with prejudice!
*
* @return whether the dependency exists, i.e. the {@link DependencySource#uri()} points to a valid file
*/
public abstract boolean exists();

public Dependency dependency() {
return dependency;
}

public URI uri() {
return uri;
}

public String classifier() {
return classifier;
}

public String type() {
return type;
}

/**
* Opens an input stream to the file located at {@link DependencySource#uri()}. It is highly recommended to check {@link DependencySource#exists()}
* beforehand.
*
* @return Either the input stream to the file, or {@code null} if failed.
*/
public abstract InputStream inputStream();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.eclipse.edc.plugins.autodoc.tasks;

import org.gradle.api.artifacts.Dependency;

import java.net.URI;

class DependencySourceFactory {
public static DependencySource createDependencySource(URI uri, Dependency dependency, String classifier, String type) {
if (uri.getScheme().equals("file")) {
return new FileSource(dependency, uri, classifier, type);
} else if (uri.getScheme().startsWith("http")) {
return new HttpSource(dependency, uri, classifier, type);
}
else throw new RuntimeException("Unknown URI scheme " + uri);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
package org.eclipse.edc.plugins.autodoc.tasks;

import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
import org.gradle.api.tasks.OutputFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.time.Instant;
Expand All @@ -37,31 +38,19 @@ public class DownloadManifestTask extends AbstractManifestResolveTask {
public static final String NAME = "downloadManifests";
private static final Duration MAX_MANIFEST_AGE = Duration.ofHours(24);

private final HttpClient httpClient;

public DownloadManifestTask() {
httpClient = HttpClient.newHttpClient();
}

@Override
protected boolean dependencyFilter(Dependency dependency) {
return !(dependency instanceof DefaultProjectDependency);
protected boolean includeDependency(Dependency dependency) {
return !(dependency instanceof ProjectDependency);
}

@Override
protected InputStream resolveManifest(DependencySource autodocManifest) {
var request = HttpRequest.newBuilder().uri(autodocManifest.uri()).GET().build();
HttpResponse<InputStream> response;
try {
response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
if (response.statusCode() != 200) {
getLogger().warn("Could not download {}, HTTP response: {}", autodocManifest.dependency(), response);
var inputStream = autodocManifest.inputStream();
if (inputStream == null) {
getLogger().warn("Could not obtain {}", autodocManifest.dependency());
return null;
}
return response.body();
return inputStream;
}

/**
Expand All @@ -79,7 +68,7 @@ protected InputStream resolveManifest(DependencySource autodocManifest) {
@Override
protected Optional<DependencySource> createSource(Dependency dependency) {
if (isLocalFileValid(dependency)) {
getLogger().debug("Local file {} was deemed to be viable, will not download", new DependencySource(dependency, null, MANIFEST_CLASSIFIER, MANIFEST_TYPE).filename());
getLogger().debug("Local file {} was deemed to be viable, will not download", dependency);
return Optional.empty();
}
var repos = getProject().getRepositories().stream().toList();
Expand All @@ -89,18 +78,14 @@ protected Optional<DependencySource> createSource(Dependency dependency) {
.map(repo -> {
var repoUrl = createArtifactUrl(dependency, repo);
try {
// we use a HEAD request, because we only want to see whether that module has a `-manifest.json`
var uri = URI.create(repoUrl);
var headRequest = HttpRequest.newBuilder()
.uri(uri)
.method("HEAD", HttpRequest.BodyPublishers.noBody())
.build();
var response = httpClient.send(headRequest, HttpResponse.BodyHandlers.discarding());
if (response.statusCode() == 200) {
return new DependencySource(dependency, uri, MANIFEST_CLASSIFIER, MANIFEST_TYPE);
var ds = DependencySourceFactory.createDependencySource(URI.create(repoUrl), dependency, MANIFEST_CLASSIFIER, MANIFEST_TYPE);
if (ds.exists()) {
getLogger().debug("Manifest found for '{}' at {}", dependency.getName(), ds.uri());
return ds;
}
getLogger().debug("Manifest not found for '{}' at {}", dependency.getName(), ds.uri());
return null;
} catch (IOException | InterruptedException | IllegalArgumentException e) {
} catch (IllegalArgumentException e) {
return null;
}
})
Expand All @@ -126,7 +111,8 @@ private String createArtifactUrl(Dependency dep, MavenArtifactRepository repo) {
*/
private boolean isLocalFileValid(Dependency dep) {
if (!downloadDirectory.toFile().exists()) return false;
var filePath = downloadDirectory.resolve(new DependencySource(dep, null, MANIFEST_CLASSIFIER, MANIFEST_TYPE).filename());
var filename = format("%s-%s-%s.%s", dep.getName(), dep.getVersion(), MANIFEST_CLASSIFIER, MANIFEST_TYPE);
var filePath = downloadDirectory.resolve(filename);
var file = filePath.toFile();
if (!file.exists() || !file.canRead()) return false;

Expand All @@ -137,6 +123,4 @@ private boolean isLocalFileValid(Dependency dep) {
throw new RuntimeException(e);
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.eclipse.edc.plugins.autodoc.tasks;

import org.gradle.api.artifacts.Dependency;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;

/**
* A dependency that is represented in the local file system, e.g. the local Maven cache
*/
public class FileSource extends DependencySource {
/**
* @param dependency the dependency in question
* @param uri the location where the physical file exists
* @param classifier what type of dependency we have, e.g. sources, sources, manifest etc
* @param type file extension
*/
public FileSource(Dependency dependency, URI uri, String classifier, String type) {
super(dependency, uri, classifier, type);
}

@Override
public boolean exists() {
return Files.exists(Path.of(uri()));
}

@Override
public InputStream inputStream() {
try {
return new FileInputStream(new File(uri()));
} catch (FileNotFoundException e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.eclipse.edc.plugins.autodoc.tasks;

import org.gradle.api.artifacts.Dependency;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

/**
* A dependency that is located in a remote repository, such as Maven Central
*/
public class HttpSource extends DependencySource {
private final HttpClient httpClient;


/**
* @param dependency the dependency in question
* @param uri the location where the physical file exists
* @param classifier what type of dependency we have, e.g. sources, sources, manifest etc
* @param type file extension
*/
public HttpSource(Dependency dependency, URI uri, String classifier, String type) {
super(dependency, uri, classifier, type);
httpClient = HttpClient.newHttpClient();

}

/**
* A HEAD request is performed to check if the file is actually present at the remote location.
*/
@Override
public boolean exists() {
var headRequest = HttpRequest.newBuilder()
.uri(uri())
.method("HEAD", HttpRequest.BodyPublishers.noBody())
.build();
try {
var response = httpClient.send(headRequest, HttpResponse.BodyHandlers.discarding());
return response.statusCode() == 200;
} catch (IOException e) {
return false;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

/**
* Opens an input stream to the remote file. If the remote file does not exist, {@code null} is returned.
* @throws RuntimeException if the HTTP request raises an {@link IOException} or an {@link InterruptedException}
*/
@Override
public InputStream inputStream() {
var request = HttpRequest.newBuilder().uri(uri()).GET().build();
HttpResponse<InputStream> response;
try {
response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
if(response.statusCode() == 200) {
return response.body();
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.eclipse.edc.plugins.autodoc.tasks;

import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;

import java.io.File;
Expand All @@ -29,8 +30,8 @@ public class ResolveManifestTask extends AbstractManifestResolveTask {
public static final String DESCRIPTION = "This task is intended for BOM modules and resolves the autodoc manifests of all modules that the project depends on. By default, all manifests are stored in {project}/build/autodoc.";

@Override
protected boolean dependencyFilter(Dependency dependency) {
return dependency instanceof DefaultProjectDependency;
protected boolean includeDependency(Dependency dependency) {
return dependency instanceof ProjectDependency;
}

@Override
Expand All @@ -51,10 +52,10 @@ protected InputStream resolveManifest(DependencySource autodocManifest) {

@Override
protected Optional<DependencySource> createSource(Dependency dependency) {
if (dependency instanceof DefaultProjectDependency localDepdendency) {
var manifestFile = localDepdendency.getDependencyProject().getLayout().getBuildDirectory().file("edc.json");
if (dependency instanceof DefaultProjectDependency localDependency) {
var manifestFile = localDependency.getDependencyProject().getLayout().getBuildDirectory().file("edc.json");
if (manifestFile.isPresent()) {
return Optional.of(new DependencySource(localDepdendency, manifestFile.get().getAsFile().toURI(), MANIFEST_CLASSIFIER, MANIFEST_TYPE));
return Optional.of(DependencySourceFactory.createDependencySource( manifestFile.get().getAsFile().toURI(), dependency, MANIFEST_CLASSIFIER, MANIFEST_TYPE));
} else {
getLogger().debug("No manifest file found for dependency {}", dependency);
}
Expand Down
Loading