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.apple; 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.clang.HeaderMap; 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.PBXAggregateTarget; import com.facebook.buck.apple.xcode.xcodeproj.PBXBuildFile; import com.facebook.buck.apple.xcode.xcodeproj.PBXBuildPhase; 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.ProductType; import com.facebook.buck.apple.xcode.xcodeproj.SourceTreePath; import com.facebook.buck.apple.xcode.xcodeproj.XCBuildConfiguration; import com.facebook.buck.apple.xcode.xcodeproj.XCConfigurationList; import com.facebook.buck.apple.xcode.xcodeproj.XCVersionGroup; import com.facebook.buck.cxx.CxxBuckConfig; import com.facebook.buck.cxx.CxxDescriptionEnhancer; import com.facebook.buck.cxx.CxxLibraryDescription; import com.facebook.buck.cxx.CxxPlatform; import com.facebook.buck.cxx.CxxSource; import com.facebook.buck.cxx.HeaderVisibility; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.event.ProjectGenerationEvent; import com.facebook.buck.halide.HalideBuckConfig; import com.facebook.buck.halide.HalideCompile; import com.facebook.buck.halide.HalideLibraryDescription; import com.facebook.buck.io.ExecutableFinder; import com.facebook.buck.io.MorePaths; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.js.IosReactNativeLibraryDescription; import com.facebook.buck.js.ReactNativeFlavors; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuckVersion; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.model.Either; import com.facebook.buck.model.Flavor; import com.facebook.buck.model.FlavorDomain; import com.facebook.buck.model.HasTests; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.AbstractDescriptionArg; import com.facebook.buck.rules.BuildRuleType; import com.facebook.buck.rules.BuildTargetSourcePath; import com.facebook.buck.rules.PathSourcePath; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.rules.TargetNode; import com.facebook.buck.rules.coercer.FrameworkPath; import com.facebook.buck.rules.coercer.SourceList; import com.facebook.buck.rules.SourceWithFlags; import com.facebook.buck.shell.ExportFileDescription; import com.facebook.buck.util.BuckConstant; import com.facebook.buck.util.Escaper; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreIterables; import com.facebook.buck.util.PackagedResource; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Strings; 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.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; 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.Multimap; import com.google.common.collect.Sets; 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.io.Resources; import com.google.common.util.concurrent.UncheckedExecutionException; import org.stringtemplate.v4.ST; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; 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.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Generator for xcode project and associated files from a set of xcode/ios rules. */ public class ProjectGenerator { public static final String BUILD_WITH_BUCK_POSTFIX = "-Buck"; private static final Logger LOG = Logger.get(ProjectGenerator.class); private static final String BUILD_WITH_BUCK_TEMPLATE = "build-with-buck.st"; private static final String FIX_UUID_TEMPLATE = "fix-uuid.st"; private static final String FIX_UUID_PY_RESOURCE = "fix_uuid.py"; private static final String CODESIGN_TEMPLATE = "codesign.st"; private static final String CODESIGN_PY_RESOURCE = "codesign.py"; private static final ImmutableList<String> DEFAULT_CFLAGS = ImmutableList.of(); private static final ImmutableList<String> DEFAULT_CXXFLAGS = ImmutableList.of(); public static final String REPORT_ABSOLUTE_PATHS = "--report-absolute-paths"; public static final String XCODE_BUILD_SCRIPT_FLAVOR_VALUE = "#$PLATFORM_NAME-$arch"; public static final String PRODUCT_NAME = "PRODUCT_NAME"; public enum Option { /** Use short BuildTarget name instead of full name for targets */ USE_SHORT_NAMES_FOR_TARGETS, /** Put targets into groups reflecting directory structure of their BUCK files */ CREATE_DIRECTORY_STRUCTURE, /** Generate read-only project files */ GENERATE_READ_ONLY_FILES, /** Include tests that test root targets in the scheme */ INCLUDE_TESTS, /** Include dependencies tests in the scheme */ INCLUDE_DEPENDENCIES_TESTS, /** Don't use header maps as header search paths */ DISABLE_HEADER_MAPS, } /** * Standard options for generating a separated project */ public static final ImmutableSet<Option> SEPARATED_PROJECT_OPTIONS = ImmutableSet .of(Option.USE_SHORT_NAMES_FOR_TARGETS); /** * Standard options for generating a combined project */ public static final ImmutableSet<Option> COMBINED_PROJECT_OPTIONS = ImmutableSet .of(Option.CREATE_DIRECTORY_STRUCTURE, Option.USE_SHORT_NAMES_FOR_TARGETS); private static final FileAttribute<?> READ_ONLY_FILE_ATTRIBUTE = PosixFilePermissions .asFileAttribute(ImmutableSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.GROUP_READ, PosixFilePermission.OTHERS_READ)); public static final Function<TargetNode<AppleNativeTargetDescriptionArg>, Iterable<String>> GET_EXPORTED_LINKER_FLAGS = new Function<TargetNode<AppleNativeTargetDescriptionArg>, Iterable<String>>() { @Override public Iterable<String> apply(TargetNode<AppleNativeTargetDescriptionArg> input) { return input.getConstructorArg().exportedLinkerFlags.get(); } }; public static final Function<TargetNode<AppleNativeTargetDescriptionArg>, Iterable<String>> GET_EXPORTED_PREPROCESSOR_FLAGS = new Function<TargetNode<AppleNativeTargetDescriptionArg>, Iterable<String>>() { @Override public Iterable<String> apply(TargetNode<AppleNativeTargetDescriptionArg> input) { return input.getConstructorArg().exportedPreprocessorFlags.get(); } }; private final Function<SourcePath, Path> sourcePathResolver; private final TargetGraph targetGraph; 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 ImmutableSet<Option> options; private final Optional<BuildTarget> targetToBuildWithBuck; private final ImmutableList<String> buildWithBuckFlags; private final ExecutableFinder executableFinder; private final ImmutableMap<String, String> environment; private final FlavorDomain<CxxPlatform> cxxPlatforms; private final CxxPlatform defaultCxxPlatform; private ImmutableSet<TargetNode<AppleTestDescription.Arg>> testsToGenerateAsStaticLibraries = ImmutableSet.of(); private ImmutableMultimap<AppleTestBundleParamsKey, TargetNode<AppleTestDescription.Arg>> additionalCombinedTestTargets = ImmutableMultimap .of(); // 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<PBXTarget> buildableCombinedTestTargets = ImmutableSet.builder(); private final ImmutableSet.Builder<BuildTarget> requiredBuildTargetsBuilder = ImmutableSet.builder(); private final Function<? super TargetNode<?>, SourcePathResolver> sourcePathResolverForNode; private final BuckEventBus buckEventBus; private boolean attemptToDetermineBestCxxPlatform; /** * Populated while generating project configurations, in order to collect the possible * project-level configurations to set. */ private final ImmutableSet.Builder<String> targetConfigNamesBuilder; private final Map<String, String> gidsToTargetNames; private final HalideBuckConfig halideBuckConfig; private final CxxBuckConfig cxxBuckConfig; public ProjectGenerator(TargetGraph targetGraph, Set<BuildTarget> initialTargets, ProjectFilesystem projectFilesystem, Path outputDirectory, String projectName, String buildFileName, Set<Option> options, Optional<BuildTarget> targetToBuildWithBuck, ImmutableList<String> buildWithBuckFlags, ExecutableFinder executableFinder, ImmutableMap<String, String> environment, FlavorDomain<CxxPlatform> cxxPlatforms, CxxPlatform defaultCxxPlatform, Function<? super TargetNode<?>, SourcePathResolver> sourcePathResolverForNode, BuckEventBus buckEventBus, boolean attemptToDetermineBestCxxPlatform, HalideBuckConfig halideBuckConfig, CxxBuckConfig cxxBuckConfig) { this.sourcePathResolver = new Function<SourcePath, Path>() { @Override public Path apply(SourcePath input) { return resolveSourcePath(input); } }; this.targetGraph = targetGraph; this.initialTargets = ImmutableSet.copyOf(initialTargets); this.projectFilesystem = projectFilesystem; this.outputDirectory = outputDirectory; this.projectName = projectName; this.buildFileName = buildFileName; this.options = ImmutableSet.copyOf(options); this.targetToBuildWithBuck = targetToBuildWithBuck; this.buildWithBuckFlags = buildWithBuckFlags; this.executableFinder = executableFinder; this.environment = environment; this.cxxPlatforms = cxxPlatforms; this.defaultCxxPlatform = defaultCxxPlatform; this.sourcePathResolverForNode = sourcePathResolverForNode; this.buckEventBus = buckEventBus; this.attemptToDetermineBestCxxPlatform = attemptToDetermineBestCxxPlatform; this.projectPath = outputDirectory.resolve(projectName + ".xcodeproj"); this.pathRelativizer = new PathRelativizer(outputDirectory, sourcePathResolver); 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(); gidsToTargetNames = new HashMap<>(); this.halideBuckConfig = halideBuckConfig; this.cxxBuckConfig = cxxBuckConfig; } /** * Sets the set of tests which should be generated as static libraries instead of test bundles. */ public ProjectGenerator setTestsToGenerateAsStaticLibraries(Set<TargetNode<AppleTestDescription.Arg>> set) { Preconditions.checkState(!projectGenerated); this.testsToGenerateAsStaticLibraries = ImmutableSet.copyOf(set); return this; } /** * Sets combined test targets which should be generated in this project. */ public ProjectGenerator setAdditionalCombinedTestTargets( Multimap<AppleTestBundleParamsKey, TargetNode<AppleTestDescription.Arg>> targets) { Preconditions.checkState(!projectGenerated); this.additionalCombinedTestTargets = ImmutableMultimap.copyOf(targets); return this; } @VisibleForTesting PBXProject getGeneratedProject() { return project; } @VisibleForTesting List<Path> getGeneratedHeaderSymlinkTrees() { return headerSymlinkTrees; } static PackagedResource getPackagedResourceNamed(ProjectFilesystem filesystem, String name) { return new PackagedResource(filesystem, ProjectGenerator.class, name); } static Path getPathToBuck(ExecutableFinder executableFinder, ImmutableMap<String, String> environment) { return executableFinder.getExecutable(Paths.get("buck"), environment); } @VisibleForTesting static Path getFixUUIDScriptPath(ProjectFilesystem filesystem) { return getPackagedResourceNamed(filesystem, FIX_UUID_PY_RESOURCE).get(); } @VisibleForTesting static Path getCodesignScriptPath(ProjectFilesystem filesystem) { return getPackagedResourceNamed(filesystem, CODESIGN_PY_RESOURCE).get(); } public Path getProjectPath() { return projectPath; } public ImmutableMultimap<BuildTarget, PBXTarget> getBuildTargetToGeneratedTargetMap() { Preconditions.checkState(projectGenerated, "Must have called createXcodeProjects"); ImmutableMultimap.Builder<BuildTarget, PBXTarget> buildTargetToPbxTargetMap = ImmutableMultimap.builder(); for (Map.Entry<TargetNode<?>, PBXTarget> entry : targetNodeToGeneratedProjectTargetBuilder.build() .entries()) { buildTargetToPbxTargetMap.put(entry.getKey().getBuildTarget(), entry.getValue()); } return buildTargetToPbxTargetMap.build(); } public ImmutableSet<PBXTarget> getBuildableCombinedTestTargets() { Preconditions.checkState(projectGenerated, "Must have called createXcodeProjects"); return buildableCombinedTestTargets.build(); } public ImmutableSet<BuildTarget> getRequiredBuildTargets() { Preconditions.checkState(projectGenerated, "Must have called createXcodeProjects"); return requiredBuildTargetsBuilder.build(); } public void createXcodeProjects() throws IOException { LOG.debug("Creating projects for targets %s", initialTargets); try { for (TargetNode<?> targetNode : targetGraph.getNodes()) { if (isBuiltByCurrentProject(targetNode.getBuildTarget())) { LOG.debug("Including rule %s in project", targetNode); // Trigger the loading cache to call the generateProjectTarget function. Optional<PBXTarget> target = targetNodeToProjectTarget.getUnchecked(targetNode); if (target.isPresent()) { targetNodeToGeneratedProjectTargetBuilder.put(targetNode, target.get()); } } else { LOG.verbose("Excluding rule %s (not built by current project)", targetNode); } } if (targetToBuildWithBuck.isPresent()) { generateBuildWithBuckTarget(targetGraph.get(targetToBuildWithBuck.get())); } int combinedTestIndex = 0; for (AppleTestBundleParamsKey key : additionalCombinedTestTargets.keySet()) { generateCombinedTestTarget(deriveCombinedTestTargetNameFromKey(key, combinedTestIndex++), key, additionalCombinedTestTargets.get(key)); } for (String configName : targetConfigNamesBuilder.build()) { XCBuildConfiguration outputConfig = project.getBuildConfigurationList() .getBuildConfigurationsByName().getUnchecked(configName); outputConfig.setBuildSettings(new NSDictionary()); } 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; } } } private void generateBuildWithBuckTarget(TargetNode<?> targetNode) throws IOException { final BuildTarget buildTarget = targetNode.getBuildTarget(); String buckTargetProductName = getXcodeTargetName(buildTarget) + BUILD_WITH_BUCK_POSTFIX; PBXAggregateTarget buildWithBuckTarget = new PBXAggregateTarget(buckTargetProductName); buildWithBuckTarget.setProductName(buckTargetProductName); PBXShellScriptBuildPhase buildShellScriptBuildPhase = new PBXShellScriptBuildPhase(); buildShellScriptBuildPhase.setShellScript(getBuildWithBuckShellScript(targetNode)); buildWithBuckTarget.getBuildPhases().add(buildShellScriptBuildPhase); // Only add a shell script for fixing UUIDs if it is an AppleBundle if (targetNode.getType().equals(AppleBundleDescription.TYPE)) { PBXShellScriptBuildPhase fixUUIDShellScriptBuildPhase = new PBXShellScriptBuildPhase(); fixUUIDShellScriptBuildPhase.setShellScript(getFixUUIDShellScript(targetNode)); buildWithBuckTarget.getBuildPhases().add(fixUUIDShellScriptBuildPhase); PBXShellScriptBuildPhase codesignPhase = new PBXShellScriptBuildPhase(); codesignPhase.setShellScript(getCodesignShellScript(targetNode)); buildWithBuckTarget.getBuildPhases().add(codesignPhase); } TargetNode<CxxLibraryDescription.Arg> node = getAppleNativeNode(targetGraph, targetNode).get(); ImmutableMap<String, ImmutableMap<String, String>> configs = getXcodeBuildConfigurationsForTargetNode(node, ImmutableMap.<String, String>of()).get(); XCConfigurationList configurationList = new XCConfigurationList(); PBXGroup group = project.getMainGroup() .getOrCreateDescendantGroupByPath(FluentIterable.from(buildTarget.getBasePath()) .transform(Functions.toStringFunction()).toList()) .getOrCreateChildGroupByName(getXcodeTargetName(buildTarget)); for (String configurationName : configs.keySet()) { XCBuildConfiguration configuration = configurationList.getBuildConfigurationsByName() .getUnchecked(configurationName); configuration.setBaseConfigurationReference(getConfigurationFileReference(group, getConfigurationNameToXcconfigPath(buildTarget).apply(configurationName))); NSDictionary inlineSettings = new NSDictionary(); inlineSettings.put("HEADER_SEARCH_PATHS", ""); inlineSettings.put("LIBRARY_SEARCH_PATHS", ""); inlineSettings.put("FRAMEWORK_SEARCH_PATHS", ""); configuration.setBuildSettings(inlineSettings); } buildWithBuckTarget.setBuildConfigurationList(configurationList); project.getTargets().add(buildWithBuckTarget); targetNodeToGeneratedProjectTargetBuilder.put(targetNode, buildWithBuckTarget); } private static Optional<String> getProductNameForTargetNode(TargetNode<?> targetNode) { Optional<String> productName = Optional.absent(); Optional<TargetNode<AppleBundleDescription.Arg>> appleBundleNode = targetNode .castArg(AppleBundleDescription.Arg.class); if (appleBundleNode.isPresent()) { productName = appleBundleNode.get().getConstructorArg().productName; } return productName; } private String getBuildFlags() { ArrayList<String> flags = new ArrayList<>(buildWithBuckFlags); if (!flags.contains(REPORT_ABSOLUTE_PATHS)) { flags.add(0, REPORT_ABSOLUTE_PATHS); } flags = new ArrayList<String>(FluentIterable.<String>from(flags).transform(Escaper.BASH_ESCAPER).toList()); return Joiner.on(' ').join(flags); } private String getBuildWithBuckShellScript(TargetNode<?> targetNode) { ST template; try { template = new ST(Resources.toString( Resources.getResource(ProjectGenerator.class, BUILD_WITH_BUCK_TEMPLATE), Charsets.UTF_8)); } catch (IOException e) { throw new RuntimeException("There was an error loading '" + BUILD_WITH_BUCK_TEMPLATE + "' template", e); } String buildFlags = getBuildFlags(); String escapedBuildTarget = Escaper.escapeAsBashString(targetNode.getBuildTarget().getFullyQualifiedName()); if (attemptToDetermineBestCxxPlatform) { escapedBuildTarget += XCODE_BUILD_SCRIPT_FLAVOR_VALUE; } template.add("repo_root", projectFilesystem.getRootPath()); template.add("path_to_buck", getPathToBuck(executableFinder, environment)); template.add("build_flags", buildFlags); template.add("escaped_build_target", escapedBuildTarget); return template.render(); } private String getFixUUIDShellScript(TargetNode<?> targetNode) { ST template; try { template = new ST(Resources.toString(Resources.getResource(ProjectGenerator.class, FIX_UUID_TEMPLATE), Charsets.UTF_8)); } catch (IOException e) { throw new RuntimeException("There was an error loading '" + FIX_UUID_TEMPLATE + "' template", e); } ImmutableSet<Flavor> flavors = ImmutableSet.copyOf(targetNode.getBuildTarget().getFlavors()); CxxPlatform cxxPlatform = cxxPlatforms.getValue(flavors).or(defaultCxxPlatform); String compDir = cxxPlatform.getDebugPathSanitizer().getCompilationDirectory(); // Use the hostname for padding instead of the directory, this way the directory matches without // having to resolve it. String sourceDir = Strings.padStart(":" + projectFilesystem.getRootPath().toString(), compDir.length(), 'f'); Optional<String> productName = getProductNameForTargetNode(targetNode); String binaryName = AppleBundle.getBinaryName(targetToBuildWithBuck.get(), productName); Path bundleDestination = getScratchPathForAppBundle(targetToBuildWithBuck.get(), binaryName); Path dsymDestination = getScratchPathForDsymBundle(targetToBuildWithBuck.get(), binaryName); Path resolvedBundleDestination = projectFilesystem.resolve(bundleDestination); Path resolvedDsymDestination = projectFilesystem.resolve(dsymDestination); Path fixUUIDScriptPath = getFixUUIDScriptPath(projectFilesystem); if (attemptToDetermineBestCxxPlatform) { template.add("buck_flavor", XCODE_BUILD_SCRIPT_FLAVOR_VALUE); } else { template.add("buck_flavor", ""); } template.add("path_to_buck", getPathToBuck(executableFinder, environment)); template.add("buck_target", targetToBuildWithBuck.get().getFullyQualifiedName()); template.add("root_path", projectFilesystem.getRootPath()); template.add("comp_dir", compDir); template.add("source_dir", sourceDir); template.add("resolved_bundle_destination", resolvedBundleDestination); template.add("resolved_bundle_destination_parent", resolvedBundleDestination.getParent()); template.add("resolved_dsym_destination", resolvedDsymDestination); template.add("binary_name", binaryName); template.add("path_to_fix_uuid_script", fixUUIDScriptPath); return template.render(); } private String getCodesignShellScript(TargetNode<?> targetNode) { ST template; try { template = new ST(Resources.toString(Resources.getResource(ProjectGenerator.class, CODESIGN_TEMPLATE), Charsets.UTF_8)); } catch (IOException e) { throw new RuntimeException("There was an error loading '" + CODESIGN_TEMPLATE + "' template", e); } Optional<String> productName = getProductNameForTargetNode(targetNode); String binaryName = AppleBundle.getBinaryName(targetToBuildWithBuck.get(), productName); Path bundleDestination = getScratchPathForAppBundle(targetToBuildWithBuck.get(), binaryName); Path resolvedBundleDestination = projectFilesystem.resolve(bundleDestination); template.add("root_path", projectFilesystem.getRootPath()); template.add("path_to_codesign_script", getCodesignScriptPath(projectFilesystem)); template.add("app_bundle_path", resolvedBundleDestination); return template.render(); } static Path getScratchPathForAppBundle(BuildTarget targetToBuildWithBuck, String binaryName) { return BuildTargets.getScratchPath(targetToBuildWithBuck, "/%s-unsanitised").resolve(binaryName + ".app"); } static Path getScratchPathForDsymBundle(BuildTarget targetToBuildWithBuck, String binaryName) { return BuildTargets.getScratchPath(targetToBuildWithBuck, "/%s-unsanitised").resolve(binaryName + ".dSYM"); } @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"); Optional<PBXTarget> result = Optional.absent(); if (targetNode.getType().equals(AppleLibraryDescription.TYPE)) { result = Optional.<PBXTarget>of( generateAppleLibraryTarget(project, (TargetNode<AppleNativeTargetDescriptionArg>) targetNode, Optional.<TargetNode<AppleBundleDescription.Arg>>absent())); } else if (targetNode.getType().equals(CxxLibraryDescription.TYPE)) { result = Optional.<PBXTarget>of( generateCxxLibraryTarget(project, (TargetNode<CxxLibraryDescription.Arg>) targetNode, ImmutableSet.<AppleResourceDescription.Arg>of(), ImmutableSet.<AppleAssetCatalogDescription.Arg>of(), Optional.<TargetNode<AppleBundleDescription.Arg>>absent())); } else if (targetNode.getType().equals(AppleBinaryDescription.TYPE)) { result = Optional.<PBXTarget>of( generateAppleBinaryTarget(project, (TargetNode<AppleNativeTargetDescriptionArg>) targetNode)); } else if (targetNode.getType().equals(AppleBundleDescription.TYPE)) { TargetNode<AppleBundleDescription.Arg> bundleTargetNode = (TargetNode<AppleBundleDescription.Arg>) targetNode; result = Optional.<PBXTarget>of(generateAppleBundleTarget(project, bundleTargetNode, (TargetNode<AppleNativeTargetDescriptionArg>) targetGraph .get(bundleTargetNode.getConstructorArg().binary), Optional.<TargetNode<AppleBundleDescription.Arg>>absent())); } else if (targetNode.getType().equals(AppleTestDescription.TYPE)) { result = generateAppleTestTarget((TargetNode<AppleTestDescription.Arg>) targetNode); } else if (targetNode.getType().equals(AppleResourceDescription.TYPE)) { checkAppleResourceTargetNodeReferencingValidContents( (TargetNode<AppleResourceDescription.Arg>) targetNode); } else if (targetNode.getType().equals(HalideLibraryDescription.TYPE)) { TargetNode<HalideLibraryDescription.Arg> halideTargetNode = (TargetNode<HalideLibraryDescription.Arg>) 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)); // Also, run the compiler once at project time to generate the header // file needed for compilation. requiredBuildTargetsBuilder.add(buildTarget.withFlavors(HalideLibraryDescription.HALIDE_COMPILE_FLAVOR, defaultCxxPlatform.getFlavor())); } buckEventBus.post(ProjectGenerationEvent.processed()); return result; } private static Path getHalideOutputPath(BuildTarget target) { return Paths.get("buck-out/halide").resolve(target.getBasePath()).resolve(target.getShortName()); } private Optional<PBXTarget> generateHalideLibraryTarget(PBXProject project, TargetNode<HalideLibraryDescription.Arg> targetNode) throws IOException { final BuildTarget buildTarget = targetNode.getBuildTarget(); String productName = getProductNameForBuildTarget(buildTarget); Path outputPath = getHalideOutputPath(buildTarget); Path scriptPath = halideBuckConfig.getXcodeCompileScriptPath(); Optional<String> script = projectFilesystem.readFileIfItExists(scriptPath); PBXShellScriptBuildPhase scriptPhase = new PBXShellScriptBuildPhase(); scriptPhase.setShellScript(script.or("")); NewNativeTargetProjectMutator mutator = new NewNativeTargetProjectMutator(pathRelativizer, sourcePathResolver); mutator.setTargetName(getXcodeTargetName(buildTarget)) .setProduct(ProductType.STATIC_LIBRARY, productName, outputPath) .setPreBuildRunScriptPhases(ImmutableList.of(scriptPhase)); NewNativeTargetProjectMutator.Result targetBuilderResult; try { targetBuilderResult = mutator.buildTargetAndAddToProject(project); } catch (NoSuchBuildTargetException e) { throw new HumanReadableException(e); } BuildTarget compilerTarget = HalideLibraryDescription.createHalideCompilerBuildTarget(buildTarget); Path compilerPath = BuildTargets.getGenPath(compilerTarget, "%s"); ImmutableMap<String, String> appendedConfig = ImmutableMap.<String, String>of(); ImmutableMap<String, String> extraSettings = ImmutableMap.<String, String>of(); ImmutableMap.Builder<String, String> defaultSettingsBuilder = ImmutableMap.builder(); defaultSettingsBuilder.put("REPO_ROOT", projectFilesystem.getRootPath().toAbsolutePath().normalize().toString()); defaultSettingsBuilder.put("HALIDE_COMPILER_PATH", compilerPath.toString()); 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, appendedConfig); PBXNativeTarget target = targetBuilderResult.target; setTargetBuildConfigurations(getConfigurationNameToXcconfigPath(buildTarget), target, project.getMainGroup(), configs.get(), extraSettings, defaultSettingsBuilder.build(), appendedConfig); return Optional.<PBXTarget>of(target); } @SuppressWarnings("unchecked") private Optional<PBXTarget> generateAppleTestTarget(TargetNode<AppleTestDescription.Arg> testTargetNode) throws IOException { Optional<TargetNode<AppleBundleDescription.Arg>> testHostBundle; if (testTargetNode.getConstructorArg().testHostApp.isPresent()) { BuildTarget testHostBundleTarget = testTargetNode.getConstructorArg().testHostApp.get(); TargetNode<?> testHostBundleNode = targetGraph.get(testHostBundleTarget); if (testHostBundleNode.getType() != AppleBundleDescription.TYPE) { throw new HumanReadableException( "The test host target '%s' has the wrong type (%s), must be apple_bundle", testHostBundleTarget, testHostBundleNode.getType()); } testHostBundle = Optional.of((TargetNode<AppleBundleDescription.Arg>) testHostBundleNode); } else { testHostBundle = Optional.absent(); } if (testsToGenerateAsStaticLibraries.contains(testTargetNode)) { return Optional.<PBXTarget>of(generateAppleLibraryTarget(project, testTargetNode, testHostBundle)); } else { return Optional.<PBXTarget>of( generateAppleBundleTarget(project, testTargetNode, testTargetNode, testHostBundle)); } } private void checkAppleResourceTargetNodeReferencingValidContents( TargetNode<AppleResourceDescription.Arg> 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 // AbstractSourcePath), just assume it's the right type; we have no way of checking now as it // may not exist yet. AppleResourceDescription.Arg arg = resource.getConstructorArg(); for (SourcePath dir : arg.dirs) { if (dir instanceof PathSourcePath && !projectFilesystem.isDirectory(sourcePathResolver.apply(dir))) { throw new HumanReadableException("%s specified in the dirs parameter of %s is not a directory", dir.toString(), resource.toString()); } } for (SourcePath file : arg.files) { if (file instanceof PathSourcePath && !projectFilesystem.isFile(sourcePathResolver.apply(file))) { throw new HumanReadableException("%s specified in the files parameter of %s is not a regular file", file.toString(), resource.toString()); } } } PBXNativeTarget generateAppleBundleTarget(PBXProject project, TargetNode<? extends HasAppleBundleFields> targetNode, TargetNode<? extends AppleNativeTargetDescriptionArg> binaryNode, Optional<TargetNode<AppleBundleDescription.Arg>> bundleLoaderNode) throws IOException { Path infoPlistPath = Preconditions .checkNotNull(sourcePathResolver.apply(targetNode.getConstructorArg().getInfoPlist())); // -- copy any binary and bundle targets into this bundle Iterable<TargetNode<?>> copiedRules = AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(targetGraph, AppleBuildRules.RecursiveDependenciesMode.COPYING, targetNode, Optional.of(AppleBuildRules.XCODE_TARGET_BUILD_RULE_TYPES)); if (bundleRequiresRemovalOfAllTransitiveFrameworks(targetNode)) { copiedRules = rulesWithoutFrameworkBundles(copiedRules); } else if (bundleRequiresAllTransitiveFrameworks(binaryNode)) { copiedRules = ImmutableSet.<TargetNode<?>>builder().addAll(copiedRules) .addAll(getTransitiveFrameworkNodes(targetNode)).build(); } ImmutableList<PBXBuildPhase> copyFilesBuildPhases = getCopyFilesBuildPhases(copiedRules); PBXNativeTarget target = generateBinaryTarget(project, Optional.of(targetNode), binaryNode, bundleToTargetProductType(targetNode, binaryNode), "%s." + getExtensionString(targetNode.getConstructorArg().getExtension()), Optional.of(infoPlistPath), /* includeFrameworks */ true, AppleResources.collectRecursiveResources(targetGraph, ImmutableList.of(targetNode)), AppleResources.collectDirectResources(targetGraph, targetNode), AppleBuildRules.collectRecursiveAssetCatalogs(targetGraph, ImmutableList.of(targetNode)), AppleBuildRules.collectDirectAssetCatalogs(targetGraph, targetNode), Optional.<Iterable<PBXBuildPhase>>of(copyFilesBuildPhases), bundleLoaderNode); LOG.debug("Generated iOS bundle target %s", target); return target; } private ImmutableSet<TargetNode<?>> getTransitiveFrameworkNodes( TargetNode<? extends HasAppleBundleFields> targetNode) { return FluentIterable.from(targetGraph.getSubgraph(ImmutableSet.of(targetNode)).getNodes()) .filter(new Predicate<TargetNode<?>>() { @Override public boolean apply(TargetNode<?> input) { Optional<TargetNode<AppleBundleDescription.Arg>> appleBundleNode = input .castArg(AppleBundleDescription.Arg.class); if (!appleBundleNode.isPresent()) { return false; } return isFrameworkBundle(appleBundleNode.get().getConstructorArg()); } }).toSet(); } /** * Returns a new list of rules which does not contain framework bundles. */ private ImmutableList<TargetNode<?>> rulesWithoutFrameworkBundles(Iterable<TargetNode<?>> copiedRules) { return FluentIterable.from(copiedRules).filter(new Predicate<TargetNode<?>>() { @Override public boolean apply(TargetNode<?> input) { Optional<TargetNode<AppleBundleDescription.Arg>> appleBundleNode = input .castArg(AppleBundleDescription.Arg.class); if (appleBundleNode.isPresent()) { return !isFrameworkBundle(appleBundleNode.get().getConstructorArg()); } return true; } }).toList(); } private PBXNativeTarget generateAppleBinaryTarget(PBXProject project, TargetNode<AppleNativeTargetDescriptionArg> targetNode) throws IOException { PBXNativeTarget target = generateBinaryTarget(project, Optional.<TargetNode<AppleBundleDescription.Arg>>absent(), targetNode, ProductType.TOOL, "%s", Optional.<Path>absent(), /* includeFrameworks */ true, ImmutableSet.<AppleResourceDescription.Arg>of(), AppleResources.collectDirectResources(targetGraph, targetNode), ImmutableSet.<AppleAssetCatalogDescription.Arg>of(), AppleBuildRules.collectDirectAssetCatalogs(targetGraph, targetNode), Optional.<Iterable<PBXBuildPhase>>absent(), Optional.<TargetNode<AppleBundleDescription.Arg>>absent()); LOG.debug("Generated Apple binary target %s", target); return target; } private PBXNativeTarget generateAppleLibraryTarget(PBXProject project, TargetNode<? extends AppleNativeTargetDescriptionArg> targetNode, Optional<TargetNode<AppleBundleDescription.Arg>> bundleLoaderNode) throws IOException { PBXNativeTarget target = generateCxxLibraryTarget(project, targetNode, AppleResources.collectDirectResources(targetGraph, targetNode), AppleBuildRules.collectDirectAssetCatalogs(targetGraph, targetNode), bundleLoaderNode); LOG.debug("Generated iOS library target %s", target); return target; } private PBXNativeTarget generateCxxLibraryTarget(PBXProject project, TargetNode<? extends CxxLibraryDescription.Arg> targetNode, ImmutableSet<AppleResourceDescription.Arg> directResources, ImmutableSet<AppleAssetCatalogDescription.Arg> directAssetCatalogs, Optional<TargetNode<AppleBundleDescription.Arg>> bundleLoaderNode) throws IOException { boolean isShared = targetNode.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR); ProductType productType = isShared ? ProductType.DYNAMIC_LIBRARY : ProductType.STATIC_LIBRARY; PBXNativeTarget target = generateBinaryTarget(project, Optional.<TargetNode<AppleBundleDescription.Arg>>absent(), targetNode, productType, AppleBuildRules.getOutputFileNameFormatForLibrary(isShared), Optional.<Path>absent(), /* includeFrameworks */ isShared, ImmutableSet.<AppleResourceDescription.Arg>of(), directResources, ImmutableSet.<AppleAssetCatalogDescription.Arg>of(), directAssetCatalogs, Optional.<Iterable<PBXBuildPhase>>absent(), bundleLoaderNode); LOG.debug("Generated Cxx library target %s", target); return target; } private PBXNativeTarget generateBinaryTarget(PBXProject project, Optional<? extends TargetNode<? extends HasAppleBundleFields>> bundle, TargetNode<? extends CxxLibraryDescription.Arg> targetNode, ProductType productType, String productOutputFormat, Optional<Path> infoPlistOptional, boolean includeFrameworks, ImmutableSet<AppleResourceDescription.Arg> recursiveResources, ImmutableSet<AppleResourceDescription.Arg> directResources, ImmutableSet<AppleAssetCatalogDescription.Arg> recursiveAssetCatalogs, ImmutableSet<AppleAssetCatalogDescription.Arg> directAssetCatalogs, Optional<Iterable<PBXBuildPhase>> copyFilesPhases, Optional<TargetNode<AppleBundleDescription.Arg>> bundleLoaderNode) throws IOException { LOG.debug("Generating binary target for node %s", targetNode); TargetNode<?> buildTargetNode = bundle.isPresent() ? bundle.get() : targetNode; final BuildTarget buildTarget = buildTargetNode.getBuildTarget(); String buildTargetName = getProductNameForBuildTarget(buildTarget); CxxLibraryDescription.Arg arg = targetNode.getConstructorArg(); NewNativeTargetProjectMutator mutator = new NewNativeTargetProjectMutator(pathRelativizer, sourcePathResolver); ImmutableSet<SourcePath> exportedHeaders = ImmutableSet.copyOf(getHeaderSourcePaths(arg.exportedHeaders)); ImmutableSet<SourcePath> headers = ImmutableSet.copyOf(getHeaderSourcePaths(arg.headers)); ImmutableMap<CxxSource.Type, ImmutableList<String>> langPreprocessorFlags = targetNode .getConstructorArg().langPreprocessorFlags.get(); mutator.setTargetName(getXcodeTargetName(buildTarget)).setLangPreprocessorFlags(langPreprocessorFlags) .setProduct(productType, buildTargetName, Paths.get(String.format(productOutputFormat, buildTargetName))) .setSourcesWithFlags(ImmutableSet.copyOf(arg.srcs.get())).setPublicHeaders(exportedHeaders) .setPrivateHeaders(headers).setPrefixHeader(arg.prefixHeader) .setRecursiveResources(recursiveResources).setDirectResources(directResources); if (bundle.isPresent()) { HasAppleBundleFields bundleArg = bundle.get().getConstructorArg(); mutator.setInfoPlist(Optional.of(bundleArg.getInfoPlist())); } Optional<TargetNode<AppleNativeTargetDescriptionArg>> appleTargetNode = targetNode .castArg(AppleNativeTargetDescriptionArg.class); if (appleTargetNode.isPresent()) { AppleNativeTargetDescriptionArg appleArg = appleTargetNode.get().getConstructorArg(); mutator = mutator.setExtraXcodeSources(ImmutableSet.copyOf(appleArg.extraXcodeSources.get())); } if (options.contains(Option.CREATE_DIRECTORY_STRUCTURE)) { mutator.setTargetGroupPath(FluentIterable.from(buildTarget.getBasePath()) .transform(Functions.toStringFunction()).toList()); } if (!recursiveAssetCatalogs.isEmpty()) { mutator.setRecursiveAssetCatalogs(recursiveAssetCatalogs); } if (!directAssetCatalogs.isEmpty()) { mutator.setDirectAssetCatalogs(directAssetCatalogs); } if (includeFrameworks) { ImmutableSet.Builder<FrameworkPath> frameworksBuilder = ImmutableSet.builder(); frameworksBuilder.addAll(targetNode.getConstructorArg().frameworks.get()); frameworksBuilder.addAll(targetNode.getConstructorArg().libraries.get()); frameworksBuilder.addAll(collectRecursiveFrameworkDependencies(ImmutableList.of(targetNode))); mutator.setFrameworks(frameworksBuilder.build()); mutator.setArchives(collectRecursiveLibraryDependencies(ImmutableList.of(targetNode))); } // TODO(Task #3772930): Go through all dependencies of the rule // and add any shell script rules here ImmutableList.Builder<TargetNode<?>> preScriptPhases = ImmutableList.builder(); ImmutableList.Builder<TargetNode<?>> postScriptPhases = ImmutableList.builder(); boolean skipRNBundle = ReactNativeFlavors.skipBundling(buildTargetNode.getBuildTarget()); if (bundle.isPresent() && targetNode != bundle.get()) { collectBuildScriptDependencies(targetGraph.getAll(bundle.get().getDeclaredDeps()), preScriptPhases, postScriptPhases, skipRNBundle); } collectBuildScriptDependencies(targetGraph.getAll(targetNode.getDeclaredDeps()), preScriptPhases, postScriptPhases, skipRNBundle); mutator.setPreBuildRunScriptPhasesFromTargetNodes(preScriptPhases.build()); if (copyFilesPhases.isPresent()) { mutator.setCopyFilesPhases(copyFilesPhases.get()); } mutator.setPostBuildRunScriptPhasesFromTargetNodes(postScriptPhases.build()); mutator.skipReactNativeBundle(skipRNBundle); NewNativeTargetProjectMutator.Result targetBuilderResult; try { targetBuilderResult = mutator.buildTargetAndAddToProject(project); } catch (NoSuchBuildTargetException e) { throw new HumanReadableException(e); } PBXGroup targetGroup = targetBuilderResult.targetGroup; SourceTreePath buckFilePath = new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputPathToBuildTargetPath(buildTarget).resolve(buildFileName), Optional.<String>absent()); PBXFileReference buckReference = targetGroup.getOrCreateFileReferenceBySourceTreePath(buckFilePath); buckReference.setExplicitFileType(Optional.of("text.script.python")); // -- configurations ImmutableMap.Builder<String, String> extraSettingsBuilder = ImmutableMap.builder(); extraSettingsBuilder.put("TARGET_NAME", buildTargetName).put("SRCROOT", pathRelativizer.outputPathToBuildTargetPath(buildTarget).toString()); if (bundleLoaderNode.isPresent()) { TargetNode<AppleBundleDescription.Arg> bundleLoader = bundleLoaderNode.get(); String bundleLoaderProductName = getProductNameForBuildTarget(bundleLoader.getBuildTarget()); 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 decdies 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. String bundleLoaderOutputPathDeepSetting = "BUNDLE_LOADER_BUNDLE_STYLE_CONDITIONAL_Contents"; String bundleLoaderOutputPathDeepValue = "Contents/MacOS/"; String bundleLoaderOutputPathValue = Joiner.on('/').join(getTargetOutputPath(bundleLoader), bundleLoaderBundleName, bundleLoaderOutputPathConditional, bundleLoaderProductName); extraSettingsBuilder.put(bundleLoaderOutputPathDeepSetting, bundleLoaderOutputPathDeepValue) .put("BUNDLE_LOADER", bundleLoaderOutputPathValue).put("TEST_HOST", "$(BUNDLE_LOADER)"); } if (infoPlistOptional.isPresent()) { Path infoPlistPath = pathRelativizer.outputDirToRootRelative(infoPlistOptional.get()); extraSettingsBuilder.put("INFOPLIST_FILE", infoPlistPath.toString()); } Optional<SourcePath> prefixHeaderOptional = targetNode.getConstructorArg().prefixHeader; if (prefixHeaderOptional.isPresent()) { Path prefixHeaderRelative = sourcePathResolver.apply(prefixHeaderOptional.get()); Path prefixHeaderPath = pathRelativizer.outputDirToRootRelative(prefixHeaderRelative); extraSettingsBuilder.put("GCC_PREFIX_HEADER", prefixHeaderPath.toString()); extraSettingsBuilder.put("GCC_PRECOMPILE_PREFIX_HEADER", "YES"); } extraSettingsBuilder.put("USE_HEADERMAP", "NO"); ImmutableMap.Builder<String, String> defaultSettingsBuilder = ImmutableMap.builder(); defaultSettingsBuilder.put("REPO_ROOT", projectFilesystem.getRootPath().toAbsolutePath().normalize().toString()); defaultSettingsBuilder.put(PRODUCT_NAME, getProductName(buildTargetNode, buildTarget)); if (bundle.isPresent()) { defaultSettingsBuilder.put("WRAPPER_EXTENSION", getExtensionString(bundle.get().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"); if (!bundle.isPresent() && (targetNode.getType().equals(AppleLibraryDescription.TYPE) || targetNode.getType().equals(CxxLibraryDescription.TYPE))) { defaultSettingsBuilder.put("EXECUTABLE_PREFIX", "lib"); } ImmutableMap.Builder<String, String> appendConfigsBuilder = ImmutableMap.builder(); ImmutableSet<Path> recursiveHeaderSearchPaths = collectRecursiveHeaderSearchPaths(targetNode); ImmutableSet<Path> headerMapBases = recursiveHeaderSearchPaths.isEmpty() ? ImmutableSet.<Path>of() : ImmutableSet.of(pathRelativizer.outputDirToRootRelative(BuckConstant.BUCK_OUTPUT_PATH)); appendConfigsBuilder .put("HEADER_SEARCH_PATHS", Joiner.on(' ').join(Iterables.concat(recursiveHeaderSearchPaths, headerMapBases))) .put("LIBRARY_SEARCH_PATHS", Joiner.on(' ').join(collectRecursiveLibrarySearchPaths(ImmutableSet.of(targetNode)))) .put("FRAMEWORK_SEARCH_PATHS", Joiner.on(' ').join(collectRecursiveFrameworkSearchPaths(ImmutableList.of(targetNode)))) .put("OTHER_CFLAGS", Joiner.on(' ') .join(Iterables.transform( Iterables.concat(cxxBuckConfig.getFlags("cflags").or(DEFAULT_CFLAGS), collectRecursiveExportedPreprocessorFlags( ImmutableList.of(targetNode)), targetNode.getConstructorArg().compilerFlags.get(), targetNode.getConstructorArg().preprocessorFlags.get()), Escaper.BASH_ESCAPER))) .put("OTHER_CPLUSPLUSFLAGS", Joiner.on(' ') .join(Iterables.transform( Iterables.concat(cxxBuckConfig.getFlags("cxxflags").or(DEFAULT_CXXFLAGS), collectRecursiveExportedPreprocessorFlags( ImmutableList.of(targetNode)), targetNode.getConstructorArg().compilerFlags.get(), targetNode.getConstructorArg().preprocessorFlags.get()), Escaper.BASH_ESCAPER))) .put("OTHER_LDFLAGS", Joiner.on(' ') .join(Iterables .transform( Iterables.concat(targetNode.getConstructorArg().linkerFlags.get(), collectRecursiveExportedLinkerFlags( ImmutableList.of(targetNode))), Escaper.BASH_ESCAPER))); ImmutableMap<String, String> appendedConfig = appendConfigsBuilder.build(); Optional<ImmutableSortedMap<String, ImmutableMap<String, String>>> configs = getXcodeBuildConfigurationsForTargetNode( targetNode, appendedConfig); PBXNativeTarget target = targetBuilderResult.target; setTargetBuildConfigurations(getConfigurationNameToXcconfigPath(buildTarget), target, project.getMainGroup(), configs.get(), extraSettingsBuilder.build(), defaultSettingsBuilder.build(), appendedConfig); // -- phases createHeaderSymlinkTree(sourcePathResolver, getPublicCxxHeaders(targetNode), AppleDescriptions.getPathToHeaderSymlinkTree(targetNode, HeaderVisibility.PUBLIC)); createHeaderSymlinkTree(sourcePathResolver, getPrivateCxxHeaders(targetNode), AppleDescriptions.getPathToHeaderSymlinkTree(targetNode, HeaderVisibility.PRIVATE)); if (appleTargetNode.isPresent()) { // Use Core Data models from immediate dependencies only. addCoreDataModelsIntoTarget(appleTargetNode.get(), targetGroup); } return target; } public static String getProductName(TargetNode<?> buildTargetNode, BuildTarget buildTarget) { return getProductNameForTargetNode(buildTargetNode).or(getProductNameForBuildTarget(buildTarget)); } private ImmutableSortedMap<Path, SourcePath> getPublicCxxHeaders( TargetNode<? extends CxxLibraryDescription.Arg> targetNode) { CxxLibraryDescription.Arg arg = targetNode.getConstructorArg(); if (arg instanceof AppleNativeTargetDescriptionArg) { Path headerPathPrefix = AppleDescriptions.getHeaderPathPrefix((AppleNativeTargetDescriptionArg) arg, targetNode.getBuildTarget()); ImmutableSortedMap<String, SourcePath> cxxHeaders = AppleDescriptions .convertAppleHeadersToPublicCxxHeaders(sourcePathResolver, headerPathPrefix, arg); return convertMapKeysToPaths(cxxHeaders); } else { SourcePathResolver sourcePathResolver = sourcePathResolverForNode.apply(targetNode); return ImmutableSortedMap.copyOf(CxxDescriptionEnhancer.parseExportedHeaders( targetNode.getBuildTarget(), sourcePathResolver, Optional.<CxxPlatform>absent(), arg)); } } private ImmutableSortedMap<Path, SourcePath> getPrivateCxxHeaders( TargetNode<? extends CxxLibraryDescription.Arg> targetNode) { CxxLibraryDescription.Arg arg = targetNode.getConstructorArg(); if (arg instanceof AppleNativeTargetDescriptionArg) { Path headerPathPrefix = AppleDescriptions.getHeaderPathPrefix((AppleNativeTargetDescriptionArg) arg, targetNode.getBuildTarget()); ImmutableSortedMap<String, SourcePath> cxxHeaders = AppleDescriptions .convertAppleHeadersToPrivateCxxHeaders(sourcePathResolver, headerPathPrefix, arg); return convertMapKeysToPaths(cxxHeaders); } else { SourcePathResolver sourcePathResolver = sourcePathResolverForNode.apply(targetNode); return ImmutableSortedMap.copyOf(CxxDescriptionEnhancer.parseHeaders(targetNode.getBuildTarget(), sourcePathResolver, Optional.<CxxPlatform>absent(), arg)); } } 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, ImmutableMap<String, String> appendedConfig) { Optional<ImmutableSortedMap<String, ImmutableMap<String, String>>> configs = Optional.absent(); Optional<TargetNode<AppleNativeTargetDescriptionArg>> appleTargetNode = targetNode .castArg(AppleNativeTargetDescriptionArg.class); Optional<TargetNode<HalideLibraryDescription.Arg>> halideTargetNode = targetNode .castArg(HalideLibraryDescription.Arg.class); if (appleTargetNode.isPresent()) { configs = appleTargetNode.get().getConstructorArg().configs; } else if (halideTargetNode.isPresent()) { configs = halideTargetNode.get().getConstructorArg().configs; } if (!configs.isPresent() || (configs.isPresent() && configs.get().isEmpty()) || targetNode.getType().equals(CxxLibraryDescription.TYPE)) { ImmutableMap<String, ImmutableMap<String, String>> defaultConfig = CxxPlatformXcodeConfigGenerator .getDefaultXcodeBuildConfigurationsFromCxxPlatform(defaultCxxPlatform, appendedConfig); configs = Optional.of(ImmutableSortedMap.copyOf(defaultConfig)); } return configs; } private void addCoreDataModelsIntoTarget(TargetNode<? extends CxxLibraryDescription.Arg> targetNode, PBXGroup targetGroup) throws IOException { addCoreDataModelBuildPhase(targetGroup, FluentIterable.from(targetNode.getDeps()).transform(new Function<BuildTarget, TargetNode<?>>() { @Override public TargetNode<?> apply(BuildTarget input) { return targetGraph.get(input); } }).filter(new Predicate<TargetNode<?>>() { @Override public boolean apply(TargetNode<?> input) { return CoreDataModelDescription.TYPE.equals(input.getType()); } }).transform(new Function<TargetNode<?>, CoreDataModelDescription.Arg>() { @Override public CoreDataModelDescription.Arg apply(TargetNode<?> input) { return (CoreDataModelDescription.Arg) input.getConstructorArg(); } }).toSet()); } private Function<String, Path> getConfigurationNameToXcconfigPath(final BuildTarget buildTarget) { return new Function<String, Path>() { @Override public Path apply(String input) { return BuildTargets.getGenPath(buildTarget, "%s-" + input + ".xcconfig"); } }; } private Iterable<SourcePath> getHeaderSourcePaths(Optional<SourceList> headers) { if (!headers.isPresent()) { return ImmutableList.of(); } else if (headers.get().getUnnamedSources().isPresent()) { return headers.get().getUnnamedSources().get(); } else { return headers.get().getNamedSources().get().values(); } } private void generateCombinedTestTarget(final String productName, AppleTestBundleParamsKey key, ImmutableCollection<TargetNode<AppleTestDescription.Arg>> tests) throws IOException { ImmutableSet.Builder<PBXFileReference> testLibs = ImmutableSet.builder(); for (TargetNode<AppleTestDescription.Arg> test : tests) { testLibs.add(getOrCreateTestLibraryFileReference(test)); } NewNativeTargetProjectMutator mutator = new NewNativeTargetProjectMutator(pathRelativizer, sourcePathResolver) .setTargetName(productName) .setProduct(dylibProductTypeByBundleExtension(key.getExtension().getLeft()).get(), productName, Paths.get(productName + "." + getExtensionString(key.getExtension()))) .setSourcesWithFlags(ImmutableSet.of(SourceWithFlags .of(new PathSourcePath(projectFilesystem, emptyFileWithExtension("c"))))) .setArchives(Sets.union(collectRecursiveLibraryDependencies(tests), testLibs.build())) .setRecursiveResources(AppleResources.collectRecursiveResources(targetGraph, tests)) .setRecursiveAssetCatalogs( AppleBuildRules.collectRecursiveAssetCatalogs(targetGraph, tests)); ImmutableSet.Builder<FrameworkPath> frameworksBuilder = ImmutableSet.builder(); frameworksBuilder.addAll(collectRecursiveFrameworkDependencies(tests)); for (TargetNode<AppleTestDescription.Arg> test : tests) { frameworksBuilder.addAll(test.getConstructorArg().frameworks.get()); frameworksBuilder.addAll(test.getConstructorArg().libraries.get()); } mutator.setFrameworks(frameworksBuilder.build()); NewNativeTargetProjectMutator.Result result; try { result = mutator.buildTargetAndAddToProject(project); } catch (NoSuchBuildTargetException e) { throw new HumanReadableException(e); } ImmutableMap.Builder<String, String> overrideBuildSettingsBuilder = ImmutableMap.<String, String>builder() .put("GCC_PREFIX_HEADER", "").put("USE_HEADERMAP", "NO"); if (key.getInfoPlist().isPresent()) { overrideBuildSettingsBuilder.put("INFOPLIST_FILE", pathRelativizer .outputDirToRootRelative(sourcePathResolver.apply(key.getInfoPlist().get())).toString()); } setTargetBuildConfigurations(new Function<String, Path>() { @Override public Path apply(String input) { return outputDirectory.resolve(String.format("xcconfigs/%s-%s.xcconfig", productName, input)); } }, result.target, project.getMainGroup(), key.getConfigs().get(), overrideBuildSettingsBuilder.build(), ImmutableMap.of(PRODUCT_NAME, productName, "WRAPPER_EXTENSION", getExtensionString(key.getExtension())), ImmutableMap.of("FRAMEWORK_SEARCH_PATHS", Joiner.on(' ').join(collectRecursiveFrameworkSearchPaths(tests)), "LIBRARY_SEARCH_PATHS", Joiner.on(' ').join(collectRecursiveLibrarySearchPaths(tests)), "OTHER_LDFLAGS", Joiner.on(' ').join(MoreIterables.zipAndConcat(Iterables.cycle("-Xlinker"), Iterables .concat(key.getLinkerFlags(), collectRecursiveExportedLinkerFlags(tests)))))); buildableCombinedTestTargets.add(result.target); } private String deriveCombinedTestTargetNameFromKey(AppleTestBundleParamsKey key, int combinedTestIndex) { return Joiner.on("-").join("_BuckCombinedTest", getExtensionString(key.getExtension()), combinedTestIndex); } /** * 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(Function<String, Path> configurationNameToXcconfigPath, PBXTarget target, PBXGroup targetGroup, ImmutableMap<String, ImmutableMap<String, String>> configurations, ImmutableMap<String, String> overrideBuildSettings, ImmutableMap<String, String> defaultBuildSettings, ImmutableMap<String, String> appendBuildSettings) throws IOException { 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 = Maps.newHashMap(overrideBuildSettings); 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()); } Iterable<Map.Entry<String, String>> entries = Iterables.concat(targetLevelInlineSettings.entrySet(), combinedOverrideConfigs.entrySet()); Path xcconfigPath = configurationNameToXcconfigPath.apply(configurationEntry.getKey()); projectFilesystem.mkdirs(Preconditions.checkNotNull(xcconfigPath).getParent()); StringBuilder stringBuilder = new StringBuilder(); for (Map.Entry<String, String> entry : entries) { stringBuilder.append(entry.getKey()); stringBuilder.append(" = "); stringBuilder.append(entry.getValue()); stringBuilder.append('\n'); } String xcconfigContents = stringBuilder.toString(); if (MorePaths.fileContentsDiffer(new ByteArrayInputStream(xcconfigContents.getBytes(Charsets.UTF_8)), xcconfigPath, projectFilesystem)) { if (shouldGenerateReadOnlyFiles()) { projectFilesystem.writeContentsToPath(xcconfigContents, xcconfigPath, READ_ONLY_FILE_ATTRIBUTE); } else { projectFilesystem.writeContentsToPath(xcconfigContents, xcconfigPath); } } PBXFileReference fileReference = getConfigurationFileReference(targetGroup, xcconfigPath); outputConfiguration.setBaseConfigurationReference(fileReference); } } 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.<String>absent())); } private void collectBuildScriptDependencies(Iterable<TargetNode<?>> targetNodes, ImmutableList.Builder<TargetNode<?>> preRules, ImmutableList.Builder<TargetNode<?>> postRules, boolean skipRNBundle) { for (TargetNode<?> targetNode : targetNodes) { BuildRuleType type = targetNode.getType(); if (type.equals(IosReactNativeLibraryDescription.TYPE)) { postRules.add(targetNode); if (!skipRNBundle) { requiredBuildTargetsBuilder.add(targetNode.getBuildTarget()); } } else if (type.equals(XcodePostbuildScriptDescription.TYPE)) { postRules.add(targetNode); } else if (type.equals(XcodePrebuildScriptDescription.TYPE)) { preRules.add(targetNode); } } } private void createHeaderSymlinkTree(Function<SourcePath, Path> pathResolver, Map<Path, SourcePath> contents, Path headerSymlinkTreeRoot) throws IOException { 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(pathResolver.apply(entry.getValue())); resolvedContentsBuilder.put(link, existing); } 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).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); 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); HeaderMap.Builder headerMapBuilder = new HeaderMap.Builder(); for (Map.Entry<Path, SourcePath> entry : contents.entrySet()) { headerMapBuilder.add(entry.getKey().toString(), BuckConstant.BUCK_OUTPUT_PATH.relativize(headerSymlinkTreeRoot).resolve(entry.getKey())); } projectFilesystem.writeBytesToPath(headerMapBuilder.build().getBytes(), headerMapLocation); } headerSymlinkTrees.add(headerSymlinkTreeRoot); } private HashCode getHeaderSymlinkTreeHashCode(ImmutableSortedMap<Path, Path> contents) { Hasher hasher = Hashing.sha1().newHasher(); hasher.putBytes(BuckVersion.getVersion().getBytes(Charsets.UTF_8)); 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<CoreDataModelDescription.Arg> dataModels) throws IOException { // TODO(Coneko): actually add a build phase for (final CoreDataModelDescription.Arg 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. final String currentVersionFileName = ".xccurrentversion"; final String currentVersionKey = "_XCCurrentVersionName"; final XCVersionGroup versionGroup = resourcesGroup.getOrCreateChildVersionGroupsBySourceTreePath( new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(dataModel.path), Optional.<String>absent())); projectFilesystem.walkRelativeFileTree(dataModel.path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { if (dir.equals(dataModel.path)) { return FileVisitResult.CONTINUE; } versionGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath(PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(dir), Optional.<String>absent())); return FileVisitResult.SKIP_SUBTREE; } }); Path currentVersionPath = dataModel.path.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.path.resolve(currentVersionName.toString())), Optional.<String>absent())); versionGroup.setCurrentVersion(Optional.of(ref)); } catch (NoSuchFileException e) { if (versionGroup.getChildren().size() == 1) { versionGroup.setCurrentVersion(Optional.of(Iterables.get(versionGroup.getChildren(), 0))); } } } else { resourcesGroup.getOrCreateFileReferenceBySourceTreePath(new SourceTreePath( PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(dataModel.path), Optional.<String>absent())); } } } private Optional<CopyFilePhaseDestinationSpec> getDestinationSpec(TargetNode<?> targetNode) { if (targetNode.getType().equals(AppleBundleDescription.TYPE)) { AppleBundleDescription.Arg arg = (AppleBundleDescription.Arg) targetNode.getConstructorArg(); AppleBundleExtension extension = arg.extension.isLeft() ? arg.extension.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 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 BUNDLE: return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.PLUGINS)); //$CASES-OMITTED$ default: return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.PRODUCTS)); } } else if (targetNode.getType().equals(AppleLibraryDescription.TYPE) || targetNode.getType().equals(CxxLibraryDescription.TYPE)) { if (targetNode.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR)) { return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.FRAMEWORKS)); } else { return Optional.absent(); } } else if (targetNode.getType().equals(AppleBinaryDescription.TYPE)) { return Optional.of(CopyFilePhaseDestinationSpec.of(PBXCopyFilesBuildPhase.Destination.EXECUTABLES)); } else if (targetNode.getType().equals(HalideLibraryDescription.TYPE)) { return Optional.absent(); } else { throw new RuntimeException("Unexpected type: " + targetNode.getType()); } } 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) { Optional<CopyFilePhaseDestinationSpec> optionalDestinationSpec = getDestinationSpec(copiedNode); if (optionalDestinationSpec.isPresent()) { ruleByDestinationSpecBuilder.put(optionalDestinationSpec.get(), 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); for (TargetNode<?> targetNode : targetNodes) { PBXFileReference fileReference = getLibraryFileReference(targetNode); copyFilesBuildPhase.getFiles().add(new PBXBuildFile(fileReference)); } return copyFilesBuildPhase; } /** * Create the project bundle structure and write {@code project.pbxproj}. */ private Path writeProjectFile(PBXProject project) throws IOException { XcodeprojSerializer serializer = new XcodeprojSerializer( new GidGenerator(ImmutableSet.copyOf(gidsToTargetNames.keySet())), 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 (MorePaths.fileContentsDiffer(new ByteArrayInputStream(contentsToWrite.getBytes(Charsets.UTF_8)), serializedProject, projectFilesystem)) { LOG.debug("Regenerating project at %s", serializedProject); if (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); } return xcodeprojDir; } private static String getProductNameForBuildTarget(BuildTarget buildTarget) { return buildTarget.getShortName(); } /** * @param targetNode Must have a header symlink tree or an exception will be thrown. */ private Path getHeaderSymlinkTreeRelativePath(TargetNode<? extends CxxLibraryDescription.Arg> targetNode, HeaderVisibility headerVisibility) { Path treeRoot = AppleDescriptions.getPathToHeaderSymlinkTree(targetNode, headerVisibility); return pathRelativizer.outputDirToRootRelative(treeRoot); } private Path getHeaderMapLocationFromSymlinkTreeRoot(Path headerSymlinkTreeRoot) { return headerSymlinkTreeRoot.resolve(".tree.hmap"); } private Path getHeaderSearchPathFromSymlinkTreeRoot(Path headerSymlinkTreeRoot) { if (options.contains(Option.DISABLE_HEADER_MAPS)) { return headerSymlinkTreeRoot; } else { return getHeaderMapLocationFromSymlinkTreeRoot(headerSymlinkTreeRoot); } } private String getBuiltProductsRelativeTargetOutputPath(TargetNode<?> targetNode) { if (targetNode.getType().equals(AppleBinaryDescription.TYPE) || targetNode.getType().equals(AppleTestDescription.TYPE) || (targetNode.getType().equals(AppleBundleDescription.TYPE) && !isFrameworkBundle((AppleBundleDescription.Arg) 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.Arg>> getAppleNativeNodeOfType(TargetGraph targetGraph, TargetNode<?> targetNode, Set<BuildRuleType> nodeTypes, Set<AppleBundleExtension> bundleExtensions) { Optional<TargetNode<CxxLibraryDescription.Arg>> nativeNode = Optional.absent(); if (nodeTypes.contains(targetNode.getType())) { nativeNode = Optional.of((TargetNode<CxxLibraryDescription.Arg>) targetNode); } else if (targetNode.getType().equals(AppleBundleDescription.TYPE)) { TargetNode<AppleBundleDescription.Arg> bundle = (TargetNode<AppleBundleDescription.Arg>) targetNode; Either<AppleBundleExtension, String> extension = bundle.getConstructorArg().getExtension(); if (extension.isLeft() && bundleExtensions.contains(extension.getLeft())) { nativeNode = Optional.of( (TargetNode<CxxLibraryDescription.Arg>) targetGraph.get(bundle.getConstructorArg().binary)); } } return nativeNode; } private static Optional<TargetNode<CxxLibraryDescription.Arg>> getAppleNativeNode(TargetGraph targetGraph, TargetNode<?> targetNode) { return getAppleNativeNodeOfType(targetGraph, targetNode, ImmutableSet.of(AppleBinaryDescription.TYPE, AppleLibraryDescription.TYPE, CxxLibraryDescription.TYPE), ImmutableSet.of(AppleBundleExtension.APP, AppleBundleExtension.FRAMEWORK)); } private static Optional<TargetNode<CxxLibraryDescription.Arg>> getLibraryNode(TargetGraph targetGraph, TargetNode<?> targetNode) { return getAppleNativeNodeOfType(targetGraph, targetNode, ImmutableSet.of(AppleLibraryDescription.TYPE, CxxLibraryDescription.TYPE), ImmutableSet.of(AppleBundleExtension.FRAMEWORK)); } private ImmutableSet<Path> collectRecursiveHeaderSearchPaths( TargetNode<? extends CxxLibraryDescription.Arg> targetNode) { ImmutableSet.Builder<Path> builder = ImmutableSet.builder(); for (Path headerSymlinkTreePath : collectRecursiveHeaderSymlinkTrees(targetNode)) { builder.add(getHeaderSearchPathFromSymlinkTreeRoot(headerSymlinkTreePath)); } for (Path halideHeaderPath : collectRecursiveHalideLibraryHeaderPaths(targetNode)) { builder.add(halideHeaderPath); } return builder.build(); } private ImmutableSet<Path> collectRecursiveHalideLibraryHeaderPaths( TargetNode<? extends CxxLibraryDescription.Arg> targetNode) { ImmutableSet.Builder<Path> builder = ImmutableSet.builder(); for (TargetNode<?> input : AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(targetGraph, AppleBuildRules.RecursiveDependenciesMode.BUILDING, targetNode, Optional.of(ImmutableSet.<BuildRuleType>of(HalideLibraryDescription.TYPE)))) { BuildTarget buildTarget = input.getBuildTarget(); builder.add( pathRelativizer.outputDirToRootRelative(HalideCompile .headerOutputPath(buildTarget.withFlavors( HalideLibraryDescription.HALIDE_COMPILE_FLAVOR, defaultCxxPlatform.getFlavor())) .getParent())); } return builder.build(); } private ImmutableSet<Path> collectRecursiveHeaderSymlinkTrees( TargetNode<? extends CxxLibraryDescription.Arg> targetNode) { ImmutableSet.Builder<Path> builder = ImmutableSet.builder(); builder.add(getHeaderSymlinkTreeRelativePath(targetNode, HeaderVisibility.PRIVATE)); builder.add(getHeaderSymlinkTreeRelativePath(targetNode, HeaderVisibility.PUBLIC)); for (TargetNode<?> input : AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(targetGraph, AppleBuildRules.RecursiveDependenciesMode.BUILDING, targetNode, Optional.of(AppleBuildRules.XCODE_TARGET_BUILD_RULE_TYPES))) { Optional<TargetNode<CxxLibraryDescription.Arg>> nativeNode = getAppleNativeNode(targetGraph, input); if (nativeNode.isPresent()) { builder.add(getHeaderSymlinkTreeRelativePath(nativeNode.get(), HeaderVisibility.PUBLIC)); } } addHeaderSymlinkTreesForSourceUnderTest(targetNode, builder, HeaderVisibility.PRIVATE); return builder.build(); } private void addHeaderSymlinkTreesForSourceUnderTest(TargetNode<? extends CxxLibraryDescription.Arg> targetNode, ImmutableSet.Builder<Path> headerSymlinkTreesBuilder, HeaderVisibility headerVisibility) { ImmutableSet<TargetNode<?>> directDependencies = ImmutableSet .copyOf(targetGraph.getAll(targetNode.getDeps())); for (TargetNode<?> dependency : directDependencies) { Optional<TargetNode<CxxLibraryDescription.Arg>> nativeNode = getAppleNativeNode(targetGraph, dependency); if (nativeNode.isPresent() && isSourceUnderTest(dependency, nativeNode.get(), targetNode)) { headerSymlinkTreesBuilder.add(getHeaderSymlinkTreeRelativePath(nativeNode.get(), headerVisibility)); } } } private boolean isSourceUnderTest(TargetNode<?> dependencyNode, TargetNode<CxxLibraryDescription.Arg> 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; } private <T extends AbstractDescriptionArg> ImmutableSet<String> collectRecursiveLibrarySearchPaths( Iterable<TargetNode<T>> targetNodes) { return new ImmutableSet.Builder<String>().add("$BUILT_PRODUCTS_DIR") .addAll(collectRecursiveSearchPathsForFrameworkPaths(targetNodes, new Function<AppleNativeTargetDescriptionArg, ImmutableSortedSet<FrameworkPath>>() { @Override public ImmutableSortedSet<FrameworkPath> apply(AppleNativeTargetDescriptionArg input) { return input.libraries.or(ImmutableSortedSet.<FrameworkPath>of()); } })) .build(); } private <T extends AbstractDescriptionArg> ImmutableSet<String> collectRecursiveFrameworkSearchPaths( Iterable<TargetNode<T>> targetNodes) { return new ImmutableSet.Builder<String>().add("$BUILT_PRODUCTS_DIR") .addAll(collectRecursiveSearchPathsForFrameworkPaths(targetNodes, new Function<AppleNativeTargetDescriptionArg, ImmutableSortedSet<FrameworkPath>>() { @Override public ImmutableSortedSet<FrameworkPath> apply(AppleNativeTargetDescriptionArg input) { return input.frameworks.or(ImmutableSortedSet.<FrameworkPath>of()); } })) .build(); } private <T extends AbstractDescriptionArg> Iterable<FrameworkPath> collectRecursiveFrameworkDependencies( Iterable<TargetNode<T>> targetNodes) { return FluentIterable.from(targetNodes) .transformAndConcat(AppleBuildRules.newRecursiveRuleDependencyTransformer(targetGraph, AppleBuildRules.RecursiveDependenciesMode.LINKING, AppleBuildRules.XCODE_TARGET_BUILD_RULE_TYPES)) .transformAndConcat(new Function<TargetNode<?>, Iterable<FrameworkPath>>() { @Override public Iterable<FrameworkPath> apply(TargetNode<?> input) { Optional<TargetNode<CxxLibraryDescription.Arg>> library = getLibraryNode(targetGraph, input); if (library.isPresent() && !AppleLibraryDescription.isSharedLibraryTarget(library.get().getBuildTarget())) { return Iterables.concat(library.get().getConstructorArg().frameworks.get(), library.get().getConstructorArg().libraries.get()); } else { return ImmutableList.of(); } } }); } private <T extends AbstractDescriptionArg> Iterable<String> collectRecursiveSearchPathsForFrameworkPaths( Iterable<TargetNode<T>> targetNodes, final Function<AppleNativeTargetDescriptionArg, ImmutableSortedSet<FrameworkPath>> pathSetExtractor) { return FluentIterable.from(targetNodes) .transformAndConcat(AppleBuildRules.newRecursiveRuleDependencyTransformer(targetGraph, AppleBuildRules.RecursiveDependenciesMode.LINKING, ImmutableSet.of(AppleLibraryDescription.TYPE, CxxLibraryDescription.TYPE))) .append(targetNodes).transformAndConcat(new Function<TargetNode<?>, Iterable<String>>() { @Override public Iterable<String> apply(TargetNode<?> input) { return input.castArg(AppleNativeTargetDescriptionArg.class) .transform(getTargetFrameworkSearchPaths(pathSetExtractor)) .or(ImmutableSet.<String>of()); } }); } private <T extends AbstractDescriptionArg> Iterable<String> collectRecursiveExportedPreprocessorFlags( Iterable<TargetNode<T>> targetNodes) { return FluentIterable.from(targetNodes) .transformAndConcat(AppleBuildRules.newRecursiveRuleDependencyTransformer(targetGraph, AppleBuildRules.RecursiveDependenciesMode.BUILDING, ImmutableSet.of(AppleLibraryDescription.TYPE, CxxLibraryDescription.TYPE))) .append(targetNodes).transformAndConcat(new Function<TargetNode<?>, Iterable<? extends String>>() { @Override public Iterable<? extends String> apply(TargetNode<?> input) { return input.castArg(AppleNativeTargetDescriptionArg.class) .transform(GET_EXPORTED_PREPROCESSOR_FLAGS).or(ImmutableSet.<String>of()); } }); } private <T extends AbstractDescriptionArg> Iterable<String> collectRecursiveExportedLinkerFlags( Iterable<TargetNode<T>> targetNodes) { return FluentIterable.from(targetNodes) .transformAndConcat(AppleBuildRules.newRecursiveRuleDependencyTransformer(targetGraph, AppleBuildRules.RecursiveDependenciesMode.LINKING, ImmutableSet.of(AppleLibraryDescription.TYPE, CxxLibraryDescription.TYPE, HalideLibraryDescription.TYPE))) .append(targetNodes).transformAndConcat(new Function<TargetNode<?>, Iterable<? extends String>>() { @Override public Iterable<String> apply(TargetNode<?> input) { return input.castArg(AppleNativeTargetDescriptionArg.class) .transform(GET_EXPORTED_LINKER_FLAGS).or(ImmutableSet.<String>of()); } }); } private <T extends AbstractDescriptionArg> ImmutableSet<PBXFileReference> collectRecursiveLibraryDependencies( Iterable<TargetNode<T>> targetNodes) { return FluentIterable.from(targetNodes) .transformAndConcat(AppleBuildRules.newRecursiveRuleDependencyTransformer(targetGraph, AppleBuildRules.RecursiveDependenciesMode.LINKING, AppleBuildRules.XCODE_TARGET_BUILD_RULE_TYPES)) .filter(getLibraryWithSourcesToCompilePredicate()) .transform(new Function<TargetNode<?>, PBXFileReference>() { @Override public PBXFileReference apply(TargetNode<?> input) { return getLibraryFileReference(input); } }).toSet(); } private Function<TargetNode<AppleNativeTargetDescriptionArg>, Iterable<String>> getTargetFrameworkSearchPaths( final Function<AppleNativeTargetDescriptionArg, ImmutableSortedSet<FrameworkPath>> pathSetExtractor) { final Function<FrameworkPath, Path> toSearchPath = FrameworkPath .getUnexpandedSearchPathFunction(sourcePathResolver, pathRelativizer.outputDirToRootRelative()); return new Function<TargetNode<AppleNativeTargetDescriptionArg>, Iterable<String>>() { @Override public Iterable<String> apply(TargetNode<AppleNativeTargetDescriptionArg> input) { return FluentIterable.from(pathSetExtractor.apply(input.getConstructorArg())) .transform(toSearchPath).transform(Functions.toStringFunction()); } }; } private SourceTreePath getProductsSourceTreePath(TargetNode<?> targetNode) { String productName = getProductNameForBuildTarget(targetNode.getBuildTarget()); String productOutputName; if (targetNode.getType().equals(AppleLibraryDescription.TYPE) || targetNode.getType().equals(CxxLibraryDescription.TYPE) || targetNode.getType().equals(HalideLibraryDescription.TYPE)) { String productOutputFormat = AppleBuildRules.getOutputFileNameFormatForLibrary( targetNode.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR)); productOutputName = String.format(productOutputFormat, productName); } else if (targetNode.getType().equals(AppleBundleDescription.TYPE) || targetNode.getType().equals(AppleTestDescription.TYPE)) { HasAppleBundleFields arg = (HasAppleBundleFields) targetNode.getConstructorArg(); productOutputName = productName + "." + getExtensionString(arg.getExtension()); } else if (targetNode.getType().equals(AppleBinaryDescription.TYPE)) { productOutputName = productName; } else { throw new RuntimeException("Unexpected type: " + targetNode.getType()); } return new SourceTreePath(PBXReference.SourceTree.BUILT_PRODUCTS_DIR, Paths.get(productOutputName), Optional.<String>absent()); } 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.getType().equals(AppleLibraryDescription.TYPE) || targetNode.getType().equals(AppleBundleDescription.TYPE) || targetNode.getType().equals(CxxLibraryDescription.TYPE) || targetNode.getType().equals(HalideLibraryDescription.TYPE)) { return project.getMainGroup().getOrCreateChildGroupByName("Frameworks") .getOrCreateFileReferenceBySourceTreePath(productsPath); } else if (targetNode.getType().equals(AppleBinaryDescription.TYPE)) { return project.getMainGroup().getOrCreateChildGroupByName("Dependencies") .getOrCreateFileReferenceBySourceTreePath(productsPath); } else { throw new RuntimeException("Unexpected type: " + targetNode.getType()); } } /** * Return a file reference to a test assuming it's built as a static library. */ private PBXFileReference getOrCreateTestLibraryFileReference(TargetNode<AppleTestDescription.Arg> test) { SourceTreePath path = new SourceTreePath(PBXReference.SourceTree.BUILT_PRODUCTS_DIR, Paths.get(getBuiltProductsRelativeTargetOutputPath(test)) .resolve(String.format(AppleBuildRules.getOutputFileNameFormatForLibrary(false), getProductNameForBuildTarget(test.getBuildTarget()))), Optional.<String>absent()); return project.getMainGroup().getOrCreateChildGroupByName("Test Libraries") .getOrCreateFileReferenceBySourceTreePath(path); } /** * 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.contains(Option.USE_SHORT_NAMES_FOR_TARGETS) ? target.getShortName() : target.getFullyQualifiedName(); } @SuppressWarnings("incomplete-switch") 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(); if (binaryNode.getType().equals(AppleLibraryDescription.TYPE) || binaryNode.getType().equals(CxxLibraryDescription.TYPE)) { if (binaryNode.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR)) { Optional<ProductType> productType = dylibProductTypeByBundleExtension(extension); if (productType.isPresent()) { return productType.get(); } } else { switch (extension) { case FRAMEWORK: return ProductType.STATIC_FRAMEWORK; } } } else if (binaryNode.getType().equals(AppleBinaryDescription.TYPE)) { switch (extension) { case APP: return ProductType.APPLICATION; } } else if (binaryNode.getType().equals(AppleTestDescription.TYPE)) { switch (extension) { case OCTEST: return ProductType.BUNDLE; case XCTEST: return ProductType.UNIT_TEST; } } } return ProductType.BUNDLE; } private boolean shouldGenerateReadOnlyFiles() { return options.contains(Option.GENERATE_READ_ONLY_FILES); } 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 bundleRequiresRemovalOfAllTransitiveFrameworks( TargetNode<? extends HasAppleBundleFields> targetNode) { return isFrameworkBundle(targetNode.getConstructorArg()); } private static boolean bundleRequiresAllTransitiveFrameworks( TargetNode<? extends AppleNativeTargetDescriptionArg> binaryNode) { return binaryNode.castArg(AppleBinaryDescription.Arg.class).isPresent(); } private Path emptyFileWithExtension(String extension) { Path path = BuckConstant.GEN_PATH.resolve("xcode-scripts/emptyFile." + extension); if (!projectFilesystem.exists(path)) { try { projectFilesystem.createParentDirs(path); projectFilesystem.newFileOutputStream(path).close(); } catch (IOException e) { throw new RuntimeException(e); } } return path; } private Path resolveSourcePath(SourcePath sourcePath) { if (sourcePath instanceof PathSourcePath) { return ((PathSourcePath) sourcePath).getRelativePath(); } Preconditions.checkArgument(sourcePath instanceof BuildTargetSourcePath); BuildTargetSourcePath buildTargetSourcePath = (BuildTargetSourcePath) sourcePath; BuildTarget buildTarget = buildTargetSourcePath.getTarget(); TargetNode<?> node = targetGraph.get(buildTarget); Optional<TargetNode<ExportFileDescription.Arg>> exportFileNode = node .castArg(ExportFileDescription.Arg.class); if (!exportFileNode.isPresent()) { SourcePathResolver sourcePathResolver = sourcePathResolverForNode.apply(node); Path output = sourcePathResolver.getRelativePath(sourcePath); if (output == null) { throw new HumanReadableException("The target '%s' does not have an output.", node.getBuildTarget()); } requiredBuildTargetsBuilder.add(buildTarget); return output; } Optional<SourcePath> src = exportFileNode.get().getConstructorArg().src; if (!src.isPresent()) { return buildTarget.getBasePath().resolve(buildTarget.getShortNameAndFlavorPostfix()); } return resolveSourcePath(src.get()); } private Predicate<TargetNode<?>> getLibraryWithSourcesToCompilePredicate() { return new Predicate<TargetNode<?>>() { @Override public boolean apply(TargetNode<?> input) { if (input.getType() == HalideLibraryDescription.TYPE) { return true; } Optional<TargetNode<CxxLibraryDescription.Arg>> library = getLibraryNode(targetGraph, input); if (!library.isPresent()) { return false; } return (library.get().getConstructorArg().srcs.get().size() != 0); } }; } /** * @return product type of a bundle containing a dylib. */ private static Optional<ProductType> dylibProductTypeByBundleExtension(AppleBundleExtension extension) { switch (extension) { case FRAMEWORK: return Optional.of(ProductType.FRAMEWORK); case APPEX: return Optional.of(ProductType.APP_EXTENSION); case BUNDLE: return Optional.of(ProductType.BUNDLE); case OCTEST: return Optional.of(ProductType.BUNDLE); case XCTEST: return Optional.of(ProductType.UNIT_TEST); // $CASES-OMITTED$ default: return Optional.absent(); } } /** * 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.getType().equals(AppleBundleDescription.TYPE)) { AppleBundleDescription.Arg arg = (AppleBundleDescription.Arg) targetNode.getConstructorArg(); return arg.getXcodeProductType().equals(Optional.of(ProductType.WATCH_APPLICATION.getIdentifier())); } return false; } }