| // Copyright 2021 The Bazel Authors. 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.worker; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.actions.cache.VirtualActionInput; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxInputs; |
| import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxOutputs; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.vfs.Root; |
| import com.google.devtools.build.lib.vfs.RootedPath; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Helper class that sets up a sandbox in a more comprehensible way. Handles setting up |
| * SandboxInputs and SandboxOutputs as well as creating related files. |
| */ |
| class SandboxHelper { |
| |
| /** Map from workdir-relative input path to optional real file path. */ |
| private final Map<PathFragment, RootedPath> inputs = new HashMap<>(); |
| |
| private final Map<VirtualActionInput, byte[]> virtualInputs = new HashMap<>(); |
| private final Map<PathFragment, PathFragment> symlinks = new HashMap<>(); |
| private final Map<PathFragment, Path> workerFiles = new HashMap<>(); |
| private final List<PathFragment> outputFiles = new ArrayList<>(); |
| private final List<PathFragment> outputDirs = new ArrayList<>(); |
| |
| /** The global execRoot. */ |
| final Path execRootPath; |
| |
| final Root execRoot; |
| /** The worker process's sandbox root. */ |
| final Path workDir; |
| |
| public SandboxHelper(Path execRoot, Path workDir) { |
| this.execRootPath = execRoot; |
| this.execRoot = Root.fromPath(execRoot); |
| this.workDir = workDir; |
| } |
| |
| /** |
| * Adds a regular input file at relativePath under {@code workDir}, with the real file at {@code |
| * workspacePath} under {@code execRoot}. |
| */ |
| @CanIgnoreReturnValue |
| public SandboxHelper addInputFile(String relativePath, String workspacePath) { |
| inputs.put( |
| PathFragment.create(relativePath), |
| workspacePath != null |
| ? RootedPath.toRootedPath(execRoot, PathFragment.create(workspacePath)) |
| : null); |
| return this; |
| } |
| |
| /** |
| * Adds a regular input file at relativePath under the {@code workDir}, with the real file at |
| * {@code workspacePath} under {@code execRoot}. The real file gets created immediately and filled |
| * with {@code contents}, which is assumed to be ASCII text. |
| */ |
| @CanIgnoreReturnValue |
| public SandboxHelper addAndCreateInputFile( |
| String relativePath, String workspacePath, String contents) throws IOException { |
| addInputFile(relativePath, workspacePath); |
| Path absPath = execRootPath.getRelative(workspacePath); |
| absPath.getParentDirectory().createDirectoryAndParents(); |
| FileSystemUtils.writeContentAsLatin1(absPath, contents); |
| return this; |
| } |
| |
| /** Adds a virtual input with some contents, which is assumed to be ASCII text. */ |
| @CanIgnoreReturnValue |
| public SandboxHelper addAndCreateVirtualInput(String relativePath, String contents) { |
| VirtualActionInput input = ActionsTestUtil.createVirtualActionInput(relativePath, contents); |
| byte[] digest = |
| execRootPath |
| .getRelative(relativePath) |
| .getFileSystem() |
| .getDigestFunction() |
| .getHashFunction() |
| .hashString(contents, UTF_8) |
| .asBytes(); |
| virtualInputs.put(input, digest); |
| return this; |
| } |
| |
| /** Adds a symlink to the inputs. */ |
| @CanIgnoreReturnValue |
| public SandboxHelper addSymlink(String relativePath, String linkTo) { |
| symlinks.put(PathFragment.create(relativePath), PathFragment.create(linkTo)); |
| return this; |
| } |
| |
| /** Adds an output file without creating it. */ |
| @CanIgnoreReturnValue |
| public SandboxHelper addOutput(String relativePath) { |
| outputFiles.add(PathFragment.create(relativePath)); |
| return this; |
| } |
| |
| /** Adds an output directory without creating it. */ |
| @CanIgnoreReturnValue |
| public SandboxHelper addOutputDir(String relativePath) { |
| outputDirs.add( |
| PathFragment.create(relativePath.endsWith("/") ? relativePath : relativePath + "/")); |
| return this; |
| } |
| |
| /** |
| * Adds a worker file that is created under {@code execRoot} and referenced under the {@code |
| * workDir}. |
| */ |
| @CanIgnoreReturnValue |
| public SandboxHelper addWorkerFile(String relativePath) { |
| Path absPath = execRootPath.getRelative(relativePath); |
| workerFiles.put(PathFragment.create(relativePath), absPath); |
| return this; |
| } |
| |
| /** |
| * Adds a worker file that is created under {@code execRoot} and referenced under the {@code |
| * workDir}. Writes the content, which is assumed to be ASCII text, under {@code execRoot}. |
| */ |
| @CanIgnoreReturnValue |
| public SandboxHelper addAndCreateWorkerFile(String relativePath, String contents) |
| throws IOException { |
| addWorkerFile(relativePath); |
| Path absPath = execRootPath.getRelative(relativePath); |
| absPath.getParentDirectory().createDirectoryAndParents(); |
| FileSystemUtils.writeContentAsLatin1(absPath, contents); |
| return this; |
| } |
| |
| /** |
| * Creates a file with {@code contents}, which is assumed to be ASCII text, at {@code relPath} |
| * under the {@code workDir}. |
| */ |
| @CanIgnoreReturnValue |
| public SandboxHelper createExecRootFile(String relativePath, String contents) throws IOException { |
| Path absPath = workDir.getRelative(relativePath); |
| absPath.getParentDirectory().createDirectoryAndParents(); |
| FileSystemUtils.writeContentAsLatin1(absPath, contents); |
| return this; |
| } |
| |
| /** |
| * Creates a file with {@code contents}, which is assumed to be ASCII text, at {@code relPath} |
| * under the {@code workDir}. |
| */ |
| @CanIgnoreReturnValue |
| public SandboxHelper createWorkspaceDirFile(String workspaceDirPath, String contents) |
| throws IOException { |
| Path absPath = execRootPath.getRelative(workspaceDirPath); |
| absPath.getParentDirectory().createDirectoryAndParents(); |
| FileSystemUtils.writeContentAsLatin1(absPath, contents); |
| return this; |
| } |
| |
| /** |
| * Creates a symlink from within the {@code workDir}. The destination is just what's written into |
| * the symlink and thus relative to the created symlink. |
| */ |
| @CanIgnoreReturnValue |
| public SandboxHelper createSymlink(String relativePath, String relativeDestination) |
| throws IOException { |
| Path fromPath = workDir.getRelative(relativePath); |
| FileSystemUtils.ensureSymbolicLink(fromPath, relativeDestination); |
| return this; |
| } |
| |
| public SandboxInputs getSandboxInputs() { |
| return new SandboxInputs(inputs, virtualInputs, symlinks, ImmutableMap.of()); |
| } |
| |
| public SandboxOutputs getSandboxOutputs() { |
| return SandboxOutputs.create( |
| ImmutableSet.copyOf(this.outputFiles), ImmutableSet.copyOf(this.outputDirs)); |
| } |
| |
| public ImmutableSet<PathFragment> getWorkerFiles() { |
| return ImmutableSet.copyOf(workerFiles.keySet()); |
| } |
| } |