// Copyright 2024 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.
//
// This file contains the protocol buffer representation of a list of supported
// flags for Bazel commands.
syntax = "proto3";

package bazel_output_service;

import "google/protobuf/any.proto";
import "google/rpc/status.proto";

option java_package = "com.google.devtools.build.lib.remote";
option java_outer_classname = "BazelOutputServiceProto";
option go_package = "bazeloutputservice";

// The Bazel Output Service may be used by users of the Remote Execution API to
// assist Bazel in constructing the output tree: a directory on the local system
// containing all output files produced by a build.
//
// Primitive implementations of this API may simply download files from the
// Content Addressable Storage (CAS) and store them at their designated
// location. Complex implementations may use a pseudo file system (e.g., FUSE)
// to support deduplication, lazy loading and snapshotting.
//
// Details:
// https://github.com/bazelbuild/proposals/blob/master/designs/2021-02-09-remote-output-service.md
// https://groups.google.com/g/remote-execution-apis/c/qOSWWwBLPzo
// https://groups.google.com/g/bazel-dev/c/lKzENsNd1Do
// https://docs.google.com/document/d/1W6Tqq8cndssnDI0yzFSoj95oezRKcIhU57nwLHaN1qk/edit
service BazelOutputService {
  // Clean all data associated with an output tree, so that the next invocation
  // of StartBuild() yields an empty output tree. This MAY be implemented in a
  // way that's faster than removing the contents manually.
  rpc Clean(CleanRequest) returns (CleanResponse);

  // Signal that a new build is about to start.
  //
  // Bazel uses this call to obtain a directory where outputs of the build may
  // be stored, called the output tree. Based on the parameters provided, the
  // server may provide an empty output tree, or one containing the result of
  // a previous build of the same workspace.
  //
  // In case the output tree contains data from a previous build, server is
  // responsible for calling ContentAddressableStorage.FindMissingBlobs() for
  // all of the objects that are stored remotely. This ensures that these
  // objects don't disappear from the Content Addressable Storage during the
  // build. Objects that are no longer available MUST be removed from the output
  // tree and reported through InitialOutputPathContents.modified_path_prefixes,
  // unless the field has been omitted because it would have been too large.
  rpc StartBuild(StartBuildRequest) returns (StartBuildResponse);

  // Stage build artifacts at the given paths with digests that are known to the
  // Content Addressable Storage. The file contents MAY be lazily downloaded
  // when they're accessed in the future, and are not guaranteed to have already
  // been downloaded upon return.
  rpc StageArtifacts(StageArtifactsRequest) returns (StageArtifactsResponse);

  // Notify the server that a set of paths are not expected to be modified
  // further by Bazel or a local build action within the current build.
  //
  // For each of these paths, the server MAY decide to store a dirty bit,
  // initially unset. Any subsequent modification, deletion or recreation of
  // that path causes the dirty bit to be set. If the server chooses to store
  // the dirty bit, any paths with the dirty bit set MUST be reported back in
  // the next InitialOutputPathContents.modified_path_prefixes.
  //
  // As an alternative to tracking modifications via a dirty bit, a server MAY
  // choose to freeze finalized paths, preventing them from being modified
  // until the next StartBuildRequest or CleanRequest.
  rpc FinalizeArtifacts(FinalizeArtifactsRequest)
      returns (FinalizeArtifactsResponse);

  // Signal that a build has been completed.
  rpc FinalizeBuild(FinalizeBuildRequest) returns (FinalizeBuildResponse);

  // Obtain the status of one or more files, directories or symbolic links
  // stored in the output tree.
  rpc BatchStat(BatchStatRequest) returns (BatchStatResponse);
}

message CleanRequest {
  // The workspace identifier that was provided to
  // StartBuildRequest.output_base_id whose data needs to be removed.
  string output_base_id = 1;
}

message CleanResponse {
  // Intentionally empty for future extensibility.
}

message StartBuildRequest {
  // The version of the protocol Bazel currently uses. The service MUST return
  // an error if it doesn't recognize the version.
  //
  // A future Bazel version may introduce incompatible changes and increment
  // this version number. The incompatible change will be first made in the
  // development tree for the next major Bazel release, and the new version thus
  // introduced should be considered unstable until that major Bazel release
  // occurs. At that time, the new version becomes stable, and the old one is
  // immediately retired.
  //
  // In particular, version 1 must not be considered stable until Bazel 8.0.0 is
  // released.
  //
  // Current version: 1 (experimental).
  int32 version = 1;

  // A client-chosen value that uniquely identifies the workspace where a build
  // is being started. Each workspace corresponds to a separately managed output
  // tree. This value MAY be used by the server to manage multiple output trees
  // concurrently.
  //
  // Bazel sets this value to the MD5 sum of the absolute path of the output
  // base.
  //
  // Starting a build finalizes any previous build with the same output_base_id
  // that has not been finalized yet as if a FinalizeBuildRequest had been sent
  // with build_successful set to false.
  string output_base_id = 2;

  // A client-chosen value that uniquely identifies this build. This value must
  // be provided to most other methods to ensure that operations are targeted
  // against the right output tree. If the server receives a subsequent request
  // with a non-matching build_id, it SHOULD send back an error response.
  //
  // Bazel sets this value to --invocation_id.
  string build_id = 3;

  // Additional arguments to pass depending on how Bazel communicates with the
  // Content Addressable Storage.
  //
  // In case of a REv2 CAS, the type is
  // [StartBuildArgs][bazel_output_service_rev2.StartBuildArgs].
  google.protobuf.Any args = 4;

  // The absolute path at which the server exposes its output tree, as seen from
  // the perspective of the client.
  //
  // This value needs to be provided by the client because file system namespace
  // virtualization may cause this directory to appear at different locations
  // as seen by the client and the server.
  //
  // In addition, this field is used to ensure the server is capable of
  // expanding symbolic links containing absolute paths into the output tree.
  //
  // If unset or empty, the server must determine where to expose its output
  // tree and return an absolute path in StartBuildResponse.output_path_suffix.
  string output_path_prefix = 5;

  // A map of paths on the system that will become symbolic links pointing to
  // locations inside the output tree. Similar to output_path_prefix, this
  // option is used to ensure the server is capable of expanding additional
  // symbolic links.
  //
  // Map keys are absolute paths, while map values are paths that are
  // relative to the output tree.
  map<string, string> output_path_aliases = 6;
}

message StartBuildResponse {
  // If set, the contents of the output tree are almost entirely identical on
  // the results of a previous build. This information may be used by Bazel to
  // prevent unnecessary scanning of the file system.
  //
  // The server MUST leave this field unset in case the contents of the output
  // path are empty, not based on a previous build, if no modification tracking
  // is performed. It MAY leave it unset if the number of changes made to the
  // output path is too large to be expressed.
  InitialOutputPathContents initial_output_path_contents = 1;

  // A path that the client must append to StartBuildRequest.output_path_prefix
  // to obtain the full path at which the output tree is available.
  //
  // Bazel replaces bazel-out/ with a symlink targeting this path.
  string output_path_suffix = 2;
}

message InitialOutputPathContents {
  // The identifier of a previously finalized build whose results are stored in
  // the output tree.
  string build_id = 1;

  // Path prefixes relative to StartBuildResponse.output_path that have been
  // modified, deleted or recreated since they were finalized. Any path exactly
  // matching or starting with one of these prefixes MUST be assumed by Bazel to
  // have been modified or deleted. Any other path MAY be assumed by Bazel to
  // have remained unchanged since it was last finalized.
  //
  // In the interest of performance, the server SHOULD only include path
  // prefixes that contain at least one of the paths that were previously
  // finalized.
  repeated string modified_path_prefixes = 2;
}

message StageArtifactsRequest {
  message Artifact {
    // Path relative to StartBuildResponse.output_path.
    string path = 1;
    // Describe how to stage the artifact.
    //
    // The concrete type of the locator depending on the CAS Bazel connects to.
    // In case of a REv2 CAS, the type is
    // [FileArtifactLocator][bazel_output_service_rev2.FileArtifactLocator].
    google.protobuf.Any locator = 2;
  }

  // The identifier of the build. Server uses this to determine which output
  // path needs to be modified.
  string build_id = 1;

  repeated Artifact artifacts = 2;
}

message StageArtifactsResponse {
  message Response {
    // If the status has a code other than `OK`, it indicates that the artifact
    // could not be staged.
    //
    // Errors:
    // * `NOT_FOUND`: The requested Artifact is not in the CAS.
    google.rpc.Status status = 1;
  }

  // The response for each of the requested artifacts, using the same order as
  // requested. This means that this list has the same length as
  // StageArtifactsRequest.artifacts.
  repeated Response responses = 1;
}

message FinalizeArtifactsRequest {
  message Artifact {
    // Path relative to StartBuildResponse.output_path.
    string path = 1;
    // Expected digest for this path. This allows the server to detect changes
    // the path has been changed after the client finished creating the path and
    // the corresponding FinalizeArtifactsRequest is processed.
    //
    // The concrete type of the locator depending on the CAS Bazel connects to.
    // In case of a REv2 CAS, the type is
    // [FileArtifactLocator][bazel_output_service_rev2.FileArtifactLocator].
    google.protobuf.Any locator = 2;
  }

  // The identifier of the build. Server uses this to determine which output
  // path needs to be modified.
  string build_id = 1;

  repeated Artifact artifacts = 2;
}

message FinalizeArtifactsResponse {
  // Intentionally empty for future extensibility.
}

message FinalizeBuildRequest {
  // The identifier of the build that should be finalized.
  string build_id = 1;

  // Whether the build completed successfully. The server MAY, for example, use
  // this option to apply different retention policies that take the outcome of
  // the build into account.
  bool build_successful = 2;
}

message FinalizeBuildResponse {
  // Intentionally empty for future extensibility.
}

message BatchStatRequest {
  // The identifier of the build. The server uses this to determine which output
  // tree needs to be inspected.
  string build_id = 1;

  // Paths whose status is to be obtained. The server MUST canonicalize each
  // path using lstat semantics, i.e., all components except the last must be
  // resolved if they are symlinks. If a symlink pointing to a location outside
  // of the output tree is encountered at any point during the canonicalization
  // process, the server MAY use the information in
  // StartBuildRequest.output_path_aliases map to continue the canonicalization.
  //
  // Refer to Stat.type for how to handle a situation where canonicalization
  // fails due to a symlink pointing to a location outside of the output tree.
  //
  // Path is relative to StartBuildResponse.output_path.
  repeated string paths = 2;
}

message BatchStatResponse {
  message StatResponse {
    // The status of the path. If the path does not exist, this field MUST be
    // unset.
    Stat stat = 1;
  }

  message Stat {
    message File {
      // The file digest.
      //
      // The server MAY leave this field unset if it is unable to compute the
      // digest.
      //
      // The concrete type of the digest depending on the CAS Bazel connects to.
      // In case of a REv2 CAS, the type is
      // [FileArtifactLocator][bazel_output_service_rev2.FileArtifactLocator].
      google.protobuf.Any locator = 1;
    }

    message Symlink {
      // The target of the symbolic link.
      string target = 1;
    }

    message Directory {}

    // If the path cannot be canonicalized, the server MUST NOT set any of the
    // type fields.
    //
    // If the path resolves to a special file, the server MUST NOT set any of
    // the type fields.
    oneof type {
      // The path resolves to a regular file.
      File file = 1;

      // The path resolves to a symbolic link.
      Symlink symlink = 2;

      // The path resolves to a directory.
      Directory directory = 3;
    }
  }

  // The status response for each of the requested paths, using the same
  // order as requested. This means that this list has the same length
  // as BatchStatRequest.paths.
  repeated StatResponse responses = 1;
}
