/*
* 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.cxx;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeThat;
import static org.junit.Assume.assumeTrue;
import com.facebook.buck.android.AssumeAndroidPlatform;
import com.facebook.buck.cli.FakeBuckConfig;
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.integration.BuckBuildLog;
import com.facebook.buck.testutil.integration.InferHelper;
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 java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Optional;
import org.apache.commons.compress.archivers.ar.ArArchiveEntry;
import org.apache.commons.compress.archivers.ar.ArArchiveInputStream;
import org.junit.Rule;
import org.junit.Test;
public class CxxLibraryIntegrationTest {
@Rule public TemporaryPaths tmp = new TemporaryPaths();
@Test
public void exportedPreprocessorFlagsApplyToBothTargetAndDependents() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "exported_preprocessor_flags", tmp);
workspace.setUp();
workspace.runBuckBuild("//:main").assertSuccess();
}
@Test
public void appleBinaryBuildsOnApplePlatform() throws IOException {
assumeThat(Platform.detect(), is(Platform.MACOS));
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "apple_cxx_library", tmp);
workspace.setUp();
workspace.runBuckBuild("//:main#iphonesimulator-i386").assertSuccess();
}
@Test
public void appleLibraryBuildsOnApplePlatform() throws IOException {
assumeThat(Platform.detect(), is(Platform.MACOS));
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "apple_cxx_library", tmp);
workspace.setUp();
workspace.runBuckBuild("//:lib#iphonesimulator-i386,static").assertSuccess();
}
@Test
public void libraryCanIncludeAllItsHeadersAndExportedHeadersOfItsDeps() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "private_and_exported_headers", tmp);
workspace.setUp();
ProjectWorkspace.ProcessResult result = workspace.runBuckBuild("//:good-bin");
result.assertSuccess();
}
@Test
public void libraryCannotIncludePrivateHeadersOfDeps() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "private_and_exported_headers", tmp);
workspace.setUp();
ProjectWorkspace.ProcessResult result = workspace.runBuckBuild("//:bad-bin");
result.assertFailure();
}
@Test
public void libraryBuildPathIsSoName() throws InterruptedException, IOException {
assumeTrue(Platform.detect() == Platform.LINUX);
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "shared_library", tmp);
workspace.setUp();
ProjectWorkspace.ProcessResult result = workspace.runBuckBuild("//:binary");
assertTrue(
Files.isRegularFile(
workspace.getPath(
BuildTargets.getGenPath(
new ProjectFilesystem(workspace.getDestPath()),
BuildTargetFactory.newInstance("//subdir:library")
.withFlavors(
DefaultCxxPlatforms.FLAVOR, CxxDescriptionEnhancer.SHARED_FLAVOR),
"%s/libsubdir_library.so"))));
result.assertSuccess();
}
@Test
public void forceStaticLibLinkedIntoSharedContextIsBuiltWithPic() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "force_static_pic", tmp);
workspace.setUp();
workspace.runBuckBuild("//:foo#shared,default").assertSuccess();
}
@Test
public void preferredLinkageOverridesParentLinkStyle() throws Exception {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "preferred_linkage", tmp);
workspace.setUp();
BuckBuildLog buildLog;
workspace.runBuckBuild("//:foo-prefer-shared#default").assertSuccess();
buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally("//:always_static#default,static-pic");
buildLog.assertTargetBuiltLocally("//:always_shared#default,shared");
buildLog.assertTargetBuiltLocally("//:agnostic#default,shared");
buildLog.assertTargetBuiltLocally("//:foo-prefer-shared#default");
workspace.runBuckBuild("//:foo-prefer-static#default").assertSuccess();
buildLog = workspace.getBuildLog();
buildLog.assertTargetBuiltLocally("//:always_static#default,static");
buildLog.assertTargetHadMatchingRuleKey("//:always_shared#default,shared");
buildLog.assertTargetBuiltLocally("//:agnostic#default,static");
buildLog.assertTargetBuiltLocally("//:foo-prefer-static#default");
}
@Test
public void runInferOnSimpleLibraryWithoutDeps() throws IOException {
assumeTrue(Platform.detect() != Platform.WINDOWS);
ProjectWorkspace workspace = InferHelper.setupCxxInferWorkspace(this, tmp, Optional.empty());
workspace.runBuckBuild("//foo:dep_one#infer").assertSuccess();
}
@Test
public void runInferCaptureOnLibraryWithHeadersOnly() throws IOException {
assumeTrue(Platform.detect() != Platform.WINDOWS);
ProjectWorkspace workspace = InferHelper.setupCxxInferWorkspace(this, tmp, Optional.empty());
workspace.runBuckBuild("//foo:headers_only_lib#infer-capture-all").assertSuccess();
}
@Test
public void thinArchivesDoNotContainAbsolutePaths() throws IOException {
CxxPlatform cxxPlatform =
CxxPlatformUtils.build(new CxxBuckConfig(FakeBuckConfig.builder().build()));
assumeTrue(cxxPlatform.getAr().supportsThinArchives());
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "cxx_library", tmp);
workspace.setUp();
Path archive =
workspace.buildAndReturnOutput("-c", "cxx.archive_contents=thin", "//:foo#default,static");
// NOTE: Replace the thin header with a normal header just so the commons compress parser
// can parse the archive contents.
try (OutputStream outputStream =
Files.newOutputStream(workspace.getPath(archive), StandardOpenOption.WRITE)) {
outputStream.write(ObjectFileScrubbers.GLOBAL_HEADER);
}
// Now iterate the archive and verify it contains no absolute paths.
try (ArArchiveInputStream stream =
new ArArchiveInputStream(new FileInputStream(workspace.getPath(archive).toFile()))) {
ArArchiveEntry entry;
while ((entry = stream.getNextArEntry()) != null) {
if (!entry.getName().isEmpty()) {
assertFalse(
"found absolute path: " + entry.getName(),
workspace.getDestPath().getFileSystem().getPath(entry.getName()).isAbsolute());
}
}
}
}
@Test
public void testCxxLibraryWithDefaultsInFlagBuildsSomething()
throws InterruptedException, IOException {
assumeTrue(Platform.detect() == Platform.MACOS);
AssumeAndroidPlatform.assumeSdkIsAvailable();
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "simple", tmp);
workspace.setUp();
BuildTarget target = BuildTargetFactory.newInstance("//foo:library_with_header");
ProjectWorkspace.ProcessResult result =
workspace.runBuckCommand(
"build",
target.getFullyQualifiedName(),
"--config",
"defaults.cxx_library.type=static-pic",
"--config",
"defaults.cxx_library.platform=android-armv7");
result.assertSuccess();
BuildTarget implicitTarget =
target.withAppendedFlavors(
InternalFlavor.of("static-pic"), InternalFlavor.of("android-armv7"));
workspace.getBuildLog().assertTargetBuiltLocally(implicitTarget.getFullyQualifiedName());
}
@Test
public void prebuiltLibraryWithHeaderMapDoesntChangeIncludeTypeOfOtherHeaderMaps()
throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "header_map_include_type", tmp);
workspace.setUp();
workspace.runBuckBuild("-v=3", "//:test_prebuilt").assertSuccess();
workspace.runBuckBuild("-v=3", "//:test_lib").assertFailure();
workspace.runBuckBuild("-v=3", "//:test_both").assertFailure();
}
@Test
public void explicitHeaderOnlyDependency() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(
this, "explicit_header_only_dependency", tmp);
workspace.setUp();
workspace.runBuckBuild("//:binary").assertSuccess();
ProjectWorkspace.ProcessResult shouldFail =
workspace.runBuckBuild("//:binary-lacking-symbols").assertFailure();
assertThat(
"Should not link in archive of direct header-only dependency.",
shouldFail.getStderr(),
containsString("lib1_function"));
assertThat(
"Dependencies of header-only dependencies should also be header only.",
shouldFail.getStderr(),
containsString("lib1_dep_function"));
}
@Test
public void sourceChangeInHeaderOnlyDependencyDoesntCauseRebuild() throws IOException {
// gcc doesn't support the `-all_load` flag which we need to use to ensure our symbols don't
// get stripped. Skip the test on linux (a gcc platform) for now.
assumeTrue(Platform.detect() != Platform.LINUX);
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "explicit_header_only_caching", tmp);
workspace.setUp();
workspace.enableDirCache();
workspace.runBuckBuild("//:binary").assertSuccess();
workspace.runBuckCommand("clean");
workspace.copyFile("lib1.c.new", "lib1.c");
workspace.runBuckBuild("//:binary").assertSuccess();
BuckBuildLog log = workspace.getBuildLog();
log.assertTargetWasFetchedFromCache("//:lib3#default,static");
}
@Test
public void sourceFromCxxGenrule() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "sources_from_cxx_genrule", tmp);
workspace.setUp();
workspace.runBuckBuild("//:lib#default,shared").assertSuccess();
}
@Test
public void headerFromCxxGenrule() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "sources_from_cxx_genrule", tmp);
workspace.setUp();
workspace.runBuckBuild("//:lib_header#default,shared").assertSuccess();
}
private void assumeSymLinkTreeWithHeaderMap(Path rootPath)
throws InterruptedException, IOException {
// We can only disable symlink trees if header map is supported.
CxxPreprocessables.HeaderMode headerMode =
CxxPlatformUtils.getHeaderModeForDefaultPlatform(rootPath);
assumeTrue(headerMode == CxxPreprocessables.HeaderMode.SYMLINK_TREE_WITH_HEADER_MAP);
}
@Test
public void buildWithHeadersSymlink() throws InterruptedException, IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "headers_symlinks", tmp);
workspace.setUp();
workspace.runBuckBuild("-v=3", "//:main#default").assertSuccess();
Path rootPath = tmp.getRoot();
assumeSymLinkTreeWithHeaderMap(rootPath);
assertTrue(
Files.exists(
rootPath.resolve(
"buck-out/gen/foobar#header-mode-symlink-tree-with-header-map,headers/foobar/public.h")));
assertTrue(
Files.exists(
rootPath.resolve("buck-out/gen/foobar#default,private-headers/foobar/private.h")));
}
@Test
public void buildWithoutPublicHeadersSymlink() throws InterruptedException, IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "headers_symlinks", tmp);
workspace.setUp();
workspace
.runBuckBuild(
"-c", "cxx.exported_headers_symlinks_enabled=false", "-v=3", "//:main#default")
.assertSuccess();
Path rootPath = tmp.getRoot();
assumeSymLinkTreeWithHeaderMap(rootPath);
assertFalse(
Files.exists(
rootPath.resolve(
"buck-out/gen/foobar#header-mode-symlink-tree-with-header-map,headers/foobar/public.h")));
assertTrue(
Files.exists(
rootPath.resolve("buck-out/gen/foobar#default,private-headers/foobar/private.h")));
}
@Test
public void buildWithoutPrivateHeadersSymlink() throws InterruptedException, IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "headers_symlinks", tmp);
workspace.setUp();
workspace
.runBuckBuild("-c", "cxx.headers_symlinks_enabled=false", "-v=3", "//:main#default")
.assertSuccess();
Path rootPath = tmp.getRoot();
assumeSymLinkTreeWithHeaderMap(rootPath);
assertTrue(
Files.exists(
rootPath.resolve(
"buck-out/gen/foobar#header-mode-symlink-tree-with-header-map,headers/foobar/public.h")));
assertFalse(
Files.exists(
rootPath.resolve("buck-out/gen/foobar#default,private-headers/foobar/private.h")));
}
@Test
public void buildWithoutHeadersSymlink() throws InterruptedException, IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "headers_symlinks", tmp);
workspace.setUp();
workspace
.runBuckBuild(
"-c",
"cxx.headers_symlinks_enabled=false",
"-c",
"cxx.exported_headers_symlinks_enabled=false",
"-v=3",
"//:main#default")
.assertSuccess();
Path rootPath = tmp.getRoot();
assumeSymLinkTreeWithHeaderMap(rootPath);
assertFalse(
Files.exists(
rootPath.resolve(
"buck-out/gen/foobar#header-mode-symlink-tree-with-header-map,headers/foobar/public.h")));
assertFalse(
Files.exists(
rootPath.resolve("buck-out/gen/foobar#default,private-headers/foobar/private.h")));
}
@Test
public void testExplicitReexportOfHeaderDeps() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "reexport_header_deps", tmp);
workspace.setUp();
// auto-reexport is off, but reexporting via exported_deps
workspace.runBuckBuild("//:bin-explicit-reexport").assertSuccess();
// auto-reexport is off, but not reexporting via exported_deps
workspace.runBuckBuild("//:bin-explicit-noexport").assertFailure();
// auto-reexport is on, but not reexporting via exported_deps
workspace.runBuckBuild("//:bin-auto-reexport").assertSuccess();
}
}