/*
* Copyright 2014-present Facebook, Inc.
*
* 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.facebook.buck.rules;
import com.facebook.buck.cli.BuckConfig;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.io.ExecutableFinder;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.io.Watchman;
import com.facebook.buck.json.ProjectBuildFileParser;
import com.facebook.buck.json.ProjectBuildFileParserOptions;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetException;
import com.facebook.buck.parser.ParserConfig;
import com.facebook.buck.rules.coercer.TypeCoercerFactory;
import com.facebook.buck.util.Console;
import com.facebook.buck.util.DefaultProcessExecutor;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.RichStream;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
/**
* Represents a single checkout of a code base. Two cells model the same code base if their
* underlying {@link ProjectFilesystem}s are equal.
*/
public class Cell {
private final ImmutableSet<Path> knownRoots;
private final Optional<String> canonicalName;
private final ProjectFilesystem filesystem;
private final Watchman watchman;
private final BuckConfig config;
private final CellProvider cellProvider;
private final Supplier<KnownBuildRuleTypes> knownBuildRuleTypesSupplier;
private final int hashCode;
/** Should only be constructed by {@link CellProvider}. */
Cell(
ImmutableSet<Path> knownRoots,
Optional<String> canonicalName,
ProjectFilesystem filesystem,
Watchman watchman,
BuckConfig config,
KnownBuildRuleTypesFactory knownBuildRuleTypesFactory,
CellProvider cellProvider) {
this.knownRoots = knownRoots;
this.canonicalName = canonicalName;
this.filesystem = filesystem;
this.watchman = watchman;
this.config = config;
this.cellProvider = cellProvider;
// Stampede needs the Cell before it can materialize all the files required by
// knownBuildRuleTypesFactory (specifically java/javac), and as such we need to load this
// lazily when getKnownBuildRuleTypes() is called.
knownBuildRuleTypesSupplier =
Suppliers.memoize(
() -> {
try {
return knownBuildRuleTypesFactory.create(config, filesystem);
} catch (IOException e) {
throw new RuntimeException(
String.format(
"Creation of KnownBuildRuleTypes failed for Cell rooted at [%s].",
filesystem.getRootPath()),
e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(
String.format(
"Creation of KnownBuildRuleTypes failed for Cell rooted at [%s].",
filesystem.getRootPath()),
e);
}
});
hashCode = Objects.hash(filesystem, config);
}
public ProjectFilesystem getFilesystem() {
return filesystem;
}
public Path getRoot() {
return getFilesystem().getRootPath();
}
public KnownBuildRuleTypes getKnownBuildRuleTypes() {
return knownBuildRuleTypesSupplier.get();
}
public BuckConfig getBuckConfig() {
return config;
}
public String getBuildFileName() {
return config.getView(ParserConfig.class).getBuildFileName();
}
public Optional<String> getCanonicalName() {
return canonicalName;
}
/**
* Whether the cell is enforcing buck package boundaries for the package at the passed path.
*
* @param path Path of package (or file in a package) relative to the cell root.
*/
public boolean isEnforcingBuckPackageBoundaries(Path path) {
ParserConfig configView = config.getView(ParserConfig.class);
if (!configView.getEnforceBuckPackageBoundary()) {
return false;
}
Path absolutePath = filesystem.resolve(path);
ImmutableList<Path> exceptions = configView.getBuckPackageBoundaryExceptions();
for (Path exception : exceptions) {
if (absolutePath.startsWith(exception)) {
return false;
}
}
return true;
}
public Cell getCellIgnoringVisibilityCheck(Path cellPath) {
return cellProvider.getCellByPath(cellPath);
}
public Cell getCell(Path cellPath) {
if (!knownRoots.contains(cellPath)) {
throw new HumanReadableException(
"Unable to find repository rooted at %s. Known roots are:\n %s",
cellPath, Joiner.on(",\n ").join(knownRoots));
}
return getCellIgnoringVisibilityCheck(cellPath);
}
public Cell getCell(BuildTarget target) {
return getCell(target.getCellPath());
}
public Optional<Cell> getCellIfKnown(BuildTarget target) {
if (knownRoots.contains(target.getCellPath())) {
return Optional.of(getCell(target));
}
return Optional.empty();
}
/**
* Returns a list of all cells, including this cell. If this cell is the root, getAllCells will
* necessarily return all possible cells that this build may interact with, since the root cell is
* required to declare a mapping for all cell names.
*/
public ImmutableList<Cell> getAllCells() {
return RichStream.from(knownRoots)
.concat(RichStream.of(getRoot()))
.distinct()
.map(cellProvider::getCellByPath)
.toImmutableList();
}
/** @return all loaded {@link Cell}s that are children of this {@link Cell}. */
public ImmutableMap<Path, Cell> getLoadedCells() {
return cellProvider.getLoadedCells();
}
public Description<?> getDescription(BuildRuleType type) {
return getKnownBuildRuleTypes().getDescription(type);
}
public BuildRuleType getBuildRuleType(String rawType) {
return getKnownBuildRuleTypes().getBuildRuleType(rawType);
}
public ImmutableSet<Description<?>> getAllDescriptions() {
return getKnownBuildRuleTypes().getAllDescriptions();
}
/**
* For use in performance-sensitive code or if you don't care if the build file actually exists,
* otherwise prefer {@link #getAbsolutePathToBuildFile(BuildTarget)}.
*
* @param target target to look up
* @return path which may or may not exist.
*/
public Path getAbsolutePathToBuildFileUnsafe(BuildTarget target) {
Cell targetCell = getCell(target);
ProjectFilesystem targetFilesystem = targetCell.getFilesystem();
Path buildFile =
targetFilesystem.resolve(target.getBasePath()).resolve(targetCell.getBuildFileName());
return buildFile;
}
public Path getAbsolutePathToBuildFile(BuildTarget target) throws MissingBuildFileException {
Path buildFile = getAbsolutePathToBuildFileUnsafe(target);
Cell cell = getCell(target);
if (!cell.getFilesystem().isFile(buildFile)) {
throw new MissingBuildFileException(target, cell.getBuckConfig());
}
return buildFile;
}
public Watchman getWatchman() {
return watchman;
}
/**
* Callers are responsible for managing the life-cycle of the created {@link
* ProjectBuildFileParser}.
*/
public ProjectBuildFileParser createBuildFileParser(
TypeCoercerFactory typeCoercerFactory, Console console, BuckEventBus eventBus) {
ParserConfig parserConfig = getBuckConfig().getView(ParserConfig.class);
boolean useWatchmanGlob =
parserConfig.getGlobHandler() == ParserConfig.GlobHandler.WATCHMAN
&& watchman.hasWildmatchGlob();
boolean watchmanGlobStatResults =
parserConfig.getWatchmanGlobSanityCheck() == ParserConfig.WatchmanGlobSanityCheck.STAT;
boolean watchmanUseGlobGenerator =
watchman.getCapabilities().contains(Watchman.Capability.GLOB_GENERATOR);
boolean useMercurialGlob = parserConfig.getGlobHandler() == ParserConfig.GlobHandler.MERCURIAL;
String pythonInterpreter = parserConfig.getPythonInterpreter(new ExecutableFinder());
Optional<String> pythonModuleSearchPath = parserConfig.getPythonModuleSearchPath();
return new ProjectBuildFileParser(
ProjectBuildFileParserOptions.builder()
.setProjectRoot(getFilesystem().getRootPath())
.setCellRoots(getCellPathResolver().getCellPaths())
.setCellName(getCanonicalName().orElse(""))
.setPythonInterpreter(pythonInterpreter)
.setPythonModuleSearchPath(pythonModuleSearchPath)
.setAllowEmptyGlobs(parserConfig.getAllowEmptyGlobs())
.setIgnorePaths(filesystem.getIgnorePaths())
.setBuildFileName(getBuildFileName())
.setDefaultIncludes(parserConfig.getDefaultIncludes())
.setDescriptions(getAllDescriptions())
.setUseWatchmanGlob(useWatchmanGlob)
.setWatchmanGlobStatResults(watchmanGlobStatResults)
.setWatchmanUseGlobGenerator(watchmanUseGlobGenerator)
.setWatchman(watchman)
.setWatchmanQueryTimeoutMs(parserConfig.getWatchmanQueryTimeoutMs())
.setUseMercurialGlob(useMercurialGlob)
.setRawConfig(getBuckConfig().getRawConfigForParser())
.setBuildFileImportWhitelist(parserConfig.getBuildFileImportWhitelist())
.build(),
typeCoercerFactory,
config.getEnvironment(),
eventBus,
new DefaultProcessExecutor(console));
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
Cell that = (Cell) o;
return Objects.equals(filesystem, that.filesystem)
&& config.equalsForDaemonRestart(that.config);
}
@Override
public String toString() {
return String.format("%s filesystem=%s config=%s", super.toString(), filesystem, config);
}
@Override
public int hashCode() {
return hashCode;
}
public CellPathResolver getCellPathResolver() {
return config.getCellPathResolver();
}
public ImmutableSet<Path> getKnownRoots() {
return knownRoots;
}
public void ensureConcreteFilesExist(BuckEventBus eventBus) {
filesystem.ensureConcreteFilesExist(eventBus);
}
@SuppressWarnings("serial")
public static class MissingBuildFileException extends BuildTargetException {
public MissingBuildFileException(BuildTarget buildTarget, BuckConfig buckConfig) {
super(
String.format(
"No build file at %s when resolving target %s.",
buildTarget
.getBasePath()
.resolve(buckConfig.getView(ParserConfig.class).getBuildFileName()),
buildTarget.getFullyQualifiedName()));
}
@Override
public String getHumanReadableErrorMessage() {
return getMessage();
}
}
}