| // Copyright 2023 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. |
| |
| #ifndef NINJA_PERSISTENT_MODE_H_ |
| #define NINJA_PERSISTENT_MODE_H_ |
| |
| #include <functional> |
| #include <string> |
| #include <vector> |
| |
| #include "build_config.h" |
| #include "exit_status.h" |
| #include "persistent_service.h" |
| |
| /// Implementation for Ninja's persistent mode. |
| /// |
| /// This class wraps Ninja's persistent mode implementation on top of the |
| /// generic PersistentService class. In particular: |
| /// |
| /// - Provides static methods to determine the role of the current process |
| /// (based on the NNINJA_PERSISTENT_MODE environment variable). |
| /// |
| /// - Provides a way to check that the client and the server correspond to |
| /// the same build plan (see Compatibility). |
| /// |
| /// - Provides client methods to check the status of the server, or stop it |
| /// (to implement the `-t server` tool commands). |
| /// |
| /// - Provides a client method to run a query on the server, automatically |
| /// starting a new instance if none is available, if the available one |
| /// is not compatible with the current build plan, or if any of the input |
| /// .ninja manifest files has been updated. |
| /// |
| /// - Provides a way to start the server early, and to process Ninja build |
| /// or tool requests on the server. |
| /// |
| /// Example Usage: |
| /// |
| /// // build plan configuration data used to match client/server |
| /// // compatibility. |
| /// auto compatibility = PersistentMode::Compatibility(); |
| /// compatibility.SetBuildDir(<absolute_build_dir>); |
| /// |
| /// // An object describing the current Ninja build query or tool |
| /// // invocation to perform. |
| /// PersistentMode::BuildQuery query; |
| /// query.config = build_config; |
| /// query.args = RemoteArguments(argc, argv).args(); |
| /// ... |
| /// |
| /// // A callable used to run the query, either on the server, or in the |
| /// // client if launching a new server was not possible. |
| /// auto handle_query = [](const PersistentMode::BuildQuery& query) -> |
| /// ExitStatus { |
| /// ... // perform build or tool query. |
| /// return 0; |
| /// }; |
| /// |
| /// auto persistent_status = PersistentMode::GetProcessStatus(); |
| /// is (status == PersistentMode::IsServer) { |
| /// PersistentMode::Server server(compatibility); |
| /// |
| /// if (!server.StartLocalServer(&err)) |
| /// Fatal("Could not start server: %s", err.c_str()); |
| /// |
| /// ... load build plan |
| /// |
| /// // A callable used by the server before running each |
| /// // query to verify if the build plan changed (e.g. an |
| /// // input build.ninja file was updated on the filesystem). |
| /// auto restart_check = []() -> bool { |
| /// return false; |
| /// }; |
| /// |
| /// // Run the server loop till the end. |
| /// server.RunServerThenExit(std::move(restart_check), |
| /// std::move(handle_query)); |
| /// } |
| /// |
| /// if (persistent_status == PersistentMode::Client) { |
| /// PersistentMode::Client client; |
| /// |
| /// // Run the query, this takes care of redirecting stdio |
| /// // and ensuring Ctrl-C will be handled by the server |
| /// // properly (i.e. to stop current query, and not abort |
| /// // the server). |
| /// int exit_code = -1; |
| /// if (!client.RunQuery(compatibility, query, &exit_code, &error)) { |
| /// Fatal("Could not run Ninja query: %s", error.c_str()); |
| /// } |
| /// |
| /// // query was run on the server. |
| /// exit(exit_code); |
| /// } |
| /// |
| /// assert(persistent_status == PersistentMode::Disabled) { |
| /// ... load build plan |
| /// ... do a local build as usual. |
| /// } |
| /// |
| struct PersistentMode { |
| /// Status of persistent mode for the current process |
| enum Status { |
| Disabled = 0, |
| IsClient, |
| IsServer, |
| }; |
| |
| /// Return the status of the current process. |
| static Status GetCurrentProcessStatus(); |
| |
| /// The configuration information that each persistent server must compare |
| /// with each client query to verify that they are compatible, i.e. correspond |
| /// to the same Ninja build plan. Note that some flags change the way |
| /// manifests are loaded, and thus introduce incompatibilities when they |
| /// are different. |
| struct Compatibility { |
| Compatibility(); |
| |
| /// Override the ninja_version() string. Only use this for tests. |
| Compatibility& SetNinjaVersionForTest(const std::string& ninja_version); |
| |
| /// Change the input file, default value if "build.ninja" |
| Compatibility& SetInputFile(const std::string& input_file); |
| |
| /// Change the build directory, and empty value matches the current working |
| /// directory. WARNING: It is critically important for the client and server |
| /// to use the same value consistenly. Outside of tests, ensure that a |
| /// non-empty value is always an absolute path! |
| Compatibility& SetBuildDir(const std::string& build_dir); |
| |
| /// Set the value of the `-w phonycycle` flag, which changes the way |
| /// build manifests are loaded. Default value is false. |
| Compatibility& SetFlagPhonyCycleShouldErr(bool enabled); |
| |
| /// Create new instance from an encoded string. |
| /// On success return new instance after clearing |*error|. |
| /// On failure return empty instance after setting |*error| to non-empty |
| /// string. |
| static Compatibility FromEncodedString(const std::string& str, |
| std::string* error); |
| |
| /// Convert to string for sending through IPC. Not human-readable. |
| std::string ToEncodedString() const; |
| |
| /// Convert to human-readable string for debugging. |
| std::string ToString() const; |
| |
| /// Return true if this instance is compatiable with |other|. |
| /// On failure, return false after setting |*reason|. |
| bool IsCompatibleWith(const Compatibility& other, |
| std::string* reason) const; |
| |
| /// Return working directory. |
| const std::string& build_dir() const { return build_dir_; } |
| |
| /// Return a string trying to identify the Ninja version being used. |
| /// Note that this may include a timestamp or content hash of the current |
| /// executable as well. The default value is computed by the constructor |
| /// automatically. |
| const std::string& ninja_version() const { return ninja_version_; }; |
| |
| /// Return a command-line used to start a server compatible with this |
| /// instance. |
| std::vector<std::string> ToServerArgs( |
| const std::string& server_executable) const; |
| |
| /// Returns true if the build directory is absolute (or empty, in which |
| /// case this will be interpreted as the absolute path of the current |
| /// directory). On failure, set |*err| then return false. |
| bool CheckBuildDir(std::string* err) const; |
| |
| private: |
| /// Return the default Ninja version, called from constructor. |
| static std::string GetDefaultNinjaVersion(); |
| |
| /// Ninja version. This includes a timestamp of the Ninja executable |
| /// as well. Must be the first field in this struct. |
| std::string ninja_version_; |
| |
| /// Current build directory. |
| std::string build_dir_; |
| |
| /// Input file name, cannot be empty, relative paths are always |
| /// relative to |build_dir|. |
| std::string input_file_ = "build.ninja"; |
| |
| /// Options controlling how the manifest is loaded. |
| bool phony_cycle_should_err_ = false; |
| }; |
| |
| /// Describe a query from the client to the server. This can be used to |
| /// run either a build or a tool (when the |tool| field is not empty). |
| struct BuildQuery { |
| /// Build configuration |
| BuildConfig config; |
| |
| /// Mirror the set of debug flags in "debug_flags.h" that can be set |
| /// individually with `-d <flag>` on the command line that do affect |
| /// each build. These are currently stored in global variables instead |
| /// of BuildConfig :-/ |
| bool debug_explaining = false; |
| bool debug_keep_depfile = false; |
| bool debug_keep_rsp = false; |
| bool debug_experimental_statcache = false; |
| |
| /// Set to true to dump metrics after a build. |
| bool dump_metrics = false; |
| |
| /// Optional tool name, empty means a build is requested, instead of a |
| /// tool run. |
| std::string tool; |
| |
| /// List of command-line arguments. If |tool| is empty, this is a list |
| /// of targets and should not contain any option. If |tool| is not empty |
| /// then this is a list of arguments for the tool. |
| std::vector<std::string> args; |
| |
| std::string ToEncodedString() const; |
| std::string ToString() const; |
| |
| void WriteGlobalVariables() const; |
| |
| static BuildQuery FromEncodedString(const std::string& str, |
| std::string* error); |
| }; |
| |
| struct BuildQueryResult { |
| /// There are three possible types of results: |
| /// |
| /// kServerRunning: Server is still running. Use exit_status known |
| /// whether the query succeeded or not. |
| /// |
| /// kServerStopped: Server had to stop due to an error, e.g. when |
| /// loading the manifest, or running the regenerator command. |
| /// Client should use exit_status to report the error then stop. |
| /// |
| /// kServerStoppedRestartNeeded: Server had to stop but the client |
| /// should try the query again. This happens after running the |
| /// regeneration command, as the in-memory graph is no longer |
| /// valid, so a new server must be started to load the new |
| /// build plan. exit_status will be ExitSuccess! |
| /// |
| enum ServerState { |
| kServerRunning = 0, |
| kServerStopped = 1, |
| kServerStoppedRestartNeeded = 2, |
| }; |
| |
| ServerState server_state = kServerRunning; |
| ExitStatus exit_status = ExitSuccess; |
| |
| /// Used for debugging / logging only when server_state is not |
| /// kServerRunning |
| std::string server_exit_reason; |
| |
| /// Make a new BuildQueryResult for kServerRunning cases. |
| static BuildQueryResult Make(ExitStatus status) { |
| return { kServerRunning, status }; |
| } |
| |
| /// Make a new BuildQueryResult for kServerStopped cases. |
| static BuildQueryResult MakeServerStopped(ExitStatus status, |
| std::string exit_reason) { |
| return { kServerStopped, status, std::move(exit_reason) }; |
| } |
| |
| /// Make a new BuildQueryResult for kServerStoppedRestartNeeded cases. |
| static BuildQueryResult MakeServerStoppedRestartNeeded( |
| std::string exit_reason) { |
| return { kServerStoppedRestartNeeded, ExitSuccess, |
| std::move(exit_reason) }; |
| } |
| |
| /// Creating human readable description for debugging. |
| std::string ToDebugString() const; |
| |
| /// Create wire encoded representation. |
| std::string ToEncodedString() const; |
| |
| /// Create new instance from an encoded string. |
| /// On success return new instance after clearing |*error|. |
| /// On failure return empty instance after setting |*error| to non-empty |
| /// string. |
| static BuildQueryResult FromEncodedString(const std::string& str, |
| std::string* error); |
| }; |
| |
| /// A callable object that implements a query. |
| using BuildQueryHandler = std::function<BuildQueryResult(const BuildQuery&)>; |
| |
| class Client { |
| public: |
| /// Create new instance. |
| Client(); |
| |
| /// Set the path to the server executable. By default, this is the value |
| /// returned by GetCurrentExecutable(), but this method allows one to |
| /// override it for testing. |
| void SetServerExecutable(const std::string& server_executable); |
| |
| /// Set the path to a file that will receive log messages from the |
| /// server process. By default, nothing is logged. |
| void SetServerLogFile(const std::string& log_file_path) { |
| server_log_file_ = log_file_path; |
| } |
| |
| /// Set logger used for client messages. The null logger is used by default. |
| void SetLogger(Logger logger) { logger_ = std::move(logger); } |
| |
| /// Return server executable path. Used for testing. |
| const std::string server_executable() const { return server_executable_; } |
| |
| /// Return true if a server is already running, serving a given build |
| /// directory. An empty |build_dir| string means the current working |
| /// directory. |
| bool IsServerRunning(const std::string& build_dir); |
| |
| /// Retrieve process ID of server, if it exists, for |build_dir|, |
| /// or -1 otherwise. NOTE: DOES NOT WORK ON WINDOWS YET. |
| int GetServerPidFor(const std::string& build_dir); |
| |
| /// Stop the server if it exists, return true if a server was running |
| /// before the call for a given build directory. An empty |build_dir| |
| /// string means the current working directory. |
| bool StopServer(const std::string& build_dir, std::string* err); |
| |
| /// Ask the server to run a query for us. |
| /// |
| /// |compatibility| is used to connect either launch a new server instance, |
| /// or verify that the existing one serving |compatibility.build_dir()| is |
| /// compatible (if not, that instance is shut down, and a new one is |
| /// launched). |
| /// |
| /// On success return true and set |*result| to the result of the |
| /// query, on failure to connect to the server, set |*error| and return |
| /// false. |
| bool RunQuery(const Compatibility& compatibility, const BuildQuery& query, |
| BuildQueryResult* result, std::string* error); |
| |
| private: |
| static PersistentService::Client GetService(const std::string& build_dir); |
| |
| std::string server_executable_; |
| std::string server_log_file_; |
| Logger logger_; |
| }; |
| |
| class Server { |
| public: |
| /// Create new instance. This does not do anything except recording |
| /// the value of |compatibility|. |
| explicit Server(const Compatibility& compatibility); |
| |
| void SetLogger(Logger logger); |
| |
| /// Set the timeout, in milliseconds, used by server instances waiting |
| /// for new client connections. On expiration, the server will shutdown |
| /// gracefully. A negative value (the default) means no timeout. |
| /// This must be called before StartLocalServer() or RunServerThenExit(). |
| void SetConnectionTimeoutMs(int64_t connection_timeout_ms) { |
| server_.SetConnectionTimeoutMs(connection_timeout_ms); |
| } |
| |
| /// Start the server in the current process. This can be called before |
| /// RunServerThenExit() to quickly acquire the service named pipe and fail |
| /// if not possible, before performing expensive operations like loading |
| /// the build plan. Called by RunServerThenExit() implicitly otherwise. |
| bool StartLocalServer(std::string* error); |
| |
| /// Run the server main loop, which will handle incoming build requests |
| /// until they return kServerStopped or kServerStoppedRestartNeeded. |
| /// This function never returns. |
| void RunServerThenExit(BuildQueryHandler query_handler); |
| |
| private: |
| bool RunQueryOnServer(ScopedHandle connection, |
| const BuildQueryHandler& query_handler); |
| |
| Compatibility compatibility_; |
| PersistentService::Server server_; |
| }; |
| }; |
| |
| #endif // NINJA_PERSISTENT_MODE_H_ |