HHH-9161 - Improve build handling of generated sources

This commit is contained in:
Steve Ebersole 2014-05-08 11:40:51 -05:00
parent 568a16eefa
commit bb91e1d3b8
3 changed files with 54 additions and 258 deletions

View File

@ -177,5 +177,5 @@ task jaxb {
} }
runSourceGenerators.dependsOn jaxb sourceSets.main.sourceGeneratorsTask.dependsOn jaxb
runSourceGenerators.dependsOn generateGrammarSource sourceSets.main.sourceGeneratorsTask.dependsOn generateGrammarSource

View File

@ -1,31 +1,19 @@
import java.nio.charset.Charset
import java.util.concurrent.Callable import java.util.concurrent.Callable
import javax.tools.Diagnostic
import javax.tools.DiagnosticListener
import javax.tools.JavaCompiler
import javax.tools.JavaFileObject
import javax.tools.StandardJavaFileManager
import javax.tools.ToolProvider
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.internal.tasks.SimpleWorkResult
import org.gradle.api.internal.tasks.compile.CompilationFailedException
import org.gradle.api.internal.tasks.compile.Compiler
import org.gradle.api.internal.tasks.compile.DefaultJavaCompileSpec
import org.gradle.api.internal.tasks.compile.DefaultJavaCompilerFactory
import org.gradle.api.internal.tasks.compile.DelegatingJavaCompiler
import org.gradle.api.internal.tasks.compile.JavaCompileSpec
import org.gradle.api.internal.tasks.compile.JavaCompilerArgumentsBuilder
import org.gradle.api.internal.tasks.compile.JavaCompilerFactory
import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager
import org.gradle.api.internal.tasks.compile.jdk6.Jdk6JavaCompiler
import org.gradle.internal.jvm.Jvm
apply plugin: SourceGenerationPlugin apply plugin: SourceGenerationPlugin
/**
* A plugin for dealing with AnnotationProcessor-based source generation.
* <p/>
* Creates (or reuses) a grouping task named `generateSources` which is responsible
* for coordinating (via task deps) the running of all source-generation tasks.
* <p/>
* Additionally a grouping task is created for each SourceSet to hold the generation
* task for that SourceSet. This task is named following the `run{SourceSet.name}SourceGenerators`.
* This task is also injected into the SourceSet as `sourceGeneratorsTask` for scripts to access
*/
class SourceGenerationPlugin implements Plugin<Project> { class SourceGenerationPlugin implements Plugin<Project> {
public static final String GROUP = "sourceGeneration"; public static final String GROUP = "sourceGeneration";
public static final String GENERATE_SOURCES_TASK_NAME = "generateSources"; public static final String GENERATE_SOURCES_TASK_NAME = "generateSources";
@Override @Override
@ -68,7 +56,20 @@ class SourceGenerationPlugin implements Plugin<Project> {
generateSourcesTask.dependsOn( sourceGeneratorsTask ); generateSourcesTask.dependsOn( sourceGeneratorsTask );
javaCompileTask.dependsOn( sourceGeneratorsTask ); javaCompileTask.dependsOn( sourceGeneratorsTask );
final File aptDir = new File( new File( project.getBuildDir(), "generated-src/apt" ), sourceSet.name );
javaCompileTask.options.compilerArgs += [ "-s", aptDir.absolutePath ];
javaCompileTask.doFirst({
if ( !aptDir.exists() ) {
if ( !aptDir.mkdirs() ) {
project.logger.warn( "Unable to create APT dir : " + aptDir.absolutePath )
}
}
})
generateSourcesTask.dependsOn( javaCompileTask )
extProps.set( "sourceGeneratorsTask", sourceGeneratorsTask ); extProps.set( "sourceGeneratorsTask", sourceGeneratorsTask );
extProps.set( "aptDir", aptDir );
} }
} }
} }
@ -80,45 +81,54 @@ class SourceGenerationPluginConvention {
private final Project project; private final Project project;
// practicality says we only ever deal with 2 source-sets: // practicality says we only ever deal with 2 source-sets:
private AnnotationProcessorOnlyTask mainProcOnlyTask; private JavaCompile mainProcOnlyTask;
private AnnotationProcessorOnlyTask testProcOnlyTask; private JavaCompile testProcOnlyTask;
SourceGenerationPluginConvention(Project project) { SourceGenerationPluginConvention(Project project) {
this.project = project this.project = project
} }
private AnnotationProcessorOnlyTask getLocateProcessorOnlyTask(SourceSet sourceSet) { /**
* Exposed to the build scripts to be able to apply JPA Metamodel Generation support to the
* SourceSet it specifies.
*
* @param sourceSet The SourceSet to which JPA Metamodel Generation support should be applied.
*/
public void addMetaGenProcessor(SourceSet sourceSet) {
if ( sourceSet.name.equals( "main" ) ) { if ( sourceSet.name.equals( "main" ) ) {
if ( mainProcOnlyTask == null ) { if ( mainProcOnlyTask == null ) {
mainProcOnlyTask = generateProcessorOnlyTask( sourceSet ) mainProcOnlyTask = generateProcessorOnlyTask( sourceSet )
} }
return mainProcOnlyTask;
} }
else if ( sourceSet.name.equals( "test" ) ) { else if ( sourceSet.name.equals( "test" ) ) {
if ( testProcOnlyTask == null ) { if ( testProcOnlyTask == null ) {
testProcOnlyTask = generateProcessorOnlyTask( sourceSet ) testProcOnlyTask = generateProcessorOnlyTask( sourceSet )
} }
return testProcOnlyTask;
} }
else { else {
throw new IllegalArgumentException( "SourceSet (" + sourceSet.name + ") not valid for source generation" ) throw new IllegalArgumentException( "SourceSet (" + sourceSet.name + ") not valid for source generation" )
} }
} }
private AnnotationProcessorOnlyTask generateProcessorOnlyTask(SourceSet sourceSet) { private JavaCompile generateProcessorOnlyTask(SourceSet sourceSet) {
final File targetDir = sourceSet.aptDir;
// find the main javac task for this sourceSet (we will alter it a bit later on) // find the main javac task for this sourceSet (we will alter it a bit later on)
final JavaCompile javaCompileTask = (JavaCompile) project.getTasks().getByName( sourceSet.getCompileJavaTaskName() ); final JavaCompile javaCompileTask = (JavaCompile) project.getTasks().getByName( sourceSet.getCompileJavaTaskName() );
final ExtraPropertiesExtension extProps = ( (ExtensionAware) sourceSet ).getExtensions().getExtraProperties(); final ExtraPropertiesExtension extProps = ( (ExtensionAware) sourceSet ).getExtensions().getExtraProperties();
// Obtain the output dir reference: generated-src/apt/{sourceSet.name}
final File outputDir = new File(
new File( project.getBuildDir(), "generated-src/apt/" ),
sourceSet.getName()
);
final String aptTaskName = sourceSet.getTaskName( "run", "annotationProcessors" ); final String aptTaskName = sourceSet.getTaskName( "run", "annotationProcessors" );
final AnnotationProcessorOnlyTask aptTask = project.getTasks().create( aptTaskName, AnnotationProcessorOnlyTask.class ); // final AnnotationProcessorOnlyTask aptTask = project.getTasks().create( aptTaskName, AnnotationProcessorOnlyTask.class );
final JavaCompile aptTask = project.getTasks().create( aptTaskName, JavaCompile.class );
aptTask.options.compilerArgs += [
"-nowarn",
"-proc:only",
"-encoding", "UTF-8",
"-s", targetDir.getAbsolutePath(),
"-processor", METAGEN_PROCESSOR_NAME
]
aptTask.setGroup( SourceGenerationPlugin.GROUP ); aptTask.setGroup( SourceGenerationPlugin.GROUP );
aptTask.setDescription( aptTask.setDescription(
String.format( String.format(
@ -132,10 +142,8 @@ class SourceGenerationPluginConvention {
// even as we add to it. The problem is that later on here we will add the output directory of this task // even as we add to it. The problem is that later on here we will add the output directory of this task
// to this SourceDirectorySet; we need to make sure that we use the view of the SourceDirectorySet *before* that // to this SourceDirectorySet; we need to make sure that we use the view of the SourceDirectorySet *before* that
// happens as the source for this task. getSrcDirs() does that // happens as the source for this task. getSrcDirs() does that
aptTask.source( sourceSet.getAllJava().getSrcDirs() ) aptTask.source( sourceSet.getAllJava() )
aptTask.destinationDir = outputDir aptTask.destinationDir = targetDir
// again for JBoss Logging...
aptTask.classesDir = outputDir
aptTask.setSourceCompatibility( javaCompileTask.getSourceCompatibility() ); aptTask.setSourceCompatibility( javaCompileTask.getSourceCompatibility() );
aptTask.setTargetCompatibility( javaCompileTask.getTargetCompatibility() ); aptTask.setTargetCompatibility( javaCompileTask.getTargetCompatibility() );
@ -145,7 +153,7 @@ class SourceGenerationPluginConvention {
"classpath", "classpath",
new Callable<FileCollection>() { new Callable<FileCollection>() {
public FileCollection call() throws Exception { public FileCollection call() throws Exception {
return javaCompileTask.getClasspath() return javaCompileTask.getClasspath() + project.configurations[METAGEN_DEPENDENCY_CONFIG_NAME]
} }
} }
); );
@ -154,223 +162,10 @@ class SourceGenerationPluginConvention {
javaCompileTask.dependsOn( aptTask ); javaCompileTask.dependsOn( aptTask );
project.tasks.findByName( SourceGenerationPlugin.GENERATE_SOURCES_TASK_NAME ).dependsOn( aptTask ) project.tasks.findByName( SourceGenerationPlugin.GENERATE_SOURCES_TASK_NAME ).dependsOn( aptTask )
// create a FileTree representation of the APT output dir and add it to the JavaCompile task (so they get compiled)
// final ConfigurableFileTree outputDirFileTree = project.fileTree( outputDir );
// outputDirFileTree.builtBy( aptTask );
// javaCompileTask.getSource().plus( outputDirFileTree );
// Add the APT output dir to the source set // Add the APT output dir to the source set
sourceSet.getJava().srcDir( outputDir ); // - so that the generated sources get compiled during main javac
sourceSet.getJava().srcDir( targetDir );
return aptTask return aptTask
} }
public void addMetaGenProcessor(SourceSet sourceSet) {
AnnotationProcessorOnlyTask task = getLocateProcessorOnlyTask( sourceSet )
task.processors += METAGEN_PROCESSOR_NAME
task.classpath += project.configurations[METAGEN_DEPENDENCY_CONFIG_NAME]
}
} }
class AnnotationProcessorOnlyTask extends AbstractCompile {
@Input
def List<String> processors = new ArrayList<String>();
// todo : support for this? really only "used" for logging and its use is questionable
//private Map<String,String> processorSettings = new HashMap<String, String>();
def File dependencyCacheDir;
def File classesDir;
private Compiler<JavaCompileSpec> javaCompiler;
AnnotationProcessorOnlyTask() {
// Stolen from Gradle's Compile/JavaCompile
org.gradle.internal.Factory<org.gradle.api.AntBuilder> antBuilderFactory = getServices().getFactory( org.gradle.api.AntBuilder.class );
JavaCompilerFactory inProcessCompilerFactory = new ExpandedJavaCompilerFactory( getLogger() );
ProjectInternal projectInternal = (ProjectInternal) getProject();
CompilerDaemonManager compilerDaemonManager = getServices().get( CompilerDaemonManager.class );
JavaCompilerFactory defaultCompilerFactory = new DefaultJavaCompilerFactory(
projectInternal,
antBuilderFactory,
inProcessCompilerFactory,
compilerDaemonManager
);
// The Gradle IncrementalJavaCompiler cant be used here for various reasons
javaCompiler = new DelegatingJavaCompiler( defaultCompilerFactory );
}
@OutputDirectory
public File getDependencyCacheDir() {
return dependencyCacheDir;
}
public void setDependencyCacheDir(File dependencyCacheDir) {
this.dependencyCacheDir = dependencyCacheDir;
}
@Override
protected void compile() {
// see if the output dir exists
if ( !getDestinationDir().exists() ) {
// its does not - create it (javac will complain if its not there)
makeDirectory( getDestinationDir() );
}
else {
// it does - clean it
project.delete( getDestinationDir() )
makeDirectory( getDestinationDir() );
}
if ( !getClassesDir().exists() ) {
// create classes dir if not there (again, javac will complain if its not there)
makeDirectory( getClassesDir() );
}
CompileOptions compileOptions = new CompileOptions();
Collections.addAll(
compileOptions.getCompilerArgs(),
"-nowarn",
"-proc:only",
"-encoding", "UTF-8",
"-s", getDestinationDir().getAbsolutePath(),
"-processor", processors.join( "," )
);
DefaultJavaCompileSpec spec = new DefaultJavaCompileSpec();
spec.setSource( getSource() );
// jboss logging needs this :(
spec.setDestinationDir( getClassesDir() );
spec.setClasspath( getClasspath() );
spec.setDependencyCacheDir( dependencyCacheDir );
spec.setSourceCompatibility( getSourceCompatibility() );
spec.setTargetCompatibility( getTargetCompatibility() );
spec.setCompileOptions( compileOptions );
WorkResult result = javaCompiler.execute( spec );
setDidWork( result.getDidWork() );
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private static void makeDirectory(File directory) {
directory.mkdirs();
}
}
/**
* Implementation of the Gradle JavaCompilerFactory contract generating {@link ExpandedJdk6JavaCompiler} instances
*/
class ExpandedJavaCompilerFactory implements JavaCompilerFactory {
private final Logger logger;
public ExpandedJavaCompilerFactory(Logger logger) {
//To change body of created methods use File | Settings | File Templates.
this.logger = logger;
}
@Override
public Compiler<JavaCompileSpec> create(CompileOptions options) {
return new ExpandedJdk6JavaCompiler( logger );
}
}
/**
* Extension of Gradle's Jdk6JavaCompiler to add DiagnosticListener for diagnostic message mapping and APT support
*/
public class ExpandedJdk6JavaCompiler extends Jdk6JavaCompiler {
private final Logger logger;
public ExpandedJdk6JavaCompiler(Logger logger) {
this.logger = logger;
}
public WorkResult execute(JavaCompileSpec spec) {
logger.info( "Compiling with JDK Java compiler API." );
final DiagnosticListenerImpl diagnosticListener = new DiagnosticListenerImpl( logger );
final JavaCompiler.CompilationTask task = createCompileTask( spec, diagnosticListener );
boolean success = task.call();
if ( !success || diagnosticListener.sawError() ) {
throw new CompilationFailedException();
}
return new SimpleWorkResult( true );
}
private static JavaCompiler.CompilationTask createCompileTask(
JavaCompileSpec spec,
DiagnosticListenerImpl diagnosticListener) {
List<String> options = new JavaCompilerArgumentsBuilder(spec).build();
JavaCompiler compiler = findCompiler();
if ( compiler == null ) {
throw new RuntimeException("Cannot find System Java Compiler. Ensure that you have installed a JDK (not just a JRE) and configured your JAVA_HOME system variable to point to the according directory.");
}
CompileOptions compileOptions = spec.getCompileOptions();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
null,
null,
compileOptions.getEncoding() != null
? Charset.forName( compileOptions.getEncoding() )
: null
);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(spec.getSource());
return compiler.getTask(null, null, diagnosticListener, options, null, compilationUnits);
}
private static class DiagnosticListenerImpl implements DiagnosticListener<JavaFileObject> {
private final Logger logger;
public DiagnosticListenerImpl(Logger logger) {
this.logger = logger;
}
@Override
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
switch ( diagnostic.getKind() ) {
case Diagnostic.Kind.ERROR:
logger.debug( "[ERROR] : " + diagnostic.toString() );
break;
case Diagnostic.Kind.WARNING:
logger.debug( "[WARNING] : " + diagnostic.toString() );
break;
case Diagnostic.Kind.MANDATORY_WARNING:
logger.debug( "[MANDATORY_WARNING] : " + diagnostic.toString() );
break;
case Diagnostic.Kind.NOTE:
logger.debug( "[NOTE] : " + diagnostic.toString() );
break;
case Diagnostic.Kind.OTHER:
logger.debug( "[OTHER] : " + diagnostic.toString() );
break
default:
logger.debug( "[UNKNOWN] : " + diagnostic.toString() );
break;
}
}
public boolean sawError() {
// technically ERROR diagnostics should end the compile cycle, but since we are just generating
// sources here we ignore errors for now (expecting the later compilation task to report them
// if still valid)
return false;
}
}
private static JavaCompiler findCompiler() {
File realJavaHome = Jvm.current().getJavaHome();
File javaHomeFromToolProvidersPointOfView = new File(System.getProperty("java.home"));
if (realJavaHome.equals(javaHomeFromToolProvidersPointOfView)) {
return ToolProvider.getSystemJavaCompiler();
}
System.setProperty("java.home", realJavaHome.getAbsolutePath());
try {
return ToolProvider.getSystemJavaCompiler();
} finally {
System.setProperty("java.home", javaHomeFromToolProvidersPointOfView.getAbsolutePath());
}
}
}

View File

@ -64,7 +64,8 @@ task jaxb {
} }
} }
} }
runSourceGenerators.dependsOn jaxb
sourceSets.main.sourceGeneratorsTask.dependsOn jaxb
checkstyleMain.exclude '**/jaxb/**' checkstyleMain.exclude '**/jaxb/**'