aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java1272
1 files changed, 1272 insertions, 0 deletions
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
new file mode 100644
index 0000000000..abf63f9e3e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -0,0 +1,1272 @@
+// Copyright 2014 Google Inc. 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.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.LabelValidator;
+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.Type.ConversionException;
+import com.google.devtools.build.lib.syntax.AbstractFunction;
+import com.google.devtools.build.lib.syntax.AssignmentStatement;
+import com.google.devtools.build.lib.syntax.BuildFileAST;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Environment.NoSuchVariableException;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Expression;
+import com.google.devtools.build.lib.syntax.FuncallExpression;
+import com.google.devtools.build.lib.syntax.Function;
+import com.google.devtools.build.lib.syntax.GlobList;
+import com.google.devtools.build.lib.syntax.Ident;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.MixedModeFunction;
+import com.google.devtools.build.lib.syntax.ParserInputSource;
+import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
+import com.google.devtools.build.lib.syntax.Statement;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.UnixGlob;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+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).
+ *
+ * <p>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<T> {
+ private final String name;
+ private final Type<T> type;
+
+ protected PackageArgument(String name, Type<T> type) {
+ this.name = name;
+ this.type = type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ private void convertAndProcess(
+ Package.LegacyBuilder pkgBuilder, Location location, Object value)
+ throws EvalException, ConversionException {
+ 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<String> includes, List<String> excludes, boolean excludeDirs)
+ throws BadGlobException;
+
+ /** Fetches the result of a previously started glob computation. */
+ List<String> 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<Pair<String, Boolean>> getGlobPatterns();
+ }
+
+ /**
+ * An extension to the global namespace of the BUILD language.
+ */
+ public interface EnvironmentExtension {
+ /**
+ * Update the global environment with the identifiers this extension contributes.
+ */
+ void update(Environment environment, MakeEnvironment.Builder pkgMakeEnv,
+ Label buildFileLabel);
+
+ Iterable<PackageArgument<?>> getPackageArguments();
+ }
+
+ private static final int EXCLUDE_DIR_DEFAULT = 1;
+
+ private static class DefaultVisibility extends PackageArgument<List<Label>> {
+ private DefaultVisibility() {
+ super("default_visibility", Type.LABEL_LIST);
+ }
+
+ @Override
+ protected void process(Package.LegacyBuilder pkgBuilder, Location location,
+ List<Label> value) {
+ pkgBuilder.setDefaultVisibility(getVisibility(value));
+ }
+ }
+
+ private static class DefaultObsolete extends PackageArgument<Boolean> {
+ private DefaultObsolete() {
+ super("default_obsolete", Type.BOOLEAN);
+ }
+
+ @Override
+ protected void process(Package.LegacyBuilder pkgBuilder, Location location,
+ Boolean value) {
+ pkgBuilder.setDefaultObsolete(value);
+ }
+ }
+
+ private static class DefaultTestOnly extends PackageArgument<Boolean> {
+ private DefaultTestOnly() {
+ super("default_testonly", Type.BOOLEAN);
+ }
+
+ @Override
+ protected void process(Package.LegacyBuilder pkgBuilder, Location location,
+ Boolean value) {
+ pkgBuilder.setDefaultTestonly(value);
+ }
+ }
+
+ private static class DefaultDeprecation extends PackageArgument<String> {
+ private DefaultDeprecation() {
+ super("default_deprecation", Type.STRING);
+ }
+
+ @Override
+ protected void process(Package.LegacyBuilder pkgBuilder, Location location,
+ String value) {
+ pkgBuilder.setDefaultDeprecation(value);
+ }
+ }
+
+ private static class Features extends PackageArgument<List<String>> {
+ private Features() {
+ super("features", Type.STRING_LIST);
+ }
+
+ @Override
+ protected void process(Package.LegacyBuilder pkgBuilder, Location location,
+ List<String> value) {
+ pkgBuilder.addFeatures(value);
+ }
+ }
+
+ private static class DefaultLicenses extends PackageArgument<License> {
+ private DefaultLicenses() {
+ super("licenses", Type.LICENSE);
+ }
+
+ @Override
+ protected void process(Package.LegacyBuilder pkgBuilder, Location location,
+ License value) {
+ pkgBuilder.setDefaultLicense(value);
+ }
+ }
+
+ private static class DefaultDistribs extends PackageArgument<Set<DistributionType>> {
+ private DefaultDistribs() {
+ super("distribs", Type.DISTRIBUTIONS);
+ }
+
+ @Override
+ protected void process(Package.LegacyBuilder pkgBuilder, Location location,
+ Set<DistributionType> value) {
+ pkgBuilder.setDefaultDistribs(value);
+ }
+ }
+
+ /**
+ * Declares the package() attribute specifying the default value for
+ * {@link RuleClass#COMPATIBLE_ENVIRONMENT_ATTR} when not explicitly specified.
+ */
+ private static class DefaultCompatibleWith extends PackageArgument<List<Label>> {
+ private DefaultCompatibleWith() {
+ super(Package.DEFAULT_COMPATIBLE_WITH_ATTRIBUTE, Type.LABEL_LIST);
+ }
+
+ @Override
+ protected void process(Package.LegacyBuilder pkgBuilder, Location location,
+ List<Label> value) {
+ pkgBuilder.setDefaultCompatibleWith(value, Package.DEFAULT_COMPATIBLE_WITH_ATTRIBUTE,
+ location);
+ }
+ }
+
+ /**
+ * Declares the package() attribute specifying the default value for
+ * {@link RuleClass#RESTRICTED_ENVIRONMENT_ATTR} when not explicitly specified.
+ */
+ private static class DefaultRestrictedTo extends PackageArgument<List<Label>> {
+ private DefaultRestrictedTo() {
+ super(Package.DEFAULT_RESTRICTED_TO_ATTRIBUTE, Type.LABEL_LIST);
+ }
+
+ @Override
+ protected void process(Package.LegacyBuilder pkgBuilder, Location location,
+ List<Label> value) {
+ pkgBuilder.setDefaultRestrictedTo(value, Package.DEFAULT_RESTRICTED_TO_ATTRIBUTE, location);
+ }
+ }
+
+ public static final String PKG_CONTEXT = "$pkg_context";
+
+ /** {@link Globber} that uses the legacy GlobCache. */
+ public static class LegacyGlobber implements Globber {
+
+ private final GlobCache globCache;
+
+ public LegacyGlobber(GlobCache globCache) {
+ this.globCache = globCache;
+ }
+
+ private class Token extends Globber.Token {
+ public final List<String> includes;
+ public final List<String> excludes;
+ public final boolean excludeDirs;
+
+ public Token(List<String> includes, List<String> excludes, boolean excludeDirs) {
+ this.includes = includes;
+ this.excludes = excludes;
+ this.excludeDirs = excludeDirs;
+ }
+ }
+
+ @Override
+ public Set<Pair<String, Boolean>> getGlobPatterns() {
+ return globCache.getKeySet();
+ }
+
+ @Override
+ public Token runAsync(List<String> includes, List<String> excludes, boolean excludeDirs)
+ throws BadGlobException {
+ for (String pattern : Iterables.concat(includes, excludes)) {
+ globCache.getGlobAsync(pattern, excludeDirs);
+ }
+ return new Token(includes, excludes, excludeDirs);
+ }
+
+ @Override
+ public List<String> fetch(Globber.Token token) throws IOException, InterruptedException {
+ Token legacyToken = (Token) token;
+ try {
+ return globCache.glob(legacyToken.includes, legacyToken.excludes,
+ legacyToken.excludeDirs);
+ } catch (BadGlobException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void onInterrupt() {
+ globCache.cancelBackgroundTasks();
+ }
+
+ @Override
+ public void onCompletion() {
+ globCache.finishBackgroundTasks();
+ }
+ }
+
+ private static final Logger LOG = Logger.getLogger(PackageFactory.class.getName());
+
+ private final RuleFactory ruleFactory;
+ private final RuleClassProvider ruleClassProvider;
+ private final Environment globalEnv;
+
+ private AtomicReference<? extends UnixGlob.FilesystemCalls> syscalls;
+ private Preprocessor.Factory preprocessorFactory = Preprocessor.Factory.NullFactory.INSTANCE;
+
+ private final ThreadPoolExecutor threadPool;
+ private Map<String, String> platformSetRegexps;
+
+ private final ImmutableList<EnvironmentExtension> environmentExtensions;
+ private final ImmutableMap<String, PackageArgument<?>> packageArguments;
+
+ /**
+ * Constructs a {@code PackageFactory} instance with the given rule factory.
+ */
+ public PackageFactory(RuleClassProvider ruleClassProvider) {
+ this(ruleClassProvider, null, ImmutableList.<EnvironmentExtension>of());
+ }
+
+ @VisibleForTesting
+ public PackageFactory(RuleClassProvider ruleClassProvider,
+ EnvironmentExtension environmentExtensions) {
+ this(ruleClassProvider, null, ImmutableList.of(environmentExtensions));
+ }
+
+ /**
+ * Constructs a {@code PackageFactory} instance with a specific glob path translator
+ * and rule factory.
+ */
+ @VisibleForTesting
+ public PackageFactory(RuleClassProvider ruleClassProvider,
+ Map<String, String> platformSetRegexps,
+ Iterable<EnvironmentExtension> environmentExtensions) {
+ this.platformSetRegexps = platformSetRegexps;
+ this.ruleFactory = new RuleFactory(ruleClassProvider);
+ this.ruleClassProvider = ruleClassProvider;
+ globalEnv = newGlobalEnvironment();
+ threadPool = new ThreadPoolExecutor(100, 100, 3L, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>(),
+ new ThreadFactoryBuilder().setNameFormat("PackageFactory %d").build());
+ // Do not consume threads when not in use.
+ threadPool.allowCoreThreadTimeOut(true);
+ this.environmentExtensions = ImmutableList.copyOf(environmentExtensions);
+ this.packageArguments = createPackageArguments();
+ }
+
+ /**
+ * Sets the preprocessor used.
+ */
+ public void setPreprocessorFactory(Preprocessor.Factory preprocessorFactory) {
+ this.preprocessorFactory = preprocessorFactory;
+ }
+
+ /**
+ * Sets the syscalls cache used in globbing.
+ */
+ public void setSyscalls(AtomicReference<? extends UnixGlob.FilesystemCalls> syscalls) {
+ this.syscalls = Preconditions.checkNotNull(syscalls);
+ }
+
+ /**
+ * Returns the static environment initialized once and shared by all packages
+ * created by this factory. No updates occur to this environment once created.
+ */
+ @VisibleForTesting
+ public Environment getEnvironment() {
+ return globalEnv;
+ }
+
+ /**
+ * Returns the immutable, unordered set of names of all the known rule
+ * classes.
+ */
+ public Set<String> 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;
+ }
+
+ /**
+ * Creates the list of arguments for the 'package' function.
+ */
+ private ImmutableMap<String, PackageArgument<?>> createPackageArguments() {
+ ImmutableList.Builder<PackageArgument<?>> arguments =
+ ImmutableList.<PackageArgument<?>>builder()
+ .add(new DefaultDeprecation())
+ .add(new DefaultDistribs())
+ .add(new DefaultLicenses())
+ .add(new DefaultObsolete())
+ .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<String, PackageArgument<?>> 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.
+ */
+ private static Function newGlobFunction(
+ final PackageContext originalContext, final boolean async) {
+ List<String> params = ImmutableList.of("include", "exclude", "exclude_directories");
+ return new MixedModeFunction("glob", params, 1, false) {
+ @Override
+ public Object call(Object[] namedArguments, 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) {
+ Preconditions.checkArgument(!async);
+ try {
+ context = (PackageContext) env.lookup(PKG_CONTEXT);
+ } catch (NoSuchVariableException e) {
+ throw new EvalException(ast.getLocation(), e.getMessage());
+ }
+ } else {
+ context = originalContext;
+ }
+
+ List<String> includes = Type.STRING_LIST.convert(namedArguments[0], "'glob' argument");
+ List<String> excludes = namedArguments[1] == null
+ ? Collections.<String>emptyList()
+ : Type.STRING_LIST.convert(namedArguments[1], "'glob' argument");
+ int excludeDirs = namedArguments[2] == null
+ ? EXCLUDE_DIR_DEFAULT
+ : Type.INTEGER.convert(namedArguments[2], "'glob' argument");
+
+ if (async) {
+ try {
+ context.globber.runAsync(includes, excludes, excludeDirs != 0);
+ } catch (GlobCache.BadGlobException e) {
+ // Ignore: errors will appear during the actual evaluation of the package.
+ }
+ return GlobList.captureResults(includes, excludes, ImmutableList.<String>of());
+ } else {
+ return handleGlob(includes, excludes, excludeDirs != 0, context, ast);
+ }
+ }
+ };
+ }
+
+ /**
+ * Adds a glob to the package, reporting any errors it finds.
+ *
+ * @param includes the list of includes which must be non-null
+ * @param excludes the list of excludes which must be non-null
+ * @param context the package context
+ * @param ast the AST
+ * @return the list of matches
+ * @throws EvalException if globbing failed
+ */
+ private static GlobList<String> handleGlob(List<String> includes, List<String> excludes,
+ boolean excludeDirs, PackageContext context, FuncallExpression ast)
+ throws EvalException, InterruptedException {
+ try {
+ Globber.Token globToken = context.globber.runAsync(includes, excludes, excludeDirs);
+ List<String> matches = context.globber.fetch(globToken);
+ return GlobList.captureResults(includes, excludes, matches);
+ } catch (IOException expected) {
+ context.eventHandler.handle(Event.error(ast.getLocation(),
+ "error globbing [" + Joiner.on(", ").join(includes) + "]: " + expected.getMessage()));
+ context.pkgBuilder.setContainsTemporaryErrors();
+ return GlobList.captureResults(includes, excludes, ImmutableList.<String>of());
+ } catch (GlobCache.BadGlobException e) {
+ throw new EvalException(ast.getLocation(), e.getMessage());
+ }
+ }
+
+ /**
+ * Returns a function value implementing the "mocksubinclude" function,
+ * emitted by the PythonPreprocessor. We annotate the
+ * package with additional dependencies. (A 'real' subinclude will never be
+ * seen by the parser, because the presence of "subinclude" triggers
+ * preprocessing.)
+ */
+ private static Function newMockSubincludeFunction(final PackageContext context) {
+ return new MixedModeFunction("mocksubinclude", ImmutableList.of("label", "path"), 2, false) {
+ @Override
+ public Object call(Object[] args, FuncallExpression ast)
+ throws ConversionException {
+ Label label = Type.LABEL.convert(args[0], "'mocksubinclude' argument",
+ context.pkgBuilder.getBuildFileLabel());
+ String pathString = Type.STRING.convert(args[1], "'mocksubinclude' argument");
+ Path path = pathString.isEmpty()
+ ? null
+ : context.pkgBuilder.getFilename().getRelative(pathString);
+ // A subinclude within a package counts as a file declaration.
+ if (label.getPackageIdentifier().equals(context.pkgBuilder.getPackageIdentifier())) {
+ Location location = ast.getLocation();
+ if (location == null) {
+ location = Location.fromFile(context.pkgBuilder.getFilename());
+ }
+ context.pkgBuilder.createInputFileMaybe(label, location);
+ }
+
+ context.pkgBuilder.addSubinclude(label, path);
+ return Environment.NONE;
+ }
+ };
+ }
+
+ /**
+ * Fake function: subinclude calls are ignored
+ * They will disappear after the Python preprocessing.
+ */
+ private static Function newSubincludeFunction() {
+ return new MixedModeFunction("subinclude", ImmutableList.of("file"), 1, false) {
+ @Override
+ public Object call(Object[] args, FuncallExpression ast) {
+ return Environment.NONE;
+ }
+ };
+ }
+
+ /**
+ * Returns a 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.
+ */
+ private static Function newEnvironmentGroupFunction(final PackageContext context) {
+ List<String> params = ImmutableList.of("name", "environments", "defaults");
+ return new MixedModeFunction("environment_group", params, params.size(), true) {
+ @Override
+ public Object call(Object[] namedArgs, FuncallExpression ast)
+ throws EvalException, ConversionException {
+ Preconditions.checkState(namedArgs[0] != null);
+ String name = Type.STRING.convert(namedArgs[0], "'environment_group' argument");
+ Preconditions.checkState(namedArgs[1] != null);
+ List<Label> environments = Type.LABEL_LIST.convert(
+ namedArgs[1], "'environment_group argument'", context.pkgBuilder.getBuildFileLabel());
+ Preconditions.checkState(namedArgs[2] != null);
+ List<Label> defaults = Type.LABEL_LIST.convert(
+ namedArgs[2], "'environment_group argument'", context.pkgBuilder.getBuildFileLabel());
+
+ try {
+ context.pkgBuilder.addEnvironmentGroup(name, environments, defaults,
+ context.eventHandler, ast.getLocation());
+ return Environment.NONE;
+ } catch (Label.SyntaxException e) {
+ throw new EvalException(ast.getLocation(),
+ "environment group has invalid name: " + name + ": " + e.getMessage());
+ } catch (Package.NameConflictException e) {
+ throw new EvalException(ast.getLocation(), e.getMessage());
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns a function-value implementing "exports_files" in the specified
+ * package context.
+ */
+ private static Function newExportsFilesFunction(final PackageContext context) {
+ final Package.LegacyBuilder pkgBuilder = context.pkgBuilder;
+ List<String> params = ImmutableList.of("srcs", "visibility", "licenses");
+ return new MixedModeFunction("exports_files", params, 1, false) {
+ @Override
+ public Object call(Object[] namedArgs, FuncallExpression ast)
+ throws EvalException, ConversionException {
+
+ List<String> files = Type.STRING_LIST.convert(namedArgs[0], "'exports_files' operand");
+
+ RuleVisibility visibility = namedArgs[1] == null
+ ? ConstantRuleVisibility.PUBLIC
+ : getVisibility(Type.LABEL_LIST.convert(
+ namedArgs[1],
+ "'exports_files' operand",
+ pkgBuilder.getBuildFileLabel()));
+ License license = namedArgs[2] == null
+ ? null
+ : Type.LICENSE.convert(namedArgs[2], "'exports_files' operand");
+
+ for (String file : files) {
+ String errorMessage = LabelValidator.validateTargetName(file);
+ if (errorMessage != null) {
+ throw new EvalException(ast.getLocation(), errorMessage);
+ }
+ try {
+ InputFile inputFile = pkgBuilder.createInputFile(file, ast.getLocation());
+ if (inputFile.isVisibilitySpecified()
+ && inputFile.getVisibility() != visibility) {
+ throw new EvalException(ast.getLocation(),
+ String.format("visibility for exported file '%s' declared twice",
+ inputFile.getName()));
+ }
+ if (license != null && inputFile.isLicenseSpecified()) {
+ throw new EvalException(ast.getLocation(),
+ String.format("licenses for exported file '%s' declared twice",
+ inputFile.getName()));
+ }
+ if (license == null && pkgBuilder.getDefaultLicense() == License.NO_LICENSE
+ && pkgBuilder.getBuildFileLabel().toString().startsWith("//third_party/")) {
+ throw new EvalException(ast.getLocation(),
+ "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(ast.getLocation(), e.getMessage());
+ }
+ }
+ return Environment.NONE;
+ }
+ };
+ }
+
+ /**
+ * Returns a function-value implementing "licenses" in the specified package
+ * context.
+ * TODO(bazel-team): Remove in favor of package.licenses.
+ */
+ private static Function newLicensesFunction(final PackageContext context) {
+ return new MixedModeFunction("licenses", ImmutableList.of("object"), 1, false) {
+ @Override
+ public Object call(Object[] args, FuncallExpression ast) {
+ try {
+ License license = Type.LICENSE.convert(args[0], "'licenses' operand");
+ context.pkgBuilder.setDefaultLicense(license);
+ } catch (ConversionException e) {
+ context.eventHandler.handle(Event.error(ast.getLocation(), e.getMessage()));
+ context.pkgBuilder.setContainsErrors();
+ }
+ return Environment.NONE;
+ }
+ };
+ }
+
+ /**
+ * Returns a function-value implementing "distribs" in the specified package
+ * context.
+ * TODO(bazel-team): Remove in favor of package.distribs.
+ */
+ private static Function newDistribsFunction(final PackageContext context) {
+ return new MixedModeFunction("distribs", ImmutableList.of("object"), 1, false) {
+ @Override
+ public Object call(Object[] args, FuncallExpression ast) {
+ try {
+ Set<DistributionType> distribs = Type.DISTRIBUTIONS.convert(args[0],
+ "'distribs' operand");
+ context.pkgBuilder.setDefaultDistribs(distribs);
+ } catch (ConversionException e) {
+ context.eventHandler.handle(Event.error(ast.getLocation(), e.getMessage()));
+ context.pkgBuilder.setContainsErrors();
+ }
+ return Environment.NONE;
+ }
+ };
+ }
+
+ private static Function newPackageGroupFunction(final PackageContext context) {
+ List<String> params = ImmutableList.of("name", "packages", "includes");
+ return new MixedModeFunction("package_group", params, 1, true) {
+ @Override
+ public Object call(Object[] namedArgs, FuncallExpression ast)
+ throws EvalException, ConversionException {
+ Preconditions.checkState(namedArgs[0] != null);
+ String name = Type.STRING.convert(namedArgs[0], "'package_group' argument");
+ List<String> packages = namedArgs[1] == null
+ ? Collections.<String>emptyList()
+ : Type.STRING_LIST.convert(namedArgs[1], "'package_group' argument");
+ List<Label> includes = namedArgs[2] == null
+ ? Collections.<Label>emptyList()
+ : Type.LABEL_LIST.convert(namedArgs[2], "'package_group argument'",
+ context.pkgBuilder.getBuildFileLabel());
+
+ try {
+ context.pkgBuilder.addPackageGroup(name, packages, includes, context.eventHandler,
+ ast.getLocation());
+ return Environment.NONE;
+ } catch (Label.SyntaxException e) {
+ throw new EvalException(ast.getLocation(),
+ "package group has invalid name: " + name + ": " + e.getMessage());
+ } catch (Package.NameConflictException e) {
+ throw new EvalException(ast.getLocation(), e.getMessage());
+ }
+ }
+ };
+ }
+
+ public static RuleVisibility getVisibility(List<Label> original) {
+ RuleVisibility result;
+
+ result = ConstantRuleVisibility.tryParse(original);
+ if (result != null) {
+ return result;
+ }
+
+ result = PackageGroupsRuleVisibility.tryParse(original);
+ return result;
+ }
+
+ /**
+ * Returns a function-value implementing "package" in the specified package
+ * context.
+ */
+ private static Function newPackageFunction(
+ final Map<String, PackageArgument<?>> packageArguments) {
+ return new MixedModeFunction("package", packageArguments.keySet(), 0, true) {
+ @Override
+ public Object call(Object[] namedArguments, FuncallExpression ast, Environment env)
+ throws EvalException, ConversionException {
+
+ Package.LegacyBuilder pkgBuilder = getContext(env, ast).pkgBuilder;
+
+ // Validate parameter list
+ if (pkgBuilder.isPackageFunctionUsed()) {
+ throw new EvalException(ast.getLocation(),
+ "'package' can only be used once per BUILD file");
+ }
+ pkgBuilder.setPackageFunctionUsed();
+
+ // Parse params
+ boolean foundParameter = false;
+
+ int argNumber = 0;
+ for (Map.Entry<String, PackageArgument<?>> entry : packageArguments.entrySet()) {
+ Object arg = namedArguments[argNumber];
+ argNumber += 1;
+ if (arg == null) {
+ continue;
+ }
+
+ foundParameter = true;
+ entry.getValue().convertAndProcess(pkgBuilder, ast.getLocation(), arg);
+ }
+
+ if (!foundParameter) {
+ throw new EvalException(ast.getLocation(),
+ "at least one argument must be given to the 'package' function");
+ }
+
+ return Environment.NONE;
+ }
+ };
+ }
+
+ // Helper function for createRuleFunction.
+ private static void addRule(RuleFactory ruleFactory,
+ String ruleClassName,
+ PackageContext context,
+ Map<String, Object> kwargs,
+ FuncallExpression ast)
+ throws RuleFactory.InvalidRuleException, Package.NameConflictException {
+ RuleClass ruleClass = getBuiltInRuleClass(ruleClassName, ruleFactory);
+ RuleFactory.createAndAddRule(context, ruleClass, kwargs, ast);
+ }
+
+ private static RuleClass getBuiltInRuleClass(String ruleClassName, RuleFactory ruleFactory) {
+ if (ruleFactory.getRuleClassNames().contains(ruleClassName)) {
+ return ruleFactory.getRuleClass(ruleClassName);
+ }
+ throw new IllegalArgumentException("no such rule class: " + ruleClassName);
+ }
+
+ /**
+ * Get the PackageContext by looking up in the environment.
+ */
+ private static PackageContext getContext(Environment env, FuncallExpression ast)
+ throws EvalException {
+ try {
+ return (PackageContext) env.lookup(PKG_CONTEXT);
+ } catch (NoSuchVariableException e) {
+ throw new EvalException(ast.getLocation(), e.getMessage());
+ }
+ }
+
+ /**
+ * Returns a function-value implementing the build rule "ruleClass" (e.g. cc_library) in the
+ * specified package context.
+ */
+ private static Function newRuleFunction(final RuleFactory ruleFactory,
+ final String ruleClass) {
+ return new AbstractFunction(ruleClass) {
+ @Override
+ public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast,
+ Environment env)
+ throws EvalException {
+ if (!args.isEmpty()) {
+ throw new EvalException(ast.getLocation(),
+ "build rules do not accept positional parameters");
+ }
+
+ try {
+ addRule(ruleFactory, ruleClass, getContext(env, ast), kwargs, ast);
+ } catch (RuleFactory.InvalidRuleException | Package.NameConflictException e) {
+ throw new EvalException(ast.getLocation(), e.getMessage());
+ }
+ return Environment.NONE;
+ }
+ };
+ }
+
+ /**
+ * Returns a new environment populated with common entries that can be shared
+ * across packages and that don't require the context.
+ */
+ private static Environment newGlobalEnvironment() {
+ Environment env = new Environment();
+ MethodLibrary.setupMethodEnvironment(env);
+ return env;
+ }
+
+ /****************************************************************************
+ * Package creation.
+ */
+
+ /**
+ * Loads, scans parses and evaluates the build file at "buildFile", and
+ * creates and returns a Package builder instance capable of building a package identified by
+ * "packageId".
+ *
+ * <p>This method returns a builder to allow the caller to do additional work, if necessary.
+ *
+ * <p>This method assumes "packageId" is a valid package name according to the
+ * {@link LabelValidator#validatePackageName} heuristic.
+ *
+ * <p>See {@link #evaluateBuildFile} for information on AST retention.
+ *
+ * <p>Executes {@code globber.onCompletion()} on completion and executes
+ * {@code globber.onInterrupt()} on an {@link InterruptedException}.
+ */
+ private Package.LegacyBuilder createPackage(PackageIdentifier packageId, Path buildFile,
+ List<Statement> preludeStatements, ParserInputSource inputSource,
+ Map<PathFragment, SkylarkEnvironment> imports, ImmutableList<Label> skylarkFileDependencies,
+ CachingPackageLocator locator, RuleVisibility defaultVisibility, Globber globber)
+ throws InterruptedException {
+ StoredEventHandler localReporter = new StoredEventHandler();
+ Preprocessor.Result preprocessingResult = preprocess(packageId, buildFile, inputSource, globber,
+ localReporter);
+ return createPackageFromPreprocessingResult(packageId, buildFile, preprocessingResult,
+ localReporter.getEvents(), preludeStatements, imports, skylarkFileDependencies, locator,
+ defaultVisibility, globber);
+ }
+
+ /**
+ * Same as {@link #createPackage}, but using a {@link Preprocessor.Result} from
+ * {@link #preprocess}.
+ *
+ * <p>Executes {@code globber.onCompletion()} on completion and executes
+ * {@code globber.onInterrupt()} on an {@link InterruptedException}.
+ */
+ // Used outside of bazel!
+ public Package.LegacyBuilder createPackageFromPreprocessingResult(PackageIdentifier packageId,
+ Path buildFile,
+ Preprocessor.Result preprocessingResult,
+ Iterable<Event> preprocessingEvents,
+ List<Statement> preludeStatements,
+ Map<PathFragment, SkylarkEnvironment> imports,
+ ImmutableList<Label> skylarkFileDependencies,
+ CachingPackageLocator locator,
+ RuleVisibility defaultVisibility,
+ Globber globber) throws InterruptedException {
+ StoredEventHandler localReporter = new StoredEventHandler();
+ // Run the lexer and parser with a local reporter, so that errors from other threads do not
+ // show up below. Merge the local and global reporters afterwards.
+ // Logged messages are used as a testability hook tracing the parsing progress
+ LOG.fine("Starting to parse " + packageId);
+ BuildFileAST buildFileAST = BuildFileAST.parseBuildFile(
+ preprocessingResult.result, preludeStatements, localReporter, locator, false);
+ LOG.fine("Finished parsing of " + packageId);
+
+ MakeEnvironment.Builder makeEnv = new MakeEnvironment.Builder();
+ if (platformSetRegexps != null) {
+ makeEnv.setPlatformSetRegexps(platformSetRegexps);
+ }
+ try {
+ // At this point the package is guaranteed to exist. It may have parse or
+ // evaluation errors, resulting in a diminished number of rules.
+ prefetchGlobs(packageId, buildFileAST, preprocessingResult.preprocessed,
+ buildFile, globber, defaultVisibility, makeEnv);
+ return evaluateBuildFile(
+ packageId, buildFileAST, buildFile, globber,
+ Iterables.concat(preprocessingEvents, localReporter.getEvents()),
+ defaultVisibility, preprocessingResult.containsErrors,
+ preprocessingResult.containsTransientErrors, makeEnv, imports, skylarkFileDependencies);
+ } catch (InterruptedException e) {
+ globber.onInterrupt();
+ throw e;
+ } finally {
+ globber.onCompletion();
+ }
+ }
+
+ /**
+ * Same as {@link #createPackage}, but does the required validation of "packageName" first,
+ * throwing a {@link NoSuchPackageException} if the name is invalid.
+ */
+ @VisibleForTesting
+ public Package createPackageForTesting(PackageIdentifier packageId,
+ Path buildFile,
+ CachingPackageLocator locator,
+ EventHandler eventHandler) throws NoSuchPackageException, InterruptedException {
+ String error = LabelValidator.validatePackageName(
+ packageId.getPackageFragment().getPathString());
+ if (error != null) {
+ throw new BuildFileNotFoundException(packageId.toString(),
+ "illegal package name: '" + packageId.toString() + "' (" + error + ")");
+ }
+ ParserInputSource inputSource = maybeGetParserInputSource(buildFile, eventHandler);
+ if (inputSource == null) {
+ throw new BuildFileContainsErrorsException(packageId.toString(), "IOException occured");
+ }
+ Package result = createPackage(packageId, buildFile,
+ ImmutableList.<Statement>of(), inputSource,
+ ImmutableMap.<PathFragment, SkylarkEnvironment>of(),
+ ImmutableList.<Label>of(),
+ locator, ConstantRuleVisibility.PUBLIC,
+ createLegacyGlobber(buildFile.getParentDirectory(), packageId, locator)).build();
+ Event.replayEventsOn(eventHandler, result.getEvents());
+ return result;
+ }
+
+ /** Preprocesses the given BUILD file. */
+ // Used outside of bazel!
+ public Preprocessor.Result preprocess(
+ PackageIdentifier packageId,
+ Path buildFile,
+ CachingPackageLocator locator,
+ EventHandler eventHandler) throws InterruptedException {
+ ParserInputSource inputSource = maybeGetParserInputSource(buildFile, eventHandler);
+ if (inputSource == null) {
+ return Preprocessor.Result.transientError(buildFile);
+ }
+ Globber globber = createLegacyGlobber(buildFile.getParentDirectory(), packageId, locator);
+ try {
+ return preprocess(packageId, buildFile, inputSource, globber, eventHandler);
+ } finally {
+ globber.onCompletion();
+ }
+ }
+
+ /**
+ * Preprocesses the given BUILD file, executing {@code globber.onInterrupt()} on an
+ * {@link InterruptedException}.
+ */
+ // Used outside of bazel!
+ public Preprocessor.Result preprocess(
+ PackageIdentifier packageId,
+ Path buildFile,
+ ParserInputSource inputSource,
+ Globber globber,
+ EventHandler eventHandler) throws InterruptedException {
+ Preprocessor preprocessor = preprocessorFactory.getPreprocessor();
+ if (preprocessor == null) {
+ return Preprocessor.Result.noPreprocessing(inputSource);
+ }
+ try {
+ return preprocessor.preprocess(inputSource, packageId.toString(), globber, eventHandler,
+ globalEnv, ruleFactory.getRuleClassNames());
+ } catch (IOException e) {
+ eventHandler.handle(Event.error(Location.fromFile(buildFile),
+ "preprocessing failed: " + e.getMessage()));
+ return Preprocessor.Result.transientError(buildFile);
+ } catch (InterruptedException e) {
+ globber.onInterrupt();
+ throw e;
+ }
+ }
+
+ // Used outside of bazel!
+ public LegacyGlobber createLegacyGlobber(Path packageDirectory, PackageIdentifier packageId,
+ CachingPackageLocator locator) {
+ return new LegacyGlobber(new GlobCache(packageDirectory, packageId, locator, syscalls,
+ threadPool));
+ }
+
+ @Nullable
+ private ParserInputSource maybeGetParserInputSource(Path buildFile, EventHandler eventHandler) {
+ try {
+ return ParserInputSource.create(buildFile);
+ } catch (IOException e) {
+ eventHandler.handle(Event.error(Location.fromFile(buildFile), e.getMessage()));
+ return null;
+ }
+ }
+
+ /**
+ * This tuple holds the current package builder, current lexer, etc, for the
+ * duration of the evaluation of one BUILD file. (We use a PackageContext
+ * object in preference to storing these values in mutable fields of the
+ * PackageFactory.)
+ *
+ * <p>PLEASE NOTE: references to PackageContext objects are held by many
+ * Function closures, but should become unreachable once the Environment is
+ * discarded at the end of evaluation. Please be aware of your memory
+ * footprint when making changes here!
+ */
+ public static class PackageContext {
+
+ final Package.LegacyBuilder pkgBuilder;
+ final Globber globber;
+ final EventHandler eventHandler;
+
+ @VisibleForTesting
+ public PackageContext(Package.LegacyBuilder pkgBuilder, Globber globber,
+ EventHandler eventHandler) {
+ this.pkgBuilder = pkgBuilder;
+ this.eventHandler = eventHandler;
+ this.globber = globber;
+ }
+ }
+
+ /**
+ * Returns the list of native rule functions created using the {@link RuleClassProvider}
+ * of this {@link PackageFactory}.
+ */
+ public ImmutableList<Function> collectNativeRuleFunctions() {
+ ImmutableList.Builder<Function> builder = ImmutableList.builder();
+ for (String ruleClass : ruleFactory.getRuleClassNames()) {
+ builder.add(newRuleFunction(ruleFactory, ruleClass));
+ }
+ builder.add(newGlobFunction(null, false));
+ builder.add(newPackageFunction(packageArguments));
+ return builder.build();
+ }
+
+ private void buildPkgEnv(Environment pkgEnv, String packageName,
+ MakeEnvironment.Builder pkgMakeEnv, PackageContext context, RuleFactory ruleFactory) {
+ pkgEnv.update("distribs", newDistribsFunction(context));
+ pkgEnv.update("glob", newGlobFunction(context, /*async=*/false));
+ pkgEnv.update("mocksubinclude", newMockSubincludeFunction(context));
+ pkgEnv.update("licenses", newLicensesFunction(context));
+ pkgEnv.update("exports_files", newExportsFilesFunction(context));
+ pkgEnv.update("package_group", newPackageGroupFunction(context));
+ pkgEnv.update("package", newPackageFunction(packageArguments));
+ pkgEnv.update("subinclude", newSubincludeFunction());
+ pkgEnv.update("environment_group", newEnvironmentGroupFunction(context));
+
+ pkgEnv.update("PACKAGE_NAME", packageName);
+
+ for (String ruleClass : ruleFactory.getRuleClassNames()) {
+ Function ruleFunction = newRuleFunction(ruleFactory, ruleClass);
+ pkgEnv.update(ruleClass, ruleFunction);
+ }
+
+ for (EnvironmentExtension extension : environmentExtensions) {
+ extension.update(pkgEnv, pkgMakeEnv, context.pkgBuilder.getBuildFileLabel());
+ }
+ }
+
+ /**
+ * Constructs a Package instance, evaluates the BUILD-file AST inside the
+ * build environment, and populates the package with Rule instances as it
+ * goes. As with most programming languages, evaluation stops when an
+ * exception is encountered: no further rules after the point of failure will
+ * be constructed. We assume that rules constructed before the point of
+ * failure are valid; this assumption is not entirely correct, since a
+ * "vardef" after a rule declaration can affect the behavior of that rule.
+ *
+ * <p>Rule attribute checking is performed during evaluation. Each attribute
+ * must conform to the type specified for that <i>(rule class, attribute
+ * name)</i> pair. Errors reported at this stage include: missing value for
+ * mandatory attribute, value of wrong type. Such error cause Rule
+ * construction to be aborted, so the resulting package will have missing
+ * members.
+ *
+ * @see PackageFactory#PackageFactory
+ */
+ @VisibleForTesting // used by PackageFactoryApparatus
+ public Package.LegacyBuilder evaluateBuildFile(PackageIdentifier packageId,
+ BuildFileAST buildFileAST, Path buildFilePath, Globber globber,
+ Iterable<Event> pastEvents, RuleVisibility defaultVisibility, boolean containsError,
+ boolean containsTransientError, MakeEnvironment.Builder pkgMakeEnv,
+ Map<PathFragment, SkylarkEnvironment> imports,
+ ImmutableList<Label> skylarkFileDependencies) throws InterruptedException {
+ // Important: Environment should be unreachable by the end of this method!
+ StoredEventHandler eventHandler = new StoredEventHandler();
+ Environment pkgEnv = new Environment(globalEnv, eventHandler);
+
+ Package.LegacyBuilder pkgBuilder =
+ new Package.LegacyBuilder(packageId)
+ .setGlobber(globber)
+ .setFilename(buildFilePath)
+ .setMakeEnv(pkgMakeEnv)
+ .setDefaultVisibility(defaultVisibility)
+ // "defaultVisibility" comes from the command line. Let's give the BUILD file a chance to
+ // set default_visibility once, be reseting the PackageBuilder.defaultVisibilitySet flag.
+ .setDefaultVisibilitySet(false)
+ .setSkylarkFileDependencies(skylarkFileDependencies);
+
+ Event.replayEventsOn(eventHandler, pastEvents);
+
+ // Stuff that closes over the package context:
+ PackageContext context = new PackageContext(pkgBuilder, globber, eventHandler);
+ buildPkgEnv(pkgEnv, packageId.toString(), pkgMakeEnv, context, ruleFactory);
+
+ if (containsError) {
+ pkgBuilder.setContainsErrors();
+ }
+
+ if (containsTransientError) {
+ pkgBuilder.setContainsTemporaryErrors();
+ }
+
+ if (!validatePackageIdentifier(packageId, buildFileAST.getLocation(), eventHandler)) {
+ pkgBuilder.setContainsErrors();
+ }
+
+ pkgEnv.setImportedExtensions(imports);
+ pkgEnv.updateAndPropagate(PKG_CONTEXT, context);
+ pkgEnv.updateAndPropagate(Environment.PKG_NAME, packageId.toString());
+
+ if (!validateAssignmentStatements(pkgEnv, buildFileAST, eventHandler)) {
+ pkgBuilder.setContainsErrors();
+ }
+
+ if (buildFileAST.containsErrors()) {
+ pkgBuilder.setContainsErrors();
+ }
+
+ // TODO(bazel-team): (2009) the invariant "if errors are reported, mark the package
+ // as containing errors" is strewn all over this class. Refactor to use an
+ // event sensor--and see if we can simplify the calling code in
+ // createPackage().
+ if (!buildFileAST.exec(pkgEnv, eventHandler)) {
+ pkgBuilder.setContainsErrors();
+ }
+
+ pkgBuilder.addEvents(eventHandler.getEvents());
+ return pkgBuilder;
+ }
+
+ /**
+ * Visit all targets and expand the globs in parallel.
+ */
+ private void prefetchGlobs(PackageIdentifier packageId, BuildFileAST buildFileAST,
+ boolean wasPreprocessed, Path buildFilePath, Globber globber,
+ RuleVisibility defaultVisibility, MakeEnvironment.Builder pkgMakeEnv)
+ throws InterruptedException {
+ if (wasPreprocessed) {
+ // No point in prefetching globs here: preprocessing implies eager evaluation
+ // of all globs.
+ return;
+ }
+ // Important: Environment should be unreachable by the end of this method!
+ Environment pkgEnv = new Environment();
+
+ Package.LegacyBuilder pkgBuilder =
+ new Package.LegacyBuilder(packageId)
+ .setFilename(buildFilePath)
+ .setMakeEnv(pkgMakeEnv)
+ .setDefaultVisibility(defaultVisibility)
+ // "defaultVisibility" comes from the command line. Let's give the BUILD file a chance to
+ // set default_visibility once, be reseting the PackageBuilder.defaultVisibilitySet flag.
+ .setDefaultVisibilitySet(false);
+
+ // Stuff that closes over the package context:
+ PackageContext context = new PackageContext(pkgBuilder, globber, NullEventHandler.INSTANCE);
+ buildPkgEnv(pkgEnv, packageId.toString(), pkgMakeEnv, context, ruleFactory);
+ pkgEnv.update("glob", newGlobFunction(context, /*async=*/true));
+ // The Fileset function is heavyweight in that it can run glob(). Avoid this during the
+ // preloading phase.
+ pkgEnv.remove("FilesetEntry");
+
+ buildFileAST.exec(pkgEnv, NullEventHandler.INSTANCE);
+ }
+
+
+ /**
+ * Tests a build AST to ensure that it contains no assignment statements that
+ * redefine built-in build rules.
+ *
+ * @param pkgEnv a package environment initialized with all of the built-in
+ * build rules
+ * @param ast the build file AST to be tested
+ * @param eventHandler a eventHandler where any errors should be logged
+ * @return true if the build file contains no redefinitions of built-in
+ * functions
+ */
+ private static boolean validateAssignmentStatements(Environment pkgEnv,
+ BuildFileAST ast,
+ EventHandler eventHandler) {
+ for (Statement stmt : ast.getStatements()) {
+ if (stmt instanceof AssignmentStatement) {
+ Expression lvalue = ((AssignmentStatement) stmt).getLValue();
+ if (!(lvalue instanceof Ident)) {
+ continue;
+ }
+ String target = ((Ident) lvalue).getName();
+ if (pkgEnv.lookup(target, null) != null) {
+ eventHandler.handle(Event.error(stmt.getLocation(), "Reassignment of builtin build "
+ + "function '" + target + "' not permitted"));
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ // Reports an error and returns false iff package identifier was illegal.
+ private static boolean validatePackageIdentifier(PackageIdentifier packageId, Location location,
+ EventHandler eventHandler) {
+ String error = LabelValidator.validatePackageName(packageId.getPackageFragment().toString());
+ if (error != null) {
+ eventHandler.handle(Event.error(location, error));
+ return false; // Invalid package name 'foo'
+ }
+ return true;
+ }
+}