/*
* 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.cxx;
import com.facebook.buck.io.MorePaths;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.nio.file.Path;
import java.util.AbstractMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
public class HeaderPathNormalizer {
private final SourcePathResolver pathResolver;
/**
* A mapping from absolute path of a header path (file or directory) to it's corresponding source
* path.
*/
private final ImmutableMap<Path, SourcePath> headers;
/**
* A mapping of unnormalized header paths that are used by the tooling to the absolute path
* representation of the corresponding source path.
*/
private final ImmutableMap<Path, SourcePath> normalized;
protected HeaderPathNormalizer(
SourcePathResolver pathResolver,
ImmutableMap<Path, SourcePath> headers,
ImmutableMap<Path, SourcePath> normalized) {
this.pathResolver = pathResolver;
this.headers = headers;
this.normalized = normalized;
}
public static HeaderPathNormalizer empty(SourcePathResolver pathResolver) {
return new HeaderPathNormalizer(pathResolver, ImmutableMap.of(), ImmutableMap.of());
}
private static <T> Optional<Map.Entry<Path, T>> pathLookup(Path path, Map<Path, T> map) {
while (path != null) {
T res = map.get(path);
if (res != null) {
return Optional.of(new AbstractMap.SimpleEntry<>(path, res));
}
path = path.getParent();
}
return Optional.empty();
}
public Optional<Path> getAbsolutePathForUnnormalizedPath(Path unnormalizedPath) {
Preconditions.checkArgument(unnormalizedPath.isAbsolute());
Optional<Map.Entry<Path, SourcePath>> result = pathLookup(unnormalizedPath, normalized);
if (!result.isPresent()) {
return Optional.empty();
}
return Optional.of(
pathResolver
.getAbsolutePath(result.get().getValue())
.resolve(result.get().getKey().relativize(unnormalizedPath)));
}
/** @return the {@link SourcePath} which corresponds to the given absolute path. */
public SourcePath getSourcePathForAbsolutePath(Path absolutePath) {
Preconditions.checkArgument(absolutePath.isAbsolute());
Optional<Map.Entry<Path, SourcePath>> path = pathLookup(absolutePath, headers);
Preconditions.checkState(path.isPresent(), "no headers mapped to %s", absolutePath);
return path.get().getValue();
}
public static class Builder {
private final SourcePathResolver pathResolver;
private final Map<Path, SourcePath> headers = new LinkedHashMap<>();
private final Map<Path, SourcePath> normalized = new LinkedHashMap<>();
public Builder(SourcePathResolver pathResolver) {
this.pathResolver = pathResolver;
}
private <V> void put(Map<Path, V> map, Path key, V value) {
Preconditions.checkArgument(key.isAbsolute());
key = MorePaths.normalize(key);
// Hack: Using dropInternalCaches here because `toString` is called on caches by
// PathShortener, and many Paths are constructed and stored due to HeaderPathNormalizer
// containing exported headers of all transitive dependencies of a library. This amounts to
// large memory usage. See t15541313. Once that is fixed, this hack can be deleted.
V previous = map.put(MorePaths.dropInternalCaches(key), value);
Preconditions.checkState(
previous == null || previous.equals(value),
"Expected header path to be consistent but key %s mapped to different values: "
+ "(old: %s, new: %s)",
key,
previous,
value);
}
public Builder addSymlinkTree(SourcePath root, ImmutableMap<Path, SourcePath> headerMap) {
Path rootPath = pathResolver.getAbsolutePath(root);
for (Map.Entry<Path, SourcePath> entry : headerMap.entrySet()) {
addHeader(entry.getValue(), rootPath.resolve(entry.getKey()));
}
return this;
}
public Builder addHeader(SourcePath sourcePath, Path... unnormalizedPaths) {
// Map the relative path of the header path to the header, as we serialize the source path
// using it's relative path.
put(headers, pathResolver.getAbsolutePath(sourcePath), sourcePath);
// Add a normalization mapping for the absolute path.
// We need it for prefix headers and in some rare cases, regular headers will also end up
// with an absolute path in the depfile for a reason we ignore.
put(normalized, pathResolver.getAbsolutePath(sourcePath), sourcePath);
// Add a normalization mapping for any unnormalized paths passed in.
for (Path unnormalizedPath : unnormalizedPaths) {
put(normalized, unnormalizedPath, sourcePath);
}
return this;
}
public Builder addHeaderDir(SourcePath sourcePath) {
return addHeader(sourcePath);
}
public Builder addPrefixHeader(SourcePath sourcePath) {
return addHeader(sourcePath);
}
public HeaderPathNormalizer build() {
return new HeaderPathNormalizer(
pathResolver, ImmutableMap.copyOf(headers), ImmutableMap.copyOf(normalized));
}
}
}