|  | // 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.skyframe; | 
|  |  | 
|  | import com.google.common.hash.HashFunction; | 
|  | import com.google.devtools.build.lib.actions.FileValue; | 
|  | import com.google.devtools.build.lib.cmdline.Label; | 
|  | import com.google.devtools.build.lib.events.Event; | 
|  | import com.google.devtools.build.lib.packages.BazelStarlarkEnvironment; | 
|  | import com.google.devtools.build.lib.packages.PackageFactory; | 
|  | import com.google.devtools.build.lib.vfs.FileSystemUtils; | 
|  | import com.google.devtools.build.lib.vfs.Path; | 
|  | import com.google.devtools.build.lib.vfs.RootedPath; | 
|  | import com.google.devtools.build.skyframe.SkyFunction; | 
|  | import com.google.devtools.build.skyframe.SkyFunctionException; | 
|  | import com.google.devtools.build.skyframe.SkyFunctionException.Transience; | 
|  | import com.google.devtools.build.skyframe.SkyKey; | 
|  | import com.google.devtools.build.skyframe.SkyValue; | 
|  | import java.io.IOException; | 
|  | import java.util.Map; | 
|  | import net.starlark.java.eval.Module; | 
|  | import net.starlark.java.eval.StarlarkSemantics; | 
|  | import net.starlark.java.syntax.FileOptions; | 
|  | import net.starlark.java.syntax.ParserInput; | 
|  | import net.starlark.java.syntax.Program; | 
|  | import net.starlark.java.syntax.StarlarkFile; | 
|  | import net.starlark.java.syntax.SyntaxError; | 
|  |  | 
|  | /** | 
|  | * A Skyframe function that compiles the .bzl file denoted by a Label. | 
|  | * | 
|  | * <p>Given a {@link Label} referencing a Starlark file, BzlCompileFunction loads, parses, resolves, | 
|  | * and compiles it. The Label must be absolute, and must not reference the special {@code external} | 
|  | * package. If the file (or the package containing it) doesn't exist, the function doesn't fail, but | 
|  | * instead returns a specific {@code NO_FILE} {@link BzlCompileValue}. | 
|  | */ | 
|  | // TODO(adonovan): actually compile. The name is a step ahead of the implementation. | 
|  | public class BzlCompileFunction implements SkyFunction { | 
|  |  | 
|  | private final PackageFactory packageFactory; | 
|  | private final HashFunction hashFunction; | 
|  |  | 
|  | public BzlCompileFunction(PackageFactory packageFactory, HashFunction hashFunction) { | 
|  | this.packageFactory = packageFactory; | 
|  | this.hashFunction = hashFunction; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public SkyValue compute(SkyKey skyKey, Environment env) | 
|  | throws SkyFunctionException, InterruptedException { | 
|  | try { | 
|  | return computeInline( | 
|  | (BzlCompileValue.Key) skyKey.argument(), env, packageFactory, hashFunction); | 
|  | } catch (FailedIOException e) { | 
|  | throw new FunctionException(e); | 
|  | } | 
|  | } | 
|  |  | 
|  | static BzlCompileValue computeInline( | 
|  | BzlCompileValue.Key key, | 
|  | Environment env, | 
|  | PackageFactory packageFactory, | 
|  | HashFunction hashFunction) | 
|  | throws FailedIOException, InterruptedException { | 
|  | byte[] bytes; | 
|  | byte[] digest; | 
|  | String inputName; | 
|  |  | 
|  | if (key.kind == BzlCompileValue.Kind.EMPTY_PRELUDE) { | 
|  | // Default prelude is empty. | 
|  | bytes = new byte[] {}; | 
|  | digest = null; | 
|  | inputName = "<default prelude>"; | 
|  | } else { | 
|  |  | 
|  | // Obtain the file. | 
|  | RootedPath rootedPath = RootedPath.toRootedPath(key.root, key.label.toPathFragment()); | 
|  | SkyKey fileSkyKey = FileValue.key(rootedPath); | 
|  | FileValue fileValue = null; | 
|  | try { | 
|  | fileValue = (FileValue) env.getValueOrThrow(fileSkyKey, IOException.class); | 
|  | } catch (IOException e) { | 
|  | throw new FailedIOException(e, Transience.PERSISTENT); | 
|  | } | 
|  | if (fileValue == null) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | if (fileValue.exists()) { | 
|  | if (!fileValue.isFile()) { | 
|  | return fileValue.isDirectory() | 
|  | ? BzlCompileValue.noFile("cannot load '%s': is a directory", key.label) | 
|  | : BzlCompileValue.noFile( | 
|  | "cannot load '%s': not a regular file (dangling link?)", key.label); | 
|  | } | 
|  |  | 
|  | // Read the file. | 
|  | Path path = rootedPath.asPath(); | 
|  | try { | 
|  | bytes = | 
|  | fileValue.isSpecialFile() | 
|  | ? FileSystemUtils.readContent(path) | 
|  | : FileSystemUtils.readWithKnownFileSize(path, fileValue.getSize()); | 
|  | } catch (IOException e) { | 
|  | throw new FailedIOException(e, Transience.TRANSIENT); | 
|  | } | 
|  | digest = fileValue.getDigest(); // may be null | 
|  | inputName = path.toString(); | 
|  | } else { | 
|  | if (key.kind == BzlCompileValue.Kind.PRELUDE) { | 
|  | // A non-existent prelude is fine. | 
|  | bytes = new byte[] {}; | 
|  | digest = null; | 
|  | inputName = "<default prelude>"; | 
|  | } else { | 
|  | return BzlCompileValue.noFile("cannot load '%s': no such file", key.label); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Compute digest if we didn't already get it from a fileValue. | 
|  | if (digest == null) { | 
|  | digest = hashFunction.hashBytes(bytes).asBytes(); | 
|  | } | 
|  |  | 
|  | StarlarkSemantics semantics = PrecomputedValue.STARLARK_SEMANTICS.get(env); | 
|  | if (semantics == null) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | Map<String, Object> predeclared; | 
|  | BazelStarlarkEnvironment starlarkEnv = packageFactory.getBazelStarlarkEnvironment(); | 
|  | if (key.kind == BzlCompileValue.Kind.BUILTINS) { | 
|  | predeclared = starlarkEnv.getBuiltinsBzlEnv(); | 
|  | } else { | 
|  | // Use the predeclared environment for BUILD-loaded bzl files, ignoring injection. It is not | 
|  | // the right env for the actual evaluation of BUILD-loaded bzl files because it doesn't | 
|  | // map to the injected symbols. But the names of the symbols are the same, and the names are | 
|  | // all we need to do symbol resolution (modulo FlagGuardedValues -- see TODO in | 
|  | // PackageFactory.createBuildBzlEnvUsingInjection()). | 
|  | // For WORKSPACE-loaded bzl files, the env isn't quite right not because of injection but | 
|  | // because the "native" object is different. But A) that will be fixed with #11954, and B) we | 
|  | // don't care for the same reason as above. | 
|  | predeclared = starlarkEnv.getUninjectedBuildBzlEnv(); | 
|  | } | 
|  |  | 
|  | // We have all deps. Parse, resolve, and return. | 
|  | ParserInput input = ParserInput.fromLatin1(bytes, inputName); | 
|  | FileOptions options = | 
|  | FileOptions.builder() | 
|  | // By default, Starlark load statements create file-local bindings. | 
|  | // However, the BUILD prelude typically contains nothing but load | 
|  | // statements whose bindings are intended to be visible in all BUILD | 
|  | // files. The loadBindsGlobally flag allows us to retrieve them. | 
|  | .loadBindsGlobally(key.isBuildPrelude()) | 
|  | .build(); | 
|  | StarlarkFile file = StarlarkFile.parse(input, options); | 
|  |  | 
|  | // compile | 
|  | Module module = Module.withPredeclared(semantics, predeclared); | 
|  | try { | 
|  | Program prog = Program.compileFile(file, module); | 
|  | return BzlCompileValue.withProgram(prog, digest); | 
|  | } catch (SyntaxError.Exception ex) { | 
|  | Event.replayEventsOn(env.getListener(), ex.errors()); | 
|  | return BzlCompileValue.noFile( | 
|  | "compilation of module '%s'%s failed", | 
|  | key.label.toPathFragment(), | 
|  | StarlarkBuiltinsValue.isBuiltinsRepo(key.label.getRepository()) ? " (internal)" : ""); | 
|  | } | 
|  | } | 
|  |  | 
|  | static final class FailedIOException extends Exception { | 
|  | private final Transience transience; | 
|  |  | 
|  | private FailedIOException(IOException cause, Transience transience) { | 
|  | super(cause.getMessage(), cause); | 
|  | this.transience = transience; | 
|  | } | 
|  |  | 
|  | Transience getTransience() { | 
|  | return transience; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final class FunctionException extends SkyFunctionException { | 
|  | private FunctionException(FailedIOException cause) { | 
|  | super(cause, cause.transience); | 
|  | } | 
|  | } | 
|  | } |