@Test public void testUberCompilationDatabase() throws IOException { ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "compilation_database", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:test#default,uber-compilation-database"); ProjectFilesystem filesystem = new FakeProjectFilesystem(); Path compilationDatabase = workspace.buildAndReturnOutput(target.getFullyQualifiedName()); Path rootPath = tmp.getRoot(); assertEquals( BuildTargets.getGenPath( filesystem, target, "uber-compilation-database-%s/compile_commands.json"), rootPath.relativize(compilationDatabase)); Path binaryHeaderSymlinkTreeFolder = BuildTargets.getGenPath( filesystem, target.withFlavors( ImmutableFlavor.of("default"), CxxDescriptionEnhancer.HEADER_SYMLINK_TREE_FLAVOR), "%s"); Path binaryExportedHeaderSymlinkTreeFolder = BuildTargets.getGenPath( filesystem, target.withFlavors( ImmutableFlavor.of("default"), CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR), "%s"); Map<String, CxxCompilationDatabaseEntry> fileToEntry = CxxCompilationDatabaseUtils.parseCompilationDatabaseJsonFile(compilationDatabase); assertEquals(1, fileToEntry.size()); assertHasEntry( fileToEntry, "test.cpp", new ImmutableList.Builder<String>() .add(COMPILER_PATH) .add("-fPIC") .add("-fPIC") .add("-I") .add(headerSymlinkTreePath(binaryHeaderSymlinkTreeFolder).toString()) .add("-I") .add(headerSymlinkTreePath(binaryExportedHeaderSymlinkTreeFolder).toString()) .addAll(getExtraFlagsForHeaderMaps(filesystem)) .addAll(COMPILER_SPECIFIC_FLAGS) .add("-x") .add("c++") .add("-c") .add("-o") .add( BuildTargets.getGenPath( filesystem, target.withFlavors( ImmutableFlavor.of("default"), ImmutableFlavor.of("compile-pic-" + sanitize("test.cpp.o"))), "%s/test.cpp.o") .toString()) .add(rootPath.resolve(Paths.get("test.cpp")).toRealPath().toString()) .build()); }
@Test public void compilationDatabaseFetchedFromCacheAlsoFetchesSymlinkTreeOrHeaderMap() throws IOException { ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "compilation_database", tmp); workspace.setUp(); ProjectFilesystem filesystem = new FakeProjectFilesystem(); // This test only fails if the directory cache is enabled and we don't update // the header map/symlink tree correctly when fetching from the cache. workspace.enableDirCache(); addLibraryHeaderFiles(workspace); BuildTarget target = BuildTargetFactory.newInstance("//:library_with_header#default,compilation-database"); // Populate the cache with the built rule workspace.buildAndReturnOutput(target.getFullyQualifiedName()); Path headerSymlinkTreeFolder = BuildTargets.getGenPath( filesystem, target.withFlavors( ImmutableFlavor.of("default"), CxxDescriptionEnhancer.HEADER_SYMLINK_TREE_FLAVOR), "%s"); Path exportedHeaderSymlinkTreeFolder = BuildTargets.getGenPath( filesystem, target.withFlavors( ImmutableFlavor.of("default"), CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR), "%s"); // Validate the symlink tree/header maps verifyHeaders(workspace, headerSymlinkTreeFolder, "bar.h", "baz.h", "blech_private.h"); verifyHeaders(workspace, exportedHeaderSymlinkTreeFolder, "bar.h", "baz.h"); // Delete the newly-added files and build again Files.delete(workspace.getPath("baz.h")); Files.delete(workspace.getPath("blech_private.h")); workspace.buildAndReturnOutput(target.getFullyQualifiedName()); verifyHeaders(workspace, headerSymlinkTreeFolder, "bar.h"); verifyHeaders(workspace, exportedHeaderSymlinkTreeFolder, "bar.h"); // Restore the headers, build again, and check the symlink tree/header maps addLibraryHeaderFiles(workspace); workspace.buildAndReturnOutput(target.getFullyQualifiedName()); verifyHeaders(workspace, headerSymlinkTreeFolder, "bar.h", "baz.h", "blech_private.h"); verifyHeaders(workspace, exportedHeaderSymlinkTreeFolder, "bar.h", "baz.h"); }
public BuildTarget createInferCaptureBuildTarget(String name) { String outputName = Flavor.replaceInvalidCharacters(getCompileOutputName(name)); return BuildTarget.builder(getParams().getBuildTarget()) .addAllFlavors(getParams().getBuildTarget().getFlavors()) .addFlavors(getCxxPlatform().getFlavor()) .addFlavors(ImmutableFlavor.of(String.format("infer-capture-%s", outputName))) .build(); }
@VisibleForTesting protected static BuildTarget createYaccBuildTarget(UnflavoredBuildTarget target, String name) { return BuildTarget.builder(target) .addFlavors( ImmutableFlavor.of( String.format( "yacc-%s", name.replace('/', '-').replace('.', '-').replace('+', '-').replace(' ', '-')))) .build(); }
@Test public void testAppleLibraryWithDefaultsInRuleBuildsSomething() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "apple_library_with_platform_and_type", tmp); workspace.setUp(); ProjectFilesystem filesystem = new ProjectFilesystem(workspace.getDestPath()); BuildTarget target = BuildTargetFactory.newInstance("//Libraries/TestLibrary:TestLibrary"); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", target.getFullyQualifiedName()); result.assertSuccess(); BuildTarget implicitTarget = target.withAppendedFlavors( ImmutableFlavor.of("shared"), ImmutableFlavor.of("iphoneos-arm64")); Path outputPath = workspace.getPath(BuildTargets.getGenPath(filesystem, implicitTarget, "%s")); assertTrue(Files.exists(outputPath)); }
/** * @return a {@link BuildTarget} used for the rule that preprocesses the source by the given name * and type. */ @VisibleForTesting public BuildTarget createPreprocessBuildTarget(String name, CxxSource.Type type) { String outputName = Flavor.replaceInvalidCharacters(getPreprocessOutputName(type, name)); return BuildTarget.builder(getParams().getBuildTarget()) .addFlavors(getCxxPlatform().getFlavor()) .addFlavors( ImmutableFlavor.of( String.format( PREPROCESS_FLAVOR_PREFIX + "%s%s", getPicType() == PicType.PIC ? "pic-" : "", outputName))) .build(); }
/** * @return a build target for a {@link CxxPreprocessAndCompile} rule for the source with the given * name. */ @VisibleForTesting public BuildTarget createCompileBuildTarget(String name) { String outputName = Flavor.replaceInvalidCharacters(getCompileOutputName(name)); return BuildTarget.builder(getParams().getBuildTarget()) .addFlavors(getCxxPlatform().getFlavor()) .addFlavors( ImmutableFlavor.of( String.format( COMPILE_FLAVOR_PREFIX + "%s%s", getPicType() == PicType.PIC ? "pic-" : "", outputName))) .build(); }
public static BuildTarget createMLBytecodeCompileBuildTarget(BuildTarget target, String name) { return BuildTarget.builder(target) .addFlavors( ImmutableFlavor.of( String.format( "ml-bytecode-compile-%s", getMLBytecodeOutputName(name) .replace('/', '-') .replace('.', '-') .replace('+', '-') .replace(' ', '-')))) .build(); }
@Test public void testAppleLibraryIsHermetic() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "apple_library_is_hermetic", tmp); workspace.setUp(); ProjectFilesystem filesystem = new ProjectFilesystem(workspace.getDestPath()); BuildTarget target = BuildTargetFactory.newInstance( "//Libraries/TestLibrary:TestLibrary#static,iphonesimulator-x86_64"); ProjectWorkspace.ProcessResult first = workspace.runBuckCommand( workspace.getPath("first"), "build", target.getFullyQualifiedName()); first.assertSuccess(); ProjectWorkspace.ProcessResult second = workspace.runBuckCommand( workspace.getPath("second"), "build", target.getFullyQualifiedName()); second.assertSuccess(); Path objectPath = BuildTargets.getGenPath( filesystem, target.withFlavors( ImmutableFlavor.of("compile-" + sanitize("TestClass.m.o")), ImmutableFlavor.of("iphonesimulator-x86_64")), "%s") .resolve("TestClass.m.o"); MoreAsserts.assertContentsEqual( workspace.getPath(Paths.get("first").resolve(objectPath)), workspace.getPath(Paths.get("second").resolve(objectPath))); Path libraryPath = BuildTargets.getGenPath(filesystem, target, "%s").resolve("libTestLibrary.a"); MoreAsserts.assertContentsEqual( workspace.getPath(Paths.get("first").resolve(libraryPath)), workspace.getPath(Paths.get("second").resolve(libraryPath))); }
@Test public void testAppleLibraryWithDefaultsInConfigBuildsSomething() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "apple_library_builds_something", tmp); workspace.setUp(); ProjectFilesystem filesystem = new ProjectFilesystem(workspace.getDestPath()); workspace.writeContentsToPath( "[defaults.apple_library]\n platform = iphonesimulator-x86_64\n type = shared\n", ".buckconfig.local"); BuildTarget target = BuildTargetFactory.newInstance("//Libraries/TestLibrary:TestLibrary"); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", target.getFullyQualifiedName()); result.assertSuccess(); BuildTarget implicitTarget = target.withAppendedFlavors( ImmutableFlavor.of("shared"), ImmutableFlavor.of("iphonesimulator-x86_64")); assertTrue( Files.exists(workspace.getPath(BuildTargets.getGenPath(filesystem, implicitTarget, "%s")))); }
/** * Constructs set of Python platform flavors given in a .buckconfig file, as is specified by * section names of the form python#{flavor name}. */ public ImmutableList<PythonPlatform> getPythonPlatforms(ProcessExecutor processExecutor) throws InterruptedException { ImmutableList.Builder<PythonPlatform> builder = ImmutableList.builder(); // Add the python platform described in the top-level section first. builder.add(getDefaultPythonPlatform(processExecutor)); // Then add all additional python platform described in the extended sections. for (String section : delegate.getSections()) { if (section.startsWith(PYTHON_PLATFORM_SECTION_PREFIX)) { builder.add( getPythonPlatform( processExecutor, ImmutableFlavor.of(section.substring(PYTHON_PLATFORM_SECTION_PREFIX.length())), delegate.getValue(section, "interpreter"), delegate.getBuildTarget(section, "library"))); } } return builder.build(); }
@Test public void testDexExopackageHasNoSecondary() throws IOException { ZipInspector zipInspector = new ZipInspector( workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTargetFactory.newInstance(DEX_EXOPACKAGE_TARGET), "%s.apk"))); zipInspector.assertFileDoesNotExist("assets/secondary-program-dex-jars/metadata.txt"); zipInspector.assertFileDoesNotExist("assets/secondary-program-dex-jars/secondary-1.dex.jar"); zipInspector.assertFileDoesNotExist("classes2.dex"); zipInspector.assertFileExists("classes.dex"); zipInspector.assertFileExists("lib/armeabi/libfakenative.so"); // It would be better if we could call getExopackageInfo on the app rule. Path secondaryDir = workspace.resolve( BuildTargets.getScratchPath( filesystem, BuildTargetFactory.newInstance(DEX_EXOPACKAGE_TARGET) .withFlavors(ImmutableFlavor.of("dex_merge")), "_%s_output/jarfiles/assets/secondary-program-dex-jars")); try (DirectoryStream<Path> stream = Files.newDirectoryStream(secondaryDir)) { List<Path> files = Lists.newArrayList(stream); assertEquals(2, files.size()); Collections.sort(files); Path secondaryJar = files.get(0); ZipInspector zi = new ZipInspector(secondaryJar); zi.assertFileExists("classes.dex"); long jarSize = Files.size(secondaryJar); long classesDexSize = zi.getSize("classes.dex"); Path dexMeta = files.get(1); assertEquals( String.format("jar:%s dex:%s", jarSize, classesDexSize), new String(Files.readAllBytes(dexMeta), "US-ASCII")); } }
@Test public void findDepsFromParamsSetsUpDepsForFlavoredTarget() { // Create the thrift target and implicit dep. BuildTarget thriftTarget = BuildTargetFactory.newInstance("//bar:thrift_compiler"); FakeBuildRule implicitDep = createFakeBuildRule("//foo:implicit_dep", new SourcePathResolver(new BuildRuleResolver())); // Setup the default values returned by the language specific enhancer. String language = "fake"; Flavor flavor = ImmutableFlavor.of("fake"); ImmutableSet<String> options = ImmutableSet.of(); ImmutableSet<BuildTarget> implicitDeps = ImmutableSet.of(implicitDep.getBuildTarget()); BuildTarget unflavoredTarget = BuildTargetFactory.newInstance("//:thrift"); BuildTarget flavoredTarget = BuildTargets.createFlavoredBuildTarget(unflavoredTarget.checkUnflavored(), flavor); // Setup an empty thrift buck config and description. FakeBuckConfig buckConfig = new FakeBuckConfig( ImmutableMap.of("thrift", ImmutableMap.of("compiler", thriftTarget.toString()))); ThriftBuckConfig thriftBuckConfig = new ThriftBuckConfig(buckConfig); ThriftLanguageSpecificEnhancer enhancer = new FakeThriftLanguageSpecificEnhancer(language, flavor, implicitDeps, options); ThriftLibraryDescription desc = new ThriftLibraryDescription(thriftBuckConfig, ImmutableList.of(enhancer)); ThriftConstructorArg constructorArg = desc.createUnpopulatedConstructorArg(); constructorArg.deps = Optional.of(ImmutableSortedSet.<BuildTarget>of()); // Now call the find deps methods and verify it returns nothing. Iterable<BuildTarget> results = desc.findDepsForTargetFromConstructorArgs(flavoredTarget, constructorArg); assertEquals( ImmutableSet.<BuildTarget>builder() .add(unflavoredTarget) .add(thriftTarget) .addAll(implicitDeps) .build(), ImmutableSet.copyOf(results)); }
public static CxxPlatform getConfigDefaultCxxPlatform( CxxBuckConfig cxxBuckConfig, ImmutableMap<Flavor, CxxPlatform> cxxPlatformsMap, CxxPlatform systemDefaultCxxPlatform) { CxxPlatform defaultCxxPlatform; Optional<String> defaultPlatform = cxxBuckConfig.getDefaultPlatform(); if (defaultPlatform.isPresent()) { defaultCxxPlatform = cxxPlatformsMap.get(ImmutableFlavor.of(defaultPlatform.get())); if (defaultCxxPlatform == null) { LOG.warn( "Couldn't find default platform %s, falling back to system default", defaultPlatform.get()); } else { LOG.debug("Using config default C++ platform %s", defaultCxxPlatform); return defaultCxxPlatform; } } else { LOG.debug("Using system default C++ platform %s", systemDefaultCxxPlatform); } return systemDefaultCxxPlatform; }
@Override public <A extends Arg> BuildRule createBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, A args) { BuildTarget libraryTarget = BuildTarget.builder(params.getBuildTarget()) .addFlavors(ImmutableFlavor.of("compile")) .build(); GoLibrary library = GoDescriptors.createGoLibraryRule( params.copyWithBuildTarget(libraryTarget), resolver, goBuckConfig, Paths.get("main"), args.srcs, args.compilerFlags.or(ImmutableList.<String>of())); resolver.addToIndex(library); BuildRuleParams binaryParams = params.copyWithDeps( Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of(library)), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())); GoSymlinkTree symlinkTree = GoDescriptors.requireTransitiveSymlinkTreeRule(binaryParams, resolver); return new GoBinary( params.copyWithDeps( Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of(symlinkTree, library)), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())), new SourcePathResolver(resolver), cxxPlatform.getLd(), symlinkTree, library, goBuckConfig.getGoLinker().get(), ImmutableList.<String>builder() .addAll(goBuckConfig.getLinkerFlags()) .addAll(args.linkerFlags.or(ImmutableList.<String>of())) .build()); }
@Test public void testCompilationDatabseWithSeperatedPreprocessAndCompileStrategy() { String root = "/Users/user/src"; final Path fakeRoot = Paths.get(root); ProjectFilesystem filesystem = new FakeProjectFilesystem() { @Override public Path getRootPath() { return fakeRoot; } @Override public Path resolve(Path relativePath) { return fakeRoot.resolve(relativePath); } }; BuildTarget testBuildTarget = BuildTarget.builder(BuildTargetFactory.newInstance("//foo:baz")) .addAllFlavors(ImmutableSet.of(CxxCompilationDatabase.COMPILATION_DATABASE)) .build(); BuildRuleParams testBuildRuleParams = new FakeBuildRuleParamsBuilder(testBuildTarget).setProjectFilesystem(filesystem).build(); BuildRuleResolver testBuildRuleResolver = new BuildRuleResolver(); SourcePathResolver testSourcePathResolver = new SourcePathResolver(testBuildRuleResolver); BuildTarget preprocessTarget = BuildTarget.builder(testBuildRuleParams.getBuildTarget().getUnflavoredBuildTarget()) .addFlavors(ImmutableFlavor.of("preprocess-test.cpp")) .build(); BuildRuleParams preprocessBuildRuleParams = new FakeBuildRuleParamsBuilder(preprocessTarget).setProjectFilesystem(filesystem).build(); CxxPreprocessAndCompile testPreprocessRule = new CxxPreprocessAndCompile( preprocessBuildRuleParams, testSourcePathResolver, CxxPreprocessAndCompileStep.Operation.PREPROCESS, Optional.<Preprocessor>of( new DefaultPreprocessor(new HashedFileTool(Paths.get("compiler")))), Optional.of(ImmutableList.<String>of()), Optional.of(ImmutableList.<String>of()), Optional.<Compiler>absent(), Optional.<ImmutableList<String>>absent(), Optional.<ImmutableList<String>>absent(), Paths.get("test.ii"), new TestSourcePath("test.cpp"), CxxSource.Type.CXX_CPP_OUTPUT, ImmutableSet.of(Paths.get("foo/bar"), Paths.get("test")), ImmutableSet.<Path>of(), ImmutableSet.<Path>of(), ImmutableSet.<Path>of(), Optional.<SourcePath>absent(), ImmutableList.<CxxHeaders>of(), CxxPlatforms.DEFAULT_DEBUG_PATH_SANITIZER); BuildTarget compileTarget = BuildTarget.builder(testBuildRuleParams.getBuildTarget().getUnflavoredBuildTarget()) .addFlavors(ImmutableFlavor.of("compile-test.cpp")) .build(); BuildRuleParams compileBuildRuleParams = new FakeBuildRuleParamsBuilder(compileTarget) .setProjectFilesystem(filesystem) .setDeclaredDeps(ImmutableSortedSet.<BuildRule>of(testPreprocessRule)) .build(); CxxPreprocessAndCompile testCompileRule = new CxxPreprocessAndCompile( compileBuildRuleParams, testSourcePathResolver, CxxPreprocessAndCompileStep.Operation.COMPILE, Optional.<Preprocessor>absent(), Optional.<ImmutableList<String>>absent(), Optional.<ImmutableList<String>>absent(), Optional.<Compiler>of(new DefaultCompiler(new HashedFileTool(Paths.get("compiler")))), Optional.of(ImmutableList.<String>of()), Optional.of(ImmutableList.<String>of()), Paths.get("test.o"), new TestSourcePath("test.ii"), CxxSource.Type.CXX_CPP_OUTPUT, ImmutableSet.<Path>of(), ImmutableSet.<Path>of(), ImmutableSet.<Path>of(), ImmutableSet.<Path>of(), Optional.<SourcePath>absent(), ImmutableList.<CxxHeaders>of(), CxxPlatforms.DEFAULT_DEBUG_PATH_SANITIZER); CxxCompilationDatabase compilationDatabase = CxxCompilationDatabase.createCompilationDatabase( testBuildRuleParams, testSourcePathResolver, CxxPreprocessMode.SEPARATE, ImmutableSortedSet.of(testPreprocessRule, testCompileRule)); assertEquals( "getPathToOutput() should be a function of the build target.", Paths.get("buck-out/gen/foo/__baz#compilation-database.json"), compilationDatabase.getPathToOutput()); BuildContext buildContext = FakeBuildContext.NOOP_CONTEXT; BuildableContext buildableContext = new FakeBuildableContext(); List<Step> buildSteps = compilationDatabase.getPostBuildSteps(buildContext, buildableContext); assertEquals(2, buildSteps.size()); assertTrue(buildSteps.get(0) instanceof MkdirStep); assertTrue(buildSteps.get(1) instanceof CxxCompilationDatabase.GenerateCompilationCommandsJson); CxxCompilationDatabase.GenerateCompilationCommandsJson step = (CxxCompilationDatabase.GenerateCompilationCommandsJson) buildSteps.get(1); Iterable<CxxCompilationDatabaseEntry> observedEntries = step.createEntries(); Iterable<CxxCompilationDatabaseEntry> expectedEntries = ImmutableList.of( CxxCompilationDatabaseEntry.of( root, root + "/test.cpp", ImmutableList.of( "compiler", "-I", "foo/bar", "-I", "test", "-x", "c++-cpp-output", "-c", "-o", "test.o", "test.cpp"))); MoreAsserts.assertIterablesEquals(expectedEntries, observedEntries); }
@Test public void createBuildRuleWithFlavoredTargetCallsEnhancerCorrectly() throws IOException { BuildRuleResolver resolver = new BuildRuleResolver(); SourcePathResolver pathResolver = new SourcePathResolver(resolver); FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); // Setup the default values returned by the language specific enhancer. String language = "fake"; Flavor flavor = ImmutableFlavor.of("fake"); final BuildRule implicitDep = createFakeBuildRule("//implicit:dep", pathResolver); resolver.addToIndex(implicitDep); filesystem.mkdirs(implicitDep.getBuildTarget().getBasePath()); filesystem.touch(implicitDep.getBuildTarget().getBasePath().resolve("BUCK")); ImmutableSet<BuildTarget> implicitDeps = ImmutableSet.of(implicitDep.getBuildTarget()); ImmutableSet<String> options = ImmutableSet.of(); // Create the build targets and params. BuildTarget unflavoredTarget = BuildTargetFactory.newInstance("//:thrift"); BuildRuleParams unflavoredParams = new FakeBuildRuleParamsBuilder(unflavoredTarget).setProjectFilesystem(filesystem).build(); BuildTarget flavoredTarget = BuildTargets.createFlavoredBuildTarget(unflavoredTarget.checkUnflavored(), flavor); BuildRuleParams flavoredParams = new FakeBuildRuleParamsBuilder(flavoredTarget).setProjectFilesystem(filesystem).build(); // Setup a thrift source file generated by a genrule. final String thriftSourceName1 = "foo.thrift"; BuildTarget genruleTarget = BuildTargetFactory.newInstance("//:genrule"); final Genrule genrule = (Genrule) GenruleBuilder.newGenruleBuilder(genruleTarget) .setOut(thriftSourceName1) .build(resolver); SourcePath thriftSource1 = new BuildTargetSourcePath(genrule.getBuildTarget()); final ImmutableList<String> thriftServices1 = ImmutableList.of(); // Setup a normal thrift source file. final String thriftSourceName2 = "bar.thrift"; SourcePath thriftSource2 = new TestSourcePath(thriftSourceName2); final ImmutableList<String> thriftServices2 = ImmutableList.of(); // Create a build rule that represents the thrift rule. final FakeBuildRule thriftRule = createFakeBuildRule("//thrift:target", pathResolver); resolver.addToIndex(thriftRule); filesystem.mkdirs(thriftRule.getBuildTarget().getBasePath()); filesystem.touch(thriftRule.getBuildTarget().getBasePath().resolve("BUCK")); // Setup a simple description with an empty config. FakeBuckConfig buckConfig = new FakeBuckConfig( ImmutableMap.of( "thrift", ImmutableMap.of("compiler", thriftRule.getBuildTarget().toString()))); ThriftBuckConfig thriftBuckConfig = new ThriftBuckConfig(buckConfig); ThriftLibraryDescription desc = new ThriftLibraryDescription( thriftBuckConfig, ImmutableList.<ThriftLanguageSpecificEnhancer>of()); // Setup the include rules. final BuildRule thriftIncludeSymlinkTree = createFakeBuildRule( desc.createThriftIncludeSymlinkTreeTarget(unflavoredTarget), pathResolver); // Setup our language enhancer FakeThriftLanguageSpecificEnhancer enhancer = new FakeThriftLanguageSpecificEnhancer(language, flavor, implicitDeps, options) { @Override public void checkCreateBuildRuleInputs( ImmutableMap<String, ThriftSource> sources, ImmutableSortedSet<BuildRule> deps) { // Verify both thrift sources are present in the list. assertEquals(2, sources.size()); ThriftSource src1 = sources.get(thriftSourceName1); assertNotNull(src1); ThriftSource src2 = sources.get(thriftSourceName2); assertNotNull(src2); // Verify the services are listed correctly for both sources. assertEquals(thriftServices1, src1.getServices()); assertEquals(thriftServices2, src2.getServices()); // Verify dependencies are setup correctly. assertEquals( ImmutableSortedSet.of(genrule, thriftRule, thriftIncludeSymlinkTree), src1.getCompileRule().getDeps()); assertEquals( ImmutableSortedSet.of(genrule, thriftRule, thriftIncludeSymlinkTree), src2.getCompileRule().getDeps()); // Verify the language specific implicit rules are added correctly. assertEquals( ImmutableSortedSet.<BuildRule>naturalOrder().add(implicitDep).build(), deps); } }; // Recreate the description with the enhancer we setup above. desc = new ThriftLibraryDescription( thriftBuckConfig, ImmutableList.<ThriftLanguageSpecificEnhancer>of(enhancer)); // Setup the internal structure indicating that the thrift target was set in the // buck config. // desc.setCompilerTarget(thriftRule.getBuildTarget()); // Setup the implicit deps for the flavored build target. // desc.setImplicitDeps(flavoredTarget, ImmutableList.of(implicitDep.getBuildTarget())); // Build up the constructor arg. ThriftConstructorArg arg = desc.createUnpopulatedConstructorArg(); arg.name = "thrift"; arg.srcs = ImmutableMap.of( thriftSource1, thriftServices1, thriftSource2, thriftServices2); arg.deps = Optional.absent(); arg.flags = Optional.absent(); // Setup the unflavored target, which should just produce a ThriftInclude, SymlinkTree, and // ThriftLibrary rule. BuildRule rule = desc.createBuildRule(TargetGraph.EMPTY, unflavoredParams, resolver, arg); resolver.addToIndex(rule); // Now attempt to create the flavored thrift library. desc.createBuildRule(TargetGraph.EMPTY, flavoredParams, resolver, arg); }
@Test public void createThriftCompilerBuildRulesHasCorrectDeps() throws IOException { BuildRuleResolver resolver = new BuildRuleResolver(); SourcePathResolver pathResolver = new SourcePathResolver(resolver); FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); String language = "fake"; Flavor flavor = ImmutableFlavor.of("fake"); ImmutableSet<String> options = ImmutableSet.of(); // Setup the default values returned by the language specific enhancer. BuildTarget unflavoredTarget = BuildTargetFactory.newInstance("//:thrift"); BuildRuleParams unflavoredParams = new FakeBuildRuleParamsBuilder(BuildTarget.builder(unflavoredTarget).build()) .setProjectFilesystem(filesystem) .build(); // Setup a thrift source file generated by a genrule. BuildTarget flavoredTarget = BuildTargets.createFlavoredBuildTarget(unflavoredTarget.checkUnflavored(), flavor); BuildRuleParams flavoredParams = new FakeBuildRuleParamsBuilder(flavoredTarget).setProjectFilesystem(filesystem).build(); // Create a path for the thrift compiler. Path thriftPath = Paths.get("thrift_path"); filesystem.touch(thriftPath); // Setup an thrift buck config, with the path to the thrift compiler set. FakeBuckConfig buckConfig = new FakeBuckConfig( ImmutableMap.of("thrift", ImmutableMap.of("compiler", thriftPath.toString())), filesystem); ThriftBuckConfig thriftBuckConfig = new ThriftBuckConfig(buckConfig); ThriftLibraryDescription desc = new ThriftLibraryDescription( thriftBuckConfig, ImmutableList.<ThriftLanguageSpecificEnhancer>of()); // Setup a simple thrift source. String sourceName = "test.thrift"; SourcePath sourcePath = new TestSourcePath(sourceName); // Generate these rules using no deps. ImmutableMap<String, ThriftCompiler> rules = desc.createThriftCompilerBuildRules( flavoredParams, resolver, ThriftLibraryDescription.CompilerType.THRIFT, ImmutableList.<String>of(), language, options, ImmutableMap.of(sourceName, sourcePath), ImmutableSortedSet.<ThriftLibrary>of()); // Now verify that the generated rule had no associated deps. assertSame(rules.size(), 1); ThriftCompiler rule = rules.get(sourceName); assertNotNull(rule); assertEquals(ImmutableSortedSet.<BuildRule>of(), rule.getDeps()); // Lets do this again, but pass in a ThriftLibrary deps, wrapping some includes we need. Path includeRoot = desc.getIncludeRoot(unflavoredTarget); HeaderSymlinkTree thriftIncludeSymlinkTree = createFakeSymlinkTree( desc.createThriftIncludeSymlinkTreeTarget(unflavoredTarget), pathResolver, includeRoot); ThriftLibrary lib = new ThriftLibrary( unflavoredParams, pathResolver, ImmutableSortedSet.<ThriftLibrary>of(), thriftIncludeSymlinkTree, ImmutableMap.<Path, SourcePath>of()); // Generate these rules using no deps. rules = desc.createThriftCompilerBuildRules( flavoredParams, resolver, ThriftLibraryDescription.CompilerType.THRIFT, ImmutableList.<String>of(), language, options, ImmutableMap.of(sourceName, sourcePath), ImmutableSortedSet.of(lib)); // Now verify that the generated rule has all the deps from the passed in thrift library. assertSame(rules.size(), 1); rule = rules.get(sourceName); assertNotNull(rule); assertEquals(ImmutableSortedSet.<BuildRule>of(thriftIncludeSymlinkTree), rule.getDeps()); // Setup a simple genrule that creates the thrift source and verify its dep is propagated. Genrule genrule = (Genrule) GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:genrule")) .setOut(sourceName) .build(resolver); SourcePath ruleSourcePath = new BuildTargetSourcePath(genrule.getBuildTarget()); // Generate these rules using no deps and the genrule generated source. rules = desc.createThriftCompilerBuildRules( flavoredParams, resolver, ThriftLibraryDescription.CompilerType.THRIFT, ImmutableList.<String>of(), language, options, ImmutableMap.of(sourceName, ruleSourcePath), ImmutableSortedSet.<ThriftLibrary>of()); // Now verify that the generated rule had no associated deps. assertSame(rules.size(), 1); rule = rules.get(sourceName); assertNotNull(rule); assertEquals(ImmutableSortedSet.<BuildRule>of(genrule), rule.getDeps()); // Create a build rule that represents the thrift rule. FakeBuildRule thriftRule = createFakeBuildRule("//thrift:target", pathResolver); resolver.addToIndex(thriftRule); filesystem.mkdirs(thriftRule.getBuildTarget().getBasePath()); filesystem.touch(thriftRule.getBuildTarget().getBasePath().resolve("BUCK")); // Setup an empty thrift buck config, and set compiler target. buckConfig = new FakeBuckConfig( ImmutableMap.of( "thrift", ImmutableMap.of("compiler", thriftRule.getBuildTarget().toString())), filesystem); thriftBuckConfig = new ThriftBuckConfig(buckConfig); desc = new ThriftLibraryDescription( thriftBuckConfig, ImmutableList.<ThriftLanguageSpecificEnhancer>of()); // Generate these rules using no deps with a compiler target. rules = desc.createThriftCompilerBuildRules( flavoredParams, resolver, ThriftLibraryDescription.CompilerType.THRIFT, ImmutableList.<String>of(), language, options, ImmutableMap.of(sourceName, sourcePath), ImmutableSortedSet.<ThriftLibrary>of()); // Now verify that the generated rule only has deps from the compiler target. assertSame(rules.size(), 1); rule = rules.get(sourceName); assertNotNull(rule); assertEquals(ImmutableSortedSet.<BuildRule>of(thriftRule), rule.getDeps()); }
/** A generator of fine-grained OCaml build rules */ public class OCamlBuildRulesGenerator { private static final Flavor DEBUG_FLAVOR = ImmutableFlavor.of("debug"); private final BuildRuleParams params; private final BuildRuleResolver resolver; private final SourcePathResolver pathResolver; private final OCamlBuildContext ocamlContext; private final ImmutableMap<Path, ImmutableList<Path>> mlInput; private final ImmutableList<SourcePath> cInput; private final Compiler cCompiler; private final Compiler cxxCompiler; public OCamlBuildRulesGenerator( BuildRuleParams params, SourcePathResolver pathResolver, BuildRuleResolver resolver, OCamlBuildContext ocamlContext, ImmutableMap<Path, ImmutableList<Path>> mlInput, ImmutableList<SourcePath> cInput, Compiler cCompiler, Compiler cxxCompiler) { this.params = params; this.pathResolver = pathResolver; this.resolver = resolver; this.ocamlContext = ocamlContext; this.mlInput = mlInput; this.cInput = cInput; this.cCompiler = cCompiler; this.cxxCompiler = cxxCompiler; } OCamlGeneratedBuildRules generate() { ImmutableList.Builder<BuildRule> rules = ImmutableList.builder(); ImmutableList<SourcePath> cmxFiles = generateMLCompilation(mlInput); ImmutableList<SourcePath> objFiles = generateCCompilation(cInput); BuildRule link = generateLinking( ImmutableList.<SourcePath>builder() .addAll(Iterables.concat(cmxFiles, objFiles)) .build()); rules.add(link); ImmutableList<SourcePath> cmoFiles = generateMLCompileBytecode(mlInput); BuildRule bytecodeLink = generateBytecodeLinking( ImmutableList.<SourcePath>builder() .addAll(Iterables.concat(cmoFiles, objFiles)) .build()); rules.add(bytecodeLink); if (!ocamlContext.isLibrary()) { rules.add(generateDebugLauncherRule()); } return OCamlGeneratedBuildRules.builder() .setRules(rules.build()) .setCompileDeps(ImmutableSortedSet.copyOf(pathResolver.filterBuildRuleInputs(cmxFiles))) .setBytecodeCompileDeps( ImmutableSortedSet.copyOf(pathResolver.filterBuildRuleInputs(cmoFiles))) .setObjectFiles(objFiles) .setBytecodeLink(bytecodeLink) .build(); } private static String getCOutputName(String name) { String base = Files.getNameWithoutExtension(name); String ext = Files.getFileExtension(name); Preconditions.checkArgument(OCamlCompilables.SOURCE_EXTENSIONS.contains(ext)); return base + ".o"; } public static BuildTarget createCCompileBuildTarget(BuildTarget target, String name) { return BuildTarget.builder(target) .addFlavors( ImmutableFlavor.of( String.format( "compile-%s", getCOutputName(name) .replace('/', '-') .replace('.', '-') .replace('+', '-') .replace(' ', '-')))) .build(); } private ImmutableList<SourcePath> generateCCompilation(ImmutableList<SourcePath> cInput) { ImmutableList.Builder<SourcePath> objects = ImmutableList.builder(); ImmutableList.Builder<String> cCompileFlags = ImmutableList.builder(); cCompileFlags.addAll(ocamlContext.getCCompileFlags()); cCompileFlags.addAll(ocamlContext.getCommonCFlags()); CxxPreprocessorInput cxxPreprocessorInput = ocamlContext.getCxxPreprocessorInput(); for (SourcePath cSrc : cInput) { String name = pathResolver.getAbsolutePath(cSrc).toFile().getName(); BuildTarget target = createCCompileBuildTarget(params.getBuildTarget(), name); BuildRuleParams cCompileParams = params.copyWithChanges( target, /* declaredDeps */ Suppliers.ofInstance( ImmutableSortedSet.<BuildRule>naturalOrder() // Depend on the rule that generates the sources and headers we're compiling. .addAll( pathResolver.filterBuildRuleInputs( ImmutableList.<SourcePath>builder() .add(cSrc) .addAll( cxxPreprocessorInput .getIncludes() .getNameToPathMap() .values()) .build())) // Also add in extra deps from the preprocessor input, such as the symlink // tree rules. .addAll( BuildRules.toBuildRulesFor( params.getBuildTarget(), resolver, cxxPreprocessorInput.getRules())) .addAll(params.getDeclaredDeps().get()) .build()), /* extraDeps */ params.getExtraDeps()); Path outputPath = ocamlContext.getCOutput(pathResolver.getRelativePath(cSrc)); OCamlCCompile compileRule = new OCamlCCompile( cCompileParams, pathResolver, new OCamlCCompileStep.Args( cCompiler.getEnvironment(pathResolver), cCompiler.getCommandPrefix(pathResolver), ocamlContext.getOcamlCompiler().get(), outputPath, cSrc, cCompileFlags.build(), ImmutableMap.copyOf(cxxPreprocessorInput.getIncludes().getNameToPathMap()))); resolver.addToIndex(compileRule); objects.add(new BuildTargetSourcePath(compileRule.getBuildTarget())); } return objects.build(); } public static BuildTarget addDebugFlavor(BuildTarget target) { return BuildTarget.builder(target).addFlavors(DEBUG_FLAVOR).build(); } private BuildRule generateDebugLauncherRule() { BuildRuleParams debugParams = params.copyWithChanges( addDebugFlavor(params.getBuildTarget()), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of()), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())); OCamlDebugLauncher debugLauncher = new OCamlDebugLauncher( debugParams, pathResolver, new OCamlDebugLauncherStep.Args( ocamlContext.getOcamlDebug().get(), ocamlContext.getBytecodeOutput(), ocamlContext.getOCamlInput(), ocamlContext.getBytecodeIncludeFlags())); resolver.addToIndex(debugLauncher); return debugLauncher; } private BuildRule generateLinking(ImmutableList<SourcePath> allInputs) { BuildRuleParams linkParams = params.copyWithChanges( params.getBuildTarget(), Suppliers.ofInstance( ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(pathResolver.filterBuildRuleInputs(allInputs)) .addAll( FluentIterable.from(ocamlContext.getLinkableInput().getArgs()) .transformAndConcat(Arg.getDepsFunction(pathResolver))) .addAll( FluentIterable.from(ocamlContext.getNativeLinkableInput().getArgs()) .transformAndConcat(Arg.getDepsFunction(pathResolver))) .build()), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())); ImmutableList<String> linkerInputs = FluentIterable.from(allInputs) .transform(pathResolver.deprecatedPathFunction()) .transform(Functions.toStringFunction()) .toList(); ImmutableList.Builder<String> flags = ImmutableList.builder(); flags.addAll(ocamlContext.getFlags()); flags.addAll(ocamlContext.getCommonCLinkerFlags()); OCamlLink link = new OCamlLink( linkParams, pathResolver, allInputs, new OCamlLinkStep.Args( cxxCompiler.getEnvironment(pathResolver), cxxCompiler.getCommandPrefix(pathResolver), ocamlContext.getOcamlCompiler().get(), ocamlContext.getOutput(), ocamlContext.getLinkableInput().getArgs(), ocamlContext.getNativeLinkableInput().getArgs(), linkerInputs, flags.build(), ocamlContext.isLibrary(), /* isBytecode */ false)); resolver.addToIndex(link); return link; } private static final Flavor BYTECODE_FLAVOR = ImmutableFlavor.of("bytecode"); public static BuildTarget addBytecodeFlavor(BuildTarget target) { return BuildTarget.builder(target).addFlavors(BYTECODE_FLAVOR).build(); } private BuildRule generateBytecodeLinking(ImmutableList<SourcePath> allInputs) { BuildRuleParams linkParams = params.copyWithChanges( addBytecodeFlavor(params.getBuildTarget()), Suppliers.ofInstance( ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(pathResolver.filterBuildRuleInputs(allInputs)) .addAll(ocamlContext.getBytecodeLinkDeps()) .addAll( FluentIterable.from(ocamlContext.getLinkableInput().getArgs()) .append(ocamlContext.getNativeLinkableInput().getArgs()) .transformAndConcat(Arg.getDepsFunction(pathResolver)) .filter(Predicates.not(Predicates.instanceOf(OCamlBuild.class)))) .build()), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())); ImmutableList<String> linkerInputs = FluentIterable.from(allInputs) .transform(pathResolver.deprecatedPathFunction()) .transform(Functions.toStringFunction()) .toList(); ImmutableList.Builder<String> flags = ImmutableList.builder(); flags.addAll(ocamlContext.getFlags()); flags.addAll(ocamlContext.getCommonCLinkerFlags()); OCamlLink link = new OCamlLink( linkParams, pathResolver, allInputs, new OCamlLinkStep.Args( cxxCompiler.getEnvironment(pathResolver), cxxCompiler.getCommandPrefix(pathResolver), ocamlContext.getOcamlBytecodeCompiler().get(), ocamlContext.getBytecodeOutput(), ocamlContext.getLinkableInput().getArgs(), ocamlContext.getNativeLinkableInput().getArgs(), linkerInputs, flags.build(), ocamlContext.isLibrary(), /* isBytecode */ true)); resolver.addToIndex(link); return link; } private ImmutableList<String> getCompileFlags(boolean isBytecode, boolean excludeDeps) { String output = isBytecode ? ocamlContext.getCompileBytecodeOutputDir().toString() : ocamlContext.getCompileOutputDir().toString(); ImmutableList.Builder<String> flagBuilder = ImmutableList.builder(); flagBuilder.addAll(ocamlContext.getIncludeFlags(isBytecode, excludeDeps)); flagBuilder.addAll(ocamlContext.getFlags()); flagBuilder.add(OCamlCompilables.OCAML_INCLUDE_FLAG, output); return flagBuilder.build(); } private static String getMLOutputName(String name) { String base = Files.getNameWithoutExtension(name); String ext = Files.getFileExtension(name); Preconditions.checkArgument( OCamlCompilables.SOURCE_EXTENSIONS.contains(ext), "Unexpected extension: " + ext); String dotExt = "." + ext; if (dotExt.equals(OCamlCompilables.OCAML_ML)) { return base + OCamlCompilables.OCAML_CMX; } else if (dotExt.equals(OCamlCompilables.OCAML_MLI)) { return base + OCamlCompilables.OCAML_CMI; } else { Preconditions.checkState(false, "Unexpected extension: " + ext); return base; } } private static String getMLBytecodeOutputName(String name) { String base = Files.getNameWithoutExtension(name); String ext = Files.getFileExtension(name); Preconditions.checkArgument(OCamlCompilables.SOURCE_EXTENSIONS.contains(ext)); String dotExt = "." + ext; if (dotExt.equals(OCamlCompilables.OCAML_ML)) { return base + OCamlCompilables.OCAML_CMO; } else if (dotExt.equals(OCamlCompilables.OCAML_MLI)) { return base + OCamlCompilables.OCAML_CMI; } else { Preconditions.checkState(false, "Unexpected extension: " + ext); return base; } } public static BuildTarget createMLCompileBuildTarget(BuildTarget target, String name) { return BuildTarget.builder(target) .addFlavors( ImmutableFlavor.of( String.format( "ml-compile-%s", getMLOutputName(name) .replace('/', '-') .replace('.', '-') .replace('+', '-') .replace(' ', '-')))) .build(); } public static BuildTarget createMLBytecodeCompileBuildTarget(BuildTarget target, String name) { return BuildTarget.builder(target) .addFlavors( ImmutableFlavor.of( String.format( "ml-bytecode-compile-%s", getMLBytecodeOutputName(name) .replace('/', '-') .replace('.', '-') .replace('+', '-') .replace(' ', '-')))) .build(); } ImmutableList<SourcePath> generateMLCompilation( ImmutableMap<Path, ImmutableList<Path>> mlSources) { ImmutableList.Builder<SourcePath> cmxFiles = ImmutableList.builder(); final Map<Path, ImmutableSortedSet<BuildRule>> sourceToRule = Maps.newHashMap(); for (ImmutableMap.Entry<Path, ImmutableList<Path>> mlSource : mlSources.entrySet()) { generateSingleMLCompilation( sourceToRule, cmxFiles, mlSource.getKey(), mlSources, ImmutableList.<Path>of()); } return cmxFiles.build(); } private void generateSingleMLCompilation( Map<Path, ImmutableSortedSet<BuildRule>> sourceToRule, ImmutableList.Builder<SourcePath> cmxFiles, Path mlSource, ImmutableMap<Path, ImmutableList<Path>> sources, ImmutableList<Path> cycleDetector) { ImmutableList<Path> newCycleDetector = ImmutableList.<Path>builder().addAll(cycleDetector).add(mlSource).build(); if (cycleDetector.contains(mlSource)) { throw new HumanReadableException( "Dependency cycle detected: %s", Joiner.on(" -> ").join(newCycleDetector)); } if (sourceToRule.containsKey(mlSource)) { return; } ImmutableSortedSet.Builder<BuildRule> depsBuilder = ImmutableSortedSet.naturalOrder(); if (sources.containsKey(mlSource)) { for (Path dep : checkNotNull(sources.get(mlSource))) { generateSingleMLCompilation(sourceToRule, cmxFiles, dep, sources, newCycleDetector); depsBuilder.addAll(checkNotNull(sourceToRule.get(dep))); } } ImmutableSortedSet<BuildRule> deps = depsBuilder.build(); String name = mlSource.toFile().getName(); BuildTarget buildTarget = createMLCompileBuildTarget(params.getBuildTarget(), name); BuildRuleParams compileParams = params.copyWithChanges( buildTarget, Suppliers.ofInstance( ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(params.getDeclaredDeps().get()) .addAll(deps) .addAll(ocamlContext.getCompileDeps()) .build()), params.getExtraDeps()); String outputFileName = getMLOutputName(name); Path outputPath = ocamlContext.getCompileOutputDir().resolve(outputFileName); final ImmutableList<String> compileFlags = getCompileFlags(/* isBytecode */ false, /* excludeDeps */ false); OCamlMLCompile compile = new OCamlMLCompile( compileParams, pathResolver, new OCamlMLCompileStep.Args( cCompiler.getEnvironment(pathResolver), cCompiler.getCommandPrefix(pathResolver), ocamlContext.getOcamlCompiler().get(), outputPath, mlSource, compileFlags)); resolver.addToIndex(compile); sourceToRule.put( mlSource, ImmutableSortedSet.<BuildRule>naturalOrder().add(compile).addAll(deps).build()); if (!outputFileName.endsWith(OCamlCompilables.OCAML_CMI)) { cmxFiles.add(new BuildTargetSourcePath(compile.getBuildTarget())); } } private ImmutableList<SourcePath> generateMLCompileBytecode( ImmutableMap<Path, ImmutableList<Path>> mlSources) { ImmutableList.Builder<SourcePath> cmoFiles = ImmutableList.builder(); final Map<Path, ImmutableSortedSet<BuildRule>> sourceToRule = Maps.newHashMap(); for (ImmutableMap.Entry<Path, ImmutableList<Path>> mlSource : mlSources.entrySet()) { generateSingleMLBytecodeCompilation( sourceToRule, cmoFiles, mlSource.getKey(), mlSources, ImmutableList.<Path>of()); } return cmoFiles.build(); } private void generateSingleMLBytecodeCompilation( Map<Path, ImmutableSortedSet<BuildRule>> sourceToRule, ImmutableList.Builder<SourcePath> cmoFiles, Path mlSource, ImmutableMap<Path, ImmutableList<Path>> sources, ImmutableList<Path> cycleDetector) { ImmutableList<Path> newCycleDetector = ImmutableList.<Path>builder().addAll(cycleDetector).add(mlSource).build(); if (cycleDetector.contains(mlSource)) { throw new HumanReadableException( "Dependency cycle detected: %s", Joiner.on(" -> ").join(newCycleDetector)); } if (sourceToRule.containsKey(mlSource)) { return; } ImmutableSortedSet.Builder<BuildRule> depsBuilder = ImmutableSortedSet.naturalOrder(); if (sources.containsKey(mlSource)) { for (Path dep : checkNotNull(sources.get(mlSource))) { generateSingleMLBytecodeCompilation(sourceToRule, cmoFiles, dep, sources, newCycleDetector); depsBuilder.addAll(checkNotNull(sourceToRule.get(dep))); } } ImmutableSortedSet<BuildRule> deps = depsBuilder.build(); String name = mlSource.toFile().getName(); BuildTarget buildTarget = createMLBytecodeCompileBuildTarget(params.getBuildTarget(), name); BuildRuleParams compileParams = params.copyWithChanges( buildTarget, Suppliers.ofInstance( ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(params.getDeclaredDeps().get()) .addAll(deps) .addAll(ocamlContext.getBytecodeCompileDeps()) .build()), params.getExtraDeps()); String outputFileName = getMLBytecodeOutputName(name); Path outputPath = ocamlContext.getCompileBytecodeOutputDir().resolve(outputFileName); final ImmutableList<String> compileFlags = getCompileFlags(/* isBytecode */ true, /* excludeDeps */ false); BuildRule compileBytecode = new OCamlMLCompile( compileParams, pathResolver, new OCamlMLCompileStep.Args( cCompiler.getEnvironment(pathResolver), cCompiler.getCommandPrefix(pathResolver), ocamlContext.getOcamlBytecodeCompiler().get(), outputPath, mlSource, compileFlags)); resolver.addToIndex(compileBytecode); sourceToRule.put( mlSource, ImmutableSortedSet.<BuildRule>naturalOrder().add(compileBytecode).addAll(deps).build()); if (!outputFileName.endsWith(OCamlCompilables.OCAML_CMI)) { cmoFiles.add(new BuildTargetSourcePath(compileBytecode.getBuildTarget())); } } }
@Test public void shouldBeAbleToConstructACxxLibraryFromThrift() throws Exception { ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "cxx", tmp); workspace.setUp(); BuckEventBus eventBus = BuckEventBusFactory.newInstance(); ProjectFilesystem filesystem = new ProjectFilesystem(workspace.getDestPath()); Path compiler = new ExecutableFinder() .getExecutable(Paths.get("echo"), ImmutableMap.copyOf(System.getenv())); BuckConfig config = FakeBuckConfig.builder() .setFilesystem(filesystem) .setSections( "[thrift]", "compiler = " + compiler, "compiler2 = " + compiler, "cpp_library = //thrift:fake", "cpp_reflection_library = //thrift:fake") .build(); TypeCoercerFactory typeCoercerFactory = new DefaultTypeCoercerFactory(); Parser parser = new Parser( new ParserConfig(config), typeCoercerFactory, new ConstructorArgMarshaller(typeCoercerFactory)); Cell cell = Cell.createCell( filesystem, new TestConsole(), Watchman.NULL_WATCHMAN, config, new KnownBuildRuleTypesFactory( new ProcessExecutor(new TestConsole()), new FakeAndroidDirectoryResolver(), Optional.<Path>absent()), new FakeAndroidDirectoryResolver(), new DefaultClock()); BuildTarget target = BuildTargetFactory.newInstance(filesystem, "//thrift:exe"); TargetGraph targetGraph = parser.buildTargetGraph( eventBus, cell, false, Executors.newSingleThreadExecutor(), ImmutableSet.of(target)); TargetNodeToBuildRuleTransformer transformer = new BuildTargetNodeToBuildRuleTransformer(); // There was a case where the cxx library being generated wouldn't put the header into the tree // with the right flavour. This catches this case without us needing to stick a working thrift // compiler into buck's own source. Pair<ActionGraph, BuildRuleResolver> actionGraphAndResolver = new TargetGraphToActionGraph(eventBus, transformer).apply(targetGraph); // This is to cover the case where we weren't passing flavors around correctly, which ended // making the binary depend 'placeholder' BuildRules instead of real ones. This is the // regression test for that case. BuildRuleResolver ruleResolver = actionGraphAndResolver.getSecond(); BuildTarget binaryFlavor = target.withFlavors(ImmutableFlavor.of("binary")); ImmutableSortedSet<BuildRule> deps = ruleResolver.getRule(binaryFlavor).getDeps(); assertThat( FluentIterable.from(deps) .anyMatch( new Predicate<BuildRule>() { @Override public boolean apply(BuildRule input) { return input instanceof NoopBuildRule; } }), Matchers.is(false)); }
public class PythonBuckConfig { public static final Flavor DEFAULT_PYTHON_PLATFORM = ImmutableFlavor.of("py-default"); private static final String SECTION = "python"; private static final String PYTHON_PLATFORM_SECTION_PREFIX = "python#"; private static final Pattern PYTHON_VERSION_REGEX = Pattern.compile(".*?(\\wy(thon|run) \\d+\\.\\d+).*"); // Prefer "python2" where available (Linux), but fall back to "python" (Mac). private static final ImmutableList<String> PYTHON_INTERPRETER_NAMES = ImmutableList.of("python2", "python"); private static final Path DEFAULT_PATH_TO_PEX = Paths.get(System.getProperty("buck.path_to_pex", "src/com/facebook/buck/python/make_pex.py")) .toAbsolutePath(); private static final LoadingCache<ProjectFilesystem, PathSourcePath> PATH_TO_TEST_MAIN = CacheBuilder.newBuilder() .build( new CacheLoader<ProjectFilesystem, PathSourcePath>() { @Override public PathSourcePath load(@Nonnull ProjectFilesystem filesystem) { return new PathSourcePath( filesystem, PythonBuckConfig.class + "/__test_main__.py", new PackagedResource(filesystem, PythonBuckConfig.class, "__test_main__.py")); } }); private final BuckConfig delegate; private final ExecutableFinder exeFinder; public PythonBuckConfig(BuckConfig config, ExecutableFinder exeFinder) { this.delegate = config; this.exeFinder = exeFinder; } @VisibleForTesting protected PythonPlatform getDefaultPythonPlatform(ProcessExecutor executor) throws InterruptedException { return getPythonPlatform( executor, DEFAULT_PYTHON_PLATFORM, delegate.getValue(SECTION, "interpreter"), delegate.getBuildTarget(SECTION, "library")); } /** * Constructs set of Python platform flavors given in a .buckconfig file, as is specified by * section names of the form python#{flavor name}. */ public ImmutableList<PythonPlatform> getPythonPlatforms(ProcessExecutor processExecutor) throws InterruptedException { ImmutableList.Builder<PythonPlatform> builder = ImmutableList.builder(); // Add the python platform described in the top-level section first. builder.add(getDefaultPythonPlatform(processExecutor)); // Then add all additional python platform described in the extended sections. for (String section : delegate.getSections()) { if (section.startsWith(PYTHON_PLATFORM_SECTION_PREFIX)) { builder.add( getPythonPlatform( processExecutor, ImmutableFlavor.of(section.substring(PYTHON_PLATFORM_SECTION_PREFIX.length())), delegate.getValue(section, "interpreter"), delegate.getBuildTarget(section, "library"))); } } return builder.build(); } private PythonPlatform getPythonPlatform( ProcessExecutor processExecutor, Flavor flavor, Optional<String> interpreter, Optional<BuildTarget> library) throws InterruptedException { return PythonPlatform.of(flavor, getPythonEnvironment(processExecutor, interpreter), library); } /** @return true if file is executable and not a directory. */ private boolean isExecutableFile(File file) { return file.canExecute() && !file.isDirectory(); } /** * Returns the path to python interpreter. If python is specified in 'interpreter' key of the * 'python' section that is used and an error reported if invalid. * * @return The found python interpreter. */ public String getPythonInterpreter(Optional<String> configPath) { ImmutableList<String> pythonInterpreterNames = PYTHON_INTERPRETER_NAMES; if (configPath.isPresent()) { // Python path in config. Use it or report error if invalid. File python = new File(configPath.get()); if (isExecutableFile(python)) { return python.getAbsolutePath(); } if (python.isAbsolute()) { throw new HumanReadableException("Not a python executable: " + configPath.get()); } pythonInterpreterNames = ImmutableList.of(configPath.get()); } for (String interpreterName : pythonInterpreterNames) { Optional<Path> python = exeFinder.getOptionalExecutable(Paths.get(interpreterName), delegate.getEnvironment()); if (python.isPresent()) { return python.get().toAbsolutePath().toString(); } } if (configPath.isPresent()) { throw new HumanReadableException("Not a python executable: " + configPath.get()); } else { throw new HumanReadableException("No python2 or python found."); } } public String getPythonInterpreter() { Optional<String> configPath = delegate.getValue(SECTION, "interpreter"); return getPythonInterpreter(configPath); } public PythonEnvironment getPythonEnvironment( ProcessExecutor processExecutor, Optional<String> configPath) throws InterruptedException { Path pythonPath = Paths.get(getPythonInterpreter(configPath)); PythonVersion pythonVersion = getPythonVersion(processExecutor, pythonPath); return new PythonEnvironment(pythonPath, pythonVersion); } public PythonEnvironment getPythonEnvironment(ProcessExecutor processExecutor) throws InterruptedException { Optional<String> configPath = delegate.getValue(SECTION, "interpreter"); return getPythonEnvironment(processExecutor, configPath); } public SourcePath getPathToTestMain(ProjectFilesystem filesystem) { return PATH_TO_TEST_MAIN.getUnchecked(filesystem); } public Optional<BuildTarget> getPexTarget() { try { return delegate.getBuildTarget(SECTION, "path_to_pex"); } catch (BuildTargetParseException e) { return Optional.absent(); } } public Tool getPexTool(BuildRuleResolver resolver) { Optional<Tool> executable = delegate.getTool(SECTION, "path_to_pex", resolver); if (executable.isPresent()) { return executable.get(); } return new VersionedTool( Paths.get(getPythonInterpreter()), ImmutableList.of(DEFAULT_PATH_TO_PEX.toString()), "pex", BuckVersion.getVersion()); } public Optional<Tool> getPathToPexExecuter(BuildRuleResolver resolver) { return delegate.getTool(SECTION, "path_to_pex_executer", resolver); } public NativeLinkStrategy getNativeLinkStrategy() { return delegate .getEnum(SECTION, "native_link_strategy", NativeLinkStrategy.class) .or(NativeLinkStrategy.SPEARATE); } public String getPexExtension() { return delegate.getValue(SECTION, "pex_extension").or(".pex"); } private static PythonVersion getPythonVersion(ProcessExecutor processExecutor, Path pythonPath) throws InterruptedException { try { ProcessExecutor.Result versionResult = processExecutor.launchAndExecute( ProcessExecutorParams.builder().addCommand(pythonPath.toString(), "-V").build(), EnumSet.of(ProcessExecutor.Option.EXPECTING_STD_ERR), /* stdin */ Optional.<String>absent(), /* timeOutMs */ Optional.<Long>absent(), /* timeoutHandler */ Optional.<Function<Process, Void>>absent()); return extractPythonVersion(pythonPath, versionResult); } catch (IOException e) { throw new HumanReadableException( e, "Could not run \"%s --version\": %s", pythonPath, e.getMessage()); } } @VisibleForTesting static PythonVersion extractPythonVersion(Path pythonPath, ProcessExecutor.Result versionResult) { if (versionResult.getExitCode() == 0) { String versionString = CharMatcher.WHITESPACE.trimFrom( CharMatcher.WHITESPACE.trimFrom(versionResult.getStderr().get()) + CharMatcher.WHITESPACE .trimFrom(versionResult.getStdout().get()) .replaceAll("\u001B\\[[;\\d]*m", "")); Matcher matcher = PYTHON_VERSION_REGEX.matcher(versionString.split("\\r?\\n")[0]); if (!matcher.matches()) { throw new HumanReadableException( "`%s -V` returned an invalid version string %s", pythonPath, versionString); } return PythonVersion.of(matcher.group(1)); } else { throw new HumanReadableException(versionResult.getStderr().get()); } } public PackageStyle getPackageStyle() { return delegate .getEnum(SECTION, "package_style", PackageStyle.class) .or(PackageStyle.STANDALONE); } public enum PackageStyle { STANDALONE, INPLACE, } }
public class JavaBinaryDescription implements Description<JavaBinaryDescription.Args> { public static final BuildRuleType TYPE = BuildRuleType.of("java_binary"); private static final Flavor FAT_JAR_INNER_JAR_FLAVOR = ImmutableFlavor.of("inner-jar"); private final JavacOptions javacOptions; private final CxxPlatform cxxPlatform; private final Optional<String> javaBinOverride; public JavaBinaryDescription( JavacOptions javacOptions, CxxPlatform cxxPlatform, Optional<String> javaBinOverride) { this.javacOptions = Preconditions.checkNotNull(javacOptions); this.cxxPlatform = Preconditions.checkNotNull(cxxPlatform); this.javaBinOverride = javaBinOverride; } @Override public BuildRuleType getBuildRuleType() { return TYPE; } @Override public Args createUnpopulatedConstructorArg() { return new Args(); } @Override public <A extends Args> BuildRule createBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, A args) { SourcePathResolver pathResolver = new SourcePathResolver(resolver); ImmutableMap<String, SourcePath> nativeLibraries = JavaLibraryRules.getNativeLibraries(targetGraph, params.getDeps(), cxxPlatform); BuildRuleParams binaryParams = params; // If we're packaging native libraries, we'll build the binary JAR in a separate rule and // package it into the final fat JAR, so adjust it's params to use a flavored target. if (!nativeLibraries.isEmpty()) { binaryParams = params.copyWithChanges( BuildTarget.builder(params.getBuildTarget()) .addFlavors(FAT_JAR_INNER_JAR_FLAVOR) .build(), params.getDeclaredDeps(), params.getExtraDeps()); } // Construct the build rule to build the binary JAR. ImmutableSetMultimap<JavaLibrary, Path> transitiveClasspathEntries = Classpaths.getClasspathEntries(binaryParams.getDeps()); BuildRule rule = new JavaBinary( binaryParams.appendExtraDeps(transitiveClasspathEntries.keys()), pathResolver, args.mainClass.orNull(), args.manifestFile.orNull(), args.mergeManifests.or(true), args.metaInfDirectory.orNull(), args.blacklist.or(ImmutableSortedSet.<String>of()), new DefaultDirectoryTraverser(), transitiveClasspathEntries, javaBinOverride); // If we're packaging native libraries, construct the rule to build the fat JAR, which packages // up the original binary JAR and any required native libraries. if (!nativeLibraries.isEmpty()) { BuildRule innerJarRule = rule; resolver.addToIndex(innerJarRule); SourcePath innerJar = new BuildTargetSourcePath(innerJarRule.getBuildTarget()); rule = new JarFattener( params.appendExtraDeps( Suppliers.<Iterable<BuildRule>>ofInstance( pathResolver.filterBuildRuleInputs( ImmutableList.<SourcePath>builder() .add(innerJar) .addAll(nativeLibraries.values()) .build()))), pathResolver, javacOptions, innerJar, nativeLibraries, javaBinOverride); } return rule; } @SuppressFieldNotInitialized public static class Args { public Optional<ImmutableSortedSet<BuildTarget>> deps; public Optional<String> mainClass; public Optional<SourcePath> manifestFile; public Optional<Boolean> mergeManifests; @Beta public Optional<Path> metaInfDirectory; @Beta public Optional<ImmutableSortedSet<String>> blacklist; } }
@Override public Flavor getFlavor() { return ImmutableFlavor.of(getGoOs() + "_" + getGoArch()); }
@VisibleForTesting static AppleCxxPlatform buildWithExecutableChecker( AppleSdk targetSdk, String minVersion, String targetArchitecture, AppleSdkPaths sdkPaths, BuckConfig buckConfig, ExecutableFinder executableFinder) { ImmutableList.Builder<Path> toolSearchPathsBuilder = ImmutableList.builder(); // Search for tools from most specific to least specific. toolSearchPathsBuilder .add(sdkPaths.getSdkPath().resolve(USR_BIN)) .add(sdkPaths.getSdkPath().resolve("Developer").resolve(USR_BIN)) .add(sdkPaths.getPlatformPath().resolve("Developer").resolve(USR_BIN)); for (Path toolchainPath : sdkPaths.getToolchainPaths()) { toolSearchPathsBuilder.add(toolchainPath.resolve(USR_BIN)); } if (sdkPaths.getDeveloperPath().isPresent()) { toolSearchPathsBuilder.add(sdkPaths.getDeveloperPath().get().resolve(USR_BIN)); toolSearchPathsBuilder.add(sdkPaths.getDeveloperPath().get().resolve("Tools")); } ImmutableList<Path> toolSearchPaths = toolSearchPathsBuilder.build(); // TODO(user): Add more and better cflags. ImmutableList.Builder<String> cflagsBuilder = ImmutableList.builder(); cflagsBuilder.add("-isysroot", sdkPaths.getSdkPath().toString()); cflagsBuilder.add("-arch", targetArchitecture); switch (targetSdk.getApplePlatform().getName()) { case ApplePlatform.Name.IPHONEOS: cflagsBuilder.add("-mios-version-min=" + minVersion); break; case ApplePlatform.Name.IPHONESIMULATOR: cflagsBuilder.add("-mios-simulator-version-min=" + minVersion); break; default: // For Mac builds, -mmacosx-version-min=<version>. cflagsBuilder.add( "-m" + targetSdk.getApplePlatform().getName() + "-version-min=" + minVersion); break; } ImmutableList<String> ldflags = ImmutableList.of("-sdk_version", targetSdk.getVersion()); ImmutableList.Builder<String> versionsBuilder = ImmutableList.builder(); versionsBuilder.add(targetSdk.getVersion()); for (AppleToolchain toolchain : targetSdk.getToolchains()) { versionsBuilder.add(toolchain.getVersion()); } String version = Joiner.on(':').join(versionsBuilder.build()); Tool clangPath = new VersionedTool( getToolPath("clang", toolSearchPaths, executableFinder), ImmutableList.<String>of(), "apple-clang", version); Tool clangXxPath = new VersionedTool( getToolPath("clang++", toolSearchPaths, executableFinder), ImmutableList.<String>of(), "apple-clang++", version); Tool ar = new VersionedTool( getToolPath("ar", toolSearchPaths, executableFinder), ImmutableList.<String>of(), "apple-ar", version); Tool actool = new VersionedTool( getToolPath("actool", toolSearchPaths, executableFinder), ImmutableList.<String>of(), "apple-actool", version); Tool ibtool = new VersionedTool( getToolPath("ibtool", toolSearchPaths, executableFinder), ImmutableList.<String>of(), "apple-ibtool", version); Tool xctest = new VersionedTool( getToolPath("xctest", toolSearchPaths, executableFinder), ImmutableList.<String>of(), "apple-xctest", version); Optional<Tool> otest = getOptionalTool("otest", toolSearchPaths, executableFinder, version); Tool dsymutil = new VersionedTool( getToolPath("dsymutil", toolSearchPaths, executableFinder), ImmutableList.<String>of(), "apple-dsymutil", version); CxxBuckConfig config = new CxxBuckConfig(buckConfig); ImmutableFlavor targetFlavor = ImmutableFlavor.of( ImmutableFlavor.replaceInvalidCharacters( targetSdk.getName() + "-" + targetArchitecture)); ImmutableBiMap.Builder<Path, Path> sanitizerPaths = ImmutableBiMap.builder(); sanitizerPaths.put(sdkPaths.getSdkPath(), Paths.get("APPLE_SDKROOT")); sanitizerPaths.put(sdkPaths.getPlatformPath(), Paths.get("APPLE_PLATFORM_DIR")); if (sdkPaths.getDeveloperPath().isPresent()) { sanitizerPaths.put(sdkPaths.getDeveloperPath().get(), Paths.get("APPLE_DEVELOPER_DIR")); } DebugPathSanitizer debugPathSanitizer = new DebugPathSanitizer(250, File.separatorChar, Paths.get("."), sanitizerPaths.build()); ImmutableList<String> cflags = cflagsBuilder.build(); CxxPlatform cxxPlatform = CxxPlatforms.build( targetFlavor, Platform.MACOS, config, clangPath, clangPath, new ClangCompiler(clangPath), new ClangCompiler(clangXxPath), clangPath, clangXxPath, clangXxPath, Optional.of(CxxPlatform.LinkerType.DARWIN), clangXxPath, ldflags, new BsdArchiver(ar), cflags, ImmutableList.<String>of(), getOptionalTool("lex", toolSearchPaths, executableFinder, version), getOptionalTool("yacc", toolSearchPaths, executableFinder, version), Optional.of(debugPathSanitizer)); return AppleCxxPlatform.builder() .setCxxPlatform(cxxPlatform) .setAppleSdk(targetSdk) .setAppleSdkPaths(sdkPaths) .setActool(actool) .setIbtool(ibtool) .setXctest(xctest) .setOtest(otest) .setDsymutil(dsymutil) .build(); }
@Test public void binaryWithDependenciesCompilationDatabase() throws IOException { BuildTarget target = BuildTargetFactory.newInstance("//:binary_with_dep#compilation-database"); Path compilationDatabase = workspace.buildAndReturnOutput(target.getFullyQualifiedName()); ProjectFilesystem filesystem = new FakeProjectFilesystem(); Path rootPath = tmp.getRoot(); assertEquals( BuildTargets.getGenPath(filesystem, target, "__%s.json"), rootPath.relativize(compilationDatabase)); Path binaryHeaderSymlinkTreeFolder = BuildTargets.getGenPath( filesystem, target.withFlavors( ImmutableFlavor.of("default"), CxxDescriptionEnhancer.HEADER_SYMLINK_TREE_FLAVOR), "%s"); assertTrue(Files.exists(rootPath.resolve(binaryHeaderSymlinkTreeFolder))); BuildTarget libraryTarget = BuildTargetFactory.newInstance("//:library_with_header"); Path libraryExportedHeaderSymlinkTreeFolder = BuildTargets.getGenPath( filesystem, libraryTarget.withFlavors( ImmutableFlavor.of("default"), CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR), "%s"); // Verify that symlink folders for headers are created and header file is linked. assertTrue(Files.exists(rootPath.resolve(libraryExportedHeaderSymlinkTreeFolder))); assertTrue(Files.exists(rootPath.resolve(libraryExportedHeaderSymlinkTreeFolder + "/bar.h"))); Map<String, CxxCompilationDatabaseEntry> fileToEntry = CxxCompilationDatabaseUtils.parseCompilationDatabaseJsonFile(compilationDatabase); assertEquals(1, fileToEntry.size()); assertHasEntry( fileToEntry, "foo.cpp", new ImmutableList.Builder<String>() .add(COMPILER_PATH) .add("-I") .add(headerSymlinkTreePath(binaryHeaderSymlinkTreeFolder).toString()) .add("-I") .add(headerSymlinkTreePath(libraryExportedHeaderSymlinkTreeFolder).toString()) .addAll(getExtraFlagsForHeaderMaps(filesystem)) .addAll(COMPILER_SPECIFIC_FLAGS) .add("-x") .add("c++") .add("-c") .add("-o") .add( BuildTargets.getGenPath( filesystem, target.withFlavors( ImmutableFlavor.of("default"), ImmutableFlavor.of("compile-" + sanitize("foo.cpp.o"))), "%s/foo.cpp.o") .toString()) .add(rootPath.resolve(Paths.get("foo.cpp")).toRealPath().toString()) .build()); }
private void runCombinedTest( CxxPreprocessMode strategy, ImmutableList<String> expectedArguments) { BuildTarget testBuildTarget = BuildTarget.builder(BuildTargetFactory.newInstance("//foo:baz")) .addAllFlavors(ImmutableSet.of(CxxCompilationDatabase.COMPILATION_DATABASE)) .build(); final String root = "/Users/user/src"; final Path fakeRoot = Paths.get(root); ProjectFilesystem filesystem = new FakeProjectFilesystem() { @Override public Path getRootPath() { return fakeRoot; } @Override public Path resolve(Path relativePath) { return fakeRoot.resolve(relativePath); } }; BuildRuleParams testBuildRuleParams = new FakeBuildRuleParamsBuilder(testBuildTarget).setProjectFilesystem(filesystem).build(); BuildRuleResolver testBuildRuleResolver = new BuildRuleResolver(); SourcePathResolver testSourcePathResolver = new SourcePathResolver(testBuildRuleResolver); BuildTarget preprocessTarget = BuildTarget.builder(testBuildRuleParams.getBuildTarget().getUnflavoredBuildTarget()) .addFlavors(ImmutableFlavor.of("preprocess-test.cpp")) .build(); BuildTarget compileTarget = BuildTarget.builder(testBuildRuleParams.getBuildTarget().getUnflavoredBuildTarget()) .addFlavors(ImmutableFlavor.of("compile-test.cpp")) .build(); ImmutableSortedSet.Builder<CxxPreprocessAndCompile> rules = ImmutableSortedSet.naturalOrder(); CxxPreprocessAndCompileStep.Operation operation; BuildRuleParams compileBuildRuleParams; switch (strategy) { case SEPARATE: operation = CxxPreprocessAndCompileStep.Operation.COMPILE; CxxPreprocessAndCompile preprocessRule = new CxxPreprocessAndCompile( new FakeBuildRuleParamsBuilder(preprocessTarget) .setProjectFilesystem(filesystem) .build(), testSourcePathResolver, operation, Optional.<Preprocessor>of( new DefaultPreprocessor(new HashedFileTool(Paths.get("preprocessor")))), Optional.of(ImmutableList.<String>of()), Optional.of(ImmutableList.<String>of()), Optional.<Compiler>absent(), Optional.<ImmutableList<String>>absent(), Optional.<ImmutableList<String>>absent(), Paths.get("test.o"), new TestSourcePath("test.cpp"), CxxSource.Type.CXX, ImmutableSet.of(Paths.get("foo/bar"), Paths.get("test")), ImmutableSet.<Path>of(), ImmutableSet.<Path>of(), ImmutableSet.<Path>of(), Optional.<SourcePath>absent(), ImmutableList.<CxxHeaders>of(), CxxPlatforms.DEFAULT_DEBUG_PATH_SANITIZER); rules.add(preprocessRule); compileBuildRuleParams = new FakeBuildRuleParamsBuilder(compileTarget) .setProjectFilesystem(filesystem) .setDeclaredDeps(ImmutableSortedSet.<BuildRule>of(preprocessRule)) .build(); break; case COMBINED: operation = CxxPreprocessAndCompileStep.Operation.COMPILE_MUNGE_DEBUGINFO; compileBuildRuleParams = new FakeBuildRuleParamsBuilder(compileTarget).setProjectFilesystem(filesystem).build(); break; case PIPED: operation = CxxPreprocessAndCompileStep.Operation.PIPED_PREPROCESS_AND_COMPILE; compileBuildRuleParams = new FakeBuildRuleParamsBuilder(compileTarget).setProjectFilesystem(filesystem).build(); break; default: throw new RuntimeException("Invalid strategy"); } rules.add( new CxxPreprocessAndCompile( compileBuildRuleParams, testSourcePathResolver, operation, Optional.<Preprocessor>of( new DefaultPreprocessor(new HashedFileTool(Paths.get("preprocessor")))), Optional.of(ImmutableList.<String>of()), Optional.of(ImmutableList.<String>of()), Optional.<Compiler>of(new DefaultCompiler(new HashedFileTool(Paths.get("compiler")))), Optional.of(ImmutableList.<String>of()), Optional.of(ImmutableList.<String>of()), Paths.get("test.o"), new TestSourcePath("test.cpp"), CxxSource.Type.CXX, ImmutableSet.of(Paths.get("foo/bar"), Paths.get("test")), ImmutableSet.<Path>of(), ImmutableSet.<Path>of(), ImmutableSet.<Path>of(), Optional.<SourcePath>absent(), ImmutableList.<CxxHeaders>of(), CxxPlatforms.DEFAULT_DEBUG_PATH_SANITIZER)); CxxCompilationDatabase compilationDatabase = CxxCompilationDatabase.createCompilationDatabase( testBuildRuleParams, testSourcePathResolver, strategy, rules.build()); assertEquals( "getPathToOutput() should be a function of the build target.", Paths.get("buck-out/gen/foo/__baz#compilation-database.json"), compilationDatabase.getPathToOutput()); BuildContext buildContext = FakeBuildContext.NOOP_CONTEXT; BuildableContext buildableContext = new FakeBuildableContext(); List<Step> buildSteps = compilationDatabase.getPostBuildSteps(buildContext, buildableContext); assertEquals(2, buildSteps.size()); assertTrue(buildSteps.get(0) instanceof MkdirStep); assertTrue(buildSteps.get(1) instanceof CxxCompilationDatabase.GenerateCompilationCommandsJson); CxxCompilationDatabase.GenerateCompilationCommandsJson step = (CxxCompilationDatabase.GenerateCompilationCommandsJson) buildSteps.get(1); Iterable<CxxCompilationDatabaseEntry> observedEntries = step.createEntries(); Iterable<CxxCompilationDatabaseEntry> expectedEntries = ImmutableList.of( CxxCompilationDatabaseEntry.of(root, root + "/test.cpp", expectedArguments)); MoreAsserts.assertIterablesEquals(expectedEntries, observedEntries); }
/** * Description for a {@link BuildRule} that generates an {@code .aar} file. * * <p>This represents an Android Library Project packaged as an {@code .aar} bundle as specified by: * <a> http://tools.android.com/tech-docs/new-build-system/aar-format</a>. * * <p>Note that the {@code aar} may be specified as a {@link SourcePath}, so it could be either a * binary {@code .aar} file checked into version control, or a zip file that conforms to the {@code * .aar} specification that is generated by another build rule. */ public class AndroidAarDescription implements Description<AndroidAarDescription.Arg> { public static final BuildRuleType TYPE = BuildRuleType.of("android_aar"); private static final Flavor AAR_ANDROID_MANIFEST_FLAVOR = ImmutableFlavor.of("aar_android_manifest"); private static final Flavor AAR_ASSEMBLE_RESOURCE_FLAVOR = ImmutableFlavor.of("aar_assemble_resource"); private static final Flavor AAR_ASSEMBLE_ASSETS_FLAVOR = ImmutableFlavor.of("aar_assemble_assets"); private static final Flavor AAR_ANDROID_RESOURCE_FLAVOR = ImmutableFlavor.of("aar_android_resource"); private final AndroidManifestDescription androidManifestDescription; private final ImmutableMap<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> nativePlatforms; public AndroidAarDescription( AndroidManifestDescription androidManifestDescription, ImmutableMap<NdkCxxPlatforms.TargetCpuType, NdkCxxPlatform> nativePlatforms) { this.androidManifestDescription = androidManifestDescription; this.nativePlatforms = nativePlatforms; } @Override public BuildRuleType getBuildRuleType() { return TYPE; } @Override public Arg createUnpopulatedConstructorArg() { return new Arg(); } @Override public <A extends Arg> BuildRule createBuildRule( TargetGraph targetGraph, BuildRuleParams originalBuildRuleParams, BuildRuleResolver resolver, A args) { UnflavoredBuildTarget originalBuildTarget = originalBuildRuleParams.getBuildTarget().checkUnflavored(); SourcePathResolver pathResolver = new SourcePathResolver(resolver); ImmutableList.Builder<BuildRule> aarExtraDepsBuilder = ImmutableList.<BuildRule>builder().addAll(originalBuildRuleParams.getExtraDeps().get()); /* android_manifest */ AndroidManifestDescription.Arg androidManifestArgs = androidManifestDescription.createUnpopulatedConstructorArg(); androidManifestArgs.skeleton = args.manifestSkeleton; androidManifestArgs.deps = args.deps; BuildRuleParams androidManifestParams = originalBuildRuleParams.copyWithChanges( BuildTargets.createFlavoredBuildTarget( originalBuildTarget, AAR_ANDROID_MANIFEST_FLAVOR), originalBuildRuleParams.getDeclaredDeps(), originalBuildRuleParams.getExtraDeps()); AndroidManifest manifest = androidManifestDescription.createBuildRule( targetGraph, androidManifestParams, resolver, androidManifestArgs); aarExtraDepsBuilder.add(resolver.addToIndex(manifest)); /* assemble dirs */ AndroidPackageableCollector collector = new AndroidPackageableCollector( originalBuildRuleParams.getBuildTarget(), /* buildTargetsToExcludeFromDex */ ImmutableSet.<BuildTarget>of(), /* resourcesToExclude */ ImmutableSet.<BuildTarget>of()); collector.addPackageables( AndroidPackageableCollector.getPackageableRules(originalBuildRuleParams.getDeps())); AndroidPackageableCollection packageableCollection = collector.build(); ImmutableSortedSet<BuildRule> androidResourceDeclaredDeps = AndroidResourceHelper.androidResOnly(originalBuildRuleParams.getDeclaredDeps().get()); ImmutableSortedSet<BuildRule> androidResourceExtraDeps = AndroidResourceHelper.androidResOnly(originalBuildRuleParams.getExtraDeps().get()); BuildRuleParams assembleAssetsParams = originalBuildRuleParams.copyWithChanges( BuildTargets.createFlavoredBuildTarget(originalBuildTarget, AAR_ASSEMBLE_ASSETS_FLAVOR), Suppliers.ofInstance(androidResourceDeclaredDeps), Suppliers.ofInstance(androidResourceExtraDeps)); ImmutableCollection<SourcePath> assetsDirectories = packageableCollection.getAssetsDirectories(); AssembleDirectories assembleAssetsDirectories = new AssembleDirectories(assembleAssetsParams, pathResolver, assetsDirectories); aarExtraDepsBuilder.add(resolver.addToIndex(assembleAssetsDirectories)); BuildRuleParams assembleResourceParams = originalBuildRuleParams.copyWithChanges( BuildTargets.createFlavoredBuildTarget( originalBuildTarget, AAR_ASSEMBLE_RESOURCE_FLAVOR), Suppliers.ofInstance(androidResourceDeclaredDeps), Suppliers.ofInstance(androidResourceExtraDeps)); ImmutableCollection<SourcePath> resDirectories = packageableCollection.getResourceDetails().getResourceDirectories(); MergeAndroidResourceSources assembleResourceDirectories = new MergeAndroidResourceSources(assembleResourceParams, pathResolver, resDirectories); aarExtraDepsBuilder.add(resolver.addToIndex(assembleResourceDirectories)); /* android_resource */ BuildRuleParams androidResourceParams = originalBuildRuleParams.copyWithChanges( BuildTargets.createFlavoredBuildTarget( originalBuildTarget, AAR_ANDROID_RESOURCE_FLAVOR), Suppliers.ofInstance( ImmutableSortedSet.<BuildRule>of( manifest, assembleAssetsDirectories, assembleResourceDirectories)), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())); AndroidResource androidResource = new AndroidResource( androidResourceParams, pathResolver, /* deps */ ImmutableSortedSet.<BuildRule>naturalOrder() .add(assembleAssetsDirectories) .add(assembleResourceDirectories) .addAll(originalBuildRuleParams.getDeclaredDeps().get()) .build(), new BuildTargetSourcePath(assembleResourceDirectories.getBuildTarget()), /* resSrcs */ ImmutableSortedSet.<SourcePath>of(), Optional.<SourcePath>absent(), /* rDotJavaPackage */ null, new BuildTargetSourcePath(assembleAssetsDirectories.getBuildTarget()), /* assetsSrcs */ ImmutableSortedSet.<SourcePath>of(), Optional.<SourcePath>absent(), new BuildTargetSourcePath(manifest.getBuildTarget()), /* hasWhitelistedStrings */ false); aarExtraDepsBuilder.add(resolver.addToIndex(androidResource)); /* native_libraries */ AndroidNativeLibsPackageableGraphEnhancer packageableGraphEnhancer = new AndroidNativeLibsPackageableGraphEnhancer( resolver, originalBuildRuleParams, nativePlatforms, ImmutableSet.<NdkCxxPlatforms.TargetCpuType>of()); Optional<CopyNativeLibraries> nativeLibrariesOptional = packageableGraphEnhancer.getCopyNativeLibraries(targetGraph, packageableCollection); if (nativeLibrariesOptional.isPresent()) { aarExtraDepsBuilder.add(resolver.addToIndex(nativeLibrariesOptional.get())); } Optional<Path> assembledNativeLibsDir = nativeLibrariesOptional.transform( new Function<CopyNativeLibraries, Path>() { @Override public Path apply(CopyNativeLibraries input) { return input.getPathToNativeLibsDir(); } }); BuildRuleParams androidAarParams = originalBuildRuleParams.copyWithExtraDeps( Suppliers.ofInstance(ImmutableSortedSet.copyOf(aarExtraDepsBuilder.build()))); return new AndroidAar( androidAarParams, pathResolver, manifest, androidResource, assembleResourceDirectories.getPathToOutput(), assembleAssetsDirectories.getPathToOutput(), assembledNativeLibsDir, packageableCollection.getNativeLibAssetsDirectories()); } @SuppressFieldNotInitialized public static class Arg extends AndroidLibraryDescription.Arg { public SourcePath manifestSkeleton; } }
public class CxxDescriptionEnhancer { private static final Logger LOG = Logger.get(CxxDescriptionEnhancer.class); public static final Flavor HEADER_SYMLINK_TREE_FLAVOR = ImmutableFlavor.of("private-headers"); public static final Flavor EXPORTED_HEADER_SYMLINK_TREE_FLAVOR = ImmutableFlavor.of("headers"); public static final Flavor STATIC_FLAVOR = ImmutableFlavor.of("static"); public static final Flavor STATIC_PIC_FLAVOR = ImmutableFlavor.of("static-pic"); public static final Flavor SHARED_FLAVOR = ImmutableFlavor.of("shared"); public static final Flavor MACH_O_BUNDLE_FLAVOR = ImmutableFlavor.of("mach-o-bundle"); public static final Flavor SHARED_LIBRARY_SYMLINK_TREE_FLAVOR = ImmutableFlavor.of("shared-library-symlink-tree"); public static final Flavor CXX_LINK_BINARY_FLAVOR = ImmutableFlavor.of("binary"); public static final Flavor LEX_YACC_SOURCE_FLAVOR = ImmutableFlavor.of("lex_yacc_sources"); protected static final MacroHandler MACRO_HANDLER = new MacroHandler( ImmutableMap.<String, MacroExpander>of("location", new LocationMacroExpander())); private CxxDescriptionEnhancer() {} private static BuildTarget createLexYaccSourcesBuildTarget(BuildTarget target) { return BuildTarget.builder(target).addFlavors(LEX_YACC_SOURCE_FLAVOR).build(); } public static CxxHeaderSourceSpec requireLexYaccSources( BuildRuleParams params, BuildRuleResolver ruleResolver, SourcePathResolver pathResolver, CxxPlatform cxxPlatform, ImmutableMap<String, SourcePath> lexSources, ImmutableMap<String, SourcePath> yaccSources) { BuildTarget lexYaccTarget = createLexYaccSourcesBuildTarget(params.getBuildTarget()); // Check the cache... Optional<BuildRule> rule = ruleResolver.getRuleOptional(lexYaccTarget); if (rule.isPresent()) { @SuppressWarnings("unchecked") ContainerBuildRule<CxxHeaderSourceSpec> containerRule = (ContainerBuildRule<CxxHeaderSourceSpec>) rule.get(); return containerRule.get(); } // Setup the rules to run lex/yacc. CxxHeaderSourceSpec lexYaccSources = CxxDescriptionEnhancer.createLexYaccBuildRules( params, ruleResolver, cxxPlatform, ImmutableList.<String>of(), lexSources, ImmutableList.<String>of(), yaccSources); ruleResolver.addToIndex( ContainerBuildRule.of(params, pathResolver, lexYaccTarget, lexYaccSources)); return lexYaccSources; } public static HeaderSymlinkTree createHeaderSymlinkTree( BuildRuleParams params, BuildRuleResolver ruleResolver, SourcePathResolver pathResolver, CxxPlatform cxxPlatform, boolean includeLexYaccHeaders, ImmutableMap<String, SourcePath> lexSources, ImmutableMap<String, SourcePath> yaccSources, ImmutableMap<Path, SourcePath> headers, HeaderVisibility headerVisibility) { BuildTarget headerSymlinkTreeTarget = CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget( params.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility); Path headerSymlinkTreeRoot = CxxDescriptionEnhancer.getHeaderSymlinkTreePath( params.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility); Optional<Path> headerMapLocation = Optional.absent(); if (cxxPlatform.getCpp().supportsHeaderMaps() && cxxPlatform.getCxxpp().supportsHeaderMaps()) { headerMapLocation = Optional.of( getHeaderMapPath(params.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility)); } CxxHeaderSourceSpec lexYaccSources; if (includeLexYaccHeaders) { lexYaccSources = requireLexYaccSources( params, ruleResolver, pathResolver, cxxPlatform, lexSources, yaccSources); } else { lexYaccSources = CxxHeaderSourceSpec.builder().build(); } return CxxPreprocessables.createHeaderSymlinkTreeBuildRule( pathResolver, headerSymlinkTreeTarget, params, headerSymlinkTreeRoot, headerMapLocation, ImmutableMap.<Path, SourcePath>builder() .putAll(headers) .putAll(lexYaccSources.getCxxHeaders()) .build()); } public static HeaderSymlinkTree requireHeaderSymlinkTree( BuildRuleParams params, BuildRuleResolver ruleResolver, SourcePathResolver pathResolver, CxxPlatform cxxPlatform, boolean includeLexYaccHeaders, ImmutableMap<String, SourcePath> lexSources, ImmutableMap<String, SourcePath> yaccSources, ImmutableMap<Path, SourcePath> headers, HeaderVisibility headerVisibility) { BuildTarget headerSymlinkTreeTarget = CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget( params.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility); // Check the cache... Optional<BuildRule> rule = ruleResolver.getRuleOptional(headerSymlinkTreeTarget); if (rule.isPresent()) { Preconditions.checkState(rule.get() instanceof HeaderSymlinkTree); return (HeaderSymlinkTree) rule.get(); } HeaderSymlinkTree symlinkTree = createHeaderSymlinkTree( params, ruleResolver, pathResolver, cxxPlatform, includeLexYaccHeaders, lexSources, yaccSources, headers, headerVisibility); ruleResolver.addToIndex(symlinkTree); return symlinkTree; } /** * @return the {@link BuildTarget} to use for the {@link BuildRule} generating the symlink tree of * headers. */ public static BuildTarget createHeaderSymlinkTreeTarget( BuildTarget target, Flavor platform, HeaderVisibility headerVisibility) { return BuildTarget.builder(target) .addFlavors(platform) .addFlavors(getHeaderSymlinkTreeFlavor(headerVisibility)) .build(); } /** @return the {@link Path} to use for the symlink tree of headers. */ public static Path getHeaderSymlinkTreePath( BuildTarget target, Flavor platform, HeaderVisibility headerVisibility) { return BuildTargets.getGenPath( createHeaderSymlinkTreeTarget(target, platform, headerVisibility), "%s"); } public static Flavor getHeaderSymlinkTreeFlavor(HeaderVisibility headerVisibility) { switch (headerVisibility) { case PUBLIC: return EXPORTED_HEADER_SYMLINK_TREE_FLAVOR; case PRIVATE: return HEADER_SYMLINK_TREE_FLAVOR; default: throw new RuntimeException("Unexpected value of enum ExportMode"); } } /** @return the {@link Path} to use for the header map for the given symlink tree. */ public static Path getHeaderMapPath( BuildTarget target, Flavor platform, HeaderVisibility headerVisibility) { return BuildTargets.getGenPath( createHeaderSymlinkTreeTarget(target, platform, headerVisibility), "%s.hmap"); } /** * @return a map of header locations to input {@link SourcePath} objects formed by parsing the * input {@link SourcePath} objects for the "headers" parameter. */ public static ImmutableMap<Path, SourcePath> parseHeaders( BuildRuleParams params, BuildRuleResolver resolver, CxxPlatform cxxPlatform, CxxConstructorArg args) { ImmutableMap.Builder<String, SourcePath> headers = ImmutableMap.builder(); SourcePathResolver pathResolver = new SourcePathResolver(resolver); putAllHeaders(args.headers.get(), headers, pathResolver, "headers", params.getBuildTarget()); for (SourceList sourceList : args.platformHeaders.get().getMatchingValues(cxxPlatform.getFlavor().toString())) { putAllHeaders(sourceList, headers, pathResolver, "platform_headers", params.getBuildTarget()); } return CxxPreprocessables.resolveHeaderMap( args.headerNamespace.transform(MorePaths.TO_PATH).or(params.getBuildTarget().getBasePath()), headers.build()); } /** * @return a map of header locations to input {@link SourcePath} objects formed by parsing the * input {@link SourcePath} objects for the "exportedHeaders" parameter. */ public static ImmutableMap<Path, SourcePath> parseExportedHeaders( BuildRuleParams params, BuildRuleResolver resolver, CxxPlatform cxxPlatform, CxxLibraryDescription.Arg args) { ImmutableMap.Builder<String, SourcePath> headers = ImmutableMap.builder(); SourcePathResolver pathResolver = new SourcePathResolver(resolver); putAllHeaders( args.exportedHeaders.get(), headers, pathResolver, "exported_headers", params.getBuildTarget()); for (SourceList sourceList : args.exportedPlatformHeaders.get().getMatchingValues(cxxPlatform.getFlavor().toString())) { putAllHeaders( sourceList, headers, pathResolver, "exported_platform_headers", params.getBuildTarget()); } return CxxPreprocessables.resolveHeaderMap( args.headerNamespace.transform(MorePaths.TO_PATH).or(params.getBuildTarget().getBasePath()), headers.build()); } /** * Resolves the headers in `sourceList` and puts them into `sources` for the specificed * `buildTarget`. */ public static void putAllHeaders( SourceList sourceList, ImmutableMap.Builder<String, SourcePath> sources, SourcePathResolver pathResolver, String parameterName, BuildTarget buildTarget) { switch (sourceList.getType()) { case NAMED: sources.putAll(sourceList.getNamedSources().get()); break; case UNNAMED: sources.putAll( pathResolver.getSourcePathNames( buildTarget, parameterName, sourceList.getUnnamedSources().get())); break; } } /** * @return a list {@link CxxSource} objects formed by parsing the input {@link SourcePath} objects * for the "srcs" parameter. */ public static ImmutableMap<String, CxxSource> parseCxxSources( BuildRuleParams params, BuildRuleResolver resolver, CxxPlatform cxxPlatform, CxxConstructorArg args) { return parseCxxSources(params, resolver, cxxPlatform, args.srcs.get(), args.platformSrcs.get()); } public static ImmutableMap<String, CxxSource> parseCxxSources( BuildRuleParams params, BuildRuleResolver resolver, CxxPlatform cxxPlatform, ImmutableSortedSet<SourceWithFlags> srcs, PatternMatchedCollection<ImmutableSortedSet<SourceWithFlags>> platformSrcs) { ImmutableMap.Builder<String, SourceWithFlags> sources = ImmutableMap.builder(); SourcePathResolver pathResolver = new SourcePathResolver(resolver); putAllSources(srcs, sources, pathResolver, params.getBuildTarget()); for (ImmutableSortedSet<SourceWithFlags> sourcesWithFlags : platformSrcs.getMatchingValues(cxxPlatform.getFlavor().toString())) { putAllSources(sourcesWithFlags, sources, pathResolver, params.getBuildTarget()); } return CxxCompilableEnhancer.resolveCxxSources(sources.build()); } private static void putAllSources( ImmutableSortedSet<SourceWithFlags> sourcesWithFlags, ImmutableMap.Builder<String, SourceWithFlags> sources, SourcePathResolver pathResolver, BuildTarget buildTarget) { sources.putAll( pathResolver.getSourcePathNames( buildTarget, "srcs", sourcesWithFlags, SourceWithFlags.TO_SOURCE_PATH)); } public static ImmutableMap<String, SourcePath> parseLexSources( BuildRuleParams params, BuildRuleResolver resolver, CxxConstructorArg args) { SourcePathResolver pathResolver = new SourcePathResolver(resolver); return pathResolver.getSourcePathNames( params.getBuildTarget(), "lexSrcs", args.lexSrcs.or(ImmutableList.<SourcePath>of())); } public static ImmutableMap<String, SourcePath> parseYaccSources( BuildRuleParams params, BuildRuleResolver resolver, CxxConstructorArg args) { SourcePathResolver pathResolver = new SourcePathResolver(resolver); return pathResolver.getSourcePathNames( params.getBuildTarget(), "yaccSrcs", args.yaccSrcs.or(ImmutableList.<SourcePath>of())); } @VisibleForTesting protected static BuildTarget createLexBuildTarget(UnflavoredBuildTarget target, String name) { return BuildTarget.builder(target) .addFlavors( ImmutableFlavor.of( String.format( "lex-%s", name.replace('/', '-').replace('.', '-').replace('+', '-').replace(' ', '-')))) .build(); } @VisibleForTesting protected static BuildTarget createYaccBuildTarget(UnflavoredBuildTarget target, String name) { return BuildTarget.builder(target) .addFlavors( ImmutableFlavor.of( String.format( "yacc-%s", name.replace('/', '-').replace('.', '-').replace('+', '-').replace(' ', '-')))) .build(); } /** @return the output path prefix to use for yacc generated files. */ @VisibleForTesting protected static Path getYaccOutputPrefix(UnflavoredBuildTarget target, String name) { BuildTarget flavoredTarget = createYaccBuildTarget(target, name); return BuildTargets.getGenPath(flavoredTarget, "%s/" + name); } /** @return the output path to use for the lex generated C/C++ source. */ @VisibleForTesting protected static Path getLexSourceOutputPath(UnflavoredBuildTarget target, String name) { BuildTarget flavoredTarget = createLexBuildTarget(target, name); return BuildTargets.getGenPath(flavoredTarget, "%s/" + name + ".cc"); } /** @return the output path to use for the lex generated C/C++ header. */ @VisibleForTesting protected static Path getLexHeaderOutputPath(UnflavoredBuildTarget target, String name) { BuildTarget flavoredTarget = createLexBuildTarget(target, name); return BuildTargets.getGenPath(flavoredTarget, "%s/" + name + ".h"); } /** * Generate {@link Lex} and {@link Yacc} rules generating C/C++ sources from the given lex/yacc * sources. * * @return {@link CxxHeaderSourceSpec} containing the generated headers/sources */ public static CxxHeaderSourceSpec createLexYaccBuildRules( BuildRuleParams params, BuildRuleResolver resolver, CxxPlatform cxxPlatform, ImmutableList<String> lexFlags, ImmutableMap<String, SourcePath> lexSrcs, ImmutableList<String> yaccFlags, ImmutableMap<String, SourcePath> yaccSrcs) { if (!lexSrcs.isEmpty() && !cxxPlatform.getLex().isPresent()) { throw new HumanReadableException( "Platform %s must support lex to compile srcs %s", cxxPlatform, lexSrcs); } if (!yaccSrcs.isEmpty() && !cxxPlatform.getYacc().isPresent()) { throw new HumanReadableException( "Platform %s must support yacc to compile srcs %s", cxxPlatform, yaccSrcs); } SourcePathResolver pathResolver = new SourcePathResolver(resolver); ImmutableMap.Builder<String, CxxSource> lexYaccCxxSourcesBuilder = ImmutableMap.builder(); ImmutableMap.Builder<Path, SourcePath> lexYaccHeadersBuilder = ImmutableMap.builder(); // Loop over all lex sources, generating build rule for each one and adding the sources // and headers it generates to our bookkeeping maps. UnflavoredBuildTarget unflavoredBuildTarget = params.getBuildTarget().getUnflavoredBuildTarget(); for (ImmutableMap.Entry<String, SourcePath> ent : lexSrcs.entrySet()) { final String name = ent.getKey(); final SourcePath source = ent.getValue(); BuildTarget target = createLexBuildTarget(unflavoredBuildTarget, name); Path outputSource = getLexSourceOutputPath(unflavoredBuildTarget, name); Path outputHeader = getLexHeaderOutputPath(unflavoredBuildTarget, name); // Create the build rule to run lex on this source and add it to the resolver. Lex lex = new Lex( params.copyWithChanges( target, Suppliers.ofInstance( ImmutableSortedSet.copyOf( pathResolver.filterBuildRuleInputs(ImmutableList.of(source)))), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())), pathResolver, cxxPlatform.getLex().get(), ImmutableList.<String>builder() .addAll(cxxPlatform.getLexFlags()) .addAll(lexFlags) .build(), outputSource, outputHeader, source); resolver.addToIndex(lex); // Record the output source and header as {@link BuildRuleSourcePath} objects. lexYaccCxxSourcesBuilder.put( name + ".cc", CxxSource.of( CxxSource.Type.CXX, new BuildTargetSourcePath(lex.getBuildTarget(), outputSource), ImmutableList.<String>of())); lexYaccHeadersBuilder.put( params.getBuildTarget().getBasePath().resolve(name + ".h"), new BuildTargetSourcePath(lex.getBuildTarget(), outputHeader)); } // Loop over all yaccc sources, generating build rule for each one and adding the sources // and headers it generates to our bookkeeping maps. for (ImmutableMap.Entry<String, SourcePath> ent : yaccSrcs.entrySet()) { final String name = ent.getKey(); final SourcePath source = ent.getValue(); BuildTarget target = createYaccBuildTarget(unflavoredBuildTarget, name); Path outputPrefix = getYaccOutputPrefix(unflavoredBuildTarget, Files.getNameWithoutExtension(name)); // Create the build rule to run yacc on this source and add it to the resolver. Yacc yacc = new Yacc( params.copyWithChanges( target, Suppliers.ofInstance( ImmutableSortedSet.copyOf( pathResolver.filterBuildRuleInputs(ImmutableList.of(source)))), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())), pathResolver, cxxPlatform.getYacc().get(), ImmutableList.<String>builder() .addAll(cxxPlatform.getYaccFlags()) .addAll(yaccFlags) .build(), outputPrefix, source); resolver.addToIndex(yacc); // Record the output source and header as {@link BuildRuleSourcePath} objects. lexYaccCxxSourcesBuilder.put( name + ".cc", CxxSource.of( CxxSource.Type.CXX, new BuildTargetSourcePath( yacc.getBuildTarget(), Yacc.getSourceOutputPath(outputPrefix)), ImmutableList.<String>of())); lexYaccHeadersBuilder.put( params.getBuildTarget().getBasePath().resolve(name + ".h"), new BuildTargetSourcePath(yacc.getBuildTarget(), Yacc.getHeaderOutputPath(outputPrefix))); } return CxxHeaderSourceSpec.of(lexYaccHeadersBuilder.build(), lexYaccCxxSourcesBuilder.build()); } public static ImmutableList<CxxPreprocessorInput> collectCxxPreprocessorInput( TargetGraph targetGraph, BuildRuleParams params, CxxPlatform cxxPlatform, ImmutableMultimap<CxxSource.Type, String> preprocessorFlags, ImmutableList<HeaderSymlinkTree> headerSymlinkTrees, ImmutableSet<Path> frameworkSearchPaths, Iterable<CxxPreprocessorInput> cxxPreprocessorInputFromDeps) { // Add the private includes of any rules which list this rule as a test. BuildTarget targetWithoutFlavor = BuildTarget.of(params.getBuildTarget().getUnflavoredBuildTarget()); ImmutableList.Builder<CxxPreprocessorInput> cxxPreprocessorInputFromTestedRulesBuilder = ImmutableList.builder(); for (BuildRule rule : params.getDeps()) { if (rule instanceof NativeTestable) { NativeTestable testable = (NativeTestable) rule; if (testable.isTestedBy(targetWithoutFlavor)) { LOG.debug( "Adding private includes of tested rule %s to testing rule %s", rule.getBuildTarget(), params.getBuildTarget()); cxxPreprocessorInputFromTestedRulesBuilder.add( testable.getCxxPreprocessorInput(targetGraph, cxxPlatform, HeaderVisibility.PRIVATE)); } } } ImmutableList<CxxPreprocessorInput> cxxPreprocessorInputFromTestedRules = cxxPreprocessorInputFromTestedRulesBuilder.build(); LOG.verbose( "Rules tested by target %s added private includes %s", params.getBuildTarget(), cxxPreprocessorInputFromTestedRules); ImmutableMap.Builder<Path, SourcePath> allLinks = ImmutableMap.builder(); ImmutableMap.Builder<Path, SourcePath> allFullLinks = ImmutableMap.builder(); ImmutableList.Builder<Path> allIncludeRoots = ImmutableList.builder(); ImmutableSet.Builder<Path> allHeaderMaps = ImmutableSet.builder(); for (HeaderSymlinkTree headerSymlinkTree : headerSymlinkTrees) { allLinks.putAll(headerSymlinkTree.getLinks()); allFullLinks.putAll(headerSymlinkTree.getFullLinks()); allIncludeRoots.add(headerSymlinkTree.getIncludePath()); allHeaderMaps.addAll(headerSymlinkTree.getHeaderMap().asSet()); } CxxPreprocessorInput localPreprocessorInput = CxxPreprocessorInput.builder() .addAllRules(Iterables.transform(headerSymlinkTrees, HasBuildTarget.TO_TARGET)) .putAllPreprocessorFlags(preprocessorFlags) .setIncludes( CxxHeaders.builder() .putAllNameToPathMap(allLinks.build()) .putAllFullNameToPathMap(allFullLinks.build()) .build()) .addAllIncludeRoots(allIncludeRoots.build()) .addAllHeaderMaps(allHeaderMaps.build()) .addAllFrameworkRoots(frameworkSearchPaths) .build(); return ImmutableList.<CxxPreprocessorInput>builder() .add(localPreprocessorInput) .addAll(cxxPreprocessorInputFromDeps) .addAll(cxxPreprocessorInputFromTestedRules) .build(); } public static BuildTarget createStaticLibraryBuildTarget( BuildTarget target, Flavor platform, CxxSourceRuleFactory.PicType pic) { return BuildTarget.builder(target) .addFlavors(platform) .addFlavors(pic == CxxSourceRuleFactory.PicType.PDC ? STATIC_FLAVOR : STATIC_PIC_FLAVOR) .build(); } public static BuildTarget createSharedLibraryBuildTarget(BuildTarget target, Flavor platform) { return BuildTarget.builder(target).addFlavors(platform).addFlavors(SHARED_FLAVOR).build(); } public static Path getStaticLibraryPath( BuildTarget target, Flavor platform, CxxSourceRuleFactory.PicType pic) { String name = String.format("lib%s.a", target.getShortName()); return BuildTargets.getGenPath(createStaticLibraryBuildTarget(target, platform, pic), "%s") .resolve(name); } public static String getDefaultSharedLibrarySoname(BuildTarget target, CxxPlatform platform) { String libName = Joiner.on('_') .join( ImmutableList.builder() .addAll( FluentIterable.from(target.getBasePath()) .transform(Functions.toStringFunction()) .filter(Predicates.not(Predicates.equalTo("")))) .add( target .withoutFlavors(ImmutableSet.of(platform.getFlavor())) .getShortNameAndFlavorPostfix()) .build()); String extension = platform.getSharedLibraryExtension(); return String.format("lib%s.%s", libName, extension); } public static Path getSharedLibraryPath(BuildTarget target, String soname, CxxPlatform platform) { return BuildTargets.getGenPath( createSharedLibraryBuildTarget(target, platform.getFlavor()), "%s/" + soname); } @VisibleForTesting protected static Path getLinkOutputPath(BuildTarget target) { return BuildTargets.getGenPath(target, "%s"); } @VisibleForTesting protected static BuildTarget createCxxLinkTarget(BuildTarget target) { return BuildTarget.builder(target).addFlavors(CXX_LINK_BINARY_FLAVOR).build(); } /** @return the framework search paths with any embedded macros expanded. */ static ImmutableSet<Path> getFrameworkSearchPaths( Optional<ImmutableSortedSet<FrameworkPath>> frameworks, CxxPlatform cxxPlatform, SourcePathResolver resolver) { ImmutableSet<Path> searchPaths = FluentIterable.from(frameworks.get()) .transform( FrameworkPath.getUnexpandedSearchPathFunction( resolver.getPathFunction(), Functions.<Path>identity())) .toSet(); return FluentIterable.from(Optional.of(searchPaths).or(ImmutableSet.<Path>of())) .transform(Functions.toStringFunction()) .transform(CxxFlags.getTranslateMacrosFn(cxxPlatform)) .transform(MorePaths.TO_PATH) .toSet(); } public static CxxLinkAndCompileRules createBuildRulesForCxxBinaryDescriptionArg( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, CxxPlatform cxxPlatform, CxxBinaryDescription.Arg args, CxxPreprocessMode preprocessMode) { ImmutableMap<String, CxxSource> srcs = parseCxxSources(params, resolver, cxxPlatform, args); ImmutableMap<Path, SourcePath> headers = parseHeaders(params, resolver, cxxPlatform, args); ImmutableMap<String, SourcePath> lexSrcs = parseLexSources(params, resolver, args); ImmutableMap<String, SourcePath> yaccSrcs = parseYaccSources(params, resolver, args); return createBuildRulesForCxxBinary( targetGraph, params, resolver, cxxPlatform, srcs, headers, lexSrcs, yaccSrcs, preprocessMode, args.linkStyle.or(Linker.LinkableDepType.STATIC), args.preprocessorFlags, args.platformPreprocessorFlags, args.langPreprocessorFlags, args.frameworks, args.compilerFlags, args.platformCompilerFlags, args.prefixHeader, args.linkerFlags, args.platformLinkerFlags, args.cxxRuntimeType); } public static CxxLinkAndCompileRules createBuildRulesForCxxBinary( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, CxxPlatform cxxPlatform, ImmutableMap<String, CxxSource> srcs, ImmutableMap<Path, SourcePath> headers, ImmutableMap<String, SourcePath> lexSrcs, ImmutableMap<String, SourcePath> yaccSrcs, CxxPreprocessMode preprocessMode, Linker.LinkableDepType linkStyle, Optional<ImmutableList<String>> preprocessorFlags, Optional<PatternMatchedCollection<ImmutableList<String>>> platformPreprocessorFlags, Optional<ImmutableMap<CxxSource.Type, ImmutableList<String>>> langPreprocessorFlags, Optional<ImmutableSortedSet<FrameworkPath>> frameworks, Optional<ImmutableList<String>> compilerFlags, Optional<PatternMatchedCollection<ImmutableList<String>>> platformCompilerFlags, Optional<SourcePath> prefixHeader, Optional<ImmutableList<String>> linkerFlags, Optional<PatternMatchedCollection<ImmutableList<String>>> platformLinkerFlags, Optional<Linker.CxxRuntimeType> cxxRuntimeType) { SourcePathResolver sourcePathResolver = new SourcePathResolver(resolver); Path linkOutput = getLinkOutputPath(params.getBuildTarget()); ImmutableList.Builder<Arg> argsBuilder = ImmutableList.builder(); CommandTool.Builder executableBuilder = new CommandTool.Builder(); // Setup the rules to run lex/yacc. CxxHeaderSourceSpec lexYaccSources = requireLexYaccSources(params, resolver, sourcePathResolver, cxxPlatform, lexSrcs, yaccSrcs); // Setup the header symlink tree and combine all the preprocessor input from this rule // and all dependencies. HeaderSymlinkTree headerSymlinkTree = requireHeaderSymlinkTree( params, resolver, sourcePathResolver, cxxPlatform, /* includeLexYaccHeaders */ true, lexSrcs, yaccSrcs, headers, HeaderVisibility.PRIVATE); ImmutableList<CxxPreprocessorInput> cxxPreprocessorInput = collectCxxPreprocessorInput( targetGraph, params, cxxPlatform, CxxFlags.getLanguageFlags( preprocessorFlags, platformPreprocessorFlags, langPreprocessorFlags, cxxPlatform), ImmutableList.of(headerSymlinkTree), getFrameworkSearchPaths(frameworks, cxxPlatform, new SourcePathResolver(resolver)), CxxPreprocessables.getTransitiveCxxPreprocessorInput( targetGraph, cxxPlatform, FluentIterable.from(params.getDeps()) .filter(Predicates.instanceOf(CxxPreprocessorDep.class)))); // The complete list of input sources. ImmutableMap<String, CxxSource> sources = ImmutableMap.<String, CxxSource>builder() .putAll(srcs) .putAll(lexYaccSources.getCxxSources()) .build(); // Generate and add all the build rules to preprocess and compile the source to the // resolver and get the `SourcePath`s representing the generated object files. ImmutableMap<CxxPreprocessAndCompile, SourcePath> objects = CxxSourceRuleFactory.requirePreprocessAndCompileRules( params, resolver, sourcePathResolver, cxxPlatform, cxxPreprocessorInput, CxxFlags.getFlags(compilerFlags, platformCompilerFlags, cxxPlatform), prefixHeader, preprocessMode, sources, linkStyle == Linker.LinkableDepType.STATIC ? CxxSourceRuleFactory.PicType.PDC : CxxSourceRuleFactory.PicType.PIC); // Build up the linker flags, which support macro expansion. ImmutableList<String> resolvedLinkerFlags = CxxFlags.getFlags(linkerFlags, platformLinkerFlags, cxxPlatform); argsBuilder.addAll( FluentIterable.from(resolvedLinkerFlags) .transform( MacroArg.toMacroArgFunction( MACRO_HANDLER, params.getBuildTarget(), params.getCellRoots(), resolver, params.getProjectFilesystem()))); // Special handling for dynamically linked binaries. if (linkStyle == Linker.LinkableDepType.SHARED) { // Create a symlink tree with for all shared libraries needed by this binary. SymlinkTree sharedLibraries = resolver.addToIndex( createSharedLibrarySymlinkTree( targetGraph, params, sourcePathResolver, cxxPlatform, Predicates.instanceOf(NativeLinkable.class))); // Embed a origin-relative library path into the binary so it can find the shared libraries. argsBuilder.addAll( StringArg.from( Linkers.iXlinker( "-rpath", String.format( "%s/%s", cxxPlatform.getLd().origin(), linkOutput.getParent().relativize(sharedLibraries.getRoot()).toString())))); // Add all the shared libraries and the symlink tree as inputs to the tool that represents // this binary, so that users can attach the proper deps. executableBuilder.addDep(sharedLibraries); executableBuilder.addInputs(sharedLibraries.getLinks().values()); } // Add object files into the args. argsBuilder.addAll(SourcePathArg.from(sourcePathResolver, objects.values())); // Generate the final link rule. We use the top-level target as the link rule's // target, so that it corresponds to the actual binary we build. CxxLink cxxLink = CxxLinkableEnhancer.createCxxLinkableBuildRule( targetGraph, cxxPlatform, params, sourcePathResolver, createCxxLinkTarget(params.getBuildTarget()), Linker.LinkType.EXECUTABLE, Optional.<String>absent(), linkOutput, argsBuilder.build(), linkStyle, params.getDeps(), cxxRuntimeType, Optional.<SourcePath>absent(), ImmutableSet.<BuildTarget>of()); resolver.addToIndex(cxxLink); // Add the output of the link as the lone argument needed to invoke this binary as a tool. executableBuilder.addArg(new BuildTargetSourcePath(cxxLink.getBuildTarget())); return new CxxLinkAndCompileRules( cxxLink, ImmutableSortedSet.copyOf(objects.keySet()), executableBuilder.build()); } private static <T> BuildRule createBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver ruleResolver, TargetNode<T> node, Flavor... flavors) { BuildTarget target = BuildTarget.builder(params.getBuildTarget()).addFlavors(flavors).build(); Description<T> description = node.getDescription(); T args = node.getConstructorArg(); return description.createBuildRule( targetGraph, params.copyWithChanges(target, params.getDeclaredDeps(), params.getExtraDeps()), ruleResolver, args); } /** * Ensure that the build rule generated by the given {@link BuildRuleParams} had been generated by * it's corresponding {@link Description} and added to the {@link BuildRuleResolver}. If not, call * into it's associated {@link Description} to generate it's {@link BuildRule}. * * @return the {@link BuildRule} generated by the description corresponding to the supplied {@link * BuildRuleParams}. */ public static BuildRule requireBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver ruleResolver, Flavor... flavors) { BuildTarget target = BuildTarget.builder(params.getBuildTarget()).addFlavors(flavors).build(); Optional<BuildRule> rule = ruleResolver.getRuleOptional(target); if (!rule.isPresent()) { TargetNode<?> node = targetGraph.get(params.getBuildTarget()); Preconditions.checkNotNull( node, String.format("%s not in target graph", params.getBuildTarget())); rule = Optional.of(createBuildRule(targetGraph, params, ruleResolver, node, flavors)); ruleResolver.addToIndex(rule.get()); } return rule.get(); } /** * @return a {@link Function} object which transforms path names from the output of a compiler or * preprocessor using {@code pathProcessor}. */ public static Function<String, Iterable<String>> createErrorMessagePathProcessor( final Function<String, String> pathProcessor) { return new Function<String, Iterable<String>>() { private final ImmutableList<Pattern> patterns = ImmutableList.of( Pattern.compile( "(?<=^(?:In file included |\\s+)from )" + "(?<path>[^:]+)" + "(?=[:,](?:\\d+[:,](?:\\d+[:,])?)?$)"), Pattern.compile("^(?<path>[^:]+)(?=:(?:\\d+:(?:\\d+:)?)? )")); @Override public Iterable<String> apply(String line) { for (Pattern pattern : patterns) { Matcher m = pattern.matcher(line); if (m.find()) { return ImmutableList.of(m.replaceAll(pathProcessor.apply(m.group("path")))); } } return ImmutableList.of(line); } }; } /** * @return the {@link BuildTarget} to use for the {@link BuildRule} generating the symlink tree of * shared libraries. */ public static BuildTarget createSharedLibrarySymlinkTreeTarget( BuildTarget target, Flavor platform) { return BuildTarget.builder(target) .addFlavors(SHARED_LIBRARY_SYMLINK_TREE_FLAVOR) .addFlavors(platform) .build(); } /** @return the {@link Path} to use for the symlink tree of headers. */ public static Path getSharedLibrarySymlinkTreePath(BuildTarget target, Flavor platform) { return BuildTargets.getGenPath(createSharedLibrarySymlinkTreeTarget(target, platform), "%s"); } /** * Build a {@link HeaderSymlinkTree} of all the shared libraries found via the top-level rule's * transitive dependencies. */ public static SymlinkTree createSharedLibrarySymlinkTree( TargetGraph targetGraph, BuildRuleParams params, SourcePathResolver pathResolver, CxxPlatform cxxPlatform, Predicate<Object> traverse) { BuildTarget symlinkTreeTarget = createSharedLibrarySymlinkTreeTarget(params.getBuildTarget(), cxxPlatform.getFlavor()); Path symlinkTreeRoot = getSharedLibrarySymlinkTreePath(params.getBuildTarget(), cxxPlatform.getFlavor()); ImmutableSortedMap<String, SourcePath> libraries = NativeLinkables.getTransitiveSharedLibraries( targetGraph, cxxPlatform, params.getDeps(), Linker.LinkableDepType.SHARED, traverse); ImmutableMap.Builder<Path, SourcePath> links = ImmutableMap.builder(); for (Map.Entry<String, SourcePath> ent : libraries.entrySet()) { links.put(Paths.get(ent.getKey()), ent.getValue()); } try { return new SymlinkTree( params.copyWithChanges( symlinkTreeTarget, Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of()), Suppliers.ofInstance(ImmutableSortedSet.<BuildRule>of())), pathResolver, symlinkTreeRoot, links.build()); } catch (SymlinkTree.InvalidSymlinkTreeException e) { throw new RuntimeException(e.getMessage()); } } }
public class AppleBundleDescription implements Description<AppleBundleDescription.Arg>, Flavored, ImplicitDepsInferringDescription<AppleBundleDescription.Arg> { public static final BuildRuleType TYPE = BuildRuleType.of("apple_bundle"); private static final Flavor WATCH = ImmutableFlavor.of("watch"); private static final ImmutableSet<Flavor> SUPPORTED_LIBRARY_FLAVORS = ImmutableSet.of(CxxDescriptionEnhancer.STATIC_FLAVOR, CxxDescriptionEnhancer.SHARED_FLAVOR); private final AppleBinaryDescription appleBinaryDescription; private final AppleLibraryDescription appleLibraryDescription; private final FlavorDomain<CxxPlatform> cxxPlatformFlavorDomain; private final ImmutableMap<Flavor, AppleCxxPlatform> platformFlavorsToAppleCxxPlatforms; private final CxxPlatform defaultCxxPlatform; private final CodeSignIdentityStore codeSignIdentityStore; private final ProvisioningProfileStore provisioningProfileStore; public AppleBundleDescription( AppleBinaryDescription appleBinaryDescription, AppleLibraryDescription appleLibraryDescription, FlavorDomain<CxxPlatform> cxxPlatformFlavorDomain, Map<Flavor, AppleCxxPlatform> platformFlavorsToAppleCxxPlatforms, CxxPlatform defaultCxxPlatform, CodeSignIdentityStore codeSignIdentityStore, ProvisioningProfileStore provisioningProfileStore) { this.appleBinaryDescription = appleBinaryDescription; this.appleLibraryDescription = appleLibraryDescription; this.cxxPlatformFlavorDomain = cxxPlatformFlavorDomain; this.platformFlavorsToAppleCxxPlatforms = ImmutableMap.copyOf(platformFlavorsToAppleCxxPlatforms); this.defaultCxxPlatform = defaultCxxPlatform; this.codeSignIdentityStore = codeSignIdentityStore; this.provisioningProfileStore = provisioningProfileStore; } @Override public BuildRuleType getBuildRuleType() { return TYPE; } @Override public Arg createUnpopulatedConstructorArg() { return new Arg(); } @Override public boolean hasFlavors(final ImmutableSet<Flavor> flavors) { if (appleLibraryDescription.hasFlavors(flavors)) { return true; } ImmutableSet.Builder<Flavor> flavorBuilder = ImmutableSet.builder(); for (Flavor flavor : flavors) { if (flavor.equals(ReactNativeFlavors.DO_NOT_BUNDLE)) { continue; } flavorBuilder.add(flavor); } return appleBinaryDescription.hasFlavors(flavorBuilder.build()); } /** Only works with thin binaries. */ private CxxPlatform getCxxPlatformForBuildTarget(BuildTarget target) { CxxPlatform cxxPlatform; try { cxxPlatform = cxxPlatformFlavorDomain.getValue(target.getFlavors()).or(defaultCxxPlatform); } catch (FlavorDomainException e) { throw new HumanReadableException(e, "%s: %s", target, e.getMessage()); } return cxxPlatform; } private AppleCxxPlatform getAppleCxxPlatformForBuildTarget(BuildTarget target) { Optional<FatBinaryInfo> fatBinaryInfo = FatBinaryInfo.create(platformFlavorsToAppleCxxPlatforms, target); AppleCxxPlatform appleCxxPlatform; if (fatBinaryInfo.isPresent()) { appleCxxPlatform = fatBinaryInfo.get().getRepresentativePlatform(); } else { CxxPlatform cxxPlatform = getCxxPlatformForBuildTarget(target); appleCxxPlatform = platformFlavorsToAppleCxxPlatforms.get(cxxPlatform.getFlavor()); if (appleCxxPlatform == null) { throw new HumanReadableException( "%s: Apple bundle requires an Apple platform, found '%s'", target, cxxPlatform.getFlavor().getName()); } } return appleCxxPlatform; } @Override public <A extends Arg> AppleBundle createBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, A args) { AppleCxxPlatform appleCxxPlatform = getAppleCxxPlatformForBuildTarget(params.getBuildTarget()); AppleBundleDestinations destinations = AppleBundleDestinations.platformDestinations( appleCxxPlatform.getAppleSdk().getApplePlatform()); ImmutableSet.Builder<SourcePath> bundleDirsBuilder = ImmutableSet.builder(); ImmutableSet.Builder<SourcePath> dirsContainingResourceDirsBuilder = ImmutableSet.builder(); ImmutableSet.Builder<SourcePath> bundleFilesBuilder = ImmutableSet.builder(); ImmutableSet.Builder<SourcePath> bundleVariantFilesBuilder = ImmutableSet.builder(); AppleResources.collectResourceDirsAndFiles( targetGraph, Preconditions.checkNotNull(targetGraph.get(params.getBuildTarget())), bundleDirsBuilder, dirsContainingResourceDirsBuilder, bundleFilesBuilder, bundleVariantFilesBuilder); ImmutableSet<SourcePath> bundleDirs = bundleDirsBuilder.build(); ImmutableSet<SourcePath> dirsContainingResourceDirs = dirsContainingResourceDirsBuilder.build(); ImmutableSet<SourcePath> bundleFiles = bundleFilesBuilder.build(); ImmutableSet<SourcePath> bundleVariantFiles = bundleVariantFilesBuilder.build(); SourcePathResolver sourcePathResolver = new SourcePathResolver(resolver); Optional<AppleAssetCatalog> assetCatalog = AppleDescriptions.createBuildRuleForTransitiveAssetCatalogDependencies( targetGraph, params, sourcePathResolver, appleCxxPlatform.getAppleSdk().getApplePlatform(), appleCxxPlatform.getActool()); // TODO(user): Sort through the changes needed to make project generation work with // binary being optional. BuildRule flavoredBinaryRule = getFlavoredBinaryRule(targetGraph, params, resolver, args); BuildRuleParams bundleParamsWithFlavoredBinaryDep = getBundleParamsWithUpdatedDeps( params, args.binary, ImmutableSet.<BuildRule>builder() .add(flavoredBinaryRule) .addAll(assetCatalog.asSet()) .addAll( BuildRules.toBuildRulesFor( params.getBuildTarget(), resolver, SourcePaths.filterBuildTargetSourcePaths( Iterables.concat( bundleFiles, bundleDirs, dirsContainingResourceDirs, bundleVariantFiles)))) .build()); ImmutableMap<SourcePath, String> extensionBundlePaths = collectFirstLevelAppleDependencyBundles(params.getDeps(), destinations); return new AppleBundle( bundleParamsWithFlavoredBinaryDep, sourcePathResolver, args.extension, args.productName, args.infoPlist, args.infoPlistSubstitutions.get(), Optional.of(flavoredBinaryRule), destinations, bundleDirs, bundleFiles, dirsContainingResourceDirs, extensionBundlePaths, Optional.of(bundleVariantFiles), appleCxxPlatform.getIbtool(), appleCxxPlatform.getDsymutil(), appleCxxPlatform.getCxxPlatform().getStrip(), assetCatalog, args.getTests(), appleCxxPlatform.getAppleSdk(), codeSignIdentityStore, provisioningProfileStore, AppleBundle.DebugInfoFormat.DSYM); } private <A extends Arg> BuildRule getFlavoredBinaryRule( TargetGraph targetGraph, final BuildRuleParams params, final BuildRuleResolver resolver, A args) { // Cxx targets must have one Platform Flavor set otherwise nothing gets compiled. ImmutableSet<Flavor> flavors = params .getBuildTarget() .withoutFlavors(ImmutableSet.of(ReactNativeFlavors.DO_NOT_BUNDLE)) .getFlavors(); if (!cxxPlatformFlavorDomain.containsAnyOf(flavors)) { flavors = new ImmutableSet.Builder<Flavor>() .addAll(flavors) .add(defaultCxxPlatform.getFlavor()) .build(); } final TargetNode<?> binaryTargetNode = Preconditions.checkNotNull(targetGraph.get(args.binary)); // If the binary target of the AppleBundle is an AppleLibrary then the build flavor // must be specified. if (binaryTargetNode.getDescription() instanceof AppleLibraryDescription && (Sets.intersection( SUPPORTED_LIBRARY_FLAVORS, binaryTargetNode.getBuildTarget().getFlavors()) .size() != 1)) { throw new HumanReadableException( "AppleExtension bundle [%s] must have exactly one of these flavors: [%s].", binaryTargetNode.getBuildTarget().toString(), Joiner.on(", ").join(SUPPORTED_LIBRARY_FLAVORS)); } BuildRuleParams binaryRuleParams = new BuildRuleParams( args.binary, Suppliers.ofInstance( BuildRules.toBuildRulesFor( params.getBuildTarget(), resolver, binaryTargetNode.getDeclaredDeps())), Suppliers.ofInstance( BuildRules.toBuildRulesFor( params.getBuildTarget(), resolver, binaryTargetNode.getExtraDeps())), params.getProjectFilesystem(), params.getCellRoots(), params.getRuleKeyBuilderFactory()); return CxxDescriptionEnhancer.requireBuildRule( targetGraph, binaryRuleParams, resolver, flavors.toArray(new Flavor[0])); } private static BuildRuleParams getBundleParamsWithUpdatedDeps( final BuildRuleParams params, final BuildTarget originalBinaryTarget, final Set<BuildRule> newDeps) { // Remove the unflavored binary rule and add the flavored one instead. final Predicate<BuildRule> notOriginalBinaryRule = Predicates.not(BuildRules.isBuildRuleWithTarget(originalBinaryTarget)); return params.copyWithDeps( Suppliers.ofInstance( FluentIterable.from(params.getDeclaredDeps().get()) .filter(notOriginalBinaryRule) .append(newDeps) .toSortedSet(Ordering.natural())), Suppliers.ofInstance( FluentIterable.from(params.getExtraDeps().get()) .filter(notOriginalBinaryRule) .toSortedSet(Ordering.natural()))); } private ImmutableMap<SourcePath, String> collectFirstLevelAppleDependencyBundles( ImmutableSortedSet<BuildRule> deps, AppleBundleDestinations destinations) { ImmutableMap.Builder<SourcePath, String> extensionBundlePaths = ImmutableMap.builder(); // We only care about the direct layer of dependencies. ExtensionBundles inside ExtensionBundles // do not get pulled in to the top-level Bundle. for (BuildRule rule : deps) { if (rule instanceof AppleBundle) { AppleBundle appleBundle = (AppleBundle) rule; if (AppleBundleExtension.APPEX.toFileExtension().equals(appleBundle.getExtension()) || AppleBundleExtension.APP.toFileExtension().equals(appleBundle.getExtension())) { Path outputPath = Preconditions.checkNotNull( appleBundle.getPathToOutput(), "Path cannot be null for AppleBundle [%s].", appleBundle); SourcePath sourcePath = new BuildTargetSourcePath(appleBundle.getBuildTarget(), outputPath); Path destinationPath; String platformName = appleBundle.getPlatformName(); if ((platformName.equals(ApplePlatform.Name.WATCHOS) || platformName.equals(ApplePlatform.Name.WATCHSIMULATOR)) && appleBundle.getExtension().equals(AppleBundleExtension.APP.toFileExtension())) { destinationPath = destinations.getWatchAppPath(); } else { destinationPath = destinations.getPlugInsPath(); } extensionBundlePaths.put(sourcePath, destinationPath.toString()); } } } return extensionBundlePaths.build(); } /** Propagate the bundle's platform flavors to its dependents. */ @Override public ImmutableSet<BuildTarget> findDepsForTargetFromConstructorArgs( BuildTarget buildTarget, Function<Optional<String>, Path> cellRoots, AppleBundleDescription.Arg constructorArg) { if (!constructorArg.deps.isPresent()) { return ImmutableSet.of(); } if (!cxxPlatformFlavorDomain.containsAnyOf(buildTarget.getFlavors())) { buildTarget = BuildTarget.builder(buildTarget) .addAllFlavors(ImmutableSet.of(defaultCxxPlatform.getFlavor())) .build(); } Optional<FatBinaryInfo> fatBinaryInfo = FatBinaryInfo.create(platformFlavorsToAppleCxxPlatforms, buildTarget); CxxPlatform cxxPlatform; if (fatBinaryInfo.isPresent()) { AppleCxxPlatform appleCxxPlatform = fatBinaryInfo.get().getRepresentativePlatform(); cxxPlatform = appleCxxPlatform.getCxxPlatform(); } else { cxxPlatform = getCxxPlatformForBuildTarget(buildTarget); } String platformName = cxxPlatform.getFlavor().getName(); final Flavor actualWatchFlavor; if (ApplePlatform.isSimulator(platformName)) { actualWatchFlavor = ImmutableFlavor.builder().name("watchsimulator-i386").build(); } else if (platformName.startsWith(ApplePlatform.Name.IPHONEOS) || platformName.startsWith(ApplePlatform.Name.WATCHOS)) { actualWatchFlavor = ImmutableFlavor.builder().name("watchos-armv7k").build(); } else { actualWatchFlavor = ImmutableFlavor.builder().name(platformName).build(); } FluentIterable<BuildTarget> depsExcludingBinary = FluentIterable.from(constructorArg.deps.get()) .filter(Predicates.not(Predicates.equalTo(constructorArg.binary))); FluentIterable<BuildTarget> targetsWithPlatformFlavors = depsExcludingBinary.filter(BuildTargets.containsFlavors(cxxPlatformFlavorDomain)); FluentIterable<BuildTarget> targetsWithoutPlatformFlavors = depsExcludingBinary.filter( Predicates.not(BuildTargets.containsFlavors(cxxPlatformFlavorDomain))); FluentIterable<BuildTarget> watchTargets = targetsWithoutPlatformFlavors .filter(BuildTargets.containsFlavor(WATCH)) .transform( new Function<BuildTarget, BuildTarget>() { @Override public BuildTarget apply(BuildTarget input) { return BuildTarget.builder(input.withoutFlavors(ImmutableSet.of(WATCH))) .addFlavors(actualWatchFlavor) .build(); } }); targetsWithoutPlatformFlavors = targetsWithoutPlatformFlavors.filter(Predicates.not(BuildTargets.containsFlavor(WATCH))); return ImmutableSet.<BuildTarget>builder() .addAll(targetsWithPlatformFlavors) .addAll(watchTargets) .addAll( BuildTargets.propagateFlavorDomains( buildTarget, ImmutableSet.<FlavorDomain<?>>of(cxxPlatformFlavorDomain), targetsWithoutPlatformFlavors)) .build(); } @SuppressFieldNotInitialized public static class Arg implements HasAppleBundleFields, HasTests { public Either<AppleBundleExtension, String> extension; public BuildTarget binary; public SourcePath infoPlist; public Optional<ImmutableMap<String, String>> infoPlistSubstitutions; @Hint(isDep = false) public Optional<ImmutableSortedSet<BuildTarget>> deps; @Hint(isDep = false) public Optional<ImmutableSortedSet<BuildTarget>> tests; public Optional<String> xcodeProductType; public Optional<String> productName; @Override public Either<AppleBundleExtension, String> getExtension() { return extension; } @Override public SourcePath getInfoPlist() { return infoPlist; } @Override public ImmutableSortedSet<BuildTarget> getTests() { return tests.get(); } @Override public Optional<String> getXcodeProductType() { return xcodeProductType; } } }
private void runCombinedTest( CxxPreprocessMode strategy, ImmutableList<String> expectedArguments) { BuildTarget testBuildTarget = BuildTarget.builder(BuildTargetFactory.newInstance("//foo:baz")) .addAllFlavors(ImmutableSet.of(CxxCompilationDatabase.COMPILATION_DATABASE)) .build(); final String root = "/Users/user/src"; final Path fakeRoot = Paths.get(root); ProjectFilesystem filesystem = new FakeProjectFilesystem(fakeRoot); BuildRuleParams testBuildRuleParams = new FakeBuildRuleParamsBuilder(testBuildTarget).setProjectFilesystem(filesystem).build(); BuildRuleResolver testBuildRuleResolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); SourcePathResolver testSourcePathResolver = new SourcePathResolver(testBuildRuleResolver); BuildTarget preprocessTarget = BuildTarget.builder(testBuildRuleParams.getBuildTarget().getUnflavoredBuildTarget()) .addFlavors(ImmutableFlavor.of("preprocess-test.cpp")) .build(); BuildTarget compileTarget = BuildTarget.builder(testBuildRuleParams.getBuildTarget().getUnflavoredBuildTarget()) .addFlavors(ImmutableFlavor.of("compile-test.cpp")) .build(); PreprocessorFlags preprocessorFlags = PreprocessorFlags.builder() .addSystemIncludePaths(filesystem.resolve("foo/bar"), filesystem.resolve("test")) .build(); ImmutableSortedSet.Builder<CxxPreprocessAndCompile> rules = ImmutableSortedSet.naturalOrder(); BuildRuleParams compileBuildRuleParams; switch (strategy) { case SEPARATE: CxxPreprocessAndCompile preprocessRule = CxxPreprocessAndCompile.preprocess( new FakeBuildRuleParamsBuilder(preprocessTarget) .setProjectFilesystem(filesystem) .build(), testSourcePathResolver, new PreprocessorDelegate( testSourcePathResolver, CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER, CxxPlatformUtils.DEFAULT_CONFIG.getHeaderVerification(), filesystem.getRootPath(), new DefaultPreprocessor(new HashedFileTool(Paths.get("compiler"))), preprocessorFlags, new RuleKeyAppendableFunction<FrameworkPath, Path>() { @Override public void appendToRuleKey(RuleKeyObjectSink sink) { // Do nothing. } @Override public Path apply(FrameworkPath input) { throw new UnsupportedOperationException("should not be called"); } }, ImmutableList.<CxxHeaders>of()), new CompilerDelegate( testSourcePathResolver, CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER, new GccCompiler(new HashedFileTool(Paths.get("compiler"))), CxxToolFlags.of()), Paths.get("test.ii"), new FakeSourcePath(filesystem, "test.cpp"), CxxSource.Type.CXX, CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER); rules.add(preprocessRule); compileBuildRuleParams = new FakeBuildRuleParamsBuilder(compileTarget) .setProjectFilesystem(filesystem) .setDeclaredDeps(ImmutableSortedSet.<BuildRule>of(preprocessRule)) .build(); rules.add( CxxPreprocessAndCompile.compile( compileBuildRuleParams, testSourcePathResolver, new CompilerDelegate( testSourcePathResolver, CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER, new GccCompiler(new HashedFileTool(Paths.get("compiler"))), CxxToolFlags.of()), Paths.get("test.o"), new FakeSourcePath(filesystem, "test.ii"), CxxSource.Type.CXX_CPP_OUTPUT, CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER)); break; case COMBINED: case PIPED: compileBuildRuleParams = new FakeBuildRuleParamsBuilder(compileTarget).setProjectFilesystem(filesystem).build(); rules.add( CxxPreprocessAndCompile.preprocessAndCompile( compileBuildRuleParams, testSourcePathResolver, new PreprocessorDelegate( testSourcePathResolver, CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER, CxxPlatformUtils.DEFAULT_CONFIG.getHeaderVerification(), filesystem.getRootPath(), new DefaultPreprocessor(new HashedFileTool(Paths.get("preprocessor"))), preprocessorFlags, new RuleKeyAppendableFunction<FrameworkPath, Path>() { @Override public void appendToRuleKey(RuleKeyObjectSink sink) { // Do nothing. } @Override public Path apply(FrameworkPath input) { throw new UnsupportedOperationException("should not be called"); } }, ImmutableList.<CxxHeaders>of()), new CompilerDelegate( testSourcePathResolver, CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER, new GccCompiler(new HashedFileTool(Paths.get("compiler"))), CxxToolFlags.of()), Paths.get("test.o"), new FakeSourcePath(filesystem, "test.cpp"), CxxSource.Type.CXX, Optional.<PrecompiledHeaderReference>absent(), CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER, strategy)); break; default: throw new RuntimeException("Invalid strategy"); } HeaderSymlinkTree privateSymlinkTree = CxxDescriptionEnhancer.createHeaderSymlinkTree( testBuildRuleParams, testBuildRuleResolver, testSourcePathResolver, CxxPlatformUtils.DEFAULT_PLATFORM, ImmutableMap.<Path, SourcePath>of(), HeaderVisibility.PRIVATE); HeaderSymlinkTree exportedSymlinkTree = CxxDescriptionEnhancer.createHeaderSymlinkTree( testBuildRuleParams, testBuildRuleResolver, testSourcePathResolver, CxxPlatformUtils.DEFAULT_PLATFORM, ImmutableMap.<Path, SourcePath>of(), HeaderVisibility.PUBLIC); CxxCompilationDatabase compilationDatabase = CxxCompilationDatabase.createCompilationDatabase( testBuildRuleParams, testSourcePathResolver, strategy, rules.build(), ImmutableSortedSet.of(privateSymlinkTree, exportedSymlinkTree)); assertThat( compilationDatabase.getRuntimeDeps(), Matchers.<BuildRule>contains(exportedSymlinkTree, privateSymlinkTree)); assertEquals( "getPathToOutput() should be a function of the build target.", BuildTargets.getGenPath(filesystem, testBuildTarget, "__%s.json"), compilationDatabase.getPathToOutput()); BuildContext buildContext = FakeBuildContext.NOOP_CONTEXT; BuildableContext buildableContext = new FakeBuildableContext(); List<Step> buildSteps = compilationDatabase.getPostBuildSteps(buildContext, buildableContext); assertEquals(2, buildSteps.size()); assertTrue(buildSteps.get(0) instanceof MkdirStep); assertTrue(buildSteps.get(1) instanceof CxxCompilationDatabase.GenerateCompilationCommandsJson); CxxCompilationDatabase.GenerateCompilationCommandsJson step = (CxxCompilationDatabase.GenerateCompilationCommandsJson) buildSteps.get(1); Iterable<CxxCompilationDatabaseEntry> observedEntries = step.createEntries(); Iterable<CxxCompilationDatabaseEntry> expectedEntries = ImmutableList.of( CxxCompilationDatabaseEntry.of(root, root + "/test.cpp", expectedArguments)); MoreAsserts.assertIterablesEquals(expectedEntries, observedEntries); }