/* * 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.io.ArchiveMemberPath; import com.facebook.buck.io.MorePaths; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.HasOutputName; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreCollectors; import com.google.common.base.Preconditions; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Maps; import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; public class SourcePathResolver { private final SourcePathRuleFinder ruleFinder; public SourcePathResolver(SourcePathRuleFinder ruleFinder) { this.ruleFinder = ruleFinder; } public <T> ImmutableMap<T, Path> getMappedPaths(Map<T, SourcePath> sourcePathMap) { ImmutableMap.Builder<T, Path> paths = ImmutableMap.builder(); for (ImmutableMap.Entry<T, SourcePath> entry : sourcePathMap.entrySet()) { paths.put(entry.getKey(), getAbsolutePath(entry.getValue())); } return paths.build(); } /** @return the {@link ProjectFilesystem} associated with {@code sourcePath}. */ public ProjectFilesystem getFilesystem(SourcePath sourcePath) { if (sourcePath instanceof PathSourcePath) { return ((PathSourcePath) sourcePath).getFilesystem(); } if (sourcePath instanceof BuildTargetSourcePath) { return ruleFinder.getRuleOrThrow((BuildTargetSourcePath) sourcePath).getProjectFilesystem(); } if (sourcePath instanceof ArchiveMemberSourcePath) { return getFilesystem(((ArchiveMemberSourcePath) sourcePath).getArchiveSourcePath()); } throw new IllegalStateException(); } /** * @return the {@link Path} for this {@code sourcePath}, resolved using its associated {@link * com.facebook.buck.io.ProjectFilesystem}. */ public Path getAbsolutePath(SourcePath sourcePath) { Path path = getPathPrivateImpl(sourcePath); if (path.isAbsolute()) { return path; } if (sourcePath instanceof BuildTargetSourcePath) { BuildRule rule = ruleFinder.getRuleOrThrow((BuildTargetSourcePath) sourcePath); return rule.getProjectFilesystem().resolve(path); } else if (sourcePath instanceof PathSourcePath) { return ((PathSourcePath) sourcePath).getFilesystem().resolve(path); } else { throw new UnsupportedOperationException(sourcePath.getClass() + " is not supported here!"); } } public ArchiveMemberPath getAbsoluteArchiveMemberPath(SourcePath sourcePath) { Preconditions.checkState(sourcePath instanceof ArchiveMemberSourcePath); ArchiveMemberSourcePath archiveMemberSourcePath = (ArchiveMemberSourcePath) sourcePath; Path archiveAbsolutePath = getAbsolutePath(archiveMemberSourcePath.getArchiveSourcePath()); return ArchiveMemberPath.of(archiveAbsolutePath, archiveMemberSourcePath.getMemberPath()); } public ArchiveMemberPath getRelativeArchiveMemberPath(SourcePath sourcePath) { Preconditions.checkState(sourcePath instanceof ArchiveMemberSourcePath); ArchiveMemberSourcePath archiveMemberSourcePath = (ArchiveMemberSourcePath) sourcePath; Path archiveRelativePath = getRelativePath(archiveMemberSourcePath.getArchiveSourcePath()); return ArchiveMemberPath.of(archiveRelativePath, archiveMemberSourcePath.getMemberPath()); } public ImmutableSortedSet<Path> getAllAbsolutePaths( Collection<? extends SourcePath> sourcePaths) { return sourcePaths .stream() .map(this::getAbsolutePath) .collect(MoreCollectors.toImmutableSortedSet()); } /** * @return The {@link Path} the {@code sourcePath} refers to, relative to its owning {@link * com.facebook.buck.io.ProjectFilesystem}. */ public Path getRelativePath(SourcePath sourcePath) { Path toReturn = getPathPrivateImpl(sourcePath); Preconditions.checkState( !toReturn.isAbsolute(), "Expected path to be relative, not absolute: %s (from %s)", toReturn, sourcePath); return toReturn; } /** * @return The {@link Path} the {@code sourcePath} refers to, ideally relative to its owning * {@link com.facebook.buck.io.ProjectFilesystem}. Absolute path may get returned however! * <p>We should make sure that {@link #getPathPrivateImpl} always returns a relative path * after which we should simply call {@link #getRelativePath}. Until then we still need this * nonsense. */ public Path getIdeallyRelativePath(SourcePath sourcePath) { return getPathPrivateImpl(sourcePath); } /** * @return the {@link SourcePath} as a {@link Path}, with no guarantee whether the return value is * absolute or relative. This should never be exposed to users. */ private Path getPathPrivateImpl(SourcePath sourcePath) { if (sourcePath instanceof PathSourcePath) { return ((PathSourcePath) sourcePath).getRelativePath(); } else if (sourcePath instanceof ExplicitBuildTargetSourcePath) { return ((ExplicitBuildTargetSourcePath) sourcePath).getResolvedPath(); } else if (sourcePath instanceof ForwardingBuildTargetSourcePath) { return getPathPrivateImpl(((ForwardingBuildTargetSourcePath) sourcePath).getDelegate()); } else if (sourcePath instanceof DefaultBuildTargetSourcePath) { DefaultBuildTargetSourcePath targetSourcePath = (DefaultBuildTargetSourcePath) sourcePath; SourcePath path = ruleFinder.getRuleOrThrow(targetSourcePath).getSourcePathToOutput(); if (path == null) { throw new HumanReadableException("No known output for: %s", targetSourcePath.getTarget()); } return getPathPrivateImpl(path); } else { throw new UnsupportedOperationException(sourcePath.getClass() + " is not supported here!"); } } /** * Resolved the logical names for a group of SourcePath objects into a map, throwing an error on * duplicates. */ public ImmutableMap<String, SourcePath> getSourcePathNames( BuildTarget target, String parameter, Iterable<SourcePath> sourcePaths) { return getSourcePathNames(target, parameter, sourcePaths, x -> true, x -> x); } /** * Resolves the logical names for a group of objects that have a SourcePath into a map, throwing * an error on duplicates. */ public <T> ImmutableMap<String, T> getSourcePathNames( BuildTarget target, String parameter, Iterable<T> objects, Predicate<T> filter, Function<T, SourcePath> objectSourcePathFunction) { Map<String, T> resolved = Maps.newLinkedHashMap(); for (T object : objects) { if (filter.test(object)) { SourcePath path = objectSourcePathFunction.apply(object); String name = getSourcePathName(target, path); T old = resolved.put(name, object); if (old != null) { throw new HumanReadableException( String.format( "%s: parameter '%s': duplicate entries for '%s'", target, parameter, name)); } } } return ImmutableMap.copyOf(resolved); } public String getSourcePathName(BuildTarget target, SourcePath sourcePath) { Preconditions.checkArgument(!(sourcePath instanceof ArchiveMemberSourcePath)); if (sourcePath instanceof BuildTargetSourcePath) { BuildRule rule = ruleFinder.getRuleOrThrow((BuildTargetSourcePath) sourcePath); if (rule instanceof HasOutputName) { HasOutputName hasOutputName = (HasOutputName) rule; return hasOutputName.getOutputName(); } if (sourcePath instanceof ForwardingBuildTargetSourcePath) { ForwardingBuildTargetSourcePath castPath = (ForwardingBuildTargetSourcePath) sourcePath; return getSourcePathName(target, castPath.getDelegate()); } else if (sourcePath instanceof ExplicitBuildTargetSourcePath) { Path path = ((ExplicitBuildTargetSourcePath) sourcePath).getResolvedPath(); if (path.startsWith(rule.getProjectFilesystem().getBuckPaths().getGenDir())) { path = rule.getProjectFilesystem().getBuckPaths().getGenDir().relativize(path); } if (path.startsWith(rule.getBuildTarget().getBasePath())) { return rule.getBuildTarget().getBasePath().relativize(path).toString(); } } return rule.getBuildTarget().getShortName(); } Preconditions.checkArgument(sourcePath instanceof PathSourcePath); Path path = ((PathSourcePath) sourcePath).getRelativePath(); return MorePaths.relativize(target.getBasePath(), path).toString(); } /** * Takes an {@link Iterable} of {@link SourcePath} objects and filters those that represent {@link * Path}s. */ public ImmutableCollection<Path> filterInputsToCompareToOutput( Iterable<? extends SourcePath> sources) { // Currently, the only implementation of SourcePath that should be included in the Iterable // returned by getInputsToCompareToOutput() is FileSourcePath, so it is safe to filter by that // and then use .asReference() to get its path. // // BuildTargetSourcePath should not be included in the output because it refers to a generated // file, and generated files are not hashed as part of a RuleKey. return FluentIterable.from(sources) .filter(PathSourcePath.class) .transform(PathSourcePath::getRelativePath) .toList(); } public ImmutableCollection<Path> filterInputsToCompareToOutput(SourcePath... sources) { return filterInputsToCompareToOutput(Arrays.asList(sources)); } /** @return the {@link PathSourcePath} backing the given {@link SourcePath}, if any. */ public Optional<PathSourcePath> getPathSourcePath(SourcePath sourcePath) { if (sourcePath instanceof ArchiveMemberSourcePath) { sourcePath = ((ArchiveMemberSourcePath) sourcePath).getArchiveSourcePath(); } return sourcePath instanceof PathSourcePath ? Optional.of((PathSourcePath) sourcePath) : Optional.empty(); } }