com.facebook.buck.features.project.intellij.IjModuleGraphFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.features.project.intellij.IjModuleGraphFactory.java

Source

/*
 * Copyright 2015-present Facebook, Inc.
 *
 * 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 com.facebook.buck.features.project.intellij;

import com.facebook.buck.core.description.arg.CommonDescriptionArg;
import com.facebook.buck.core.model.BuildTarget;
import com.facebook.buck.core.model.targetgraph.TargetGraph;
import com.facebook.buck.core.model.targetgraph.TargetNode;
import com.facebook.buck.features.project.intellij.aggregation.AggregationModule;
import com.facebook.buck.features.project.intellij.aggregation.AggregationModuleFactory;
import com.facebook.buck.features.project.intellij.aggregation.AggregationTree;
import com.facebook.buck.features.project.intellij.model.DependencyType;
import com.facebook.buck.features.project.intellij.model.IjLibrary;
import com.facebook.buck.features.project.intellij.model.IjLibraryFactory;
import com.facebook.buck.features.project.intellij.model.IjModule;
import com.facebook.buck.features.project.intellij.model.IjModuleFactory;
import com.facebook.buck.features.project.intellij.model.IjModuleType;
import com.facebook.buck.features.project.intellij.model.IjProjectConfig;
import com.facebook.buck.features.project.intellij.model.IjProjectElement;
import com.facebook.buck.io.filesystem.ProjectFilesystem;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

public final class IjModuleGraphFactory {
    /**
     * Create all the modules we are capable of representing in IntelliJ from the supplied graph.
     *
     * @param targetGraph graph whose nodes will be converted to {@link IjModule}s.
     * @return map which for every BuildTarget points to the corresponding IjModule. Multiple
     *     BuildTarget can point to one IjModule (many:one mapping), the BuildTargetPaths which can't
     *     be prepresented in IntelliJ are missing from this mapping.
     */
    private static ImmutableMap<BuildTarget, IjModule> createModules(ProjectFilesystem projectFilesystem,
            IjProjectConfig projectConfig, TargetGraph targetGraph, IjModuleFactory moduleFactory,
            AggregationModuleFactory aggregationModuleFactory, int minimumPathDepth,
            ImmutableSet<String> ignoredTargetLabels) {

        Stream<TargetNode<?>> nodes = targetGraph.getNodes().stream().filter(
                input -> SupportedTargetTypeRegistry.isTargetTypeSupported(input.getDescription().getClass()))
                .filter(targetNode -> {
                    CommonDescriptionArg arg = (CommonDescriptionArg) targetNode.getConstructorArg();
                    return !arg.labelsContainsAnyOf(ignoredTargetLabels);
                })
                // Experimental support for generating modules outside the project root
                .filter(targetNode -> projectConfig.isMultiCellModuleSupportEnabled()
                        || isInRootCell(projectFilesystem, targetNode));

        ImmutableListMultimap<Path, TargetNode<?>> baseTargetPathMultimap = (projectConfig.getProjectRoot()
                .isEmpty()
                        ? nodes
                        : nodes.filter(
                                targetNode -> shouldConvertToIjModule(projectConfig.getProjectRoot(), targetNode)))
                                        .collect(
                                                ImmutableListMultimap
                                                        .toImmutableListMultimap(
                                                                targetNode -> projectFilesystem
                                                                        .relativize(targetNode.getFilesystem()
                                                                                .resolve(targetNode.getBuildTarget()
                                                                                        .getBasePath())),
                                                                targetNode -> targetNode));

        AggregationTree aggregationTree = createAggregationTree(projectConfig, aggregationModuleFactory,
                baseTargetPathMultimap);

        aggregationTree.aggregateModules(minimumPathDepth);

        ImmutableMap.Builder<BuildTarget, IjModule> moduleByBuildTarget = new ImmutableMap.Builder<>();

        aggregationTree.getModules().parallelStream()
                .filter(aggregationModule -> !aggregationModule.getTargets().isEmpty())
                .forEach(aggregationModule -> {
                    IjModule module = moduleFactory.createModule(aggregationModule.getModuleBasePath(),
                            aggregationModule.getTargets(), aggregationModule.getExcludes());
                    synchronized (moduleByBuildTarget) {
                        module.getTargets().forEach(buildTarget -> moduleByBuildTarget.put(buildTarget, module));
                    }
                });

        return moduleByBuildTarget.build();
    }

    private static boolean shouldConvertToIjModule(String projectRoot, TargetNode<?> targetNode) {
        return targetNode.getBuildTarget().getBasePath().startsWith(projectRoot);
    }

    private static AggregationTree createAggregationTree(IjProjectConfig projectConfig,
            AggregationModuleFactory aggregationModuleFactory,
            ImmutableListMultimap<Path, TargetNode<?>> targetNodesByBasePath) {
        Map<Path, AggregationModule> pathToAggregationModuleMap = targetNodesByBasePath.asMap().entrySet().stream()
                .collect(
                        ImmutableMap.toImmutableMap(Map.Entry::getKey,
                                pathWithTargetNode -> aggregationModuleFactory.createAggregationModule(
                                        pathWithTargetNode.getKey(),
                                        ImmutableSet.copyOf(pathWithTargetNode.getValue()))));

        Path rootPath = Paths.get("");

        AggregationModule rootAggregationModule = pathToAggregationModuleMap.get(rootPath);
        if (rootAggregationModule == null) {
            rootAggregationModule = aggregationModuleFactory.createAggregationModule(rootPath, ImmutableSet.of());
        }

        AggregationTree aggregationTree = new AggregationTree(projectConfig, rootAggregationModule);

        pathToAggregationModuleMap.entrySet().stream().filter(e -> !rootPath.equals(e.getKey()))
                .forEach(e -> aggregationTree.addModule(e.getKey(), e.getValue()));

        return aggregationTree;
    }

    private static ImmutableSet<IjProjectElement> getProjectElementFromBuildTargets(TargetGraph targetGraph,
            IjLibraryFactory libraryFactory, ImmutableMap<BuildTarget, IjModule> rulesToModules, IjModule module,
            Stream<BuildTarget> buildTargetStream) {
        return buildTargetStream.filter(input -> {
            // The exported deps closure can contain references back to targets contained
            // in the module, so filter those out.
            return !module.getTargets().contains(input);
        }).flatMap(depTarget -> {
            List<IjProjectElement> elements = new ArrayList<>();
            IjModule depModule = rulesToModules.get(depTarget);
            if (depModule != null) {
                elements.add(depModule);
            }
            if (depModule == null || depModule.getNonSourceBuildTargets().contains(depTarget)) {
                // all BuildTarget's are merged into IJModule
                // if a BuildTarget is not built from Java sources, it will also be added as a
                // library
                TargetNode<?> targetNode = targetGraph.get(depTarget);
                elements.add(libraryFactory.getLibrary(targetNode).orElse(null));
            }
            return elements.stream();
        }).filter(Objects::nonNull).collect(ImmutableSet.toImmutableSet());
    }

    /**
     * @param projectConfig the project config used
     * @param targetGraph input graph.
     * @param libraryFactory library factory.
     * @param moduleFactory module factory.
     * @return module graph corresponding to the supplied {@link TargetGraph}. Multiple targets from
     *     the same base path are mapped to a single module, therefore an IjModuleGraph edge exists
     *     between two modules (Ma, Mb) if a TargetGraph edge existed between a pair of nodes (Ta, Tb)
     *     and Ma contains Ta and Mb contains Tb.
     */
    public static IjModuleGraph from(ProjectFilesystem projectFilesystem, IjProjectConfig projectConfig,
            TargetGraph targetGraph, IjLibraryFactory libraryFactory, IjModuleFactory moduleFactory,
            AggregationModuleFactory aggregationModuleFactory) {
        ImmutableSet<String> ignoredTargetLabels = projectConfig.getIgnoredTargetLabels();
        ImmutableMap<BuildTarget, IjModule> rulesToModules = createModules(projectFilesystem, projectConfig,
                targetGraph, moduleFactory, aggregationModuleFactory,
                projectConfig.getAggregationMode().getGraphMinimumDepth(targetGraph.getNodes().size()),
                ignoredTargetLabels);
        ExportedDepsClosureResolver exportedDepsClosureResolver = new ExportedDepsClosureResolver(targetGraph,
                ignoredTargetLabels);
        TransitiveDepsClosureResolver transitiveDepsClosureResolver = new TransitiveDepsClosureResolver(targetGraph,
                ignoredTargetLabels);
        ImmutableMap.Builder<IjProjectElement, ImmutableMap<IjProjectElement, DependencyType>> depsBuilder = ImmutableMap
                .builder();
        Set<IjLibrary> referencedLibraries = new HashSet<>();
        Optional<Path> extraCompileOutputRootPath = projectConfig.getExtraCompilerOutputModulesPath();

        for (IjModule module : ImmutableSet.copyOf(rulesToModules.values())) {
            Map<IjProjectElement, DependencyType> moduleDeps = new LinkedHashMap<>();

            if (!module.getExtraClassPathDependencies().isEmpty()) {
                IjLibrary extraClassPathLibrary = IjLibrary.builder()
                        .setBinaryJars(module.getExtraClassPathDependencies()).setTargets(ImmutableSet.of())
                        .setName("library_" + module.getName() + "_extra_classpath").build();
                moduleDeps.put(extraClassPathLibrary, DependencyType.PROD);
            }

            if (extraCompileOutputRootPath.isPresent() && !module.getExtraModuleDependencies().isEmpty()) {
                IjModule extraModule = createExtraModuleForCompilerOutput(module, extraCompileOutputRootPath.get());
                moduleDeps.put(extraModule, DependencyType.PROD);
                depsBuilder.put(extraModule, ImmutableMap.of());
            }

            for (Map.Entry<BuildTarget, DependencyType> entry : module.getDependencies().entrySet()) {
                BuildTarget depBuildTarget = entry.getKey();
                TargetNode<?> depTargetNode = targetGraph.get(depBuildTarget);

                CommonDescriptionArg arg = (CommonDescriptionArg) depTargetNode.getConstructorArg();
                if (arg.labelsContainsAnyOf(ignoredTargetLabels)) {
                    continue;
                }

                DependencyType depType = entry.getValue();
                ImmutableSet<IjProjectElement> depElements;
                ImmutableSet<IjProjectElement> transitiveDepElements = ImmutableSet.of();

                if (depType.equals(DependencyType.COMPILED_SHADOW)) {
                    Optional<IjLibrary> library = libraryFactory.getLibrary(depTargetNode);
                    if (library.isPresent()) {
                        depElements = ImmutableSet.of(library.get());
                    } else {
                        depElements = ImmutableSet.of();
                    }
                } else {
                    depElements = getProjectElementFromBuildTargets(targetGraph, libraryFactory, rulesToModules,
                            module,
                            Stream.concat(
                                    exportedDepsClosureResolver.getExportedDepsClosure(depBuildTarget).stream(),
                                    Stream.of(depBuildTarget)));
                    if (projectConfig.isIncludeTransitiveDependency()) {
                        transitiveDepElements = getProjectElementFromBuildTargets(targetGraph, libraryFactory,
                                rulesToModules, module,
                                Stream.concat(transitiveDepsClosureResolver.getTransitiveDepsClosure(depBuildTarget)
                                        .stream(), Stream.of(depBuildTarget)));
                    }
                }

                for (IjProjectElement depElement : transitiveDepElements) {
                    Preconditions.checkState(!depElement.equals(module));
                    DependencyType.putWithMerge(moduleDeps, depElement, DependencyType.RUNTIME);
                }
                for (IjProjectElement depElement : depElements) {
                    Preconditions.checkState(!depElement.equals(module));
                    DependencyType.putWithMerge(moduleDeps, depElement, depType);
                }
            }

            moduleDeps.keySet().stream().filter(dep -> dep instanceof IjLibrary).map(library -> (IjLibrary) library)
                    .forEach(referencedLibraries::add);

            depsBuilder.put(module, ImmutableMap.copyOf(moduleDeps));
        }

        referencedLibraries.forEach(library -> depsBuilder.put(library, ImmutableMap.of()));

        return new IjModuleGraph(depsBuilder.build());
    }

    private static IjModule createExtraModuleForCompilerOutput(IjModule module, Path extraCompileOutputRootPath) {
        return IjModule.builder().setModuleBasePath(extraCompileOutputRootPath.resolve(module.getModuleBasePath()))
                .setTargets(ImmutableSet.of()).addAllFolders(ImmutableSet.of())
                .putAllDependencies(ImmutableMap.of()).setLanguageLevel(module.getLanguageLevel())
                .setModuleType(IjModuleType.ANDROID_MODULE)
                .setCompilerOutputPath(module.getExtraModuleDependencies().asList().get(0)).build();
    }

    private static boolean isInRootCell(ProjectFilesystem projectFilesystem, TargetNode<?> targetNode) {
        return targetNode.getFilesystem().equals(projectFilesystem);
    }

    private IjModuleGraphFactory() {
    }
}