-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17 from mk868/maven-plugin
Add cabe-maven-plugin
- Loading branch information
Showing
8 changed files
with
388 additions
and
3 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,107 @@ | ||
import groovy.util.Node | ||
import groovy.util.NodeList | ||
|
||
plugins { | ||
id("java-library") | ||
id("maven-publish") | ||
id("com.github.ben-manes.versions") version "0.50.0" | ||
id("de.benediktritter.maven-plugin-development") version "0.4.3" | ||
} | ||
|
||
group = "com.dua3.cabe" | ||
version = project.findProperty("plugin_version") as String? ?: project.version | ||
description = "A plugin that adds assertions for annotated method parameters at compile time." | ||
|
||
repositories { | ||
mavenLocal() | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
var processor_version = rootProject.extra["processor_version"] as String | ||
implementation("com.dua3.cabe:cabe-processor-all:${processor_version}") | ||
|
||
compileOnlyApi("org.apache.maven:maven-plugin-api:3.9.9") | ||
compileOnlyApi("org.apache.maven.plugin-tools:maven-plugin-annotations:3.15.1") | ||
compileOnlyApi("org.apache.maven:maven-core:3.9.9") | ||
} | ||
|
||
java { | ||
sourceCompatibility = JavaVersion.VERSION_17 | ||
targetCompatibility = JavaVersion.VERSION_17 | ||
} | ||
|
||
///////////////////////////////////////////////////////////////////////////// | ||
object Meta { | ||
const val SCM = "https://github.com/xzel23/cabe.git" | ||
const val REPO = "public" | ||
const val LICENSE_NAME = "Apache License 2.0" | ||
const val LICENSE_URL = "https://www.apache.org/licenses/LICENSE-2.0" | ||
const val DEVELOPER_ID = "axh" | ||
const val DEVELOPER_NAME = "Axel Howind" | ||
const val DEVELOPER_EMAIL = "axh@dua3.com" | ||
const val ORGANIZATION_NAME = "dua3" | ||
const val ORGANIZATION_URL = "https://www.dua3.com" | ||
} | ||
///////////////////////////////////////////////////////////////////////////// | ||
|
||
publishing { | ||
publications { | ||
create<MavenPublication>("maven") { | ||
groupId = project.group as String? | ||
artifactId = project.name | ||
version = project.version.toString() | ||
|
||
from(components["java"]) | ||
|
||
pom { | ||
description.set(project.description) | ||
packaging = "maven-plugin" | ||
|
||
withXml { | ||
val root = asNode() | ||
val dependenciesNode = (root.get("dependencies") as NodeList)[0] as Node | ||
|
||
// set maven dependencies with provided scope | ||
dependenciesNode.children().forEach { dep -> | ||
val dependencyNode = dep as Node | ||
val artifactId = | ||
((dependencyNode.get("artifactId") as NodeList)[0] as Node).text() | ||
if (artifactId == "maven-plugin-api" | ||
|| artifactId == "maven-plugin-annotations" | ||
|| artifactId == "maven-core" | ||
) { | ||
val scopeNodes = (dependencyNode.get("scope") as NodeList) | ||
if (scopeNodes.isEmpty()) { | ||
dependencyNode.appendNode("scope", "provided") | ||
} else { | ||
(scopeNodes[0] as Node).setValue("provided") | ||
} | ||
} | ||
} | ||
} | ||
|
||
licenses { | ||
license { | ||
name.set(Meta.LICENSE_NAME) | ||
url.set(Meta.LICENSE_URL) | ||
} | ||
} | ||
developers { | ||
developer { | ||
id.set(Meta.DEVELOPER_ID) | ||
name.set(Meta.DEVELOPER_NAME) | ||
email.set(Meta.DEVELOPER_EMAIL) | ||
organization.set(Meta.ORGANIZATION_NAME) | ||
organizationUrl.set(Meta.ORGANIZATION_URL) | ||
} | ||
} | ||
|
||
scm { | ||
url.set(Meta.SCM) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
175 changes: 175 additions & 0 deletions
175
cabe-maven-plugin/src/main/java/com/dua3/cabe/maven/CabeMojo.java
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,175 @@ | ||
package com.dua3.cabe.maven; | ||
|
||
import com.dua3.cabe.processor.ClassPatcher; | ||
import java.io.BufferedReader; | ||
import java.io.File; | ||
import java.io.Reader; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.function.Consumer; | ||
import java.util.stream.Collectors; | ||
import org.apache.maven.artifact.Artifact; | ||
import org.apache.maven.plugin.AbstractMojo; | ||
import org.apache.maven.plugin.MojoExecutionException; | ||
import org.apache.maven.plugin.MojoFailureException; | ||
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; | ||
|
||
/** | ||
* Cabe Maven goal definition | ||
*/ | ||
@Mojo(name = "cabe", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) | ||
public class CabeMojo extends AbstractMojo { | ||
|
||
@Parameter(defaultValue = "${project}", required = true, readonly = true) | ||
private MavenProject project; | ||
/** | ||
* The verbosity level. | ||
* <ul> | ||
* <li> <b>0</b> - show warnings and errors only (default) | ||
* <li> <b>1</b> - show basic processing information | ||
* <li> <b>2</b> - show detailed information | ||
* <li> <b>3</b> - show all information | ||
* </ul> | ||
*/ | ||
@Parameter(property = "cabe.verbosity") | ||
private Integer verbosity; | ||
/** | ||
* The input directory for the Cabe processing | ||
*/ | ||
@Parameter(property = "cabe.inputDirectory", defaultValue = "${project.build.outputDirectory}") | ||
private Path inputDirectory; | ||
/** | ||
* The output directory for the Cabe processing | ||
*/ | ||
@Parameter(property = "cabe.outputDirectory", defaultValue = "${project.build.outputDirectory}") | ||
public Path outputDirectory; | ||
/** | ||
* The configuration string for the Cabe | ||
* <ul> | ||
* <li> <b>STANDARD</b> - use standard assertions for private API methods, throw NullPointerException for public API methods | ||
* <li> <b>DEVELOPMENT</b> - failed checks will always throw an AssertionError, also checks return values | ||
* <li> <b>NO_CHECKS</b> - do not add any null checks (class files are copied unchanged) | ||
* <li> <configstr> - custom configuration string, please check documentation for details | ||
* </ul> | ||
*/ | ||
@Parameter(property = "cabe.configurationString", defaultValue = "STANDARD") | ||
public String configurationString; | ||
|
||
/** | ||
* Default constructor | ||
*/ | ||
public CabeMojo() { | ||
} | ||
|
||
@Override | ||
public void execute() throws MojoExecutionException, MojoFailureException { | ||
try { | ||
String jarLocation = Paths.get( | ||
ClassPatcher.class.getProtectionDomain().getCodeSource().getLocation().toURI()) | ||
.toString(); | ||
String systemClassPath = System.getProperty("java.class.path"); | ||
|
||
String classpath = project.getArtifacts().stream() | ||
.map(Artifact::getFile) | ||
.map(File::toString) | ||
.distinct() | ||
.collect(Collectors.joining(File.pathSeparator)); | ||
|
||
String javaExec = Path.of(System.getProperty("java.home"), "bin", "java").toString(); | ||
getLog().info("Java executable: %s".formatted(javaExec)); | ||
|
||
int v = Objects.requireNonNullElse(verbosity, 0); | ||
String[] args = { | ||
javaExec, | ||
"-classpath", systemClassPath, | ||
"-jar", jarLocation, | ||
"-i", inputDirectory.toString(), | ||
"-o", outputDirectory.toString(), | ||
"-c", configurationString, | ||
"-cp", classpath, | ||
"-v", Integer.toString(v) | ||
}; | ||
|
||
if (v > 0) { | ||
getLog().debug("Instrumenting class files: %s".formatted(String.join(" ", args))); | ||
} | ||
|
||
getLog().info(String.join(" ", args)); | ||
ProcessBuilder pb = new ProcessBuilder(args); | ||
|
||
Process process = pb.start(); | ||
|
||
try (CopyOutput copyStdErr = new CopyOutput(process.errorReader(), System.err::println); | ||
CopyOutput ignored = new CopyOutput(process.inputReader(), | ||
v > 1 ? System.out::println : s -> { | ||
})) { | ||
int exitCode = process.waitFor(); | ||
if (exitCode != 0) { | ||
throw new MojoFailureException("Instrumenting class files failed\n\n" + copyStdErr); | ||
} | ||
} | ||
} catch (InterruptedException e) { | ||
Thread.currentThread().interrupt(); | ||
} catch (Exception e) { | ||
throw new MojoFailureException( | ||
"An error occurred while instrumenting classes: " + e.getMessage(), e); | ||
} | ||
} | ||
|
||
/** | ||
* This class is responsible for copying the output of a Reader to a specified Consumer. The first | ||
* 10 lines are stored. | ||
*/ | ||
private class CopyOutput implements AutoCloseable { | ||
|
||
public static final int MAX_LINES = 10; | ||
Thread thread; | ||
List<String> firstLines = new ArrayList<>(); | ||
|
||
CopyOutput(Reader reader, Consumer<String> printer) { | ||
thread = new Thread(() -> { | ||
try (BufferedReader r = new BufferedReader(reader)) { | ||
String line; | ||
while ((line = r.readLine()) != null) { | ||
printer.accept(line); | ||
if (firstLines.size() < MAX_LINES) { | ||
firstLines.add(line); | ||
} else if (firstLines.size() == MAX_LINES) { | ||
firstLines.add("..."); | ||
} | ||
} | ||
} catch (Exception e) { | ||
getLog().warn("exception reading ClassPatcher error output"); | ||
} | ||
}); | ||
thread.start(); | ||
} | ||
|
||
@Override | ||
public void close() { | ||
try { | ||
thread.join(5000); // Wait 5000ms for the thread to die. | ||
} catch (InterruptedException e) { | ||
Thread.currentThread().interrupt(); | ||
} | ||
|
||
if (thread.isAlive()) { | ||
getLog().warn("output thread did not stop"); | ||
thread.interrupt(); | ||
} | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return String.join("\n", firstLines); | ||
} | ||
} | ||
|
||
} |
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,13 @@ | ||
# Examples: hello-maven | ||
|
||
To build the project, use: | ||
|
||
``` | ||
mvn package | ||
``` | ||
|
||
To run the compiled example with instrumentation applied, use: | ||
|
||
``` | ||
java -jar target/hello-maven.jar | ||
``` |
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,76 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<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/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>com.example</groupId> | ||
<artifactId>hello-maven</artifactId> | ||
<version>1.0.0-SNAPSHOT</version> | ||
|
||
<properties> | ||
<maven.compiler.release>17</maven.compiler.release> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.jspecify</groupId> | ||
<artifactId>jspecify</artifactId> | ||
<version>1.0.0</version> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<finalName>hello-maven</finalName> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>3.13.0</version> | ||
<executions> | ||
<execution> | ||
<id>default-compile</id> | ||
<configuration> | ||
<compilerArguments> | ||
<d>${project.build.directory}/unprocessed-classes</d> | ||
</compilerArguments> | ||
</configuration> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<groupId>com.dua3.cabe</groupId> | ||
<artifactId>cabe-maven-plugin</artifactId> | ||
<version>3.0.1</version> | ||
<configuration> | ||
<inputDirectory>${project.build.directory}/unprocessed-classes</inputDirectory> | ||
<verbosity>1</verbosity> | ||
<configurationString>STANDARD</configurationString> | ||
</configuration> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>cabe</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-jar-plugin</artifactId> | ||
<version>3.4.2</version> | ||
<configuration> | ||
<archive> | ||
<manifest> | ||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries> | ||
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries> | ||
<addClasspath>true</addClasspath> | ||
<mainClass>hello.Hello</mainClass> | ||
</manifest> | ||
</archive> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
Oops, something went wrong.