Java tutorial
/* * 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() { } }