/*
* Copyright 2016-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.config.Config;
import com.facebook.buck.config.Configs;
import com.facebook.buck.io.MorePaths;
import com.facebook.buck.log.Logger;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
public class DefaultCellPathResolver implements CellPathResolver {
private static final Logger LOG = Logger.get(DefaultCellPathResolver.class);
public static final String REPOSITORIES_SECTION = "repositories";
private final Path root;
private final ImmutableMap<String, Path> cellPaths;
private final ImmutableMap<Path, String> canonicalNames;
public DefaultCellPathResolver(Path root, ImmutableMap<String, Path> cellPaths) {
this.root = root;
this.cellPaths = cellPaths;
this.canonicalNames =
cellPaths
.entrySet()
.stream()
.collect(
Collectors.collectingAndThen(
Collectors.toMap(
Map.Entry::getValue,
Map.Entry::getKey,
BinaryOperator.minBy(Comparator.<String>naturalOrder())),
ImmutableMap::copyOf));
}
public DefaultCellPathResolver(Path root, Config config) {
this(root, getCellPathsFromConfigRepositoriesSection(root, config.get(REPOSITORIES_SECTION)));
}
static ImmutableMap<String, Path> getCellPathsFromConfigRepositoriesSection(
Path root, ImmutableMap<String, String> repositoriesSection) {
return ImmutableMap.copyOf(
Maps.transformValues(
repositoriesSection,
input ->
root.resolve(MorePaths.expandHomeDir(root.getFileSystem().getPath(input)))
.normalize()));
}
/**
* Recursively walks configuration files to find all possible {@link Cell} locations.
*
* @return MultiMap of Path to cell name. The map will contain multiple names for a path if that
* cell is reachable through different paths from the current cell.
*/
public ImmutableMap<RelativeCellName, Path> getTransitivePathMapping() {
ImmutableMap.Builder<RelativeCellName, Path> builder = ImmutableMap.builder();
builder.put(RelativeCellName.of(ImmutableList.of()), root);
HashSet<Path> seenPaths = new HashSet<>();
seenPaths.add(root);
constructFullMapping(builder, seenPaths, RelativeCellName.of(ImmutableList.of()), this);
return builder.build();
}
public Path getRoot() {
return root;
}
private ImmutableMap<String, Path> getPartialMapping() {
ImmutableSortedSet<String> sortedCellNames =
ImmutableSortedSet.<String>naturalOrder().addAll(cellPaths.keySet()).build();
ImmutableMap.Builder<String, Path> rootsMap = ImmutableMap.builder();
for (String cellName : sortedCellNames) {
Path cellRoot = Preconditions.checkNotNull(getCellPath(cellName));
rootsMap.put(cellName, cellRoot);
}
return rootsMap.build();
}
private static void constructFullMapping(
ImmutableMap.Builder<RelativeCellName, Path> result,
Set<Path> pathStack,
RelativeCellName parentCellPath,
DefaultCellPathResolver parentStub) {
ImmutableMap<String, Path> partialMapping = parentStub.getPartialMapping();
for (Map.Entry<String, Path> entry : partialMapping.entrySet()) {
Path cellRoot = entry.getValue().normalize();
try {
cellRoot = cellRoot.toRealPath().normalize();
} catch (IOException e) {
LOG.warn("cellroot [" + cellRoot + "] does not exist in filesystem");
}
// Do not recurse into previously visited Cell roots. It's OK for cell references to form
// cycles as long as the targets don't form a cycle.
// We intentionally allow for the map to contain entries whose Config objects can't be
// created. These are still technically reachable and will not cause problems as long as none
// of the BuildTargets in the build reference them.
if (pathStack.contains(cellRoot)) {
continue;
}
pathStack.add(cellRoot);
RelativeCellName relativeCellName = parentCellPath.withAppendedComponent(entry.getKey());
result.put(relativeCellName, cellRoot);
Config config;
try {
// We don't support overriding repositories from the command line so creating the config
// with no overrides is OK.
config = Configs.createDefaultConfig(cellRoot);
} catch (IOException e) {
LOG.debug(e, "Error when constructing cell, skipping path %s", cellRoot);
continue;
}
constructFullMapping(
result, pathStack, relativeCellName, new DefaultCellPathResolver(cellRoot, config));
pathStack.remove(cellRoot);
}
}
private Path getCellPath(String cellName) {
Path path = cellPaths.get(cellName);
if (path == null) {
throw new HumanReadableException(
"In cell rooted at %s: cell named '%s' is not defined.", root, cellName);
}
return path;
}
@Override
public Path getCellPath(Optional<String> cellName) {
if (!cellName.isPresent()) {
return root;
}
return getCellPath(cellName.get());
}
@Override
public ImmutableMap<String, Path> getCellPaths() {
return cellPaths;
}
@Override
public Optional<String> getCanonicalCellName(Path cellPath) {
if (cellPath.equals(root)) {
return Optional.empty();
} else {
String name = canonicalNames.get(cellPath);
if (name == null) {
throw new IllegalArgumentException("Unknown cell path: " + cellPath);
}
return Optional.of(name);
}
}
}