| // Copyright 2020 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.bazel.rules.ninja.actions; |
| |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableSortedMap; |
| import com.google.common.collect.ImmutableSortedSet; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact; |
| import com.google.devtools.build.lib.actions.ArtifactRoot; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.bazel.rules.ninja.file.GenericParsingException; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| |
| /** |
| * Helper class to create artifacts for {@link NinjaAction} to be used from {@link NinjaGraphRule}. |
| * All created output artifacts are accumulated in the NestedSetBuilder. |
| * |
| * <p>Input and putput paths are interpreted relative to the working directory, see |
| * working_directory property in {@link NinjaGraphRule}. All output artifact are created under the |
| * derived artifacts root <execroot>/<outputRoot>, see output_root property in {@link |
| * NinjaGraphRule}. |
| */ |
| class NinjaGraphArtifactsHelper { |
| private final RuleContext ruleContext; |
| private final PathFragment outputRootPath; |
| private final PathFragment workingDirectory; |
| private final ArtifactRoot derivedOutputRoot; |
| |
| private final ImmutableSortedMap<PathFragment, Artifact> symlinkPathToArtifact; |
| private final ImmutableSortedSet<PathFragment> outputRootSymlinks; |
| |
| /** |
| * Constructor |
| * |
| * @param ruleContext parent NinjaGraphRule rule context |
| * @param outputRootPath name of output directory for Ninja actions under execroot |
| * @param workingDirectory relative path under execroot, the root for interpreting all paths in |
| * Ninja file |
| * @param symlinkPathToArtifact mapping of paths to artifacts for input symlinks under output_root |
| * @param outputRootSymlinks list of output paths for which symlink artifacts should be created, |
| * paths are relative to the output_root. |
| */ |
| NinjaGraphArtifactsHelper( |
| RuleContext ruleContext, |
| PathFragment outputRootPath, |
| PathFragment workingDirectory, |
| ImmutableSortedMap<PathFragment, Artifact> symlinkPathToArtifact, |
| ImmutableSortedSet<PathFragment> outputRootSymlinks) { |
| this.ruleContext = ruleContext; |
| this.outputRootPath = outputRootPath; |
| this.workingDirectory = workingDirectory; |
| this.symlinkPathToArtifact = symlinkPathToArtifact; |
| this.outputRootSymlinks = outputRootSymlinks; |
| Path execRoot = |
| Preconditions.checkNotNull(ruleContext.getConfiguration()) |
| .getDirectories() |
| .getExecRoot(ruleContext.getWorkspaceName()); |
| this.derivedOutputRoot = |
| ArtifactRoot.asDerivedRoot(execRoot, outputRootPath.getSegments().toArray(new String[0])); |
| } |
| |
| DerivedArtifact createOutputArtifact(PathFragment pathRelativeToWorkingDirectory) |
| throws GenericParsingException { |
| PathFragment execPath = workingDirectory.getRelative(pathRelativeToWorkingDirectory); |
| |
| if (!execPath.startsWith(outputRootPath)) { |
| throw new GenericParsingException( |
| String.format( |
| "Ninja actions are allowed to create outputs only under output_root," |
| + " path '%s' is not allowed.", |
| pathRelativeToWorkingDirectory)); |
| } |
| // If the path was declared as output symlink, create a symlink artifact. |
| if (outputRootSymlinks.contains(execPath.relativeTo(outputRootPath))) { |
| return ruleContext |
| .getAnalysisEnvironment() |
| .getSymlinkArtifact(execPath.relativeTo(outputRootPath), derivedOutputRoot); |
| } |
| return ruleContext.getDerivedArtifact(execPath.relativeTo(outputRootPath), derivedOutputRoot); |
| } |
| |
| Artifact getInputArtifact(PathFragment workingDirectoryPath) throws GenericParsingException { |
| if (symlinkPathToArtifact.containsKey(workingDirectoryPath)) { |
| return symlinkPathToArtifact.get(workingDirectoryPath); |
| } |
| |
| PathFragment execPath = workingDirectory.getRelative(workingDirectoryPath); |
| if (execPath.startsWith(outputRootPath)) { |
| // In the output directory, so it is either specified via output_root_symlinks, or |
| // it is a derived artifact. |
| if (outputRootSymlinks.contains(execPath.relativeTo(outputRootPath))) { |
| return ruleContext |
| .getAnalysisEnvironment() |
| .getSymlinkArtifact(execPath.relativeTo(outputRootPath), derivedOutputRoot); |
| } |
| return ruleContext.getDerivedArtifact(execPath.relativeTo(outputRootPath), derivedOutputRoot); |
| } |
| |
| if (!execPath.startsWith(ruleContext.getPackageDirectory())) { |
| throw new GenericParsingException( |
| String.format( |
| "Source artifact '%s' is not under the package of the ninja_build rule", execPath)); |
| } |
| |
| // Not a derived artifact. Create a corresponding source artifact. This isn't really great |
| // because we have no guarantee that the artifact is not under a different package which |
| // invalidates the guarantee of "bazel query" that the dependencies reported for a target are a |
| // superset of all possible targets that are needed to build it, worse yet, there isn't even a |
| // guarantee that there isn't a package on a different package path in between. |
| // |
| // For example, if the ninja_build rule is in a/BUILD and has a file a/b/c, it's possible that |
| // there is a BUILD file a/b/BUILD and thus the source file a/b/c is created from the package |
| // //a even though package //a/b exists (violating the above "bazel query" invariant) and it can |
| // be that a/b/BUILD is on a different package path entry (is not correct because the other |
| // package path entry can contain a *different* source file whose execpath is a/b/c) |
| // |
| // TODO(lberki): Check whether there is a package in between from another package path entry. |
| // We probably can't prohibit packages in between, though. Schade. |
| return ruleContext |
| .getAnalysisEnvironment() |
| .getSourceArtifactForNinjaBuild( |
| execPath, ruleContext.getRule().getPackage().getSourceRoot()); |
| } |
| |
| public PathFragment getOutputRootPath() { |
| return outputRootPath; |
| } |
| |
| public PathFragment getWorkingDirectory() { |
| return workingDirectory; |
| } |
| } |