// Copyright 2014 The Bazel Authors. All rights reserved. // // 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.google.devtools.build.lib.packages; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.LabelValidator; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.ExtendedEventHandler; import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.events.StoredEventHandler; import com.google.devtools.build.lib.packages.Globber.BadGlobException; import com.google.devtools.build.lib.packages.License.DistributionType; import com.google.devtools.build.lib.packages.RuleFactory.BuildLangTypedAttributeValuesMap; import com.google.devtools.build.lib.skylarkinterface.Param; import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature; import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.BuildFileAST; import com.google.devtools.build.lib.syntax.BuiltinFunction; import com.google.devtools.build.lib.syntax.ClassObject; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.Environment.Extension; import com.google.devtools.build.lib.syntax.Environment.Phase; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.FunctionSignature; import com.google.devtools.build.lib.syntax.Mutability; import com.google.devtools.build.lib.syntax.ParserInputSource; import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.syntax.SkylarkDict; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.syntax.SkylarkList.MutableList; import com.google.devtools.build.lib.syntax.SkylarkSemantics; import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; import com.google.devtools.build.lib.syntax.SkylarkUtils; import com.google.devtools.build.lib.syntax.Statement; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.syntax.Type.ConversionException; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.UnixGlob; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; import javax.annotation.Nullable; /** * The package factory is responsible for constructing Package instances * from a BUILD file's abstract syntax tree (AST). * *

A PackageFactory is a heavy-weight object; create them sparingly. * Typically only one is needed per client application. */ public final class PackageFactory { /** * An argument to the {@code package()} function. */ public abstract static class PackageArgument { private final String name; private final Type type; protected PackageArgument(String name, Type type) { this.name = name; this.type = type; } public String getName() { return name; } private void convertAndProcess( Package.Builder pkgBuilder, Location location, Object value) throws EvalException { T typedValue = type.convert(value, "'package' argument", pkgBuilder.getBuildFileLabel()); process(pkgBuilder, location, typedValue); } /** * Process an argument. * * @param pkgBuilder the package builder to be mutated * @param location the location of the {@code package} function for error reporting * @param value the value of the argument. Typically passed to {@link Type#convert} */ protected abstract void process( Package.Builder pkgBuilder, Location location, T value) throws EvalException; } /** * An extension to the global namespace of the BUILD language. */ // TODO(bazel-team): this is largely unrelated to syntax.Environment.Extension, // and should probably be renamed PackageFactory.RuntimeExtension, since really, // we're extending the Runtime with more classes. public interface EnvironmentExtension { /** * Update the global environment with the identifiers this extension contributes. */ void update(Environment environment); /** * Update the global environment of WORKSPACE files. */ void updateWorkspace(Environment environment); /** * Returns the extra functions needed to be added to the Skylark native module. */ ImmutableList nativeModuleFunctions(); /** * Returns the extra arguments to the {@code package()} statement. */ Iterable> getPackageArguments(); } private static class DefaultVisibility extends PackageArgument> { private DefaultVisibility() { super("default_visibility", BuildType.LABEL_LIST); } @Override protected void process(Package.Builder pkgBuilder, Location location, List

Only intended to be called by BlazeRuntime or {@link BuilderForTesting#build}. * *

Do not call this constructor directly in tests; please use * TestConstants#PACKAGE_FACTORY_BUILDER_FACTORY_FOR_TESTING instead. */ public PackageFactory( RuleClassProvider ruleClassProvider, Function attributeContainerFactory, Iterable environmentExtensions, String version, Package.Builder.Helper packageBuilderHelper) { this.ruleFactory = new RuleFactory(ruleClassProvider, attributeContainerFactory); this.ruleFunctions = buildRuleFunctions(ruleFactory); this.ruleClassProvider = ruleClassProvider; threadPool = new ThreadPoolExecutor( 100, Integer.MAX_VALUE, 15L, TimeUnit.SECONDS, new LinkedBlockingQueue(), new ThreadFactoryBuilder().setNameFormat("Legacy globber %d").setDaemon(true).build()); // Do not consume threads when not in use. threadPool.allowCoreThreadTimeOut(true); this.environmentExtensions = ImmutableList.copyOf(environmentExtensions); this.packageArguments = createPackageArguments(); this.nativeModule = newNativeModule(); this.workspaceNativeModule = WorkspaceFactory.newNativeModule(ruleClassProvider, version); this.packageBuilderHelper = packageBuilderHelper; } /** * Sets the syscalls cache used in globbing. */ public void setSyscalls(AtomicReference syscalls) { this.syscalls = Preconditions.checkNotNull(syscalls); } /** * Sets the max number of threads to use for globbing. */ public void setGlobbingThreads(int globbingThreads) { threadPool.setCorePoolSize(globbingThreads); } /** * Sets the number of directories to eagerly traverse on the first glob for a given package, in * order to warm the filesystem. -1 means do no eager traversal. See {@code * PackageCacheOptions#maxDirectoriesToEagerlyVisitInGlobbing}. */ public void setMaxDirectoriesToEagerlyVisitInGlobbing( int maxDirectoriesToEagerlyVisitInGlobbing) { this.maxDirectoriesToEagerlyVisitInGlobbing = maxDirectoriesToEagerlyVisitInGlobbing; } /** * Returns the immutable, unordered set of names of all the known rule * classes. */ public Set getRuleClassNames() { return ruleFactory.getRuleClassNames(); } /** * Returns the {@link RuleClass} for the specified rule class name. */ public RuleClass getRuleClass(String ruleClassName) { return ruleFactory.getRuleClass(ruleClassName); } /** * Returns the {@link RuleClassProvider} of this {@link PackageFactory}. */ public RuleClassProvider getRuleClassProvider() { return ruleClassProvider; } public ImmutableList getEnvironmentExtensions() { return environmentExtensions; } /** * Creates the list of arguments for the 'package' function. */ private ImmutableMap> createPackageArguments() { ImmutableList.Builder> arguments = ImmutableList.>builder() .add(new DefaultDeprecation()) .add(new DefaultDistribs()) .add(new DefaultLicenses()) .add(new DefaultTestOnly()) .add(new DefaultVisibility()) .add(new Features()) .add(new DefaultCompatibleWith()) .add(new DefaultRestrictedTo()); for (EnvironmentExtension extension : environmentExtensions) { arguments.addAll(extension.getPackageArguments()); } ImmutableMap.Builder> packageArguments = ImmutableMap.builder(); for (PackageArgument argument : arguments.build()) { packageArguments.put(argument.getName(), argument); } return packageArguments.build(); } /** * ************************************************************************** Environment function * factories. */ /** * Returns a function-value implementing "glob" in the specified package context. * * @param async if true, start globs in the background but don't block on their completion. Only * use this for heuristic preloading. */ @SkylarkSignature( name = "glob", objectType = Object.class, returnType = SkylarkList.class, doc = "Returns a list of files that match glob search pattern.", parameters = { @Param( name = "include", type = SkylarkList.class, generic1 = String.class, doc = "a list of strings specifying patterns of files to include." ), @Param( name = "exclude", type = SkylarkList.class, generic1 = String.class, defaultValue = "[]", positional = false, named = true, doc = "a list of strings specifying patterns of files to exclude." ), // TODO(bazel-team): migrate all existing code to use boolean instead? @Param( name = "exclude_directories", type = Integer.class, defaultValue = "1", positional = false, named = true, doc = "a integer that if non-zero indicates directories should not be matched." ) }, documented = false, useAst = true, useEnvironment = true ) private static final BuiltinFunction.Factory newGlobFunction = new BuiltinFunction.Factory("glob") { public BuiltinFunction create(final PackageContext originalContext) { return new BuiltinFunction("glob", this) { public SkylarkList invoke( SkylarkList include, SkylarkList exclude, Integer excludeDirectories, FuncallExpression ast, Environment env) throws EvalException, ConversionException, InterruptedException { return callGlob(originalContext, include, exclude, excludeDirectories != 0, ast, env); } }; } }; static SkylarkList callGlob( @Nullable PackageContext originalContext, Object include, Object exclude, boolean excludeDirs, FuncallExpression ast, Environment env) throws EvalException, ConversionException, InterruptedException { // Skylark build extensions need to get the PackageContext from the Environment; // async glob functions cannot do the same because the Environment is not thread safe. PackageContext context; if (originalContext == null) { context = getContext(env, ast.getLocation()); } else { context = originalContext; } List includes = Type.STRING_LIST.convert(include, "'glob' argument"); List excludes = Type.STRING_LIST.convert(exclude, "'glob' argument"); List matches; try { Globber.Token globToken = context.globber.runAsync(includes, excludes, excludeDirs); matches = context.globber.fetch(globToken); } catch (IOException e) { String errorMessage = String.format( "error globbing [%s]%s: %s", Joiner.on(", ").join(includes), excludes.isEmpty() ? "" : " - [" + Joiner.on(", ").join(excludes) + "]", e.getMessage()); context.eventHandler.handle(Event.error(ast.getLocation(), errorMessage)); context.pkgBuilder.setIOExceptionAndMessage(e, errorMessage); matches = ImmutableList.of(); } catch (BadGlobException e) { throw new EvalException(ast.getLocation(), e.getMessage()); } return MutableList.copyOf(env, matches); } /** * Returns a function value implementing "environment_group" in the specified package context. * Syntax is as follows: * *
{@code
   *   environment_group(
   *       name = "sample_group",
   *       environments = [":env1", ":env2", ...],
   *       defaults = [":env1", ...]
   *   )
   * }
* *

Where ":env1", "env2", ... are all environment rules declared in the same package. All * parameters are mandatory. */ @SkylarkSignature(name = "environment_group", returnType = Runtime.NoneType.class, doc = "Defines a set of related environments that can be tagged onto rules to prevent" + "incompatible rules from depending on each other.", parameters = { @Param(name = "name", type = String.class, positional = false, named = true, doc = "The name of the rule."), // Both parameter below are lists of label designators @Param(name = "environments", type = SkylarkList.class, generic1 = Object.class, positional = false, named = true, doc = "A list of Labels for the environments to be grouped, from the same package."), @Param(name = "defaults", type = SkylarkList.class, generic1 = Object.class, positional = false, named = true, doc = "A list of Labels.")}, // TODO(bazel-team): document what that is documented = false, useLocation = true) private static final BuiltinFunction.Factory newEnvironmentGroupFunction = new BuiltinFunction.Factory("environment_group") { public BuiltinFunction create(final PackageContext context) { return new BuiltinFunction("environment_group", this) { public Runtime.NoneType invoke( String name, SkylarkList environmentsList, SkylarkList defaultsList, Location loc) throws EvalException, ConversionException { List

All of the types returned are immutable. If we want, we can change this to * immutable in the future, but this is the safe choice for now. */ @Nullable private static Object skylarkifyValue(Object val, Package pkg) throws NotRepresentableException { // TODO(bazel-team): the location of this function is ad-hoc. Arguably, the conversion // from Java native types to Skylark types should be part of the Type class hierarchy, if (val == null) { return null; } if (val instanceof Boolean) { return val; } if (val instanceof Integer) { return val; } if (val instanceof String) { return val; } if (val instanceof TriState) { switch ((TriState) val) { case AUTO: return Integer.valueOf(-1); case YES: return Integer.valueOf(1); case NO: return Integer.valueOf(0); } } if (val instanceof Label) { Label l = (Label) val; if (l.getPackageName().equals(pkg.getName())) { return ":" + l.getName(); } return l.getCanonicalForm(); } if (val instanceof List) { List l = new ArrayList<>(); for (Object o : (List) val) { Object elt = skylarkifyValue(o, pkg); if (elt == null) { continue; } l.add(elt); } return SkylarkList.Tuple.copyOf(l); } if (val instanceof Map) { Map m = new TreeMap<>(); for (Map.Entry e : ((Map) val).entrySet()) { Object key = skylarkifyValue(e.getKey(), pkg); Object mapVal = skylarkifyValue(e.getValue(), pkg); if (key == null || mapVal == null) { continue; } m.put(key, mapVal); } return m; } if (val.getClass().isAnonymousClass()) { // Computed defaults. They will be represented as // "deprecation": com.google.devtools.build.lib.analysis.BaseRuleClasses$2@6960884a, // Filter them until we invent something more clever. return null; } if (val instanceof SkylarkValue) { return val; } if (val instanceof License) { // TODO(bazel-team): convert License.getLicenseTypes() to a list of strings. return null; } if (val instanceof BuildType.SelectorList) { // This is terrible: // 1) this value is opaque, and not a BUILD value, so it cannot be used in rule arguments // 2) its representation has a pointer address, so it breaks hermeticity. // // Even though this is clearly imperfect, we return this value because otherwise // native.rules() fails if there is any rule using a select() in the BUILD file. // // To remedy this, we should return a syntax.SelectorList. To do so, we have to // 1) recurse into the Selector contents of SelectorList, so those values are skylarkified too // 2) get the right Class value. We could probably get at that by looking at // ((SelectorList)val).getSelectors().first().getEntries().first().getClass(). return val; } // We are explicit about types we don't understand so we minimize changes to existing callers // if we add more types that we can represent. throw new NotRepresentableException( String.format( "cannot represent %s (%s) in skylark", val.toString(), val.getClass().toString())); } static SkylarkDict> callGetRulesFunction( FuncallExpression ast, Environment env) throws EvalException { PackageContext context = getContext(env, ast.getLocation()); Collection targets = context.pkgBuilder.getTargets(); Location loc = ast.getLocation(); SkylarkDict> rules = SkylarkDict.of(env); for (Target t : targets) { if (t instanceof Rule) { SkylarkDict m = targetDict(t, loc, env); Preconditions.checkNotNull(m); rules.put(t.getName(), m, loc, env); } } return rules; } static Runtime.NoneType callPackageFunction(String name, Object packagesO, Object includesO, FuncallExpression ast, Environment env) throws EvalException, ConversionException { PackageContext context = getContext(env, ast.getLocation()); List packages = Type.STRING_LIST.convert( packagesO, "'package_group.packages argument'"); List