/* * Copyright 2012-2015 Sergey Ignatov * * 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 org.intellij.erlang.jps.builder; import com.intellij.openapi.util.Conditions; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.graph.Graph; import com.intellij.util.graph.GraphGenerator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.builders.BuildOutputConsumer; import org.jetbrains.jps.builders.BuildRootIndex; import org.jetbrains.jps.builders.BuildTarget; import org.jetbrains.jps.builders.DirtyFilesHolder; import org.jetbrains.jps.incremental.CompileContext; import org.jetbrains.jps.incremental.ProjectBuildException; import org.jetbrains.jps.incremental.TargetBuilder; import org.jetbrains.jps.incremental.messages.BuildMessage; import org.jetbrains.jps.incremental.messages.CompilerMessage; import java.io.File; import java.io.IOException; import java.util.*; import static org.intellij.erlang.jps.builder.ErlangBuilderUtil.*; public class ErlangModuleBuildOrderBuilder extends TargetBuilder<ErlangSourceRootDescriptor, ErlangModuleBuildOrderTarget> { private static final String NAME = "Build order builder"; public ErlangModuleBuildOrderBuilder() { super(Collections.singletonList(ErlangModuleBuildOrderTargetType.INSTANCE)); } @Override public void build(@NotNull ErlangModuleBuildOrderTarget target, @NotNull DirtyFilesHolder<ErlangSourceRootDescriptor, ErlangModuleBuildOrderTarget> holder, @NotNull BuildOutputConsumer outputConsumer, @NotNull CompileContext context) throws ProjectBuildException, IOException { LOG.info("Computing dirty files"); LOG.debug("Load project build order."); ErlangProjectBuildOrder projectBuildOrder = loadProjectBuildOrder(context); if (projectBuildOrder == null) { addPrepareDependenciesFailedMessage(context); return; } setEmptyBuildOrders(context); LOG.debug("Collect dirty files."); List<String> dirtyErlangFilePaths = new DirtyFilesProcessor<String, ErlangModuleBuildOrderTarget>() { @Nullable @Override protected String getDirtyElement(@NotNull File file) throws IOException { String fileName = file.getName(); return isSource(fileName) || isHeader(fileName) ? ErlangBuilderUtil.getPath(file) : null; } }.collectDirtyElements(holder); if (dirtyErlangFilePaths.isEmpty()) { LOG.debug("There are no dirty .erl or .hrl files."); } else { LOG.debug("Search dirty modules."); List<String> sortedDirtyModules = getSortedDirtyModules(projectBuildOrder, dirtyErlangFilePaths); addFilesToBuildTarget(context, sortedDirtyModules); } } @NotNull @Override public String getPresentableName() { return NAME; } private static void setEmptyBuildOrders(@NotNull CompileContext context) { List<BuildTarget<?>> allTargets = context.getProjectDescriptor().getBuildTargetIndex().getAllTargets(); List<BuildTarget<?>> erlangTargets = ContainerUtil.filter(allTargets, Conditions.instanceOf(ErlangTarget.class)); for (BuildTarget<?> erlangTarget : erlangTargets) { ((ErlangTarget) erlangTarget).setBuildOrder(new ErlangModuleBuildOrder()); } } @Nullable private static ErlangProjectBuildOrder loadProjectBuildOrder(@NotNull CompileContext context) { return readFromXML(context, BUILD_ORDER_FILE_NAME, ErlangProjectBuildOrder.class); } @NotNull private static List<String> getSortedDirtyModules(@NotNull ErlangProjectBuildOrder projectBuildOrder, @NotNull List<String> dirtyErlangFilePaths) { Set<String> allDirtyFiles = getAllDirtyFiles(projectBuildOrder, dirtyErlangFilePaths); return getSortedDirtyModules(projectBuildOrder.myErlangFiles, allDirtyFiles); } private static void addPrepareDependenciesFailedMessage(@NotNull CompileContext context) { context.processMessage(new CompilerMessage(NAME, BuildMessage.Kind.WARNING, "The project will be fully rebuilt due to errors.")); } @NotNull private static Set<String> getAllDirtyFiles(@NotNull ErlangProjectBuildOrder projectBuildOrder, @NotNull List<String> dirtyFiles) { Graph<String> dependencies = GraphGenerator.create(new SortedModuleDependencyGraph(projectBuildOrder)); Set<String> allDirtyFiles = ContainerUtil.newHashSet(); for (String dirtyFile : dirtyFiles) { collectDirtyFiles(dirtyFile, dependencies, allDirtyFiles); } return allDirtyFiles; } private static void collectDirtyFiles(@NotNull String filePath, @NotNull Graph<String> dependenciesGraph, @NotNull Set<String> dirtyFiles) { if (dirtyFiles.contains(filePath)) return; dirtyFiles.add(filePath); if (!dependenciesGraph.getNodes().contains(filePath)) { LOG.warn("Unexpected dirty file detected. " + "Please, report to https://github.com/ignatov/intellij-erlang/issues/685. " + "Path: " + filePath); return; } Iterator<String> dependentFilesIterator = dependenciesGraph.getOut(filePath); while (dependentFilesIterator.hasNext()) { collectDirtyFiles(dependentFilesIterator.next(), dependenciesGraph, dirtyFiles); } } @NotNull private static List<String> getSortedDirtyModules(@NotNull List<ErlangFileDescriptor> sortedFiles, @NotNull final Set<String> allDirtyFiles) { return ContainerUtil.mapNotNull(sortedFiles, node -> isSource(node.myPath) && allDirtyFiles.contains(node.myPath) ? node.myPath : null); } private static void addFilesToBuildTarget(@NotNull CompileContext context, @NotNull List<String> sortedDirtyErlangModules) { List<ErlangTargetType> targetTypes = Collections.singletonList(ErlangTargetType.INSTANCE); BuildRootIndex buildRootIndex = context.getProjectDescriptor().getBuildRootIndex(); for (String filePath : sortedDirtyErlangModules) { ErlangSourceRootDescriptor root = buildRootIndex.findParentDescriptor(new File(filePath), targetTypes, context); if (root == null) { LOG.error("Source root not found."); return; } ErlangTarget target = (ErlangTarget) root.getTarget(); ErlangModuleBuildOrder buildOrder = target.getBuildOrder(); if (buildOrder == null) { LOG.error("buildOrder for erlang module target are not set."); return; } if (root.isTests()) { buildOrder.myOrderedErlangTestFilePaths.add(filePath); } else { buildOrder.myOrderedErlangFilePaths.add(filePath); } } } private static class SortedModuleDependencyGraph implements GraphGenerator.SemiGraph<String> { private final LinkedHashMap<String, List<String>> myPathsToDependenciesMap = ContainerUtil.newLinkedHashMap(); public SortedModuleDependencyGraph(@NotNull ErlangProjectBuildOrder projectBuildOrder) { for (ErlangFileDescriptor node : projectBuildOrder.myErlangFiles) { myPathsToDependenciesMap.put(node.myPath, node.myDependencies); } } @Override public Collection<String> getNodes() { return myPathsToDependenciesMap.keySet(); } @Override public Iterator<String> getIn(String node) { return myPathsToDependenciesMap.get(node).iterator(); } } }