From 1f683a998c125e27b6df1166f814034e902a82be Mon Sep 17 00:00:00 2001 From: Jinbo Wang Date: Mon, 27 May 2024 20:25:57 +0800 Subject: [PATCH] Add the option to use an alternative compiler for compiling classes in JavaBuilder --- .../internal/AptCompilationParticipant.java | 44 +++++++ .../META-INF/MANIFEST.MF | 1 + .../core/tests/builder/BasicBuildTests.java | 77 +++++++++++- .../core/compiler/CompilationParticipant.java | 25 ++++ .../core/compiler/CompilerConfiguration.java | 100 ++++++++++++++++ .../core/builder/AbstractImageBuilder.java | 110 ++++++++++++++++-- 6 files changed, 349 insertions(+), 8 deletions(-) create mode 100644 org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilerConfiguration.java diff --git a/org.eclipse.jdt.apt.core/src/org/eclipse/jdt/apt/core/internal/AptCompilationParticipant.java b/org.eclipse.jdt.apt.core/src/org/eclipse/jdt/apt/core/internal/AptCompilationParticipant.java index ecf85e7d265..df91e0549d9 100644 --- a/org.eclipse.jdt.apt.core/src/org/eclipse/jdt/apt/core/internal/AptCompilationParticipant.java +++ b/org.eclipse.jdt.apt.core/src/org/eclipse/jdt/apt/core/internal/AptCompilationParticipant.java @@ -15,19 +15,24 @@ package org.eclipse.jdt.apt.core.internal; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.apt.core.internal.generatedfile.GeneratedSourceFolderManager; import org.eclipse.jdt.apt.core.internal.util.FactoryPath; +import org.eclipse.jdt.apt.core.internal.util.FactoryPathUtil; import org.eclipse.jdt.apt.core.internal.util.TestCodeUtil; import org.eclipse.jdt.apt.core.util.AptConfig; import org.eclipse.jdt.core.ICompilationUnit; @@ -208,6 +213,45 @@ public void processAnnotations(BuildContext[] allfiles) { } } + @Override + public String[] getAnnotationProcessorPaths(IJavaProject project, boolean isTest) { + List processorPaths = new ArrayList<>(); + FactoryPath factoryPath = FactoryPathUtil.getFactoryPath(project); + if (factoryPath == null) { + return super.getAnnotationProcessorPaths(project, isTest); + } + + factoryPath.getEnabledContainers().keySet().forEach(container -> { + if (container instanceof JarFactoryContainer jarContainer) { + if (jarContainer.exists()) { + processorPaths.add(jarContainer.getJarFile().getAbsolutePath()); + } + } + }); + + if (!processorPaths.isEmpty()) { + return processorPaths.toArray(new String[processorPaths.size()]); + } + + return super.getAnnotationProcessorPaths(project, isTest); + } + + @Override + public String[] getGeneratedSourcePaths(IJavaProject project, boolean isTest) { + AptProject aptProject = AptPlugin.getAptProject(project); + if (aptProject == null) { + return null; + } + + GeneratedSourceFolderManager generatedSourceFolderManager = aptProject.getGeneratedSourceFolderManager(isTest); + if (generatedSourceFolderManager == null) { + return null; + } + + IFolder folder = generatedSourceFolderManager.getFolder(); + return folder == null ? null : new String[] { folder.getLocation().toOSString() }; + } + @Override public void reconcile(ReconcileContext context){ final ICompilationUnit workingCopy = context.getWorkingCopy(); diff --git a/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF index 0d4125e0e56..98ab5ba5709 100644 --- a/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF @@ -5,6 +5,7 @@ Bundle-SymbolicName: org.eclipse.jdt.core.tests.builder; singleton:=true Bundle-Version: 3.12.400.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin +Fragment-Host: org.eclipse.jdt.core Export-Package: org.eclipse.jdt.core.tests.builder Require-Bundle: org.junit;bundle-version="3.8.1", org.eclipse.jdt.core;bundle-version="[3.36.0,4.0.0)", diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BasicBuildTests.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BasicBuildTests.java index db417116ddb..56ea43a8449 100644 --- a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BasicBuildTests.java +++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BasicBuildTests.java @@ -23,12 +23,21 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.jdt.core.compiler.*; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.core.tests.util.Util; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; +import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; +import org.eclipse.jdt.internal.compiler.env.INameEnvironment; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; +import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import org.eclipse.jdt.internal.core.JavaModelManager; +import org.eclipse.jdt.internal.core.builder.AbstractImageBuilder; /** * Basic tests of the image builder. @@ -687,4 +696,70 @@ public void testBug549942() throws JavaModelException { org.eclipse.jdt.internal.core.builder.AbstractImageBuilder.MAX_AT_ONCE = save; } } + + public void testCustomCompiler() throws JavaModelException { + final String CUSTOM_COMPILER_KEY = AbstractImageBuilder.class.getSimpleName() + ".compiler"; + final String CUSTOM_COMPILER_VALUE = MockCompiler.class.getName(); + try { + System.setProperty(CUSTOM_COMPILER_KEY, CUSTOM_COMPILER_VALUE); + IPath projectPath = env.addProject("Project"); //$NON-NLS-1$ + env.addExternalJars(projectPath, Util.getJavaClassLibs()); + + // remove old package fragment root so that names don't collide + env.removePackageFragmentRoot(projectPath, ""); //$NON-NLS-1$ + + IPath root = env.addPackageFragmentRoot(projectPath, "src"); //$NON-NLS-1$ + env.setOutputFolder(projectPath, "bin"); //$NON-NLS-1$ + + IPath path = env.addClass(root, "p1", "Hello", //$NON-NLS-1$ //$NON-NLS-2$ + "package p1;\n"+ //$NON-NLS-1$ + "public class Hello {\n"+ //$NON-NLS-1$ + " public static void main(String args[]) {\n"+ //$NON-NLS-1$ + " System.out.println(\"Hello world\");\n"+ //$NON-NLS-1$ + " }\n"+ //$NON-NLS-1$ + "}\n" //$NON-NLS-1$ + ); + + fullBuild(projectPath); + + Problem[] problems = allSortedProblems(new IPath[] {path}); + + expectingProblemsFor( + path, + "Problem : Compilation error from MockCompiler [ resource : range : <0,1> category : <60> severity : <2>]" + ); + } finally { + System.clearProperty(CUSTOM_COMPILER_KEY); + } + } + + public static class MockCompiler extends org.eclipse.jdt.internal.compiler.Compiler { + private CompilerConfiguration compilerConfig; + + public MockCompiler(INameEnvironment environment, IErrorHandlingPolicy policy, CompilerConfiguration compilerConfig, + ICompilerRequestor requestor, IProblemFactory problemFactory) { + super(environment, policy, compilerConfig.getOptions(), requestor, problemFactory); + this.compilerConfig = compilerConfig; + } + + @Override + public void compile(ICompilationUnit[] sourceUnits) { + for (int i = 0; i < sourceUnits.length; i++) { + ICompilationUnit in = sourceUnits[i]; + CompilationResult result = new CompilationResult(in, i, sourceUnits.length, Integer.MAX_VALUE); + if (i == 0) { + CategorizedProblem problem = new DefaultProblem(in.getFileName(), + "Compilation error from MockCompiler", + 0, + new String[0], + ProblemSeverities.Error, + 0, 0, 0, 0); + result.problems = new CategorizedProblem[] { problem }; + result.problemCount = result.problems.length; + } + + this.requestor.acceptResult(result); + } + } + } } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilationParticipant.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilationParticipant.java index d64d91fa920..a630048dfd9 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilationParticipant.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilationParticipant.java @@ -152,6 +152,31 @@ public void processAnnotations(BuildContext[] files) { // do nothing by default } +/** + * Returns where to find annotation processors. If this is not specified, + * then the class path is searched for processors. + * + * @param project the project to participate in + * @param isTest whether the annotation processor path is for test code + * @return the annotation processor paths + * @since 3.38 + */ +public String[] getAnnotationProcessorPaths(IJavaProject project, boolean isTest) { + return null; +} + +/** + * Returns the locations to place the generated source files. + * + * @param project the project to participate in + * @param isTest whether the generated source paths are for test code + * @return the locations to place the generated source files + * @since 3.38 + */ +public String[] getGeneratedSourcePaths(IJavaProject project, boolean isTest) { + return null; +} + /** * Returns whether this participant is interested in post processing of generated bytecode. *

diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilerConfiguration.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilerConfiguration.java new file mode 100644 index 00000000000..f2a83b9707b --- /dev/null +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilerConfiguration.java @@ -0,0 +1,100 @@ +/******************************************************************************* +* Copyright (c) 2024 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package org.eclipse.jdt.core.compiler; + +import java.io.File; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; + +/** + * @since 3.38 + */ +public class CompilerConfiguration { + List sourcepaths; + List moduleSourcepaths; + List classpaths; + List modulepaths; + Map sourceOutputMapping; + CompilerOptions options; + // Location to search for annotation processors. + List annotationProcessorPaths; + // Locations to place generated source files. + List generatedSourcePaths; + + public List getClasspaths() { + return this.classpaths; + } + + public void setClasspaths(List classpaths) { + this.classpaths = classpaths; + } + + public List getModulepaths() { + return this.modulepaths; + } + + public void setModulepaths(List modulepaths) { + this.modulepaths = modulepaths; + } + + public List getSourcepaths() { + return this.sourcepaths; + } + + public void setSourcepaths(List sourcepaths) { + this.sourcepaths = sourcepaths; + } + + public List getModuleSourcepaths() { + return this.moduleSourcepaths; + } + + public void setModuleSourcepaths(List moduleSourcepaths) { + this.moduleSourcepaths = moduleSourcepaths; + } + + public Map getSourceOutputMapping() { + return this.sourceOutputMapping; + } + + public void setSourceOutputMapping(Map sourceOutputMapping) { + this.sourceOutputMapping = sourceOutputMapping; + } + + public CompilerOptions getOptions() { + return this.options; + } + + public void setOptions(CompilerOptions options) { + this.options = options; + } + + public List getAnnotationProcessorPaths() { + return this.annotationProcessorPaths; + } + + public void setAnnotationProcessorPaths(List annotationProcessorPaths) { + this.annotationProcessorPaths = annotationProcessorPaths; + } + + public List getGeneratedSourcePaths() { + return this.generatedSourcePaths; + } + + public void setGeneratedSourcePaths(List generatedSourcePaths) { + this.generatedSourcePaths = generatedSourcePaths; + } +} diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/AbstractImageBuilder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/AbstractImageBuilder.java index fc2ea679229..09c66ece56f 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/AbstractImageBuilder.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/AbstractImageBuilder.java @@ -23,6 +23,7 @@ import org.eclipse.jdt.internal.compiler.Compiler; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; +import org.eclipse.jdt.internal.compiler.env.INameEnvironment; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; @@ -38,6 +39,7 @@ import static org.eclipse.jdt.internal.core.JavaModelManager.trace; import java.io.*; +import java.lang.reflect.*; import java.util.*; /** @@ -62,6 +64,7 @@ public abstract class AbstractImageBuilder implements ICompilerRequestor, ICompi protected boolean compiledAllAtOnce; private boolean inCompiler; +private boolean useDefaultCompiler = true; protected boolean keepStoringProblemMarkers; protected Map filesWithAnnotations = null; @@ -573,12 +576,39 @@ protected Compiler newCompiler() { CompilerOptions compilerOptions = new CompilerOptions(projectOptions); compilerOptions.performMethodsFullRecovery = true; compilerOptions.performStatementsRecovery = true; - Compiler newCompiler = new Compiler( - this.nameEnvironment, - DefaultErrorHandlingPolicies.proceedWithAllProblems(), - compilerOptions, - this, - ProblemFactory.getProblemFactory(Locale.getDefault())); + Compiler newCompiler = null; + String compilerClassName = System.getProperty(AbstractImageBuilder.class.getSimpleName() + ".compiler"); //$NON-NLS-1$ + if (compilerClassName != null) { + try { + Class compilerClass = (Class) Class.forName(compilerClassName); + Constructor constructor = compilerClass.getDeclaredConstructor( + INameEnvironment.class, + IErrorHandlingPolicy.class, + CompilerConfiguration.class, + ICompilerRequestor.class, + IProblemFactory.class); + newCompiler = constructor.newInstance(this.nameEnvironment, + DefaultErrorHandlingPolicies.proceedWithAllProblems(), + prepareCompilerConfiguration(compilerOptions), + this, + ProblemFactory.getProblemFactory(Locale.getDefault())); + this.useDefaultCompiler = false; + } catch (ClassNotFoundException e) { + ILog.get().error("Could not load class " + compilerClassName, e); //$NON-NLS-1$ + } catch (NoSuchMethodException e) { + ILog.get().error("Couldn't find compatible constructor " + compilerClassName); //$NON-NLS-1$ + } catch (IllegalAccessException | IllegalArgumentException | InstantiationException | InvocationTargetException e) { + ILog.get().error("Failed invoking constructor " + compilerClassName); //$NON-NLS-1$ + } + } + if (newCompiler == null) { + newCompiler = new Compiler( + this.nameEnvironment, + DefaultErrorHandlingPolicies.proceedWithAllProblems(), + compilerOptions, + this, + ProblemFactory.getProblemFactory(Locale.getDefault())); + } CompilerOptions options = newCompiler.options; // temporary code to allow the compiler to revert to a single thread String setting = System.getProperty("jdt.compiler.useSingleThread"); //$NON-NLS-1$ @@ -596,6 +626,72 @@ protected Compiler newCompiler() { return newCompiler; } +private CompilerConfiguration prepareCompilerConfiguration(CompilerOptions options) { + CompilerConfiguration configuration = new CompilerConfiguration(); + configuration.setOptions(options); + List annotationProcessorPaths = new ArrayList<>(); + List generatedSourcePaths = new ArrayList<>(); + boolean isTest = this.compilationGroup == CompilationGroup.TEST; + if (this.javaBuilder.participants != null) { + for (CompilationParticipant participant : this.javaBuilder.participants) { + if (participant.isAnnotationProcessor()) { + String[] paths = participant.getAnnotationProcessorPaths(this.javaBuilder.javaProject, isTest); + if (paths != null) { + annotationProcessorPaths.addAll(Arrays.asList(paths)); + } + String[] generatedSrc = participant.getGeneratedSourcePaths(this.javaBuilder.javaProject, isTest); + if (generatedSrc != null) { + generatedSourcePaths.addAll(Arrays.asList(generatedSrc)); + } + } + } + } + configuration.setAnnotationProcessorPaths(annotationProcessorPaths); + configuration.setGeneratedSourcePaths(generatedSourcePaths); + ClasspathLocation[] classpathLocations = this.nameEnvironment.binaryLocations; + List classpaths = new ArrayList<>(); + List modulepaths = new ArrayList<>(); + for (ClasspathLocation location : classpathLocations) { + if (location instanceof ClasspathDirectory cpDirectory) { + String filepath = cpDirectory.binaryFolder.getLocation().toFile().getAbsolutePath(); + if (cpDirectory.isOnModulePath && !modulepaths.contains(filepath)) { + modulepaths.add(filepath); + } else if (!cpDirectory.isOnModulePath && !classpaths.contains(filepath)) { + classpaths.add(filepath); + } + } else if (location instanceof ClasspathJar cpJar) { + String filepath = cpJar.zipFilename; + if (cpJar.isOnModulePath && !modulepaths.contains(filepath)) { + modulepaths.add(filepath); + } else if (!cpJar.isOnModulePath && !classpaths.contains(filepath)) { + classpaths.add(filepath); + } + } + } + configuration.setClasspaths(classpaths); + configuration.setModulepaths(modulepaths); + + Map sourceOutputMapping = new HashMap<>(); + List sourcepaths = new ArrayList<>(); + List moduleSourcepaths = new ArrayList<>(); + ClasspathMultiDirectory[] srcLocations = this.nameEnvironment.sourceLocations; + for (ClasspathMultiDirectory sourceLocation : srcLocations) { + File sourceFolder = sourceLocation.sourceFolder.getLocation().toFile(); + File outputFolder = sourceLocation.binaryFolder.getLocation().toFile(); + sourceOutputMapping.put(sourceFolder, outputFolder); + String sourcepath = sourceFolder.getAbsolutePath(); + if (sourceLocation.isOnModulePath && !moduleSourcepaths.contains(sourcepath)) { + moduleSourcepaths.add(sourcepath); + } else if (!sourceLocation.isOnModulePath && !sourcepaths.contains(sourcepath)) { + sourcepaths.add(sourcepath); + } + } + configuration.setSourceOutputMapping(sourceOutputMapping); + configuration.setSourcepaths(sourcepaths); + configuration.setModuleSourcepaths(moduleSourcepaths); + return configuration; +} + protected CompilationParticipantResult[] notifyParticipants(SourceFile[] unitsAboutToCompile) { CompilationParticipantResult[] results = new CompilationParticipantResult[unitsAboutToCompile.length]; for (int i = unitsAboutToCompile.length; --i >= 0;) @@ -668,7 +764,7 @@ protected void processAnnotations(CompilationParticipantResult[] results) { // even if no files have annotations, must still tell every annotation processor in case the file used to have them for (CompilationParticipant participant : this.javaBuilder.participants) - if (participant.isAnnotationProcessor()) + if (this.useDefaultCompiler && participant.isAnnotationProcessor()) participant.processAnnotations(results); processAnnotationResults(results); }