// Copyright 2014 Google Inc. 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.skyframe;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.PackageIdentifier;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.vfs.Dirent;
import com.google.devtools.build.lib.vfs.Dirent.Type;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/**
* RecursivePkgFunction builds up the set of packages underneath a given directory
* transitively.
*
*
Example: foo/BUILD, foo/sub/x, foo/subpkg/BUILD would yield transitive packages "foo" and
* "foo/subpkg".
*/
public class RecursivePkgFunction implements SkyFunction {
private static final Order ORDER = Order.STABLE_ORDER;
@Override
public SkyValue compute(SkyKey skyKey, Environment env) {
RootedPath rootedPath = (RootedPath) skyKey.argument();
Path root = rootedPath.getRoot();
PathFragment rootRelativePath = rootedPath.getRelativePath();
SkyKey fileKey = FileValue.key(rootedPath);
FileValue fileValue = (FileValue) env.getValue(fileKey);
if (fileValue == null) {
return null;
}
if (!fileValue.isDirectory()) {
return new RecursivePkgValue(NestedSetBuilder.emptySet(ORDER));
}
if (fileValue.isSymlink()) {
// We do not follow directory symlinks when we look recursively for packages. It also
// prevents symlink loops.
return new RecursivePkgValue(NestedSetBuilder.emptySet(ORDER));
}
PackageIdentifier packageId = PackageIdentifier.createInDefaultRepo(
rootRelativePath.getPathString());
PackageLookupValue pkgLookupValue =
(PackageLookupValue) env.getValue(PackageLookupValue.key(packageId));
if (pkgLookupValue == null) {
return null;
}
NestedSetBuilder packages = new NestedSetBuilder<>(ORDER);
if (pkgLookupValue.packageExists()) {
if (pkgLookupValue.getRoot().equals(root)) {
try {
PackageValue pkgValue = (PackageValue)
env.getValueOrThrow(PackageValue.key(packageId),
NoSuchPackageException.class);
if (pkgValue == null) {
return null;
}
packages.add(pkgValue.getPackage().getName());
} catch (NoSuchPackageException e) {
// The package had errors, but don't fail-fast as there might subpackages below the
// current directory.
env.getListener().handle(Event.error(
"package contains errors: " + rootRelativePath.getPathString()));
if (e.getPackage() != null) {
packages.add(e.getPackage().getName());
}
}
}
// The package lookup succeeded, but was under a different root. We still, however, need to
// recursively consider subdirectories. For example:
//
// Pretend --package_path=rootA/workspace:rootB/workspace and these are the only files:
// rootA/workspace/foo/
// rootA/workspace/foo/bar/BUILD
// rootB/workspace/foo/BUILD
// If we're doing a recursive package lookup under 'rootA/workspace' starting at 'foo', note
// that even though the package 'foo' is under 'rootB/workspace', there is still a package
// 'foo/bar' under 'rootA/workspace'.
}
DirectoryListingValue dirValue = (DirectoryListingValue)
env.getValue(DirectoryListingValue.key(rootedPath));
if (dirValue == null) {
return null;
}
List childDeps = Lists.newArrayList();
for (Dirent dirent : dirValue.getDirents()) {
if (dirent.getType() != Type.DIRECTORY) {
// Non-directories can never host packages, and we do not follow symlinks (see above).
continue;
}
String basename = dirent.getName();
if (rootRelativePath.equals(PathFragment.EMPTY_FRAGMENT)
&& PathPackageLocator.DEFAULT_TOP_LEVEL_EXCLUDES.contains(basename)) {
continue;
}
SkyKey req = RecursivePkgValue.key(RootedPath.toRootedPath(root,
rootRelativePath.getRelative(basename)));
childDeps.add(req);
}
Map childValueMap = env.getValues(childDeps);
if (env.valuesMissing()) {
return null;
}
// Aggregate the transitive subpackages.
for (SkyValue childValue : childValueMap.values()) {
if (childValue != null) {
packages.addTransitive(((RecursivePkgValue) childValue).getPackages());
}
}
return new RecursivePkgValue(packages.build());
}
@Nullable
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
}