Skip to content

Commit 18e6f5e

Browse files
Fleex255JoeHegarty
authored andcommitted
Create Gradle plugin for build-time instrumentation (#46)
* Create module for Gradle plugin * Implement instrumentation Gradle plugin * Detect CompletionStage subclasses defined in the project * Remove unneeded dependencies
1 parent d8790bd commit 18e6f5e

File tree

4 files changed

+324
-0
lines changed

4 files changed

+324
-0
lines changed

gradle-plugin/pom.xml

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<!--
2+
Copyright (C) 2020 Electronic Arts Inc. All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions
6+
are met:
7+
8+
1. Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
2. Redistributions in binary form must reproduce the above copyright
11+
notice, this list of conditions and the following disclaimer in the
12+
documentation and/or other materials provided with the distribution.
13+
3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14+
its contributors may be used to endorse or promote products derived
15+
from this software without specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20+
DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
-->
28+
29+
<project>
30+
<modelVersion>4.0.0</modelVersion>
31+
<parent>
32+
<groupId>com.ea.async</groupId>
33+
<artifactId>ea-async-parent</artifactId>
34+
<version>1.2.4-SNAPSHOT</version>
35+
<relativePath>..</relativePath>
36+
</parent>
37+
<artifactId>ea-async-gradle-plugin</artifactId>
38+
<packaging>jar</packaging>
39+
<name>EA Async-Await Gradle Plugin</name>
40+
<description>Pre-instruments class files to work with the async-await paradigm</description>
41+
42+
<build>
43+
<plugins>
44+
<plugin>
45+
<groupId>org.apache.maven.plugins</groupId>
46+
<artifactId>maven-compiler-plugin</artifactId>
47+
</plugin>
48+
<plugin>
49+
<artifactId>maven-clean-plugin</artifactId>
50+
<version>3.0.0</version>
51+
<configuration>
52+
<failOnError>false</failOnError>
53+
</configuration>
54+
</plugin>
55+
<plugin>
56+
<artifactId>maven-assembly-plugin</artifactId>
57+
<configuration>
58+
<descriptorRefs>
59+
<descriptorRef>jar-with-dependencies</descriptorRef>
60+
</descriptorRefs>
61+
</configuration>
62+
</plugin>
63+
</plugins>
64+
</build>
65+
<repositories>
66+
<repository>
67+
<id>gradle</id>
68+
<name>Gradle Releases Repository</name>
69+
<url>https://repo.gradle.org/gradle/libs-releases-local/</url>
70+
</repository>
71+
</repositories>
72+
<dependencies>
73+
<dependency>
74+
<groupId>com.ea.async</groupId>
75+
<artifactId>ea-async</artifactId>
76+
<version>${project.version}</version>
77+
</dependency>
78+
79+
<dependency>
80+
<groupId>org.apache.maven</groupId>
81+
<artifactId>maven-artifact</artifactId>
82+
<version>3.5.3</version>
83+
</dependency>
84+
85+
<dependency>
86+
<groupId>org.apache.maven</groupId>
87+
<artifactId>maven-core</artifactId>
88+
<version>3.5.3</version>
89+
</dependency>
90+
91+
<!-- Gradle dependencies -->
92+
93+
<dependency>
94+
<groupId>org.codehaus.groovy</groupId>
95+
<artifactId>groovy</artifactId>
96+
<version>2.5.8</version>
97+
<scope>provided</scope>
98+
</dependency>
99+
100+
<dependency>
101+
<groupId>org.gradle</groupId>
102+
<artifactId>gradle-core</artifactId>
103+
<version>5.6.4</version>
104+
<scope>provided</scope>
105+
</dependency>
106+
107+
<dependency>
108+
<groupId>org.gradle</groupId>
109+
<artifactId>gradle-core-api</artifactId>
110+
<version>5.6.4</version>
111+
<scope>provided</scope>
112+
</dependency>
113+
114+
<dependency>
115+
<groupId>org.gradle</groupId>
116+
<artifactId>gradle-base-services</artifactId>
117+
<version>5.6.4</version>
118+
<scope>provided</scope>
119+
</dependency>
120+
121+
<dependency>
122+
<groupId>org.gradle</groupId>
123+
<artifactId>gradle-model-core</artifactId>
124+
<version>5.6.4</version>
125+
<scope>provided</scope>
126+
</dependency>
127+
128+
<dependency>
129+
<groupId>org.gradle</groupId>
130+
<artifactId>gradle-language-jvm</artifactId>
131+
<version>5.6.4</version>
132+
<scope>provided</scope>
133+
</dependency>
134+
135+
<dependency>
136+
<groupId>org.gradle</groupId>
137+
<artifactId>gradle-language-java</artifactId>
138+
<version>5.6.4</version>
139+
<scope>provided</scope>
140+
</dependency>
141+
142+
<dependency>
143+
<groupId>org.gradle</groupId>
144+
<artifactId>gradle-platform-jvm</artifactId>
145+
<version>5.6.4</version>
146+
<scope>provided</scope>
147+
</dependency>
148+
149+
</dependencies>
150+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
Copyright (C) 2020 Electronic Arts Inc. All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions
6+
are met:
7+
8+
1. Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
2. Redistributions in binary form must reproduce the above copyright
11+
notice, this list of conditions and the following disclaimer in the
12+
documentation and/or other materials provided with the distribution.
13+
3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14+
its contributors may be used to endorse or promote products derived
15+
from this software without specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20+
DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
*/
28+
29+
package com.ea.async.gradle.plugin;
30+
31+
import com.ea.async.instrumentation.Transformer;
32+
33+
import org.gradle.api.Plugin;
34+
import org.gradle.api.Project;
35+
import org.gradle.api.tasks.compile.JavaCompile;
36+
37+
import java.io.File;
38+
import java.io.FileInputStream;
39+
import java.io.IOException;
40+
import java.net.MalformedURLException;
41+
import java.net.URL;
42+
import java.net.URLClassLoader;
43+
import java.nio.file.Files;
44+
import java.nio.file.Paths;
45+
import java.util.ArrayList;
46+
import java.util.List;
47+
import java.util.Objects;
48+
49+
/**
50+
* A plugin that allows easily integrating EA Async instrumentation into the Gradle build process.
51+
*/
52+
public class AsyncPlugin implements Plugin<Project>
53+
{
54+
55+
/**
56+
* Applies the plugin to the project. Prepares Java compilation tasks to be followed up on by instrumentation.
57+
* @param project the Gradle project to which the plugin was applied
58+
*/
59+
@Override
60+
public void apply(final Project project)
61+
{
62+
project.getTasks().withType(JavaCompile.class, task ->
63+
{
64+
// This will be called for every JavaCompile task, whether already existing or added dynamically later
65+
task.doLast("EA Async instrumentation", t -> instrumentCompileResults(task));
66+
});
67+
}
68+
69+
/**
70+
* Rewrites compiled classes output by a Java compilation task.
71+
* Called for each compilation task after it finishes.
72+
* @param task the Java compilation task whose output to instrument
73+
*/
74+
private void instrumentCompileResults(final JavaCompile task)
75+
{
76+
// Set up the Transformer to explode on failure
77+
final Transformer asyncTransformer = new Transformer();
78+
asyncTransformer.setErrorListener(err ->
79+
{
80+
throw new RuntimeException("Failed to instrument the output of " + task.toString() + ": " + err);
81+
});
82+
// Create a classloader that can see the same CompletionStage subclasses as the compiler
83+
final List<URL> classpathUrls = new ArrayList<>();
84+
try
85+
{
86+
for (File f : task.getClasspath().plus(task.getOutputs().getFiles()).getFiles())
87+
{
88+
classpathUrls.add(f.toURI().toURL());
89+
}
90+
}
91+
catch (MalformedURLException e)
92+
{
93+
throw new RuntimeException("Bad URL when preparing instrumentation of " + task.toString(), e);
94+
}
95+
final ClassLoader classLoader = new URLClassLoader(classpathUrls.toArray(new URL[0]));
96+
// Rewrite the class files in the output directory
97+
instrumentFile(task.getDestinationDir(), task, asyncTransformer, classLoader);
98+
}
99+
100+
/**
101+
* Recursively instruments the specified file system entry and its descendants.
102+
* @param fsEntry a File in the output directory of a task
103+
* @param task the JavaCompile task whose output is being instrumented
104+
* @param asyncTransformer the transformer used to instrument the classes
105+
* @param classLoader the classloader to supply to the transformer
106+
*/
107+
private void instrumentFile(final File fsEntry, final JavaCompile task,
108+
final Transformer asyncTransformer, final ClassLoader classLoader)
109+
{
110+
if (fsEntry.isDirectory())
111+
{
112+
// Recurse into subdirectories and files
113+
for (File subentry : Objects.requireNonNull(fsEntry.listFiles())) {
114+
instrumentFile(subentry, task, asyncTransformer, classLoader);
115+
}
116+
}
117+
else if (fsEntry.isFile() && fsEntry.getName().endsWith(".class"))
118+
{
119+
// Instrument a class
120+
byte[] instrumentedClass;
121+
try (FileInputStream fis = new FileInputStream(fsEntry))
122+
{
123+
instrumentedClass = asyncTransformer.instrument(classLoader, fis);
124+
}
125+
catch (IOException e)
126+
{
127+
throw new RuntimeException("Failed to instrument '" + fsEntry.getPath() + "' from " + task.toString(), e);
128+
}
129+
if (instrumentedClass != null)
130+
{
131+
// Transformer#instrument returns null if no changes were needed
132+
try
133+
{
134+
Files.write(Paths.get(fsEntry.getAbsolutePath()), instrumentedClass);
135+
}
136+
catch (IOException e)
137+
{
138+
throw new RuntimeException("Failed to write '" + fsEntry.getPath() + "'", e);
139+
}
140+
}
141+
}
142+
}
143+
144+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#
2+
# Copyright (C) 2020 Electronic Arts Inc. All rights reserved.
3+
#
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted provided that the following conditions
6+
# are met:
7+
#
8+
# 1. Redistributions of source code must retain the above copyright
9+
# notice, this list of conditions and the following disclaimer.
10+
# 2. Redistributions in binary form must reproduce the above copyright
11+
# notice, this list of conditions and the following disclaimer in the
12+
# documentation and/or other materials provided with the distribution.
13+
# 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14+
# its contributors may be used to endorse or promote products derived
15+
# from this software without specific prior written permission.
16+
#
17+
# THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18+
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20+
# DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26+
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
#
28+
29+
implementation-class=com.ea.async.gradle.plugin.AsyncPlugin

pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4848
<module>async</module>
4949
<module>maven-plugin</module>
5050
<module>maven-plugin/src/test/project-to-test</module>
51+
<module>gradle-plugin</module>
5152
</modules>
5253

5354
<dependencyManagement>

0 commit comments

Comments
 (0)