com.google.devtools.build.lib.packages.PackageFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.packages.PackageFactory.java

Source

// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.devtools.build.lib.packages;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.LabelValidator;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.events.NullEventHandler;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.Globber.BadGlobException;
import com.google.devtools.build.lib.packages.License.DistributionType;
import com.google.devtools.build.lib.packages.Preprocessor.AstAfterPreprocessing;
import com.google.devtools.build.lib.packages.RuleFactory.BuildLangTypedAttributeValuesMap;
import com.google.devtools.build.lib.skylarkinterface.Param;
import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
import com.google.devtools.build.lib.syntax.AssignmentStatement;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.BazelLibrary;
import com.google.devtools.build.lib.syntax.BuildFileAST;
import com.google.devtools.build.lib.syntax.BuiltinFunction;
import com.google.devtools.build.lib.syntax.ClassObject;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.Environment.Extension;
import com.google.devtools.build.lib.syntax.Environment.Phase;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalUtils;
import com.google.devtools.build.lib.syntax.Expression;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.FunctionSignature;
import com.google.devtools.build.lib.syntax.GlobList;
import com.google.devtools.build.lib.syntax.Identifier;
import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.ParserInputSource;
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.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.Statement;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.syntax.Type.ConversionException;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.UnixGlob;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.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.Builder pkgBuilder, Location location, Object value)
                throws EvalException {
            T typedValue = type.convert(value, "'package' argument", pkgBuilder.getBuildFileLabel());
            process(pkgBuilder, location, typedValue);
        }

        /**
         * Process an argument.
         *
         * @param pkgBuilder the package builder to be mutated
         * @param location the location of the {@code package} function for error reporting
         * @param value the value of the argument. Typically passed to {@link Type#convert}
         */
        protected abstract void process(Package.Builder pkgBuilder, Location location, T value)
                throws EvalException;
    }

    /**
     * An extension to the global namespace of the BUILD language.
     */
    // TODO(bazel-team): this is largely unrelated to syntax.Environment.Extension,
    // and should probably be renamed PackageFactory.RuntimeExtension, since really,
    // we're extending the Runtime with more classes.
    public interface EnvironmentExtension {
        /**
         * Update the global environment with the identifiers this extension contributes.
         */
        void update(Environment environment);

        /**
         * Update the global environment of WORKSPACE files.
         */
        void updateWorkspace(Environment environment);

        /**
         * Returns the extra functions needed to be added to the Skylark native module.
         */
        ImmutableList<BaseFunction> nativeModuleFunctions();

        /**
         * Returns the extra arguments to the {@code package()} statement.
         */
        Iterable<PackageArgument<?>> getPackageArguments();
    }

    private static class DefaultVisibility extends PackageArgument<List<Label>> {
        private DefaultVisibility() {
            super("default_visibility", BuildType.LABEL_LIST);
        }

        @Override
        protected void process(Package.Builder pkgBuilder, Location location, List<Label> value)
                throws EvalException {
            try {
                pkgBuilder.setDefaultVisibility(getVisibility(pkgBuilder.getBuildFileLabel(), value));
            } catch (EvalException e) {
                throw new EvalException(location, e.getMessage());
            }
        }
    }

    private static class DefaultTestOnly extends PackageArgument<Boolean> {
        private DefaultTestOnly() {
            super("default_testonly", Type.BOOLEAN);
        }

        @Override
        protected void process(Package.Builder 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.Builder 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.Builder pkgBuilder, Location location, List<String> value) {
            pkgBuilder.addFeatures(value);
        }
    }

    private static class DefaultLicenses extends PackageArgument<License> {
        private DefaultLicenses() {
            super("licenses", BuildType.LICENSE);
        }

        @Override
        protected void process(Package.Builder pkgBuilder, Location location, License value) {
            pkgBuilder.setDefaultLicense(value);
        }
    }

    private static class DefaultDistribs extends PackageArgument<Set<DistributionType>> {
        private DefaultDistribs() {
            super("distribs", BuildType.DISTRIBUTIONS);
        }

        @Override
        protected void process(Package.Builder 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, BuildType.LABEL_LIST);
        }

        @Override
        protected void process(Package.Builder 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, BuildType.LABEL_LIST);
        }

        @Override
        protected void process(Package.Builder pkgBuilder, Location location, List<Label> value) {
            pkgBuilder.setDefaultRestrictedTo(value, Package.DEFAULT_RESTRICTED_TO_ATTRIBUTE, location);
        }
    }

    public static final String PKG_CONTEXT = "$pkg_context";

    // Used outside of Bazel!
    /** {@link Globber} that uses the legacy GlobCache. */
    public static class LegacyGlobber implements Globber {
        private final GlobCache globCache;
        private final boolean sort;

        private LegacyGlobber(GlobCache globCache, boolean sort) {
            this.globCache = globCache;
            this.sort = sort;
        }

        private static 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 Token runAsync(List<String> includes, List<String> excludes, boolean excludeDirs)
                throws BadGlobException {
            for (String pattern : Iterables.concat(includes, excludes)) {
                globCache.getGlobUnsortedAsync(pattern, excludeDirs);
            }
            return new Token(includes, excludes, excludeDirs);
        }

        @Override
        public List<String> fetch(Globber.Token token) throws IOException, InterruptedException {
            List<String> result;
            Token legacyToken = (Token) token;
            try {
                result = globCache.globUnsorted(legacyToken.includes, legacyToken.excludes,
                        legacyToken.excludeDirs);
            } catch (BadGlobException e) {
                throw new IllegalStateException(e);
            }
            if (sort) {
                Collections.sort(result);
            }
            return result;
        }

        @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 AtomicReference<? extends UnixGlob.FilesystemCalls> syscalls;
    private Preprocessor.Factory preprocessorFactory = Preprocessor.Factory.NullFactory.INSTANCE;

    private final ThreadPoolExecutor threadPool;
    private Map<String, String> platformSetRegexps;

    private int maxDirectoriesToEagerlyVisitInGlobbing;

    private final ImmutableList<EnvironmentExtension> environmentExtensions;
    private final ImmutableMap<String, PackageArgument<?>> packageArguments;

    private final Package.Builder.Helper packageBuilderHelper;

    /** Factory for {@link PackageFactory} instances. Intended to only be used by unit tests. */
    @VisibleForTesting
    public abstract static class FactoryForTesting {
        public final PackageFactory create(RuleClassProvider ruleClassProvider, FileSystem fs) {
            return create(ruleClassProvider, null, ImmutableList.<EnvironmentExtension>of(), fs);
        }

        public final PackageFactory create(RuleClassProvider ruleClassProvider,
                EnvironmentExtension environmentExtension, FileSystem fs) {
            return create(ruleClassProvider, null, ImmutableList.of(environmentExtension), fs);
        }

        public final PackageFactory create(RuleClassProvider ruleClassProvider,
                Map<String, String> platformSetRegexps, Iterable<EnvironmentExtension> environmentExtensions,
                FileSystem fs) {
            return create(ruleClassProvider, platformSetRegexps, AttributeContainer.ATTRIBUTE_CONTAINER_FACTORY,
                    environmentExtensions, "test", fs);
        }

        protected abstract PackageFactory create(RuleClassProvider ruleClassProvider,
                Map<String, String> platformSetRegexps,
                Function<RuleClass, AttributeContainer> attributeContainerFactory,
                Iterable<EnvironmentExtension> environmentExtensions, String version, FileSystem fs);
    }

    /**
     * Constructs a {@code PackageFactory} instance with a specific glob path translator
     * and rule factory.
     *
     * <p>Only intended to be called by BlazeRuntime or {@link FactoryForTesting#create}.
     *
     * <p>Do not call this constructor directly in tests; please use
     * TestConstants#PACKAGE_FACTORY_FACTORY_FOR_TESTING instead.
     */
    public PackageFactory(RuleClassProvider ruleClassProvider, Map<String, String> platformSetRegexps,
            Function<RuleClass, AttributeContainer> attributeContainerFactory,
            Iterable<EnvironmentExtension> environmentExtensions, String version,
            Package.Builder.Helper packageBuilderHelper) {
        this.platformSetRegexps = platformSetRegexps;
        this.ruleFactory = new RuleFactory(ruleClassProvider, attributeContainerFactory);
        this.ruleClassProvider = ruleClassProvider;
        threadPool = new ThreadPoolExecutor(100, 100, 15L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
                new ThreadFactoryBuilder().setNameFormat("Legacy globber %d").build());
        // Do not consume threads when not in use.
        threadPool.allowCoreThreadTimeOut(true);
        this.environmentExtensions = ImmutableList.copyOf(environmentExtensions);
        this.packageArguments = createPackageArguments();
        this.nativeModule = newNativeModule();
        this.workspaceNativeModule = WorkspaceFactory.newNativeModule(ruleClassProvider, version);
        this.packageBuilderHelper = packageBuilderHelper;
    }

    /**
     * Sets the 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);
    }

    /**
     * Sets the max number of threads to use for globbing.
     */
    public void setGlobbingThreads(int globbingThreads) {
        threadPool.setCorePoolSize(globbingThreads);
        threadPool.setMaximumPoolSize(globbingThreads);
    }

    public void setMaxDirectoriesToEagerlyVisitInGlobbing(int maxDirectoriesToEagerlyVisitInGlobbing) {
        this.maxDirectoriesToEagerlyVisitInGlobbing = maxDirectoriesToEagerlyVisitInGlobbing;
    }

    /**
     * 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;
    }

    public ImmutableList<EnvironmentExtension> getEnvironmentExtensions() {
        return environmentExtensions;
    }

    /**
     * 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 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.
     */
    @SkylarkSignature(name = "glob", objectType = Object.class, returnType = SkylarkList.class, doc = "Returns a list of files that match glob search pattern.", parameters = {
            @Param(name = "include", type = SkylarkList.class, generic1 = String.class, doc = "a list of strings specifying patterns of files to include."),
            @Param(name = "exclude", type = SkylarkList.class, generic1 = String.class, defaultValue = "[]", positional = false, named = true, doc = "a list of strings specifying patterns of files to exclude."),
            // TODO(bazel-team): migrate all existing code to use boolean instead?
            @Param(name = "exclude_directories", type = Integer.class, defaultValue = "1", positional = false, named = true, doc = "a integer that if non-zero indicates directories should not be matched.") }, documented = false, useAst = true, useEnvironment = true)
    private static final BuiltinFunction.Factory newGlobFunction = new BuiltinFunction.Factory("glob") {
        public BuiltinFunction create(final PackageContext originalContext, final boolean async) {
            return new BuiltinFunction("glob", this) {
                public SkylarkList invoke(SkylarkList include, SkylarkList exclude, Integer excludeDirectories,
                        FuncallExpression ast, Environment env)
                        throws EvalException, ConversionException, InterruptedException {
                    return callGlob(originalContext, async, include, exclude, excludeDirectories != 0, ast, env);
                }
            };
        }
    };

    static SkylarkList callGlob(@Nullable PackageContext originalContext, boolean async, Object include,
            Object exclude, boolean excludeDirs, FuncallExpression ast, Environment env)
            throws EvalException, ConversionException, InterruptedException {
        // Skylark build extensions need to get the PackageContext from the Environment;
        // async glob functions cannot do the same because the Environment is not thread safe.
        PackageContext context;
        if (originalContext == null) {
            Preconditions.checkArgument(!async);
            context = getContext(env, ast);
        } else {
            context = originalContext;
        }

        List<String> includes = Type.STRING_LIST.convert(include, "'glob' argument");
        List<String> excludes = Type.STRING_LIST.convert(exclude, "'glob' argument");

        GlobList<String> globList;
        if (async) {
            try {
                context.globber.runAsync(includes, excludes, excludeDirs);
            } catch (BadGlobException e) {
                // Ignore: errors will appear during the actual evaluation of the package.
            }
            globList = GlobList.captureResults(includes, excludes, ImmutableList.<String>of());
        } else {
            globList = handleGlob(includes, excludes, excludeDirs, context, ast);
        }
        return new MutableList(globList, env);
    }

    /**
     * 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.setContainsErrors();
            return GlobList.captureResults(includes, excludes, ImmutableList.<String>of());
        } catch (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.)
     */
    @SkylarkSignature(name = "mocksubinclude", returnType = Runtime.NoneType.class, doc = "implement the mocksubinclude function emitted by the PythonPreprocessor.", parameters = {
            @Param(name = "label", type = Object.class, doc = "a label designator."),
            @Param(name = "path", type = String.class, doc = "a path.") }, documented = false, useLocation = true)
    private static final BuiltinFunction.Factory newMockSubincludeFunction = new BuiltinFunction.Factory(
            "mocksubinclude") {
        public BuiltinFunction create(final PackageContext context) {
            return new BuiltinFunction("mocksubinclude", this) {
                public Runtime.NoneType invoke(Object labelO, String pathString, Location loc)
                        throws ConversionException {
                    Label label = BuildType.LABEL.convert(labelO, "'mocksubinclude' argument",
                            context.pkgBuilder.getBuildFileLabel());
                    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())) {
                        if (loc == null) {
                            loc = Location.fromFile(context.pkgBuilder.getFilename());
                        }
                        context.pkgBuilder.createInputFileMaybe(label, loc);
                    }

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

                    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, useAst = true, useEnvironment = true)
    private static final BuiltinFunction.Factory newExportsFilesFunction = new BuiltinFunction.Factory(
            "exports_files") {
        public BuiltinFunction create() {
            return new BuiltinFunction("exports_files", this) {
                public Runtime.NoneType invoke(SkylarkList srcs, Object visibility, Object licenses,
                        FuncallExpression ast, Environment env) throws EvalException, ConversionException {
                    return callExportsFiles(srcs, visibility, licenses, ast, env);
                }
            };
        }
    };

    static Runtime.NoneType callExportsFiles(Object srcs, Object visibilityO, Object licensesO,
            FuncallExpression ast, Environment env) throws EvalException, ConversionException {
        Package.Builder pkgBuilder = getContext(env, ast).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(ast.getLocation(), 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(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
                        && RuleClass.isThirdPartyPackage(pkgBuilder.getPackageIdentifier())) {
                    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 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, useLocation = true)
    private static final BuiltinFunction.Factory newLicensesFunction = new BuiltinFunction.Factory("licenses") {
        public BuiltinFunction create(final PackageContext context) {
            return new BuiltinFunction("licenses", this) {
                public Runtime.NoneType invoke(SkylarkList licensesList, Location loc) {
                    try {
                        License license = BuildType.LICENSE.convert(licensesList, "'licenses' operand");
                        context.pkgBuilder.setDefaultLicense(license);
                    } catch (ConversionException e) {
                        context.eventHandler.handle(Event.error(loc, e.getMessage()));
                        context.pkgBuilder.setContainsErrors();
                    }
                    return Runtime.NONE;
                }
            };
        }
    };

    /**
     * Returns a function-value implementing "distribs" in the specified package
     * context.
     */
    // TODO(bazel-team): Remove in favor of package.distribs.
    // TODO(bazel-team): Remove all these new*Function-s and/or have static functions
    // that consult the context dynamically via getContext(env, ast) since we have that,
    // and share the functions with the native package... which requires unifying the List types.
    @SkylarkSignature(name = "distribs", returnType = Runtime.NoneType.class, doc = "Declare the distribution(s) for the code in the current package.", parameters = {
            @Param(name = "distribution_strings", type = Object.class, doc = "The distributions.") }, documented = false, useLocation = true)
    private static final BuiltinFunction.Factory newDistribsFunction = new BuiltinFunction.Factory("distribs") {
        public BuiltinFunction create(final PackageContext context) {
            return new BuiltinFunction("distribs", this) {
                public Runtime.NoneType invoke(Object object, Location loc) {
                    try {
                        Set<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, useAst = true, useEnvironment = true)
    private static final BuiltinFunction.Factory newPackageGroupFunction = new BuiltinFunction.Factory(
            "package_group") {
        public BuiltinFunction create() {
            return new BuiltinFunction("package_group", this) {
                public Runtime.NoneType invoke(String name, SkylarkList packages, SkylarkList includes,
                        FuncallExpression ast, Environment env) throws EvalException, ConversionException {
                    return callPackageFunction(name, packages, includes, ast, env);
                }
            };
        }
    };

    @Nullable
    static SkylarkDict<String, Object> callGetRuleFunction(String name, FuncallExpression ast, Environment env)
            throws EvalException, ConversionException {
        PackageContext context = getContext(env, ast);
        Target target = context.pkgBuilder.getTarget(name);

        return targetDict(target, ast.getLocation(), env);
    }

    @Nullable
    private static SkylarkDict<String, Object> targetDict(Target target, Location loc, Environment env)
            throws NotRepresentableException, EvalException {
        if (target == null || !(target instanceof Rule)) {
            return null;
        }
        SkylarkDict<String, Object> values = SkylarkDict.<String, Object>of(env);

        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, env);
            } 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, env);
        values.put("kind", rule.getRuleClass(), loc, env);
        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 SkylarkValue) {
            return val;
        }

        if (val instanceof License) {
            // TODO(bazel-team): convert License.getLicenseTypes() to a list of strings.
            return null;
        }

        if (val instanceof BuildType.SelectorList) {
            // This is terrible:
            //  1) this value is opaque, and not a BUILD value, so it cannot be used in rule arguments
            //  2) its representation has a pointer address, so it breaks hermeticity.
            //
            // Even though this is clearly imperfect, we return this value because otherwise
            // native.rules() fails if there is any rule using a select() in the BUILD file.
            //
            // To remedy this, we should return a syntax.SelectorList. To do so, we have to
            // 1) recurse into the Selector contents of SelectorList, so those values are skylarkified too
            // 2) get the right Class<?> value. We could probably get at that by looking at
            //    ((SelectorList)val).getSelectors().first().getEntries().first().getClass().

            return val;
        }

        // We are explicit about types we don't understand so we minimize changes to existing callers
        // if we add more types that we can represent.
        throw new NotRepresentableException(
                String.format("cannot represent %s (%s) in skylark", val.toString(), val.getClass().toString()));
    }

    static SkylarkDict<String, SkylarkDict<String, Object>> callGetRulesFunction(FuncallExpression ast,
            Environment env) throws EvalException {
        PackageContext context = getContext(env, ast);
        Collection<Target> targets = context.pkgBuilder.getTargets();
        Location loc = ast.getLocation();

        SkylarkDict<String, SkylarkDict<String, Object>> rules = SkylarkDict.of(env);
        for (Target t : targets) {
            if (t instanceof Rule) {
                SkylarkDict<String, Object> m = targetDict(t, loc, env);
                Preconditions.checkNotNull(m);
                rules.put(t.getName(), m, loc, env);
            }
        }

        return rules;
    }

    static Runtime.NoneType callPackageFunction(String name, Object packagesO, Object includesO,
            FuncallExpression ast, Environment env) throws EvalException, ConversionException {
        PackageContext context = getContext(env, ast);

        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, ast.getLocation());
            return Runtime.NONE;
        } catch (LabelSyntaxException 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(Label ruleLabel, List<Label> original) throws EvalException {
        RuleVisibility result;

        result = ConstantRuleVisibility.tryParse(original);
        if (result != null) {
            return result;
        }

        result = PackageGroupsRuleVisibility.tryParse(ruleLabel, original);
        return result;
    }

    /**
     * Returns a function-value implementing "package" in the specified package
     * context.
     */
    private static BaseFunction newPackageFunction(
            final ImmutableMap<String, PackageArgument<?>> packageArguments) {
        // Flatten the map of argument name of PackageArgument specifier in two co-indexed arrays:
        // one for the argument names, to create a FunctionSignature when we create the function,
        // one of the PackageArgument specifiers, over which to iterate at every function invocation
        // at the same time that we iterate over the function arguments.
        final int numArgs = packageArguments.size();
        final String[] argumentNames = new String[numArgs];
        final PackageArgument<?>[] argumentSpecifiers = new PackageArgument<?>[numArgs];
        int i = 0;
        for (Map.Entry<String, PackageArgument<?>> entry : packageArguments.entrySet()) {
            argumentNames[i] = entry.getKey();
            argumentSpecifiers[i++] = entry.getValue();
        }

        return new BaseFunction("package", FunctionSignature.namedOnly(0, argumentNames)) {
            @Override
            public Object call(Object[] arguments, FuncallExpression ast, Environment env) throws EvalException {

                Package.Builder 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;

                for (int i = 0; i < numArgs; i++) {
                    Object value = arguments[i];
                    if (value != null) {
                        foundParameter = true;
                        argumentSpecifiers[i].convertAndProcess(pkgBuilder, ast.getLocation(), value);
                    }
                }

                if (!foundParameter) {
                    throw new EvalException(ast.getLocation(),
                            "at least one argument must be given to the 'package' function");
                }

                return Runtime.NONE;
            }
        };
    }

    // Helper function for createRuleFunction.
    private static void addRule(RuleFactory ruleFactory, String ruleClassName, PackageContext context,
            Map<String, Object> kwargs, FuncallExpression ast, Environment env)
            throws RuleFactory.InvalidRuleException, Package.NameConflictException, InterruptedException {
        RuleClass ruleClass = getBuiltInRuleClass(ruleClassName, ruleFactory);
        BuildLangTypedAttributeValuesMap attributeValues = new BuildLangTypedAttributeValuesMap(kwargs);
        AttributeContainer attributeContainer = ruleFactory.getAttributeContainer(ruleClass);
        RuleFactory.createAndAddRule(context, ruleClass, attributeValues, ast, env, attributeContainer);
    }

    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.
     */
    public static PackageContext getContext(Environment env, FuncallExpression ast) throws EvalException {
        PackageContext value = (PackageContext) env.lookup(PKG_CONTEXT);
        if (value == null) {
            // if PKG_CONTEXT is missing, we're not called from a BUILD file. This happens if someone
            // uses native.some_func() in the wrong place.
            throw new EvalException(ast.getLocation(), "The native module cannot be accessed from here. "
                    + "Wrap the function in a macro and call it from a BUILD file");
        }
        return value;
    }

    /**
     * Returns a function-value implementing the build rule "ruleClass" (e.g. cc_library) in the
     * specified package context.
     */
    private static BuiltinFunction newRuleFunction(final RuleFactory ruleFactory, final String ruleClass) {
        return new BuiltinFunction(ruleClass, FunctionSignature.KWARGS, BuiltinFunction.USE_AST_ENV) {
            @SuppressWarnings({ "unchecked", "unused" })
            public Runtime.NoneType invoke(Map<String, Object> kwargs, FuncallExpression ast, Environment env)
                    throws EvalException, InterruptedException {
                env.checkLoadingOrWorkspacePhase(ruleClass, ast.getLocation());
                try {
                    addRule(ruleFactory, ruleClass, getContext(env, ast), kwargs, ast, env);
                } catch (RuleFactory.InvalidRuleException | Package.NameConflictException e) {
                    throw new EvalException(ast.getLocation(), e.getMessage());
                }
                return Runtime.NONE;
            }
        };
    }

    /****************************************************************************
     * 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}.
     */
    // Used outside of bazel!
    public Package.Builder createPackageFromPreprocessingResult(Package externalPkg, PackageIdentifier packageId,
            Path buildFile, Preprocessor.Result preprocessingResult, List<Statement> preludeStatements,
            Map<String, Extension> imports, ImmutableList<Label> skylarkFileDependencies,
            RuleVisibility defaultVisibility, Globber globber) throws InterruptedException {
        StoredEventHandler localReporterForParsing = new StoredEventHandler();
        // Run the lexer and parser with a local reporter, so that errors from other threads do not
        // show up below.
        BuildFileAST buildFileAST = parseBuildFile(packageId, preprocessingResult.result, preludeStatements,
                localReporterForParsing);
        AstAfterPreprocessing astAfterPreprocessing = new AstAfterPreprocessing(preprocessingResult, buildFileAST,
                localReporterForParsing);
        return createPackageFromPreprocessingAst(externalPkg, packageId, buildFile, astAfterPreprocessing, imports,
                skylarkFileDependencies, defaultVisibility, globber);
    }

    public static BuildFileAST parseBuildFile(PackageIdentifier packageId, ParserInputSource in,
            List<Statement> preludeStatements, EventHandler eventHandler) {
        // Logged messages are used as a testability hook tracing the parsing progress
        LOG.fine("Starting to parse " + packageId);
        BuildFileAST buildFileAST = BuildFileAST.parseBuildFile(in, preludeStatements, eventHandler);
        LOG.fine("Finished parsing of " + packageId);
        return buildFileAST;
    }

    public Package.Builder createPackageFromPreprocessingAst(Package externalPkg, PackageIdentifier packageId,
            Path buildFile, Preprocessor.AstAfterPreprocessing astAfterPreprocessing,
            Map<String, Extension> imports, ImmutableList<Label> skylarkFileDependencies,
            RuleVisibility defaultVisibility, Globber globber) throws InterruptedException {
        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, astAfterPreprocessing.ast, astAfterPreprocessing.preprocessed, buildFile,
                    globber, defaultVisibility, makeEnv);
            return evaluateBuildFile(externalPkg, packageId, astAfterPreprocessing.ast, buildFile, globber,
                    astAfterPreprocessing.allEvents, defaultVisibility,
                    astAfterPreprocessing.containsPreprocessingErrors, makeEnv, imports, skylarkFileDependencies);
        } catch (InterruptedException e) {
            globber.onInterrupt();
            throw e;
        } finally {
            globber.onCompletion();
        }
    }

    @VisibleForTesting
    public Package.Builder newExternalPackageBuilder(Path workspacePath, String runfilesPrefix) {
        return Package.newExternalPackageBuilder(packageBuilderHelper, workspacePath, runfilesPrefix);
    }

    @VisibleForTesting
    public Package.Builder newPackageBuilder(PackageIdentifier packageId, String runfilesPrefix) {
        return new Package.Builder(packageBuilderHelper, packageId, runfilesPrefix);
    }

    @VisibleForTesting
    public Package createPackageForTesting(PackageIdentifier packageId, Path buildFile,
            CachingPackageLocator locator, EventHandler eventHandler)
            throws NoSuchPackageException, InterruptedException {
        Package externalPkg = newExternalPackageBuilder(buildFile.getRelative("WORKSPACE"), "TESTING").build();
        return createPackageForTesting(packageId, externalPkg, buildFile, locator, eventHandler);
    }

    /**
     * Same as createPackage, but does the required validation of "packageName" first,
     * throwing a {@link NoSuchPackageException} if the name is invalid.
     */
    @VisibleForTesting
    public Package createPackageForTesting(PackageIdentifier packageId, Package externalPkg, Path buildFile,
            CachingPackageLocator locator, EventHandler eventHandler)
            throws NoSuchPackageException, InterruptedException {
        String error = LabelValidator.validatePackageName(packageId.getPackageFragment().getPathString());
        if (error != null) {
            throw new BuildFileNotFoundException(packageId,
                    "illegal package name: '" + packageId + "' (" + error + ")");
        }
        byte[] buildFileBytes = maybeGetBuildFileBytes(buildFile, eventHandler);
        if (buildFileBytes == null) {
            throw new BuildFileContainsErrorsException(packageId, "IOException occured");
        }

        Globber globber = createLegacyGlobber(buildFile.getParentDirectory(), packageId, locator);
        Preprocessor.Result preprocessingResult;
        try {
            preprocessingResult = preprocess(buildFile, packageId, buildFileBytes, globber);
        } catch (IOException e) {
            eventHandler
                    .handle(Event.error(Location.fromFile(buildFile), "preprocessing failed: " + e.getMessage()));
            throw new BuildFileContainsErrorsException(packageId, "preprocessing failed", e);
        }

        Package result = createPackageFromPreprocessingResult(externalPkg, packageId, buildFile,
                preprocessingResult, /*preludeStatements=*/ImmutableList.<Statement>of(),
                /*imports=*/ImmutableMap.<String, Extension>of(),
                /*skylarkFileDependencies=*/ImmutableList.<Label>of(),
                /*defaultVisibility=*/ConstantRuleVisibility.PUBLIC, globber).build();
        Event.replayEventsOn(eventHandler, result.getEvents());
        return result;
    }

    /** Preprocesses the given BUILD file. */
    public Preprocessor.Result preprocess(PackageIdentifier packageId, Path buildFile,
            CachingPackageLocator locator) throws InterruptedException, IOException {
        byte[] buildFileBytes = FileSystemUtils.readWithKnownFileSize(buildFile, buildFile.getFileSize());
        Globber globber = createLegacyGlobber(buildFile.getParentDirectory(), packageId, locator);
        try {
            return preprocess(buildFile, packageId, buildFileBytes, globber);
        } finally {
            globber.onCompletion();
        }
    }

    /**
     * Preprocesses the given BUILD file, executing {@code globber.onInterrupt()} on an
     * {@link InterruptedException}.
     */
    public Preprocessor.Result preprocess(Path buildFilePath, PackageIdentifier packageId, byte[] buildFileBytes,
            Globber globber) throws InterruptedException, IOException {
        Preprocessor preprocessor = preprocessorFactory.getPreprocessor();
        if (preprocessor == null) {
            return Preprocessor.Result.noPreprocessing(buildFilePath.asFragment(), buildFileBytes);
        }
        try {
            return preprocessor.preprocess(buildFilePath, buildFileBytes, packageId.toString(), globber,
                    ruleFactory.getRuleClassNames());
        } catch (InterruptedException e) {
            globber.onInterrupt();
            throw e;
        }
    }

    /** Returns a new {@link LegacyGlobber}. */
    public LegacyGlobber createLegacyGlobber(Path packageDirectory, PackageIdentifier packageId,
            CachingPackageLocator locator) {
        return createLegacyGlobber(new GlobCache(packageDirectory, packageId, locator, syscalls, threadPool,
                maxDirectoriesToEagerlyVisitInGlobbing));
    }

    /** Returns a new {@link LegacyGlobber}. */
    public static LegacyGlobber createLegacyGlobber(GlobCache globCache) {
        return new LegacyGlobber(globCache, /*sort=*/ true);
    }

    /**
     * Returns a new {@link LegacyGlobber}, the same as in {@link #createLegacyGlobber}, except that
     * the implementation of {@link Globber#fetch} intentionally breaks the contract and doesn't
     * return sorted results.
     */
    public LegacyGlobber createLegacyGlobberThatDoesntSort(Path packageDirectory, PackageIdentifier packageId,
            CachingPackageLocator locator) {
        return new LegacyGlobber(new GlobCache(packageDirectory, packageId, locator, syscalls, threadPool,
                maxDirectoriesToEagerlyVisitInGlobbing), /*sort=*/ false);
    }

    @Nullable
    private byte[] maybeGetBuildFileBytes(Path buildFile, EventHandler eventHandler) {
        try {
            return FileSystemUtils.readWithKnownFileSize(buildFile, buildFile.getFileSize());
        } 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
     * BaseFunction 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.Builder pkgBuilder;
        final Globber globber;
        final EventHandler eventHandler;
        private final Function<RuleClass, AttributeContainer> attributeContainerFactory;

        @VisibleForTesting
        public PackageContext(Package.Builder pkgBuilder, Globber globber, EventHandler eventHandler,
                Function<RuleClass, AttributeContainer> attributeContainerFactory) {
            this.pkgBuilder = pkgBuilder;
            this.eventHandler = eventHandler;
            this.globber = globber;
            this.attributeContainerFactory = attributeContainerFactory;
        }

        /**
         * Returns the Label of this Package.
         */
        public Label getLabel() {
            return pkgBuilder.getBuildFileLabel();
        }

        /**
         * Returns the MakeEnvironment Builder of this Package.
         */
        public MakeEnvironment.Builder getMakeEnvironment() {
            return pkgBuilder.getMakeEnvironment();
        }

        /**
         * Returns the builder of this Package.
         */
        public Package.Builder getBuilder() {
            return pkgBuilder;
        }

        public Function<RuleClass, AttributeContainer> getAttributeContainerFactory() {
            return attributeContainerFactory;
        }
    }

    private final ClassObject nativeModule;
    private final ClassObject workspaceNativeModule;

    /** @return the Skylark struct to bind to "native" */
    public ClassObject getNativeModule(boolean workspace) {
        return workspace ? workspaceNativeModule : nativeModule;
    }

    /**
     * Returns a native module with the functions created using the {@link RuleClassProvider}
     * of this {@link PackageFactory}.
     */
    private ClassObject newNativeModule() {
        ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
        for (String nativeFunction : Runtime.getFunctionNames(SkylarkNativeModule.class)) {
            builder.put(nativeFunction, Runtime.getFunction(SkylarkNativeModule.class, nativeFunction));
        }
        for (String ruleClass : ruleFactory.getRuleClassNames()) {
            builder.put(ruleClass, newRuleFunction(ruleFactory, ruleClass));
        }
        builder.put("package", newPackageFunction(packageArguments));
        for (EnvironmentExtension extension : environmentExtensions) {
            for (BaseFunction function : extension.nativeModuleFunctions()) {
                builder.put(function.getName(), function);
            }
        }
        return SkylarkClassObjectConstructor.STRUCT.create(builder.build(), "no native function or rule '%s'");
    }

    private void buildPkgEnv(Environment pkgEnv, PackageContext context, RuleFactory ruleFactory) {
        // 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 Environment.Frame for these common bindings
        // (that shares a backing ImmutableMap for the bindings?)
        pkgEnv.setup("native", nativeModule).setup("distribs", newDistribsFunction.apply(context))
                .setup("glob", newGlobFunction.apply(context, /*async=*/false))
                .setup("mocksubinclude", newMockSubincludeFunction.apply(context))
                .setup("licenses", newLicensesFunction.apply(context))
                .setup("exports_files", newExportsFilesFunction.apply())
                .setup("package_group", newPackageGroupFunction.apply())
                .setup("package", newPackageFunction(packageArguments))
                .setup("environment_group", newEnvironmentGroupFunction.apply(context));

        for (String ruleClass : ruleFactory.getRuleClassNames()) {
            BaseFunction ruleFunction = newRuleFunction(ruleFactory, ruleClass);
            pkgEnv.setup(ruleClass, ruleFunction);
        }

        for (EnvironmentExtension extension : environmentExtensions) {
            extension.update(pkgEnv);
        }
    }

    /**
     * Called by a caller of {@link #createPackageFromPreprocessingAst} after this caller has fully
     * loaded the package.
     */
    public void afterDoneLoadingPackage(Package pkg) {
        packageBuilderHelper.onLoadingComplete(pkg);
    }

    /**
     * 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.Builder evaluateBuildFile(Package externalPkg, PackageIdentifier packageId,
            BuildFileAST buildFileAST, Path buildFilePath, Globber globber, Iterable<Event> pastEvents,
            RuleVisibility defaultVisibility, boolean containsError, MakeEnvironment.Builder pkgMakeEnv,
            Map<String, Extension> imports, ImmutableList<Label> skylarkFileDependencies)
            throws InterruptedException {
        Package.Builder pkgBuilder = new Package.Builder(
                packageBuilderHelper.createFreshPackage(packageId, ruleClassProvider.getRunfilesPrefix()));
        StoredEventHandler eventHandler = new StoredEventHandler();

        try (Mutability mutability = Mutability.create("package %s", packageId)) {
            Environment pkgEnv = Environment.builder(mutability).setGlobals(BazelLibrary.GLOBALS)
                    .setEventHandler(eventHandler).setImportedExtensions(imports).setPhase(Phase.LOADING).build();
            SkylarkUtils.setToolsRepository(pkgEnv, ruleClassProvider.getToolsRepository());

            pkgBuilder.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)
                    .setWorkspaceName(externalPkg.getWorkspaceName());

            Event.replayEventsOn(eventHandler, pastEvents);

            // Stuff that closes over the package context:
            PackageContext context = new PackageContext(pkgBuilder, globber, eventHandler,
                    ruleFactory.getAttributeContainerFactory());
            buildPkgEnv(pkgEnv, context, ruleFactory);
            pkgEnv.setupDynamic(PKG_CONTEXT, context);
            pkgEnv.setupDynamic(Runtime.PKG_NAME, packageId.getPackageFragment().getPathString());
            pkgEnv.setupDynamic(Runtime.REPOSITORY_NAME, packageId.getRepository().toString());

            if (containsError) {
                pkgBuilder.setContainsErrors();
            }

            if (!validatePackageIdentifier(packageId, buildFileAST.getLocation(), eventHandler)) {
                pkgBuilder.setContainsErrors();
            }

            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 && preprocessorFactory.considersGlobs()) {
            // All the globs have either already been evaluated and they aren't in the ast anymore, or
            // they are in the ast but the globber has been evaluating them lazily and so there is no
            // point in prefetching them again.
            return;
        }
        // TODO(bazel-team): It may be wasteful to evaluate the BUILD file here, only to throw away the
        // result. It may be better to first scan the ast and see if there are even possibly any globs
        // at all. Additionally, it's wasteful to execute Skylark code that cannot invoke globs. So one
        // strategy would be to crawl the ast and tag statements whose execution cannot involve globs -
        // these can be executed and their impact on the resulting package can be saved.
        try (Mutability mutability = Mutability.create("prefetchGlobs for %s", packageId)) {
            Environment pkgEnv = Environment.builder(mutability).setGlobals(BazelLibrary.GLOBALS)
                    .setEventHandler(NullEventHandler.INSTANCE).setPhase(Phase.LOADING).build();
            SkylarkUtils.setToolsRepository(pkgEnv, ruleClassProvider.getToolsRepository());

            Package.Builder pkgBuilder = new Package.Builder(
                    packageBuilderHelper.createFreshPackage(packageId, ruleClassProvider.getRunfilesPrefix()));

            pkgBuilder.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,
                    ruleFactory.getAttributeContainerFactory());
            buildPkgEnv(pkgEnv, context, ruleFactory);
            try {
                pkgEnv.update("glob", newGlobFunction.apply(context, /*async=*/true));
                // The Fileset function is heavyweight in that it can run glob(). Avoid this during the
                // preloading phase.
                pkgEnv.update("FilesetEntry", Runtime.NONE);
            } catch (EvalException e) {
                throw new AssertionError(e);
            }
            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
     */
    // TODO(bazel-team): Remove this check. It should be moved to LValue.assign
    private static boolean validateAssignmentStatements(Environment pkgEnv, BuildFileAST ast,
            EventHandler eventHandler) {
        for (Statement stmt : ast.getStatements()) {
            if (stmt instanceof AssignmentStatement) {
                Expression lvalue = ((AssignmentStatement) stmt).getLValue().getExpression();
                if (!(lvalue instanceof Identifier)) {
                    continue;
                }
                String target = ((Identifier) lvalue).getName();
                if (pkgEnv.hasVariable(target)) {
                    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;
    }

    static {
        SkylarkSignatureProcessor.configureSkylarkFunctions(PackageFactory.class);
    }

    /** Empty EnvironmentExtension */
    public static class EmptyEnvironmentExtension implements EnvironmentExtension {
        @Override
        public void update(Environment environment) {
        }

        @Override
        public void updateWorkspace(Environment environment) {
        }

        @Override
        public Iterable<PackageArgument<?>> getPackageArguments() {
            return ImmutableList.of();
        }

        @Override
        public ImmutableList<BaseFunction> nativeModuleFunctions() {
            return ImmutableList.<BaseFunction>of();
        }
    }
}