| // 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); |
| }; |