Migrate PackageFactory callable methods to be defined with SkylarkCallable Also consolidate the multiple Starlark API definitions for various Starlark functions (for example, glob()) This is not a no-op change. In consolidating the definition of glob(), the method signatures of native.glob() and glob() are made consistent; this makes the signature of glob() effectively more lenient. Notably: 1. all parameters are allowed positionally as well as by keyword 2. there are default values for all parameters, meaning that `glob()` (no arguments) is allowed RELNOTES: None. PiperOrigin-RevId: 277809204
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java index a32f773..6bc8f63 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java +++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -15,14 +15,12 @@ package com.google.devtools.build.lib.packages; import com.google.common.annotations.VisibleForTesting; -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.ImmutableSet; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelConstants; -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; @@ -36,17 +34,11 @@ 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.RuleClass.Builder.RuleClassType; -import com.google.devtools.build.lib.packages.RuleClass.Builder.ThirdPartyLicenseExistencePolicy; import com.google.devtools.build.lib.packages.RuleFactory.BuildLangTypedAttributeValuesMap; -import com.google.devtools.build.lib.packages.Type.ConversionException; -import com.google.devtools.build.lib.skylarkinterface.Param; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; -import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature; -import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; import com.google.devtools.build.lib.syntax.Argument; import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.BuiltinFunction; -import com.google.devtools.build.lib.syntax.CallUtils; import com.google.devtools.build.lib.syntax.ClassObject; import com.google.devtools.build.lib.syntax.DefStatement; import com.google.devtools.build.lib.syntax.EvalException; @@ -65,9 +57,6 @@ import com.google.devtools.build.lib.syntax.NodeVisitor; import com.google.devtools.build.lib.syntax.ParserInput; 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.SkylarkSignatureProcessor; import com.google.devtools.build.lib.syntax.SkylarkUtils; import com.google.devtools.build.lib.syntax.SkylarkUtils.Phase; @@ -86,13 +75,11 @@ 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.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeMap; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; @@ -488,703 +475,12 @@ 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, - named = true, - 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."), - @Param( - name = "allow_empty", - type = Boolean.class, - defaultValue = "unbound", - positional = false, - named = true, - doc = - "Whether we allow glob patterns to match nothing. If `allow_empty` is False, each" - + " individual include pattern must match something and also the final" - + " result must be non-empty (after the matches of the `exclude` patterns are" - + " excluded).") - }, - documented = false, - useLocation = true, - useStarlarkThread = true) - private static final BuiltinFunction globFunction = - new BuiltinFunction("glob") { - public SkylarkList<String> invoke( - SkylarkList<?> include, // <String> - SkylarkList<?> exclude, // <String> - Integer excludeDirectories, - Object allowEmpty, - Location loc, - StarlarkThread thread) - throws EvalException, InterruptedException { - return callGlob( - getContext(thread, loc), - include, - exclude, - excludeDirectories != 0, - allowEmpty, - loc, - thread); - } - }; - - static SkylarkList<String> callGlob( - @Nullable PackageContext originalContext, - SkylarkList<?> include, // <String> - SkylarkList<?> exclude, // <String> - boolean excludeDirs, - Object allowEmptyArgument, - Location loc, - StarlarkThread thread) - throws EvalException, ConversionException, InterruptedException { - // Skylark build extensions need to get the PackageContext from the StarlarkThread; - // async glob functions cannot do the same because the StarlarkThread is not thread safe. - PackageContext context; - if (originalContext == null) { - context = getContext(thread, loc); - } else { - context = originalContext; - } - - List<String> includes = Type.STRING_LIST.convert(include, "'glob' argument"); - List<String> excludes = Type.STRING_LIST.convert(exclude, "'glob' argument"); - - List<String> matches; - boolean allowEmpty; - if (allowEmptyArgument == Runtime.UNBOUND) { - allowEmpty = !thread.getSemantics().incompatibleDisallowEmptyGlob(); - } else if (allowEmptyArgument instanceof Boolean) { - allowEmpty = (Boolean) allowEmptyArgument; - } else { - throw new EvalException( - loc, "expected boolean for argument `allow_empty`, got `" + allowEmptyArgument + "`"); - } - - try { - Globber.Token globToken = - context.globber.runAsync(includes, excludes, excludeDirs, allowEmpty); - 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(loc, errorMessage)); - context.pkgBuilder.setIOExceptionAndMessage(e, errorMessage); - matches = ImmutableList.of(); - } catch (BadGlobException e) { - throw new EvalException(loc, e.getMessage()); - } - - return MutableList.copyOf(thread, matches); - } - - /** - * Returns a dictionary representing the attributes of a previously defined target, or `None` if - * the target does not exist. - * - * @param name name of the rule. - */ - @SkylarkSignature( - name = "existing_rule", - doc = - "Returns a dictionary representing the attributes of a previously defined target, or " - + "<code>None</code> if the target does not exist." - + "" - + "<p><i>Note: If possible, avoid using this function. It makes BUILD files brittle " - + "and order-dependent.</i>", - parameters = { - @Param(name = "name", type = String.class, doc = "The name of the target."), - }, - documented = false, - useLocation = true, - useStarlarkThread = true) - private static final BuiltinFunction existingRuleFunction = - new BuiltinFunction("existing_rule") { - public Object invoke(String name, Location loc, StarlarkThread thread) - throws EvalException { - return callExistingRule(name, loc, thread); - } - }; - - static Object callExistingRule(String name, Location loc, StarlarkThread thread) - throws EvalException { - PackageContext context = getContext(thread, loc); - Target target = context.pkgBuilder.getTarget(name); - SkylarkDict<String, Object> rule = targetDict(target, loc, thread); - return rule != null ? rule : Runtime.NONE; - } - - /** - * Returns a dictionary containing all the targets instantiated so far. The map key is the name of - * the target. The map value is equivalent to the `existing_rule` output for that target. - */ - @SkylarkSignature( - name = "existing_rules", - returnType = SkylarkDict.class, - doc = - "Returns a dictionary containing all the targets instantiated so far. The map key is the " - + "name of the target. The map value is equivalent to the <code>existing_rule</code> " - + "output for that target." - + "" - + "<p><i>Note: If possible, avoid using this function. It makes BUILD files brittle " - + "and order-dependent.</i>", - useLocation = true, - useStarlarkThread = true) - private static final BuiltinFunction existingRulesFunction = - new BuiltinFunction("existing_rules") { - public SkylarkDict<String, SkylarkDict<String, Object>> invoke( - Location loc, StarlarkThread thread) throws EvalException { - return callExistingRules(loc, thread); - } - }; - - static SkylarkDict<String, SkylarkDict<String, Object>> callExistingRules( - Location loc, StarlarkThread thread) throws EvalException { - PackageContext context = getContext(thread, loc); - Collection<Target> targets = context.pkgBuilder.getTargets(); - SkylarkDict<String, SkylarkDict<String, Object>> rules = SkylarkDict.of(thread); - for (Target t : targets) { - if (t instanceof Rule) { - SkylarkDict<String, Object> rule = targetDict(t, loc, thread); - Preconditions.checkNotNull(rule); - rules.put(t.getName(), rule, loc, thread); - } - } - - return rules; - } - - /** - * Function value implementing "environment_group" in the specified package context. Syntax is as - * follows: - * - * <pre>{@code - * environment_group( - * name = "sample_group", - * environments = [":env1", ":env2", ...], - * defaults = [":env1", ...] - * ) - * }</pre> - * - * <p>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, - useStarlarkThread = true) - private static final BuiltinFunction environmentGroupFunction = - new BuiltinFunction("environment_group") { - public Runtime.NoneType invoke( - String name, - SkylarkList<?> environmentsList, // <Label> - SkylarkList<?> defaultsList, // <Label> - Location loc, - StarlarkThread thread) - throws EvalException { - PackageContext context = getContext(thread, loc); - List<Label> environments = - BuildType.LABEL_LIST.convert( - environmentsList, - "'environment_group argument'", - context.pkgBuilder.getBuildFileLabel()); - List<Label> defaults = - BuildType.LABEL_LIST.convert( - defaultsList, - "'environment_group argument'", - context.pkgBuilder.getBuildFileLabel()); - - if (environments.isEmpty()) { - throw new EvalException( - location, "environment group " + name + " must contain at least one environment"); - } - try { - context.pkgBuilder.addEnvironmentGroup( - name, environments, defaults, context.eventHandler, loc); - return Runtime.NONE; - } catch (LabelSyntaxException e) { - throw new EvalException( - loc, "environment group has invalid name: " + name + ": " + e.getMessage()); - } catch (Package.NameConflictException e) { - throw new EvalException(loc, e.getMessage()); - } - } - }; - - /** Returns a function-value implementing "exports_files" in the specified package context. */ - @SkylarkSignature( - name = "exports_files", - returnType = Runtime.NoneType.class, - doc = "Declare a set of files as exported", - parameters = { - @Param( - name = "srcs", - type = SkylarkList.class, - generic1 = String.class, - doc = "A list of strings, the names of the files to export."), - // TODO(blaze-team): make it possible to express a list of label designators, - // i.e. a java List or Skylark list of Label or String. - @Param( - name = "visibility", - type = SkylarkList.class, - noneable = true, - defaultValue = "None", - doc = - "A list of Labels specifying the visibility of the exported files " - + "(defaults to public)."), - @Param( - name = "licenses", - type = SkylarkList.class, - generic1 = String.class, - noneable = true, - defaultValue = "None", - doc = "A list of strings specifying the licenses used in the exported code.") - }, - documented = false, - useLocation = true, - useStarlarkThread = true) - private static final BuiltinFunction exportsFilesFunction = - new BuiltinFunction("exports_files") { - public Runtime.NoneType invoke( - SkylarkList<?> srcs, // <String> - Object visibility, - Object licenses, - Location loc, - StarlarkThread thread) - throws EvalException { - return callExportsFiles(srcs, visibility, licenses, loc, thread); - } - }; - - static Runtime.NoneType callExportsFiles( - Object srcs, Object visibilityO, Object licensesO, Location loc, StarlarkThread thread) - throws EvalException, ConversionException { - Package.Builder pkgBuilder = getContext(thread, loc).pkgBuilder; - List<String> files = Type.STRING_LIST.convert(srcs, "'exports_files' operand"); - - RuleVisibility visibility; - try { - visibility = EvalUtils.isNullOrNone(visibilityO) - ? ConstantRuleVisibility.PUBLIC - : getVisibility(pkgBuilder.getBuildFileLabel(), BuildType.LABEL_LIST.convert( - visibilityO, - "'exports_files' operand", - pkgBuilder.getBuildFileLabel())); - } catch (EvalException e) { - throw new EvalException(loc, e.getMessage()); - } - // TODO(bazel-team): is licenses plural or singular? - License license = BuildType.LICENSE.convertOptional(licensesO, "'exports_files' operand"); - - for (String file : files) { - String errorMessage = LabelValidator.validateTargetName(file); - if (errorMessage != null) { - throw new EvalException(loc, errorMessage); - } - try { - InputFile inputFile = pkgBuilder.createInputFile(file, loc); - if (inputFile.isVisibilitySpecified() - && inputFile.getVisibility() != visibility) { - throw new EvalException( - loc, - String.format( - "visibility for exported file '%s' declared twice", inputFile.getName())); - } - if (license != null && inputFile.isLicenseSpecified()) { - throw new EvalException( - loc, - String.format("licenses for exported file '%s' declared twice", inputFile.getName())); - } - - // See if we should check third-party licenses: first checking for any hard-coded policy, - // then falling back to user-settable flags. - boolean checkLicenses; - if (pkgBuilder.getThirdPartyLicenseExistencePolicy() - == ThirdPartyLicenseExistencePolicy.ALWAYS_CHECK) { - checkLicenses = true; - } else if (pkgBuilder.getThirdPartyLicenseExistencePolicy() - == ThirdPartyLicenseExistencePolicy.NEVER_CHECK) { - checkLicenses = false; - } else { - checkLicenses = !thread.getSemantics().incompatibleDisableThirdPartyLicenseChecking(); - } - - if (checkLicenses - && license == null - && !pkgBuilder.getDefaultLicense().isSpecified() - && RuleClass.isThirdPartyPackage(pkgBuilder.getPackageIdentifier())) { - throw new EvalException( - loc, - "third-party file '" - + inputFile.getName() - + "' lacks a license declaration " - + "with one of the following types: notice, reciprocal, permissive, " - + "restricted, unencumbered, by_exception_only"); - } - - pkgBuilder.setVisibilityAndLicense(inputFile, visibility, license); - } catch (Package.Builder.GeneratedLabelConflict e) { - throw new EvalException(loc, e.getMessage()); - } - } - return Runtime.NONE; - } - - /** - * Returns a function-value implementing "licenses" in the specified package context. - * TODO(bazel-team): Remove in favor of package.licenses. - */ - @SkylarkSignature( - name = "licenses", - returnType = Runtime.NoneType.class, - doc = "Declare the license(s) for the code in the current package.", - parameters = { - @Param( - name = "license_strings", - type = SkylarkList.class, - generic1 = String.class, - doc = "A list of strings, the names of the licenses used.") - }, - documented = false, - useStarlarkThread = true, - useLocation = true) - private static final BuiltinFunction licensesFunction = - new BuiltinFunction("licenses") { - public Runtime.NoneType invoke( - SkylarkList<?> licensesList, // list of license strings - Location loc, - StarlarkThread thread) - throws EvalException { - PackageContext context = getContext(thread, 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): share functions with the native package. Requires unifying the List types. - @SkylarkSignature( - name = "distribs", - returnType = Runtime.NoneType.class, - doc = "Declare the distribution(s) for the code in the current package.", - parameters = { - @Param(name = "distribution_strings", type = Object.class, doc = "The distributions.") - }, - documented = false, - useStarlarkThread = true, - useLocation = true) - private static final BuiltinFunction distribsFunction = - new BuiltinFunction("distribs") { - public Runtime.NoneType invoke(Object object, Location loc, StarlarkThread thread) - throws EvalException { - PackageContext context = getContext(thread, loc); - - try { - Set<DistributionType> 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.", - parameters = { - @Param( - name = "name", - type = String.class, - named = true, - positional = false, - doc = "The name of the rule."), - @Param( - name = "packages", - type = SkylarkList.class, - generic1 = String.class, - defaultValue = "[]", - named = true, - positional = false, - 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 = "[]", - named = true, - positional = false, - doc = "A list of Label specifiers for the files to include.") - }, - documented = false, - useLocation = true, - useStarlarkThread = true) - private static final BuiltinFunction packageGroupFunction = - new BuiltinFunction("package_group") { - public Runtime.NoneType invoke( - String name, - SkylarkList<?> packages, // <String> - SkylarkList<?> includes, // <Label> - Location loc, - StarlarkThread thread) - throws EvalException { - return callPackageFunction(name, packages, includes, loc, thread); - } - }; - - @Nullable - private static SkylarkDict<String, Object> targetDict( - Target target, Location loc, StarlarkThread thread) - throws NotRepresentableException, EvalException { - if (target == null || !(target instanceof Rule)) { - return null; - } - SkylarkDict<String, Object> values = SkylarkDict.<String, Object>of(thread); - - 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, loc, thread); - } catch (NotRepresentableException e) { - throw new NotRepresentableException( - String.format( - "target %s, attribute %s: %s", target.getName(), attr.getName(), e.getMessage())); - } - } - - values.put("name", rule.getName(), loc, thread); - values.put("kind", rule.getRuleClass(), loc, thread); - 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. - * - * <p>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<Object> 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<Object, Object> 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 License) { - // License is deprecated as a Starlark type, so omit this type from Starlark values - // to avoid exposing these objects, even though they are technically SkylarkValue. - return null; - } - - if (val instanceof SkylarkValue) { - return val; - } - - 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 Starlark", val, val.getClass())); - } - - static Runtime.NoneType callPackageFunction( - String name, Object packagesO, Object includesO, Location loc, StarlarkThread thread) - throws EvalException, ConversionException { - PackageContext context = getContext(thread, loc); - - List<String> packages = Type.STRING_LIST.convert( - packagesO, "'package_group.packages argument'"); - List<Label> includes = BuildType.LABEL_LIST.convert(includesO, - "'package_group.includes argument'", context.pkgBuilder.getBuildFileLabel()); - - try { - context.pkgBuilder.addPackageGroup(name, packages, includes, context.eventHandler, loc); - return Runtime.NONE; - } catch (LabelSyntaxException e) { - throw new EvalException( - loc, "package group has invalid name: " + name + ": " + e.getMessage()); - } catch (Package.NameConflictException e) { - throw new EvalException(loc, e.getMessage()); - } - } - public static RuleVisibility getVisibility(Label ruleLabel, List<Label> original) throws EvalException { RuleVisibility result; @@ -1198,10 +494,8 @@ return result; } - /** - * Returns a function-value implementing "package" in the specified package - * context. - */ + /** Returns a function-value implementing "package" in the specified package context. */ + // TODO(cparsons): Migrate this function to be defined with @SkylarkCallable. private static BaseFunction newPackageFunction( final ImmutableMap<String, PackageArgument<?>> packageArguments) { // Flatten the map of argument name of PackageArgument specifier in two co-indexed arrays: @@ -1606,11 +900,7 @@ */ private ClassObject newNativeModule() { ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); - SkylarkNativeModule nativeModuleInstance = new SkylarkNativeModule(); - for (String nativeFunction : CallUtils.getMethodNames(SkylarkNativeModule.class)) { - builder.put( - nativeFunction, CallUtils.getBuiltinCallable(nativeModuleInstance, nativeFunction)); - } + builder.putAll(SkylarkNativeModule.BINDINGS_FOR_BUILD_FILES); builder.putAll(ruleFunctions); builder.put("package", newPackageFunction(packageArguments)); for (EnvironmentExtension extension : environmentExtensions) { @@ -1622,34 +912,10 @@ } private void populateEnvironment(ImmutableMap.Builder<String, Object> env) { - // TODO(bazel-team): remove the naked functions that are redundant with the nativeModule, - // or if not possible, at least make them straight copies from the native module variant. - // or better, use a common StarlarkThread.Frame for these common bindings - // (that shares a backing ImmutableMap for the bindings?) - Object packageNameFunction; - Object repositoryNameFunction; - try { - packageNameFunction = nativeModule.getValue("package_name"); - repositoryNameFunction = nativeModule.getValue("repository_name"); - } catch (EvalException exception) { - // This should not occur, as nativeModule.getValue should never throw an exception. - throw new IllegalStateException( - "error getting package_name or repository_name functions from the native module", - exception); - } - env.putAll(BazelLibrary.GLOBALS.getBindings()); - env.put("distribs", distribsFunction); - env.put("glob", globFunction); - env.put("licenses", licensesFunction); - env.put("exports_files", exportsFilesFunction); - env.put("package_group", packageGroupFunction); + env.putAll(StarlarkBuildLibrary.BINDINGS); + env.putAll(SkylarkNativeModule.BINDINGS_FOR_BUILD_FILES); env.put("package", newPackageFunction(packageArguments)); - env.put("package_name", packageNameFunction); - env.put("repository_name", repositoryNameFunction); - env.put("environment_group", environmentGroupFunction); - env.put("existing_rule", existingRuleFunction); - env.put("existing_rules", existingRulesFunction); env.putAll(ruleFunctions); for (EnvironmentExtension ext : environmentExtensions) {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java index ad5d8f4..f93f6f3 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java +++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
@@ -14,58 +14,124 @@ package com.google.devtools.build.lib.packages; +import static com.google.devtools.build.lib.packages.PackageFactory.getContext; + +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.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.Location; +import com.google.devtools.build.lib.packages.Globber.BadGlobException; +import com.google.devtools.build.lib.packages.PackageFactory.NotRepresentableException; +import com.google.devtools.build.lib.packages.PackageFactory.PackageContext; +import com.google.devtools.build.lib.packages.RuleClass.Builder.ThirdPartyLicenseExistencePolicy; import com.google.devtools.build.lib.packages.Type.ConversionException; import com.google.devtools.build.lib.skylarkbuildapi.SkylarkNativeModuleApi; +import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; +import com.google.devtools.build.lib.syntax.CallUtils; import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.EvalUtils; 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.SkylarkUtils; import com.google.devtools.build.lib.syntax.StarlarkThread; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import javax.annotation.Nullable; /** The Skylark native module. */ -// TODO(laurentlb): Some definitions are duplicated from PackageFactory. -// This class defines: -// native.existing_rule -// native.existing_rules -// native.exports_files -- also global -// native.glob -- also global -// native.package_group -- also global -// native.package_name -// native.repository_name -// -// PackageFactory also defines: -// distribs -- hidden? -// licenses -- hidden? -// package -- global -// environment_group -- hidden? +// TODO(cparsons): Move the definition of native.package() to this class. public class SkylarkNativeModule implements SkylarkNativeModuleApi { + /** + * This map contains all the (non-rule) functions of the native module (keyed by their symbol + * name). These native module bindings should be added (without the 'native' module namespace) to + * the global Starlark environment for BUILD files. + * + * <p>For example, the function "glob" is available under both a global symbol name {@code glob()} + * as well as under the native module namepsace {@code native.glob()}. An entry of this map is + * thus ("glob" : glob function). + */ + public static final ImmutableMap<String, Object> BINDINGS_FOR_BUILD_FILES = initializeBindings(); + + private static ImmutableMap<String, Object> initializeBindings() { + SkylarkNativeModule nativeModule = new SkylarkNativeModule(); + ImmutableMap.Builder<String, Object> bindings = ImmutableMap.builder(); + for (String methodName : CallUtils.getMethodNames(SkylarkNativeModule.class)) { + bindings.put(methodName, CallUtils.getBuiltinCallable(nativeModule, methodName)); + } + return bindings.build(); + } + @Override public SkylarkList<?> glob( SkylarkList<?> include, SkylarkList<?> exclude, - Integer excludeDirectories, - Object allowEmpty, + Integer excludeDirs, + Object allowEmptyArgument, Location loc, StarlarkThread thread) throws EvalException, ConversionException, InterruptedException { SkylarkUtils.checkLoadingPhase(thread, "native.glob", loc); + + PackageContext context = getContext(thread, loc); + + List<String> includes = Type.STRING_LIST.convert(include, "'glob' argument"); + List<String> excludes = Type.STRING_LIST.convert(exclude, "'glob' argument"); + + List<String> matches; + boolean allowEmpty; + if (allowEmptyArgument == Runtime.UNBOUND) { + allowEmpty = !thread.getSemantics().incompatibleDisallowEmptyGlob(); + } else if (allowEmptyArgument instanceof Boolean) { + allowEmpty = (Boolean) allowEmptyArgument; + } else { + throw new EvalException( + loc, "expected boolean for argument `allow_empty`, got `" + allowEmptyArgument + "`"); + } + try { - return PackageFactory.callGlob( - null, include, exclude, excludeDirectories != 0, allowEmpty, loc, thread); + Globber.Token globToken = + context.globber.runAsync(includes, excludes, excludeDirs != 0, allowEmpty); + 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(loc, errorMessage)); + context.pkgBuilder.setIOExceptionAndMessage(e, errorMessage); + matches = ImmutableList.of(); + } catch (BadGlobException e) { + throw new EvalException(loc, e.getMessage()); } catch (IllegalArgumentException e) { throw new EvalException(loc, "illegal argument in call to glob", e); } + + return MutableList.copyOf(thread, matches); } @Override public Object existingRule(String name, Location loc, StarlarkThread thread) throws EvalException, InterruptedException { SkylarkUtils.checkLoadingOrWorkspacePhase(thread, "native.existing_rule", loc); - return PackageFactory.callExistingRule(name, loc, thread); + PackageContext context = getContext(thread, loc); + Target target = context.pkgBuilder.getTarget(name); + SkylarkDict<String, Object> rule = targetDict(target, loc, thread); + return rule != null ? rule : Runtime.NONE; } /* @@ -76,27 +142,126 @@ public SkylarkDict<String, SkylarkDict<String, Object>> existingRules( Location loc, StarlarkThread thread) throws EvalException, InterruptedException { SkylarkUtils.checkLoadingOrWorkspacePhase(thread, "native.existing_rules", loc); - return PackageFactory.callExistingRules(loc, thread); + PackageContext context = getContext(thread, loc); + Collection<Target> targets = context.pkgBuilder.getTargets(); + SkylarkDict<String, SkylarkDict<String, Object>> rules = SkylarkDict.of(thread); + for (Target t : targets) { + if (t instanceof Rule) { + SkylarkDict<String, Object> rule = targetDict(t, loc, thread); + Preconditions.checkNotNull(rule); + rules.put(t.getName(), rule, loc, thread); + } + } + + return rules; } @Override public Runtime.NoneType packageGroup( String name, - SkylarkList<?> packages, - SkylarkList<?> includes, + SkylarkList<?> packagesO, + SkylarkList<?> includesO, Location loc, StarlarkThread thread) throws EvalException { SkylarkUtils.checkLoadingPhase(thread, "native.package_group", loc); - return PackageFactory.callPackageFunction(name, packages, includes, loc, thread); + PackageContext context = getContext(thread, loc); + + List<String> packages = + Type.STRING_LIST.convert(packagesO, "'package_group.packages argument'"); + List<Label> includes = + BuildType.LABEL_LIST.convert( + includesO, "'package_group.includes argument'", context.pkgBuilder.getBuildFileLabel()); + + try { + context.pkgBuilder.addPackageGroup(name, packages, includes, context.eventHandler, loc); + return Runtime.NONE; + } catch (LabelSyntaxException e) { + throw new EvalException( + loc, "package group has invalid name: " + name + ": " + e.getMessage()); + } catch (Package.NameConflictException e) { + throw new EvalException(loc, e.getMessage()); + } } @Override public Runtime.NoneType exportsFiles( - SkylarkList<?> srcs, Object visibility, Object licenses, Location loc, StarlarkThread thread) + SkylarkList<?> srcs, + Object visibilityO, + Object licensesO, + Location loc, + StarlarkThread thread) throws EvalException { SkylarkUtils.checkLoadingPhase(thread, "native.exports_files", loc); - return PackageFactory.callExportsFiles(srcs, visibility, licenses, loc, thread); + Package.Builder pkgBuilder = getContext(thread, loc).pkgBuilder; + List<String> files = Type.STRING_LIST.convert(srcs, "'exports_files' operand"); + + RuleVisibility visibility; + try { + visibility = + EvalUtils.isNullOrNone(visibilityO) + ? ConstantRuleVisibility.PUBLIC + : PackageFactory.getVisibility( + pkgBuilder.getBuildFileLabel(), + BuildType.LABEL_LIST.convert( + visibilityO, "'exports_files' operand", pkgBuilder.getBuildFileLabel())); + } catch (EvalException e) { + throw new EvalException(loc, e.getMessage()); + } + // TODO(bazel-team): is licenses plural or singular? + License license = BuildType.LICENSE.convertOptional(licensesO, "'exports_files' operand"); + + for (String file : files) { + String errorMessage = LabelValidator.validateTargetName(file); + if (errorMessage != null) { + throw new EvalException(loc, errorMessage); + } + try { + InputFile inputFile = pkgBuilder.createInputFile(file, loc); + if (inputFile.isVisibilitySpecified() && inputFile.getVisibility() != visibility) { + throw new EvalException( + loc, + String.format( + "visibility for exported file '%s' declared twice", inputFile.getName())); + } + if (license != null && inputFile.isLicenseSpecified()) { + throw new EvalException( + loc, + String.format("licenses for exported file '%s' declared twice", inputFile.getName())); + } + + // See if we should check third-party licenses: first checking for any hard-coded policy, + // then falling back to user-settable flags. + boolean checkLicenses; + if (pkgBuilder.getThirdPartyLicenseExistencePolicy() + == ThirdPartyLicenseExistencePolicy.ALWAYS_CHECK) { + checkLicenses = true; + } else if (pkgBuilder.getThirdPartyLicenseExistencePolicy() + == ThirdPartyLicenseExistencePolicy.NEVER_CHECK) { + checkLicenses = false; + } else { + checkLicenses = !thread.getSemantics().incompatibleDisableThirdPartyLicenseChecking(); + } + + if (checkLicenses + && license == null + && !pkgBuilder.getDefaultLicense().isSpecified() + && RuleClass.isThirdPartyPackage(pkgBuilder.getPackageIdentifier())) { + throw new EvalException( + loc, + "third-party file '" + + inputFile.getName() + + "' lacks a license declaration " + + "with one of the following types: notice, reciprocal, permissive, " + + "restricted, unencumbered, by_exception_only"); + } + + pkgBuilder.setVisibilityAndLicense(inputFile, visibility, license); + } catch (Package.Builder.GeneratedLabelConflict e) { + throw new EvalException(loc, e.getMessage()); + } + } + return Runtime.NONE; } @Override @@ -114,4 +279,152 @@ PackageFactory.getContext(thread, location).getBuilder().getPackageIdentifier(); return packageId.getRepository().toString(); } + + @Nullable + private static SkylarkDict<String, Object> targetDict( + Target target, Location loc, StarlarkThread thread) throws EvalException { + if (!(target instanceof Rule)) { + return null; + } + SkylarkDict<String, Object> values = SkylarkDict.<String, Object>of(thread); + + 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, loc, thread); + } catch (NotRepresentableException e) { + throw new NotRepresentableException( + String.format( + "target %s, attribute %s: %s", target.getName(), attr.getName(), e.getMessage())); + } + } + + values.put("name", rule.getName(), loc, thread); + values.put("kind", rule.getRuleClass(), loc, thread); + return values; + } + + /** + * 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. + * + * <p>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 -1; + case YES: + return 1; + case NO: + return 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<Object> 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<Object, Object> 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 License) { + // License is deprecated as a Starlark type, so omit this type from Starlark values + // to avoid exposing these objects, even though they are technically SkylarkValue. + return null; + } + + if (val instanceof SkylarkValue) { + return val; + } + + 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 Starlark", val, val.getClass())); + } }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkBuildLibrary.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkBuildLibrary.java new file mode 100644 index 0000000..26df081 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkBuildLibrary.java
@@ -0,0 +1,178 @@ +// Copyright 2019 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 static com.google.devtools.build.lib.packages.PackageFactory.getContext; + +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.cmdline.LabelSyntaxException; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.events.Location; +import com.google.devtools.build.lib.packages.License.DistributionType; +import com.google.devtools.build.lib.packages.PackageFactory.PackageContext; +import com.google.devtools.build.lib.packages.Type.ConversionException; +import com.google.devtools.build.lib.skylarkinterface.Param; +import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; +import com.google.devtools.build.lib.skylarkinterface.SkylarkGlobalLibrary; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.Runtime; +import com.google.devtools.build.lib.syntax.SkylarkList; +import com.google.devtools.build.lib.syntax.StarlarkThread; +import java.util.List; +import java.util.Set; + +/** A global library of Starlark functions which are available only when evaluating BUILD files. */ +@SkylarkGlobalLibrary() +class StarlarkBuildLibrary { + + /** + * Map of Starlark values (keyed by their symbol name) defined by this global library for use in + * the global Starlark environment for BUILD files. + */ + public static final ImmutableMap<String, Object> BINDINGS = initializeBindings(); + + private static ImmutableMap<String, Object> initializeBindings() { + ImmutableMap.Builder<String, Object> bindings = ImmutableMap.builder(); + Runtime.setupSkylarkLibrary(bindings, new StarlarkBuildLibrary()); + return bindings.build(); + } + + private StarlarkBuildLibrary() { + // Not instantiable outside this class. + } + + @SkylarkCallable( + name = "environment_group", + 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 + // Not documented by docgen, as this is only available in BUILD files. + // TODO(cparsons): Devise a solution to document BUILD functions. + documented = false, + useLocation = true, + useStarlarkThread = true) + public Runtime.NoneType environmentGroup( + String name, + SkylarkList<?> environmentsList, // <Label> + SkylarkList<?> defaultsList, // <Label> + Location loc, + StarlarkThread thread) + throws EvalException { + PackageContext context = getContext(thread, loc); + List<Label> environments = + BuildType.LABEL_LIST.convert( + environmentsList, + "'environment_group argument'", + context.pkgBuilder.getBuildFileLabel()); + List<Label> defaults = + BuildType.LABEL_LIST.convert( + defaultsList, "'environment_group argument'", context.pkgBuilder.getBuildFileLabel()); + + if (environments.isEmpty()) { + throw new EvalException( + loc, "environment group " + name + " must contain at least one environment"); + } + try { + context.pkgBuilder.addEnvironmentGroup( + name, environments, defaults, context.eventHandler, loc); + return Runtime.NONE; + } catch (LabelSyntaxException e) { + throw new EvalException( + loc, "environment group has invalid name: " + name + ": " + e.getMessage()); + } catch (Package.NameConflictException e) { + throw new EvalException(loc, e.getMessage()); + } + } + + @SkylarkCallable( + name = "licenses", + doc = "Declare the license(s) for the code in the current package.", + parameters = { + @Param( + name = "license_strings", + type = SkylarkList.class, + generic1 = String.class, + doc = "A list of strings, the names of the licenses used.") + }, + // Not documented by docgen, as this is only available in BUILD files. + // TODO(cparsons): Devise a solution to document BUILD functions. + documented = false, + useStarlarkThread = true, + useLocation = true) + public Runtime.NoneType invoke( + SkylarkList<?> licensesList, // list of license strings + Location loc, + StarlarkThread thread) + throws EvalException { + PackageContext context = getContext(thread, 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; + } + + @SkylarkCallable( + name = "distribs", + doc = "Declare the distribution(s) for the code in the current package.", + parameters = { + @Param(name = "distribution_strings", type = Object.class, doc = "The distributions.") + }, + // Not documented by docgen, as this is only available in BUILD files. + // TODO(cparsons): Devise a solution to document BUILD functions. + documented = false, + useStarlarkThread = true, + useLocation = true) + public Runtime.NoneType distribs(Object object, Location loc, StarlarkThread thread) + throws EvalException { + PackageContext context = getContext(thread, loc); + + try { + Set<DistributionType> 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; + } +}
diff --git a/src/test/java/com/google/devtools/build/lib/packages/PackageFactoryTest.java b/src/test/java/com/google/devtools/build/lib/packages/PackageFactoryTest.java index f6d68d0..eea1d64 100644 --- a/src/test/java/com/google/devtools/build/lib/packages/PackageFactoryTest.java +++ b/src/test/java/com/google/devtools/build/lib/packages/PackageFactoryTest.java
@@ -142,15 +142,16 @@ @Test public void testExportsFilesVisibilityMustBeSequence() throws Exception { expectEvalError( - "expected sequence or NoneType for 'visibility' while calling exports_files but got depset", + "expected value of type 'sequence or NoneType' for parameter 'visibility', " + + "for call to method exports_files", "exports_files(srcs=[], visibility=depset(['notice']))"); } @Test public void testExportsFilesLicensesMustBeSequence() throws Exception { expectEvalError( - "expected sequence of strings or NoneType for 'licenses' while calling exports_files but" - + " got depset", + "expected value of type 'sequence of strings or NoneType' for parameter 'licenses', " + + "for call to method exports_files", "exports_files(srcs=[], licenses=depset(['notice']))"); } @@ -642,33 +643,11 @@ } @Test - public void testInsufficientArgumentGlobErrors() throws Exception { - events.setFailFast(false); - assertGlobFails( - "glob()", - "insufficient arguments received by glob(include: sequence of strings, " - + "*, exclude: sequence of strings = [], exclude_directories: int = 1, " - + "allow_empty: bool = <unbound>) (got 0, expected at least 1)"); - } - - @Test - public void testGlobUnamedExclude() throws Exception { - events.setFailFast(false); - assertGlobFails( - "glob(['a'], ['b'])", - "too many (2) positional arguments in call to glob(include: sequence of strings, " - + "*, exclude: sequence of strings = [], exclude_directories: int = 1, " - + "allow_empty: bool = <unbound>)"); - } - - @Test public void testTooManyArgumentsGlobErrors() throws Exception { events.setFailFast(false); assertGlobFails( - "glob(1,2,3,4)", - "too many (4) positional arguments in call to glob(include: sequence of strings, " - + "*, exclude: sequence of strings = [], exclude_directories: int = 1, " - + "allow_empty: bool = <unbound>)"); + "glob(['incl'],['excl'],3,True,'extraarg')", + "expected no more than 4 positional arguments, but got 5, for call to method glob"); } @Test @@ -676,7 +655,8 @@ events.setFailFast(false); assertGlobFails( "glob(1, exclude=2)", - "expected sequence of strings for 'include' while calling glob but got int instead: 1"); + "expected value of type 'sequence of strings' for parameter 'include', " + + "for call to method glob"); } @Test @@ -839,7 +819,9 @@ @Test public void testPackageGroupNamedArguments() throws Exception { - expectEvalError("does not accept positional arguments", "package_group('skin')"); + expectEvalError( + "expected no more than 0 positional arguments, but got 1,", + "package_group('skin', name = 'x')"); } @Test @@ -1060,8 +1042,7 @@ @Test public void testIncompleteEnvironmentGroup() throws Exception { expectEvalError( - "missing mandatory named-only argument 'defaults' while calling " - + "environment_group(*, name: string, ", + "parameter 'defaults' has no default value, for call to function environment_group", "environment(name = 'foo')", "environment_group(name='group', environments = [':foo'])"); }