/* * 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.project_generator; import com.dd.plist.NSArray; import com.dd.plist.NSDictionary; import com.dd.plist.NSString; import com.facebook.buck.apple.AppleAssetCatalogDescriptionArg; import com.facebook.buck.apple.AppleHeaderVisibilities; import com.facebook.buck.apple.AppleResourceDescriptionArg; import com.facebook.buck.apple.AppleWrapperResourceArg; import com.facebook.buck.apple.GroupedSource; import com.facebook.buck.apple.RuleUtils; import com.facebook.buck.apple.XcodePostbuildScriptDescription; import com.facebook.buck.apple.XcodePrebuildScriptDescription; import com.facebook.buck.apple.XcodeScriptDescriptionArg; import com.facebook.buck.apple.xcode.xcodeproj.PBXBuildFile; import com.facebook.buck.apple.xcode.xcodeproj.PBXBuildPhase; import com.facebook.buck.apple.xcode.xcodeproj.PBXFileReference; import com.facebook.buck.apple.xcode.xcodeproj.PBXFrameworksBuildPhase; import com.facebook.buck.apple.xcode.xcodeproj.PBXGroup; import com.facebook.buck.apple.xcode.xcodeproj.PBXHeadersBuildPhase; 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.PBXResourcesBuildPhase; import com.facebook.buck.apple.xcode.xcodeproj.PBXShellScriptBuildPhase; import com.facebook.buck.apple.xcode.xcodeproj.PBXSourcesBuildPhase; import com.facebook.buck.apple.xcode.xcodeproj.PBXVariantGroup; import com.facebook.buck.apple.xcode.xcodeproj.ProductType; import com.facebook.buck.apple.xcode.xcodeproj.SourceTreePath; import com.facebook.buck.cxx.CxxSource; import com.facebook.buck.cxx.HeaderVisibility; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.js.CoreReactNativeLibraryArg; import com.facebook.buck.js.IosReactNativeLibraryDescription; import com.facebook.buck.js.ReactNativeBundle; import com.facebook.buck.js.ReactNativeLibraryArg; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourceWithFlags; import com.facebook.buck.rules.TargetNode; import com.facebook.buck.rules.coercer.FrameworkPath; import com.facebook.buck.util.HumanReadableException; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.io.Files; import com.google.common.io.Resources; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import org.stringtemplate.v4.ST; /** * Configures a PBXProject by adding a PBXNativeTarget and its associated dependencies into a * PBXProject object graph. */ class NewNativeTargetProjectMutator { private static final Logger LOG = Logger.get(NewNativeTargetProjectMutator.class); private static final String REACT_NATIVE_PACKAGE_TEMPLATE = "rn-package.st"; public static class Result { public final PBXNativeTarget target; public final Optional<PBXGroup> targetGroup; private Result(PBXNativeTarget target, Optional<PBXGroup> targetGroup) { this.target = target; this.targetGroup = targetGroup; } } private final PathRelativizer pathRelativizer; private final Function<SourcePath, Path> sourcePathResolver; private ProductType productType = ProductType.BUNDLE; private Path productOutputPath = Paths.get(""); private String productName = ""; private String targetName = ""; private boolean frameworkHeadersEnabled = false; private ImmutableMap<CxxSource.Type, ImmutableList<String>> langPreprocessorFlags = ImmutableMap.of(); private ImmutableList<String> targetGroupPath = ImmutableList.of(); private ImmutableSet<SourceWithFlags> sourcesWithFlags = ImmutableSet.of(); private ImmutableSet<SourcePath> extraXcodeSources = ImmutableSet.of(); private ImmutableSet<SourcePath> extraXcodeFiles = ImmutableSet.of(); private ImmutableSet<SourcePath> publicHeaders = ImmutableSet.of(); private ImmutableSet<SourcePath> privateHeaders = ImmutableSet.of(); private Optional<SourcePath> prefixHeader = Optional.empty(); private Optional<SourcePath> infoPlist = Optional.empty(); private Optional<SourcePath> bridgingHeader = Optional.empty(); private ImmutableSet<FrameworkPath> frameworks = ImmutableSet.of(); private ImmutableSet<PBXFileReference> archives = ImmutableSet.of(); private ImmutableSet<AppleResourceDescriptionArg> recursiveResources = ImmutableSet.of(); private ImmutableSet<AppleResourceDescriptionArg> directResources = ImmutableSet.of(); private ImmutableSet<AppleAssetCatalogDescriptionArg> recursiveAssetCatalogs = ImmutableSet.of(); private ImmutableSet<AppleAssetCatalogDescriptionArg> directAssetCatalogs = ImmutableSet.of(); private ImmutableSet<AppleWrapperResourceArg> wrapperResources = ImmutableSet.of(); private Iterable<PBXShellScriptBuildPhase> preBuildRunScriptPhases = ImmutableList.of(); private Iterable<PBXBuildPhase> copyFilesPhases = ImmutableList.of(); private Iterable<PBXShellScriptBuildPhase> postBuildRunScriptPhases = ImmutableList.of(); public NewNativeTargetProjectMutator( PathRelativizer pathRelativizer, Function<SourcePath, Path> sourcePathResolver) { this.pathRelativizer = pathRelativizer; this.sourcePathResolver = sourcePathResolver; } /** * Set product related configuration. * * @param productType declared product type * @param productName product display name * @param productOutputPath build output relative product path. */ public NewNativeTargetProjectMutator setProduct( ProductType productType, String productName, Path productOutputPath) { this.productName = productName; this.productType = productType; this.productOutputPath = productOutputPath; return this; } public NewNativeTargetProjectMutator setTargetName(String targetName) { this.targetName = targetName; return this; } public NewNativeTargetProjectMutator setFrameworkHeadersEnabled(boolean enabled) { this.frameworkHeadersEnabled = enabled; return this; } public NewNativeTargetProjectMutator setLangPreprocessorFlags( ImmutableMap<CxxSource.Type, ImmutableList<String>> langPreprocessorFlags) { this.langPreprocessorFlags = langPreprocessorFlags; return this; } public NewNativeTargetProjectMutator setTargetGroupPath(ImmutableList<String> targetGroupPath) { this.targetGroupPath = targetGroupPath; return this; } public NewNativeTargetProjectMutator setSourcesWithFlags(Set<SourceWithFlags> sourcesWithFlags) { this.sourcesWithFlags = ImmutableSet.copyOf(sourcesWithFlags); return this; } public NewNativeTargetProjectMutator setExtraXcodeSources(Set<SourcePath> extraXcodeSources) { this.extraXcodeSources = ImmutableSet.copyOf(extraXcodeSources); return this; } public NewNativeTargetProjectMutator setExtraXcodeFiles(Set<SourcePath> extraXcodeFiles) { this.extraXcodeFiles = ImmutableSet.copyOf(extraXcodeFiles); return this; } public NewNativeTargetProjectMutator setPublicHeaders(Set<SourcePath> publicHeaders) { this.publicHeaders = ImmutableSet.copyOf(publicHeaders); return this; } public NewNativeTargetProjectMutator setPrivateHeaders(Set<SourcePath> privateHeaders) { this.privateHeaders = ImmutableSet.copyOf(privateHeaders); return this; } public NewNativeTargetProjectMutator setPrefixHeader(Optional<SourcePath> prefixHeader) { this.prefixHeader = prefixHeader; return this; } public NewNativeTargetProjectMutator setInfoPlist(Optional<SourcePath> infoPlist) { this.infoPlist = infoPlist; return this; } public NewNativeTargetProjectMutator setBridgingHeader(Optional<SourcePath> bridgingHeader) { this.bridgingHeader = bridgingHeader; return this; } public NewNativeTargetProjectMutator setFrameworks(Set<FrameworkPath> frameworks) { this.frameworks = ImmutableSet.copyOf(frameworks); return this; } public NewNativeTargetProjectMutator setArchives(Set<PBXFileReference> archives) { this.archives = ImmutableSet.copyOf(archives); return this; } public NewNativeTargetProjectMutator setRecursiveResources( Set<AppleResourceDescriptionArg> recursiveResources) { this.recursiveResources = ImmutableSet.copyOf(recursiveResources); return this; } public NewNativeTargetProjectMutator setDirectResources( ImmutableSet<AppleResourceDescriptionArg> directResources) { this.directResources = directResources; return this; } public NewNativeTargetProjectMutator setWrapperResources( ImmutableSet<AppleWrapperResourceArg> wrapperResources) { this.wrapperResources = wrapperResources; return this; } public NewNativeTargetProjectMutator setPreBuildRunScriptPhasesFromTargetNodes( Iterable<TargetNode<?, ?>> nodes) { preBuildRunScriptPhases = createScriptsForTargetNodes(nodes); return this; } public NewNativeTargetProjectMutator setPreBuildRunScriptPhases( Iterable<PBXShellScriptBuildPhase> phases) { preBuildRunScriptPhases = phases; return this; } public NewNativeTargetProjectMutator setCopyFilesPhases(Iterable<PBXBuildPhase> phases) { copyFilesPhases = phases; return this; } public NewNativeTargetProjectMutator setPostBuildRunScriptPhasesFromTargetNodes( Iterable<TargetNode<?, ?>> nodes) { postBuildRunScriptPhases = createScriptsForTargetNodes(nodes); return this; } /** * @param recursiveAssetCatalogs List of asset catalog targets of targetNode and dependencies of * targetNode. */ public NewNativeTargetProjectMutator setRecursiveAssetCatalogs( Set<AppleAssetCatalogDescriptionArg> recursiveAssetCatalogs) { this.recursiveAssetCatalogs = ImmutableSet.copyOf(recursiveAssetCatalogs); return this; } /** @param directAssetCatalogs List of asset catalog targets targetNode directly depends on */ public NewNativeTargetProjectMutator setDirectAssetCatalogs( Set<AppleAssetCatalogDescriptionArg> directAssetCatalogs) { this.directAssetCatalogs = ImmutableSet.copyOf(directAssetCatalogs); return this; } public Result buildTargetAndAddToProject(PBXProject project, boolean addBuildPhases) { PBXNativeTarget target = new PBXNativeTarget(targetName); Optional<PBXGroup> optTargetGroup; if (addBuildPhases) { PBXGroup targetGroup = project.getMainGroup().getOrCreateDescendantGroupByPath(targetGroupPath); targetGroup = targetGroup.getOrCreateChildGroupByName(targetName); // Phases addRunScriptBuildPhases(target, preBuildRunScriptPhases); addPhasesAndGroupsForSources(target, targetGroup); addFrameworksBuildPhase(project, target); addResourcesFileReference(targetGroup); addResourcesBuildPhase(target, targetGroup); target.getBuildPhases().addAll((Collection<? extends PBXBuildPhase>) copyFilesPhases); addRunScriptBuildPhases(target, postBuildRunScriptPhases); optTargetGroup = Optional.of(targetGroup); } else { optTargetGroup = Optional.empty(); } // Product PBXGroup productsGroup = project.getMainGroup().getOrCreateChildGroupByName("Products"); PBXFileReference productReference = productsGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath( PBXReference.SourceTree.BUILT_PRODUCTS_DIR, productOutputPath, Optional.empty())); target.setProductName(productName); target.setProductReference(productReference); target.setProductType(productType); project.getTargets().add(target); return new Result(target, optTargetGroup); } private void addPhasesAndGroupsForSources(PBXNativeTarget target, PBXGroup targetGroup) { PBXGroup sourcesGroup = targetGroup.getOrCreateChildGroupByName("Sources"); // Sources groups stay in the order in which they're declared in the BUCK file. sourcesGroup.setSortPolicy(PBXGroup.SortPolicy.UNSORTED); PBXSourcesBuildPhase sourcesBuildPhase = new PBXSourcesBuildPhase(); PBXHeadersBuildPhase headersBuildPhase = new PBXHeadersBuildPhase(); traverseGroupsTreeAndHandleSources( sourcesGroup, sourcesBuildPhase, headersBuildPhase, RuleUtils.createGroupsFromSourcePaths( pathRelativizer::outputPathToSourcePath, sourcesWithFlags, extraXcodeSources, extraXcodeFiles, publicHeaders, privateHeaders)); if (prefixHeader.isPresent()) { SourceTreePath prefixHeaderSourceTreePath = new SourceTreePath( PBXReference.SourceTree.GROUP, pathRelativizer.outputPathToSourcePath(prefixHeader.get()), Optional.empty()); sourcesGroup.getOrCreateFileReferenceBySourceTreePath(prefixHeaderSourceTreePath); } if (infoPlist.isPresent()) { SourceTreePath infoPlistSourceTreePath = new SourceTreePath( PBXReference.SourceTree.GROUP, pathRelativizer.outputPathToSourcePath(infoPlist.get()), Optional.empty()); sourcesGroup.getOrCreateFileReferenceBySourceTreePath(infoPlistSourceTreePath); } if (bridgingHeader.isPresent()) { SourceTreePath bridgingHeaderSourceTreePath = new SourceTreePath( PBXReference.SourceTree.GROUP, pathRelativizer.outputPathToSourcePath(bridgingHeader.get()), Optional.empty()); sourcesGroup.getOrCreateFileReferenceBySourceTreePath(bridgingHeaderSourceTreePath); } if (!sourcesBuildPhase.getFiles().isEmpty()) { target.getBuildPhases().add(sourcesBuildPhase); } if (!headersBuildPhase.getFiles().isEmpty()) { target.getBuildPhases().add(headersBuildPhase); } } private void traverseGroupsTreeAndHandleSources( final PBXGroup sourcesGroup, final PBXSourcesBuildPhase sourcesBuildPhase, final PBXHeadersBuildPhase headersBuildPhase, Iterable<GroupedSource> groupedSources) { GroupedSource.Visitor visitor = new GroupedSource.Visitor() { @Override public void visitSourceWithFlags(SourceWithFlags sourceWithFlags) { addSourcePathToSourcesBuildPhase(sourceWithFlags, sourcesGroup, sourcesBuildPhase); } @Override public void visitIgnoredSource(SourcePath source) { addSourcePathToSourceTree(source, sourcesGroup); } @Override public void visitPublicHeader(SourcePath publicHeader) { addSourcePathToHeadersBuildPhase( publicHeader, sourcesGroup, headersBuildPhase, HeaderVisibility.PUBLIC); } @Override public void visitPrivateHeader(SourcePath privateHeader) { addSourcePathToHeadersBuildPhase( privateHeader, sourcesGroup, headersBuildPhase, HeaderVisibility.PRIVATE); } @Override public void visitSourceGroup( String sourceGroupName, Path sourceGroupPathRelativeToTarget, List<GroupedSource> sourceGroup) { PBXGroup newSourceGroup = sourcesGroup.getOrCreateChildGroupByName(sourceGroupName); newSourceGroup.setSourceTree(PBXReference.SourceTree.SOURCE_ROOT); newSourceGroup.setPath(sourceGroupPathRelativeToTarget.toString()); // Sources groups stay in the order in which they're in the GroupedSource. newSourceGroup.setSortPolicy(PBXGroup.SortPolicy.UNSORTED); traverseGroupsTreeAndHandleSources( newSourceGroup, sourcesBuildPhase, headersBuildPhase, sourceGroup); } }; for (GroupedSource groupedSource : groupedSources) { groupedSource.visit(visitor); } } private void addSourcePathToSourcesBuildPhase( SourceWithFlags sourceWithFlags, PBXGroup sourcesGroup, PBXSourcesBuildPhase sourcesBuildPhase) { SourceTreePath sourceTreePath = new SourceTreePath( PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative( sourcePathResolver.apply(sourceWithFlags.getSourcePath())), Optional.empty()); PBXFileReference fileReference = sourcesGroup.getOrCreateFileReferenceBySourceTreePath(sourceTreePath); PBXBuildFile buildFile = new PBXBuildFile(fileReference); sourcesBuildPhase.getFiles().add(buildFile); ImmutableList<String> customLangPreprocessorFlags = ImmutableList.of(); Optional<CxxSource.Type> sourceType = CxxSource.Type.fromExtension(Files.getFileExtension(sourceTreePath.toString())); if (sourceType.isPresent() && langPreprocessorFlags.containsKey(sourceType.get())) { customLangPreprocessorFlags = langPreprocessorFlags.get(sourceType.get()); } ImmutableList<String> customFlags = ImmutableList.copyOf( Iterables.concat(customLangPreprocessorFlags, sourceWithFlags.getFlags())); if (!customFlags.isEmpty()) { NSDictionary settings = new NSDictionary(); settings.put("COMPILER_FLAGS", Joiner.on(' ').join(customFlags)); buildFile.setSettings(Optional.of(settings)); } LOG.verbose( "Added source path %s to group %s, flags %s, PBXFileReference %s", sourceWithFlags, sourcesGroup.getName(), customFlags, fileReference); } private void addSourcePathToSourceTree(SourcePath sourcePath, PBXGroup sourcesGroup) { sourcesGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath( PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputPathToSourcePath(sourcePath), Optional.empty())); } private void addSourcePathToHeadersBuildPhase( SourcePath headerPath, PBXGroup headersGroup, PBXHeadersBuildPhase headersBuildPhase, HeaderVisibility visibility) { PBXFileReference fileReference = headersGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath( PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputPathToSourcePath(headerPath), Optional.empty())); PBXBuildFile buildFile = new PBXBuildFile(fileReference); if (visibility != HeaderVisibility.PRIVATE) { if (this.frameworkHeadersEnabled && (this.productType == ProductType.FRAMEWORK || this.productType == ProductType.STATIC_FRAMEWORK)) { headersBuildPhase.getFiles().add(buildFile); } NSDictionary settings = new NSDictionary(); settings.put( "ATTRIBUTES", new NSArray(new NSString(AppleHeaderVisibilities.toXcodeAttribute(visibility)))); buildFile.setSettings(Optional.of(settings)); } else { buildFile.setSettings(Optional.empty()); } } private void addFrameworksBuildPhase(PBXProject project, PBXNativeTarget target) { if (frameworks.isEmpty() && archives.isEmpty()) { return; } PBXGroup sharedFrameworksGroup = project.getMainGroup().getOrCreateChildGroupByName("Frameworks"); PBXFrameworksBuildPhase frameworksBuildPhase = new PBXFrameworksBuildPhase(); target.getBuildPhases().add(frameworksBuildPhase); for (FrameworkPath framework : frameworks) { SourceTreePath sourceTreePath; if (framework.getSourceTreePath().isPresent()) { sourceTreePath = framework.getSourceTreePath().get(); } else if (framework.getSourcePath().isPresent()) { sourceTreePath = new SourceTreePath( PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputPathToSourcePath(framework.getSourcePath().get()), Optional.empty()); } else { throw new RuntimeException(); } PBXFileReference fileReference = sharedFrameworksGroup.getOrCreateFileReferenceBySourceTreePath(sourceTreePath); frameworksBuildPhase.getFiles().add(new PBXBuildFile(fileReference)); } for (PBXFileReference archive : archives) { frameworksBuildPhase.getFiles().add(new PBXBuildFile(archive)); } } private void addResourcesFileReference(PBXGroup targetGroup) { ImmutableSet.Builder<Path> resourceFiles = ImmutableSet.builder(); ImmutableSet.Builder<Path> resourceDirs = ImmutableSet.builder(); ImmutableSet.Builder<Path> variantResourceFiles = ImmutableSet.builder(); collectResourcePathsFromConstructorArgs( directResources, directAssetCatalogs, ImmutableSet.of(), resourceFiles, resourceDirs, variantResourceFiles); addResourcesFileReference( targetGroup, resourceFiles.build(), resourceDirs.build(), variantResourceFiles.build(), ignored -> {}, ignored -> {}); } private PBXBuildPhase addResourcesBuildPhase(PBXNativeTarget target, PBXGroup targetGroup) { ImmutableSet.Builder<Path> resourceFiles = ImmutableSet.builder(); ImmutableSet.Builder<Path> resourceDirs = ImmutableSet.builder(); ImmutableSet.Builder<Path> variantResourceFiles = ImmutableSet.builder(); collectResourcePathsFromConstructorArgs( recursiveResources, recursiveAssetCatalogs, wrapperResources, resourceFiles, resourceDirs, variantResourceFiles); final PBXBuildPhase phase = new PBXResourcesBuildPhase(); addResourcesFileReference( targetGroup, resourceFiles.build(), resourceDirs.build(), variantResourceFiles.build(), input -> { PBXBuildFile buildFile = new PBXBuildFile(input); phase.getFiles().add(buildFile); }, input -> { PBXBuildFile buildFile = new PBXBuildFile(input); phase.getFiles().add(buildFile); }); if (!phase.getFiles().isEmpty()) { target.getBuildPhases().add(phase); LOG.debug("Added resources build phase %s", phase); } return phase; } private void collectResourcePathsFromConstructorArgs( Set<AppleResourceDescriptionArg> resourceArgs, Set<AppleAssetCatalogDescriptionArg> assetCatalogArgs, Set<AppleWrapperResourceArg> resourcePathArgs, ImmutableSet.Builder<Path> resourceFilesBuilder, ImmutableSet.Builder<Path> resourceDirsBuilder, ImmutableSet.Builder<Path> variantResourceFilesBuilder) { for (AppleResourceDescriptionArg arg : resourceArgs) { resourceFilesBuilder.addAll(Iterables.transform(arg.getFiles(), sourcePathResolver)); resourceDirsBuilder.addAll(Iterables.transform(arg.getDirs(), sourcePathResolver)); variantResourceFilesBuilder.addAll( Iterables.transform(arg.getVariants(), sourcePathResolver)); } for (AppleAssetCatalogDescriptionArg arg : assetCatalogArgs) { resourceDirsBuilder.addAll(Iterables.transform(arg.getDirs(), sourcePathResolver)); } for (AppleWrapperResourceArg arg : resourcePathArgs) { resourceDirsBuilder.add(arg.getPath()); } } private void addResourcesFileReference( PBXGroup targetGroup, ImmutableSet<Path> resourceFiles, ImmutableSet<Path> resourceDirs, ImmutableSet<Path> variantResourceFiles, Consumer<? super PBXFileReference> resourceCallback, Consumer<? super PBXVariantGroup> variantGroupCallback) { if (resourceFiles.isEmpty() && resourceDirs.isEmpty() && variantResourceFiles.isEmpty()) { return; } PBXGroup resourcesGroup = targetGroup.getOrCreateChildGroupByName("Resources"); for (Path path : resourceFiles) { PBXFileReference fileReference = resourcesGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath( PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(path), Optional.empty())); resourceCallback.accept(fileReference); } for (Path path : resourceDirs) { PBXFileReference fileReference = resourcesGroup.getOrCreateFileReferenceBySourceTreePath( new SourceTreePath( PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(path), Optional.of("folder"))); resourceCallback.accept(fileReference); } Map<String, PBXVariantGroup> variantGroups = new HashMap<>(); for (Path variantFilePath : variantResourceFiles) { String lprojSuffix = ".lproj"; Path variantDirectory = variantFilePath.getParent(); if (variantDirectory == null || !variantDirectory.toString().endsWith(lprojSuffix)) { throw new HumanReadableException( "Variant files have to be in a directory with name ending in '.lproj', " + "but '%s' is not.", variantFilePath); } String variantDirectoryName = variantDirectory.getFileName().toString(); String variantLocalization = variantDirectoryName.substring(0, variantDirectoryName.length() - lprojSuffix.length()); String variantFileName = variantFilePath.getFileName().toString(); PBXVariantGroup variantGroup = variantGroups.get(variantFileName); if (variantGroup == null) { variantGroup = resourcesGroup.getOrCreateChildVariantGroupByName(variantFileName); variantGroupCallback.accept(variantGroup); variantGroups.put(variantFileName, variantGroup); } SourceTreePath sourceTreePath = new SourceTreePath( PBXReference.SourceTree.SOURCE_ROOT, pathRelativizer.outputDirToRootRelative(variantFilePath), Optional.empty()); variantGroup.getOrCreateVariantFileReferenceByNameAndSourceTreePath( variantLocalization, sourceTreePath); } } private ImmutableList<PBXShellScriptBuildPhase> createScriptsForTargetNodes( Iterable<TargetNode<?, ?>> nodes) throws IllegalStateException { ImmutableList.Builder<PBXShellScriptBuildPhase> builder = ImmutableList.builder(); for (TargetNode<?, ?> node : nodes) { PBXShellScriptBuildPhase shellScriptBuildPhase = new PBXShellScriptBuildPhase(); boolean nodeIsPrebuildScript = node.getDescription() instanceof XcodePrebuildScriptDescription; boolean nodeIsPostbuildScript = node.getDescription() instanceof XcodePostbuildScriptDescription; if (nodeIsPrebuildScript || nodeIsPostbuildScript) { XcodeScriptDescriptionArg arg = (XcodeScriptDescriptionArg) node.getConstructorArg(); shellScriptBuildPhase .getInputPaths() .addAll( FluentIterable.from(arg.getSrcs()) .transform(sourcePathResolver) .transform(pathRelativizer::outputDirToRootRelative) .transform(Object::toString) .toSet()); shellScriptBuildPhase.getOutputPaths().addAll(arg.getOutputs()); shellScriptBuildPhase.setShellScript(arg.getCmd()); } else if (node.getDescription() instanceof IosReactNativeLibraryDescription) { shellScriptBuildPhase.setShellScript(generateXcodeShellScript(node)); } else { // unreachable throw new IllegalStateException("Invalid rule type for shell script build phase"); } builder.add(shellScriptBuildPhase); } return builder.build(); } private void addRunScriptBuildPhases( PBXNativeTarget target, Iterable<PBXShellScriptBuildPhase> phases) { for (PBXShellScriptBuildPhase phase : phases) { target.getBuildPhases().add(phase); } } private String generateXcodeShellScript(TargetNode<?, ?> targetNode) { Preconditions.checkArgument(targetNode.getConstructorArg() instanceof ReactNativeLibraryArg); ST template; try { template = new ST( Resources.toString( Resources.getResource( NewNativeTargetProjectMutator.class, REACT_NATIVE_PACKAGE_TEMPLATE), Charsets.UTF_8)); } catch (IOException e) { throw new RuntimeException("There was an error loading 'rn_package.st' template", e); } CoreReactNativeLibraryArg args = (CoreReactNativeLibraryArg) targetNode.getConstructorArg(); template.add("bundle_name", args.getBundleName()); ProjectFilesystem filesystem = targetNode.getFilesystem(); BuildTarget buildTarget = targetNode.getBuildTarget(); Path jsOutput = ReactNativeBundle.getPathToJSBundleDir(buildTarget, filesystem) .resolve(args.getBundleName()); template.add("built_bundle_path", filesystem.resolve(jsOutput)); Path resourceOutput = ReactNativeBundle.getPathToResources(buildTarget, filesystem); template.add("built_resources_path", filesystem.resolve(resourceOutput)); Path sourceMap = ReactNativeBundle.getPathToSourceMap(buildTarget, filesystem); template.add("built_source_map_path", filesystem.resolve(sourceMap)); return template.render(); } }