Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add embed_xctest_in_test_bundles and add_xctest_import_paths to fix Xcode 12.5 XCTest linkage #18

Merged
merged 1 commit into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/com/facebook/buck/apple/AppleBinaryDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,8 @@ private BuildRule createBundleBuildRule(
cxxBuckConfig.shouldCacheStrip(),
appleConfig.useEntitlementsWhenAdhocCodeSigning(),
Predicates.alwaysTrue(),
Optional.empty());
Optional.empty(),
false);
}

private BuildRule createBinary(
Expand Down
3 changes: 2 additions & 1 deletion src/com/facebook/buck/apple/AppleBundleDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ public AppleBundle createBuildRule(
cxxBuckConfig.shouldCacheStrip(),
appleConfig.useEntitlementsWhenAdhocCodeSigning(),
resourceFilter,
args.getIsAppClip());
args.getIsAppClip(),
false);
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/com/facebook/buck/apple/AppleConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public class AppleConfig implements ConfigView<BuckConfig> {

public static final String LINK_SCRUB_CONCURRENTLY = "link_scrub_concurrently";

private static final String EMBED_XCTEST_IN_TEST_BUNDLES = "embed_xctest_in_test_bundles";

private final BuckConfig delegate;

// Reflection-based factory for ConfigView
Expand Down Expand Up @@ -214,6 +216,10 @@ public ImmutableList<String> getXctestPlatformNames() {
return delegate.getListWithoutComments(APPLE_SECTION, "xctest_platforms");
}

public boolean getEmbedXctestInTestBundles() {
return delegate.getBooleanValue(APPLE_SECTION, EMBED_XCTEST_IN_TEST_BUNDLES, false);
}

public Optional<Path> getXctoolPath() {
Path xctool = getOptionalPath(APPLE_SECTION, "xctool_path").orElse(Paths.get("xctool"));
return new ExecutableFinder().getOptionalExecutable(xctool, delegate.getEnvironment());
Expand Down
80 changes: 79 additions & 1 deletion src/com/facebook/buck/apple/AppleDescriptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import com.facebook.buck.cxx.toolchain.nativelink.NativeLinkableGroup;
import com.facebook.buck.io.file.MorePaths;
import com.facebook.buck.io.filesystem.ProjectFilesystem;
import com.facebook.buck.rules.coercer.FrameworkPath;
import com.facebook.buck.rules.coercer.PatternMatchedCollection;
import com.facebook.buck.rules.coercer.SourceSortedSet;
import com.facebook.buck.rules.macros.StringWithMacros;
Expand Down Expand Up @@ -676,7 +677,8 @@ static AppleBundle createAppleBundle(
boolean cacheStrips,
boolean useEntitlementsWhenAdhocCodeSigning,
Predicate<BuildTarget> filter,
Optional<Boolean> isAppClip) {
Optional<Boolean> isAppClip,
boolean shouldEmbedXctest) {
AppleCxxPlatform appleCxxPlatform =
ApplePlatforms.getAppleCxxPlatformForBuildTarget(
graphBuilder,
Expand Down Expand Up @@ -744,6 +746,14 @@ static AppleBundle createAppleBundle(
frameworksBuilder.add(graphBuilder.requireRule(dep).getSourcePathToOutput());
}
}
if (shouldEmbedXctest) {
addXctestLibrariesToFrameworks(
frameworksBuilder,
appleCxxPlatform.getAppleSdkPaths().getPlatformPath(),
projectFilesystem,
hasSwiftXCTestDependencies(xcodeDescriptions, targetGraph, binaryTarget, graphBuilder.getSourcePathResolver())
);
}
ImmutableSet<SourcePath> frameworks = frameworksBuilder.build();

BuildTarget buildTargetWithoutBundleSpecificFlavors = stripBundleSpecificFlavors(buildTarget);
Expand Down Expand Up @@ -923,6 +933,74 @@ static AppleBundle createAppleBundle(
isAppClip);
}

private static void addXctestLibrariesToFrameworks(
ImmutableSet.Builder<SourcePath> frameworksBuilder,
Path platformPath,
ProjectFilesystem projectFilesystem,
boolean hasSwiftXCTestDependencies) {
Path xctestPlatformPath = Paths.get("Developer/Library/Frameworks/XCTest.framework");
SourcePath xctestFrameworkPath =
PathSourcePath.of(projectFilesystem, platformPath.resolve(xctestPlatformPath));
frameworksBuilder.add(xctestFrameworkPath);

if (!hasSwiftXCTestDependencies) {
return;
}

Path xctestSwiftSupportPlatformPath =
Paths.get("Developer/usr/lib/libXCTestSwiftSupport.dylib");
SourcePath xctestSwiftSupportPath =
PathSourcePath.of(projectFilesystem, platformPath.resolve(xctestSwiftSupportPlatformPath));
frameworksBuilder.add(xctestSwiftSupportPath);
}

private static boolean hasSwiftXCTestDependencies(
XCodeDescriptions xcodeDescriptions,
TargetGraph targetGraph,
BuildTarget binaryTarget,
SourcePathResolverAdapter resolver) {
Optional<TargetNode<AppleTestDescriptionArg>> testLibraryTargetNode =
TargetNodes.castArg(targetGraph.get(binaryTarget), AppleTestDescriptionArg.class);
if (!testLibraryTargetNode.isPresent()) {
return false;
}
if (targetNodeContainsSwiftSourceCode(testLibraryTargetNode.get())) {
return true;
}

return !AppleBuildRules.collectTransitiveBuildRuleTargetsWithTransform(
xcodeDescriptions,
targetGraph,
Optional.empty(),
ImmutableSet.of(AppleLibraryDescription.class),
ImmutableList.of(testLibraryTargetNode.get()),
RecursiveDependenciesMode.LINKING,
input -> input,
buildTarget -> {
Optional<TargetNode<AppleLibraryDescriptionArg>> depTargetNode =
TargetNodes.castArg(
targetGraph.get(buildTarget), AppleLibraryDescriptionArg.class);
if (!depTargetNode.isPresent()) {
return false;
}
AppleLibraryDescriptionArg arg = depTargetNode.get().getConstructorArg();
return dependsOnXCTest(arg, resolver)
&& targetNodeContainsSwiftSourceCode(depTargetNode.get());
})
.isEmpty();
}

private static boolean dependsOnXCTest(
AppleLibraryDescriptionArg arg, SourcePathResolverAdapter resolver) {
for (FrameworkPath path : arg.getFrameworks()) {
if (path.getFileName(sourcePath -> resolver.getAbsolutePath(sourcePath))
.endsWith("XCTest.framework")) {
return true;
}
}
return false;
}

/**
* Returns a build target of the apple binary for the requested target platform.
*
Expand Down
3 changes: 2 additions & 1 deletion src/com/facebook/buck/apple/AppleLibraryDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,8 @@ private <A extends AbstractAppleLibraryDescriptionArg> BuildRule createFramework
cxxBuckConfig.shouldCacheStrip(),
appleConfig.useEntitlementsWhenAdhocCodeSigning(),
Predicates.alwaysTrue(),
Optional.empty());
Optional.empty(),
false);
}

/**
Expand Down
37 changes: 33 additions & 4 deletions src/com/facebook/buck/apple/AppleTestDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
import com.facebook.buck.io.filesystem.ProjectFilesystem;
import com.facebook.buck.rules.macros.AbsoluteOutputMacroExpander;
import com.facebook.buck.rules.macros.LocationMacroExpander;
import com.facebook.buck.rules.macros.StringWithMacros;
import com.facebook.buck.rules.macros.StringWithMacrosConverter;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.fs.MakeCleanDirectoryStep;
Expand Down Expand Up @@ -310,7 +311,8 @@ public BuildRule createBuildRule(
testHostWithTargetApp.flatMap(TestHostInfo::getTestHostAppBinarySourcePath),
testHostWithTargetApp.map(TestHostInfo::getBlacklist).orElse(ImmutableSet.of()),
libraryTarget,
RichStream.from(args.getTestHostApp()).toImmutableSortedSet(Ordering.natural()));
RichStream.from(args.getTestHostApp()).toImmutableSortedSet(Ordering.natural()),
appleCxxPlatform);
if (!createBundle || SwiftLibraryDescription.isSwiftTarget(libraryTarget)) {
return library;
}
Expand Down Expand Up @@ -375,7 +377,8 @@ public BuildRule createBuildRule(
cxxBuckConfig.shouldCacheStrip(),
appleConfig.useEntitlementsWhenAdhocCodeSigning(),
Predicates.alwaysTrue(),
Optional.empty())));
Optional.empty(),
appleConfig.getEmbedXctestInTestBundles())));

Optional<SourcePath> xctool =
getXctool(projectFilesystem, params, targetConfiguration, graphBuilder);
Expand Down Expand Up @@ -705,7 +708,8 @@ private BuildRule createTestLibraryRule(
Optional<SourcePath> testHostAppBinarySourcePath,
ImmutableSet<BuildTarget> blacklist,
BuildTarget libraryTarget,
ImmutableSortedSet<BuildTarget> extraCxxDeps) {
ImmutableSortedSet<BuildTarget> extraCxxDeps,
AppleCxxPlatform appleCxxPlatform) {
BuildTarget existingLibraryTarget =
libraryTarget
.withAppendedFlavors(AppleDebuggableBinary.RULE_FLAVOR, CxxStrip.RULE_FLAVOR)
Expand All @@ -721,7 +725,7 @@ private BuildRule createTestLibraryRule(
libraryTarget,
params,
graphBuilder,
args,
appendTestArgs(args, appleCxxPlatform),
// For now, instead of building all deps as dylibs and fixing up their install_names,
// we'll just link them statically.
Optional.of(Linker.LinkableDepType.STATIC),
Expand All @@ -734,6 +738,31 @@ private BuildRule createTestLibraryRule(
return library;
}

private AppleTestDescriptionArg appendTestArgs(
AppleTestDescriptionArg args, AppleCxxPlatform appleCxxPlatform) {
AppleTestDescriptionArg.Builder builder = AppleTestDescriptionArg.builder().from(args);

if (appleConfig.getEmbedXctestInTestBundles()) {
AppleBundleDestinations destination =
AppleBundleDestinations.platformDestinations(
appleCxxPlatform.getAppleSdk().getApplePlatform());
Path pathToFrameworks =
destination.getExecutablesPath().relativize(destination.getFrameworksPath());
String linkerArg = "-Wl,-rpath,@executable_path/" + pathToFrameworks.toString();
builder.addLinkerFlags(StringWithMacros.ofConstantString(linkerArg));
}

if (swiftBuckConfig.getAddXctestImportPaths()) {
// When importing XCTest in Swift, we will need to add the linker search paths to
// find libXCTestSupport.dylib and XCTest.framework.
builder.addLinkerFlags(
StringWithMacros.ofConstantString("-L$PLATFORM_DIR/Developer/usr/lib"),
StringWithMacros.ofConstantString("-F$PLATFORM_DIR/Developer/Library/Frameworks"));
}

return builder.build();
}

@Override
public void findDepsForTargetFromConstructorArgs(
BuildTarget buildTarget,
Expand Down
9 changes: 9 additions & 0 deletions src/com/facebook/buck/swift/SwiftBuckConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class SwiftBuckConfig implements ConfigView<BuckConfig> {
public static final String COPY_STDLIB_TO_FRAMEWORKS = "copy_stdlib_to_frameworks";
public static final String USE_LIPO_THIN = "use_lipo_thin";
public static final String EMIT_SWIFTDOCS = "emit_swiftdocs";
public static final String ADD_XCTEST_IMPORT_PATHS = "add_xctest_import_paths";
public static final String USE_ARG_FILE = "use_arg_file";
public static final String CODE_COVERAGE_ENABLED = "code_coverage_enabled";
private final BuckConfig delegate;
Expand Down Expand Up @@ -132,6 +133,14 @@ public boolean getEmitSwiftdocs() {
return delegate.getBooleanValue(SECTION_NAME, EMIT_SWIFTDOCS, false);
}

/**
* If true, add -I$PLATFORM_DIR/Developer/usr/lib so that libXCTestSwiftSupport.dylib can be found
* at compile time.
*/
public boolean getAddXctestImportPaths() {
return delegate.getBooleanValue(SECTION_NAME, ADD_XCTEST_IMPORT_PATHS, true);
}

public boolean getCodeCoverageEnabled() {
return delegate.getBooleanValue(SECTION_NAME, CODE_COVERAGE_ENABLED, false);
}
Expand Down
29 changes: 28 additions & 1 deletion src/com/facebook/buck/swift/SwiftCompile.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ public class SwiftCompile extends AbstractBuildRule implements SupportsInputBase
@AddToRuleKey private final PreprocessorFlags cxxDeps;

@AddToRuleKey private final boolean importUnderlyingModule;
@AddToRuleKey private final boolean addXCTestImportPaths;

private BuildableSupport.DepsSupplier depsSupplier;

Expand All @@ -142,7 +143,8 @@ public class SwiftCompile extends AbstractBuildRule implements SupportsInputBase
Optional<SourcePath> bridgingHeader,
Preprocessor preprocessor,
PreprocessorFlags cxxDeps,
boolean importUnderlyingModule) {
boolean importUnderlyingModule,
boolean addXCTestImportPaths) {
super(buildTarget, projectFilesystem);
this.frameworks = frameworks;
this.frameworkPathToSearchPath = frameworkPathToSearchPath;
Expand Down Expand Up @@ -199,6 +201,7 @@ public class SwiftCompile extends AbstractBuildRule implements SupportsInputBase
this.cPreprocessor = preprocessor;
this.cxxDeps = cxxDeps;
this.depsSupplier = BuildableSupport.buildDepsSupplier(this, graphBuilder);
this.addXCTestImportPaths = addXCTestImportPaths;
performChecks(buildTarget);
}

Expand Down Expand Up @@ -233,6 +236,10 @@ private SwiftCompileStep makeCompileStep(SourcePathResolverAdapter resolver) {
.flatMap(searchPath -> ImmutableSet.of("-F", searchPath.toString()).stream())
.iterator());

if (addXCTestImportPaths) {
compilerCommand.addAll(xctestImportArgs(resolver));
}

compilerCommand.addAll(
MoreIterables.zipAndConcat(Iterables.cycle("-Xcc"), getSwiftIncludeArgs(resolver)));
compilerCommand.addAll(
Expand Down Expand Up @@ -289,6 +296,26 @@ private SwiftCompileStep makeCompileStep(SourcePathResolverAdapter resolver) {
projectFilesystem.getRootPath(), ImmutableMap.of(), compilerCommand.build());
}

private Iterable<String> xctestImportArgs(SourcePathResolverAdapter resolver) {
for (FrameworkPath frameworkPath : frameworks) {
if (frameworkPath
.getFileName(sourcePath -> resolver.getAbsolutePath(sourcePath))
.endsWith("XCTest.framework")) {
// XCTest.swiftmodule is in a different path to XCTest.framework
// which we need to import for Swift specific API

Path developerFrameworkPath = frameworkPathToSearchPath.apply(frameworkPath);
if (developerFrameworkPath.getParent() != null
&& developerFrameworkPath.getParent().getParent() != null) {
Path xctestImportPath =
developerFrameworkPath.getParent().getParent().resolve("usr/lib");
return ImmutableList.of("-I", xctestImportPath.toString());
}
}
}
return ImmutableList.of();
}

@VisibleForTesting
static String validVersionString(String originalVersionString) {
// Swiftc officially only accepts the major version, but it respects the minor
Expand Down
6 changes: 4 additions & 2 deletions src/com/facebook/buck/swift/SwiftLibraryDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ public Iterable<BuildRule> visit(BuildRule rule) {
args.getBridgingHeader(),
preprocessor,
cxxDeps,
false);
false,
swiftBuckConfig.getAddXctestImportPaths());
}

// Otherwise, we return the generic placeholder of this library.
Expand Down Expand Up @@ -484,7 +485,8 @@ public static SwiftCompile createSwiftCompileRule(
args.getBridgingHeader(),
preprocessor,
preprocessFlags,
importUnderlyingModule);
importUnderlyingModule,
swiftBuckConfig.getAddXctestImportPaths());
}

public static boolean isSwiftTarget(BuildTarget buildTarget) {
Expand Down