// 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.Joiner; 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.events.Event; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.events.NullEventHandler; import com.google.devtools.build.lib.events.StoredEventHandler; import com.google.devtools.build.lib.packages.GlobCache.BadGlobException; import com.google.devtools.build.lib.packages.License.DistributionType; import com.google.devtools.build.lib.packages.Preprocessor.AstAfterPreprocessing; import com.google.devtools.build.lib.packages.RuleFactory.BuildLangTypedAttributeValuesMap; import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature; import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature.Param; import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; import com.google.devtools.build.lib.syntax.AssignmentStatement; 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.NoSuchVariableException; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.Expression; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.FunctionSignature; import com.google.devtools.build.lib.syntax.GlobList; import com.google.devtools.build.lib.syntax.Identifier; 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.SkylarkList; import com.google.devtools.build.lib.syntax.SkylarkList.MutableList; import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; 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.util.Pair; import com.google.devtools.build.lib.util.Preconditions; 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.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; 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.LegacyBuilder 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.LegacyBuilder pkgBuilder, Location location, T value) throws EvalException; } /** Interface for evaluating globs during package loading. */ public static interface Globber { /** An opaque token for fetching the result of a glob computation. */ abstract static class Token {} /** * Asynchronously starts the given glob computation and returns a token for fetching the * result. */ Token runAsync(List includes, List excludes, boolean excludeDirs) throws BadGlobException; /** Fetches the result of a previously started glob computation. */ List fetch(Token token) throws IOException, InterruptedException; /** Should be called when the globber is about to be discarded due to an interrupt. */ void onInterrupt(); /** Should be called when the globber is no longer needed. */ void onCompletion(); /** Returns all the glob computations requested before {@link #onCompletion} was called. */ Set> getGlobPatterns(); } /** * 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.LegacyBuilder pkgBuilder, Location location, List

{@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 cc_library, by wrapping around the usual library " + "and also defining a headers target.", mandatoryNamedOnly = { @Param(name = "name", type = String.class, doc = "The name of the rule."), // Both parameter below are lists of label designators @Param(name = "environments", type = SkylarkList.class, generic1 = Object.class, doc = "A list of Labels for the environments to be grouped, from the same package."), @Param(name = "defaults", type = SkylarkList.class, generic1 = Object.class, 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

" + "
restricted
Requires mandatory source distribution.
" + "
reciprocal
Allows usage of software freely in " + "unmodified form. Any modifications must be made freely available.
" + "
notice
Original or modified third-party software may be " + "shipped without danger nor encumbering other sources. All of the licenses in this " + "category do, however, have an \"original Copyright notice\" or " + "\"advertising clause\", wherein any external distributions must include the notice " + "or clause specified in the license.
" + "
permissive
Code that is under a license but does not " + "require a notice.
" + "
unencumbered
Public domain, free for any use.
" + "
", mandatoryPositionals = { @Param(name = "license_strings", type = SkylarkList.class, generic1 = String.class, doc = "A list of strings, the names of the licenses used.")}, documented = false, useLocation = true) private static final BuiltinFunction.Factory newLicensesFunction = new BuiltinFunction.Factory("licenses") { public BuiltinFunction create(final PackageContext context) { return new BuiltinFunction("licenses", this) { public Runtime.NoneType invoke(SkylarkList licensesList, Location loc) { try { License license = BuildType.LICENSE.convert(licensesList, "'licenses' operand"); context.pkgBuilder.setDefaultLicense(license); } catch (ConversionException e) { context.eventHandler.handle(Event.error(loc, e.getMessage())); context.pkgBuilder.setContainsErrors(); } return Runtime.NONE; } }; } }; /** * Returns a function-value implementing "distribs" in the specified package * context. */ // TODO(bazel-team): Remove in favor of package.distribs. // TODO(bazel-team): Remove all these new*Function-s and/or have static functions // that consult the context dynamically via getContext(env, ast) since we have that, // and share the functions with the native package... which requires unifying the List types. @SkylarkSignature(name = "distribs", returnType = Runtime.NoneType.class, doc = "Declare the distribution(s) for the code in the current package.", mandatoryPositionals = { @Param(name = "distribution_strings", type = Object.class, doc = "The distributions.")}, documented = false, useLocation = true) private static final BuiltinFunction.Factory newDistribsFunction = new BuiltinFunction.Factory("distribs") { public BuiltinFunction create(final PackageContext context) { return new BuiltinFunction("distribs", this) { public Runtime.NoneType invoke(Object object, Location loc) { try { Set distribs = BuildType.DISTRIBUTIONS.convert(object, "'distribs' operand"); context.pkgBuilder.setDefaultDistribs(distribs); } catch (ConversionException e) { context.eventHandler.handle(Event.error(loc, e.getMessage())); context.pkgBuilder.setContainsErrors(); } return Runtime.NONE; } }; } }; @SkylarkSignature(name = "package_group", returnType = Runtime.NoneType.class, doc = "Declare a set of files as exported", mandatoryNamedOnly = { @Param(name = "name", type = String.class, doc = "The name of the rule.")}, optionalNamedOnly = { @Param(name = "packages", type = SkylarkList.class, generic1 = String.class, defaultValue = "[]", doc = "A list of Strings specifying the packages grouped."), // java list or list of label designators: Label or String @Param(name = "includes", type = SkylarkList.class, generic1 = Object.class, defaultValue = "[]", doc = "A list of Label specifiers for the files to include.")}, documented = false, useAst = true, useEnvironment = true) private static final BuiltinFunction.Factory newPackageGroupFunction = new BuiltinFunction.Factory("package_group") { public BuiltinFunction create() { return new BuiltinFunction("package_group", this) { public Runtime.NoneType invoke(String name, SkylarkList packages, SkylarkList includes, FuncallExpression ast, Environment env) throws EvalException, ConversionException { return callPackageFunction(name, packages, includes, ast, env); } }; } }; @Nullable static Map callGetRuleFunction( String name, FuncallExpression ast, Environment env) throws EvalException, ConversionException { PackageContext context = getContext(env, ast); Target target = context.pkgBuilder.getTarget(name); return targetDict(target); } @Nullable private static Map targetDict(Target target) throws NotRepresentableException { if (target == null && !(target instanceof Rule)) { return null; } Map values = new TreeMap<>(); Rule rule = (Rule) target; AttributeContainer cont = rule.getAttributeContainer(); for (Attribute attr : rule.getAttributes()) { if (!Character.isAlphabetic(attr.getName().charAt(0))) { continue; } if (attr.getName().equals("distribs")) { // attribute distribs: cannot represent type class java.util.Collections$SingletonSet // in Skylark: [INTERNAL]. continue; } try { Object val = skylarkifyValue(cont.getAttr(attr.getName()), target.getPackage()); if (val == null) { continue; } values.put(attr.getName(), val); } catch (NotRepresentableException e) { throw new NotRepresentableException( String.format( "target %s, attribute %s: %s", target.getName(), attr.getName(), e.getMessage())); } } values.put("name", rule.getName()); values.put("kind", rule.getRuleClass()); return values; } static class NotRepresentableException extends EvalException { NotRepresentableException(String msg) { super(null, msg); } }; /** * Converts back to type that will work in BUILD and skylark, * such as string instead of label, SkylarkList instead of List, * Returns null if we don't want to export the value. * *

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 new Integer(-1); case YES: return new Integer(1); case NO: return new Integer(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 Map callGetRulesFunction(FuncallExpression ast, Environment env) throws EvalException { PackageContext context = getContext(env, ast); Collection targets = context.pkgBuilder.getTargets(); // Sort by name. Map> rules = new TreeMap<>(); for (Target t : targets) { if (t instanceof Rule) { Map m = targetDict(t); Preconditions.checkNotNull(m); rules.put(t.getName(), m); } } return rules; } static Runtime.NoneType callPackageFunction(String name, Object packagesO, Object includesO, FuncallExpression ast, Environment env) throws EvalException, ConversionException { PackageContext context = getContext(env, ast); List packages = Type.STRING_LIST.convert( packagesO, "'package_group.packages argument'"); List