/*
* Copyright 2014-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 static com.facebook.buck.cxx.CxxFlavorSanitizer.sanitize;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNotNull;
import com.facebook.buck.cxx.CxxCompilationDatabaseEntry;
import com.facebook.buck.cxx.CxxCompilationDatabaseUtils;
import com.facebook.buck.cxx.CxxDescriptionEnhancer;
import com.facebook.buck.cxx.CxxPlatformUtils;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.InternalFlavor;
import com.facebook.buck.testutil.FakeProjectFilesystem;
import com.facebook.buck.testutil.integration.ProjectWorkspace;
import com.facebook.buck.testutil.integration.TemporaryPaths;
import com.facebook.buck.testutil.integration.TestDataHelper;
import com.facebook.buck.util.environment.Platform;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class CompilationDatabaseIntegrationTest {
/** This is the value of xcode_developer_dir in the .buckconfig for this test. */
private static final Path XCODE_DEVELOPER_DIR = Paths.get("xcode-developer-dir");
@Rule public TemporaryPaths tmp = new TemporaryPaths();
private ProjectWorkspace workspace;
@Before
public void setupWorkspace() throws IOException {
Platform platform = Platform.detect();
Assume.assumeTrue(platform == Platform.MACOS);
workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "ios-project", tmp);
workspace.setUp();
Path platforms = workspace.getPath("xcode-developer-dir/Platforms");
Path sdk = platforms.resolve("iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk");
Files.createSymbolicLink(sdk.getParent().resolve("iPhoneOS8.0.sdk"), sdk.getFileName());
sdk = platforms.resolve("iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk");
Files.createSymbolicLink(sdk.getParent().resolve("iPhoneSimulator8.0.sdk"), sdk.getFileName());
}
@Test
public void testCreateCompilationDatabaseForAppleLibraryWithNoDeps()
throws InterruptedException, IOException {
// buck build the #compilation-database.
BuildTarget target =
BuildTargetFactory.newInstance(
"//Libraries/EXExample:EXExample#compilation-database,iphonesimulator-x86_64");
Path compilationDatabase = workspace.buildAndReturnOutput(target.getFullyQualifiedName());
ProjectFilesystem filesystem = new FakeProjectFilesystem();
// Parse the compilation_database.json file.
Map<String, CxxCompilationDatabaseEntry> fileToEntry =
CxxCompilationDatabaseUtils.parseCompilationDatabaseJsonFile(compilationDatabase);
ImmutableSet<String> frameworks =
ImmutableSet.of(
Paths.get("/System/Library/Frameworks/Foundation.framework").getParent().toString());
String pathToPrivateHeaders =
BuildTargets.getGenPath(
filesystem,
target.withFlavors(
InternalFlavor.of("iphonesimulator-x86_64"),
CxxDescriptionEnhancer.HEADER_SYMLINK_TREE_FLAVOR),
"%s.hmap")
.toString();
String pathToPublicHeaders =
BuildTargets.getGenPath(
filesystem,
target.withFlavors(
CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR,
CxxPlatformUtils.getHeaderModeForDefaultPlatform(tmp.getRoot()).getFlavor()),
"%s.hmap")
.toString();
Iterable<String> includes =
ImmutableList.of(
pathToPrivateHeaders,
pathToPublicHeaders,
filesystem.getBuckPaths().getBuckOut().toString());
// Verify the entries in the compilation database.
assertFlags(
filesystem,
"Libraries/EXExample/EXExample/EXExampleModel.m",
target.withFlavors(
InternalFlavor.of("iphonesimulator-x86_64"),
InternalFlavor.of("compile-pic-" + sanitize("EXExample/EXExampleModel.m.o"))),
Paths.get("EXExample/EXExampleModel.m.o"),
/* isLibrary */ true,
fileToEntry,
frameworks,
includes);
assertFlags(
filesystem,
"Libraries/EXExample/EXExample/EXUser.mm",
target.withFlavors(
InternalFlavor.of("iphonesimulator-x86_64"),
InternalFlavor.of("compile-pic-" + sanitize("EXExample/EXUser.mm.o"))),
Paths.get("EXExample/EXUser.mm.o"),
/* isLibrary */ true,
fileToEntry,
frameworks,
includes);
assertFlags(
filesystem,
"Libraries/EXExample/EXExample/Categories/NSString+Palindrome.m",
target.withFlavors(
InternalFlavor.of("iphonesimulator-x86_64"),
InternalFlavor.of(
"compile-pic-" + sanitize("EXExample/Categories/NSString+Palindrome.m.o"))),
Paths.get("EXExample/Categories/NSString+Palindrome.m.o"),
/* isLibrary */ true,
fileToEntry,
frameworks,
includes);
}
@Test
public void testCreateCompilationDatabaseForAppleBinaryWithDeps()
throws InterruptedException, IOException {
// buck build the #compilation-database.
BuildTarget target =
BuildTargetFactory.newInstance(
"//Apps/Weather:Weather#iphonesimulator-x86_64,compilation-database");
Path compilationDatabase = workspace.buildAndReturnOutput(target.getFullyQualifiedName());
ProjectFilesystem filesystem = new FakeProjectFilesystem();
// Parse the compilation_database.json file.
Map<String, CxxCompilationDatabaseEntry> fileToEntry =
CxxCompilationDatabaseUtils.parseCompilationDatabaseJsonFile(compilationDatabase);
ImmutableSet<String> frameworks =
ImmutableSet.of(
Paths.get("/System/Library/Frameworks/Foundation.framework").getParent().toString(),
Paths.get("/System/Library/Frameworks/UIKit.framework").getParent().toString());
String pathToPrivateHeaders =
BuildTargets.getGenPath(
filesystem,
target.withFlavors(
InternalFlavor.of("iphonesimulator-x86_64"),
CxxDescriptionEnhancer.HEADER_SYMLINK_TREE_FLAVOR),
"%s.hmap")
.toString();
String pathToPublicHeaders =
BuildTargets.getGenPath(
filesystem,
BuildTargetFactory.newInstance("//Libraries/EXExample:EXExample")
.withAppendedFlavors(
CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR,
CxxPlatformUtils.getHeaderModeForDefaultPlatform(tmp.getRoot())
.getFlavor()),
"%s.hmap")
.toString();
Iterable<String> includes =
ImmutableList.of(
pathToPrivateHeaders,
pathToPublicHeaders,
filesystem.getBuckPaths().getBuckOut().toString());
assertFlags(
filesystem,
"Apps/Weather/Weather/EXViewController.m",
target.withFlavors(
InternalFlavor.of("iphonesimulator-x86_64"),
InternalFlavor.of("compile-" + sanitize("Weather/EXViewController.m.o"))),
Paths.get("Weather/EXViewController.m.o"),
/* isLibrary */ false,
fileToEntry,
frameworks,
includes);
assertFlags(
filesystem,
"Apps/Weather/Weather/main.m",
target.withFlavors(
InternalFlavor.of("iphonesimulator-x86_64"),
InternalFlavor.of("compile-" + sanitize("Weather/main.m.o"))),
Paths.get("Weather/main.m.o"),
/* isLibrary */ false,
fileToEntry,
frameworks,
includes);
}
private void assertFlags(
ProjectFilesystem filesystem,
String source,
BuildTarget outputTarget,
Path outputPath,
boolean isLibrary,
Map<String, CxxCompilationDatabaseEntry> fileToEntry,
ImmutableSet<String> additionalFrameworks,
Iterable<String> includes)
throws IOException {
Path tmpRoot = tmp.getRoot().toRealPath();
String key = tmpRoot.resolve(source).toString();
CxxCompilationDatabaseEntry entry = fileToEntry.get(key);
assertNotNull("There should be an entry for " + key + ".", entry);
Path xcodeDeveloperDir = tmpRoot.resolve(XCODE_DEVELOPER_DIR);
Path platformDir = xcodeDeveloperDir.resolve("Platforms/iPhoneSimulator.platform");
Path sdkRoot = platformDir.resolve("Developer/SDKs/iPhoneSimulator.sdk");
String clang =
xcodeDeveloperDir.resolve("Toolchains/XcodeDefault.xctoolchain/usr/bin/clang").toString();
String language = "objective-c";
String languageStandard = "-std=gnu11";
if (source.endsWith(".mm")) {
language = "objective-c++";
languageStandard = "-std=c++11";
clang += "++";
}
ImmutableList<String> commandArgs1 =
ImmutableList.of("'" + languageStandard + "'", "-Wno-deprecated", "-Wno-conversion");
ImmutableList<String> commandArgs2 =
ImmutableList.of(
"-isysroot",
sdkRoot.toString(),
"-iquote",
tmpRoot.toString(),
"-arch",
"x86_64",
"'-mios-simulator-version-min=8.0'");
List<String> commandArgs = new ArrayList<>();
commandArgs.add(clang);
if (isLibrary) {
commandArgs.add("-fPIC");
commandArgs.add("-fPIC");
}
// TODO(coneko, jakubzika): It seems like a bug that this set of flags gets inserted twice.
// Perhaps this has something to do with how the [cxx] section in .buckconfig is processed.
// (Err, it's probably adding both the preprocessor and regular rule command suffixes. Should
// be harmless.)
commandArgs.addAll(commandArgs2);
commandArgs.addAll(commandArgs1);
commandArgs.addAll(commandArgs2);
for (String include : includes) {
commandArgs.add("-I");
commandArgs.add(include);
}
for (String framework : additionalFrameworks) {
commandArgs.add("-F");
commandArgs.add(sdkRoot + framework);
}
String output =
BuildTargets.getGenPath(filesystem, outputTarget, "%s").resolve(outputPath).toString();
commandArgs.add("-Xclang");
commandArgs.add("-fdebug-compilation-dir");
commandArgs.add("-Xclang");
commandArgs.add("." + Strings.repeat("/", 399));
commandArgs.add("-x");
commandArgs.add(language);
commandArgs.add("'-fdebug-prefix-map=" + tmpRoot + "=.'");
commandArgs.add("'-fdebug-prefix-map=" + xcodeDeveloperDir + "=APPLE_DEVELOPER_DIR'");
commandArgs.add("'-fdebug-prefix-map=" + platformDir + "=APPLE_PLATFORM_DIR'");
commandArgs.add("'-fdebug-prefix-map=" + sdkRoot + "=APPLE_SDKROOT'");
commandArgs.add("-c");
commandArgs.add("-MD");
commandArgs.add("-MF");
commandArgs.add(output + ".dep");
commandArgs.add(source);
commandArgs.add("-o");
commandArgs.add(output);
assertThat(ImmutableList.copyOf(entry.getCommand().split(" ")), equalTo(commandArgs));
}
}