// Copyright 2016 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.bazel.repository.skylark; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.analysis.RuleDefinition; import com.google.devtools.build.lib.bazel.repository.downloader.HttpDownloader; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue; import com.google.devtools.build.lib.rules.repository.RepositoryFunction; import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.Mutability; import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.skyframe.SkyFunction.Environment; import com.google.devtools.build.skyframe.SkyFunctionException.Transience; import java.io.IOException; import java.util.Map; import javax.annotation.Nullable; /** * A repository function to delegate work done by skylark remote repositories. */ public class SkylarkRepositoryFunction extends RepositoryFunction { /** * An exception thrown when a dependency is missing to notify the SkyFunction from a skylark * evaluation. */ private static class SkylarkRepositoryMissingDependencyException extends EvalException { SkylarkRepositoryMissingDependencyException() { super(Location.BUILTIN, "Internal exception"); } } private final HttpDownloader httpDownloader; public SkylarkRepositoryFunction(HttpDownloader httpDownloader) { this.httpDownloader = httpDownloader; } /** * Skylark repository context functions can throw the result of this function to notify the * SkylarkRepositoryFunction that a dependency was missing and the evaluation of the function must * be restarted. */ static EvalException restart() { return new SkylarkRepositoryMissingDependencyException(); } @Nullable @Override public RepositoryDirectoryValue.Builder fetch(Rule rule, Path outputDirectory, BlazeDirectories directories, Environment env, Map<String, String> markerData) throws RepositoryFunctionException, InterruptedException { BaseFunction function = rule.getRuleClassObject().getConfiguredTargetFunction(); if (declareEnvironmentDependencies(markerData, env, getEnviron(rule)) == null) { return null; } try (Mutability mutability = Mutability.create("skylark repository")) { // This Skylark environment ignores command line flags. com.google.devtools.build.lib.syntax.Environment buildEnv = com.google.devtools.build.lib.syntax.Environment.builder(mutability) .setGlobals(rule.getRuleClassObject().getRuleDefinitionEnvironment().getGlobals()) .setEventHandler(env.getListener()) .build(); SkylarkRepositoryContext skylarkRepositoryContext = new SkylarkRepositoryContext( rule, outputDirectory, env, clientEnvironment, httpDownloader, markerData); // This has side-effect, we don't care about the output. // Also we do a lot of stuff in there, maybe blocking operations and we should certainly make // it possible to return null and not block but it doesn't seem to be easy with Skylark // structure as it is. Object retValue = function.call( ImmutableList.<Object>of(skylarkRepositoryContext), ImmutableMap.<String, Object>of(), null, buildEnv); if (retValue != Runtime.NONE) { throw new RepositoryFunctionException( new EvalException( rule.getLocation(), "Call to repository rule " + rule.getName() + " returned a non-None value, None expected."), Transience.PERSISTENT); } } catch (EvalException e) { if (e.getCause() instanceof SkylarkRepositoryMissingDependencyException) { // A dependency is missing, cleanup and returns null try { if (outputDirectory.exists()) { FileSystemUtils.deleteTree(outputDirectory); } } catch (IOException e1) { throw new RepositoryFunctionException(e1, Transience.TRANSIENT); } return null; } throw new RepositoryFunctionException(e, Transience.TRANSIENT); } if (!outputDirectory.isDirectory()) { throw new RepositoryFunctionException( new IOException(rule + " must create a directory"), Transience.TRANSIENT); } if (!outputDirectory.getRelative("WORKSPACE").exists()) { createWorkspaceFile(outputDirectory, rule.getTargetKind(), rule.getName()); } return RepositoryDirectoryValue.builder().setPath(outputDirectory); } @SuppressWarnings("unchecked") private static Iterable<String> getEnviron(Rule rule) { return (Iterable<String>) rule.getAttributeContainer().getAttr("$environ"); } @Override public boolean verifyMarkerData(Rule rule, Map<String, String> markerData, Environment env) throws InterruptedException { if (verifyEnvironMarkerData(markerData, env, getEnviron(rule))) { return SkylarkRepositoryContext.verifyMarkerDataForFiles(markerData, env); } return false; } @Override protected boolean isLocal(Rule rule) { return (Boolean) rule.getAttributeContainer().getAttr("$local"); } @Override public Class<? extends RuleDefinition> getRuleDefinition() { return null; // unused so safe to return null } }