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

Issue #13: Implement signatures artifacts, downloaded from Maven repos #79

Merged
merged 10 commits into from
Sep 29, 2015
1 change: 1 addition & 0 deletions ivy.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<!-- we compile against the minimum ANT / Maven / Gradle versions: -->
<dependency org="org.apache.ant" name="ant" rev="1.7.0" conf="build"/>
<dependency org="org.apache.maven" name="maven-plugin-api" rev="2.0" conf="build"/>
<dependency org="org.apache.maven" name="maven-artifact" rev="2.0" conf="build"/>
<dependency org="org.apache.maven.plugin-tools" name="maven-plugin-annotations" rev="3.2" conf="build"/>
<dependency org="org.gradle" name="gradle-core" rev="2.3" conf="build"/>
<dependency org="org.gradle" name="gradle-base-services" rev="2.3" conf="build"/>
Expand Down
135 changes: 114 additions & 21 deletions src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@

import static de.thetaphi.forbiddenapis.Checker.Option.*;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.util.DirectoryScanner;

Expand All @@ -33,6 +39,8 @@
import java.io.File;
import java.io.IOException;
import java.lang.annotation.RetentionPolicy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLClassLoader;
import java.net.MalformedURLException;
import java.net.URL;
Expand All @@ -41,6 +49,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/**
* Base class for forbiddenapis Mojos.
Expand All @@ -56,6 +65,36 @@ public abstract class AbstractCheckMojo extends AbstractMojo {
@Parameter(required = false)
private File[] signaturesFiles;

/**
* Lists all Maven artifacts, which contain signatures and comments for forbidden API calls.
* The artifact needs to be specified like a Maven dependency. Resolution is not transitive.
* You can refer to plain text Maven artifacts ({@code type="txt"}, e.g., with a separate {@code classifier}):
* <pre>
* &lt;signaturesArtifact&gt;
* &lt;groupId&gt;org.apache.foobar&lt;/groupId&gt;
* &lt;artifactId&gt;example&lt;/artifactId&gt;
* &lt;version&gt;1.0&lt;/version&gt;
* &lt;classifier&gt;signatures&lt;/classifier&gt;
* &lt;type&gt;txt&lt;/type&gt;
* &lt;/signaturesArtifact&gt;
* </pre>
* Alternatively, refer to signatures files inside JAR artifacts. In that case, the additional
* parameter {@code path} has to be given:
* <pre>
* &lt;signaturesArtifact&gt;
* &lt;groupId&gt;org.apache.foobar&lt;/groupId&gt;
* &lt;artifactId&gt;example&lt;/artifactId&gt;
* &lt;version&gt;1.0&lt;/version&gt;
* &lt;type&gt;jar&lt;/type&gt;
* &lt;path&gt;path/inside/jar/file/signatures.txt&lt;/path&gt;
* &lt;/signaturesArtifact&gt;
* </pre>
* <p>The signatures are resolved against the compile classpath.
* @since 2.0
*/
@Parameter(required = false)
private SignaturesArtifact[] signaturesArtifacts;

/**
* Gives a multiline list of signatures, inline in the pom.xml. Use an XML CDATA section to do that!
* The signatures are resolved against the compile classpath.
Expand Down Expand Up @@ -163,6 +202,18 @@ public abstract class AbstractCheckMojo extends AbstractMojo {
/** The project packaging (pom, jar, etc.). */
@Parameter(defaultValue = "${project.packaging}", readonly = true, required = true)
private String packaging;

@Component
private ArtifactFactory artifactFactory;

@Component
private ArtifactResolver artifactResolver;

@Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true)
private List<ArtifactRepository> remoteRepositories;

@Parameter(defaultValue = "${localRepository}", readonly = true, required = true)
private ArtifactRepository localRepository;

/** provided by the concrete Mojos for compile and test classes processing */
protected abstract List<String> getClassPathElements();
Expand All @@ -174,10 +225,46 @@ public abstract class AbstractCheckMojo extends AbstractMojo {
protected String getTargetVersion() {
return targetVersion;
}

private File resolveSignaturesArtifact(SignaturesArtifact signaturesArtifact) throws ArtifactResolutionException, ArtifactNotFoundException {
final Artifact artifact = signaturesArtifact.createArtifact(artifactFactory);
artifactResolver.resolve(artifact, this.remoteRepositories, this.localRepository);
return artifact.getFile();
}

private String encodeUrlPath(String path) {
try {
// hack to encode the URL path by misusing URI class:
return new URI(null, path, null).toASCIIString();
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage());
}
}

private URL createJarUrl(File f, String jarPath) throws MalformedURLException {
final URL fileUrl = f.toURI().toURL();
final URL jarBaseUrl = new URL("jar", null, fileUrl.toExternalForm() + "!/");
return new URL(jarBaseUrl, encodeUrlPath(jarPath));
}

@Override
public void execute() throws MojoExecutionException {
final Log log = getLog();
final Logger log = new Logger() {
@Override
public void error(String msg) {
getLog().error(msg);
}

@Override
public void warn(String msg) {
getLog().warn(msg);
}

@Override
public void info(String msg) {
getLog().info(msg);
}
};

if (skip) {
log.info("Skipping forbidden-apis checks.");
Expand All @@ -202,7 +289,6 @@ public void execute() throws MojoExecutionException {
urls[i++] = new File(cpElement).toURI().toURL();
}
assert i == urls.length;
if (log.isDebugEnabled()) log.debug("Compile Classpath: " + Arrays.toString(urls));
} catch (MalformedURLException e) {
throw new MojoExecutionException("Failed to build classpath.", e);
}
Expand All @@ -218,22 +304,7 @@ public void execute() throws MojoExecutionException {
if (failOnMissingClasses) options.add(FAIL_ON_MISSING_CLASSES);
if (failOnViolation) options.add(FAIL_ON_VIOLATION);
if (failOnUnresolvableSignatures) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
final Checker checker = new Checker(new Logger() {
@Override
public void error(String msg) {
log.error(msg);
}

@Override
public void warn(String msg) {
log.warn(msg);
}

@Override
public void info(String msg) {
log.info(msg);
}
}, loader, options);
final Checker checker = new Checker(log, loader, options);

if (!checker.isSupportedJDK) {
final String msg = String.format(Locale.ENGLISH,
Expand Down Expand Up @@ -287,9 +358,27 @@ public void info(String msg) {
checker.parseBundledSignatures(bs, targetVersion);
}
}
if (signaturesFiles != null) for (final File f : new LinkedHashSet<File>(Arrays.asList(signaturesFiles))) {
final Set<File> sigFiles = new LinkedHashSet<File>();
final Set<URL> sigUrls = new LinkedHashSet<URL>();
if (signaturesFiles != null) {
sigFiles.addAll(Arrays.asList(signaturesFiles));
}
if (signaturesArtifacts != null) {
for (final SignaturesArtifact artifact : signaturesArtifacts) {
final File f = resolveSignaturesArtifact(artifact);
if (artifact.path != null) {
sigUrls.add(createJarUrl(f, artifact.path));
} else {
sigFiles.add(f);
}
}
}
for (final File f : sigFiles) {
checker.parseSignaturesFile(f);
}
for (final URL u : sigUrls) {
checker.parseSignaturesFile(u);
}
final String sig = (signatures != null) ? signatures.trim() : null;
if (sig != null && sig.length() != 0) {
checker.parseSignaturesString(sig);
Expand All @@ -298,11 +387,15 @@ public void info(String msg) {
throw new MojoExecutionException("IO problem while reading files with API signatures.", ioe);
} catch (ParseException pe) {
throw new MojoExecutionException("Parsing signatures failed: " + pe.getMessage(), pe);
} catch (ArtifactResolutionException e) {
throw new MojoExecutionException("Problem while resolving Maven artifact.", e);
} catch (ArtifactNotFoundException e) {
throw new MojoExecutionException("Maven artifact does not exist.", e);
}

if (checker.hasNoSignatures()) {
if (failOnUnresolvableSignatures) {
throw new MojoExecutionException("No API signatures found; use parameters 'signatures', 'bundledSignatures', and/or 'signaturesFiles' to define those!");
throw new MojoExecutionException("No API signatures found; use parameters 'signatures', 'bundledSignatures', 'signaturesFiles', and/or 'signaturesArtifacts' to define those!");
} else {
log.info("Skipping execution because no API signatures are available.");
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package de.thetaphi.forbiddenapis.maven;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;

/*
* (C) Copyright Uwe Schindler (Generics Policeman) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Defines coordinates of a Maven artifact that provides signatures files.
* It may be a plain text file ({@link #path} is not given) or alternatively
* refer to a text file inside a JAR file. For that, define the resource path
* using {@link #path}.
* @since 2.0
*/
public final class SignaturesArtifact {

/** Artifact's group ID (required) */
public String groupId;

/** Artifact's ID (required) */
public String artifactId;

/** Version (required) */
public String version;

/** Classifier (optional) */
public String classifier;

/**
* Type (required; {@code txt} or {@code jar}).
* If the artifact refers to a JAR file, the {@link #path} should be
* given, that identifies the signatures file inside the JAR.
* */
public String type;

/** Path to resource inside JAR artifacts. If given, the {@link #type} must be {@code "jar"} or {@code "zip"}. */
public String path;

/** Used by the mojo to fetch the artifact */
Artifact createArtifact(ArtifactFactory artifactFactory) {
if (groupId == null || artifactId == null || version == null || type == null) {
throw new NullPointerException("signaturesArtifact is missing some properties. Required are: groupId, artifactId, version, type");
}
return artifactFactory.createArtifactWithClassifier(groupId, artifactId, version, type, classifier);
}
}
18 changes: 18 additions & 0 deletions src/test/antunit/TestMavenMojo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,22 @@
<au:assertLogContains text="Reading bundled API signatures: jdk-unsafe"/>
</target>

<target name="testSigArtifacts-Maven2">
<artifact:mvn pom="pom-sigArtifacts.xml" mavenVersion="${maven.version}" failonerror="true" fork="${maven.fork}">
<syspropertyset refid="injected-properties"/>
<arg value="${groupId}:${artifactId}:${version}:check"/>
</artifact:mvn>
<au:assertLogContains text=" 0 error(s)."/>
<au:assertLogContains text="Reading API signatures: jar:file:"/>
</target>

<target name="testSigArtifacts-Maven3">
<artifact:mvn pom="pom-sigArtifacts.xml" mavenVersion="${antunit.maven3.version}" failonerror="true" fork="${maven.fork}">
<syspropertyset refid="injected-properties"/>
<arg value="${groupId}:${artifactId}:${version}:check"/>
</artifact:mvn>
<au:assertLogContains text=" 0 error(s)."/>
<au:assertLogContains text="Reading API signatures: jar:file:"/>
</target>

</project>
56 changes: 56 additions & 0 deletions src/test/antunit/pom-sigArtifacts.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* (C) Copyright Uwe Schindler (Generics Policeman) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.thetaphi</groupId>
<artifactId>forbiddenapi-test3</artifactId>
<version>0.0-SNAPSHOT</version>
<name>Dummy Project to test signatures from Maven artifacts</name>

<build>
<plugins>
<plugin>
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
<configuration>
<classesDirectory>.</classesDirectory>
<internalRuntimeForbidden>false</internalRuntimeForbidden>
<failOnMissingClasses>false</failOnMissingClasses>
<signaturesArtifacts>
<signaturesArtifact>
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
<type>jar</type>
<path>de/thetaphi/forbiddenapis/signatures/jdk-deprecated-${jdk.version}.txt</path>
</signaturesArtifact>
</signaturesArtifacts>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>