-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Maintenance score based on commit frequency
- Loading branch information
1 parent
899a413
commit f053b0e
Showing
27 changed files
with
1,821 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package com.giovds; | ||
|
||
import com.giovds.dto.PomResponse; | ||
import com.giovds.dto.Scm; | ||
import org.apache.maven.plugin.logging.Log; | ||
import org.w3c.dom.Document; | ||
import org.w3c.dom.Element; | ||
import org.w3c.dom.NodeList; | ||
import org.xml.sax.SAXException; | ||
|
||
import javax.xml.parsers.DocumentBuilder; | ||
import javax.xml.parsers.DocumentBuilderFactory; | ||
import javax.xml.parsers.ParserConfigurationException; | ||
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.charset.StandardCharsets; | ||
|
||
public class PomClient implements PomClientInterface { | ||
|
||
private final String basePath; | ||
private final String pomPathTemplate; | ||
|
||
private final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | ||
|
||
private final HttpClient client = HttpClient.newBuilder() | ||
.version(HttpClient.Version.HTTP_2) | ||
.build(); | ||
|
||
private final Log log; | ||
|
||
public PomClient(Log log) { | ||
this("https://repo1.maven.org", "/maven2/%s/%s/%s/%s-%s.pom", log); | ||
} | ||
|
||
public PomClient(String basePath, String pomPathTemplate, Log log) { | ||
this.basePath = basePath; | ||
this.pomPathTemplate = pomPathTemplate; | ||
this.log = log; | ||
} | ||
|
||
public PomResponse getPom(String group, String artifact, String version) throws IOException, InterruptedException { | ||
final String path = String.format(pomPathTemplate, group.replace(".", "/"), artifact, version, artifact, version); | ||
final HttpRequest request = HttpRequest.newBuilder() | ||
.GET() | ||
.uri(URI.create(basePath + path)) | ||
.build(); | ||
|
||
return client.send(request, new PomResponseBodyHandler()).body(); | ||
} | ||
|
||
private class PomResponseBodyHandler implements HttpResponse.BodyHandler<PomResponse> { | ||
|
||
@Override | ||
public HttpResponse.BodySubscriber<PomResponse> apply(final HttpResponse.ResponseInfo responseInfo) { | ||
int statusCode = responseInfo.statusCode(); | ||
|
||
if (statusCode < 200 || statusCode >= 300) { | ||
return HttpResponse.BodySubscribers.mapping(HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8), s -> { | ||
throw new RuntimeException("Search failed: status: %d body: %s".formatted(responseInfo.statusCode(), s)); | ||
}); | ||
} | ||
|
||
HttpResponse.BodySubscriber<InputStream> stream = HttpResponse.BodySubscribers.ofInputStream(); | ||
|
||
return HttpResponse.BodySubscribers.mapping(stream, this::toPomResponse); | ||
} | ||
|
||
private PomResponse toPomResponse(final InputStream inputStream) { | ||
try (final InputStream input = inputStream) { | ||
DocumentBuilder documentBuilder = PomClient.this.documentBuilderFactory.newDocumentBuilder(); | ||
Document doc = documentBuilder.parse(input); | ||
|
||
doc.getDocumentElement().normalize(); | ||
|
||
Element root = doc.getDocumentElement(); | ||
NodeList urlNodes = root.getElementsByTagName("url"); | ||
|
||
if (urlNodes.getLength() == 0) { | ||
return PomResponse.empty(); | ||
} | ||
String url = urlNodes.item(0).getTextContent(); | ||
|
||
Scm scm = Scm.empty(); | ||
NodeList scmNodes = root.getElementsByTagName("scm"); | ||
if (scmNodes.getLength() > 0) { | ||
Element scmElement = (Element) scmNodes.item(0); | ||
NodeList scmUrlNodes = scmElement.getElementsByTagName("url"); | ||
if (scmUrlNodes.getLength() > 0) { | ||
String scmUrl = scmUrlNodes.item(0).getTextContent(); | ||
scm = new Scm(scmUrl); | ||
} | ||
} | ||
|
||
return new PomResponse(url, scm); | ||
} catch (IOException | ParserConfigurationException | SAXException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.giovds; | ||
|
||
import com.giovds.dto.PomResponse; | ||
|
||
import java.io.IOException; | ||
|
||
public interface PomClientInterface { | ||
PomResponse getPom(String group, String artifact, String version) throws IOException, InterruptedException; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package com.giovds; | ||
|
||
import com.giovds.collector.github.GithubCollector; | ||
import com.giovds.collector.github.GithubGuesser; | ||
import com.giovds.dto.PomResponse; | ||
import com.giovds.dto.github.internal.Collected; | ||
import com.giovds.evaluator.MaintenanceEvaluator; | ||
import org.apache.maven.model.Dependency; | ||
import org.apache.maven.plugin.AbstractMojo; | ||
import org.apache.maven.plugin.MojoFailureException; | ||
import org.apache.maven.plugin.logging.Log; | ||
import org.apache.maven.plugin.logging.SystemStreamLog; | ||
import org.apache.maven.plugins.annotations.LifecyclePhase; | ||
import org.apache.maven.plugins.annotations.Mojo; | ||
import org.apache.maven.plugins.annotations.Parameter; | ||
import org.apache.maven.plugins.annotations.ResolutionScope; | ||
import org.apache.maven.project.MavenProject; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.stream.Collectors; | ||
|
||
@Mojo(name = "unmaintained", | ||
defaultPhase = LifecyclePhase.TEST_COMPILE, | ||
requiresOnline = true, | ||
requiresDependencyResolution = ResolutionScope.TEST) | ||
public class UnmaintainedMojo extends AbstractMojo { | ||
|
||
private final PomClientInterface client; | ||
private final GithubGuesser githubGuesser; | ||
private final GithubCollector githubCollector; | ||
private final MaintenanceEvaluator maintenanceEvaluator; | ||
|
||
@Parameter(readonly = true, required = true, defaultValue = "${project}") | ||
private MavenProject project; | ||
|
||
/** | ||
* Required for initialization by Maven | ||
*/ | ||
public UnmaintainedMojo() { | ||
this(new SystemStreamLog()); | ||
} | ||
|
||
public UnmaintainedMojo(Log log) { | ||
this(new PomClient(log), new GithubGuesser(), new GithubCollector(log), new MaintenanceEvaluator()); | ||
} | ||
|
||
public UnmaintainedMojo( | ||
final PomClientInterface client, | ||
final GithubGuesser githubGuesser, | ||
final GithubCollector githubCollector, | ||
final MaintenanceEvaluator maintenanceEvaluator) { | ||
this.client = client; | ||
this.githubGuesser = githubGuesser; | ||
this.githubCollector = githubCollector; | ||
this.maintenanceEvaluator = maintenanceEvaluator; | ||
} | ||
|
||
@Override | ||
public void execute() throws MojoFailureException { | ||
final List<Dependency> dependencies = project.getDependencies(); | ||
|
||
if (dependencies.isEmpty()) { | ||
// When building a POM without any dependencies there will be nothing to query. | ||
return; | ||
} | ||
|
||
final Map<Dependency, PomResponse> pomResponses = dependencies.stream() | ||
.map(dependency -> { | ||
try { | ||
PomResponse pomResponse = client.getPom(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()); | ||
|
||
return new DependencyPomResponsePair(dependency, pomResponse); | ||
} catch (Exception e) { | ||
getLog().error("Failed to fetch POM for %s:%s:%s".formatted(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()), e); | ||
return new DependencyPomResponsePair(dependency, PomResponse.empty()); | ||
} | ||
}) | ||
.collect(Collectors.toMap(DependencyPomResponsePair::dependency, DependencyPomResponsePair::pomResponse)); | ||
|
||
for (Dependency dependency : pomResponses.keySet()) { | ||
final PomResponse pomResponse = pomResponses.get(dependency); | ||
final String projectUrl = pomResponse.url(); | ||
final String projectScmUrl = pomResponse.scmUrl(); | ||
|
||
// First try to get the Github owner and repo from the url otherwise try to get it from the SCM url | ||
var guess = projectUrl != null ? githubGuesser.guess(projectUrl) : null; | ||
if (guess == null && projectScmUrl != null) { | ||
guess = githubGuesser.guess(projectScmUrl); | ||
} | ||
|
||
if (guess == null) { | ||
getLog().warn("Could not guess Github owner and repo for %s".formatted(dependency.getManagementKey())); | ||
continue; | ||
} | ||
|
||
Collected collected; | ||
try { | ||
collected = githubCollector.collect(guess.owner(), guess.repo()); | ||
} catch (ExecutionException | InterruptedException e) { | ||
throw new MojoFailureException("Failed to collect Github data for %s".formatted(dependency.getManagementKey()), e); | ||
} | ||
|
||
double score = maintenanceEvaluator.evaluateCommitsFrequency(collected); | ||
getLog().info("Maintenance score for %s: %f".formatted(dependency.getManagementKey(), score)); | ||
} | ||
} | ||
|
||
private record DependencyPomResponsePair(Dependency dependency, PomResponse pomResponse) { | ||
} | ||
} |
Oops, something went wrong.