Search in sources :

Example 1 with FilesetOutputSymlink

use of com.google.devtools.build.lib.actions.FilesetOutputSymlink in project bazel by bazelbuild.

the class FilesetEntryFunction method compute.

@Override
public SkyValue compute(SkyKey key, Environment env) throws FilesetEntryFunctionException, InterruptedException {
    FilesetTraversalParams t = (FilesetTraversalParams) key.argument();
    Preconditions.checkState(t.getNestedTraversal().isPresent() != t.getDirectTraversal().isPresent(), "Exactly one of the nested and direct traversals must be specified: %s", t);
    // Create the set of excluded files. Only top-level files can be excluded, i.e. ones that are
    // directly under the root if the root is a directory.
    Set<String> exclusions = createExclusionSet(t.getExcludedFiles());
    // The map of output symlinks. Each key is the path of a output symlink that the Fileset must
    // create, relative to the Fileset.out directory, and each value specifies extra information
    // about the link (its target, associated metadata and again its name).
    Map<PathFragment, FilesetOutputSymlink> outputSymlinks = new LinkedHashMap<>();
    if (t.getNestedTraversal().isPresent()) {
        // The "nested" traversal parameters are present if and only if FilesetEntry.srcdir specifies
        // another Fileset (a "nested" one).
        FilesetEntryValue nested = (FilesetEntryValue) env.getValue(FilesetEntryValue.key(t.getNestedTraversal().get()));
        if (env.valuesMissing()) {
            return null;
        }
        for (FilesetOutputSymlink s : nested.getSymlinks()) {
            if (!exclusions.contains(s.name.getPathString())) {
                maybeStoreSymlink(s, t.getDestPath(), outputSymlinks);
            }
        }
    } else {
        // The "nested" traversal params are absent if and only if the "direct" traversal params are
        // present, which is the case when the FilesetEntry specifies a package's BUILD file, a
        // directory or a list of files.
        // The root of the direct traversal is defined as follows.
        //
        // If FilesetEntry.files is specified, then a TraversalRequest is created for each entry, the
        // root being the respective entry itself. These are all traversed for they may be
        // directories or symlinks to directories, and we need to establish Skyframe dependencies on
        // their contents for incremental correctness. If an entry is indeed a directory (but not when
        // it's a symlink to one) then we have to create symlinks to each of their childen.
        // (NB: there seems to be no good reason for this, it's just how legacy Fileset works. We may
        // want to consider creating a symlink just for the directory and not for its child elements.)
        //
        // If FilesetEntry.files is not specified, then srcdir refers to either a BUILD file or a
        // directory. For the former, the root will be the parent of the BUILD file. For the latter,
        // the root will be srcdir itself.
        DirectTraversal direct = t.getDirectTraversal().get();
        RecursiveFilesystemTraversalValue rftv;
        try {
            // Traverse the filesystem to establish skyframe dependencies.
            rftv = traverse(env, createErrorInfo(t), direct);
        } catch (MissingDepException e) {
            return null;
        }
        // The root can only be absent for the EMPTY rftv instance.
        if (!rftv.getResolvedRoot().isPresent()) {
            return FilesetEntryValue.EMPTY;
        }
        ResolvedFile resolvedRoot = rftv.getResolvedRoot().get();
        // Handle dangling symlinks gracefully be returning empty results.
        if (!resolvedRoot.getType().exists()) {
            return FilesetEntryValue.EMPTY;
        }
        // The prefix to remove is the entire path of the root. This is OK:
        // - when the root is a file, this removes the entire path, but the traversal's destination
        //   path is actually the name of the output symlink, so this works out correctly
        // - when the root is a directory or a symlink to one then we need to strip off the
        //   directory's path from every result (this is how the output symlinks must be created)
        //   before making them relative to the destination path
        PathFragment prefixToRemove = direct.getRoot().getRelativePart();
        Iterable<ResolvedFile> results = null;
        if (direct.isRecursive() || (resolvedRoot.getType().isDirectory() && !resolvedRoot.getType().isSymlink())) {
            // The traversal is recursive (requested for an entire FilesetEntry.srcdir) or it was
            // requested for a FilesetEntry.files entry which turned out to be a directory. We need to
            // create an output symlink for every file in it and all of its subdirectories. Only
            // exception is when the subdirectory is really a symlink to a directory -- no output
            // shall be created for the contents of those.
            // Now we create Dir objects to model the filesystem tree. The object employs a trick to
            // find directory symlinks: directory symlinks have corresponding ResolvedFile entries and
            // are added as files too, while their children, also added as files, contain the path of
            // the parent. Finding and discarding the children is easy if we traverse the tree from
            // root to leaf.
            DirectoryTree root = new DirectoryTree();
            for (ResolvedFile f : rftv.getTransitiveFiles().toCollection()) {
                PathFragment path = f.getNameInSymlinkTree().relativeTo(prefixToRemove);
                if (path.segmentCount() > 0) {
                    path = t.getDestPath().getRelative(path);
                    DirectoryTree dir = root;
                    for (int i = 0; i < path.segmentCount() - 1; ++i) {
                        dir = dir.addOrGetSubdir(path.getSegment(i));
                    }
                    dir.maybeAddFile(f);
                }
            }
            // Here's where the magic happens. The returned iterable will yield all files in the
            // directory that are not under symlinked directories, as well as all directory symlinks.
            results = root.iterateFiles();
        } else {
            // If we're on this branch then the traversal was done for just one entry in
            // FilesetEntry.files (which was not a directory, so it was either a file, a symlink to one
            // or a symlink to a directory), meaning we'll have only one output symlink.
            results = ImmutableList.of(resolvedRoot);
        }
        // Create one output symlink for each entry in the results.
        for (ResolvedFile f : results) {
            // The linkName has to be under the traversal's root, which is also the prefix to remove.
            PathFragment linkName = f.getNameInSymlinkTree().relativeTo(prefixToRemove);
            // It may be dangling, but excluding it is still fine.
            if (exclusions.contains(linkName.getPathString())) {
                continue;
            }
            PathFragment targetName;
            try {
                targetName = f.getTargetInSymlinkTree(direct.isFollowingSymlinks());
            } catch (DanglingSymlinkException e) {
                throw new FilesetEntryFunctionException(e);
            }
            // Metadata field must be present. It can only be absent when stripped by tests.
            String metadata = Integer.toHexString(f.getMetadataHash());
            maybeStoreSymlink(linkName, targetName, metadata, t.getDestPath(), outputSymlinks);
        }
    }
    return FilesetEntryValue.of(ImmutableSet.copyOf(outputSymlinks.values()));
}
Also used : PathFragment(com.google.devtools.build.lib.vfs.PathFragment) DirectTraversal(com.google.devtools.build.lib.actions.FilesetTraversalParams.DirectTraversal) DanglingSymlinkException(com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalFunction.DanglingSymlinkException) LinkedHashMap(java.util.LinkedHashMap) ResolvedFile(com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFile) FilesetTraversalParams(com.google.devtools.build.lib.actions.FilesetTraversalParams) FilesetOutputSymlink(com.google.devtools.build.lib.actions.FilesetOutputSymlink)

Example 2 with FilesetOutputSymlink

use of com.google.devtools.build.lib.actions.FilesetOutputSymlink in project bazel by bazelbuild.

the class FilesetEntryFunctionTest method assertRecursiveTraversalForDirectorySymlink.

private void assertRecursiveTraversalForDirectorySymlink(SymlinkBehavior symlinks, PackageBoundaryMode pkgBoundaryMode) throws Exception {
    Artifact dir = getSourceArtifact("foo/dir_real");
    Artifact symlink = getSourceArtifact("foo/dir_sym");
    createFile(childOf(dir, "file.a"), "blah");
    RootedPath fileAsym = childOf(dir, "subdir/file.a.sym");
    createFile(childOf(dir, "subpkg/BUILD"), "blah");
    createFile(childOf(dir, "subpkg/file.b"), "blah");
    fileAsym.asPath().getParentDirectory().createDirectory();
    fileAsym.asPath().createSymbolicLink(new PathFragment("../file.a"));
    symlink.getPath().createSymbolicLink(new PathFragment("dir_real"));
    FilesetOutputSymlink outA = symlink("output-name/file.a", childOf(symlink, "file.a"));
    FilesetOutputSymlink outASym = null;
    FilesetOutputSymlink outBuild = symlink("output-name/subpkg/BUILD", childOf(symlink, "subpkg/BUILD"));
    FilesetOutputSymlink outB = symlink("output-name/subpkg/file.b", childOf(symlink, "subpkg/file.b"));
    switch(symlinks) {
        case COPY:
            outASym = symlink("output-name/subdir/file.a.sym", "../file.a");
            break;
        case DEREFERENCE:
            outASym = symlink("output-name/subdir/file.a.sym", childOf(dir, "file.a"));
            break;
        default:
            throw new IllegalStateException(symlinks.toString());
    }
    FilesetTraversalParams params = FilesetTraversalParamsFactory.recursiveTraversalOfDirectory(/*ownerLabel=*/
    label("//foo"), /*directoryToTraverse=*/
    symlink, /*destPath=*/
    new PathFragment("output-name"), /*excludes=*/
    null, /*symlinkBehaviorMode=*/
    symlinks, /*pkgBoundaryMode=*/
    pkgBoundaryMode);
    switch(pkgBoundaryMode) {
        case CROSS:
            assertSymlinksInOrder(params, outA, outASym, outBuild, outB);
            break;
        case DONT_CROSS:
            assertSymlinksInOrder(params, outA, outASym);
            break;
        case REPORT_ERROR:
            SkyKey key = FilesetEntryValue.key(params);
            EvaluationResult<SkyValue> result = eval(key);
            assertThat(result.hasError()).isTrue();
            assertThat(result.getError(key).getException().getMessage()).contains("'foo/dir_sym' crosses package boundary into package rooted at foo/dir_sym/subpkg");
            break;
        default:
            throw new IllegalStateException(pkgBoundaryMode.toString());
    }
}
Also used : SkyKey(com.google.devtools.build.skyframe.SkyKey) SkyValue(com.google.devtools.build.skyframe.SkyValue) FilesetTraversalParams(com.google.devtools.build.lib.actions.FilesetTraversalParams) FilesetOutputSymlink(com.google.devtools.build.lib.actions.FilesetOutputSymlink) PathFragment(com.google.devtools.build.lib.vfs.PathFragment) Artifact(com.google.devtools.build.lib.actions.Artifact) RootedPath(com.google.devtools.build.lib.vfs.RootedPath)

Example 3 with FilesetOutputSymlink

use of com.google.devtools.build.lib.actions.FilesetOutputSymlink in project bazel by bazelbuild.

the class FilesetEntryFunctionTest method assertSymlinksInOrder.

private void assertSymlinksInOrder(FilesetTraversalParams request, FilesetOutputSymlink... expectedSymlinks) throws Exception {
    List<FilesetOutputSymlink> expected = Arrays.asList(expectedSymlinks);
    Collection<FilesetOutputSymlink> actual = Collections2.transform(evalFilesetTraversal(request).getSymlinks(), // Strip the metadata from the actual results.
    new Function<FilesetOutputSymlink, FilesetOutputSymlink>() {

        @Override
        public FilesetOutputSymlink apply(FilesetOutputSymlink input) {
            return new FilesetOutputSymlink(input.name, input.target);
        }
    });
    assertThat(actual).containsExactlyElementsIn(expected).inOrder();
}
Also used : FilesetOutputSymlink(com.google.devtools.build.lib.actions.FilesetOutputSymlink)

Example 4 with FilesetOutputSymlink

use of com.google.devtools.build.lib.actions.FilesetOutputSymlink in project bazel by bazelbuild.

the class FilesetEntryFunctionTest method assertRecursiveTraversalForPackage.

private void assertRecursiveTraversalForPackage(SymlinkBehavior symlinks, PackageBoundaryMode pkgBoundaryMode) throws Exception {
    Artifact buildFile = createSourceArtifact("foo/BUILD");
    Artifact subpkgBuildFile = createSourceArtifact("foo/subpkg/BUILD");
    Artifact subpkgSymlink = getSourceArtifact("foo/subpkg_sym");
    RootedPath fileA = createFile(siblingOf(buildFile, "file.a"), "blah");
    RootedPath fileAsym = siblingOf(buildFile, "subdir/file.a.sym");
    RootedPath fileB = createFile(siblingOf(subpkgBuildFile, "file.b"), "blah");
    scratch.dir(fileAsym.asPath().getParentDirectory().getPathString());
    fileAsym.asPath().createSymbolicLink(new PathFragment("../file.a"));
    subpkgSymlink.getPath().createSymbolicLink(new PathFragment("subpkg"));
    FilesetOutputSymlink outBuild = symlink("output-name/BUILD", buildFile);
    FilesetOutputSymlink outA = symlink("output-name/file.a", fileA);
    FilesetOutputSymlink outAsym = null;
    FilesetOutputSymlink outSubpkgBuild = symlink("output-name/subpkg/BUILD", subpkgBuildFile);
    FilesetOutputSymlink outSubpkgB = symlink("output-name/subpkg/file.b", fileB);
    FilesetOutputSymlink outSubpkgSymBuild;
    switch(symlinks) {
        case COPY:
            outAsym = symlink("output-name/subdir/file.a.sym", "../file.a");
            outSubpkgSymBuild = symlink("output-name/subpkg_sym", "subpkg");
            break;
        case DEREFERENCE:
            outAsym = symlink("output-name/subdir/file.a.sym", fileA);
            outSubpkgSymBuild = symlink("output-name/subpkg_sym", getSourceArtifact("foo/subpkg"));
            break;
        default:
            throw new IllegalStateException(symlinks.toString());
    }
    FilesetTraversalParams params = FilesetTraversalParamsFactory.recursiveTraversalOfPackage(/*ownerLabel=*/
    label("//foo"), /*directoryToTraverse=*/
    buildFile, /*destPath=*/
    new PathFragment("output-name"), /*excludes=*/
    null, /*symlinkBehaviorMode=*/
    symlinks, /*pkgBoundaryMode=*/
    pkgBoundaryMode);
    switch(pkgBoundaryMode) {
        case CROSS:
            assertSymlinksInOrder(params, outBuild, outA, outSubpkgSymBuild, outAsym, outSubpkgBuild, outSubpkgB);
            break;
        case DONT_CROSS:
            assertSymlinksInOrder(params, outBuild, outA, outAsym);
            break;
        case REPORT_ERROR:
            SkyKey key = FilesetEntryValue.key(params);
            EvaluationResult<SkyValue> result = eval(key);
            assertThat(result.hasError()).isTrue();
            assertThat(result.getError(key).getException().getMessage()).contains("'foo' crosses package boundary into package rooted at foo/subpkg");
            break;
        default:
            throw new IllegalStateException(pkgBoundaryMode.toString());
    }
}
Also used : SkyKey(com.google.devtools.build.skyframe.SkyKey) SkyValue(com.google.devtools.build.skyframe.SkyValue) FilesetTraversalParams(com.google.devtools.build.lib.actions.FilesetTraversalParams) FilesetOutputSymlink(com.google.devtools.build.lib.actions.FilesetOutputSymlink) PathFragment(com.google.devtools.build.lib.vfs.PathFragment) Artifact(com.google.devtools.build.lib.actions.Artifact) RootedPath(com.google.devtools.build.lib.vfs.RootedPath)

Example 5 with FilesetOutputSymlink

use of com.google.devtools.build.lib.actions.FilesetOutputSymlink in project bazel by bazelbuild.

the class FilesetEntryFunctionTest method assertRecursiveTraversalForDirectory.

private void assertRecursiveTraversalForDirectory(SymlinkBehavior symlinks, PackageBoundaryMode pkgBoundaryMode) throws Exception {
    Artifact dir = getSourceArtifact("foo/dir");
    RootedPath fileA = createFile(childOf(dir, "file.a"), "blah");
    RootedPath fileAsym = childOf(dir, "subdir/file.a.sym");
    RootedPath buildFile = createFile(childOf(dir, "subpkg/BUILD"), "blah");
    RootedPath fileB = createFile(childOf(dir, "subpkg/file.b"), "blah");
    fileAsym.asPath().getParentDirectory().createDirectory();
    fileAsym.asPath().createSymbolicLink(new PathFragment("../file.a"));
    FilesetOutputSymlink outA = symlink("output-name/file.a", childOf(dir, "file.a"));
    FilesetOutputSymlink outAsym = null;
    FilesetOutputSymlink outBuild = symlink("output-name/subpkg/BUILD", buildFile);
    FilesetOutputSymlink outB = symlink("output-name/subpkg/file.b", fileB);
    switch(symlinks) {
        case COPY:
            outAsym = symlink("output-name/subdir/file.a.sym", "../file.a");
            break;
        case DEREFERENCE:
            outAsym = symlink("output-name/subdir/file.a.sym", fileA);
            break;
        default:
            throw new IllegalStateException(symlinks.toString());
    }
    FilesetTraversalParams params = FilesetTraversalParamsFactory.recursiveTraversalOfDirectory(/*ownerLabel=*/
    label("//foo"), /*directoryToTraverse=*/
    dir, /*destPath=*/
    new PathFragment("output-name"), /*excludes=*/
    null, /*symlinkBehaviorMode=*/
    symlinks, /*pkgBoundaryMode=*/
    pkgBoundaryMode);
    switch(pkgBoundaryMode) {
        case CROSS:
            assertSymlinksInOrder(params, outA, outAsym, outBuild, outB);
            break;
        case DONT_CROSS:
            assertSymlinksInOrder(params, outA, outAsym);
            break;
        case REPORT_ERROR:
            SkyKey key = FilesetEntryValue.key(params);
            EvaluationResult<SkyValue> result = eval(key);
            assertThat(result.hasError()).isTrue();
            assertThat(result.getError(key).getException().getMessage()).contains("'foo/dir' crosses package boundary into package rooted at foo/dir/subpkg");
            break;
        default:
            throw new IllegalStateException(pkgBoundaryMode.toString());
    }
}
Also used : SkyKey(com.google.devtools.build.skyframe.SkyKey) SkyValue(com.google.devtools.build.skyframe.SkyValue) FilesetTraversalParams(com.google.devtools.build.lib.actions.FilesetTraversalParams) FilesetOutputSymlink(com.google.devtools.build.lib.actions.FilesetOutputSymlink) PathFragment(com.google.devtools.build.lib.vfs.PathFragment) Artifact(com.google.devtools.build.lib.actions.Artifact) RootedPath(com.google.devtools.build.lib.vfs.RootedPath)

Aggregations

FilesetOutputSymlink (com.google.devtools.build.lib.actions.FilesetOutputSymlink)5 FilesetTraversalParams (com.google.devtools.build.lib.actions.FilesetTraversalParams)4 PathFragment (com.google.devtools.build.lib.vfs.PathFragment)4 Artifact (com.google.devtools.build.lib.actions.Artifact)3 RootedPath (com.google.devtools.build.lib.vfs.RootedPath)3 SkyKey (com.google.devtools.build.skyframe.SkyKey)3 SkyValue (com.google.devtools.build.skyframe.SkyValue)3 DirectTraversal (com.google.devtools.build.lib.actions.FilesetTraversalParams.DirectTraversal)1 DanglingSymlinkException (com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalFunction.DanglingSymlinkException)1 ResolvedFile (com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFile)1 LinkedHashMap (java.util.LinkedHashMap)1