| // 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 "ipc_utils.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) -> int { |
| /// ... // 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 { |
| /// Create new instance from command-line options. |
| /// |
| /// |input_file| is an optional pointer to the top-level Ninja manifest |
| /// file name. The nullptr value defaults to "build.ninja". |
| /// |
| /// |build_dir| is an optional pointer to the build directory, the nullptr |
| /// value defaults to the current working directory. |
| /// |
| /// |dupe_edges_should_err| corresponds to the `-w dupbuild=err` option, |
| /// and |phony_cycle_should_err| corresponds to the `-w phonycycle=err` one, |
| /// and both change the way manifest files are loaded. |
| static Compatibility FromOptions(const char* input_file, |
| const char* build_dir, |
| bool dupe_edges_should_err, |
| bool phony_cycle_should_err); |
| |
| Compatibility(); |
| |
| /// Override the ninja_version() string. Only use this for tests. |
| Compatibility& SetNinjaVersionForTest(const std::string& new_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 dupedges` flag, which changes the way build |
| /// manifests are loaded. Default value is true. |
| Compatibility& SetFlagDupeEdgesShouldErr(bool enabled); |
| |
| /// 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 dupe_edges_should_err_ = true; |
| bool phony_cycle_should_err_ = false; |
| }; |
| |
| /// A callable object that returns true if any condition invalidated |
| /// the content of the in-memory build graph. In practice this would be |
| /// when any of the input .ninja files has changed. A return value of true |
| /// indicates that the server must be stopped, and another one restarted. |
| using RestartChecker = std::function<bool(void)>; |
| |
| /// 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); |
| }; |
| |
| /// A special value that can be returned by a BuildQueryHandler to |
| /// indicate that the server should exit immediately after |
| /// serving the query. |
| static constexpr int kServerExit = -2; |
| |
| /// A callable object that implements a query. The result value can |
| /// be the special kServerExit value to indicate that the server |
| /// should stop immediately. Otherwise, it must be a process exit |
| /// code that is >= 0, corresponding to the result of the query. |
| using BuildQueryHandler = std::function<int(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_path); |
| |
| /// 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 |*exit_code_ptr| 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, |
| int* exit_code_ptr, std::string* error); |
| |
| private: |
| static PersistentService::Client GetService(const std::string& build_dir); |
| |
| std::string server_executable_; |
| }; |
| |
| class Server { |
| public: |
| /// Create new instance. This does not do anything except recording |
| /// the value of |compatibility|. |
| explicit Server(const Compatibility& compatibility); |
| |
| /// 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 by first calling |restart_checker| to verify whether |
| /// a restart is needed (e.g. if the build plan changed), then serve |
| /// the request with |query_handler|. This function never returns. |
| void RunServerThenExit(RestartChecker restart_checker, |
| BuildQueryHandler query_handler); |
| |
| private: |
| bool RunQueryOnServer(IpcHandle connection, |
| const BuildQueryHandler& query_handler); |
| |
| Compatibility compatibility_; |
| int64_t connection_timeout_ms_ = -1; |
| PersistentService::Server server_; |
| }; |
| }; |
| |
| #endif // NINJA_PERSISTENT_MODE_H_ |