/* * Copyright 2016-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.swift; import com.facebook.buck.cxx.CxxDescriptionEnhancer; import com.facebook.buck.cxx.CxxHeaders; import com.facebook.buck.cxx.CxxPlatform; import com.facebook.buck.cxx.CxxPreprocessables; import com.facebook.buck.cxx.CxxPreprocessorInput; import com.facebook.buck.cxx.HeaderVisibility; import com.facebook.buck.cxx.LinkerMapMode; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.AbstractBuildRule; import com.facebook.buck.rules.AddToRuleKey; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildableContext; import com.facebook.buck.rules.ExplicitBuildTargetSourcePath; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.Tool; import com.facebook.buck.rules.args.Arg; import com.facebook.buck.rules.args.FileListableLinkerInputArg; import com.facebook.buck.rules.args.SourcePathArg; import com.facebook.buck.rules.args.StringArg; import com.facebook.buck.rules.coercer.FrameworkPath; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.MkdirStep; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.MoreIterables; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import java.nio.file.Path; import java.util.LinkedHashSet; import java.util.Optional; /** A build rule which compiles one or more Swift sources into a Swift module. */ class SwiftCompile extends AbstractBuildRule { private static final String INCLUDE_FLAG = "-I"; @AddToRuleKey private final Tool swiftCompiler; @AddToRuleKey private final String moduleName; @AddToRuleKey(stringify = true) private final Path outputPath; private final Path modulePath; private final Path objectPath; @AddToRuleKey private final ImmutableSortedSet<SourcePath> srcs; @AddToRuleKey private final ImmutableList<String> compilerFlags; private final Path headerPath; private final CxxPlatform cxxPlatform; private final ImmutableSet<FrameworkPath> frameworks; private final boolean enableObjcInterop; private final Optional<SourcePath> bridgingHeader; private final SwiftBuckConfig swiftBuckConfig; private final Iterable<CxxPreprocessorInput> cxxPreprocessorInputs; SwiftCompile( CxxPlatform cxxPlatform, SwiftBuckConfig swiftBuckConfig, BuildRuleParams params, Tool swiftCompiler, ImmutableSet<FrameworkPath> frameworks, String moduleName, Path outputPath, Iterable<SourcePath> srcs, ImmutableList<String> compilerFlags, Optional<Boolean> enableObjcInterop, Optional<SourcePath> bridgingHeader) throws NoSuchBuildTargetException { super(params); this.cxxPlatform = cxxPlatform; this.frameworks = frameworks; this.swiftBuckConfig = swiftBuckConfig; this.cxxPreprocessorInputs = CxxPreprocessables.getTransitiveCxxPreprocessorInput(cxxPlatform, params.getBuildDeps()); this.swiftCompiler = swiftCompiler; this.outputPath = outputPath; this.headerPath = outputPath.resolve(SwiftDescriptions.toSwiftHeaderName(moduleName) + ".h"); String escapedModuleName = CxxDescriptionEnhancer.normalizeModuleName(moduleName); this.moduleName = escapedModuleName; this.modulePath = outputPath.resolve(escapedModuleName + ".swiftmodule"); this.objectPath = outputPath.resolve(escapedModuleName + ".o"); this.srcs = ImmutableSortedSet.copyOf(srcs); this.compilerFlags = compilerFlags; this.enableObjcInterop = enableObjcInterop.orElse(true); this.bridgingHeader = bridgingHeader; performChecks(params); } private void performChecks(BuildRuleParams params) { Preconditions.checkArgument( !LinkerMapMode.FLAVOR_DOMAIN.containsAnyOf(params.getBuildTarget().getFlavors()), "SwiftCompile %s should not be created with LinkerMapMode flavor (%s)", this, LinkerMapMode.FLAVOR_DOMAIN); Preconditions.checkArgument( !params.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SHARED_FLAVOR)); } private SwiftCompileStep makeCompileStep(SourcePathResolver resolver) { ImmutableList.Builder<String> compilerCommand = ImmutableList.builder(); compilerCommand.addAll(swiftCompiler.getCommandPrefix(resolver)); if (bridgingHeader.isPresent()) { compilerCommand.add( "-import-objc-header", resolver.getRelativePath(bridgingHeader.get()).toString()); } final Function<FrameworkPath, Path> frameworkPathToSearchPath = CxxDescriptionEnhancer.frameworkPathToSearchPath(cxxPlatform, resolver); compilerCommand.addAll( frameworks .stream() .map(frameworkPathToSearchPath::apply) .flatMap(searchPath -> ImmutableSet.of("-F", searchPath.toString()).stream()) .iterator()); compilerCommand.addAll( MoreIterables.zipAndConcat(Iterables.cycle("-Xcc"), getSwiftIncludeArgs(resolver))); compilerCommand.addAll( MoreIterables.zipAndConcat( Iterables.cycle(INCLUDE_FLAG), getBuildDeps() .stream() .filter(SwiftCompile.class::isInstance) .map(BuildRule::getSourcePathToOutput) .map(input -> resolver.getAbsolutePath(input).toString()) .collect(MoreCollectors.toImmutableSet()))); Optional<Iterable<String>> configFlags = swiftBuckConfig.getFlags(); if (configFlags.isPresent()) { compilerCommand.addAll(configFlags.get()); } boolean hasMainEntry = srcs.stream() .map(input -> resolver.getAbsolutePath(input).getFileName().toString()) .anyMatch(SwiftDescriptions.SWIFT_MAIN_FILENAME::equalsIgnoreCase); compilerCommand.add( "-c", enableObjcInterop ? "-enable-objc-interop" : "", hasMainEntry ? "" : "-parse-as-library", "-module-name", moduleName, "-emit-module", "-emit-module-path", modulePath.toString(), "-o", objectPath.toString(), "-emit-objc-header-path", headerPath.toString()); compilerCommand.addAll(compilerFlags); for (SourcePath sourcePath : srcs) { compilerCommand.add(resolver.getRelativePath(sourcePath).toString()); } ProjectFilesystem projectFilesystem = getProjectFilesystem(); return new SwiftCompileStep( projectFilesystem.getRootPath(), ImmutableMap.of(), compilerCommand.build()); } @Override public ImmutableList<Step> getBuildSteps( BuildContext context, BuildableContext buildableContext) { buildableContext.recordArtifact(outputPath); return ImmutableList.of( MkdirStep.of(getProjectFilesystem(), outputPath), makeCompileStep(context.getSourcePathResolver())); } @Override public SourcePath getSourcePathToOutput() { return new ExplicitBuildTargetSourcePath(getBuildTarget(), outputPath); } /** * @return the arguments to add to the preprocessor command line to include the given header packs * in preprocessor search path. * <p>We can't use CxxHeaders.getArgs() because 1. we don't need the system include roots. 2. * swift doesn't like spaces after the "-I" flag. */ @VisibleForTesting ImmutableList<String> getSwiftIncludeArgs(SourcePathResolver resolver) { ImmutableList.Builder<String> args = ImmutableList.builder(); // Collect the header maps and roots into buckets organized by include type, so that we can: // 1) Apply the header maps first (so that they work properly). // 2) De-duplicate redundant include paths. LinkedHashSet<String> headerMaps = new LinkedHashSet<String>(); LinkedHashSet<String> roots = new LinkedHashSet<String>(); for (CxxPreprocessorInput cxxPreprocessorInput : cxxPreprocessorInputs) { Iterable<CxxHeaders> cxxHeaderses = cxxPreprocessorInput.getIncludes(); for (CxxHeaders cxxHeaders : cxxHeaderses) { // Swift doesn't need to reference anything from system headers if (cxxHeaders.getIncludeType() == CxxPreprocessables.IncludeType.SYSTEM) { continue; } Optional<SourcePath> headerMap = cxxHeaders.getHeaderMap(); if (headerMap.isPresent()) { headerMaps.add(resolver.getAbsolutePath(headerMap.get()).toString()); } roots.add(resolver.getAbsolutePath(cxxHeaders.getIncludeRoot()).toString()); } } if (bridgingHeader.isPresent()) { for (HeaderVisibility headerVisibility : HeaderVisibility.values()) { Path headerPath = CxxDescriptionEnhancer.getHeaderSymlinkTreePath( getProjectFilesystem(), BuildTarget.builder(getBuildTarget().getUnflavoredBuildTarget()).build(), headerVisibility, cxxPlatform.getFlavor()); headerMaps.add(headerPath.toString()); } } // Apply the header maps first, so that headers that matching there avoid falling back to // stat'ing files in the normal include roots. args.addAll(Iterables.transform(headerMaps, INCLUDE_FLAG::concat)); // Apply the regular includes last. args.addAll(Iterables.transform(roots, INCLUDE_FLAG::concat)); return args.build(); } ImmutableList<Arg> getAstLinkArgs() { return ImmutableList.<Arg>builder() .addAll(StringArg.from("-Xlinker", "-add_ast_path")) .add(SourcePathArg.of(new ExplicitBuildTargetSourcePath(getBuildTarget(), modulePath))) .build(); } Arg getFileListLinkArg() { return FileListableLinkerInputArg.withSourcePathArg( SourcePathArg.of(new ExplicitBuildTargetSourcePath(getBuildTarget(), objectPath))); } }