/* * Copyright 2003-2017 JetBrains s.r.o. * * 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. */ package jetbrains.mps.make; import jetbrains.mps.compiler.EclipseJavaCompiler; import jetbrains.mps.compiler.JavaCompilerOptions; import jetbrains.mps.make.CompilationErrorsHandler.ClassesErrorsTracker; import jetbrains.mps.make.ModuleAnalyzer.ModuleAnalyzerResult; import jetbrains.mps.project.facets.JavaModuleOperations; import jetbrains.mps.reloading.IClassPathItem; import jetbrains.mps.util.FileUtil; import jetbrains.mps.vfs.IFile; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.internal.compiler.CompilationResult; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.module.SModule; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import static jetbrains.mps.project.SModuleOperations.getJavaFacet; /** * fixme use bundle for this package */ class InternalJavaCompiler { private final static String MODULE_WITH_REMOVALS_WAS_NOT_CHANGED = "Module With Removals Is Not In The Changed Modules: %s"; private final static String NO_CHANGES_AFTER_COMPILATION_ERROR = "Compilation Passed But The Changed Modules Are Empty"; private final static String CALCULATING_DEPS_MSG = "Calculating Classpath"; private final static String COMPILING_JAVA_MSG = "Compiling Java"; private final static String PREPARING_TO_COMPILE_MSG = "Preparing"; private final static String COPYING_RESOURCES_MSG = "Copying Resources"; private final static String HANDLING_ERRORS_MSG = "Handling Errors"; private final static String WRITING_CLASSES_MSG = "Writing Classes"; private final static String ECLIPSE_COMPILER_MSG = "Running ECJ"; @NotNull private final ModulesContainer myModulesContainer; @Nullable private final JavaCompilerOptions myCompilerOptions; InternalJavaCompiler(@NotNull ModulesContainer modulesContainer, @Nullable JavaCompilerOptions compilerOptions) { myModulesContainer = modulesContainer; myCompilerOptions = compilerOptions; } @NotNull public MPSCompilationResult compile(CompositeTracer tracer) { if (!myModulesContainer.hasModuleToCompile()) { return MPSCompilationResult.ZERO_COMPILATION_RESULT; } tracer.start("", 5); try { ModuleAnalyzerResult analysisResult = new ModuleAnalyzer(myModulesContainer).analyze(); EclipseJavaCompiler compiler; tracer.push(PREPARING_TO_COMPILE_MSG); try { if (!analysisResult.hasJavaToCompile && !analysisResult.hasResourcesToUpdate) { return MPSCompilationResult.nothingToDoCompilationResult(); } analysisResult.filesToDelete.forEach(FileUtil::delete); // removing all stale files compiler = collectSources(); } finally { tracer.pop(1); } MPSCompilationResult result; if (!analysisResult.hasJavaToCompile) { result = MPSCompilationResult.noJavaCompiledCompilationResult(); } else { result = compileJava(compiler, tracer.subTracer(3)); reportModulesWithRemovalsAreNotChanged(analysisResult.modulesWithRemovals, result.getChangedModules(), tracer); } copyResources(tracer.subTracer(1)); return result; } finally { tracer.done(); } } /** * @return eclipse java compiler with sources attached */ private EclipseJavaCompiler collectSources() { EclipseJavaCompiler compiler = new EclipseJavaCompiler(); for (SModule module : myModulesContainer.getModules()) { if (!myModulesContainer.areClassesUpToDate(module)) { for (JavaFile javaFile : myModulesContainer.getSources(module).getFilesToCompile()) { compiler.addSource(javaFile.getClassName(), javaFile.getContents()); myModulesContainer.putClassForModule(javaFile.getClassName(), module); } } } return compiler; } private void copyResources(CompositeTracer tracer) { tracer.start(COPYING_RESOURCES_MSG, 1); try { for (SModule module : myModulesContainer.getModules()) { ModuleSources sources = myModulesContainer.getSources(module); IFile classesGen = getJavaFacet(module).getClassesGen(); if (classesGen == null) { continue; } for (ResourceFile toCopy : sources.getResourcesToCopy()) { String fqName = toCopy.getPath(); fqName = fqName.substring(0, fqName.length() - toCopy.getFile().getName().length()); String path = fqName + toCopy.getFile().getName(); if (new File(toCopy.getFile().getAbsolutePath()).exists()) { FileUtil.copyFile(new File(toCopy.getFile().getAbsolutePath()), new File(classesGen.getDescendant(path).toPath().toAbsolute().toString())); } } } } finally { tracer.done(1); } } private void reportModulesWithRemovalsAreNotChanged(Set<SModule> modulesWithRemovals, Set<SModule> changedModules, CompositeTracer tracer) { for (SModule module : modulesWithRemovals) { if (!changedModules.contains(module)) { tracer.getSender().warn(String.format(MODULE_WITH_REMOVALS_WAS_NOT_CHANGED, module), module.getModuleReference()); } } } @NotNull private MPSCompilationResult compileJava(EclipseJavaCompiler compiler, CompositeTracer tracer) { tracer.start(COMPILING_JAVA_MSG, 10); try { IClassPathItem classPath = computeDependenciesClassPath(myModulesContainer.getModules(), tracer.subTracer(1)); final CompilationErrorsHandler errorsHandler = new CompilationErrorsHandler(myModulesContainer, tracer.getSender(), classPath); CompilationHandler compilationHandler = new CompilationHandler(classPath, tracer.subTracer(3), errorsHandler); final CollectingResultsListener listener = new CollectingResultsListener(errorsHandler); compiler.addCompilationResultListener(listener); doCompileJava(compiler, classPath, myCompilerOptions, tracer.subTracer(6)); compiler.removeCompilationResultListener(listener); Collection<SModule> changedModules = compilationHandler.process(listener.getResults()); if (changedModules.isEmpty()){ tracer.getSender().error(NO_CHANGES_AFTER_COMPILATION_ERROR); } return new MPSCompilationResult(errorsHandler.getErrorsCount(), 0, false, changedModules); // fixme: no warnings in the result } finally { tracer.done(); } } private void doCompileJava(EclipseJavaCompiler compiler, IClassPathItem classPath, @Nullable JavaCompilerOptions compilerOptions, CompositeTracer tracer) { try { tracer.start(ECLIPSE_COMPILER_MSG, 1); if (compilerOptions == null) { compiler.compile(classPath); } else { compiler.compile(classPath, compilerOptions); } } finally { tracer.done(1); } } // FIXME at least twice we count all the dependencies WHY private static IClassPathItem computeDependenciesClassPath(Set<SModule> modules, CompositeTracer tracer) { tracer.start(CALCULATING_DEPS_MSG, 1); try { Set<String> classpath = JavaModuleOperations.collectCompileClasspath(modules, true); tracer.getSender().debug("ClassPath: " + classpath); return JavaModuleOperations.createClassPathItem(classpath, ModuleMaker.class.getName()); } finally { tracer.done(1); } } /** * Memorizes and returns all the results. Also handles fatal errors */ private class CollectingResultsListener extends jetbrains.mps.compiler.CompilationResultAdapter { private final CompilationErrorsHandler myErrorsHandler; private final List<CompilationResult> myResults = new ArrayList<>(); CollectingResultsListener(@NotNull CompilationErrorsHandler errorsHandler) { myErrorsHandler = errorsHandler; } @Override public void onFatalError(@NotNull String msg) { myErrorsHandler.handleFatal(msg); } @Override public void onCompilationResult(CompilationResult r) { myResults.add(r); } public List<CompilationResult> getResults() { return Collections.unmodifiableList(myResults); } } /** * Process all the compilation results */ private class CompilationHandler { private final IClassPathItem myClassPath; private final CompositeTracer myTracer; private final CompilationErrorsHandler myErrorsHandler; private final ClassFileWriter myWriter; CompilationHandler(IClassPathItem classPath, CompositeTracer tracer, CompilationErrorsHandler errorsHandler) { myClassPath = classPath; myTracer = tracer; myErrorsHandler = errorsHandler; myWriter = new ClassFileWriter(myModulesContainer, tracer, myClassPath); } /** * @return a set of changed modules */ public Set<SModule> process(List<CompilationResult> results) { myTracer.start(HANDLING_ERRORS_MSG, 11); try { ClassesErrorsTracker errorsTracker = new ClassesErrorsTracker(); for (CompilationResult result : results) { CategorizedProblem[] errors = result.getErrors(); if (errors != null && errors.length > 0) { errorsTracker = myErrorsHandler.handle(result); } } myTracer.advance(1); myTracer.push(WRITING_CLASSES_MSG); Set<SModule> changedModules = myWriter.write(results, errorsTracker); myTracer.pop(10); return changedModules; } finally { myTracer.done(); } } } }