/* * 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.cxx.CxxPlatform; import com.facebook.buck.cxx.CxxPlatforms; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.Flavor; import com.facebook.buck.model.Flavored; import com.facebook.buck.model.InternalFlavor; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.CellPathResolver; import com.facebook.buck.rules.CommonDescriptionArg; import com.facebook.buck.rules.Description; import com.facebook.buck.rules.HasDeclaredDeps; import com.facebook.buck.rules.HasSrcs; import com.facebook.buck.rules.ImplicitDepsInferringDescription; import com.facebook.buck.rules.MetadataProvidingDescription; import com.facebook.buck.rules.NoopBuildRule; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.rules.Tool; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.facebook.buck.versions.Version; import com.google.common.base.Preconditions; import com.google.common.base.Suppliers; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; 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 java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import org.immutables.value.Value; public class GoTestDescription implements Description<GoTestDescriptionArg>, Flavored, MetadataProvidingDescription<GoTestDescriptionArg>, ImplicitDepsInferringDescription<GoTestDescription.AbstractGoTestDescriptionArg> { private static final Flavor TEST_LIBRARY_FLAVOR = InternalFlavor.of("test-library"); private final GoBuckConfig goBuckConfig; private final Optional<Long> defaultTestRuleTimeoutMs; public GoTestDescription(GoBuckConfig goBuckConfig, Optional<Long> defaultTestRuleTimeoutMs) { this.goBuckConfig = goBuckConfig; this.defaultTestRuleTimeoutMs = defaultTestRuleTimeoutMs; } @Override public Class<GoTestDescriptionArg> getConstructorArgType() { return GoTestDescriptionArg.class; } @Override public boolean hasFlavors(ImmutableSet<Flavor> flavors) { return goBuckConfig.getPlatformFlavorDomain().containsAnyOf(flavors) || flavors.contains(TEST_LIBRARY_FLAVOR); } @Override public <U> Optional<U> createMetadata( BuildTarget buildTarget, final BuildRuleResolver resolver, GoTestDescriptionArg args, Optional<ImmutableMap<BuildTarget, Version>> selectedVersions, Class<U> metadataClass) throws NoSuchBuildTargetException { Optional<GoPlatform> platform = goBuckConfig.getPlatformFlavorDomain().getValue(buildTarget); if (metadataClass.isAssignableFrom(GoLinkable.class) && buildTarget.getFlavors().contains(TEST_LIBRARY_FLAVOR)) { Preconditions.checkState(platform.isPresent()); Path packageName = getGoPackageName(resolver, buildTarget, args); SourcePath output = resolver.requireRule(buildTarget).getSourcePathToOutput(); return Optional.of( metadataClass.cast( GoLinkable.builder().setGoLinkInput(ImmutableMap.of(packageName, output)).build())); } else if (buildTarget.getFlavors().contains(GoDescriptors.TRANSITIVE_LINKABLES_FLAVOR) && buildTarget.getFlavors().contains(TEST_LIBRARY_FLAVOR)) { Preconditions.checkState(platform.isPresent()); ImmutableSet<BuildTarget> deps; if (args.getLibrary().isPresent()) { GoLibraryDescriptionArg libraryArg = resolver.requireMetadata(args.getLibrary().get(), GoLibraryDescriptionArg.class).get(); deps = ImmutableSortedSet.<BuildTarget>naturalOrder() .addAll(args.getDeps()) .addAll(libraryArg.getDeps()) .build(); } else { deps = args.getDeps(); } return Optional.of( metadataClass.cast( GoDescriptors.requireTransitiveGoLinkables( buildTarget, resolver, platform.get(), deps, /* includeSelf */ true))); } else { return Optional.empty(); } } private GoTestMain requireTestMainGenRule( BuildRuleParams params, BuildRuleResolver resolver, ImmutableSet<SourcePath> srcs, Path packageName) throws NoSuchBuildTargetException { Tool testMainGenerator = GoDescriptors.getTestMainGenerator(goBuckConfig, params, resolver); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); GoTestMain generatedTestMain = new GoTestMain( params .withAppendedFlavor(InternalFlavor.of("test-main-src")) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance( ImmutableSortedSet.copyOf(testMainGenerator.getDeps(ruleFinder))), Suppliers.ofInstance(ImmutableSortedSet.of())), testMainGenerator, srcs, packageName); resolver.addToIndex(generatedTestMain); return generatedTestMain; } @Override public BuildRule createBuildRule( TargetGraph targetGraph, BuildRuleParams params, final BuildRuleResolver resolver, CellPathResolver cellRoots, GoTestDescriptionArg args) throws NoSuchBuildTargetException { GoPlatform platform = goBuckConfig .getPlatformFlavorDomain() .getValue(params.getBuildTarget()) .orElse(goBuckConfig.getDefaultPlatform()); if (params.getBuildTarget().getFlavors().contains(TEST_LIBRARY_FLAVOR)) { return createTestLibrary(params, resolver, args, platform); } GoBinary testMain = createTestMainRule(params, resolver, args, platform); resolver.addToIndex(testMain); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); return new GoTest( params.copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(ImmutableSortedSet.of(testMain)), Suppliers.ofInstance(ImmutableSortedSet.of())), ruleFinder, testMain, args.getLabels(), args.getContacts(), args.getTestRuleTimeoutMs().map(Optional::of).orElse(defaultTestRuleTimeoutMs), args.getRunTestSeparately(), args.getResources()); } private GoBinary createTestMainRule( BuildRuleParams params, final BuildRuleResolver resolver, GoTestDescriptionArg args, GoPlatform platform) throws NoSuchBuildTargetException { Path packageName = getGoPackageName(resolver, params.getBuildTarget(), args); BuildRuleParams testTargetParams = params.withAppendedFlavor(TEST_LIBRARY_FLAVOR); BuildRule testLibrary = new NoopBuildRule(testTargetParams); resolver.addToIndex(testLibrary); BuildRule generatedTestMain = requireTestMainGenRule(params, resolver, args.getSrcs(), packageName); GoBinary testMain = GoDescriptors.createGoBinaryRule( params .withAppendedFlavor(InternalFlavor.of("test-main")) .copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(ImmutableSortedSet.of(testLibrary)), Suppliers.ofInstance(ImmutableSortedSet.of(generatedTestMain))), resolver, goBuckConfig, ImmutableSet.of(generatedTestMain.getSourcePathToOutput()), args.getCompilerFlags(), args.getAssemblerFlags(), args.getLinkerFlags(), platform); resolver.addToIndex(testMain); return testMain; } private Path getGoPackageName( BuildRuleResolver resolver, BuildTarget target, GoTestDescriptionArg args) throws NoSuchBuildTargetException { target = target.withFlavors(); // remove flavors. if (args.getLibrary().isPresent()) { final Optional<GoLibraryDescriptionArg> libraryArg = resolver.requireMetadata(args.getLibrary().get(), GoLibraryDescriptionArg.class); if (!libraryArg.isPresent()) { throw new HumanReadableException( "Library specified in %s (%s) is not a go_library rule.", target, args.getLibrary().get()); } if (args.getPackageName().isPresent()) { throw new HumanReadableException( "Test target %s specifies both library and package_name - only one should be specified", target); } if (!libraryArg.get().getTests().contains(target)) { throw new HumanReadableException( "go internal test target %s is not listed in `tests` of library %s", target, args.getLibrary().get()); } return libraryArg .get() .getPackageName() .map(Paths::get) .orElse(goBuckConfig.getDefaultPackageName(args.getLibrary().get())); } else if (args.getPackageName().isPresent()) { return Paths.get(args.getPackageName().get()); } else { Path packageName = goBuckConfig.getDefaultPackageName(target); return packageName.resolveSibling(packageName.getFileName() + "_test"); } } private GoCompile createTestLibrary( BuildRuleParams params, final BuildRuleResolver resolver, GoTestDescriptionArg args, GoPlatform platform) throws NoSuchBuildTargetException { Path packageName = getGoPackageName(resolver, params.getBuildTarget(), args); GoCompile testLibrary; if (args.getLibrary().isPresent()) { // We should have already type-checked the arguments in the base rule. final GoLibraryDescriptionArg libraryArg = resolver.requireMetadata(args.getLibrary().get(), GoLibraryDescriptionArg.class).get(); final BuildRuleParams originalParams = params; BuildRuleParams testTargetParams = params.copyReplacingDeclaredAndExtraDeps( () -> ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(originalParams.getDeclaredDeps().get()) .addAll(resolver.getAllRules(libraryArg.getDeps())) .build(), () -> { SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); return ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(originalParams.getExtraDeps().get()) // Make sure to include dynamically generated sources as deps. .addAll(ruleFinder.filterBuildRuleInputs(libraryArg.getSrcs())) .build(); }); testLibrary = GoDescriptors.createGoCompileRule( testTargetParams, resolver, goBuckConfig, packageName, ImmutableSet.<SourcePath>builder() .addAll(libraryArg.getSrcs()) .addAll(args.getSrcs()) .build(), ImmutableList.<String>builder() .addAll(libraryArg.getCompilerFlags()) .addAll(args.getCompilerFlags()) .build(), ImmutableList.<String>builder() .addAll(libraryArg.getAssemblerFlags()) .addAll(args.getAssemblerFlags()) .build(), platform, FluentIterable.from(params.getDeclaredDeps().get()) .transform(BuildRule::getBuildTarget)); } else { testLibrary = GoDescriptors.createGoCompileRule( params, resolver, goBuckConfig, packageName, args.getSrcs(), args.getCompilerFlags(), args.getAssemblerFlags(), platform, FluentIterable.from(params.getDeclaredDeps().get()) .transform(BuildRule::getBuildTarget)); } return testLibrary; } @Override public void findDepsForTargetFromConstructorArgs( BuildTarget buildTarget, CellPathResolver cellRoots, AbstractGoTestDescriptionArg constructorArg, ImmutableCollection.Builder<BuildTarget> extraDepsBuilder, ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) { // Add the C/C++ linker parse time deps. GoPlatform goPlatform = goBuckConfig .getPlatformFlavorDomain() .getValue(buildTarget) .orElse(goBuckConfig.getDefaultPlatform()); Optional<CxxPlatform> cxxPlatform = goPlatform.getCxxPlatform(); if (cxxPlatform.isPresent()) { extraDepsBuilder.addAll(CxxPlatforms.getParseTimeDeps(cxxPlatform.get())); } } @BuckStyleImmutable @Value.Immutable interface AbstractGoTestDescriptionArg extends CommonDescriptionArg, HasDeclaredDeps, HasSrcs { Optional<BuildTarget> getLibrary(); Optional<String> getPackageName(); ImmutableList<String> getCompilerFlags(); ImmutableList<String> getAssemblerFlags(); ImmutableList<String> getLinkerFlags(); ImmutableSet<String> getContacts(); Optional<Long> getTestRuleTimeoutMs(); @Value.Default default boolean getRunTestSeparately() { return false; } @Value.NaturalOrder ImmutableSortedSet<SourcePath> getResources(); } }