blob: cfc915c49a54226261525e44e5acf258138fce94 [file] [log] [blame]
// Copyright 2025 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.skyframe;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.devtools.build.lib.skyframe.FileOpNodeOrFuture.EmptyFileOpNode.EMPTY_FILE_OP_NODE;
import com.google.devtools.build.lib.skyframe.FileOpNodeOrFuture.FileOpNode;
import com.google.devtools.build.lib.skyframe.FileOpNodeOrFuture.FileOpNodeOrEmpty;
import java.util.Collection;
import javax.annotation.Nullable;
/**
* Represents a collection of {@link FileOpNode}s, allowing for nested structures to represent
* complex file dependencies.
*
* <p>This class serves as a container for multiple {@link FileOpNode} instances, enabling the
* representation of file operation dependencies in a hierarchical manner. It differentiates between
* analysis dependencies (for example, BUILD and .bzl files) and "source" dependencies, used during
* execution (for example, .cpp, .h or .java files). It keeps them together to optimize storage.
*
* <p><b>Source vs. Analysis Dependencies:</b>
*
* <ul>
* <li><b>Analysis:</b> During the analysis phase, source files are declared, but configured
* targets (which define actions) do not depend on the <i>contents</i> of these source files,
* for example, .cpp, .h or .java files.
* <li><b>Execution:</b> The execution phase creates actual dependencies on the contents of source
* files as actions are run.
* </ul>
*
* <p><b>Why combine them?</b> <br>
* Logically, source and analysis dependencies could be tracked separately with different {@link
* FileOpNode}s. However, this would duplicate the dependency graph structure in persistent storage,
* which is expensive. This class keeps them together, trading off a bit of complexity for reduced
* storage overhead. The structure is written only once, and the interpretation of dependencies must
* be handled by the client.
*
* <p><b>Subclasses:</b>
*
* <ul>
* <li>{@link NestedFileOpNodes}: Represents a set of {@link FileOpNode}s without any immediate
* source file dependencies.
* <li>{@link NestedFileOpNodesWithSources}: Represents a set of {@link FileOpNode}s along with a
* list of immediate source file dependencies ({@link FileKey}s).
* </ul>
*/
public abstract sealed class AbstractNestedFileOpNodes implements FileOpNodeOrFuture.FileOpNode
permits AbstractNestedFileOpNodes.NestedFileOpNodes,
AbstractNestedFileOpNodes.NestedFileOpNodesWithSources {
private final FileOpNode[] analysisDependencies;
/**
* Opaque storage for use by serialization.
*
* <p>{@link FileOpNode}, {@link FileKey} and {@link DirectoryListingKey} are mutually dependent
* via {@link FileOpNode}. This type is opaque to avoid forcing {@link FileKey} and {@link
* DirectoryListingKey} to depend on serialization implementation code.
*
* <p>The serialization implementation initializes this field with double-checked locking so it is
* marked volatile.
*/
private volatile Object serializationScratch;
/**
* Effectively, a factory method for {@link NestedFileOpNodes}, but formally a factory method for
* {@link FileOpNodeOrEmpty}.
*
* <p>Returns {@link EMPTY_FILE_OP_NODE} if {@code analysisDependencies} is empty. When {@code
* analysisDependencies} contains only one node, returns the node directly instead of wrapping it.
* Otherwise, returns a {@link NestedFileOpNodes} instance wrapping {@code analysisDependencies}.
*/
public static FileOpNodeOrEmpty from(Collection<FileOpNode> analysisDependencies) {
if (analysisDependencies.isEmpty()) {
return EMPTY_FILE_OP_NODE;
}
if (analysisDependencies.size() == 1) {
return analysisDependencies.iterator().next();
}
return new NestedFileOpNodes(analysisDependencies.toArray(FileOpNode[]::new));
}
/**
* Creates {@link NestedFileOpNodesWithSources} with reductions similar to {@link
* #from(Collection<FileOpNode>)}.
*/
public static FileOpNodeOrEmpty from(
Collection<FileOpNode> analysisDependencies, Collection<FileKey> sources) {
if (sources.isEmpty()) {
return from(analysisDependencies);
}
// It's unclear if `analysisDependencies` can ever be empty here in practice, but it's
// permitted. It should be rare enough that defining a special type for it isn't worth it.
return new NestedFileOpNodesWithSources(
analysisDependencies.toArray(FileOpNode[]::new), sources.toArray(FileKey[]::new));
}
private AbstractNestedFileOpNodes(FileOpNode[] analysisDependencies) {
this.analysisDependencies = analysisDependencies;
}
public int analysisDependenciesCount() {
return analysisDependencies.length;
}
public FileOpNode getAnalysisDependency(int index) {
return analysisDependencies[index];
}
@Nullable
public Object getSerializationScratch() {
return serializationScratch;
}
public void setSerializationScratch(Object value) {
this.serializationScratch = value;
}
/** A set of {@link FileOpNode}s with no immediate source dependencies. */
public static final class NestedFileOpNodes extends AbstractNestedFileOpNodes {
private NestedFileOpNodes(FileOpNode[] analysisDependencies) {
super(analysisDependencies);
checkArgument(analysisDependencies.length > 0);
}
}
/** A set of analysis and source file dependencies. */
public static final class NestedFileOpNodesWithSources extends AbstractNestedFileOpNodes {
private final FileKey[] sources; // never empty
private NestedFileOpNodesWithSources(FileOpNode[] nodes, FileKey[] sources) {
super(nodes);
this.sources = sources;
}
public int sourceCount() {
return sources.length;
}
public FileKey getSource(int i) {
return sources[i];
}
}
}