|
| 1 | +use std::ffi::OsString; |
| 2 | +use std::io; |
| 3 | +use std::process::{Command, ExitCode}; |
| 4 | + |
| 5 | +use anyhow::Context; |
| 6 | +use clap::{Arg, ArgMatches}; |
| 7 | +use spacetimedb_paths::SpacetimePaths; |
| 8 | + |
| 9 | +pub fn cli() -> clap::Command { |
| 10 | + clap::Command::new("start") |
| 11 | + .about("Start a local SpacetimeDB instance") |
| 12 | + .long_about( |
| 13 | + "\ |
| 14 | +Start a local SpacetimeDB instance |
| 15 | +
|
| 16 | +Run `spacetime start --help` to see all options.", |
| 17 | + ) |
| 18 | + .disable_help_flag(true) |
| 19 | + .arg( |
| 20 | + Arg::new("edition") |
| 21 | + .long("edition") |
| 22 | + .help("The edition of SpacetimeDB to start up") |
| 23 | + .value_parser(clap::value_parser!(Edition)) |
| 24 | + .default_value("standalone"), |
| 25 | + ) |
| 26 | + .arg( |
| 27 | + Arg::new("args") |
| 28 | + .help("The args to pass to `spacetimedb-{edition} start`") |
| 29 | + .value_parser(clap::value_parser!(OsString)) |
| 30 | + .allow_hyphen_values(true) |
| 31 | + .num_args(0..), |
| 32 | + ) |
| 33 | +} |
| 34 | + |
| 35 | +#[derive(clap::ValueEnum, Clone, Copy)] |
| 36 | +enum Edition { |
| 37 | + Standalone, |
| 38 | + Cloud, |
| 39 | +} |
| 40 | + |
| 41 | +pub async fn exec(paths: &SpacetimePaths, args: &ArgMatches) -> anyhow::Result<ExitCode> { |
| 42 | + let edition = args.get_one::<Edition>("edition").unwrap(); |
| 43 | + let args = args.get_many::<OsString>("args").unwrap_or_default(); |
| 44 | + let bin_name = match edition { |
| 45 | + Edition::Standalone => "spacetimedb-standalone", |
| 46 | + Edition::Cloud => "spacetimedb-cloud", |
| 47 | + }; |
| 48 | + let resolved_exe = std::env::current_exe().context("could not retrieve current exe")?; |
| 49 | + let bin_path = resolved_exe |
| 50 | + .parent() |
| 51 | + .unwrap() |
| 52 | + .join(bin_name) |
| 53 | + .with_extension(std::env::consts::EXE_EXTENSION); |
| 54 | + let mut cmd = Command::new(&bin_path); |
| 55 | + cmd.arg("start") |
| 56 | + .arg("--data-dir") |
| 57 | + .arg(&paths.data_dir) |
| 58 | + .arg("--jwt-key-dir") |
| 59 | + .arg(&paths.cli_config_dir) |
| 60 | + .args(args); |
| 61 | + |
| 62 | + exec_replace(&mut cmd).with_context(|| format!("exec failed for {}", bin_path.display())) |
| 63 | +} |
| 64 | + |
| 65 | +// implementation based on and docs taken verbatim from `cargo_util::ProcessBuilder::exec_replace` |
| 66 | +// |
| 67 | +/// Replaces the current process with the target process. |
| 68 | +/// |
| 69 | +/// On Unix, this executes the process using the Unix syscall `execvp`, which will block |
| 70 | +/// this process, and will only return if there is an error. |
| 71 | +/// |
| 72 | +/// On Windows this isn't technically possible. Instead we emulate it to the best of our |
| 73 | +/// ability. One aspect we fix here is that we specify a handler for the Ctrl-C handler. |
| 74 | +/// In doing so (and by effectively ignoring it) we should emulate proxying Ctrl-C |
| 75 | +/// handling to the application at hand, which will either terminate or handle it itself. |
| 76 | +/// According to Microsoft's documentation at |
| 77 | +/// <https://docs.microsoft.com/en-us/windows/console/ctrl-c-and-ctrl-break-signals>. |
| 78 | +/// the Ctrl-C signal is sent to all processes attached to a terminal, which should |
| 79 | +/// include our child process. If the child terminates then we'll reap them in Cargo |
| 80 | +/// pretty quickly, and if the child handles the signal then we won't terminate |
| 81 | +/// (and we shouldn't!) until the process itself later exits. |
| 82 | +fn exec_replace(cmd: &mut Command) -> io::Result<ExitCode> { |
| 83 | + #[cfg(unix)] |
| 84 | + { |
| 85 | + use std::os::unix::process::CommandExt; |
| 86 | + // if exec() succeeds, it diverges, so the function just returns an io::Error |
| 87 | + let err = cmd.exec(); |
| 88 | + Err(err) |
| 89 | + } |
| 90 | + #[cfg(windows)] |
| 91 | + { |
| 92 | + use windows_sys::Win32::Foundation::{BOOL, FALSE, TRUE}; |
| 93 | + use windows_sys::Win32::System::Console::SetConsoleCtrlHandler; |
| 94 | + |
| 95 | + unsafe extern "system" fn ctrlc_handler(_: u32) -> BOOL { |
| 96 | + // Do nothing. Let the child process handle it. |
| 97 | + TRUE |
| 98 | + } |
| 99 | + unsafe { |
| 100 | + if SetConsoleCtrlHandler(Some(ctrlc_handler), TRUE) == FALSE { |
| 101 | + return Err(io::Error::new(io::ErrorKind::Other, "Unable to set console handler")); |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + cmd.status() |
| 106 | + .map(|status| ExitCode::from(status.code().unwrap_or(1).try_into().unwrap_or(1))) |
| 107 | + } |
| 108 | +} |
0 commit comments