Java tutorial
/* * Copyright 2013-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.apple.project; import com.dd.plist.NSDictionary; import com.dd.plist.NSObject; import com.dd.plist.NSString; import com.dd.plist.PropertyListParser; import com.facebook.buck.apple.AppleAssetCatalogDescriptionArg; import com.facebook.buck.apple.AppleBinaryDescription; import com.facebook.buck.apple.AppleBinaryDescriptionArg; import com.facebook.buck.apple.AppleBuildRules; import com.facebook.buck.apple.AppleBuildRules.RecursiveDependenciesMode; import com.facebook.buck.apple.AppleBundle; import com.facebook.buck.apple.AppleBundleDescription; import com.facebook.buck.apple.AppleBundleDescriptionArg; import com.facebook.buck.apple.AppleBundleExtension; import com.facebook.buck.apple.AppleConfig; import com.facebook.buck.apple.AppleDependenciesCache; import com.facebook.buck.apple.AppleDescriptions; import com.facebook.buck.apple.AppleHeaderVisibilities; import com.facebook.buck.apple.AppleLibraryDescription; import com.facebook.buck.apple.AppleLibraryDescriptionArg; import com.facebook.buck.apple.AppleNativeTargetDescriptionArg; import com.facebook.buck.apple.AppleResourceDescription; import com.facebook.buck.apple.AppleResourceDescriptionArg; import com.facebook.buck.apple.AppleResources; import com.facebook.buck.apple.AppleTestDescription; import com.facebook.buck.apple.AppleTestDescriptionArg; import com.facebook.buck.apple.AppleWrapperResourceArg; import com.facebook.buck.apple.CoreDataModelDescription; import com.facebook.buck.apple.HasAppleBundleFields; import com.facebook.buck.apple.InfoPlistSubstitution; import com.facebook.buck.apple.PrebuiltAppleFrameworkDescription; import com.facebook.buck.apple.PrebuiltAppleFrameworkDescriptionArg; import com.facebook.buck.apple.SceneKitAssetsDescription; import com.facebook.buck.apple.XCodeDescriptions; import com.facebook.buck.apple.XcodePostbuildScriptDescription; import com.facebook.buck.apple.XcodePrebuildScriptDescription; import com.facebook.buck.apple.clang.HeaderMap; import com.facebook.buck.apple.clang.ModuleMap; import com.facebook.buck.apple.clang.UmbrellaHeader; import com.facebook.buck.apple.clang.VFSOverlay; import com.facebook.buck.apple.xcode.GidGenerator; import com.facebook.buck.apple.xcode.XcodeprojSerializer; import com.facebook.buck.apple.xcode.xcodeproj.CopyFilePhaseDestinationSpec; import com.facebook.buck.apple.xcode.xcodeproj.PBXBuildFile; import com.facebook.buck.apple.xcode.xcodeproj.PBXBuildPhase; import com.facebook.buck.apple.xcode.xcodeproj.PBXContainerItemProxy; import com.facebook.buck.apple.xcode.xcodeproj.PBXCopyFilesBuildPhase; import com.facebook.buck.apple.xcode.xcodeproj.PBXFileReference; import com.facebook.buck.apple.xcode.xcodeproj.PBXGroup; import com.facebook.buck.apple.xcode.xcodeproj.PBXNativeTarget; import com.facebook.buck.apple.xcode.xcodeproj.PBXProject; import com.facebook.buck.apple.xcode.xcodeproj.PBXReference; import com.facebook.buck.apple.xcode.xcodeproj.PBXShellScriptBuildPhase; import com.facebook.buck.apple.xcode.xcodeproj.PBXTarget; import com.facebook.buck.apple.xcode.xcodeproj.PBXTargetDependency; import com.facebook.buck.apple.xcode.xcodeproj.ProductType; import com.facebook.buck.apple.xcode.xcodeproj.ProductTypes; import com.facebook.buck.apple.xcode.xcodeproj.SourceTreePath; import com.facebook.buck.apple.xcode.xcodeproj.XCBuildConfiguration; import com.facebook.buck.apple.xcode.xcodeproj.XCVersionGroup; import com.facebook.buck.core.cell.Cell; import com.facebook.buck.core.cell.CellPathResolver; import com.facebook.buck.core.description.BaseDescription; import com.facebook.buck.core.description.arg.HasTests; import com.facebook.buck.core.exceptions.HumanReadableException; import com.facebook.buck.core.macros.MacroException; import com.facebook.buck.core.model.BuildTarget; import com.facebook.buck.core.model.Flavor; import com.facebook.buck.core.model.UnflavoredBuildTarget; import com.facebook.buck.core.model.impl.BuildTargetPaths; import com.facebook.buck.core.model.targetgraph.DescriptionWithTargetGraph; import com.facebook.buck.core.model.targetgraph.NoSuchTargetException; import com.facebook.buck.core.model.targetgraph.TargetGraph; import com.facebook.buck.core.model.targetgraph.TargetNode; import com.facebook.buck.core.model.targetgraph.impl.TargetGraphAndTargets; import com.facebook.buck.core.model.targetgraph.impl.TargetNodes; import com.facebook.buck.core.rules.ActionGraphBuilder; import com.facebook.buck.core.rules.BuildRule; import com.facebook.buck.core.rules.BuildRuleResolver; import com.facebook.buck.core.rules.SourcePathRuleFinder; import com.facebook.buck.core.rules.resolver.impl.SingleThreadedActionGraphBuilder; import com.facebook.buck.core.rules.transformer.impl.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.core.sourcepath.BuildTargetSourcePath; import com.facebook.buck.core.sourcepath.PathSourcePath; import com.facebook.buck.core.sourcepath.SourcePath; import com.facebook.buck.core.sourcepath.SourceWithFlags; import com.facebook.buck.core.sourcepath.resolver.SourcePathResolver; import com.facebook.buck.core.sourcepath.resolver.impl.DefaultSourcePathResolver; import com.facebook.buck.core.util.graph.AcyclicDepthFirstPostOrderTraversal; import com.facebook.buck.core.util.graph.GraphTraversable; import com.facebook.buck.core.util.log.Logger; import com.facebook.buck.cxx.CxxCompilationDatabase; import com.facebook.buck.cxx.CxxDescriptionEnhancer; import com.facebook.buck.cxx.CxxLibraryDescription; import com.facebook.buck.cxx.CxxLibraryDescription.CommonArg; import com.facebook.buck.cxx.CxxPrecompiledHeaderTemplate; import com.facebook.buck.cxx.CxxPreprocessables; import com.facebook.buck.cxx.CxxSource; import com.facebook.buck.cxx.PrebuiltCxxLibraryDescription; import com.facebook.buck.cxx.PrebuiltCxxLibraryDescriptionArg; import com.facebook.buck.cxx.toolchain.CxxBuckConfig; import com.facebook.buck.cxx.toolchain.CxxPlatform; import com.facebook.buck.cxx.toolchain.HasSystemFrameworkAndLibraries; import com.facebook.buck.cxx.toolchain.HeaderVisibility; import com.facebook.buck.cxx.toolchain.nativelink.NativeLinkable; import com.facebook.buck.cxx.toolchain.nativelink.NativeLinkable.Linkage; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.event.PerfEventId; import com.facebook.buck.event.ProjectGenerationEvent; import com.facebook.buck.event.SimplePerfEvent; import com.facebook.buck.features.halide.HalideBuckConfig; import com.facebook.buck.features.halide.HalideCompile; import com.facebook.buck.features.halide.HalideLibraryDescription; import com.facebook.buck.features.halide.HalideLibraryDescriptionArg; import com.facebook.buck.features.js.JsBundleOutputsDescription; import com.facebook.buck.io.MoreProjectFilesystems; import com.facebook.buck.io.file.MorePaths; import com.facebook.buck.io.filesystem.ProjectFilesystem; import com.facebook.buck.rules.args.Arg; import com.facebook.buck.rules.args.StringArg; import com.facebook.buck.rules.coercer.FrameworkPath; import com.facebook.buck.rules.coercer.PatternMatchedCollection; import com.facebook.buck.rules.coercer.SourceSortedSet; import com.facebook.buck.rules.keys.config.RuleKeyConfiguration; import com.facebook.buck.rules.macros.LocationMacro; import com.facebook.buck.rules.macros.LocationMacroExpander; import com.facebook.buck.rules.macros.MacroContainer; import com.facebook.buck.rules.macros.StringWithMacros; import com.facebook.buck.rules.macros.StringWithMacrosConverter; import com.facebook.buck.shell.AbstractGenruleDescription; import com.facebook.buck.shell.ExportFileDescriptionArg; import com.facebook.buck.swift.SwiftBuckConfig; import com.facebook.buck.swift.SwiftCommonArg; import com.facebook.buck.swift.SwiftLibraryDescriptionArg; import com.facebook.buck.util.Escaper; import com.facebook.buck.util.MoreMaps; import com.facebook.buck.util.RichStream; import com.facebook.buck.util.types.Either; import com.facebook.buck.util.types.Pair; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.collect.Streams; import com.google.common.hash.HashCode; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import com.google.common.io.BaseEncoding; import com.google.common.util.concurrent.UncheckedExecutionException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.nio.file.FileVisitResult; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; /** Generator for xcode project and associated files from a set of xcode/ios rules. */ public class ProjectGenerator { private static final Logger LOG = Logger.get(ProjectGenerator.class); private static final ImmutableList<String> DEFAULT_CFLAGS = ImmutableList.of(); private static final ImmutableList<String> DEFAULT_CXXFLAGS = ImmutableList.of(); private static final ImmutableList<String> DEFAULT_CPPFLAGS = ImmutableList.of(); private static final ImmutableList<String> DEFAULT_CXXPPFLAGS = ImmutableList.of(); private static final ImmutableList<String> DEFAULT_LDFLAGS = ImmutableList.of(); private static final ImmutableList<String> DEFAULT_SWIFTFLAGS = ImmutableList.of(); private static final String PRODUCT_NAME = "PRODUCT_NAME"; private static final ImmutableSet<Class<? extends DescriptionWithTargetGraph<?>>> APPLE_NATIVE_DESCRIPTION_CLASSES = ImmutableSet .of(AppleBinaryDescription.class, AppleLibraryDescription.class, CxxLibraryDescription.class); private static final ImmutableSet<AppleBundleExtension> APPLE_NATIVE_BUNDLE_EXTENSIONS = ImmutableSet .of(AppleBundleExtension.APP, AppleBundleExtension.FRAMEWORK); private static final FileAttribute<?> READ_ONLY_FILE_ATTRIBUTE = PosixFilePermissions .asFileAttribute(ImmutableSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.GROUP_READ, PosixFilePermission.OTHERS_READ)); private final XCodeDescriptions xcodeDescriptions; private final TargetGraph targetGraph; private final AppleDependenciesCache dependenciesCache; private final ProjectGenerationStateCache projGenerationStateCache; private final Cell projectCell; private final ProjectFilesystem projectFilesystem; private final Path outputDirectory; private final String projectName; private final ImmutableSet<BuildTarget> initialTargets; private final Path projectPath; private final PathRelativizer pathRelativizer; private final String buildFileName; private final ProjectGeneratorOptions options; private final CxxPlatform defaultCxxPlatform; // These fields are created/filled when creating the projects. private final PBXProject project; private final LoadingCache<TargetNode<?>, Optional<PBXTarget>> targetNodeToProjectTarget; private final ImmutableMultimap.Builder<TargetNode<?>, PBXTarget> targetNodeToGeneratedProjectTargetBuilder; private boolean projectGenerated; private final List<Path> headerSymlinkTrees; private final ImmutableSet.Builder<BuildTarget> requiredBuildTargetsBuilder = ImmutableSet.builder(); private final Function<? super TargetNode<?>, ActionGraphBuilder> actionGraphBuilderForNode; private final SourcePathResolver defaultPathResolver; private final BuckEventBus buckEventBus; private final RuleKeyConfiguration ruleKeyConfiguration; /** * Populated while generating project configurations, in order to collect the possible * project-level configurations to set. */ private final ImmutableSet.Builder<String> targetConfigNamesBuilder; private final GidGenerator gidGenerator; private final ImmutableSet<Flavor> appleCxxFlavors; private final HalideBuckConfig halideBuckConfig; private final CxxBuckConfig cxxBuckConfig; private final SwiftBuckConfig swiftBuckConfig; private final AppleConfig appleConfig; private final FocusedModuleTargetMatcher focusModules; private final boolean isMainProject; private final Optional<BuildTarget> workspaceTarget; private final ImmutableSet<BuildTarget> targetsInRequiredProjects; private final ImmutableSet.Builder<Path> xcconfigPathsBuilder = ImmutableSet.builder(); private final ImmutableList.Builder<CopyInXcode> filesToCopyInXcodeBuilder = ImmutableList.builder(); private final ImmutableList.Builder<SourcePath> genruleFiles = ImmutableList.builder(); private final ImmutableSet.Builder<SourcePath> filesAddedBuilder = ImmutableSet.builder(); private final Set<BuildTarget> generatedTargets = new HashSet<>(); /** * Mapping from an apple_library target to the associated apple_bundle which names it as its * 'binary' */ private final Optional<ImmutableMap<BuildTarget, TargetNode<?>>> sharedLibraryToBundle; public ProjectGenerator(XCodeDescriptions xcodeDescriptions, TargetGraph targetGraph, AppleDependenciesCache dependenciesCache, ProjectGenerationStateCache projGenerationStateCache, Set<BuildTarget> initialTargets, Cell cell, Path outputDirectory, String projectName, String buildFileName, ProjectGeneratorOptions options, RuleKeyConfiguration ruleKeyConfiguration, boolean isMainProject, Optional<BuildTarget> workspaceTarget, ImmutableSet<BuildTarget> targetsInRequiredProjects, FocusedModuleTargetMatcher focusModules, CxxPlatform defaultCxxPlatform, ImmutableSet<Flavor> appleCxxFlavors, Function<? super TargetNode<?>, ActionGraphBuilder> actionGraphBuilderForNode, BuckEventBus buckEventBus, HalideBuckConfig halideBuckConfig, CxxBuckConfig cxxBuckConfig, AppleConfig appleConfig, SwiftBuckConfig swiftBuckConfig, Optional<ImmutableMap<BuildTarget, TargetNode<?>>> sharedLibraryToBundle) { this.xcodeDescriptions = xcodeDescriptions; this.targetGraph = targetGraph; this.dependenciesCache = dependenciesCache; this.projGenerationStateCache = projGenerationStateCache; this.initialTargets = ImmutableSet.copyOf(initialTargets); this.projectCell = cell; this.projectFilesystem = cell.getFilesystem(); this.outputDirectory = outputDirectory; this.projectName = projectName; this.buildFileName = buildFileName; this.options = options; this.ruleKeyConfiguration = ruleKeyConfiguration; this.isMainProject = isMainProject; this.workspaceTarget = workspaceTarget; this.targetsInRequiredProjects = targetsInRequiredProjects; this.defaultCxxPlatform = defaultCxxPlatform; this.appleCxxFlavors = appleCxxFlavors; this.actionGraphBuilderForNode = actionGraphBuilderForNode; this.defaultPathResolver = DefaultSourcePathResolver .from(new SourcePathRuleFinder(new SingleThreadedActionGraphBuilder(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer(), cell.getCellProvider()))); this.buckEventBus = buckEventBus; this.projectPath = outputDirectory.resolve(projectName + ".xcodeproj"); this.pathRelativizer = new PathRelativizer(outputDirectory, this::resolveSourcePath); this.sharedLibraryToBundle = sharedLibraryToBundle; LOG.debug("Output directory %s, profile fs root path %s, repo root relative to output dir %s", this.outputDirectory, projectFilesystem.getRootPath(), this.pathRelativizer.outputDirToRootRelative(Paths.get("."))); this.project = new PBXProject(projectName); this.headerSymlinkTrees = new ArrayList<>(); this.targetNodeToGeneratedProjectTargetBuilder = ImmutableMultimap.builder(); this.targetNodeToProjectTarget = CacheBuilder.newBuilder() .build(new CacheLoader<TargetNode<?>, Optional<PBXTarget>>() { @Override public Optional<PBXTarget> load(TargetNode<?> key) throws Exception { return generateProjectTarget(key); } }); targetConfigNamesBuilder = ImmutableSet.builder(); this.halideBuckConfig = halideBuckConfig; this.cxxBuckConfig = cxxBuckConfig; this.appleConfig = appleConfig; this.swiftBuckConfig = swiftBuckConfig; this.focusModules = focusModules; gidGenerator = new GidGenerator(); } @VisibleForTesting PBXProject getGeneratedProject() { return project; } @VisibleForTesting List<Path> getGeneratedHeaderSymlinkTrees() { return headerSymlinkTrees; } public Path getProjectPath() { return projectPath; } private boolean shouldMergeHeaderMaps() { return options.shouldMergeHeaderMaps() && workspaceTarget.isPresent() && options.shouldUseHeaderMaps(); } public ImmutableMap<BuildTarget, PBXTarget> getBuildTargetToGeneratedTargetMap() { Preconditions.checkState(projectGenerated, "Must have called createXcodeProjects"); ImmutableMap.Builder<BuildTarget, PBXTarget> buildTargetToPbxTargetMap = ImmutableMap.builder(); for (Map.Entry<TargetNode<?>, PBXTarget> entry : targetNodeToGeneratedProjectTargetBuilder.build() .entries()) { buildTargetToPbxTargetMap.put(entry.getKey().getBuildTarget(), entry.getValue()); } return buildTargetToPbxTargetMap.build(); } /** @return Map from the generated project to all contained targets. */ public ImmutableSetMultimap<PBXProject, PBXTarget> getGeneratedProjectToGeneratedTargets() { Preconditions.checkState(projectGenerated, "Must have called createXcodeProjects"); ImmutableSetMultimap.Builder<PBXProject, PBXTarget> generatedProjectToPbxTargetsBuilder = ImmutableSetMultimap .builder(); generatedProjectToPbxTargetsBuilder.putAll(getGeneratedProject(), targetNodeToGeneratedProjectTargetBuilder.build().values()); return generatedProjectToPbxTargetsBuilder.build(); } public ImmutableSet<BuildTarget> getRequiredBuildTargets() { Preconditions.checkState(projectGenerated, "Must have called createXcodeProjects"); return requiredBuildTargetsBuilder.build(); } // Returns a set of generated xcconfig files. public ImmutableSet<Path> getXcconfigPaths() { return xcconfigPathsBuilder.build(); } // Returns the list of infos about the files that we need to copy during Xcode build. public ImmutableList<CopyInXcode> getFilesToCopyInXcode() { return filesToCopyInXcodeBuilder.build(); } // Returns true if we ran the project generation and we decided to eventually generate // the project. public boolean isProjectGenerated() { return projectGenerated; } // Loads the target into the project cache and updates the internal generated project map. private void triggerLoadingCache(TargetNode<?> targetNode) { Optional<PBXTarget> target = targetNodeToProjectTarget.getUnchecked(targetNode); target.ifPresent(pbxTarget -> targetNodeToGeneratedProjectTargetBuilder.put(targetNode, pbxTarget)); } public void createXcodeProjects() throws IOException { LOG.debug("Creating projects for targets %s", initialTargets); boolean hasAtLeastOneTarget = false; try (SimplePerfEvent.Scope scope = SimplePerfEvent.scope(buckEventBus, PerfEventId.of("xcode_project_generation"), ImmutableMap.of("Path", getProjectPath()))) { // Filter out nodes that aren't included in project. ImmutableSet.Builder<TargetNode<?>> projectTargetsBuilder = ImmutableSet.builder(); for (TargetNode<?> targetNode : targetGraph.getNodes()) { if (isBuiltByCurrentProject(targetNode.getBuildTarget())) { LOG.debug("Including rule %s in project", targetNode); projectTargetsBuilder.add(targetNode); if (focusModules.isFocusedOn(targetNode.getBuildTarget())) { // If the target is not included, we still need to do other operations to generate // the required header maps. hasAtLeastOneTarget = true; } } else { LOG.verbose("Excluding rule %s (not built by current project)", targetNode); } } final ImmutableSet<TargetNode<?>> projectTargets = projectTargetsBuilder.build(); // Trigger the loading cache for the workspace target if it's in the project. This ensures the // workspace target isn't filtered later by loading it first. final Optional<TargetNode<?>> workspaceTargetNode = workspaceTarget .map(target -> targetGraph.get(target)); workspaceTargetNode.filter(targetNode -> projectTargets.contains(targetNode)) .ifPresent(targetNode -> triggerLoadingCache(targetNode)); // Trigger the loading cache to call the generateProjectTarget function on all other targets. // Exclude the workspace target since it's loaded above. RichStream.from(projectTargets) .filter(input -> !workspaceTargetNode.isPresent() || !input.equals(workspaceTargetNode.get())) .forEach(input -> triggerLoadingCache(input)); addGenruleFiles(); if (!hasAtLeastOneTarget && focusModules.hasFocus()) { return; } if (shouldMergeHeaderMaps() && isMainProject) { createMergedHeaderMap(); } for (String configName : targetConfigNamesBuilder.build()) { XCBuildConfiguration outputConfig = project.getBuildConfigurationList() .getBuildConfigurationsByName().getUnchecked(configName); outputConfig.setBuildSettings(new NSDictionary()); } if (!options.shouldGenerateHeaderSymlinkTreesOnly()) { writeProjectFile(project); } projectGenerated = true; } catch (UncheckedExecutionException e) { // if any code throws an exception, they tend to get wrapped in LoadingCache's // UncheckedExecutionException. Unwrap it if its cause is HumanReadable. UncheckedExecutionException originalException = e; while (e.getCause() instanceof UncheckedExecutionException) { e = (UncheckedExecutionException) e.getCause(); } if (e.getCause() instanceof HumanReadableException) { throw (HumanReadableException) e.getCause(); } else { throw originalException; } } } /** Add all source files of genrules in a group "Other". */ private void addGenruleFiles() { ImmutableSet<SourcePath> filesAdded = filesAddedBuilder.build(); PBXGroup group = project.getMainGroup(); ImmutableList<SourcePath> files = genruleFiles.build(); if (files.size() > 0) { PBXGroup otherGroup = group.getOrCreateChildGroupByName("Other"); for (SourcePath sourcePath : files) { // Make sure we don't add duplicates of existing files in this section. if (filesAdded.contains(sourcePath)) { continue; } Path path = pathRelativizer.outputPathToSourcePath(sourcePath); ImmutableList<String> targetGroupPath = null; PBXGroup sourceGroup = otherGroup; if (path.getParent() != null) { targetGroupPath = RichStream.from(path.getParent()).map(Object::toString).toImmutableList(); sourceGroup = otherGroup.getOrCreateDescendantGroupByPath(targetGroupPath); } sourceGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, path, Optional.empty())); } } } @SuppressWarnings("unchecked") private Optional<PBXTarget> generateProjectTarget(TargetNode<?> targetNode) throws IOException { Preconditions.checkState(isBuiltByCurrentProject(targetNode.getBuildTarget()), "should not generate rule if it shouldn't be built by current project"); if (shouldExcludeLibraryFromProject(targetNode)) { return Optional.empty(); } // Ignore certain flavors when considering a target previously generated: // AppleCxx - Differing platforms should be generated as one target. // static - Static is the default. This avoids duplication when `static` is passed directly. BuildTarget targetWithoutAppleCxxFlavors = targetNode.getBuildTarget().withoutFlavors(appleCxxFlavors); BuildTarget targetWithoutSpecificFlavors = targetWithoutAppleCxxFlavors .withoutFlavors(CxxDescriptionEnhancer.STATIC_FLAVOR); if (generatedTargets.contains(targetWithoutSpecificFlavors)) { return Optional.empty(); } generatedTargets.add(targetWithoutSpecificFlavors); Optional<PBXTarget> result = Optional.empty(); if (targetNode.getDescription() instanceof AppleLibraryDescription) { result = Optional.of(generateAppleLibraryTarget(project, (TargetNode<AppleNativeTargetDescriptionArg>) targetNode, Optional.empty())); } else if (targetNode.getDescription() instanceof CxxLibraryDescription) { result = Optional .of(generateCxxLibraryTarget(project, (TargetNode<CxxLibraryDescription.CommonArg>) targetNode, ImmutableSet.of(), ImmutableSet.of(), Optional.empty())); } else if (targetNode.getDescription() instanceof AppleBinaryDescription) { result = Optional.of( generateAppleBinaryTarget(project, (TargetNode<AppleNativeTargetDescriptionArg>) targetNode)); } else if (targetNode.getDescription() instanceof AppleBundleDescription) { TargetNode<AppleBundleDescriptionArg> bundleTargetNode = (TargetNode<AppleBundleDescriptionArg>) targetNode; result = Optional.of(generateAppleBundleTarget(project, bundleTargetNode, (TargetNode<AppleNativeTargetDescriptionArg>) targetGraph .get(getBundleBinaryTarget(bundleTargetNode)), Optional.empty())); } else if (targetNode.getDescription() instanceof AppleTestDescription) { result = Optional.of(generateAppleTestTarget((TargetNode<AppleTestDescriptionArg>) targetNode)); } else if (targetNode.getDescription() instanceof AppleResourceDescription) { checkAppleResourceTargetNodeReferencingValidContents( (TargetNode<AppleResourceDescriptionArg>) targetNode); } else if (targetNode.getDescription() instanceof HalideLibraryDescription) { TargetNode<HalideLibraryDescriptionArg> halideTargetNode = (TargetNode<HalideLibraryDescriptionArg>) targetNode; BuildTarget buildTarget = targetNode.getBuildTarget(); // The generated target just runs a shell script that invokes the "compiler" with the // correct target architecture. result = generateHalideLibraryTarget(project, halideTargetNode); // Make sure the compiler gets built at project time, since we'll need // it to generate the shader code during the Xcode build. requiredBuildTargetsBuilder.add(HalideLibraryDescription.createHalideCompilerBuildTarget(buildTarget)); // HACK: Don't generate the Halide headers unless the compiler is expected // to generate output for the default platform -- a Halide library that // uses a platform regex may not be able to use the default platform. // This assumes that there's a 'default' variant of the rule to generate // headers from. if (HalideLibraryDescription.isPlatformSupported(halideTargetNode.getConstructorArg(), defaultCxxPlatform)) { // Run the compiler once at project time to generate the header // file needed for compilation if the Halide target is for the default // platform. requiredBuildTargetsBuilder.add(buildTarget.withFlavors( HalideLibraryDescription.HALIDE_COMPILE_FLAVOR, defaultCxxPlatform.getFlavor())); } } else if (targetNode.getDescription() instanceof AbstractGenruleDescription) { TargetNode<AbstractGenruleDescription.CommonArg> genruleNode = (TargetNode<AbstractGenruleDescription.CommonArg>) targetNode; genruleFiles.addAll(genruleNode.getConstructorArg().getSrcs().getPaths()); } buckEventBus.post(ProjectGenerationEvent.processed()); return result; } private static Path getHalideOutputPath(ProjectFilesystem filesystem, BuildTarget target) { return filesystem.getBuckPaths().getConfiguredBuckOut().resolve("halide").resolve(target.getBasePath()) .resolve(target.getShortName()); } private Optional<PBXTarget> generateHalideLibraryTarget(PBXProject project, TargetNode<HalideLibraryDescriptionArg> targetNode) throws IOException { BuildTarget buildTarget = targetNode.getBuildTarget(); boolean isFocusedOnTarget = focusModules.isFocusedOn(buildTarget); String productName = getProductNameForBuildTargetNode(targetNode); Path outputPath = getHalideOutputPath(targetNode.getFilesystem(), buildTarget); Path scriptPath = halideBuckConfig.getXcodeCompileScriptPath(); Optional<String> script = projectFilesystem.readFileIfItExists(scriptPath); PBXShellScriptBuildPhase scriptPhase = new PBXShellScriptBuildPhase(); scriptPhase.setShellScript(script.orElse("")); NewNativeTargetProjectMutator mutator = new NewNativeTargetProjectMutator(pathRelativizer, this::resolveSourcePath); mutator.setTargetName(getXcodeTargetName(buildTarget)) .setProduct(ProductTypes.STATIC_LIBRARY, productName, outputPath) .setPreBuildRunScriptPhases(ImmutableList.of(scriptPhase)); NewNativeTargetProjectMutator.Result targetBuilderResult; targetBuilderResult = mutator.buildTargetAndAddToProject(project, isFocusedOnTarget); BuildTarget compilerTarget = HalideLibraryDescription.createHalideCompilerBuildTarget(buildTarget); Path compilerPath = BuildTargetPaths.getGenPath(projectFilesystem, compilerTarget, "%s"); ImmutableMap<String, String> appendedConfig = ImmutableMap.of(); ImmutableMap<String, String> extraSettings = ImmutableMap.of(); ImmutableMap.Builder<String, String> defaultSettingsBuilder = ImmutableMap.builder(); defaultSettingsBuilder.put("REPO_ROOT", projectFilesystem.getRootPath().toAbsolutePath().normalize().toString()); defaultSettingsBuilder.put("HALIDE_COMPILER_PATH", compilerPath.toString()); // pass the source list to the xcode script String halideCompilerSrcs; Iterable<Path> compilerSrcFiles = Iterables.transform(targetNode.getConstructorArg().getSrcs(), input -> resolveSourcePath(input.getSourcePath())); halideCompilerSrcs = Joiner.on(" ").join(compilerSrcFiles); defaultSettingsBuilder.put("HALIDE_COMPILER_SRCS", halideCompilerSrcs); String halideCompilerFlags; halideCompilerFlags = Joiner.on(" ").join(targetNode.getConstructorArg().getCompilerFlags()); defaultSettingsBuilder.put("HALIDE_COMPILER_FLAGS", halideCompilerFlags); defaultSettingsBuilder.put("HALIDE_OUTPUT_PATH", outputPath.toString()); defaultSettingsBuilder.put("HALIDE_FUNC_NAME", buildTarget.getShortName()); defaultSettingsBuilder.put(PRODUCT_NAME, productName); Optional<ImmutableSortedMap<String, ImmutableMap<String, String>>> configs = getXcodeBuildConfigurationsForTargetNode( targetNode); PBXNativeTarget target = targetBuilderResult.target; setTargetBuildConfigurations(buildTarget, target, project.getMainGroup(), configs.get(), getTargetCxxBuildConfigurationForTargetNode(targetNode, appendedConfig), extraSettings, defaultSettingsBuilder.build(), appendedConfig); return Optional.of(target); } private PBXTarget generateAppleTestTarget(TargetNode<AppleTestDescriptionArg> testTargetNode) throws IOException { AppleTestDescriptionArg args = testTargetNode.getConstructorArg(); Optional<BuildTarget> testTargetApp = extractTestTargetForTestDescriptionArg(args); Optional<TargetNode<AppleBundleDescriptionArg>> testHostBundle = testTargetApp.map(testHostBundleTarget -> { TargetNode<?> testHostBundleNode = targetGraph.get(testHostBundleTarget); return TargetNodes.castArg(testHostBundleNode, AppleBundleDescriptionArg.class).orElseGet(() -> { throw new HumanReadableException( "The test host target '%s' has the wrong type (%s), must be apple_bundle", testHostBundleTarget, testHostBundleNode.getDescription().getClass()); }); }); return generateAppleBundleTarget(project, testTargetNode, testTargetNode, testHostBundle); } private Optional<BuildTarget> extractTestTargetForTestDescriptionArg(AppleTestDescriptionArg args) { if (args.getUiTestTargetApp().isPresent()) { return args.getUiTestTargetApp(); } return args.getTestHostApp(); } private void checkAppleResourceTargetNodeReferencingValidContents( TargetNode<AppleResourceDescriptionArg> resource) { // Check that the resource target node is referencing valid files or directories. // If a SourcePath is a BuildTargetSourcePath (or some hypothetical future implementation of // SourcePath), just assume it's the right type; we have no way of checking now as it // may not exist yet. AppleResourceDescriptionArg arg = resource.getConstructorArg(); for (SourcePath dir : arg.getDirs()) { if (dir instanceof PathSourcePath && !projectFilesystem.isDirectory(resolveSourcePath(dir))) { throw new HumanReadableException("%s specified in the dirs parameter of %s is not a directory", dir.toString(), resource.toString()); } } for (SourcePath file : arg.getFiles()) { if (file instanceof PathSourcePath && !projectFilesystem.isFile(resolveSourcePath(file))) { throw new HumanReadableException("%s specified in the files parameter of %s is not a regular file", file.toString(), resource.toString()); } } } private PBXNativeTarget generateAppleBundleTarget(PBXProject project, TargetNode<? extends HasAppleBundleFields> targetNode, TargetNode<? extends AppleNativeTargetDescriptionArg> binaryNode, Optional<TargetNode<AppleBundleDescriptionArg>> bundleLoaderNode) throws IOException { Path infoPlistPath = Objects .requireNonNull(resolveSourcePath(targetNode.getConstructorArg().getInfoPlist())); // -- copy any binary and bundle targets into this bundle Iterable<TargetNode<?>> copiedRules = AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes( xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), appleConfig.shouldIncludeSharedLibraryResources() ? RecursiveDependenciesMode.COPYING_INCLUDE_SHARED_RESOURCES : AppleBuildRules.RecursiveDependenciesMode.COPYING, targetNode, Optional.of(xcodeDescriptions.getXCodeDescriptions())); if (bundleRequiresRemovalOfAllTransitiveFrameworks(targetNode)) { copiedRules = rulesWithoutFrameworkBundles(copiedRules); } else if (bundleRequiresAllTransitiveFrameworks(binaryNode, bundleLoaderNode)) { copiedRules = ImmutableSet.<TargetNode<?>>builder().addAll(copiedRules) .addAll(getTransitiveFrameworkNodes(targetNode)).build(); } if (bundleLoaderNode.isPresent()) { copiedRules = rulesWithoutBundleLoader(copiedRules, bundleLoaderNode.get()); } ImmutableList<PBXBuildPhase> copyFilesBuildPhases = getCopyFilesBuildPhases(copiedRules); RecursiveDependenciesMode mode = appleConfig.shouldIncludeSharedLibraryResources() ? RecursiveDependenciesMode.COPYING_INCLUDE_SHARED_RESOURCES : RecursiveDependenciesMode.COPYING; PBXNativeTarget target = generateBinaryTarget(project, Optional.of(targetNode), binaryNode, bundleToTargetProductType(targetNode, binaryNode), "%s." + getExtensionString(targetNode.getConstructorArg().getExtension()), Optional.of(infoPlistPath), /* includeFrameworks */ true, AppleResources.collectRecursiveResources(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), targetNode, mode), AppleResources.collectDirectResources(targetGraph, targetNode), AppleBuildRules.collectRecursiveAssetCatalogs(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), ImmutableList.of(targetNode), mode), AppleBuildRules.collectDirectAssetCatalogs(targetGraph, targetNode), AppleBuildRules.collectRecursiveWrapperResources(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), ImmutableList.of(targetNode), mode), Optional.of(copyFilesBuildPhases), bundleLoaderNode); if (bundleLoaderNode.isPresent()) { LOG.debug("Generated iOS bundle target %s with binarynode: %s bundleLoadernode: %s", targetNode.getBuildTarget().getFullyQualifiedName(), binaryNode.getBuildTarget().getFullyQualifiedName(), bundleLoaderNode.get().getBuildTarget().getFullyQualifiedName()); } else { LOG.debug("Generated iOS bundle target %s with binarynode: %s and without bundleloader", targetNode.getBuildTarget().getFullyQualifiedName(), binaryNode.getBuildTarget().getFullyQualifiedName()); } return target; } /** * Traverses the graph to find all (non-system) frameworks that should be embedded into the * target's bundle. */ private ImmutableSet<TargetNode<?>> getTransitiveFrameworkNodes( TargetNode<? extends HasAppleBundleFields> targetNode) { GraphTraversable<TargetNode<?>> graphTraversable = node -> { if (!(node.getDescription() instanceof AppleResourceDescription)) { Set<BuildTarget> buildDeps = node.getBuildDeps(); if (node.getDescription() instanceof AppleBundleDescription) { AppleBundleDescriptionArg arg = (AppleBundleDescriptionArg) node.getConstructorArg(); // TODO: handle platform binaries in addition to regular binaries if (arg.getBinary().isPresent()) { buildDeps = Sets.union(buildDeps, ImmutableSet.of(arg.getBinary().get())); } } return targetGraph.getAll(buildDeps).iterator(); } else { return Collections.emptyIterator(); } }; ImmutableSet.Builder<TargetNode<?>> filteredRules = ImmutableSet.builder(); AcyclicDepthFirstPostOrderTraversal<TargetNode<?>> traversal = new AcyclicDepthFirstPostOrderTraversal<>( graphTraversable); try { for (TargetNode<?> node : traversal.traverse(ImmutableList.of(targetNode))) { if (node != targetNode) { TargetNodes.castArg(node, AppleBundleDescriptionArg.class).ifPresent(appleBundleNode -> { if (isFrameworkBundle(appleBundleNode.getConstructorArg())) { filteredRules.add(node); } }); TargetNodes.castArg(node, PrebuiltAppleFrameworkDescriptionArg.class) .ifPresent(prebuiltFramework -> { // Technically (see Apple Tech Notes 2435), static frameworks are lies. In case // a static framework is used, they can escape the incorrect project generation // by marking its preferred linkage static (what does preferred linkage even // mean for a prebuilt thing? none of this makes sense anyways). if (prebuiltFramework.getConstructorArg() .getPreferredLinkage() != NativeLinkable.Linkage.STATIC) { filteredRules.add(node); } }); } } } catch (AcyclicDepthFirstPostOrderTraversal.CycleException e) { throw new RuntimeException(e); } return filteredRules.build(); } /** Returns a new list of rules which does not contain framework bundles. */ private ImmutableList<TargetNode<?>> rulesWithoutFrameworkBundles(Iterable<TargetNode<?>> copiedRules) { return RichStream.from(copiedRules) .filter(input -> TargetNodes.castArg(input, AppleBundleDescriptionArg.class) .map(argTargetNode -> !isFrameworkBundle(argTargetNode.getConstructorArg())).orElse(true)) .toImmutableList(); } private ImmutableList<TargetNode<?>> rulesWithoutBundleLoader(Iterable<TargetNode<?>> copiedRules, TargetNode<?> bundleLoader) { return RichStream.from(copiedRules).filter(x -> !bundleLoader.equals(x)).toImmutableList(); } private PBXNativeTarget generateAppleBinaryTarget(PBXProject project, TargetNode<AppleNativeTargetDescriptionArg> targetNode) throws IOException { PBXNativeTarget target = generateBinaryTarget(project, Optional.empty(), targetNode, ProductTypes.TOOL, "%s", Optional.empty(), /* includeFrameworks */ true, ImmutableSet.of(), AppleResources.collectDirectResources(targetGraph, targetNode), ImmutableSet.of(), AppleBuildRules.collectDirectAssetCatalogs(targetGraph, targetNode), ImmutableSet.of(), Optional.empty(), Optional.empty()); LOG.debug("Generated Apple binary target %s", targetNode.getBuildTarget().getFullyQualifiedName()); return target; } private PBXNativeTarget generateAppleLibraryTarget(PBXProject project, TargetNode<? extends AppleNativeTargetDescriptionArg> targetNode, Optional<TargetNode<AppleBundleDescriptionArg>> bundleLoaderNode) throws IOException { PBXNativeTarget target = generateCxxLibraryTarget(project, targetNode, AppleResources.collectDirectResources(targetGraph, targetNode), AppleBuildRules.collectDirectAssetCatalogs(targetGraph, targetNode), bundleLoaderNode); LOG.debug("Generated iOS library target %s", targetNode.getBuildTarget().getFullyQualifiedName()); return target; } private PBXNativeTarget generateCxxLibraryTarget(PBXProject project, TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, ImmutableSet<AppleResourceDescriptionArg> directResources, ImmutableSet<AppleAssetCatalogDescriptionArg> directAssetCatalogs, Optional<TargetNode<AppleBundleDescriptionArg>> bundleLoaderNode) throws IOException { boolean isShared = targetNode.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR); ProductType productType = isShared ? ProductTypes.DYNAMIC_LIBRARY : ProductTypes.STATIC_LIBRARY; PBXNativeTarget target = generateBinaryTarget(project, Optional.empty(), targetNode, productType, AppleBuildRules.getOutputFileNameFormatForLibrary(isShared), Optional.empty(), /* includeFrameworks */ isShared, ImmutableSet.of(), directResources, ImmutableSet.of(), directAssetCatalogs, ImmutableSet.of(), Optional.empty(), bundleLoaderNode); LOG.debug("Generated Cxx library target %s", targetNode.getBuildTarget().getFullyQualifiedName()); return target; } private ImmutableList<String> convertStringWithMacros(TargetNode<?> node, Iterable<StringWithMacros> flags) { // TODO(cjhopman): This seems really broken, it's totally inconsistent about what graphBuilder // is // provided. This should either just do rule resolution like normal or maybe do its own custom // MacroReplacer<>. LocationMacroExpander locationMacroExpander = new LocationMacroExpander() { @Override public Arg expandFrom(BuildTarget target, CellPathResolver cellNames, ActionGraphBuilder graphBuilder, LocationMacro input) throws MacroException { BuildTarget locationMacroTarget = input.getTarget(); ActionGraphBuilder builderFromNode = actionGraphBuilderForNode .apply(targetGraph.get(locationMacroTarget)); try { builderFromNode.requireRule(locationMacroTarget); } catch (NoSuchTargetException e) { throw new MacroException( String.format("couldn't find rule referenced by location macro: %s", e.getMessage()), e); } requiredBuildTargetsBuilder.add(locationMacroTarget); return StringArg.of(Arg.stringify(super.expandFrom(target, cellNames, builderFromNode, input), DefaultSourcePathResolver.from(new SourcePathRuleFinder(builderFromNode)))); } }; ActionGraphBuilder emptyGraphBuilder = new SingleThreadedActionGraphBuilder(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer(), projectCell.getCellProvider()); ImmutableList.Builder<String> result = new ImmutableList.Builder<>(); StringWithMacrosConverter macrosConverter = StringWithMacrosConverter.of(node.getBuildTarget(), node.getCellNames(), ImmutableList.of(locationMacroExpander)); for (StringWithMacros flag : flags) { macrosConverter.convert(flag, emptyGraphBuilder).appendToCommandLine(result::add, defaultPathResolver); } return result.build(); } private ImmutableMultimap<String, ImmutableList<String>> convertPlatformFlags(TargetNode<?> node, Iterable<PatternMatchedCollection<ImmutableList<StringWithMacros>>> matchers) { ImmutableMultimap.Builder<String, ImmutableList<String>> flagsBuilder = ImmutableMultimap.builder(); for (PatternMatchedCollection<ImmutableList<StringWithMacros>> matcher : matchers) { for (Flavor flavor : appleCxxFlavors) { String platform = flavor.toString(); for (ImmutableList<StringWithMacros> flags : matcher.getMatchingValues(platform)) { flagsBuilder.put(platform, convertStringWithMacros(node, flags)); } } } return flagsBuilder.build(); } private String generateConfigKey(String key, String platform) { int index = platform.lastIndexOf('-'); String sdk = platform.substring(0, index); String arch = platform.substring(index + 1); return String.format("%s[sdk=%s*][arch=%s]", key, sdk, arch); } private Optional<String> getSwiftVersionForTargetNode(TargetNode<?> targetNode) { Optional<TargetNode<SwiftCommonArg>> targetNodeWithSwiftArgs = TargetNodes.castArg(targetNode, SwiftCommonArg.class); Optional<String> targetExplicitSwiftVersion = targetNodeWithSwiftArgs .flatMap(t -> t.getConstructorArg().getSwiftVersion()); if (!targetExplicitSwiftVersion.isPresent() && (targetNode.getDescription() instanceof AppleLibraryDescription || targetNode.getDescription() instanceof AppleBinaryDescription || targetNode.getDescription() instanceof AppleTestDescription)) { return swiftBuckConfig.getVersion(); } return targetExplicitSwiftVersion; } private static String sourceNameRelativeToOutput(SourcePath source, SourcePathResolver pathResolver, Path outputDirectory) { Path pathRelativeToCell = pathResolver.getRelativePath(source); Path pathRelativeToOutput = outputDirectory.relativize(pathRelativeToCell); return pathRelativeToOutput.toString(); } private static void appendPlatformSourceToAllPlatformSourcesAndSourcesByPlatform(Set<String> allPlatformSources, Map<String, Set<String>> platformSourcesByPlatform, String platformName, String sourceName) { allPlatformSources.add(sourceName); if (platformSourcesByPlatform.get(platformName) != null) { platformSourcesByPlatform.get(platformName).add(sourceName); } } @VisibleForTesting static ImmutableMap<String, ImmutableSortedSet<String>> gatherExcludedSources( ImmutableSet<String> applePlatforms, ImmutableList<Pair<Pattern, ImmutableSortedSet<SourceWithFlags>>> platformSources, ImmutableList<Pair<Pattern, Iterable<SourcePath>>> platformHeaders, Path outputDirectory, SourcePathResolver pathResolver) { Set<String> allPlatformSpecificSources = new HashSet<>(); Map<String, Set<String>> includedSourcesByPlatform = new HashMap<>(); for (Pair<Pattern, ImmutableSortedSet<SourceWithFlags>> platformSource : platformSources) { String platformName = platformSource.getFirst().toString(); includedSourcesByPlatform.putIfAbsent(platformName, new HashSet<>()); for (SourceWithFlags source : platformSource.getSecond()) { appendPlatformSourceToAllPlatformSourcesAndSourcesByPlatform(allPlatformSpecificSources, includedSourcesByPlatform, platformName, sourceNameRelativeToOutput(source.getSourcePath(), pathResolver, outputDirectory)); } } for (Pair<Pattern, Iterable<SourcePath>> platformHeader : platformHeaders) { String platformName = platformHeader.getFirst().toString(); includedSourcesByPlatform.putIfAbsent(platformName, new HashSet<>()); for (SourcePath source : platformHeader.getSecond()) { appendPlatformSourceToAllPlatformSourcesAndSourcesByPlatform(allPlatformSpecificSources, includedSourcesByPlatform, platformName, sourceNameRelativeToOutput(source, pathResolver, outputDirectory)); } } Map<String, SortedSet<String>> result = new HashMap<>(); result.put("EXCLUDED_SOURCE_FILE_NAMES", ImmutableSortedSet .copyOf(allPlatformSpecificSources.stream().map(s -> "'" + s + "'").collect(Collectors.toSet()))); // We need to convert the regex to a glob that Xcode will recognize so we match the regex // against the name of a known sdk with the matcher, then glob that. for (String platformMatcher : includedSourcesByPlatform.keySet()) { for (String flavor : applePlatforms) { Pattern pattern = Pattern.compile(platformMatcher); Matcher matcher = pattern.matcher(flavor); if (matcher.lookingAt()) { String key = "INCLUDED_SOURCE_FILE_NAMES[sdk=" + flavor + "*]"; Set<String> sourcesMatchingPlatform = includedSourcesByPlatform.get(platformMatcher); if (sourcesMatchingPlatform != null) { Set<String> quotedSources = sourcesMatchingPlatform.stream().map(s -> "'" + s + "'") .collect(Collectors.toSet()); // They may have different matchers for similar things in which case the key will // already // be included if (result.get(key) != null) { result.get(key).addAll(quotedSources); } else { result.put("INCLUDED_SOURCE_FILE_NAMES[sdk=" + flavor + "*]", new TreeSet<>(quotedSources)); } } } } } ImmutableMap.Builder<String, ImmutableSortedSet<String>> finalResultBuilder = ImmutableMap.builder(); for (Map.Entry<String, SortedSet<String>> entry : result.entrySet()) { finalResultBuilder.put(entry.getKey(), ImmutableSortedSet.copyOf(entry.getValue())); } return finalResultBuilder.build(); } @VisibleForTesting static Pair<String, String> applePlatformAndArchitecture(Flavor platformFlavor) { String platformName = platformFlavor.getName(); int index = platformName.lastIndexOf('-'); String sdk = platformName.substring(0, index); String sdkWithoutVersion = sdk.split("\\d+")[0]; String arch = platformName.substring(index + 1); return new Pair<>(sdkWithoutVersion, arch); } /** @return a map of all exported platform headers without matching a specific platform. */ public static ImmutableMap<Path, SourcePath> parseAllPlatformHeaders(BuildTarget buildTarget, SourcePathResolver sourcePathResolver, ImmutableList<SourceSortedSet> platformHeaders, boolean export, CxxLibraryDescription.CommonArg args) { ImmutableMap.Builder<String, SourcePath> parsed = ImmutableMap.builder(); String parameterName = (export) ? "exported_platform_headers" : "platform_headers"; // Include all platform specific headers. for (SourceSortedSet sourceList : platformHeaders) { parsed.putAll(sourceList.toNameMap(buildTarget, sourcePathResolver, parameterName, path -> true, path -> path)); } return CxxPreprocessables.resolveHeaderMap( args.getHeaderNamespace().map(Paths::get).orElse(buildTarget.getBasePath()), parsed.build()); } private PBXNativeTarget generateBinaryTarget(PBXProject project, Optional<? extends TargetNode<? extends HasAppleBundleFields>> bundle, TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, ProductType productType, String productOutputFormat, Optional<Path> infoPlistOptional, boolean includeFrameworks, ImmutableSet<AppleResourceDescriptionArg> recursiveResources, ImmutableSet<AppleResourceDescriptionArg> directResources, ImmutableSet<AppleAssetCatalogDescriptionArg> recursiveAssetCatalogs, ImmutableSet<AppleAssetCatalogDescriptionArg> directAssetCatalogs, ImmutableSet<AppleWrapperResourceArg> wrapperResources, Optional<Iterable<PBXBuildPhase>> copyFilesPhases, Optional<TargetNode<AppleBundleDescriptionArg>> bundleLoaderNode) throws IOException { LOG.debug("Generating binary target for node %s", targetNode); TargetNode<?> buildTargetNode = bundle.isPresent() ? bundle.get() : targetNode; BuildTarget buildTarget = buildTargetNode.getBuildTarget(); boolean containsSwiftCode = projGenerationStateCache.targetContainsSwiftSourceCode(targetNode); String buildTargetName = getProductNameForBuildTargetNode(buildTargetNode); CxxLibraryDescription.CommonArg arg = targetNode.getConstructorArg(); NewNativeTargetProjectMutator mutator = new NewNativeTargetProjectMutator(pathRelativizer, this::resolveSourcePath); // Both exported headers and exported platform headers will be put into the symlink tree // exported platform headers will be excluded and then included by platform ImmutableSet.Builder<SourcePath> exportedHeadersBuilder = ImmutableSet.builder(); exportedHeadersBuilder.addAll(getHeaderSourcePaths(arg.getExportedHeaders())); PatternMatchedCollection<SourceSortedSet> exportedPlatformHeaders = arg.getExportedPlatformHeaders(); for (SourceSortedSet headersSet : exportedPlatformHeaders.getValues()) { exportedHeadersBuilder.addAll(getHeaderSourcePaths(headersSet)); } ImmutableSet<SourcePath> exportedHeaders = exportedHeadersBuilder.build(); ImmutableSet.Builder<SourcePath> headersBuilder = ImmutableSet.builder(); headersBuilder.addAll(getHeaderSourcePaths(arg.getHeaders())); for (SourceSortedSet headersSet : arg.getPlatformHeaders().getValues()) { headersBuilder.addAll(getHeaderSourcePaths(headersSet)); } ImmutableSet<SourcePath> headers = headersBuilder.build(); ImmutableMap<CxxSource.Type, ImmutableList<StringWithMacros>> langPreprocessorFlags = targetNode .getConstructorArg().getLangPreprocessorFlags(); boolean isFocusedOnTarget = focusModules.isFocusedOn(buildTarget); Optional<String> swiftVersion = getSwiftVersionForTargetNode(targetNode); boolean hasSwiftVersionArg = swiftVersion.isPresent(); if (!swiftVersion.isPresent()) { swiftVersion = swiftBuckConfig.getVersion(); } mutator.setTargetName(getXcodeTargetName(buildTarget)).setProduct(productType, buildTargetName, Paths.get(String.format(productOutputFormat, buildTargetName))); boolean isModularAppleLibrary = isModularAppleLibrary(targetNode) && isFocusedOnTarget; mutator.setFrameworkHeadersEnabled(isModularAppleLibrary); ImmutableMap.Builder<String, String> swiftDepsSettingsBuilder = ImmutableMap.builder(); ImmutableList.Builder<String> swiftDebugLinkerFlagsBuilder = ImmutableList.builder(); ImmutableMap.Builder<String, String> extraSettingsBuilder = ImmutableMap.builder(); ImmutableMap.Builder<String, String> defaultSettingsBuilder = ImmutableMap.builder(); ImmutableList<Pair<Pattern, SourceSortedSet>> platformHeaders = arg.getPlatformHeaders() .getPatternsAndValues(); ImmutableList.Builder<Pair<Pattern, Iterable<SourcePath>>> platformHeadersIterableBuilder = ImmutableList .builder(); for (Pair<Pattern, SourceSortedSet> platformHeader : platformHeaders) { platformHeadersIterableBuilder .add(new Pair<>(platformHeader.getFirst(), getHeaderSourcePaths(platformHeader.getSecond()))); } ImmutableList<Pair<Pattern, SourceSortedSet>> exportedPlatformHeadersPatternsAndValues = exportedPlatformHeaders .getPatternsAndValues(); for (Pair<Pattern, SourceSortedSet> exportedPlatformHeader : exportedPlatformHeadersPatternsAndValues) { platformHeadersIterableBuilder.add(new Pair<>(exportedPlatformHeader.getFirst(), getHeaderSourcePaths(exportedPlatformHeader.getSecond()))); } ImmutableList<Pair<Pattern, Iterable<SourcePath>>> platformHeadersIterable = platformHeadersIterableBuilder .build(); ImmutableList<Pair<Pattern, ImmutableSortedSet<SourceWithFlags>>> platformSources = arg.getPlatformSrcs() .getPatternsAndValues(); ImmutableMap<String, ImmutableSortedSet<String>> platformExcludedSourcesMapping = ProjectGenerator .gatherExcludedSources( appleCxxFlavors.stream().map(f -> applePlatformAndArchitecture(f).getFirst()) .collect(ImmutableSet.toImmutableSet()), platformSources, platformHeadersIterable, outputDirectory, defaultPathResolver); for (Map.Entry<String, ImmutableSortedSet<String>> platformExcludedSources : platformExcludedSourcesMapping .entrySet()) { if (platformExcludedSources.getValue().size() > 0) { extraSettingsBuilder.put(platformExcludedSources.getKey(), String.join(" ", platformExcludedSources.getValue())); } } ImmutableSortedSet<SourceWithFlags> nonPlatformSrcs = arg.getSrcs(); ImmutableSortedSet.Builder<SourceWithFlags> allSrcsBuilder = ImmutableSortedSet.naturalOrder(); allSrcsBuilder.addAll(nonPlatformSrcs); for (Pair<Pattern, ImmutableSortedSet<SourceWithFlags>> platformSource : platformSources) { allSrcsBuilder.addAll(platformSource.getSecond()); } ImmutableSortedSet<SourceWithFlags> allSrcs = allSrcsBuilder.build(); if (!options.shouldGenerateHeaderSymlinkTreesOnly()) { if (isFocusedOnTarget) { filesAddedBuilder.addAll( allSrcs.stream().map(s -> s.getSourcePath()).collect(ImmutableList.toImmutableList())); mutator.setLangPreprocessorFlags(ImmutableMap.copyOf( Maps.transformValues(langPreprocessorFlags, f -> convertStringWithMacros(targetNode, f)))) .setPublicHeaders(exportedHeaders).setPrefixHeader(getPrefixHeaderSourcePath(arg)) .setSourcesWithFlags(ImmutableSet.copyOf(allSrcs)).setPrivateHeaders(headers) .setRecursiveResources(recursiveResources).setDirectResources(directResources) .setWrapperResources(wrapperResources) .setExtraXcodeSources(ImmutableSet.copyOf(arg.getExtraXcodeSources())) .setExtraXcodeFiles(ImmutableSet.copyOf(arg.getExtraXcodeFiles())); } if (bundle.isPresent() && isFocusedOnTarget) { HasAppleBundleFields bundleArg = bundle.get().getConstructorArg(); mutator.setInfoPlist(Optional.of(bundleArg.getInfoPlist())); } mutator.setBridgingHeader(arg.getBridgingHeader()); if (options.shouldCreateDirectoryStructure() && isFocusedOnTarget) { mutator.setTargetGroupPath( RichStream.from(buildTarget.getBasePath()).map(Object::toString).toImmutableList()); } if (!recursiveAssetCatalogs.isEmpty() && isFocusedOnTarget) { mutator.setRecursiveAssetCatalogs(recursiveAssetCatalogs); } if (!directAssetCatalogs.isEmpty() && isFocusedOnTarget) { mutator.setDirectAssetCatalogs(directAssetCatalogs); } FluentIterable<TargetNode<?>> depTargetNodes = collectRecursiveLibraryDepTargets(targetNode); if (includeFrameworks && isFocusedOnTarget) { if (!options.shouldAddLinkedLibrariesAsFlags()) { mutator.setFrameworks(getSytemFrameworksLibsForTargetNode(targetNode)); } if (sharedLibraryToBundle.isPresent()) { // Replace target nodes of libraries which are actually constituents of embedded // frameworks to the bundle representing the embedded framework. // This will be converted to a reference to the xcode build product for the embedded // framework rather than the dylib depTargetNodes = swapSharedLibrariesForBundles(depTargetNodes, sharedLibraryToBundle.get()); } ImmutableSet<PBXFileReference> targetNodeDeps = filterRecursiveLibraryDependenciesForLinkerPhase( depTargetNodes); if (isTargetNodeApplicationTestTarget(targetNode, bundleLoaderNode)) { ImmutableSet<PBXFileReference> bundleLoaderDeps = bundleLoaderNode.isPresent() ? collectRecursiveLibraryDependencies(bundleLoaderNode.get()) : ImmutableSet.of(); mutator.setArchives(Sets.difference(targetNodeDeps, bundleLoaderDeps)); } else { mutator.setArchives(targetNodeDeps); } } if (isFocusedOnTarget) { ImmutableSet<TargetNode<?>> swiftDepTargets = filterRecursiveLibraryDepTargetsWithSwiftSources( depTargetNodes); if (!includeFrameworks && !swiftDepTargets.isEmpty()) { // If the current target, which is non-shared (e.g., static lib), depends on other focused // targets which include Swift code, we must ensure those are treated as dependencies so // that Xcode builds the targets in the correct order. Unfortunately, those deps can be // part of other projects which would require cross-project references. // // Thankfully, there's an easy workaround because we can just create a phony copy phase // which depends on the outputs of the deps (i.e., the static libs). The copy phase // will effectively say "Copy libX.a from Products Dir into Products Dir" which is a nop. // To be on the safe side, we're explicitly marking the copy phase as only running for // deployment postprocessing (i.e., "Copy only when installing") and disabling // deployment postprocessing (it's enabled by default for release builds). CopyFilePhaseDestinationSpec.Builder destSpecBuilder = CopyFilePhaseDestinationSpec.builder(); destSpecBuilder.setDestination(PBXCopyFilesBuildPhase.Destination.PRODUCTS); PBXCopyFilesBuildPhase copyFiles = new PBXCopyFilesBuildPhase(destSpecBuilder.build()); copyFiles.setRunOnlyForDeploymentPostprocessing(Optional.of(Boolean.TRUE)); copyFiles.setName(Optional.of("Fake Swift Dependencies (Copy Files Phase)")); ImmutableSet<PBXFileReference> swiftDepsFileRefs = targetNodesSetToPBXFileReference( swiftDepTargets); for (PBXFileReference fileRef : swiftDepsFileRefs) { PBXBuildFile buildFile = new PBXBuildFile(fileRef); copyFiles.getFiles().add(buildFile); } swiftDepsSettingsBuilder.put("DEPLOYMENT_POSTPROCESSING", "NO"); mutator.setSwiftDependenciesBuildPhase(copyFiles); } if (includeFrameworks && !swiftDepTargets.isEmpty() && shouldEmbedSwiftRuntimeInBundleTarget(bundle) && swiftBuckConfig.getProjectEmbedRuntime()) { // This is a binary that transitively depends on a library that uses Swift. We must ensure // that the Swift runtime is bundled. swiftDepsSettingsBuilder.put("ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES"); } if (includeFrameworks && !swiftDepTargets.isEmpty() && swiftBuckConfig.getProjectAddASTPaths()) { for (TargetNode<?> swiftNode : swiftDepTargets) { String swiftModulePath = String.format( "${BUILT_PRODUCTS_DIR}/%s.swiftmodule/${CURRENT_ARCH}.swiftmodule", getModuleName(swiftNode)); swiftDebugLinkerFlagsBuilder.add("-Xlinker"); swiftDebugLinkerFlagsBuilder.add("-add_ast_path"); swiftDebugLinkerFlagsBuilder.add("-Xlinker"); swiftDebugLinkerFlagsBuilder.add(swiftModulePath); } } } // TODO(Task #3772930): Go through all dependencies of the rule // and add any shell script rules here ImmutableList.Builder<TargetNode<?>> preScriptPhasesBuilder = ImmutableList.builder(); ImmutableList.Builder<TargetNode<?>> postScriptPhasesBuilder = ImmutableList.builder(); if (bundle.isPresent() && targetNode != bundle.get() && isFocusedOnTarget) { collectBuildScriptDependencies(targetGraph.getAll(bundle.get().getDeclaredDeps()), preScriptPhasesBuilder, postScriptPhasesBuilder); } collectBuildScriptDependencies(targetGraph.getAll(targetNode.getDeclaredDeps()), preScriptPhasesBuilder, postScriptPhasesBuilder); if (isFocusedOnTarget) { ImmutableList<TargetNode<?>> preScriptPhases = preScriptPhasesBuilder.build(); ImmutableList<TargetNode<?>> postScriptPhases = postScriptPhasesBuilder.build(); mutator.setPreBuildRunScriptPhasesFromTargetNodes(preScriptPhases, actionGraphBuilderForNode::apply); if (copyFilesPhases.isPresent()) { mutator.setCopyFilesPhases(copyFilesPhases.get()); } mutator.setPostBuildRunScriptPhasesFromTargetNodes(postScriptPhases, actionGraphBuilderForNode::apply); ImmutableList<TargetNode<?>> scriptPhases = Stream .concat(preScriptPhases.stream(), postScriptPhases.stream()) .collect(ImmutableList.toImmutableList()); mutator.collectFilesToCopyInXcode(filesToCopyInXcodeBuilder, scriptPhases, projectCell, actionGraphBuilderForNode::apply); } } NewNativeTargetProjectMutator.Result targetBuilderResult = mutator.buildTargetAndAddToProject(project, isFocusedOnTarget); PBXNativeTarget target = targetBuilderResult.target; Optional<PBXGroup> targetGroup = targetBuilderResult.targetGroup; extraSettingsBuilder.putAll(swiftDepsSettingsBuilder.build()); setAppIconSettings(recursiveAssetCatalogs, directAssetCatalogs, buildTarget, defaultSettingsBuilder); setLaunchImageSettings(recursiveAssetCatalogs, directAssetCatalogs, buildTarget, defaultSettingsBuilder); ImmutableSortedMap<Path, SourcePath> publicCxxHeaders = getPublicCxxHeaders(targetNode); if (isModularAppleLibrary(targetNode) && isFrameworkProductType(productType)) { // Modular frameworks should not include Buck-generated hmaps as they break the VFS overlay // that's generated by Xcode and consequently, all headers part of a framework's umbrella // header fail the modularity test, as they're expected to be mapped by the VFS layer under // $BUILT_PRODUCTS_DIR/Module.framework/Versions/A/Headers. publicCxxHeaders = ImmutableSortedMap.of(); } if (!options.shouldGenerateHeaderSymlinkTreesOnly()) { if (isFocusedOnTarget) { SourceTreePath buckFilePath = new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputPathToBuildTargetPath(buildTarget).resolve(buildFileName), Optional.empty()); PBXFileReference buckReference = targetGroup.get() .getOrCreateFileReferenceBySourceTreePath(buckFilePath); buckReference.setExplicitFileType(Optional.of("text.script.python")); } // Watch dependencies need to have explicit target dependencies setup in order for Xcode to // build them properly within the IDE. It is unable to match the implicit dependency because // of the different in flavor between the targets (iphoneos vs watchos). if (bundle.isPresent() && isFocusedOnTarget) { collectProjectTargetWatchDependencies(targetNode.getBuildTarget().getFlavorPostfix(), target, targetGraph.getAll(bundle.get().getExtraDeps())); } // -- configurations extraSettingsBuilder.put("TARGET_NAME", buildTargetName).put("SRCROOT", pathRelativizer.outputPathToBuildTargetPath(buildTarget).toString()); if (productType == ProductTypes.UI_TEST && isFocusedOnTarget) { if (bundleLoaderNode.isPresent()) { BuildTarget testTarget = bundleLoaderNode.get().getBuildTarget(); extraSettingsBuilder.put("TEST_TARGET_NAME", getXcodeTargetName(testTarget)); addPBXTargetDependency(target, testTarget); } else { throw new HumanReadableException( "The test rule '%s' is configured with 'is_ui_test' but has no test_host_app", buildTargetName); } } else if (bundleLoaderNode.isPresent() && isFocusedOnTarget) { TargetNode<AppleBundleDescriptionArg> bundleLoader = bundleLoaderNode.get(); String bundleLoaderProductName = getProductName(bundleLoader); String bundleLoaderBundleName = bundleLoaderProductName + "." + getExtensionString(bundleLoader.getConstructorArg().getExtension()); // NOTE(grp): This is a hack. We need to support both deep (OS X) and flat (iOS) // style bundles for the bundle loader, but at this point we don't know what platform // the bundle loader (or current target) is going to be built for. However, we can be // sure that it's the same as the target (presumably a test) we're building right now. // // Using that knowledge, we can do build setting tricks to defer choosing the bundle // loader path until Xcode build time, when the platform is known. There's no build // setting that conclusively says whether the current platform uses deep bundles: // that would be too easy. But in the cases we care about (unit test bundles), the // current bundle will have a style matching the style of the bundle loader app, so // we can take advantage of that to do the determination. // // Unfortunately, the build setting for the bundle structure (CONTENTS_FOLDER_PATH) // includes the WRAPPER_NAME, so we can't just interpolate that in. Instead, we have // to use another trick with build setting operations and evaluation. By using the // $(:file) operation, we can extract the last component of the contents path: either // "Contents" or the current bundle name. Then, we can interpolate with that expected // result in the build setting name to conditionally choose a different loader path. // The conditional that decides which path is used. This is a complex Xcode build setting // expression that expands to one of two values, depending on the last path component of // the CONTENTS_FOLDER_PATH variable. As described above, this will be either "Contents" // for deep bundles or the bundle file name itself for flat bundles. Finally, to santiize // the potentially invalid build setting names from the bundle file name, it converts that // to an identifier. We rely on BUNDLE_LOADER_BUNDLE_STYLE_CONDITIONAL_<bundle file name> // being undefined (and thus expanding to nothing) for the path resolution to work. // // The operations on the CONTENTS_FOLDER_PATH are documented here: // http://codeworkshop.net/posts/xcode-build-setting-transformations String bundleLoaderOutputPathConditional = "$(BUNDLE_LOADER_BUNDLE_STYLE_CONDITIONAL_$(CONTENTS_FOLDER_PATH:file:identifier))"; // If the $(CONTENTS_FOLDER_PATH:file:identifier) expands to this, we add the deep bundle // path into the bundle loader. See above for the case when it will expand to this value. extraSettingsBuilder.put("BUNDLE_LOADER_BUNDLE_STYLE_CONDITIONAL_Contents", Joiner.on('/').join(getTargetOutputPath(bundleLoader), bundleLoaderBundleName, "Contents/MacOS", bundleLoaderProductName)); extraSettingsBuilder.put( "BUNDLE_LOADER_BUNDLE_STYLE_CONDITIONAL_" + getProductName(bundle.get()) + "_" + getExtensionString(bundle.get().getConstructorArg().getExtension()), Joiner.on('/').join(getTargetOutputPath(bundleLoader), bundleLoaderBundleName, bundleLoaderProductName)); extraSettingsBuilder.put("BUNDLE_LOADER", bundleLoaderOutputPathConditional).put("TEST_HOST", "$(BUNDLE_LOADER)"); addPBXTargetDependency(target, bundleLoader.getBuildTarget()); } if (infoPlistOptional.isPresent()) { Path infoPlistPath = pathRelativizer.outputDirToRootRelative(infoPlistOptional.get()); extraSettingsBuilder.put("INFOPLIST_FILE", infoPlistPath.toString()); } if (arg.getBridgingHeader().isPresent()) { Path bridgingHeaderPath = pathRelativizer .outputDirToRootRelative(resolveSourcePath(arg.getBridgingHeader().get())); extraSettingsBuilder.put("SWIFT_OBJC_BRIDGING_HEADER", Joiner.on('/').join("$(SRCROOT)", bridgingHeaderPath.toString())); } swiftVersion.ifPresent(s -> extraSettingsBuilder.put("SWIFT_VERSION", s)); swiftVersion.ifPresent(s -> extraSettingsBuilder.put("PRODUCT_MODULE_NAME", getModuleName(targetNode))); if (hasSwiftVersionArg && containsSwiftCode && isFocusedOnTarget) { extraSettingsBuilder.put("SWIFT_OBJC_INTERFACE_HEADER_NAME", getSwiftObjCGeneratedHeaderName(buildTargetNode)); if (swiftBuckConfig.getProjectWMO()) { // We must disable "Index While Building" as there's a bug in the LLVM infra which // makes the compilation fail. extraSettingsBuilder.put("COMPILER_INDEX_STORE_ENABLE", "NO"); // This is a hidden Xcode setting which is needed for two reasons: // - Stops Xcode adding .o files for each Swift compilation unit to dependency db // which is used during linking (which will fail with WMO). // - Turns on WMO itself. // // Note that setting SWIFT_OPTIMIZATION_LEVEL (which is public) to '-Owholemodule' // ends up crashing the Swift compiler for some reason while this doesn't. extraSettingsBuilder.put("SWIFT_WHOLE_MODULE_OPTIMIZATION", "YES"); } } Optional<SourcePath> prefixHeaderOptional = getPrefixHeaderSourcePath(targetNode.getConstructorArg()); if (prefixHeaderOptional.isPresent()) { Path prefixHeaderRelative = resolveSourcePath(prefixHeaderOptional.get()); Path prefixHeaderPath = pathRelativizer.outputDirToRootRelative(prefixHeaderRelative); extraSettingsBuilder.put("GCC_PREFIX_HEADER", prefixHeaderPath.toString()); extraSettingsBuilder.put("GCC_PRECOMPILE_PREFIX_HEADER", "YES"); } boolean shouldSetUseHeadermap = false; if (isModularAppleLibrary) { extraSettingsBuilder.put("CLANG_ENABLE_MODULES", "YES"); extraSettingsBuilder.put("DEFINES_MODULE", "YES"); if (isFrameworkProductType(productType)) { // Modular frameworks need to have both USE_HEADERMAP enabled so that Xcode generates // .framework VFS overlays, in modular libraries we handle this in buck shouldSetUseHeadermap = true; } } extraSettingsBuilder.put("USE_HEADERMAP", shouldSetUseHeadermap ? "YES" : "NO"); defaultSettingsBuilder.put("REPO_ROOT", projectFilesystem.getRootPath().toAbsolutePath().normalize().toString()); if (hasSwiftVersionArg && containsSwiftCode && isFocusedOnTarget) { // We need to be able to control the directory where Xcode places the derived sources, so // that the Obj-C Generated Header can be included in the header map and imported through // a framework-style import like <Module/Module-Swift.h> Path derivedSourcesDir = getDerivedSourcesDirectoryForBuildTarget(buildTarget, projectFilesystem); Path derivedSourceDirRelativeToProjectRoot = pathRelativizer .outputDirToRootRelative(derivedSourcesDir); defaultSettingsBuilder.put("DERIVED_FILE_DIR", derivedSourceDirRelativeToProjectRoot.toString()); } defaultSettingsBuilder.put(PRODUCT_NAME, getProductName(buildTargetNode)); bundle.ifPresent(bundleNode -> defaultSettingsBuilder.put("WRAPPER_EXTENSION", getExtensionString(bundleNode.getConstructorArg().getExtension()))); // We use BUILT_PRODUCTS_DIR as the root for the everything being built. Target- // specific output is placed within CONFIGURATION_BUILD_DIR, inside BUILT_PRODUCTS_DIR. // That allows Copy Files build phases to reference files in the CONFIGURATION_BUILD_DIR // of other targets by using paths relative to the target-independent BUILT_PRODUCTS_DIR. defaultSettingsBuilder.put("BUILT_PRODUCTS_DIR", // $EFFECTIVE_PLATFORM_NAME starts with a dash, so this expands to something like: // $SYMROOT/Debug-iphonesimulator Joiner.on('/').join("$SYMROOT", "$CONFIGURATION$EFFECTIVE_PLATFORM_NAME")); defaultSettingsBuilder.put("CONFIGURATION_BUILD_DIR", "$BUILT_PRODUCTS_DIR"); boolean nodeIsAppleLibrary = targetNode.getDescription() instanceof AppleLibraryDescription; boolean nodeIsCxxLibrary = targetNode.getDescription() instanceof CxxLibraryDescription; if (!bundle.isPresent() && (nodeIsAppleLibrary || nodeIsCxxLibrary)) { defaultSettingsBuilder.put("EXECUTABLE_PREFIX", "lib"); } if (isFocusedOnTarget) { ImmutableSet<Path> recursiveHeaderSearchPaths = collectRecursiveHeaderSearchPaths(targetNode); ImmutableSet<Path> headerMapBases = collectRecursiveHeaderMapBases(targetNode); ImmutableMap.Builder<String, String> appendConfigsBuilder = ImmutableMap.builder(); appendConfigsBuilder.putAll(getFrameworkAndLibrarySearchPathConfigs(targetNode, includeFrameworks)); appendConfigsBuilder.put("HEADER_SEARCH_PATHS", Joiner.on(' ').join(Iterables.concat(recursiveHeaderSearchPaths, headerMapBases))); if (hasSwiftVersionArg && containsSwiftCode && isFocusedOnTarget) { ImmutableSet<Path> swiftIncludePaths = collectRecursiveSwiftIncludePaths(targetNode); Stream<String> allValues = Streams.concat(Stream.of("$BUILT_PRODUCTS_DIR"), Streams .stream(swiftIncludePaths).map((path) -> path.toString()).map(Escaper.BASH_ESCAPER)); appendConfigsBuilder.put("SWIFT_INCLUDE_PATHS", allValues.collect(Collectors.joining(" "))); } ImmutableList.Builder<String> targetSpecificSwiftFlags = ImmutableList.builder(); Optional<TargetNode<SwiftCommonArg>> swiftTargetNode = TargetNodes.castArg(targetNode, SwiftCommonArg.class); targetSpecificSwiftFlags.addAll(swiftTargetNode.map( x -> convertStringWithMacros(targetNode, x.getConstructorArg().getSwiftCompilerFlags())) .orElse(ImmutableList.of())); if (containsSwiftCode && isModularAppleLibrary && publicCxxHeaders.size() > 0) { targetSpecificSwiftFlags.addAll(collectModularTargetSpecificSwiftFlags(targetNode)); } ImmutableList<String> testingOverlay = getFlagsForExcludesForModulesUnderTests(targetNode); Iterable<String> otherSwiftFlags = Iterables.concat( swiftBuckConfig.getCompilerFlags().orElse(DEFAULT_SWIFTFLAGS), targetSpecificSwiftFlags.build()); Iterable<String> otherCFlags = ImmutableList.<String>builder() .addAll(cxxBuckConfig.getCflags().orElse(DEFAULT_CFLAGS)) .addAll(cxxBuckConfig.getCppflags().orElse(DEFAULT_CPPFLAGS)) .addAll(convertStringWithMacros(targetNode, collectRecursiveExportedPreprocessorFlags(targetNode))) .addAll(convertStringWithMacros(targetNode, targetNode.getConstructorArg().getCompilerFlags())) .addAll(convertStringWithMacros(targetNode, targetNode.getConstructorArg().getPreprocessorFlags())) .addAll(convertStringWithMacros(targetNode, collectRecursiveSystemPreprocessorFlags(targetNode))) .addAll(testingOverlay).build(); Iterable<String> otherCxxFlags = ImmutableList.<String>builder() .addAll(cxxBuckConfig.getCxxflags().orElse(DEFAULT_CXXFLAGS)) .addAll(cxxBuckConfig.getCxxppflags().orElse(DEFAULT_CXXPPFLAGS)) .addAll(convertStringWithMacros(targetNode, collectRecursiveExportedPreprocessorFlags(targetNode))) .addAll(convertStringWithMacros(targetNode, targetNode.getConstructorArg().getCompilerFlags())) .addAll(convertStringWithMacros(targetNode, targetNode.getConstructorArg().getPreprocessorFlags())) .addAll(convertStringWithMacros(targetNode, collectRecursiveSystemPreprocessorFlags(targetNode))) .addAll(testingOverlay).build(); appendConfigsBuilder .put("OTHER_SWIFT_FLAGS", Streams.stream(otherSwiftFlags).map(Escaper.BASH_ESCAPER) .collect(Collectors.joining(" "))) .put("OTHER_CFLAGS", Streams.stream(otherCFlags).map(Escaper.BASH_ESCAPER) .collect(Collectors.joining(" "))) .put("OTHER_CPLUSPLUSFLAGS", Streams.stream(otherCxxFlags).map(Escaper.BASH_ESCAPER) .collect(Collectors.joining(" "))); Iterable<String> otherLdFlags = ImmutableList.<String>builder() .addAll(cxxBuckConfig.getLdflags().orElse(DEFAULT_LDFLAGS)) .addAll(appleConfig.linkAllObjC() ? ImmutableList.of("-ObjC") : ImmutableList.of()) .addAll(convertStringWithMacros(targetNode, Iterables.concat(targetNode.getConstructorArg().getLinkerFlags(), collectRecursiveExportedLinkerFlags(targetNode)))) .addAll(swiftDebugLinkerFlagsBuilder.build()).build(); updateOtherLinkerFlagsForOptions(targetNode, bundleLoaderNode, appendConfigsBuilder, otherLdFlags); ImmutableMultimap<String, ImmutableList<String>> platformFlags = convertPlatformFlags(targetNode, Iterables.concat( ImmutableList.of(targetNode.getConstructorArg().getPlatformCompilerFlags()), ImmutableList.of(targetNode.getConstructorArg().getPlatformPreprocessorFlags()), collectRecursiveExportedPlatformPreprocessorFlags(targetNode))); for (String platform : platformFlags.keySet()) { appendConfigsBuilder.put(generateConfigKey("OTHER_CFLAGS", platform), Streams.stream(Iterables.transform( Iterables.concat(otherCFlags, Iterables.concat(platformFlags.get(platform))), Escaper.BASH_ESCAPER::apply)).collect( Collectors.joining(" "))) .put(generateConfigKey("OTHER_CPLUSPLUSFLAGS", platform), Streams.stream(Iterables.transform( Iterables.concat(otherCxxFlags, Iterables.concat(platformFlags.get(platform))), Escaper.BASH_ESCAPER::apply)).collect(Collectors.joining(" "))); } ImmutableMultimap<String, ImmutableList<String>> platformLinkerFlags = convertPlatformFlags( targetNode, Iterables.concat(ImmutableList.of(targetNode.getConstructorArg().getPlatformLinkerFlags()), collectRecursiveExportedPlatformLinkerFlags(targetNode))); for (String platform : platformLinkerFlags.keySet()) { appendConfigsBuilder .put(generateConfigKey("OTHER_LDFLAGS", platform), Streams.stream( Iterables .transform( Iterables.concat(otherLdFlags, Iterables.concat( platformLinkerFlags.get(platform))), Escaper.BASH_ESCAPER::apply)) .collect(Collectors.joining(" "))); } ImmutableMap<String, String> appendedConfig = appendConfigsBuilder.build(); Optional<ImmutableSortedMap<String, ImmutableMap<String, String>>> configs = getXcodeBuildConfigurationsForTargetNode( targetNode); setTargetBuildConfigurations(buildTarget, target, project.getMainGroup(), configs.get(), getTargetCxxBuildConfigurationForTargetNode(targetNode, appendedConfig), extraSettingsBuilder.build(), defaultSettingsBuilder.build(), appendedConfig); } } Optional<String> moduleName = isModularAppleLibrary ? Optional.of(getModuleName(targetNode)) : Optional.empty(); // -- phases createHeaderSymlinkTree(publicCxxHeaders, getSwiftPublicHeaderMapEntriesForTarget(targetNode), moduleName, getPathToHeaderSymlinkTree(targetNode, HeaderVisibility.PUBLIC), arg.getXcodePublicHeadersSymlinks().orElse(cxxBuckConfig.getPublicHeadersSymlinksEnabled()) || !options.shouldUseHeaderMaps() || isModularAppleLibrary, !shouldMergeHeaderMaps(), options.shouldGenerateMissingUmbrellaHeader()); if (isFocusedOnTarget) { createHeaderSymlinkTree(getPrivateCxxHeaders(targetNode), ImmutableMap.of(), // private interfaces never have a modulemap Optional.empty(), getPathToHeaderSymlinkTree(targetNode, HeaderVisibility.PRIVATE), arg.getXcodePrivateHeadersSymlinks().orElse(cxxBuckConfig.getPrivateHeadersSymlinksEnabled()) || !options.shouldUseHeaderMaps(), options.shouldUseHeaderMaps(), options.shouldGenerateMissingUmbrellaHeader()); } Optional<TargetNode<AppleNativeTargetDescriptionArg>> appleTargetNode = TargetNodes.castArg(targetNode, AppleNativeTargetDescriptionArg.class); if (appleTargetNode.isPresent() && isFocusedOnTarget && !options.shouldGenerateHeaderSymlinkTreesOnly()) { // Use Core Data models from immediate dependencies only. addCoreDataModelsIntoTarget(appleTargetNode.get(), targetGroup.get()); addSceneKitAssetsIntoTarget(appleTargetNode.get(), targetGroup.get()); } if (bundle.isPresent() && isFocusedOnTarget && !options.shouldGenerateHeaderSymlinkTreesOnly()) { addEntitlementsPlistIntoTarget(bundle.get(), targetGroup.get()); } return target; } /** Generate a mapping from libraries to the framework bundles that include them. */ public static ImmutableMap<BuildTarget, TargetNode<?>> computeSharedLibrariesToBundles( ImmutableSet<TargetNode<?>> targetNodes, TargetGraphAndTargets targetGraphAndTargets) throws HumanReadableException { Map<BuildTarget, TargetNode<?>> sharedLibraryToBundle = new HashMap<>(); for (TargetNode<?> targetNode : targetNodes) { Optional<TargetNode<CxxLibraryDescription.CommonArg>> binaryNode = TargetNodes .castArg(targetNode, AppleBundleDescriptionArg.class) .flatMap(bundleNode -> bundleNode.getConstructorArg().getBinary()) .map(target -> targetGraphAndTargets.getTargetGraph().get(target)) .flatMap(node -> TargetNodes.castArg(node, CxxLibraryDescription.CommonArg.class)); if (!binaryNode.isPresent()) { continue; } CxxLibraryDescription.CommonArg arg = binaryNode.get().getConstructorArg(); if (arg.getPreferredLinkage().equals(Optional.of(Linkage.SHARED))) { BuildTarget binaryBuildTargetWithoutFlavors = binaryNode.get().getBuildTarget().withoutFlavors(); if (sharedLibraryToBundle.containsKey(binaryBuildTargetWithoutFlavors)) { throw new HumanReadableException(String.format( "Library %s is declared as the 'binary' of multiple bundles:\n first bundle: %s\n second bundle: %s", binaryBuildTargetWithoutFlavors, sharedLibraryToBundle.get(binaryBuildTargetWithoutFlavors).getBuildTarget(), targetNode.getBuildTarget())); } else { sharedLibraryToBundle.put(binaryBuildTargetWithoutFlavors, targetNode); } } } return ImmutableMap.copyOf(sharedLibraryToBundle); } @VisibleForTesting static FluentIterable<TargetNode<?>> swapSharedLibrariesForBundles(FluentIterable<TargetNode<?>> targetDeps, ImmutableMap<BuildTarget, TargetNode<?>> sharedLibrariesToBundles) { return targetDeps.transform(t -> sharedLibrariesToBundles.getOrDefault(t.getBuildTarget(), t)); } private ImmutableSet<FrameworkPath> getSytemFrameworksLibsForTargetNode( TargetNode<? extends CommonArg> targetNode) { ImmutableSet.Builder<FrameworkPath> frameworksBuilder = ImmutableSet.builder(); frameworksBuilder.addAll(collectRecursiveFrameworkDependencies(targetNode)); frameworksBuilder.addAll(targetNode.getConstructorArg().getFrameworks()); frameworksBuilder.addAll(targetNode.getConstructorArg().getLibraries()); return frameworksBuilder.build(); } /** * Subdivide the various deps and write out to the xcconfig file for scripts to post process if * needed* */ private void updateOtherLinkerFlagsForOptions(TargetNode<? extends CommonArg> targetNode, Optional<TargetNode<AppleBundleDescriptionArg>> bundleLoaderNode, Builder<String, String> appendConfigsBuilder, Iterable<String> otherLdFlags) { // Local: Local to the current project and built by Xcode. // Focused: Included in the workspace and built by Xcode but not in current project. // Other: Not included in the workspace to be built by Xcode. FluentIterable<TargetNode<?>> depTargetNodes = collectRecursiveLibraryDepTargets(targetNode); // Don't duplicate linker flags for the bundle loader. FluentIterable<TargetNode<?>> filteredDeps = collectRecursiveLibraryDepsMinusBundleLoaderDeps(targetNode, depTargetNodes, bundleLoaderNode); ImmutableSet<FrameworkPath> systemFwkOrLibs = getSytemFrameworksLibsForTargetNode(targetNode); ImmutableList<String> systemFwkOrLibFlags = collectSystemLibraryAndFrameworkLinkerFlags(systemFwkOrLibs); if (options.shouldForceLoadLinkWholeLibraries() || options.shouldAddLinkedLibrariesAsFlags()) { ImmutableList<String> forceLoadLocal = collectForceLoadLinkerFlags(filterRecursiveLibraryDepsIterable( filteredDeps, FilterFlags.LIBRARY_CURRENT_PROJECT_WITH_FORCE_LOAD)); ImmutableList<String> forceLoadFocused = collectForceLoadLinkerFlags( filterRecursiveLibraryDepsIterable(filteredDeps, FilterFlags.LIBRARY_FOCUSED_WITH_FORCE_LOAD)); ImmutableList<String> forceLoadOther = collectForceLoadLinkerFlags( filterRecursiveLibraryDepsIterable(filteredDeps, FilterFlags.LIBRARY_OTHER_WITH_FORCE_LOAD)); appendConfigsBuilder.put("BUCK_LINKER_FLAGS_LIBRARY_FORCE_LOAD_LOCAL", Streams.stream(forceLoadLocal).map(Escaper.BASH_ESCAPER).collect(Collectors.joining(" "))); appendConfigsBuilder.put("BUCK_LINKER_FLAGS_LIBRARY_FORCE_LOAD_FOCUSED", Streams.stream(forceLoadFocused).map(Escaper.BASH_ESCAPER).collect(Collectors.joining(" "))); appendConfigsBuilder.put("BUCK_LINKER_FLAGS_LIBRARY_FORCE_LOAD_OTHER", Streams.stream(forceLoadOther).map(Escaper.BASH_ESCAPER).collect(Collectors.joining(" "))); } if (options.shouldAddLinkedLibrariesAsFlags()) { // If force load enabled, then don't duplicate the flags in the OTHER flags, otherwise they // will just be included like normal dependencies. boolean shouldLimitByForceLoad = options.shouldForceLoadLinkWholeLibraries(); Iterable<String> localLibraryFlags = collectLibraryLinkerFlags(filterRecursiveLibraryDepsIterable( filteredDeps, shouldLimitByForceLoad ? FilterFlags.LIBRARY_CURRENT_PROJECT_WITHOUT_FORCE_LOAD : FilterFlags.LIBRARY_CURRENT_PROJECT)); Iterable<String> focusedLibraryFlags = collectLibraryLinkerFlags(filterRecursiveLibraryDepsIterable( filteredDeps, shouldLimitByForceLoad ? FilterFlags.LIBRARY_FOCUSED_WITHOUT_FORCE_LOAD : FilterFlags.LIBRARY_FOCUSED)); Iterable<String> otherLibraryFlags = collectLibraryLinkerFlags(filterRecursiveLibraryDepsIterable( filteredDeps, shouldLimitByForceLoad ? FilterFlags.LIBRARY_OTHER_WITHOUT_FORCE_LOAD : FilterFlags.LIBRARY_OTHER)); Iterable<String> localFrameworkFlags = collectFrameworkLinkerFlags( filterRecursiveLibraryDepsIterable(depTargetNodes, FilterFlags.FRAMEWORK_CURRENT_PROJECT)); Iterable<String> focusedFrameworkFlags = collectFrameworkLinkerFlags( filterRecursiveLibraryDepsIterable(depTargetNodes, FilterFlags.FRAMEWORK_FOCUSED)); Iterable<String> otherFrameworkFlags = collectFrameworkLinkerFlags( filterRecursiveLibraryDepsIterable(depTargetNodes, FilterFlags.FRAMEWORK_OTHER)); appendConfigsBuilder .put("BUCK_LINKER_FLAGS_LIBRARY_LOCAL", Streams.stream(localLibraryFlags).collect(Collectors.joining(" "))) .put("BUCK_LINKER_FLAGS_LIBRARY_FOCUSED", Streams.stream(focusedLibraryFlags).collect(Collectors.joining(" "))) .put("BUCK_LINKER_FLAGS_LIBRARY_OTHER", Streams.stream(otherLibraryFlags).collect(Collectors.joining(" "))) .put("BUCK_LINKER_FLAGS_FRAMEWORK_LOCAL", Streams.stream(localFrameworkFlags).collect(Collectors.joining(" "))) .put("BUCK_LINKER_FLAGS_FRAMEWORK_FOCUSED", Streams.stream(focusedFrameworkFlags).collect(Collectors.joining(" "))) .put("BUCK_LINKER_FLAGS_FRAMEWORK_OTHER", Streams.stream(otherFrameworkFlags).collect(Collectors.joining(" "))) .put("BUCK_LINKER_FLAGS_SYSTEM", Streams.stream(systemFwkOrLibFlags).collect(Collectors.joining(" "))); } Stream<String> otherLdFlagsStream = Streams.stream(otherLdFlags).map(Escaper.BASH_ESCAPER); if (options.shouldForceLoadLinkWholeLibraries() && options.shouldAddLinkedLibrariesAsFlags()) { appendConfigsBuilder.put("OTHER_LDFLAGS", Streams.concat(otherLdFlagsStream, Stream.of("$BUCK_LINKER_FLAGS_SYSTEM", "$BUCK_LINKER_FLAGS_FRAMEWORK_LOCAL", "$BUCK_LINKER_FLAGS_FRAMEWORK_FOCUSED", "$BUCK_LINKER_FLAGS_FRAMEWORK_OTHER", "$BUCK_LINKER_FLAGS_LIBRARY_FORCE_LOAD_LOCAL", "$BUCK_LINKER_FLAGS_LIBRARY_FORCE_LOAD_FOCUSED", "$BUCK_LINKER_FLAGS_LIBRARY_FORCE_LOAD_OTHER", "$BUCK_LINKER_FLAGS_LIBRARY_LOCAL", "$BUCK_LINKER_FLAGS_LIBRARY_FOCUSED", "$BUCK_LINKER_FLAGS_LIBRARY_OTHER")) .collect(Collectors.joining(" "))); } else if (options.shouldForceLoadLinkWholeLibraries() && !options.shouldAddLinkedLibrariesAsFlags()) { appendConfigsBuilder.put("OTHER_LDFLAGS", Streams .concat(otherLdFlagsStream, Stream.of("$BUCK_LINKER_FLAGS_LIBRARY_FORCE_LOAD_LOCAL", "$BUCK_LINKER_FLAGS_LIBRARY_FORCE_LOAD_FOCUSED", "$BUCK_LINKER_FLAGS_LIBRARY_FORCE_LOAD_OTHER")) .collect(Collectors.joining(" "))); } else if (options.shouldAddLinkedLibrariesAsFlags()) { appendConfigsBuilder.put("OTHER_LDFLAGS", Streams.concat(otherLdFlagsStream, Stream.of("$BUCK_LINKER_FLAGS_SYSTEM", "$BUCK_LINKER_FLAGS_FRAMEWORK_LOCAL", "$BUCK_LINKER_FLAGS_FRAMEWORK_FOCUSED", "$BUCK_LINKER_FLAGS_FRAMEWORK_OTHER", "$BUCK_LINKER_FLAGS_LIBRARY_LOCAL", "$BUCK_LINKER_FLAGS_LIBRARY_FOCUSED", "$BUCK_LINKER_FLAGS_LIBRARY_OTHER")) .collect(Collectors.joining(" "))); } else { appendConfigsBuilder.put("OTHER_LDFLAGS", otherLdFlagsStream.collect(Collectors.joining(" "))); } } private void setAppIconSettings(ImmutableSet<AppleAssetCatalogDescriptionArg> recursiveAssetCatalogs, ImmutableSet<AppleAssetCatalogDescriptionArg> directAssetCatalogs, BuildTarget buildTarget, ImmutableMap.Builder<String, String> defaultSettingsBuilder) { ImmutableList<String> appIcon = Stream.concat(directAssetCatalogs.stream(), recursiveAssetCatalogs.stream()) .map(x -> x.getAppIcon()).filter(Optional::isPresent).map(Optional::get) .collect(ImmutableList.toImmutableList()); if (appIcon.size() > 1) { throw new HumanReadableException( "At most one asset catalog in the dependencies of %s " + "can have a app_icon", buildTarget); } else if (appIcon.size() == 1) { defaultSettingsBuilder.put("ASSETCATALOG_COMPILER_APPICON_NAME", appIcon.get(0)); } } private void setLaunchImageSettings(ImmutableSet<AppleAssetCatalogDescriptionArg> recursiveAssetCatalogs, ImmutableSet<AppleAssetCatalogDescriptionArg> directAssetCatalogs, BuildTarget buildTarget, ImmutableMap.Builder<String, String> defaultSettingsBuilder) { ImmutableList<String> launchImage = Stream .concat(directAssetCatalogs.stream(), recursiveAssetCatalogs.stream()).map(x -> x.getLaunchImage()) .filter(Optional::isPresent).map(Optional::get).collect(ImmutableList.toImmutableList()); if (launchImage.size() > 1) { throw new HumanReadableException( "At most one asset catalog in the dependencies of %s " + "can have a launch_image", buildTarget); } else if (launchImage.size() == 1) { defaultSettingsBuilder.put("ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME", launchImage.get(0)); } } private boolean shouldEmbedSwiftRuntimeInBundleTarget( Optional<? extends TargetNode<? extends HasAppleBundleFields>> bundle) { return bundle.map(b -> b.getConstructorArg().getExtension().transform(bundleExtension -> { switch (bundleExtension) { case APP: case APPEX: case PLUGIN: case BUNDLE: case XCTEST: case PREFPANE: case XPC: case QLGENERATOR: // All of the above bundles can have loaders which do not contain // a Swift runtime, so it must get bundled to ensure they run. return true; case FRAMEWORK: case DSYM: return false; } return false; }, stringExtension -> false)).orElse(false); } private ImmutableList<String> getFlagsForExcludesForModulesUnderTests( TargetNode<? extends CxxLibraryDescription.CommonArg> testingTarget) { ImmutableList.Builder<String> testingOverlayBuilder = new ImmutableList.Builder<>(); visitRecursivePrivateHeaderSymlinkTreesForTests(testingTarget, (targetUnderTest, headerVisibility) -> { // If we are testing a modular apple_library, we expose it non-modular. This allows the // testing target to see both the public and private interfaces of the tested target // without triggering header errors related to modules. We hide the module definition by // using a filesystem overlay that overrides the module.modulemap with an empty file. if (isModularAppleLibrary(targetUnderTest)) { testingOverlayBuilder.add("-ivfsoverlay"); Path vfsOverlay = getTestingModulemapVFSOverlayLocationFromSymlinkTreeRoot( getPathToHeaderSymlinkTree(targetUnderTest, HeaderVisibility.PUBLIC)); testingOverlayBuilder.add("$REPO_ROOT/" + vfsOverlay); } }); return testingOverlayBuilder.build(); } private boolean isFrameworkProductType(ProductType productType) { return productType == ProductTypes.FRAMEWORK || productType == ProductTypes.STATIC_FRAMEWORK; } private void addPBXTargetDependency(PBXNativeTarget pbxTarget, BuildTarget dependency) { // Xcode appears to only support target dependencies if both targets are within the same // project. // If the desired target dependency is not in the same project, then just ignore it. if (!isBuiltByCurrentProject(dependency)) { return; } targetNodeToProjectTarget.getUnchecked(targetGraph.get(dependency)).ifPresent(dependencyPBXTarget -> { if (project.getGlobalID() == null) { project.setGlobalID(project.generateGid(gidGenerator)); } if (dependencyPBXTarget.getGlobalID() == null) { dependencyPBXTarget.setGlobalID(dependencyPBXTarget.generateGid(gidGenerator)); } PBXContainerItemProxy dependencyProxy = new PBXContainerItemProxy(project, dependencyPBXTarget.getGlobalID(), PBXContainerItemProxy.ProxyType.TARGET_REFERENCE); pbxTarget.getDependencies().add(new PBXTargetDependency(dependencyProxy)); }); } private ImmutableMap<String, String> getFrameworkAndLibrarySearchPathConfigs( TargetNode<? extends CxxLibraryDescription.CommonArg> node, boolean includeFrameworks) { HashSet<String> frameworkSearchPaths = new HashSet<>(); frameworkSearchPaths.add("$BUILT_PRODUCTS_DIR"); HashSet<String> librarySearchPaths = new HashSet<>(); librarySearchPaths.add("$BUILT_PRODUCTS_DIR"); HashSet<String> iOSLdRunpathSearchPaths = new HashSet<>(); HashSet<String> macOSLdRunpathSearchPaths = new HashSet<>(); FluentIterable<TargetNode<?>> depTargetNodes = collectRecursiveLibraryDepTargets(node); ImmutableSet<PBXFileReference> swiftDeps = filterRecursiveLibraryDependenciesWithSwiftSources( depTargetNodes); Stream.concat( // Collect all the nodes that contribute to linking // ... Which the node includes itself Stream.of(node), // ... And recursive dependencies that gets linked in AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.RecursiveDependenciesMode.LINKING, node, ImmutableSet.of(AppleLibraryDescription.class, CxxLibraryDescription.class, PrebuiltAppleFrameworkDescription.class, PrebuiltCxxLibraryDescription.class)) .stream()) .map(castedNode -> { // If the item itself is a prebuilt library, add it to framework_search_paths. // This is needed for prebuilt framework's headers to be reference-able. TargetNodes.castArg(castedNode, PrebuiltCxxLibraryDescriptionArg.class).ifPresent(prebuilt -> { SourcePath path = null; if (prebuilt.getConstructorArg().getSharedLib().isPresent()) { path = prebuilt.getConstructorArg().getSharedLib().get(); } else if (prebuilt.getConstructorArg().getStaticLib().isPresent()) { path = prebuilt.getConstructorArg().getStaticLib().get(); } else if (prebuilt.getConstructorArg().getStaticPicLib().isPresent()) { path = prebuilt.getConstructorArg().getStaticPicLib().get(); } if (path != null) { librarySearchPaths.add("$REPO_ROOT/" + resolveSourcePath(path).getParent()); } }); return castedNode; }) // Keep only the ones that may have frameworks and libraries fields. .flatMap(input -> RichStream.from(TargetNodes.castArg(input, HasSystemFrameworkAndLibraries.class))) // Then for each of them .forEach(castedNode -> { // ... Add the framework path strings. castedNode.getConstructorArg().getFrameworks().stream().filter(x -> !x.isSDKROOTFrameworkPath()) .map(frameworkPath -> FrameworkPath.getUnexpandedSearchPath(this::resolveSourcePath, pathRelativizer::outputDirToRootRelative, frameworkPath).toString()) .forEach(frameworkSearchPaths::add); // ... And do the same for libraries. castedNode.getConstructorArg().getLibraries().stream() .map(libraryPath -> FrameworkPath.getUnexpandedSearchPath(this::resolveSourcePath, pathRelativizer::outputDirToRootRelative, libraryPath).toString()) .forEach(librarySearchPaths::add); // If the item itself is a prebuilt framework, add it to framework_search_paths. // This is needed for prebuilt framework's headers to be reference-able. TargetNodes.castArg(castedNode, PrebuiltAppleFrameworkDescriptionArg.class) .ifPresent(prebuilt -> { frameworkSearchPaths.add("$REPO_ROOT/" + resolveSourcePath(prebuilt.getConstructorArg().getFramework()) .getParent()); if (prebuilt.getConstructorArg() .getPreferredLinkage() != NativeLinkable.Linkage.STATIC) { // Frameworks that are copied into the binary. iOSLdRunpathSearchPaths.add("@loader_path/Frameworks"); iOSLdRunpathSearchPaths.add("@executable_path/Frameworks"); macOSLdRunpathSearchPaths.add("@loader_path/../Frameworks"); macOSLdRunpathSearchPaths.add("@executable_path/../Frameworks"); } }); }); if (includeFrameworks && swiftDeps.size() > 0) { // When Xcode compiles static Swift libs, it will include linker commands (LC_LINKER_OPTION) // that will be carried over for the final binary to link to the appropriate Swift overlays // and libs. This means that the final binary must be able to locate the Swift libs in the // library search path. If an Xcode target includes Swift, Xcode will automatically append // the Swift lib folder when invoking the linker. Unfortunately, this will not happen if // we have a plain apple_binary that has Swift deps. So we're manually doing exactly what // Xcode does to make sure binaries link successfully if they use Swift directly or // transitively. librarySearchPaths.add("$DT_TOOLCHAIN_DIR/usr/lib/swift/$PLATFORM_NAME"); } if (swiftDeps.size() > 0 || projGenerationStateCache.targetContainsSwiftSourceCode(node)) { iOSLdRunpathSearchPaths.add("@executable_path/Frameworks"); iOSLdRunpathSearchPaths.add("@loader_path/Frameworks"); macOSLdRunpathSearchPaths.add("@executable_path/../Frameworks"); macOSLdRunpathSearchPaths.add("@loader_path/../Frameworks"); } ImmutableMap.Builder<String, String> results = ImmutableMap.<String, String>builder() .put("FRAMEWORK_SEARCH_PATHS", Joiner.on(' ').join(frameworkSearchPaths)) .put("LIBRARY_SEARCH_PATHS", Joiner.on(' ').join(librarySearchPaths)); if (!iOSLdRunpathSearchPaths.isEmpty()) { results.put("LD_RUNPATH_SEARCH_PATHS[sdk=iphoneos*]", Joiner.on(' ').join(iOSLdRunpathSearchPaths)); results.put("LD_RUNPATH_SEARCH_PATHS[sdk=iphonesimulator*]", Joiner.on(' ').join(iOSLdRunpathSearchPaths)); } if (!macOSLdRunpathSearchPaths.isEmpty()) { results.put("LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]", Joiner.on(' ').join(macOSLdRunpathSearchPaths)); } return results.build(); } private ImmutableSortedMap<Path, SourcePath> getPublicCxxHeaders( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode) { CxxLibraryDescription.CommonArg arg = targetNode.getConstructorArg(); if (arg instanceof AppleNativeTargetDescriptionArg) { Path headerPathPrefix = AppleDescriptions.getHeaderPathPrefix((AppleNativeTargetDescriptionArg) arg, targetNode.getBuildTarget()); ImmutableSortedMap.Builder<String, SourcePath> exportedHeadersBuilder = ImmutableSortedMap .naturalOrder(); exportedHeadersBuilder .putAll(AppleDescriptions.convertHeadersToPublicCxxHeaders(targetNode.getBuildTarget(), this::resolveSourcePath, headerPathPrefix, arg.getExportedHeaders())); for (Pair<Pattern, SourceSortedSet> patternMatchedHeader : arg.getExportedPlatformHeaders() .getPatternsAndValues()) { exportedHeadersBuilder .putAll(AppleDescriptions.convertHeadersToPublicCxxHeaders(targetNode.getBuildTarget(), this::resolveSourcePath, headerPathPrefix, patternMatchedHeader.getSecond())); } ImmutableSortedMap<String, SourcePath> fullExportedHeaders = exportedHeadersBuilder.build(); return convertMapKeysToPaths(fullExportedHeaders); } else { ActionGraphBuilder graphBuilder = actionGraphBuilderForNode.apply(targetNode); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(graphBuilder); SourcePathResolver pathResolver = DefaultSourcePathResolver.from(ruleFinder); ImmutableSortedMap.Builder<Path, SourcePath> allHeadersBuilder = ImmutableSortedMap.naturalOrder(); String platform = defaultCxxPlatform.getFlavor().toString(); ImmutableList<SourceSortedSet> platformHeaders = arg.getExportedPlatformHeaders() .getMatchingValues(platform); return allHeadersBuilder .putAll(CxxDescriptionEnhancer.parseExportedHeaders(targetNode.getBuildTarget(), graphBuilder, ruleFinder, pathResolver, Optional.empty(), arg)) .putAll(ProjectGenerator.parseAllPlatformHeaders(targetNode.getBuildTarget(), pathResolver, platformHeaders, true, arg)) .build(); } } private ImmutableSortedMap<Path, SourcePath> getPrivateCxxHeaders( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode) { CxxLibraryDescription.CommonArg arg = targetNode.getConstructorArg(); if (arg instanceof AppleNativeTargetDescriptionArg) { Path headerPathPrefix = AppleDescriptions.getHeaderPathPrefix((AppleNativeTargetDescriptionArg) arg, targetNode.getBuildTarget()); ImmutableSortedMap.Builder<String, SourcePath> fullHeadersBuilder = ImmutableSortedMap.naturalOrder(); fullHeadersBuilder .putAll(AppleDescriptions.convertHeadersToPrivateCxxHeaders(targetNode.getBuildTarget(), this::resolveSourcePath, headerPathPrefix, arg.getHeaders(), arg.getExportedHeaders())); for (Pair<Pattern, SourceSortedSet> patternMatchedHeader : arg.getExportedPlatformHeaders() .getPatternsAndValues()) { fullHeadersBuilder.putAll(AppleDescriptions.convertHeadersToPrivateCxxHeaders( targetNode.getBuildTarget(), this::resolveSourcePath, headerPathPrefix, SourceSortedSet.ofNamedSources(ImmutableSortedMap.of()), patternMatchedHeader.getSecond())); } for (Pair<Pattern, SourceSortedSet> patternMatchedHeader : arg.getPlatformHeaders() .getPatternsAndValues()) { fullHeadersBuilder.putAll(AppleDescriptions.convertHeadersToPrivateCxxHeaders( targetNode.getBuildTarget(), this::resolveSourcePath, headerPathPrefix, patternMatchedHeader.getSecond(), SourceSortedSet.ofNamedSources(ImmutableSortedMap.of()))); } ImmutableSortedMap<String, SourcePath> fullHeaders = fullHeadersBuilder.build(); return convertMapKeysToPaths(fullHeaders); } else { ActionGraphBuilder graphBuilder = actionGraphBuilderForNode.apply(targetNode); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(graphBuilder); SourcePathResolver pathResolver = DefaultSourcePathResolver.from(ruleFinder); ImmutableSortedMap.Builder<Path, SourcePath> allHeadersBuilder = ImmutableSortedMap.naturalOrder(); String platform = defaultCxxPlatform.getFlavor().toString(); ImmutableList<SourceSortedSet> platformHeaders = arg.getPlatformHeaders().getMatchingValues(platform); return allHeadersBuilder .putAll(CxxDescriptionEnhancer.parseHeaders(targetNode.getBuildTarget(), graphBuilder, ruleFinder, pathResolver, Optional.empty(), arg)) .putAll(ProjectGenerator.parseAllPlatformHeaders(targetNode.getBuildTarget(), pathResolver, platformHeaders, false, arg)) .build(); } } private ImmutableSortedMap<Path, SourcePath> convertMapKeysToPaths( ImmutableSortedMap<String, SourcePath> input) { ImmutableSortedMap.Builder<Path, SourcePath> output = ImmutableSortedMap.naturalOrder(); for (Map.Entry<String, SourcePath> entry : input.entrySet()) { output.put(Paths.get(entry.getKey()), entry.getValue()); } return output.build(); } private Optional<ImmutableSortedMap<String, ImmutableMap<String, String>>> getXcodeBuildConfigurationsForTargetNode( TargetNode<?> targetNode) { Optional<ImmutableSortedMap<String, ImmutableMap<String, String>>> configs = Optional.empty(); Optional<TargetNode<AppleNativeTargetDescriptionArg>> appleTargetNode = TargetNodes.castArg(targetNode, AppleNativeTargetDescriptionArg.class); Optional<TargetNode<HalideLibraryDescriptionArg>> halideTargetNode = TargetNodes.castArg(targetNode, HalideLibraryDescriptionArg.class); if (appleTargetNode.isPresent()) { configs = Optional.of(appleTargetNode.get().getConstructorArg().getConfigs()); } else if (halideTargetNode.isPresent()) { configs = Optional.of(halideTargetNode.get().getConstructorArg().getConfigs()); } HashMap<String, HashMap<String, String>> combinedConfig = new HashMap<String, HashMap<String, String>>(); combinedConfig.put(CxxPlatformXcodeConfigGenerator.DEBUG_BUILD_CONFIGURATION_NAME, new HashMap<String, String>(getDefaultDebugBuildConfiguration())); combinedConfig.put(CxxPlatformXcodeConfigGenerator.PROFILE_BUILD_CONFIGURATION_NAME, new HashMap<String, String>()); combinedConfig.put(CxxPlatformXcodeConfigGenerator.RELEASE_BUILD_CONFIGURATION_NAME, new HashMap<String, String>()); if (configs.isPresent() && !configs.get().isEmpty() && !(targetNode.getDescription() instanceof CxxLibraryDescription)) { for (Map.Entry<String, ImmutableMap<String, String>> targetLevelConfig : configs.get().entrySet()) { HashMap<String, String> pendingConfig = new HashMap<String, String>(targetLevelConfig.getValue()); String configTarget = targetLevelConfig.getKey(); if (combinedConfig.containsKey(configTarget)) { combinedConfig.get(configTarget).putAll(pendingConfig); } else { combinedConfig.put(configTarget, pendingConfig); } } } ImmutableSortedMap.Builder<String, ImmutableMap<String, String>> configBuilder = ImmutableSortedMap .naturalOrder(); for (Map.Entry<String, HashMap<String, String>> config : combinedConfig.entrySet()) { configBuilder.put(config.getKey(), ImmutableMap.copyOf(config.getValue())); } return Optional.of(configBuilder.build()); } private static ImmutableMap<String, String> getDefaultDebugBuildConfiguration() { return new ImmutableMap.Builder<String, String>().put("DEAD_CODE_STRIPPING", "NO") .put("ONLY_ACTIVE_ARCH", "YES").put("GCC_SYMBOLS_PRIVATE_EXTERN", "NO").build(); } private ImmutableMap<String, String> getTargetCxxBuildConfigurationForTargetNode(TargetNode<?> targetNode, Map<String, String> appendedConfig) { if (targetNode.getDescription() instanceof CxxLibraryDescription) { return CxxPlatformXcodeConfigGenerator .getCxxXcodeTargetBuildConfigurationsFromCxxPlatform(defaultCxxPlatform, appendedConfig); } else { return CxxPlatformXcodeConfigGenerator .getAppleXcodeTargetBuildConfigurationsFromCxxPlatform(defaultCxxPlatform, appendedConfig); } } private void addEntitlementsPlistIntoTarget(TargetNode<? extends HasAppleBundleFields> targetNode, PBXGroup targetGroup) { ImmutableMap<String, String> infoPlistSubstitutions = targetNode.getConstructorArg() .getInfoPlistSubstitutions(); if (infoPlistSubstitutions.containsKey(AppleBundle.CODE_SIGN_ENTITLEMENTS)) { String entitlementsPlistPath = InfoPlistSubstitution.replaceVariablesInString( "$(" + AppleBundle.CODE_SIGN_ENTITLEMENTS + ")", AppleBundle.withDefaults( infoPlistSubstitutions, ImmutableMap.of("SOURCE_ROOT", ".", "SRCROOT", "."))); targetGroup.getOrCreateFileReferenceBySourceTreePath(new SourceTreePath( PBXReference.SourceTree.SOURCE_ROOT, Paths.get(entitlementsPlistPath), Optional.empty())); } } private void addCoreDataModelsIntoTarget(TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, PBXGroup targetGroup) throws IOException { addCoreDataModelBuildPhase(targetGroup, AppleBuildRules.collectTransitiveBuildRules(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.CORE_DATA_MODEL_DESCRIPTION_CLASSES, ImmutableList.of(targetNode), RecursiveDependenciesMode.COPYING)); } private void addSceneKitAssetsIntoTarget(TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, PBXGroup targetGroup) { ImmutableSet<AppleWrapperResourceArg> allSceneKitAssets = AppleBuildRules.collectTransitiveBuildRules( xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.SCENEKIT_ASSETS_DESCRIPTION_CLASSES, ImmutableList.of(targetNode), RecursiveDependenciesMode.COPYING); for (AppleWrapperResourceArg sceneKitAssets : allSceneKitAssets) { PBXGroup resourcesGroup = targetGroup.getOrCreateChildGroupByName("Resources"); resourcesGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(sceneKitAssets.getPath()), Optional.empty())); } } private Path getConfigurationXcconfigPath(BuildTarget buildTarget, String input) { return BuildTargetPaths.getGenPath(projectFilesystem, buildTarget, "%s-" + input + ".xcconfig"); } @VisibleForTesting static Iterable<SourcePath> getHeaderSourcePaths(SourceSortedSet headers) { if (headers.getUnnamedSources().isPresent()) { return headers.getUnnamedSources().get(); } else { return headers.getNamedSources().get().values(); } } /** * Create target level configuration entries. * * @param target Xcode target for which the configurations will be set. * @param targetGroup Xcode group in which the configuration file references will be placed. * @param configurations Configurations as extracted from the BUCK file. * @param overrideBuildSettings Build settings that will override ones defined elsewhere. * @param defaultBuildSettings Target-inline level build settings that will be set if not already * defined. * @param appendBuildSettings Target-inline level build settings that will incorporate the * existing value or values at a higher level. */ private void setTargetBuildConfigurations(BuildTarget buildTarget, PBXTarget target, PBXGroup targetGroup, ImmutableMap<String, ImmutableMap<String, String>> configurations, ImmutableMap<String, String> cxxPlatformXcodeBuildSettings, ImmutableMap<String, String> overrideBuildSettings, ImmutableMap<String, String> defaultBuildSettings, ImmutableMap<String, String> appendBuildSettings) throws IOException { if (options.shouldGenerateHeaderSymlinkTreesOnly()) { return; } for (Map.Entry<String, ImmutableMap<String, String>> configurationEntry : configurations.entrySet()) { targetConfigNamesBuilder.add(configurationEntry.getKey()); ImmutableMap<String, String> targetLevelInlineSettings = configurationEntry.getValue(); XCBuildConfiguration outputConfiguration = target.getBuildConfigurationList() .getBuildConfigurationsByName().getUnchecked(configurationEntry.getKey()); HashMap<String, String> combinedOverrideConfigs = new HashMap<>(overrideBuildSettings); for (Map.Entry<String, String> entry : cxxPlatformXcodeBuildSettings.entrySet()) { String existingSetting = targetLevelInlineSettings.get(entry.getKey()); if (existingSetting == null) { combinedOverrideConfigs.put(entry.getKey(), entry.getValue()); } } for (Map.Entry<String, String> entry : defaultBuildSettings.entrySet()) { String existingSetting = targetLevelInlineSettings.get(entry.getKey()); if (existingSetting == null) { combinedOverrideConfigs.put(entry.getKey(), entry.getValue()); } } for (Map.Entry<String, String> entry : appendBuildSettings.entrySet()) { String existingSetting = targetLevelInlineSettings.get(entry.getKey()); String settingPrefix = existingSetting != null ? existingSetting : "$(inherited)"; combinedOverrideConfigs.put(entry.getKey(), settingPrefix + " " + entry.getValue()); } ImmutableSortedMap<String, String> mergedSettings = MoreMaps.mergeSorted(targetLevelInlineSettings, combinedOverrideConfigs); Path xcconfigPath = getConfigurationXcconfigPath(buildTarget, configurationEntry.getKey()); projectFilesystem.mkdirs(Objects.requireNonNull(xcconfigPath).getParent()); StringBuilder stringBuilder = new StringBuilder(); for (Map.Entry<String, String> entry : mergedSettings.entrySet()) { stringBuilder.append(entry.getKey()); stringBuilder.append(" = "); stringBuilder.append(entry.getValue()); stringBuilder.append('\n'); } String xcconfigContents = stringBuilder.toString(); if (MoreProjectFilesystems.fileContentsDiffer( new ByteArrayInputStream(xcconfigContents.getBytes(Charsets.UTF_8)), xcconfigPath, projectFilesystem)) { if (options.shouldGenerateReadOnlyFiles()) { projectFilesystem.writeContentsToPath(xcconfigContents, xcconfigPath, READ_ONLY_FILE_ATTRIBUTE); } else { projectFilesystem.writeContentsToPath(xcconfigContents, xcconfigPath); } } PBXFileReference fileReference = getConfigurationFileReference(targetGroup, xcconfigPath); outputConfiguration.setBaseConfigurationReference(fileReference); xcconfigPathsBuilder.add(projectFilesystem.getPathForRelativePath(xcconfigPath).toAbsolutePath()); } } private PBXFileReference getConfigurationFileReference(PBXGroup targetGroup, Path xcconfigPath) { return targetGroup.getOrCreateChildGroupByName("Configurations") .getOrCreateChildGroupByName("Buck (Do Not Modify)") .getOrCreateFileReferenceBySourceTreePath(new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(xcconfigPath), Optional.empty())); } private void collectProjectTargetWatchDependencies(String targetFlavorPostfix, PBXNativeTarget target, Iterable<TargetNode<?>> targetNodes) { for (TargetNode<?> targetNode : targetNodes) { String targetNodeFlavorPostfix = targetNode.getBuildTarget().getFlavorPostfix(); if (targetNodeFlavorPostfix.startsWith("#watch") && !targetNodeFlavorPostfix.equals(targetFlavorPostfix) && targetNode.getDescription() instanceof AppleBundleDescription) { addPBXTargetDependency(target, targetNode.getBuildTarget()); } } } private void collectBuildScriptDependencies(Iterable<TargetNode<?>> targetNodes, ImmutableList.Builder<TargetNode<?>> preRules, ImmutableList.Builder<TargetNode<?>> postRules) { for (TargetNode<?> targetNode : targetNodes) { if (targetNode.getDescription() instanceof JsBundleOutputsDescription) { postRules.add(targetNode); requiredBuildTargetsBuilder.add(targetNode.getBuildTarget()); } else if (targetNode.getDescription() instanceof XcodePostbuildScriptDescription) { postRules.add(targetNode); } else if (targetNode.getDescription() instanceof XcodePrebuildScriptDescription) { preRules.add(targetNode); } } } /** Adds the set of headers defined by headerVisibility to the merged header maps. */ private void addToMergedHeaderMap(TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, HeaderMap.Builder headerMapBuilder) { CxxLibraryDescription.CommonArg arg = targetNode.getConstructorArg(); boolean shouldCreateHeadersSymlinks = arg.getXcodePublicHeadersSymlinks() .orElse(cxxBuckConfig.getPublicHeadersSymlinksEnabled()); Path headerSymlinkTreeRoot = getPathToHeaderSymlinkTree(targetNode, HeaderVisibility.PUBLIC); Path basePath; if (shouldCreateHeadersSymlinks) { basePath = projectFilesystem.getRootPath().resolve(targetNode.getBuildTarget().getCellPath()) .resolve(headerSymlinkTreeRoot); } else { basePath = projectFilesystem.getRootPath(); } for (Map.Entry<Path, SourcePath> entry : getPublicCxxHeaders(targetNode).entrySet()) { Path path; if (shouldCreateHeadersSymlinks) { path = basePath.resolve(entry.getKey()); } else { path = basePath.resolve(resolveSourcePath(entry.getValue())); } headerMapBuilder.add(entry.getKey().toString(), path); } ImmutableMap<Path, Path> swiftHeaderMapEntries = getSwiftPublicHeaderMapEntriesForTarget(targetNode); for (Map.Entry<Path, Path> entry : swiftHeaderMapEntries.entrySet()) { headerMapBuilder.add(entry.getKey().toString(), entry.getValue()); } } /** Generates the merged header maps and write it to the public header symlink tree location. */ private void createMergedHeaderMap() throws IOException { HeaderMap.Builder headerMapBuilder = new HeaderMap.Builder(); Set<TargetNode<? extends CxxLibraryDescription.CommonArg>> processedNodes = new HashSet<>(); for (TargetNode<?> targetNode : targetGraph.getAll(targetsInRequiredProjects)) { // Includes the public headers of the dependencies in the merged header map. getAppleNativeNode(targetGraph, targetNode) .ifPresent(argTargetNode -> visitRecursiveHeaderSymlinkTrees(argTargetNode, (depNativeNode, headerVisibility) -> { if (processedNodes.contains(depNativeNode)) { return; } if (headerVisibility == HeaderVisibility.PUBLIC) { addToMergedHeaderMap(depNativeNode, headerMapBuilder); processedNodes.add(depNativeNode); } })); } // Writes the resulting header map. Path mergedHeaderMapRoot = getPathToMergedHeaderMap(); Path headerMapLocation = getHeaderMapLocationFromSymlinkTreeRoot(mergedHeaderMapRoot); Cell workspaceCell = projectCell.getCell(workspaceTarget.get()); workspaceCell.getFilesystem().mkdirs(mergedHeaderMapRoot); workspaceCell.getFilesystem().writeBytesToPath(headerMapBuilder.build().getBytes(), headerMapLocation); } private void createHeaderSymlinkTree(Map<Path, SourcePath> contents, ImmutableMap<Path, Path> nonSourcePaths, Optional<String> moduleName, Path headerSymlinkTreeRoot, boolean shouldCreateHeadersSymlinks, boolean shouldCreateHeaderMap, boolean shouldGenerateUmbrellaHeaderIfMissing) throws IOException { if (!shouldCreateHeaderMap && !shouldCreateHeadersSymlinks) { return; } LOG.verbose("Building header symlink tree at %s with contents %s", headerSymlinkTreeRoot, contents); ImmutableSortedMap.Builder<Path, Path> resolvedContentsBuilder = ImmutableSortedMap.naturalOrder(); for (Map.Entry<Path, SourcePath> entry : contents.entrySet()) { Path link = headerSymlinkTreeRoot.resolve(entry.getKey()); Path existing = projectFilesystem.resolve(resolveSourcePath(entry.getValue())); resolvedContentsBuilder.put(link, existing); } for (Map.Entry<Path, Path> entry : nonSourcePaths.entrySet()) { Path link = headerSymlinkTreeRoot.resolve(entry.getKey()); resolvedContentsBuilder.put(link, entry.getValue()); } ImmutableSortedMap<Path, Path> resolvedContents = resolvedContentsBuilder.build(); Path headerMapLocation = getHeaderMapLocationFromSymlinkTreeRoot(headerSymlinkTreeRoot); Path hashCodeFilePath = headerSymlinkTreeRoot.resolve(".contents-hash"); Optional<String> currentHashCode = projectFilesystem.readFileIfItExists(hashCodeFilePath); String newHashCode = getHeaderSymlinkTreeHashCode(resolvedContents, moduleName, shouldCreateHeadersSymlinks, shouldCreateHeaderMap).toString(); if (Optional.of(newHashCode).equals(currentHashCode)) { LOG.debug("Symlink tree at %s is up to date, not regenerating (key %s).", headerSymlinkTreeRoot, newHashCode); } else { LOG.debug("Updating symlink tree at %s (old key %s, new key %s).", headerSymlinkTreeRoot, currentHashCode, newHashCode); projectFilesystem.deleteRecursivelyIfExists(headerSymlinkTreeRoot); projectFilesystem.mkdirs(headerSymlinkTreeRoot); if (shouldCreateHeadersSymlinks) { for (Map.Entry<Path, Path> entry : resolvedContents.entrySet()) { Path link = entry.getKey(); Path existing = entry.getValue(); projectFilesystem.createParentDirs(link); projectFilesystem.createSymLink(link, existing, /* force */ false); } } projectFilesystem.writeContentsToPath(newHashCode, hashCodeFilePath); if (shouldCreateHeaderMap) { HeaderMap.Builder headerMapBuilder = new HeaderMap.Builder(); for (Map.Entry<Path, SourcePath> entry : contents.entrySet()) { if (shouldCreateHeadersSymlinks) { headerMapBuilder.add(entry.getKey().toString(), getHeaderMapRelativeSymlinkPathForEntry(entry, headerSymlinkTreeRoot)); } else { headerMapBuilder.add(entry.getKey().toString(), projectFilesystem.resolve(resolveSourcePath(entry.getValue()))); } } for (Map.Entry<Path, Path> entry : nonSourcePaths.entrySet()) { if (shouldCreateHeadersSymlinks) { headerMapBuilder.add(entry.getKey().toString(), getHeaderMapRelativeSymlinkPathForEntry(entry, headerSymlinkTreeRoot)); } else { headerMapBuilder.add(entry.getKey().toString(), entry.getValue()); } } projectFilesystem.writeBytesToPath(headerMapBuilder.build().getBytes(), headerMapLocation); } if (moduleName.isPresent() && resolvedContents.size() > 0) { if (shouldGenerateUmbrellaHeaderIfMissing) { writeUmbrellaHeaderIfNeeded(moduleName.get(), resolvedContents.keySet(), headerSymlinkTreeRoot); } boolean containsSwift = !nonSourcePaths.isEmpty(); if (containsSwift) { projectFilesystem.writeContentsToPath( new ModuleMap(moduleName.get(), ModuleMap.SwiftMode.INCLUDE_SWIFT_HEADER).render(), headerSymlinkTreeRoot.resolve(moduleName.get()).resolve("module.modulemap")); projectFilesystem.writeContentsToPath( new ModuleMap(moduleName.get(), ModuleMap.SwiftMode.EXCLUDE_SWIFT_HEADER).render(), headerSymlinkTreeRoot.resolve(moduleName.get()).resolve("objc.modulemap")); Path absoluteModuleRoot = projectFilesystem.getRootPath() .resolve(headerSymlinkTreeRoot.resolve(moduleName.get())); VFSOverlay vfsOverlay = new VFSOverlay( ImmutableSortedMap.of(absoluteModuleRoot.resolve("module.modulemap"), absoluteModuleRoot.resolve("objc.modulemap"))); projectFilesystem.writeContentsToPath(vfsOverlay.render(), getObjcModulemapVFSOverlayLocationFromSymlinkTreeRoot(headerSymlinkTreeRoot)); } else { projectFilesystem.writeContentsToPath( new ModuleMap(moduleName.get(), ModuleMap.SwiftMode.NO_SWIFT).render(), headerSymlinkTreeRoot.resolve(moduleName.get()).resolve("module.modulemap")); } Path absoluteModuleRoot = projectFilesystem.getRootPath() .resolve(headerSymlinkTreeRoot.resolve(moduleName.get())); VFSOverlay vfsOverlay = new VFSOverlay( ImmutableSortedMap.of(absoluteModuleRoot.resolve("module.modulemap"), absoluteModuleRoot.resolve("testing.modulemap"))); projectFilesystem.writeContentsToPath(vfsOverlay.render(), getTestingModulemapVFSOverlayLocationFromSymlinkTreeRoot(headerSymlinkTreeRoot)); projectFilesystem.writeContentsToPath("", // empty modulemap to allow non-modular imports for testing headerSymlinkTreeRoot.resolve(moduleName.get()).resolve("testing.modulemap")); } } headerSymlinkTrees.add(headerSymlinkTreeRoot); } private void writeUmbrellaHeaderIfNeeded(String moduleName, ImmutableSortedSet<Path> headerPaths, Path headerSymlinkTreeRoot) throws IOException { ImmutableList<String> headerPathStrings = headerPaths.stream().map(Path::getFileName).map(Path::toString) .collect(ImmutableList.toImmutableList()); if (!headerPathStrings.contains(moduleName + ".h")) { Path umbrellaPath = headerSymlinkTreeRoot.resolve(Paths.get(moduleName, moduleName + ".h")); Preconditions.checkState(!projectFilesystem.exists(umbrellaPath)); projectFilesystem.writeContentsToPath(new UmbrellaHeader(moduleName, headerPathStrings).render(), umbrellaPath); } } private Path getHeaderMapRelativeSymlinkPathForEntry(Map.Entry<Path, ?> entry, Path headerSymlinkTreeRoot) { return projectCell.getFilesystem() .resolve(projectCell.getFilesystem().getBuckPaths().getConfiguredBuckOut()).normalize() .relativize(projectCell.getFilesystem().resolve(headerSymlinkTreeRoot).resolve(entry.getKey()) .normalize()); } private HashCode getHeaderSymlinkTreeHashCode(ImmutableSortedMap<Path, Path> contents, Optional<String> moduleName, boolean shouldCreateHeadersSymlinks, boolean shouldCreateHeaderMap) { Hasher hasher = Hashing.sha1().newHasher(); hasher.putBytes(ruleKeyConfiguration.getCoreKey().getBytes(Charsets.UTF_8)); String symlinkState = shouldCreateHeadersSymlinks ? "symlinks-enabled" : "symlinks-disabled"; byte[] symlinkStateValue = symlinkState.getBytes(Charsets.UTF_8); hasher.putInt(symlinkStateValue.length); hasher.putBytes(symlinkStateValue); String hmapState = shouldCreateHeaderMap ? "hmap-enabled" : "hmap-disabled"; byte[] hmapStateValue = hmapState.getBytes(Charsets.UTF_8); hasher.putInt(hmapStateValue.length); hasher.putBytes(hmapStateValue); if (moduleName.isPresent()) { byte[] moduleNameValue = moduleName.get().getBytes(Charsets.UTF_8); hasher.putInt(moduleNameValue.length); hasher.putBytes(moduleNameValue); } hasher.putInt(0); for (Map.Entry<Path, Path> entry : contents.entrySet()) { byte[] key = entry.getKey().toString().getBytes(Charsets.UTF_8); byte[] value = entry.getValue().toString().getBytes(Charsets.UTF_8); hasher.putInt(key.length); hasher.putBytes(key); hasher.putInt(value.length); hasher.putBytes(value); } return hasher.hash(); } private void addCoreDataModelBuildPhase(PBXGroup targetGroup, Iterable<AppleWrapperResourceArg> dataModels) throws IOException { // TODO(coneko): actually add a build phase for (AppleWrapperResourceArg dataModel : dataModels) { // Core data models go in the resources group also. PBXGroup resourcesGroup = targetGroup.getOrCreateChildGroupByName("Resources"); if (CoreDataModelDescription.isVersionedDataModel(dataModel)) { // It's safe to do I/O here to figure out the current version because we're returning all // the versions and the file pointing to the current version from // getInputsToCompareToOutput(), so the rule will be correctly detected as stale if any of // them change. String currentVersionFileName = ".xccurrentversion"; String currentVersionKey = "_XCCurrentVersionName"; XCVersionGroup versionGroup = resourcesGroup.getOrCreateChildVersionGroupsBySourceTreePath( new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(dataModel.getPath()), Optional.empty())); projectFilesystem.walkRelativeFileTree(dataModel.getPath(), new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { if (dir.equals(dataModel.getPath())) { return FileVisitResult.CONTINUE; } versionGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(dir), Optional.empty())); return FileVisitResult.SKIP_SUBTREE; } }); Path currentVersionPath = dataModel.getPath().resolve(currentVersionFileName); try (InputStream in = projectFilesystem.newFileInputStream(currentVersionPath)) { NSObject rootObject; try { rootObject = PropertyListParser.parse(in); } catch (IOException e) { throw e; } catch (Exception e) { rootObject = null; } if (!(rootObject instanceof NSDictionary)) { throw new HumanReadableException("Malformed %s file.", currentVersionFileName); } NSDictionary rootDictionary = (NSDictionary) rootObject; NSObject currentVersionName = rootDictionary.objectForKey(currentVersionKey); if (!(currentVersionName instanceof NSString)) { throw new HumanReadableException("Malformed %s file.", currentVersionFileName); } PBXFileReference ref = versionGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative( dataModel.getPath().resolve(currentVersionName.toString())), Optional.empty())); versionGroup.setCurrentVersion(ref); } catch (NoSuchFileException e) { if (versionGroup.getChildren().size() == 1) { versionGroup.setCurrentVersion(Iterables.get(versionGroup.getChildren(), 0)); } } } else { resourcesGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(dataModel.getPath()), Optional.empty())); } } } private Optional<CopyFilePhaseDestinationSpec> getDestinationSpec(TargetNode<?> targetNode) { if (targetNode.getDescription() instanceof AppleBundleDescription) { AppleBundleDescriptionArg arg = (AppleBundleDescriptionArg) targetNode.getConstructorArg(); AppleBundleExtension extension = arg.getExtension().isLeft() ? arg.getExtension().getLeft() : AppleBundleExtension.BUNDLE; switch (extension) { case FRAMEWORK: return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.FRAMEWORKS)); case APPEX: case PLUGIN: return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.PLUGINS)); case PREFPANE: return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.RESOURCES)); case APP: if (isWatchApplicationNode(targetNode)) { return Optional.of(CopyFilePhaseDestinationSpec.builder() .setDestination(PBXCopyFilesBuildPhase.Destination.PRODUCTS) .setPath("$(CONTENTS_FOLDER_PATH)/Watch").build()); } else { return Optional .of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.EXECUTABLES)); } case QLGENERATOR: return Optional.of(CopyFilePhaseDestinationSpec.builder() .setDestination(PBXCopyFilesBuildPhase.Destination.QLGENERATOR) .setPath("$(CONTENTS_FOLDER_PATH)/Library/QuickLook").build()); case BUNDLE: return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.PLUGINS)); case XPC: return Optional.of(CopyFilePhaseDestinationSpec.builder() .setDestination(PBXCopyFilesBuildPhase.Destination.XPC) .setPath("$(CONTENTS_FOLDER_PATH)/XPCServices").build()); // $CASES-OMITTED$ default: return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.PRODUCTS)); } } else if (targetNode.getDescription() instanceof AppleLibraryDescription || targetNode.getDescription() instanceof CxxLibraryDescription) { if (targetNode.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR)) { return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.FRAMEWORKS)); } else { return Optional.empty(); } } else if (targetNode.getDescription() instanceof AppleBinaryDescription) { return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.EXECUTABLES)); } else if (targetNode.getDescription() instanceof HalideLibraryDescription) { return Optional.empty(); } else if (targetNode.getDescription() instanceof CoreDataModelDescription || targetNode.getDescription() instanceof SceneKitAssetsDescription) { return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.RESOURCES)); } else if (targetNode.getDescription() instanceof PrebuiltAppleFrameworkDescription) { return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.FRAMEWORKS)); } else if (targetNode.getDescription() instanceof PrebuiltCxxLibraryDescription) { return Optional.empty(); } else { String message = "Unexpected type: " + targetNode.getDescription().getClass() + " for target " + targetNode.getBuildTarget() + "\n" + "It means that it's been added as a dependency of another target but it's not a supported type of dependency."; throw new RuntimeException(message); } } /** * Convert a list of rules that should be somehow included into the bundle, into build phases * which copies them into the bundle. The parameters of these copy phases are divined by * scrutinizing the type of node we want to include. */ private ImmutableList<PBXBuildPhase> getCopyFilesBuildPhases(Iterable<TargetNode<?>> copiedNodes) { // Bucket build rules into bins by their destinations ImmutableSetMultimap.Builder<CopyFilePhaseDestinationSpec, TargetNode<?>> ruleByDestinationSpecBuilder = ImmutableSetMultimap .builder(); for (TargetNode<?> copiedNode : copiedNodes) { getDestinationSpec(copiedNode).ifPresent(copyFilePhaseDestinationSpec -> ruleByDestinationSpecBuilder .put(copyFilePhaseDestinationSpec, copiedNode)); } ImmutableList.Builder<PBXBuildPhase> phases = ImmutableList.builder(); ImmutableSetMultimap<CopyFilePhaseDestinationSpec, TargetNode<?>> ruleByDestinationSpec = ruleByDestinationSpecBuilder .build(); // Emit a copy files phase for each destination. for (CopyFilePhaseDestinationSpec destinationSpec : ruleByDestinationSpec.keySet()) { Iterable<TargetNode<?>> targetNodes = ruleByDestinationSpec.get(destinationSpec); phases.add(getSingleCopyFilesBuildPhase(destinationSpec, targetNodes)); } return phases.build(); } private PBXCopyFilesBuildPhase getSingleCopyFilesBuildPhase(CopyFilePhaseDestinationSpec destinationSpec, Iterable<TargetNode<?>> targetNodes) { PBXCopyFilesBuildPhase copyFilesBuildPhase = new PBXCopyFilesBuildPhase(destinationSpec); HashSet<UnflavoredBuildTarget> frameworkTargets = new HashSet<UnflavoredBuildTarget>(); for (TargetNode<?> targetNode : targetNodes) { /* Shared libraries with no sources will not produce an output file, so do not add them to the copy phase since the Xcode build will fail for a missing file. */ boolean shouldCopy = shouldCopyOutputFile(targetNode); PBXFileReference fileReference = getLibraryFileReference(targetNode); PBXBuildFile buildFile = new PBXBuildFile(fileReference); if (fileReference.getExplicitFileType().equals(Optional.of("wrapper.framework"))) { UnflavoredBuildTarget buildTarget = targetNode.getBuildTarget().getUnflavoredBuildTarget(); if (frameworkTargets.contains(buildTarget)) { continue; } frameworkTargets.add(buildTarget); NSDictionary settings = new NSDictionary(); settings.put("ATTRIBUTES", new String[] { "CodeSignOnCopy", "RemoveHeadersOnCopy" }); buildFile.setSettings(Optional.of(settings)); } if (shouldCopy) { copyFilesBuildPhase.getFiles().add(buildFile); } } return copyFilesBuildPhase; } /** Create the project bundle structure and write {@code project.pbxproj}. */ private void writeProjectFile(PBXProject project) throws IOException { XcodeprojSerializer serializer = new XcodeprojSerializer(gidGenerator, project); NSDictionary rootObject = serializer.toPlist(); Path xcodeprojDir = outputDirectory.resolve(projectName + ".xcodeproj"); projectFilesystem.mkdirs(xcodeprojDir); Path serializedProject = xcodeprojDir.resolve("project.pbxproj"); String contentsToWrite = rootObject.toXMLPropertyList(); // Before we write any files, check if the file contents have changed. if (MoreProjectFilesystems.fileContentsDiffer( new ByteArrayInputStream(contentsToWrite.getBytes(Charsets.UTF_8)), serializedProject, projectFilesystem)) { LOG.debug("Regenerating project at %s", serializedProject); if (options.shouldGenerateReadOnlyFiles()) { projectFilesystem.writeContentsToPath(contentsToWrite, serializedProject, READ_ONLY_FILE_ATTRIBUTE); } else { projectFilesystem.writeContentsToPath(contentsToWrite, serializedProject); } } else { LOG.debug("Not regenerating project at %s (contents have not changed)", serializedProject); } } private String getModuleName(TargetNode<?> buildTargetNode) { Optional<String> swiftName = TargetNodes.castArg(buildTargetNode, SwiftLibraryDescriptionArg.class) .flatMap(node -> node.getConstructorArg().getModuleName()); if (swiftName.isPresent()) { return swiftName.get(); } return TargetNodes.castArg(buildTargetNode, CommonArg.class) .flatMap(node -> node.getConstructorArg().getModuleName()) .orElse(buildTargetNode.getBuildTarget().getShortName()); } private String getProductName(TargetNode<?> buildTargetNode) { return TargetNodes.castArg(buildTargetNode, AppleBundleDescriptionArg.class) .flatMap(node -> node.getConstructorArg().getProductName()) .orElse(getProductNameForBuildTargetNode(buildTargetNode)); } private String getProductNameForBuildTargetNode(TargetNode<?> targetNode) { Optional<TargetNode<CxxLibraryDescription.CommonArg>> library = getLibraryNode(targetGraph, targetNode); boolean isStaticLibrary = library.isPresent() && !AppleLibraryDescription.isNotStaticallyLinkedLibraryNode(library.get()); if (isStaticLibrary) { Optional<String> basename = library.get().getConstructorArg().getStaticLibraryBasename(); if (basename.isPresent()) { return basename.get(); } return CxxDescriptionEnhancer.getStaticLibraryBasename(targetNode.getBuildTarget(), "", cxxBuckConfig.isUniqueLibraryNameEnabled()); } else { return targetNode.getBuildTarget().getShortName(); } } private static Path getDerivedSourcesDirectoryForBuildTarget(BuildTarget buildTarget, ProjectFilesystem fs) { String fullTargetName = buildTarget.getFullyQualifiedName(); byte[] utf8Bytes = fullTargetName.getBytes(Charset.forName("UTF-8")); Hasher hasher = Hashing.sha1().newHasher(); hasher.putBytes(utf8Bytes); String targetSha1Hash = hasher.hash().toString(); String targetFolderName = buildTarget.getShortName() + "-" + targetSha1Hash; Path xcodeDir = fs.getBuckPaths().getXcodeDir(); Path derivedSourcesDir = xcodeDir.resolve("derived-sources").resolve(targetFolderName); return derivedSourcesDir; } private String getSwiftObjCGeneratedHeaderName(TargetNode<?> node) { return getModuleName(node) + "-Swift.h"; } private Path getSwiftObjCGeneratedHeaderPath(TargetNode<?> node, ProjectFilesystem fs) { Path derivedSourcesDir = getDerivedSourcesDirectoryForBuildTarget(node.getBuildTarget(), fs); return derivedSourcesDir.resolve(getSwiftObjCGeneratedHeaderName(node)); } private ImmutableMap<Path, Path> getSwiftPublicHeaderMapEntriesForTarget( TargetNode<? extends CxxLibraryDescription.CommonArg> node) { boolean hasSwiftVersionArg = getSwiftVersionForTargetNode(node).isPresent(); if (!hasSwiftVersionArg) { return ImmutableMap.of(); } Optional<TargetNode<AppleNativeTargetDescriptionArg>> maybeAppleNode = TargetNodes.castArg(node, AppleNativeTargetDescriptionArg.class); if (!maybeAppleNode.isPresent()) { return ImmutableMap.of(); } TargetNode<? extends AppleNativeTargetDescriptionArg> appleNode = maybeAppleNode.get(); if (!projGenerationStateCache.targetContainsSwiftSourceCode(appleNode)) { return ImmutableMap.of(); } BuildTarget buildTarget = appleNode.getBuildTarget(); Path headerPrefix = AppleDescriptions.getHeaderPathPrefix(appleNode.getConstructorArg(), buildTarget); Path relativePath = headerPrefix.resolve(getSwiftObjCGeneratedHeaderName(appleNode)); ImmutableSortedMap.Builder<Path, Path> builder = ImmutableSortedMap.naturalOrder(); builder.put(relativePath, getSwiftObjCGeneratedHeaderPath(appleNode, projectFilesystem).toAbsolutePath()); return builder.build(); } /** @param targetNode Must have a header symlink tree or an exception will be thrown. */ private Path getHeaderSymlinkTreePath(TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, HeaderVisibility headerVisibility) { Path treeRoot = getAbsolutePathToHeaderSymlinkTree(targetNode, headerVisibility); if (options.shouldUseAbsoluteHeaderMapPaths()) { return treeRoot; } else { return projectFilesystem.resolve(outputDirectory).relativize(treeRoot); } } private Path getObjcModulemapVFSOverlayLocationFromSymlinkTreeRoot(Path headerSymlinkTreeRoot) { return headerSymlinkTreeRoot.resolve("objc-module-overlay.yaml"); } private Path getTestingModulemapVFSOverlayLocationFromSymlinkTreeRoot(Path headerSymlinkTreeRoot) { return headerSymlinkTreeRoot.resolve("testing-overlay.yaml"); } private Path getHeaderMapLocationFromSymlinkTreeRoot(Path headerSymlinkTreeRoot) { return headerSymlinkTreeRoot.resolve(".hmap"); } private Path getHeaderSearchPathFromSymlinkTreeRoot(Path headerSymlinkTreeRoot) { if (!options.shouldUseHeaderMaps()) { return headerSymlinkTreeRoot; } else { return getHeaderMapLocationFromSymlinkTreeRoot(headerSymlinkTreeRoot); } } private Path getRelativePathToMergedHeaderMap() { Path treeRoot = getPathToMergedHeaderMap(); Path cellRoot = MorePaths.relativize(projectFilesystem.getRootPath(), workspaceTarget.get().getCellPath()); return pathRelativizer.outputDirToRootRelative(cellRoot.resolve(treeRoot)); } private String getBuiltProductsRelativeTargetOutputPath(TargetNode<?> targetNode) { if (targetNode.getDescription() instanceof AppleBinaryDescription || targetNode.getDescription() instanceof AppleTestDescription || (targetNode.getDescription() instanceof AppleBundleDescription && !isFrameworkBundle((AppleBundleDescriptionArg) targetNode.getConstructorArg()))) { // TODO(grp): These should be inside the path below. Right now, that causes issues with // bundle loader paths hardcoded in .xcconfig files that don't expect the full target path. // It also causes issues where Xcode doesn't know where to look for a final .app to run it. return "."; } else { return BaseEncoding.base32().omitPadding() .encode(targetNode.getBuildTarget().getFullyQualifiedName().getBytes()); } } private String getTargetOutputPath(TargetNode<?> targetNode) { return Joiner.on('/').join("$BUILT_PRODUCTS_DIR", getBuiltProductsRelativeTargetOutputPath(targetNode)); } @SuppressWarnings("unchecked") private static Optional<TargetNode<CxxLibraryDescription.CommonArg>> getAppleNativeNodeOfType( TargetGraph targetGraph, TargetNode<?> targetNode, Set<Class<? extends DescriptionWithTargetGraph<?>>> nodeTypes, Set<AppleBundleExtension> bundleExtensions) { Optional<TargetNode<CxxLibraryDescription.CommonArg>> nativeNode = Optional.empty(); if (nodeTypes.contains(targetNode.getDescription().getClass())) { nativeNode = Optional.of((TargetNode<CxxLibraryDescription.CommonArg>) targetNode); } else if (targetNode.getDescription() instanceof AppleBundleDescription) { TargetNode<AppleBundleDescriptionArg> bundle = (TargetNode<AppleBundleDescriptionArg>) targetNode; Either<AppleBundleExtension, String> extension = bundle.getConstructorArg().getExtension(); if (extension.isLeft() && bundleExtensions.contains(extension.getLeft())) { nativeNode = Optional.of((TargetNode<CxxLibraryDescription.CommonArg>) targetGraph .get(getBundleBinaryTarget(bundle))); } } return nativeNode; } private static BuildTarget getBundleBinaryTarget(TargetNode<AppleBundleDescriptionArg> bundle) { return bundle.getConstructorArg().getBinary().orElseThrow( () -> new HumanReadableException("apple_bundle rules without binary attribute are not supported.")); } private static Optional<TargetNode<CxxLibraryDescription.CommonArg>> getAppleNativeNode(TargetGraph targetGraph, TargetNode<?> targetNode) { return getAppleNativeNodeOfType(targetGraph, targetNode, APPLE_NATIVE_DESCRIPTION_CLASSES, APPLE_NATIVE_BUNDLE_EXTENSIONS); } private static Optional<TargetNode<CxxLibraryDescription.CommonArg>> getLibraryNode(TargetGraph targetGraph, TargetNode<?> targetNode) { return getAppleNativeNodeOfType(targetGraph, targetNode, ImmutableSet.of(AppleLibraryDescription.class, CxxLibraryDescription.class), ImmutableSet.of(AppleBundleExtension.FRAMEWORK)); } private ImmutableSet<Path> collectRecursiveHeaderSearchPaths( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode) { ImmutableSet.Builder<Path> builder = ImmutableSet.builder(); if (shouldMergeHeaderMaps()) { builder.add(getHeaderSearchPathFromSymlinkTreeRoot( getHeaderSymlinkTreePath(targetNode, HeaderVisibility.PRIVATE))); if (options.shouldUseAbsoluteHeaderMapPaths()) { Cell workspaceCell = projectCell.getCell(workspaceTarget.get()); Path absolutePath = workspaceCell.getFilesystem().resolve(getPathToMergedHeaderMap()); builder.add(getHeaderSearchPathFromSymlinkTreeRoot(absolutePath)); } else { builder.add(getHeaderSearchPathFromSymlinkTreeRoot(getRelativePathToMergedHeaderMap())); } visitRecursivePrivateHeaderSymlinkTreesForTests(targetNode, (nativeNode, headerVisibility) -> { builder.add(getHeaderSearchPathFromSymlinkTreeRoot( getHeaderSymlinkTreePath(nativeNode, headerVisibility))); }); } else { for (Path headerSymlinkTreePath : collectRecursiveHeaderSymlinkTrees(targetNode)) { builder.add(getHeaderSearchPathFromSymlinkTreeRoot(headerSymlinkTreePath)); } } for (Path halideHeaderPath : collectRecursiveHalideLibraryHeaderPaths(targetNode)) { builder.add(halideHeaderPath); } return builder.build(); } private ImmutableSet<Path> collectRecursiveHeaderMapBases( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode) { ImmutableSet.Builder<Path> builder = ImmutableSet.builder(); if (shouldMergeHeaderMaps()) { return ImmutableSet.of(); } else { visitRecursiveHeaderSymlinkTrees(targetNode, (nativeNode, headerVisibility) -> { if (options.shouldUseAbsoluteHeaderMapPaths()) { ProjectFilesystem filesystem = nativeNode.getFilesystem(); Path buckOut = filesystem.resolve(filesystem.getBuckPaths().getConfiguredBuckOut()); builder.add(buckOut.toAbsolutePath().normalize()); } else { builder.add(targetNode.getFilesystem().resolve(outputDirectory) .relativize(nativeNode.getFilesystem() .resolve(nativeNode.getFilesystem().getBuckPaths().getConfiguredBuckOut()))); } }); } return builder.build(); } @SuppressWarnings("unchecked") private ImmutableSet<Path> collectRecursiveHalideLibraryHeaderPaths( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode) { ImmutableSet.Builder<Path> builder = ImmutableSet.builder(); for (TargetNode<?> input : AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.RecursiveDependenciesMode.BUILDING, targetNode, Optional.of(ImmutableSet.of(HalideLibraryDescription.class)))) { TargetNode<HalideLibraryDescriptionArg> halideNode = (TargetNode<HalideLibraryDescriptionArg>) input; BuildTarget buildTarget = halideNode.getBuildTarget(); builder.add(pathRelativizer.outputDirToRootRelative(HalideCompile.headerOutputPath( buildTarget.withFlavors(HalideLibraryDescription.HALIDE_COMPILE_FLAVOR, defaultCxxPlatform.getFlavor()), projectFilesystem, halideNode.getConstructorArg().getFunctionName()).getParent())); } return builder.build(); } private void visitRecursiveHeaderSymlinkTrees(TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, BiConsumer<TargetNode<? extends CxxLibraryDescription.CommonArg>, HeaderVisibility> visitor) { // Visits public and private headers from current target. visitor.accept(targetNode, HeaderVisibility.PRIVATE); visitor.accept(targetNode, HeaderVisibility.PUBLIC); // Visits public headers from dependencies. for (TargetNode<?> input : AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.RecursiveDependenciesMode.BUILDING, targetNode, Optional.of(xcodeDescriptions.getXCodeDescriptions()))) { getAppleNativeNode(targetGraph, input) .ifPresent(argTargetNode -> visitor.accept(argTargetNode, HeaderVisibility.PUBLIC)); } visitRecursivePrivateHeaderSymlinkTreesForTests(targetNode, visitor); } private void visitRecursivePrivateHeaderSymlinkTreesForTests( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, BiConsumer<TargetNode<? extends CxxLibraryDescription.CommonArg>, HeaderVisibility> visitor) { // Visits headers of source under tests. ImmutableSet<TargetNode<?>> directDependencies = ImmutableSet .copyOf(targetGraph.getAll(targetNode.getBuildDeps())); for (TargetNode<?> dependency : directDependencies) { Optional<TargetNode<CxxLibraryDescription.CommonArg>> nativeNode = getAppleNativeNode(targetGraph, dependency); if (nativeNode.isPresent() && isSourceUnderTest(dependency, nativeNode.get(), targetNode)) { visitor.accept(nativeNode.get(), HeaderVisibility.PRIVATE); } } } private ImmutableSet<Path> collectRecursiveHeaderSymlinkTrees( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode) { ImmutableSet.Builder<Path> builder = ImmutableSet.builder(); visitRecursiveHeaderSymlinkTrees(targetNode, (nativeNode, headerVisibility) -> { builder.add(getHeaderSymlinkTreePath(nativeNode, headerVisibility)); }); return builder.build(); } private ImmutableSet<Path> collectRecursiveSwiftIncludePaths( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode) { ImmutableSet.Builder<Path> builder = ImmutableSet.builder(); visitRecursiveHeaderSymlinkTrees(targetNode, (nativeNode, headerVisibility) -> { if (headerVisibility.equals(HeaderVisibility.PUBLIC) && isModularAppleLibrary(nativeNode)) { builder.add(getHeaderSymlinkTreePath(nativeNode, headerVisibility)); } }); return builder.build(); } private boolean isSourceUnderTest(TargetNode<?> dependencyNode, TargetNode<CxxLibraryDescription.CommonArg> nativeNode, TargetNode<?> testNode) { boolean isSourceUnderTest = nativeNode.getConstructorArg().getTests().contains(testNode.getBuildTarget()); if (dependencyNode != nativeNode && dependencyNode.getConstructorArg() instanceof HasTests) { ImmutableSortedSet<BuildTarget> tests = ((HasTests) dependencyNode.getConstructorArg()).getTests(); if (tests.contains(testNode.getBuildTarget())) { isSourceUnderTest = true; } } return isSourceUnderTest; } /** List of frameworks and libraries that goes into the "Link Binary With Libraries" phase. */ private Iterable<FrameworkPath> collectRecursiveFrameworkDependencies(TargetNode<?> targetNode) { return FluentIterable .from(AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.RecursiveDependenciesMode.LINKING, targetNode, ImmutableSet.<Class<? extends BaseDescription<?>>>builder() .addAll(xcodeDescriptions.getXCodeDescriptions()) .add(PrebuiltAppleFrameworkDescription.class).build())) .transformAndConcat(input -> { // Libraries and bundles which has system frameworks and libraries. Optional<TargetNode<CxxLibraryDescription.CommonArg>> library = getLibraryNode(targetGraph, input); if (library.isPresent() && !AppleLibraryDescription.isNotStaticallyLinkedLibraryNode(library.get())) { return Iterables.concat(library.get().getConstructorArg().getFrameworks(), library.get().getConstructorArg().getLibraries()); } Optional<TargetNode<PrebuiltAppleFrameworkDescriptionArg>> prebuilt = TargetNodes.castArg(input, PrebuiltAppleFrameworkDescriptionArg.class); if (prebuilt.isPresent()) { return Iterables.concat(prebuilt.get().getConstructorArg().getFrameworks(), prebuilt.get().getConstructorArg().getLibraries(), ImmutableList.of(FrameworkPath .ofSourcePath(prebuilt.get().getConstructorArg().getFramework()))); } Optional<TargetNode<PrebuiltCxxLibraryDescriptionArg>> prebuiltCxxLib = TargetNodes .castArg(input, PrebuiltCxxLibraryDescriptionArg.class); if (prebuiltCxxLib.isPresent()) { Iterable<FrameworkPath> deps = Iterables.concat( prebuiltCxxLib.get().getConstructorArg().getFrameworks(), prebuiltCxxLib.get().getConstructorArg().getLibraries()); if (prebuiltCxxLib.get().getConstructorArg().getSharedLib().isPresent()) { return Iterables.concat(deps, ImmutableList.of(FrameworkPath .ofSourcePath(prebuiltCxxLib.get().getConstructorArg().getSharedLib().get()))); } else if (prebuiltCxxLib.get().getConstructorArg().getStaticLib().isPresent()) { return Iterables.concat(deps, ImmutableList.of(FrameworkPath .ofSourcePath(prebuiltCxxLib.get().getConstructorArg().getStaticLib().get()))); } else if (prebuiltCxxLib.get().getConstructorArg().getStaticPicLib().isPresent()) { return Iterables.concat(deps, ImmutableList.of(FrameworkPath.ofSourcePath( prebuiltCxxLib.get().getConstructorArg().getStaticPicLib().get()))); } } return ImmutableList.of(); }); } private Iterable<StringWithMacros> collectRecursiveExportedPreprocessorFlags(TargetNode<?> targetNode) { return FluentIterable .from(AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.RecursiveDependenciesMode.BUILDING, targetNode, ImmutableSet.of(AppleLibraryDescription.class, CxxLibraryDescription.class))) .append(targetNode) .transformAndConcat(input -> TargetNodes.castArg(input, CxxLibraryDescription.CommonArg.class) .map(input1 -> input1.getConstructorArg().getExportedPreprocessorFlags()) .orElse(ImmutableList.of())); } private Iterable<PatternMatchedCollection<ImmutableList<StringWithMacros>>> collectRecursiveExportedPlatformPreprocessorFlags( TargetNode<?> targetNode) { return FluentIterable .from(AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.RecursiveDependenciesMode.BUILDING, targetNode, ImmutableSet.of(AppleLibraryDescription.class, CxxLibraryDescription.class, PrebuiltCxxLibraryDescription.class, PrebuiltAppleFrameworkDescription.class))) .append(targetNode).transformAndConcat(input -> { Optional<Iterable<PatternMatchedCollection<ImmutableList<StringWithMacros>>>> result; result = TargetNodes.castArg(input, CxxLibraryDescription.CommonArg.class) .map(input1 -> ImmutableList .of(input1.getConstructorArg().getExportedPlatformPreprocessorFlags())); if (result.isPresent()) { return result.get(); } result = TargetNodes.castArg(input, PrebuiltCxxLibraryDescriptionArg.class) .map(input1 -> ImmutableList .of(input1.getConstructorArg().getExportedPlatformPreprocessorFlags())); if (result.isPresent()) { return result.get(); } return ImmutableList.of(); }); } private Iterable<StringWithMacros> collectRecursiveSystemPreprocessorFlags(TargetNode<?> targetNode) { return FluentIterable .from(AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.RecursiveDependenciesMode.BUILDING, targetNode, ImmutableSet.of(PrebuiltCxxLibraryDescription.class))) .append(targetNode).transformAndConcat(input -> { Optional<ImmutableList<SourcePath>> result; result = TargetNodes.castArg(input, PrebuiltCxxLibraryDescriptionArg.class) .map(input1 -> input1.getConstructorArg().getHeaderDirs().orElse(ImmutableList.of())); if (result.isPresent()) { return result.get(); } return ImmutableList.of(); }).transform(headerDir -> { if (headerDir instanceof BuildTargetSourcePath) { BuildTargetSourcePath targetSourcePath = (BuildTargetSourcePath) headerDir; return StringWithMacros.of(ImmutableList.of(Either.ofLeft("-isystem"), Either.ofRight( MacroContainer.of(LocationMacro.of(targetSourcePath.getTarget()), false)))); } return StringWithMacros.of(ImmutableList.of(Either.ofLeft("-isystem" + headerDir))); }); } private ImmutableList<StringWithMacros> collectRecursiveExportedLinkerFlags(TargetNode<?> targetNode) { return FluentIterable .from(AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.RecursiveDependenciesMode.LINKING, targetNode, ImmutableSet.of(AppleLibraryDescription.class, CxxLibraryDescription.class, HalideLibraryDescription.class, PrebuiltCxxLibraryDescription.class, PrebuiltAppleFrameworkDescription.class))) .append(targetNode).transformAndConcat(input -> { Optional<ImmutableList<StringWithMacros>> result; result = TargetNodes.castArg(input, CxxLibraryDescription.CommonArg.class) .map(input1 -> input1.getConstructorArg().getExportedLinkerFlags()); if (result.isPresent()) { return result.get(); } result = TargetNodes.castArg(input, PrebuiltCxxLibraryDescriptionArg.class) .map(input1 -> input1.getConstructorArg().getExportedLinkerFlags()); if (result.isPresent()) { return result.get(); } return ImmutableList.of(); }).toList(); } private Iterable<PatternMatchedCollection<ImmutableList<StringWithMacros>>> collectRecursiveExportedPlatformLinkerFlags( TargetNode<?> targetNode) { return FluentIterable .from(AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.RecursiveDependenciesMode.LINKING, targetNode, ImmutableSet.of(AppleLibraryDescription.class, CxxLibraryDescription.class, HalideLibraryDescription.class))) .append(targetNode) .transformAndConcat(input -> TargetNodes.castArg(input, CxxLibraryDescription.CommonArg.class).map( input1 -> ImmutableList.of(input1.getConstructorArg().getExportedPlatformLinkerFlags())) .orElse(ImmutableList.of())); } private Iterable<String> collectModularTargetSpecificSwiftFlags( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode) { ImmutableList.Builder<String> targetSpecificSwiftFlags = ImmutableList.builder(); targetSpecificSwiftFlags.add("-import-underlying-module"); Path vfsOverlay = getObjcModulemapVFSOverlayLocationFromSymlinkTreeRoot( getPathToHeaderSymlinkTree(targetNode, HeaderVisibility.PUBLIC)); targetSpecificSwiftFlags.add("-Xcc"); targetSpecificSwiftFlags.add("-ivfsoverlay"); targetSpecificSwiftFlags.add("-Xcc"); targetSpecificSwiftFlags.add("$REPO_ROOT/" + vfsOverlay); return targetSpecificSwiftFlags.build(); } private boolean isTargetNodeApplicationTestTarget(TargetNode<?> targetNode, Optional<TargetNode<AppleBundleDescriptionArg>> bundleLoaderNode) { // This is an application test if it is not a UI test and has a bundle loader. Optional<TargetNode<AppleTestDescriptionArg>> testNode = TargetNodes.castArg(targetNode, AppleTestDescriptionArg.class); if (testNode.isPresent() && bundleLoaderNode.isPresent()) { AppleTestDescriptionArg testArg = testNode.get().getConstructorArg(); return !testArg.getIsUiTest(); } else { return false; } } private boolean isLibraryWithForceLoad(TargetNode<?> input) { Optional<TargetNode<CxxLibraryDescription.CommonArg>> library = getLibraryNode(targetGraph, input); if (!library.isPresent()) { return false; } else { TargetNode<CxxLibraryDescription.CommonArg> target = library.get(); CxxLibraryDescription.CommonArg arg = target.getConstructorArg(); return arg.getLinkWhole().orElse(false); } } private boolean shouldExcludeLibraryFromProject(TargetNode<?> targetNode) { // targets with flavor #compilation-database are not meant to be built by Xcode, they are used // only to generate the compile commands for a library during buck build return targetNode.getBuildTarget().getFlavors().contains(CxxCompilationDatabase.COMPILATION_DATABASE); } private boolean isLibraryBuiltByCurrentProject(TargetNode<?> targetNode) { return isBuiltByCurrentProject(targetNode.getBuildTarget()); } private ImmutableSet<PBXFileReference> targetNodesSetToPBXFileReference( ImmutableSet<TargetNode<?>> targetNodes) { return FluentIterable.from(targetNodes).transform(this::getLibraryFileReference).toSet(); } private ImmutableSet<PBXFileReference> targetNodesIterableToPBXFileReference( FluentIterable<TargetNode<?>> targetNodes) { return targetNodes.transform(this::getLibraryFileReference).toSet(); } private FluentIterable<TargetNode<?>> collectRecursiveLibraryDepTargets(TargetNode<?> targetNode) { FluentIterable<TargetNode<?>> allDeps = FluentIterable .from(AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(xcodeDescriptions, targetGraph, Optional.of(dependenciesCache), AppleBuildRules.RecursiveDependenciesMode.LINKING, targetNode, xcodeDescriptions.getXCodeDescriptions())); return allDeps.filter(this::isLibraryWithSourcesToCompile); } private ImmutableSet<PBXFileReference> collectRecursiveLibraryDependencies(TargetNode<?> targetNode) { return targetNodesIterableToPBXFileReference(collectRecursiveLibraryDepTargets(targetNode)); } private ImmutableSet<TargetNode<?>> filterRecursiveLibraryDepTargetsWithSwiftSources( FluentIterable<TargetNode<?>> targetNodes) { return targetNodes.filter(this::isLibraryWithSwiftSources).toSet(); } private ImmutableSet<PBXFileReference> filterRecursiveLibraryDependenciesWithSwiftSources( FluentIterable<TargetNode<?>> targetNodes) { return targetNodesSetToPBXFileReference(filterRecursiveLibraryDepTargetsWithSwiftSources(targetNodes)); } private boolean isLibraryFocused(TargetNode<?> targetNode) { BuildTarget buildTarget = targetNode.getBuildTarget(); return focusModules.isFocusedOn(buildTarget); } boolean isFrameworkTarget(TargetNode<?> targetNode) { if (targetNode.getDescription() instanceof AppleBundleDescription || targetNode.getDescription() instanceof AppleTestDescription) { HasAppleBundleFields arg = (HasAppleBundleFields) targetNode.getConstructorArg(); return isFrameworkBundle(arg); } return false; } /** Filter Flags for subdividing dependencies */ public enum FilterFlags { CURRENT_PROJECT, // Refers to deps that are built within the current project. OTHER_FOCUSED, // Refers to deps that are built within the workspace but not by the current // project. OTHER_NON_FOCUSED, // Refers to deps that not built by the workspace. FRAMEWORK, // Dependency built as a framework. LIBRARY, // Dependency built as a library. WITH_FORCE_LOAD, // Dependency with link_whole = True WITHOUT_FORCE_LOAD; // Dependency with link_whole = False /** Filter deps for the current project * */ public static final EnumSet<FilterFlags> LIBRARY_CURRENT_PROJECT = EnumSet.of(LIBRARY, CURRENT_PROJECT); public static final EnumSet<FilterFlags> LIBRARY_CURRENT_PROJECT_WITH_FORCE_LOAD = EnumSet.of(LIBRARY, CURRENT_PROJECT, WITH_FORCE_LOAD); public static final EnumSet<FilterFlags> LIBRARY_CURRENT_PROJECT_WITHOUT_FORCE_LOAD = EnumSet.of(LIBRARY, CURRENT_PROJECT, WITHOUT_FORCE_LOAD); /** Filter deps for within the workspace and built by Xcode but not in the current project * */ public static final EnumSet<FilterFlags> LIBRARY_FOCUSED = EnumSet.of(LIBRARY, OTHER_FOCUSED); public static final EnumSet<FilterFlags> LIBRARY_FOCUSED_WITH_FORCE_LOAD = EnumSet.of(LIBRARY, OTHER_FOCUSED, WITH_FORCE_LOAD); public static final EnumSet<FilterFlags> LIBRARY_FOCUSED_WITHOUT_FORCE_LOAD = EnumSet.of(LIBRARY, OTHER_FOCUSED, WITHOUT_FORCE_LOAD); /** Filter deps not included within the workspace * */ public static final EnumSet<FilterFlags> LIBRARY_OTHER = EnumSet.of(LIBRARY, OTHER_NON_FOCUSED); public static final EnumSet<FilterFlags> LIBRARY_OTHER_WITH_FORCE_LOAD = EnumSet.of(LIBRARY, OTHER_NON_FOCUSED, WITH_FORCE_LOAD); public static final EnumSet<FilterFlags> LIBRARY_OTHER_WITHOUT_FORCE_LOAD = EnumSet.of(LIBRARY, OTHER_NON_FOCUSED, WITHOUT_FORCE_LOAD); /** Filter framework deps * */ public static final EnumSet<FilterFlags> FRAMEWORK_CURRENT_PROJECT = EnumSet.of(FRAMEWORK, CURRENT_PROJECT); public static final EnumSet<FilterFlags> FRAMEWORK_FOCUSED = EnumSet.of(FRAMEWORK, OTHER_FOCUSED); public static final EnumSet<FilterFlags> FRAMEWORK_OTHER = EnumSet.of(FRAMEWORK, OTHER_NON_FOCUSED); } private FluentIterable<TargetNode<?>> filterRecursiveDeps(FluentIterable<TargetNode<?>> targetNodes, EnumSet<FilterFlags> filters) { FluentIterable<TargetNode<?>> filtered = targetNodes; boolean shouldBeFramework = filters.contains(FilterFlags.FRAMEWORK); filtered = targetNodes.filter(dep -> shouldBeFramework == isFrameworkTarget(dep)); if (filters.contains(FilterFlags.CURRENT_PROJECT)) { filtered = filtered.filter(dep -> isLibraryBuiltByCurrentProject(dep)); } else if (filters.contains(FilterFlags.OTHER_FOCUSED)) { filtered = filtered.filter(dep -> !isLibraryBuiltByCurrentProject(dep)) .filter(dep -> isLibraryFocused(dep)); } else if (filters.contains(FilterFlags.OTHER_NON_FOCUSED)) { filtered = filtered.filter(dep -> !isLibraryBuiltByCurrentProject(dep)) .filter(dep -> !isLibraryFocused(dep)); } // If neither is specified then do not limit by force_load at all. if (filters.contains(FilterFlags.WITH_FORCE_LOAD) || filters.contains(FilterFlags.WITHOUT_FORCE_LOAD)) { boolean shouldBeForceLoad = filters.contains(FilterFlags.WITH_FORCE_LOAD); filtered = filtered.filter(dep -> shouldBeForceLoad == isLibraryWithForceLoad(dep)); } return filtered; } private FluentIterable<TargetNode<?>> filterRecursiveLibraryDepsIterable( FluentIterable<TargetNode<?>> targetNodes, EnumSet<FilterFlags> filters) { return filterRecursiveDeps(targetNodes, filters); } private ImmutableSet<PBXFileReference> filterRecursiveLibraryDeps(FluentIterable<TargetNode<?>> targetNodes, EnumSet<FilterFlags> filters) { return targetNodesIterableToPBXFileReference(filterRecursiveLibraryDepsIterable(targetNodes, filters)); } /** Dependencies to be included in the Xcode Linker Phase Step* */ private ImmutableSet<PBXFileReference> filterRecursiveLibraryDependenciesForLinkerPhase( FluentIterable<TargetNode<?>> targetNodes) { /** Local -- local to the current project and built by Xcode * */ ImmutableSet<PBXFileReference> librariesLocal = filterRecursiveLibraryDeps(targetNodes, FilterFlags.LIBRARY_CURRENT_PROJECT); /** Focused -- included in the workspace and built by Xcode but not in current project * */ ImmutableSet<PBXFileReference> librariesOtherFocused = filterRecursiveLibraryDeps(targetNodes, FilterFlags.LIBRARY_FOCUSED); /** Other -- not included in the workspace to be built by Xcode * */ ImmutableSet<PBXFileReference> librariesOther = filterRecursiveLibraryDeps(targetNodes, FilterFlags.LIBRARY_OTHER); ImmutableSet<PBXFileReference> frameworksLocal = filterRecursiveLibraryDeps(targetNodes, FilterFlags.FRAMEWORK_CURRENT_PROJECT); ImmutableSet<PBXFileReference> frameworksOther = filterRecursiveLibraryDeps(targetNodes, FilterFlags.FRAMEWORK_OTHER); ImmutableSet<PBXFileReference> frameworksOtherFocused = filterRecursiveLibraryDeps(targetNodes, FilterFlags.FRAMEWORK_FOCUSED); if (options.shouldAddLinkedLibrariesAsFlags()) { // only include the local and focused so they can be picked up by xcode as implicit deps // exclude other libraries since they will be linked using the flags only ImmutableSet.Builder<PBXFileReference> builder = ImmutableSet.builder(); return builder.addAll(frameworksLocal).addAll(frameworksOtherFocused).addAll(librariesLocal) .addAll(librariesOtherFocused).build(); } else { ImmutableSet.Builder<PBXFileReference> builder = ImmutableSet.builder(); return builder.addAll(frameworksLocal).addAll(frameworksOtherFocused).addAll(frameworksOther) .addAll(librariesLocal).addAll(librariesOtherFocused).addAll(librariesOther).build(); } } private ImmutableList<String> collectSystemLibraryAndFrameworkLinkerFlags(ImmutableSet<FrameworkPath> paths) { FluentIterable<FrameworkPath> pathList = FluentIterable.from(paths); return pathList.transform(fwkOrLib -> getSystemFrameworkOrLibraryLinkerFlag(fwkOrLib)) .filter(Optional::isPresent).transform(input -> input.get()).toList(); } private ImmutableList<String> collectLibraryLinkerFlags(FluentIterable<TargetNode<?>> targetNodes) { return targetNodes.transform(dep -> getLibraryLinkerFlag(dep)).filter(Optional::isPresent) .transform(input -> input.get()).toList(); } private ImmutableList<String> collectFrameworkLinkerFlags(FluentIterable<TargetNode<?>> targetNodes) { return targetNodes .transform(input -> TargetNodes.castArg(input, AppleBundleDescriptionArg.class) .flatMap(this::getFrameworkLinkerFlag)) .filter(Optional::isPresent).transform(input -> input.get()).toList(); } private FluentIterable<TargetNode<?>> collectRecursiveLibraryDepsMinusBundleLoaderDeps(TargetNode<?> targetNode, FluentIterable<TargetNode<?>> targetNodes, Optional<TargetNode<AppleBundleDescriptionArg>> bundleLoaderNode) { // Don't duplicate force_load params from the test host app if this is an app test. if (isTargetNodeApplicationTestTarget(targetNode, bundleLoaderNode) && bundleLoaderNode.isPresent()) { FluentIterable<TargetNode<?>> bundleLoaderDeps = collectRecursiveLibraryDepTargets( bundleLoaderNode.get()); Set<TargetNode<?>> directDeps = Sets.difference(targetNodes.toSet(), bundleLoaderDeps.toSet()); return FluentIterable.from(ImmutableSet.copyOf(directDeps)); } return targetNodes; } private ImmutableList<String> collectForceLoadLinkerFlags(FluentIterable<TargetNode<?>> targetNodes) { return targetNodes .transform(input -> TargetNodes.castArg(input, CxxLibraryDescription.CommonArg.class) .flatMap(this::getForceLoadLinkerFlag)) .filter(Optional::isPresent).transform(input -> input.get()).toList(); } private Optional<String> getFrameworkLinkerFlag(TargetNode<? extends AppleBundleDescriptionArg> targetNode) { if (isFrameworkBundle(targetNode.getConstructorArg())) { return Optional.of("-framework " + getProductOutputBaseName(targetNode)); } else { return Optional.empty(); } } private Optional<String> getSystemFrameworkOrLibraryLinkerFlag(FrameworkPath framework) { SourceTreePath sourceTreePath; if (framework.getSourceTreePath().isPresent()) { sourceTreePath = framework.getSourceTreePath().get(); } else if (framework.getSourcePath().isPresent()) { sourceTreePath = new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputPathToSourcePath(framework.getSourcePath().get()), Optional.empty()); } else { return Optional.empty(); } String nameWithoutExtension = MorePaths.getNameWithoutExtension(sourceTreePath.getPath()); if (nameWithoutExtension.length() > 0) { String libraryPrefix = "lib"; if (nameWithoutExtension.startsWith(libraryPrefix)) { return Optional.of("-l" + nameWithoutExtension.substring(libraryPrefix.length())); } else { return Optional.of("-framework " + nameWithoutExtension); } } return Optional.empty(); } private Optional<String> getLibraryLinkerFlag(TargetNode<?> targetNode) { return Optional.of("-l" + getProductOutputBaseName(targetNode)); } private Optional<String> getForceLoadLinkerFlag( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode) { BuildTarget buildTarget = targetNode.getBuildTarget(); boolean isFocusedOnTarget = focusModules.isFocusedOn(buildTarget); CxxLibraryDescription.CommonArg arg = targetNode.getConstructorArg(); if (arg.getLinkWhole().orElse(false)) { String flag = "-Wl,-force_load," + appleConfig.getForceLoadLibraryPath(isFocusedOnTarget) + "/" + getProductOutputNameWithExtension(targetNode); return Optional.of(flag); } else { return Optional.empty(); } } private String getProductOutputBaseName(TargetNode<?> targetNode) { String productName = getProductNameForBuildTargetNode(targetNode); if (targetNode.getDescription() instanceof AppleBundleDescription || targetNode.getDescription() instanceof AppleTestDescription) { HasAppleBundleFields arg = (HasAppleBundleFields) targetNode.getConstructorArg(); productName = arg.getProductName().orElse(productName); } return productName; } private String getProductOutputNameWithExtension(TargetNode<?> targetNode) { String productName = getProductOutputBaseName(targetNode); String productOutputName; if (targetNode.getDescription() instanceof AppleLibraryDescription || targetNode.getDescription() instanceof CxxLibraryDescription || targetNode.getDescription() instanceof HalideLibraryDescription) { String productOutputFormat = AppleBuildRules.getOutputFileNameFormatForLibrary( targetNode.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR)); productOutputName = String.format(productOutputFormat, productName); } else if (targetNode.getDescription() instanceof AppleBundleDescription || targetNode.getDescription() instanceof AppleTestDescription) { HasAppleBundleFields arg = (HasAppleBundleFields) targetNode.getConstructorArg(); productOutputName = productName + "." + getExtensionString(arg.getExtension()); } else if (targetNode.getDescription() instanceof AppleBinaryDescription) { productOutputName = productName; } else if (targetNode.getDescription() instanceof PrebuiltAppleFrameworkDescription) { PrebuiltAppleFrameworkDescriptionArg arg = (PrebuiltAppleFrameworkDescriptionArg) targetNode .getConstructorArg(); productOutputName = pathRelativizer.outputPathToSourcePath(arg.getFramework()).toString(); } else { throw new RuntimeException("Unexpected type: " + targetNode.getDescription().getClass()); } return productOutputName; } private SourceTreePath getProductsSourceTreePath(TargetNode<?> targetNode) { String productOutputName = getProductOutputNameWithExtension(targetNode); PBXReference.SourceTree path = PBXReference.SourceTree.BUILT_PRODUCTS_DIR; if (targetNode.getDescription() instanceof PrebuiltAppleFrameworkDescription) { path = PBXReference.SourceTree.SOURCE_ROOT; } return new SourceTreePath(path, Paths.get(productOutputName), Optional.empty()); } private PBXFileReference getLibraryFileReference(TargetNode<?> targetNode) { // Don't re-use the productReference from other targets in this project. // File references set as a productReference don't work with custom paths. SourceTreePath productsPath = getProductsSourceTreePath(targetNode); if (isWatchApplicationNode(targetNode)) { return project.getMainGroup().getOrCreateChildGroupByName("Products") .getOrCreateFileReferenceBySourceTreePath(productsPath); } else if (targetNode.getDescription() instanceof AppleLibraryDescription || targetNode.getDescription() instanceof AppleBundleDescription || targetNode.getDescription() instanceof CxxLibraryDescription || targetNode.getDescription() instanceof HalideLibraryDescription || targetNode.getDescription() instanceof PrebuiltAppleFrameworkDescription) { return project.getMainGroup().getOrCreateChildGroupByName("Frameworks") .getOrCreateFileReferenceBySourceTreePath(productsPath); } else if (targetNode.getDescription() instanceof AppleBinaryDescription) { return project.getMainGroup().getOrCreateChildGroupByName("Dependencies") .getOrCreateFileReferenceBySourceTreePath(productsPath); } else { throw new RuntimeException("Unexpected type: " + targetNode.getDescription().getClass()); } } /** * Whether a given build target is built by the project being generated, or being build elsewhere. */ private boolean isBuiltByCurrentProject(BuildTarget buildTarget) { return initialTargets.contains(buildTarget); } private String getXcodeTargetName(BuildTarget target) { return options.shouldUseShortNamesForTargets() ? target.getShortNameAndFlavorPostfix() // make sure Xcode UI shows unique names by flavor : target.getFullyQualifiedName(); } private ProductType bundleToTargetProductType(TargetNode<? extends HasAppleBundleFields> targetNode, TargetNode<? extends AppleNativeTargetDescriptionArg> binaryNode) { if (targetNode.getConstructorArg().getXcodeProductType().isPresent()) { return ProductType.of(targetNode.getConstructorArg().getXcodeProductType().get()); } else if (targetNode.getConstructorArg().getExtension().isLeft()) { AppleBundleExtension extension = targetNode.getConstructorArg().getExtension().getLeft(); boolean nodeIsAppleLibrary = binaryNode.getDescription() instanceof AppleLibraryDescription; boolean nodeIsCxxLibrary = ((DescriptionWithTargetGraph<?>) binaryNode .getDescription()) instanceof CxxLibraryDescription; if (nodeIsAppleLibrary || nodeIsCxxLibrary) { if (binaryNode.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR)) { Optional<ProductType> productType = dylibProductTypeByBundleExtension(extension); if (productType.isPresent()) { return productType.get(); } } else if (extension == AppleBundleExtension.FRAMEWORK) { return ProductTypes.STATIC_FRAMEWORK; } } else if (binaryNode.getDescription() instanceof AppleBinaryDescription) { if (extension == AppleBundleExtension.APP) { return ProductTypes.APPLICATION; } } else if (binaryNode.getDescription() instanceof AppleTestDescription) { TargetNode<AppleTestDescriptionArg> testNode = TargetNodes .castArg(binaryNode, AppleTestDescriptionArg.class).get(); if (testNode.getConstructorArg().getIsUiTest()) { return ProductTypes.UI_TEST; } else { return ProductTypes.UNIT_TEST; } } } return ProductTypes.BUNDLE; } private static String getExtensionString(Either<AppleBundleExtension, String> extension) { return extension.isLeft() ? extension.getLeft().toFileExtension() : extension.getRight(); } private static boolean isFrameworkBundle(HasAppleBundleFields arg) { return arg.getExtension().isLeft() && arg.getExtension().getLeft().equals(AppleBundleExtension.FRAMEWORK); } private static boolean isModularAppleLibrary(TargetNode<?> libraryNode) { Optional<TargetNode<AppleLibraryDescriptionArg>> appleLibNode = TargetNodes.castArg(libraryNode, AppleLibraryDescriptionArg.class); if (appleLibNode.isPresent()) { AppleLibraryDescriptionArg constructorArg = appleLibNode.get().getConstructorArg(); return constructorArg.isModular(); } return false; } private static boolean bundleRequiresRemovalOfAllTransitiveFrameworks( TargetNode<? extends HasAppleBundleFields> targetNode) { return isFrameworkBundle(targetNode.getConstructorArg()); } private static boolean bundleRequiresAllTransitiveFrameworks( TargetNode<? extends AppleNativeTargetDescriptionArg> binaryNode, Optional<TargetNode<AppleBundleDescriptionArg>> bundleLoaderNode) { return TargetNodes.castArg(binaryNode, AppleBinaryDescriptionArg.class).isPresent() || (!bundleLoaderNode.isPresent() && TargetNodes.castArg(binaryNode, AppleTestDescriptionArg.class).isPresent()); } private Path resolveSourcePath(SourcePath sourcePath) { if (sourcePath instanceof PathSourcePath) { return projectFilesystem.relativize(defaultPathResolver.getAbsolutePath(sourcePath)); } Preconditions.checkArgument(sourcePath instanceof BuildTargetSourcePath); BuildTargetSourcePath buildTargetSourcePath = (BuildTargetSourcePath) sourcePath; BuildTarget buildTarget = buildTargetSourcePath.getTarget(); TargetNode<?> node = targetGraph.get(buildTarget); Optional<TargetNode<ExportFileDescriptionArg>> exportFileNode = TargetNodes.castArg(node, ExportFileDescriptionArg.class); if (!exportFileNode.isPresent()) { BuildRuleResolver resolver = actionGraphBuilderForNode.apply(node); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = DefaultSourcePathResolver.from(ruleFinder); Path output = pathResolver.getAbsolutePath(sourcePath); if (output == null) { throw new HumanReadableException("The target '%s' does not have an output.", node.getBuildTarget()); } requiredBuildTargetsBuilder.add(buildTarget); return projectFilesystem.relativize(output); } Optional<SourcePath> src = exportFileNode.get().getConstructorArg().getSrc(); if (!src.isPresent()) { Path output = buildTarget.getCellPath().resolve(buildTarget.getBasePath()) .resolve(buildTarget.getShortNameAndFlavorPostfix()); return projectFilesystem.relativize(output); } return resolveSourcePath(src.get()); } private boolean shouldCopyOutputFile(TargetNode<?> input) { if (input.getDescription() instanceof CxxLibraryDescription || input.getDescription() instanceof AppleLibraryDescription) { return isLibraryWithSourcesToCompile(input); } return true; } private boolean isLibraryWithSourcesToCompile(TargetNode<?> input) { if (input.getDescription() instanceof HalideLibraryDescription) { return true; } Optional<TargetNode<CxxLibraryDescription.CommonArg>> library = getLibraryNode(targetGraph, input); if (!library.isPresent()) { return false; } PatternMatchedCollection<ImmutableSortedSet<SourceWithFlags>> platformSources = library.get() .getConstructorArg().getPlatformSrcs(); int platFormSourcesSize = platformSources.getValues().size(); return (library.get().getConstructorArg().getSrcs().size() + platFormSourcesSize != 0); } private boolean isLibraryWithSwiftSources(TargetNode<?> input) { Optional<TargetNode<CxxLibraryDescription.CommonArg>> library = getLibraryNode(targetGraph, input); return library.filter(projGenerationStateCache::targetContainsSwiftSourceCode).isPresent(); } /** @return product type of a bundle containing a dylib. */ private static Optional<ProductType> dylibProductTypeByBundleExtension(AppleBundleExtension extension) { switch (extension) { case FRAMEWORK: return Optional.of(ProductTypes.FRAMEWORK); case APPEX: return Optional.of(ProductTypes.APP_EXTENSION); case BUNDLE: return Optional.of(ProductTypes.BUNDLE); case XCTEST: return Optional.of(ProductTypes.UNIT_TEST); // $CASES-OMITTED$ default: return Optional.empty(); } } /** * Determines if a target node is for watchOS2 application * * @param targetNode A target node * @return If the given target node is for an watchOS2 application */ private static boolean isWatchApplicationNode(TargetNode<?> targetNode) { if (targetNode.getDescription() instanceof AppleBundleDescription) { AppleBundleDescriptionArg arg = (AppleBundleDescriptionArg) targetNode.getConstructorArg(); return arg.getXcodeProductType().equals(Optional.of(ProductTypes.WATCH_APPLICATION.getIdentifier())); } return false; } private Optional<SourcePath> getPrefixHeaderSourcePath(CxxLibraryDescription.CommonArg arg) { // The prefix header could be stored in either the `prefix_header` or the `precompiled_header` // field. Use either, but prefer the prefix_header. if (arg.getPrefixHeader().isPresent()) { return arg.getPrefixHeader(); } if (!arg.getPrecompiledHeader().isPresent()) { return Optional.empty(); } SourcePath pchPath = arg.getPrecompiledHeader().get(); // `precompiled_header` requires a cxx_precompiled_header target, but we want to give Xcode the // path to the pch file itself. Resolve our target reference into a path Preconditions.checkArgument(pchPath instanceof BuildTargetSourcePath); BuildTargetSourcePath pchTargetSourcePath = (BuildTargetSourcePath) pchPath; BuildTarget pchTarget = pchTargetSourcePath.getTarget(); TargetNode<?> node = targetGraph.get(pchTarget); BuildRuleResolver resolver = actionGraphBuilderForNode.apply(node); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); BuildRule rule = ruleFinder.getRule(pchTargetSourcePath); Preconditions.checkArgument(rule instanceof CxxPrecompiledHeaderTemplate); CxxPrecompiledHeaderTemplate pch = (CxxPrecompiledHeaderTemplate) rule; return Optional.of(pch.getHeaderSourcePath()); } private ProjectFilesystem getFilesystemForTarget(Optional<BuildTarget> target) { if (target.isPresent()) { Path cellPath = target.get().getCellPath(); Cell cell = projectCell.getCellProvider().getCellByPath(cellPath); return cell.getFilesystem(); } else { return projectFilesystem; } } private Path getPathToHeaderMapsRoot(Optional<BuildTarget> target) { ProjectFilesystem filesystem = getFilesystemForTarget(target); return filesystem.getBuckPaths().getGenDir().resolve("_p"); } private Path getFilenameToHeadersPath(TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, String suffix) { String hashedPath = BaseEncoding.base64Url().omitPadding() .encode(Hashing.sha1() .hashString(targetNode.getBuildTarget().getUnflavoredBuildTarget().getFullyQualifiedName(), Charsets.UTF_8) .asBytes()) .substring(0, 10); return Paths.get(hashedPath + suffix); } private Path getPathToHeadersPath(TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, String suffix) { return getPathToHeaderMapsRoot(Optional.of(targetNode.getBuildTarget())) .resolve(getFilenameToHeadersPath(targetNode, suffix)); } private Path getAbsolutePathToHeaderSymlinkTree( TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, HeaderVisibility headerVisibility) { ProjectFilesystem filesystem = getFilesystemForTarget(Optional.of(targetNode.getBuildTarget())); return filesystem.resolve(getPathToHeaderSymlinkTree(targetNode, headerVisibility)); } private Path getPathToHeaderSymlinkTree(TargetNode<? extends CxxLibraryDescription.CommonArg> targetNode, HeaderVisibility headerVisibility) { return getPathToHeadersPath(targetNode, AppleHeaderVisibilities.getHeaderSymlinkTreeSuffix(headerVisibility)); } private Path getPathToMergedHeaderMap() { return getPathToHeaderMapsRoot(Optional.of(workspaceTarget.get())).resolve("pub-hmap"); } }