blob: 4f21aef0d70c6cabd00121353867e0fa9132a8f4 [file] [log] [blame]
// Copyright 2019 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.repository;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.cmdline.LabelConstants;
import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
import com.google.devtools.build.lib.packages.BuildFileName;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.WorkspaceFileValue;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.SkyFunction.Environment;
import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import com.google.devtools.build.skyframe.SkyKey;
import javax.annotation.Nullable;
/** Helper class for looking up data from the external package. */
public class ExternalPackageHelper {
private final ImmutableList<BuildFileName> workspaceFilesByPriority;
public ExternalPackageHelper(ImmutableList<BuildFileName> workspaceFilesByPriority) {
Preconditions.checkArgument(!workspaceFilesByPriority.isEmpty());
this.workspaceFilesByPriority = workspaceFilesByPriority;
}
/** Uses a rule name to fetch the corresponding Rule from the external package. */
@Nullable
public Rule getRuleByName(String ruleName, Environment env)
throws ExternalPackageException, InterruptedException {
ExternalPackageRuleExtractor extractor = new ExternalPackageRuleExtractor(ruleName);
if (!iterateWorkspaceFragments(env, extractor)) {
// Values missing
return null;
}
return extractor.getRule();
}
@Nullable
private static RootedPath checkWorkspaceFile(
Environment env, Root root, PathFragment workspaceFile) throws InterruptedException {
RootedPath candidate = RootedPath.toRootedPath(root, workspaceFile);
FileValue fileValue = (FileValue) env.getValue(FileValue.key(candidate));
if (env.valuesMissing()) {
return null;
}
return fileValue.isFile() ? candidate : null;
}
/**
* Returns the path of the main WORKSPACE file or null when a Skyframe restart is required.
*
* <p>Should also return null when the WORKSPACE file is not present, but then some tests break,
* so then it lies and returns the RootedPath corresponding to the last package path entry.
*/
@Nullable
public RootedPath findWorkspaceFile(Environment env) throws InterruptedException {
PathPackageLocator packageLocator = PrecomputedValue.PATH_PACKAGE_LOCATOR.get(env);
ImmutableList<Root> packagePath = packageLocator.getPathEntries();
for (Root candidateRoot : packagePath) {
for (BuildFileName workspaceFile : workspaceFilesByPriority) {
RootedPath path =
checkWorkspaceFile(env, candidateRoot, workspaceFile.getFilenameFragment());
if (env.valuesMissing()) {
return null;
}
if (path != null) {
return path;
}
}
}
// TODO(lberki): Technically, this means that the WORKSPACE file was not found. I'd love to not
// have this here, but a lot of tests break without it because they rely on Bazel kinda working
// even if the WORKSPACE file is not present.
return RootedPath.toRootedPath(
Iterables.getLast(packagePath), BuildFileName.WORKSPACE.getFilenameFragment());
}
/** Returns false if some SkyValues were missing. */
private boolean iterateWorkspaceFragments(Environment env, WorkspaceFileValueProcessor processor)
throws InterruptedException {
RootedPath workspacePath = findWorkspaceFile(env);
if (env.valuesMissing()) {
return false;
}
SkyKey workspaceKey = WorkspaceFileValue.key(workspacePath);
WorkspaceFileValue value;
do {
value = (WorkspaceFileValue) env.getValue(workspaceKey);
if (value == null) {
return false;
}
} while (processor.processAndShouldContinue(value) && (workspaceKey = value.next()) != null);
return true;
}
private static class ExternalPackageRuleExtractor implements WorkspaceFileValueProcessor {
private final String ruleName;
private ExternalPackageException exception;
private Rule rule;
private ExternalPackageRuleExtractor(String ruleName) {
this.ruleName = ruleName;
}
@Override
public boolean processAndShouldContinue(WorkspaceFileValue workspaceFileValue) {
Package externalPackage = workspaceFileValue.getPackage();
if (externalPackage.containsErrors()) {
exception =
new ExternalPackageException(
new BuildFileContainsErrorsException(
LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER,
"Could not load //external package"),
Transience.PERSISTENT);
// Stop iteration when encountered errors.
return false;
}
rule = externalPackage.getRule(ruleName);
// Stop if the rule is found = continue while it is null.
return rule == null;
}
public Rule getRule() throws ExternalPackageException {
if (exception != null) {
throw exception;
}
if (rule == null) {
throw new ExternalRuleNotFoundException(ruleName);
}
return rule;
}
}
private interface WorkspaceFileValueProcessor {
boolean processAndShouldContinue(WorkspaceFileValue value);
}
}