Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ic_principal = "0.1.1"
notify = "8.2.0"
pocket-ic = { git = "https://github.com/dfinity/ic", rev = "97ad9167816c28cf91ed76fece6c6a04a77d7d89" }
reqwest = { version = "0.12.24", default-features = false, features = ["rustls-tls", "json"] }
semver = "1.0.27"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
sysinfo = "0.37.2"
Expand Down
65 changes: 62 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
use std::{
fs,
io::ErrorKind,
mem,
net::{IpAddr, SocketAddr},
path::PathBuf,
time::Duration,
};

use anyhow::Context;
use clap::{ArgAction, Parser, ValueEnum};
use clap::{ArgAction, CommandFactory, Parser, ValueEnum};
use ic_principal::Principal;
use notify::{Event, RecursiveMode, Watcher, recommended_watcher};
use pocket_ic::{
PocketIcBuilder,
common::rest::{AutoProgressConfig, IcpFeatures, IcpFeaturesConfig, InstanceHttpGatewayConfig},
};
use reqwest::Client;
use semver::{Version, VersionReq};
use serde::Serialize;
use sysinfo::{ProcessesToUpdate, Signal, System};
use tempfile::TempDir;
Expand All @@ -24,6 +26,8 @@ use tokio::{process::Command, signal::unix::SignalKind};
#[derive(Parser)]
#[command(version)]
struct Cli {
#[arg(long)]
interface_version: Option<Version>,
#[arg(long)]
gateway_port: Option<u16>,
#[arg(long)]
Expand All @@ -50,10 +54,12 @@ struct Cli {
stdout_file: Option<PathBuf>,
#[arg(long)]
stderr_file: Option<PathBuf>,
#[arg(long)]
#[arg(long, requires = "interface_version")]
status_dir: Option<PathBuf>,
#[arg(long)]
verbose: bool,
#[arg(trailing_var_arg = true, hide = true, allow_hyphen_values = true)]
unknown_args: Vec<String>,
}

#[derive(ValueEnum, Clone)]
Expand Down Expand Up @@ -85,7 +91,9 @@ async fn main() -> anyhow::Result<()> {
stderr_file,
status_dir,
verbose,
} = Cli::parse();
interface_version: _,
unknown_args: _,
} = get_errorchecked_args();
// pocket-ic is expected to be installed next to the launcher (see package.sh)
let pocketic_server_path = if let Some(path) = pocketic_server_path {
path
Expand Down Expand Up @@ -298,6 +306,57 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}

fn get_errorchecked_args() -> Cli {
let mut cli = Cli::parse();
let mut command = Cli::command();
// If no interface version is provided, normal behavior.
let Some(interface_version) = &cli.interface_version else {
if !cli.unknown_args.is_empty() {
unknown_arg(&mut command, &cli.unknown_args[0]);
}
return cli;
};
let our_version = Version::parse("1.0.0").expect("valid version");
// Backwards compatibility: if at all possible, the requirement should be kept at ^1.0.0 while retaining semver.
let requirement = VersionReq::parse("^1.0.0").expect("valid version req");
if !requirement.matches(interface_version) {
eprintln!(
"Error: Unsupported interface version {interface_version}. Supported versions: {requirement}",
);
std::process::exit(1);
}
// Forwards compatibility: unknown arguments for a newer version should be ignored rather than erroring.
if !cli.unknown_args.is_empty() {
if *interface_version == our_version {
// If this is the exact same version, unknown args are bad args.
unknown_arg(&mut command, &cli.unknown_args[0]);
} else {
// If this is a future version, unknown args are possibly correct.
// It is a lot more likely to be misinput if the user is writing them (vs automation),
// which is why the behavior is disabled without an explicit interface version,
// since manual usage likely will not involve this flag.
let mut unknown_args = vec![];
while !cli.unknown_args.is_empty() {
let mut prev_unknown_args = mem::take(&mut cli.unknown_args);
unknown_args.push(prev_unknown_args.remove(0));
cli.update_from(&prev_unknown_args);
}
eprintln!("Warning: Unknown launcher parameters: {unknown_args:?}");
}
}
cli
}

fn unknown_arg(cmd: &mut clap::Command, arg: &str) -> ! {
let mut err = clap::Error::new(clap::error::ErrorKind::UnknownArgument);
err.insert(
clap::error::ContextKind::InvalidArg,
clap::error::ContextValue::String(arg.to_string()),
);
let err = err.format(cmd);
err.exit();
}

#[derive(Serialize)]
struct Status {
v: String,
Expand Down