blob: 9e41ae44c2233d45e855566dfa15ca2e773e2b1d [file] [edit]
// Copyright 2025 Google Inc. 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.
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include "build_metadata.h"
#include "exit_status.h"
#include "string_piece.h"
struct DiskInterface;
struct Edge;
struct SubprocessSet;
/// Abstract interface to run an external worker tool script.
struct DelayedWorkerRunner {
virtual ~DelayedWorkerRunner() = default;
/// Run the external tool. Note that it will run in the foreground
/// in console mode meaning that the current stdout / stderr will be
/// passed to it as-is. It will run in the same process group as the
/// caller, allowing the terminal to send SIGINT to it on Ctrl-C in
/// interactive cases.
///
/// |tool| is the tool path passed to the shell as-is. This must
/// run a program that expects two command-line arguments, which
/// will be |tool_input_path| and |tool_output_path|, in this order.
///
/// |tool_input_path| is the path of a file written by Ninja before
/// this call, containing the JSON-encoded request.
///
/// |tool_output_path| is the path of a file that the tool should
/// write, containing the JSON-encoded response.
///
/// |tool_description| is a description for the tool invocation.
///
virtual ExitStatus Run(const std::string& tool,
const std::string& tool_input_path,
const std::string& tool_output_path,
const std::string& tool_description) = 0;
/// Return an instance that uses a SubprocessSet to launch the tool.
static std::unique_ptr<DelayedWorkerRunner> CreateFrom(
SubprocessSet& subprocess_set);
};
/// Base class for all delayed worker types.
///
/// A delayed worker is an external tool invoked by Ninja to run several
/// related commands as a single sub-process call.
///
/// See DelayedCommands which implements the control logic used to select
/// a worker to build edges. In a nutshell:
///
/// During the build, Ninja calls DelayedCommands::CheckEdge() which in
/// turn calls DelayedWorker::CheckEdge() on each known worker type.
///
/// If this method returns true, the edge is recorded as "delayed" and
/// no subprocess is actually started to build it yet. The delayed state
/// also means that the edge has not completed, hence that none of its
/// dependents can be started yet.
///
/// Later, when Ninja detects that no edges can be ready anymore, it
/// invokes DelayedCommands::BuildEdges() which will select one worker
/// instances with delayed edges, and invoke its
/// DelayedWorker::BuildEdges() method.
///
/// Communication between the worker tool and Ninja is ensured by
/// two JSON-encoded files for the input request, and the output response,
/// as stdout and stderr must be passed unmodified to the tool to let
/// it (or the commands it invokes) print progress messages on the
/// user's terminal.
///
struct DelayedWorker {
/// Description of a request to the worker tool. Encoded as a JSON
/// object with the following schema:
///
/// "version": Required. Integer. Must be 2
/// Historical note: version 1 didn't have the "ninja_depfile"
/// key.
/// "request_id": Required. Integer. Must be in 1..INT32_MAX range.
/// "actions": Required. Array of objects. Each one with:
///
/// "action_id": Required. Integer. Must be in 1..INT32_MAX range and
/// correspond to the action's index in the request.
/// "command": Required. String. Command to run as a single string.
/// "description": Optional. String. Command description from GN.
/// "ninja_outputs": Optional. Array of Ninja output path strings,
/// relative to the build directory.
/// "ninja_depfile": Optional. String. Path to the Ninja depfile
/// that must be written by this action.
///
/// "build_metadata": Optional. Object of key-value string pairs.
///
struct RequestInfo {
/// Individual description for each action for tests.
struct Action {
uint32_t action_id = 0;
std::string command;
std::string description;
std::vector<std::string> ninja_outputs;
std::string ninja_depfile;
};
uint32_t request_id = 0; // starts at 1
std::vector<Action> actions;
BuildMetadataMap build_metadata;
/// Encode request as a JSON string.
std::string ToJson() const;
/// Decode from a JSON input string. On success, set |*result| and return
/// true. On failure, set |*error| and return false.
static bool FromJson(StringPiece input_json, RequestInfo* result,
std::string* error);
};
/// A description of the response from the worker tool.
/// Encoded as a JSON object with the following schema:
///
/// "version": Required. Integer. Must be 1
/// "request_id": Required. Integer. Must be in 1..INT32_MAX range.
/// "status": Required. Integer. Must be in 0..255 range.
/// "output": Required. String. Combined stdout/stderr, may include
/// ANSI VT code sequences.
///
struct ResponseInfo {
uint32_t request_id = 0; // starts at 1
uint8_t status = 0; // 0 for success, 1 for failure, 130 if interrupted.
std::string
output; // Combined stdout/stderr, can have ANSI VT code sequences.
/// Encode response as a JSON string.
std::string ToJson() const;
/// Decode from a JSON input string. On success, set |*result| and return
/// true. On failure, set |*error| and return false.
static bool FromJson(StringPiece input_json, ResponseInfo* result,
std::string* error);
};
/// Destructor, virtual since this is an abstract base class.
virtual ~DelayedWorker() {}
/// Return true if |edge| can be handled by this worker instance.
virtual bool CheckEdge(const Edge* edge,
const std::string& description) const = 0;
/// Invoke the worker's tool to build all recorded delayed edges.
/// This uses |subprocess_set| to invoke its command within a single
/// console subprocess. |build_metadata| is optional.
virtual ResponseInfo BuildEdges(const RequestInfo& request,
DelayedWorkerRunner& tool_runner) = 0;
/// Utility function to run an external worker tool script to build
/// edges. Note that the current stdout and stderr are passed to the
/// tool to let it print progress messages on the user's terminal.
///
/// Due to this, the tool should accepts two command-line arguments:
///
/// - First, a temporary input file path, containing the JSON-encoded
/// request, written by Ninja before invoking the tool.
///
/// - Second, a temporary output file path, where the tool should
/// write the JSON-encoded response. This is read by Ninja after
/// the tool has terminated.
///
/// The tool should not fail if any of the actions listed in the request
/// failed, only if it could not run properly for some unspecified reason,
/// or if a user interrupted the build with Ctrl-C (in this case a status
/// code of 130 is expected, which is the standard status reported by
/// programs interrupted by SIGINT).
///
/// This function handles serialization of request and response to and
/// from temporary JSON files, and invokes the tool with a given tool
/// runner.
///
/// |tool_path| is the path to the tool to run.
/// |tool_description| is a description of the tool.
/// |request| is the request to pass to the tool.
/// |tool_runner| is used to run the tool.
/// |disk_interface| is used to create temporary files.
///
/// On success, set |*response| then return true.
///
/// On failure, set |*error| then return false. This corresponds to
/// cases where the tool could not be launched, didn't generate an output
/// response file, if the output file was malformed, etc.
///
static bool RunTool(const std::string& tool_path,
const std::string& tool_description,
const RequestInfo& request,
DelayedWorkerRunner& tool_runner,
DiskInterface& disk_interface, ResponseInfo* response,
std::string* error);
};