/* * Copyright 2015-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.go; import com.facebook.buck.file.WriteFile; import com.facebook.buck.graph.AbstractBreadthFirstThrowingTraversal; import com.facebook.buck.io.MorePaths; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.model.Flavor; import com.facebook.buck.model.InternalFlavor; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.BinaryBuildRule; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.SymlinkTree; import com.facebook.buck.rules.Tool; import com.facebook.buck.util.HumanReadableException; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Suppliers; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.io.Files; import com.google.common.io.Resources; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; abstract class GoDescriptors { private static final Logger LOG = Logger.get(GoDescriptors.class); private static final String TEST_MAIN_GEN_PATH = "com/facebook/buck/go/testmaingen.go"; public static final Flavor TRANSITIVE_LINKABLES_FLAVOR = InternalFlavor.of("transitive-linkables"); @SuppressWarnings("unchecked") public static ImmutableSet<GoLinkable> requireTransitiveGoLinkables( final BuildTarget sourceTarget, final BuildRuleResolver resolver, final GoPlatform platform, Iterable<BuildTarget> targets, boolean includeSelf) throws NoSuchBuildTargetException { FluentIterable<GoLinkable> linkables = FluentIterable.from(targets) .transformAndConcat( new Function<BuildTarget, ImmutableSet<GoLinkable>>() { @Override public ImmutableSet<GoLinkable> apply(BuildTarget input) { BuildTarget flavoredTarget = BuildTarget.builder(input) .addFlavors(platform.getFlavor(), TRANSITIVE_LINKABLES_FLAVOR) .build(); try { return resolver.requireMetadata(flavoredTarget, ImmutableSet.class).get(); } catch (NoSuchBuildTargetException ex) { throw new RuntimeException(ex); } } }); if (includeSelf) { Preconditions.checkArgument(sourceTarget.getFlavors().contains(TRANSITIVE_LINKABLES_FLAVOR)); linkables = linkables.append( requireGoLinkable( sourceTarget, resolver, platform, sourceTarget.withoutFlavors(TRANSITIVE_LINKABLES_FLAVOR))); } return linkables.toSet(); } static GoCompile createGoCompileRule( BuildRuleParams params, BuildRuleResolver resolver, GoBuckConfig goBuckConfig, Path packageName, ImmutableSet<SourcePath> srcs, List<String> compilerFlags, List<String> assemblerFlags, GoPlatform platform, Iterable<BuildTarget> deps) throws NoSuchBuildTargetException { SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); Preconditions.checkState(params.getBuildTarget().getFlavors().contains(platform.getFlavor())); ImmutableSet<GoLinkable> linkables = requireGoLinkables(params.getBuildTarget(), resolver, platform, deps); ImmutableList.Builder<BuildRule> linkableDepsBuilder = ImmutableList.builder(); for (GoLinkable linkable : linkables) { linkableDepsBuilder.addAll(linkable.getDeps(ruleFinder)); } ImmutableList<BuildRule> linkableDeps = linkableDepsBuilder.build(); BuildTarget target = createSymlinkTreeTarget(params.getBuildTarget()); SymlinkTree symlinkTree = makeSymlinkTree(params.withBuildTarget(target), pathResolver, ruleFinder, linkables); resolver.addToIndex(symlinkTree); LOG.verbose( "Symlink tree for compiling %s: %s", params.getBuildTarget(), symlinkTree.getLinks()); return new GoCompile( params .copyAppendingExtraDeps(linkableDeps) .copyAppendingExtraDeps(ImmutableList.of(symlinkTree)), symlinkTree, packageName, getPackageImportMap( goBuckConfig.getVendorPaths(), params.getBuildTarget().getBasePath(), FluentIterable.from(linkables) .transformAndConcat( new Function<GoLinkable, ImmutableSet<Path>>() { @Override public ImmutableSet<Path> apply(GoLinkable input) { return input.getGoLinkInput().keySet(); } })), ImmutableSet.copyOf(srcs), ImmutableList.copyOf(compilerFlags), goBuckConfig.getCompiler(), ImmutableList.copyOf(assemblerFlags), goBuckConfig.getAssemblerIncludeDirs(), goBuckConfig.getAssembler(), goBuckConfig.getPacker(), platform); } @VisibleForTesting static ImmutableMap<Path, Path> getPackageImportMap( ImmutableList<Path> globalVendorPaths, Path basePackagePath, Iterable<Path> packageNameIter) { Map<Path, Path> importMapBuilder = new HashMap<>(); ImmutableSortedSet<Path> packageNames = ImmutableSortedSet.copyOf(packageNameIter); ImmutableList.Builder<Path> vendorPathsBuilder = ImmutableList.builder(); vendorPathsBuilder.addAll(globalVendorPaths); Path prefix = Paths.get(""); for (Path component : FluentIterable.from(new Path[] {Paths.get("")}).append(basePackagePath)) { prefix = prefix.resolve(component); vendorPathsBuilder.add(prefix.resolve("vendor")); } for (Path vendorPrefix : vendorPathsBuilder.build()) { for (Path vendoredPackage : packageNames.tailSet(vendorPrefix)) { if (!vendoredPackage.startsWith(vendorPrefix)) { break; } importMapBuilder.put(MorePaths.relativize(vendorPrefix, vendoredPackage), vendoredPackage); } } return ImmutableMap.copyOf(importMapBuilder); } static GoBinary createGoBinaryRule( BuildRuleParams params, final BuildRuleResolver resolver, GoBuckConfig goBuckConfig, ImmutableSet<SourcePath> srcs, List<String> compilerFlags, List<String> assemblerFlags, List<String> linkerFlags, GoPlatform platform) throws NoSuchBuildTargetException { BuildTarget libraryTarget = params .getBuildTarget() .withAppendedFlavors(InternalFlavor.of("compile"), platform.getFlavor()); GoCompile library = GoDescriptors.createGoCompileRule( params.withBuildTarget(libraryTarget), resolver, goBuckConfig, Paths.get("main"), srcs, compilerFlags, assemblerFlags, platform, FluentIterable.from(params.getDeclaredDeps().get()) .transform(BuildRule::getBuildTarget)); resolver.addToIndex(library); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); BuildTarget target = createTransitiveSymlinkTreeTarget(params.getBuildTarget()); SymlinkTree symlinkTree = makeSymlinkTree( params.withBuildTarget(target), pathResolver, ruleFinder, requireTransitiveGoLinkables( params.getBuildTarget(), resolver, platform, FluentIterable.from(params.getDeclaredDeps().get()) .transform(BuildRule::getBuildTarget), /* includeSelf */ false)); resolver.addToIndex(symlinkTree); LOG.verbose("Symlink tree for linking of %s: %s", params.getBuildTarget(), symlinkTree); return new GoBinary( params.copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance( ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(ruleFinder.filterBuildRuleInputs(symlinkTree.getLinks().values())) .add(symlinkTree) .add(library) .build()), Suppliers.ofInstance(ImmutableSortedSet.of())), platform.getCxxPlatform().map(input -> input.getLd().resolve(resolver)), symlinkTree, library, goBuckConfig.getLinker(), ImmutableList.copyOf(linkerFlags), platform); } static Tool getTestMainGenerator( GoBuckConfig goBuckConfig, BuildRuleParams sourceParams, BuildRuleResolver resolver) throws NoSuchBuildTargetException { Optional<Tool> configTool = goBuckConfig.getGoTestMainGenerator(resolver); if (configTool.isPresent()) { return configTool.get(); } // TODO(mikekap): Make a single test main gen, rather than one per test. The generator itself // doesn't vary per test. BuildTarget generatorTarget = sourceParams.getBuildTarget().withFlavors(InternalFlavor.of("make-test-main-gen")); Optional<BuildRule> generator = resolver.getRuleOptional(generatorTarget); if (generator.isPresent()) { return ((BinaryBuildRule) generator.get()).getExecutableCommand(); } BuildTarget generatorSourceTarget = sourceParams .getBuildTarget() .withAppendedFlavors(InternalFlavor.of("test-main-gen-source")); WriteFile writeFile = resolver.addToIndex( new WriteFile( sourceParams .withBuildTarget(generatorSourceTarget) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(ImmutableSortedSet.of()), Suppliers.ofInstance(ImmutableSortedSet.of())), extractTestMainGenerator(), BuildTargets.getGenPath( sourceParams.getProjectFilesystem(), generatorSourceTarget, "%s/main.go"), /* executable */ false)); GoBinary binary = resolver.addToIndex( createGoBinaryRule( sourceParams .withBuildTarget(generatorTarget) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(ImmutableSortedSet.of()), Suppliers.ofInstance(ImmutableSortedSet.of(writeFile))), resolver, goBuckConfig, ImmutableSet.of(writeFile.getSourcePathToOutput()), ImmutableList.of(), ImmutableList.of(), ImmutableList.of(), goBuckConfig.getDefaultPlatform())); return binary.getExecutableCommand(); } private static String extractTestMainGenerator() { try { return Resources.toString(Resources.getResource(TEST_MAIN_GEN_PATH), Charsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); } } private static BuildTarget createSymlinkTreeTarget(BuildTarget source) { return BuildTarget.builder(source).addFlavors(InternalFlavor.of("symlink-tree")).build(); } private static BuildTarget createTransitiveSymlinkTreeTarget(BuildTarget source) { return BuildTarget.builder(source) .addFlavors(InternalFlavor.of("transitive-symlink-tree")) .build(); } private static GoLinkable requireGoLinkable( BuildTarget sourceRule, BuildRuleResolver resolver, GoPlatform platform, BuildTarget target) throws NoSuchBuildTargetException { Optional<GoLinkable> linkable = resolver.requireMetadata( BuildTarget.builder(target).addFlavors(platform.getFlavor()).build(), GoLinkable.class); if (!linkable.isPresent()) { throw new HumanReadableException( "%s (needed for %s) is not an instance of go_library!", target.getFullyQualifiedName(), sourceRule.getFullyQualifiedName()); } return linkable.get(); } private static ImmutableSet<GoLinkable> requireGoLinkables( final BuildTarget sourceTarget, final BuildRuleResolver resolver, final GoPlatform platform, Iterable<BuildTarget> targets) throws NoSuchBuildTargetException { final ImmutableSet.Builder<GoLinkable> linkables = ImmutableSet.builder(); new AbstractBreadthFirstThrowingTraversal<BuildTarget, NoSuchBuildTargetException>(targets) { @Override public Iterable<BuildTarget> visit(BuildTarget target) throws NoSuchBuildTargetException { GoLinkable linkable = requireGoLinkable(sourceTarget, resolver, platform, target); linkables.add(linkable); return linkable.getExportedDeps(); } }.start(); return linkables.build(); } private static SymlinkTree makeSymlinkTree( BuildRuleParams params, SourcePathResolver pathResolver, SourcePathRuleFinder ruleFinder, ImmutableSet<GoLinkable> linkables) { ImmutableMap.Builder<Path, SourcePath> treeMapBuilder = ImmutableMap.builder(); for (GoLinkable linkable : linkables) { for (Map.Entry<Path, SourcePath> linkInput : linkable.getGoLinkInput().entrySet()) { treeMapBuilder.put( getPathInSymlinkTree(pathResolver, linkInput.getKey(), linkInput.getValue()), linkInput.getValue()); } } ImmutableMap<Path, SourcePath> treeMap; try { treeMap = treeMapBuilder.build(); } catch (IllegalArgumentException ex) { throw new HumanReadableException( ex, "Multiple go targets have the same package name when compiling %s", params.getBuildTarget().getFullyQualifiedName()); } Path root = BuildTargets.getScratchPath( params.getProjectFilesystem(), params.getBuildTarget(), "__%s__tree"); return new SymlinkTree( params.getBuildTarget(), params.getProjectFilesystem(), root, treeMap, ruleFinder); } /** * @return the path in the symlink tree as used by the compiler. This is usually the package name * + '.a'. */ private static Path getPathInSymlinkTree( SourcePathResolver resolver, Path goPackageName, SourcePath ruleOutput) { Path output = resolver.getRelativePath(ruleOutput); String extension = Files.getFileExtension(output.toString()); return Paths.get(goPackageName.toString() + (extension.equals("") ? "" : "." + extension)); } }