/* * 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.classloading.ClassLoaderManager; import jetbrains.mps.compiler.EclipseJavaCompiler; import jetbrains.mps.compiler.JavaCompilerOptions; import jetbrains.mps.make.dependencies.StronglyConnectedModules; import jetbrains.mps.messages.IMessageHandler; import jetbrains.mps.messages.MessageKind; import jetbrains.mps.project.dependency.GlobalModuleDependenciesManager; import jetbrains.mps.project.dependency.GlobalModuleDependenciesManager.Deptype; import jetbrains.mps.project.facets.JavaModuleFacet; import jetbrains.mps.util.FileUtil; import jetbrains.mps.util.annotation.ToRemove; import jetbrains.mps.util.performance.IPerformanceTracer.NullPerformanceTracer; import jetbrains.mps.util.performance.PerformanceTracer; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.util.ProgressMonitor; import org.jetbrains.mps.openapi.util.SubProgressKind; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeSet; /** * ModuleMaker is able to make sources of the given modules. * Main API is two #make methods, one of them accepts also the compiler options argument (e.g. to choose the java language level * for the compiler) * * Underneath this class analyzes module dependencies, * chooses which of the modules are dirty, collects all the java sources and handles * them to the eclipse java compiler (the mps wrapper {@link EclipseJavaCompiler}) * * fixme use bundle for this package * fixme check multiple computations of the same modules' dependencies (time wasting) */ public final class ModuleMaker { public final static Comparator<SModule> MODULE_BY_NAME_COMPARATOR = (module1, module2) -> module1.getModuleName().compareTo(module2.getModuleName()); private final static String BUILDING_MODULES_MSG = "Building %d Modules"; private final static String CYCLE_FORMAT_MSG = "Cycle #%d: [%s]"; private final static String COLLECTING_DEPENDENCIES_MSG = "Collecting Dependent Candidates"; private final static String LOADING_DEPENDENCIES_MSG = "Loading Dependencies"; private final static String CALCULATING_DEPENDENCIES_TO_COMPILE_MSG = "Calculating Modules To Compile"; private final static String BUILDING_MODULE_CYCLES_MSG = "Building Module Cycles"; private final static String BUILDING_MODULES = "Building"; private final static String BUILDING_BACK_DEPS_MSG = "Building Closure"; private final static String BUILDING_DIRTY_CLOSURE = "Dirty Modules"; private final static String CHECKING_DIRTY_MODULES_MSG = "Checking"; @NotNull private final CompositeTracer myTracer; /** * The empty constructor delegates only error messages to the apache's logger and traces nothing */ public ModuleMaker() { Logger logger = LogManager.getLogger(ModuleMaker.class); MessageSender sender = new MessageSender(IMessageHandler.NULL_HANDLER, logger, this, Level.ERROR); myTracer = new CompositeTracer(new NullPerformanceTracer(), sender); } /** * Constructor for regular use, if uncertain, use this one. * * @param handler sink for end-user messages */ public ModuleMaker(@NotNull IMessageHandler handler) { // End-user messages piped through supplied handler, trace and debug messages go to log according to external configuration Logger logger = LogManager.getLogger(ModuleMaker.class); String mmName = ModuleMaker.class.getName(); MessageSender sender = new MessageSender(handler, logger, mmName, Level.ALL); // PerformanceTracer.printReport sends it with info level, but it doesn't seem reasonable to collect performance data unless we debug MM. myTracer = new CompositeTracer(logger.isDebugEnabled() ? new PerformanceTracer(mmName) : new NullPerformanceTracer(), sender); } /** * Accepts the logging strategy (via {@link IMessageHandler}) * and the logging level {@link MessageKind}. * @deprecated level is ignored, use {@link #ModuleMaker(IMessageHandler)} instead */ @Deprecated @ToRemove(version = 2017.2) public ModuleMaker(@NotNull IMessageHandler handler, @NotNull MessageKind level) { this(handler); } /** * TODO move or rename the ModuleMaker (the naming is quite disturbing) */ public void clean(final Set<? extends SModule> modules, @NotNull final ProgressMonitor monitor) { monitor.start("Cleaning...", modules.size()); try { for (SModule module : modules) { if (monitor.isCanceled()) { break; } if (!ModulesContainer.isExcluded(module)) { monitor.step(module.getModuleName()); JavaModuleFacet facet = module.getFacet(JavaModuleFacet.class); assert facet != null && facet.getClassesGen() != null; File classesGenFile = new File(facet.getClassesGen().toPath().toString()); FileUtil.delete(classesGenFile); } monitor.advance(1); } } finally { monitor.done(); } } @NotNull public MPSCompilationResult makeAndDeploy(final Collection<? extends SModule> modules, @NotNull final ProgressMonitor monitor, @Nullable JavaCompilerOptions compilerOptions) { monitor.start(BUILDING_MODULES, 4); try { MPSCompilationResult result = make(modules, monitor.subTask(3, SubProgressKind.REPLACING), compilerOptions); ClassLoaderManager.getInstance().reloadModules(modules, monitor.subTask(1)); return result; } finally { monitor.done(); } } @NotNull public MPSCompilationResult make(final Collection<? extends SModule> modules, @NotNull final ProgressMonitor monitor) { return make(modules, monitor, null); } @NotNull public MPSCompilationResult make(final Collection<? extends SModule> modules, @NotNull final ProgressMonitor monitor, @Nullable final JavaCompilerOptions compilerOptions) { CompositeTracer tracer = new CompositeTracer(myTracer, monitor); tracer.start(String.format(BUILDING_MODULES_MSG, modules.size()), 10); try { tracer.push(COLLECTING_DEPENDENCIES_MSG); Set<SModule> candidates = new LinkedHashSet<>(new GlobalModuleDependenciesManager(modules).getModules(Deptype.COMPILE)); tracer.pop(1); tracer.push(LOADING_DEPENDENCIES_MSG); Dependencies dependencies = new Dependencies(candidates); // fixme AP why do we need to look for some other deps?? tracer.pop(1); tracer.push(CALCULATING_DEPENDENCIES_TO_COMPILE_MSG); Set<SModule> toCompile = buildDirtyModulesClosure(new ModulesContainer(candidates, dependencies), tracer.subTracer(1)); tracer.pop(); tracer.push(BUILDING_MODULE_CYCLES_MSG); List<Set<SModule>> schedule = new StronglyConnectedModules<>(toCompile).getStronglyConnectedComponents(); tracer.pop(1); return compileCycles(compilerOptions, schedule, tracer.subTracer(6, SubProgressKind.REPLACING), dependencies); } finally { tracer.done(); tracer.printReport(); } } @NotNull private MPSCompilationResult compileCycles(@Nullable JavaCompilerOptions compilerOptions, List<Set<SModule>> cyclesToCompile, @NotNull CompositeTracer tracer, @NotNull Dependencies dependencies) { List<MPSCompilationResult> cycleCompilationResults = new ArrayList<>(); tracer.start("", cyclesToCompile.size()); try { int cycleNumber = 0; for (Set<SModule> modulesInCycle : cyclesToCompile) { if (tracer.isMonitorCanceled()) { break; } ++cycleNumber; CompositeTracer cycleTracer = tracer.subTracer(1); tracer.getSender().info(String.format(CYCLE_FORMAT_MSG, cycleNumber, modulesInCycle)); cycleTracer.start(getCycleString(cycleNumber, modulesInCycle), 1); ModulesContainer modulesContainer = new ModulesContainer(modulesInCycle, dependencies); InternalJavaCompiler internalJavaCompiler = new InternalJavaCompiler(modulesContainer, compilerOptions); MPSCompilationResult cycleCompilationResult = internalJavaCompiler.compile(cycleTracer.subTracer(1, SubProgressKind.AS_COMMENT)); cycleCompilationResults.add(cycleCompilationResult); cycleTracer.done(0); } } finally { tracer.done(); } return combineCycleCompilationResults(cycleCompilationResults); } private String getCycleString(int cycleNumber, Set<SModule> modulesInCycle) { Optional<SModule> first = modulesInCycle.stream().findFirst(); String firstModule = ""; if (first.isPresent()) { firstModule = first.get().getModuleName(); if (modulesInCycle.size() > 1) { firstModule += " and " + (modulesInCycle.size() - 1) + " others"; } } return String.format(CYCLE_FORMAT_MSG, cycleNumber, firstModule); } @NotNull private MPSCompilationResult combineCycleCompilationResults(List<MPSCompilationResult> results) { int errorCount = 0; int warnCount = 0; Set<SModule> changedModules = new HashSet<>(); for (MPSCompilationResult result : results) { errorCount += result.getErrorsCount(); warnCount += result.getWarningsCount(); changedModules.addAll(result.getChangedModules()); } return new MPSCompilationResult(errorCount, warnCount, false, changedModules); } /** * The answer is always sorted by name */ private Set<SModule> buildDirtyModulesClosure(ModulesContainer modulesContainer, CompositeTracer tracer) { tracer.start(BUILDING_DIRTY_CLOSURE, 3); Set<SModule> candidates = modulesContainer.getModules(); tracer.push(CHECKING_DIRTY_MODULES_MSG, false); List<SModule> dirtyModules = new ArrayList<SModule>(candidates.size()); for (SModule m : candidates) { if (modulesContainer.isDirty(m)) { dirtyModules.add(m); } } tracer.pop(1); // select from modules those that are affected by the "dirty" modules // M={m}, D={m*}, D<=M, R:M->2^M (required), R* transitive closure of R // C={m|m from M, exists m* from D: m* in R*(m)} // to compile T=D union C Map<SModule, Set<SModule>> backDependencies = new HashMap<>(); tracer.push(BUILDING_BACK_DEPS_MSG, true); for (SModule m : candidates) { for (SModule dep : new GlobalModuleDependenciesManager(m).getModules(Deptype.COMPILE)) { Set<SModule> incoming = backDependencies.get(dep); if (incoming == null) { incoming = new HashSet<>(); backDependencies.put(dep, incoming); } incoming.add(m); } } Set<SModule> toCompile = new LinkedHashSet<>(); // BFS from dirtyModules along backDependencies LinkedList<SModule> queue = new LinkedList<SModule>(dirtyModules); while (!queue.isEmpty()) { SModule m = queue.removeFirst(); if (candidates.contains(m)) { toCompile.add(m); } Set<SModule> backDeps = backDependencies.remove(m); if (backDeps != null) { queue.addAll(backDeps); } } tracer.pop(1); Set<SModule> result = new TreeSet<>(MODULE_BY_NAME_COMPARATOR); result.addAll(toCompile); return result; } }