Skip to content
Draft
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
81 changes: 81 additions & 0 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ notify.workspace = true

[dev-dependencies]
pretty_assertions.workspace = true
fs_extra.workspace = true
assert_cmd = "2"
predicates = "3"
portpicker = "0.1"
reqwest = { version = "0.12", features = ["blocking", "json"] }

[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { workspace = true }
Expand Down
10 changes: 9 additions & 1 deletion crates/cli/src/subcommands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ pub fn cli() -> clap::Command {
.default_value("src")
.help("The directory to lint for nonfunctional print statements. If set to the empty string, skips linting.")
)
.arg(
Arg::new("features")
.long("features")
.value_parser(clap::value_parser!(OsString))
.required(false)
.help("Additional features to pass to the build process (e.g. `--features feature1,feature2` for Rust modules).")
)
.arg(
Arg::new("debug")
.long("debug")
Expand All @@ -33,6 +40,7 @@ pub fn cli() -> clap::Command {

pub async fn exec(_config: Config, args: &ArgMatches) -> Result<(PathBuf, &'static str), anyhow::Error> {
let project_path = args.get_one::<PathBuf>("project_path").unwrap();
let features = args.get_one::<OsString>("features");
let lint_dir = args.get_one::<OsString>("lint_dir").unwrap();
let lint_dir = if lint_dir.is_empty() {
None
Expand All @@ -56,7 +64,7 @@ pub async fn exec(_config: Config, args: &ArgMatches) -> Result<(PathBuf, &'stat
));
}

let result = crate::tasks::build(project_path, lint_dir.as_deref(), build_debug)?;
let result = crate::tasks::build(project_path, lint_dir.as_deref(), build_debug, features)?;
println!("Build finished successfully.");

Ok(result)
Expand Down
17 changes: 8 additions & 9 deletions crates/cli/src/subcommands/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ async fn generate_build_and_publish(

println!("{}", "Building...".cyan());
let (_path_to_program, _host_type) =
tasks::build(spacetimedb_dir, Some(Path::new("src")), false).context("Failed to build project")?;
tasks::build(spacetimedb_dir, Some(Path::new("src")), false, None).context("Failed to build project")?;
println!("{}", "Build complete!".green());

println!("{}", "Generating module bindings...".cyan());
Expand All @@ -413,15 +413,14 @@ async fn generate_build_and_publish(
ClearMode::OnConflict => "on-conflict",
};
let mut publish_args = vec![
"publish",
database_name,
"--project-path",
project_path_str,
"--yes",
"--delete-data",
clear_flag,
"publish".to_string(),
database_name.to_string(),
"--project-path".to_string(),
project_path_str.to_string(),
"--yes".to_string(),
format!("--delete-data={}", clear_flag),
];
publish_args.extend_from_slice(&["--server", server]);
publish_args.extend_from_slice(&["--server".to_string(), server.to_string()]);

let publish_cmd = publish::cli();
let publish_matches = publish_cmd
Expand Down
78 changes: 42 additions & 36 deletions crates/cli/src/subcommands/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub fn cli() -> clap::Command {
.arg(
Arg::new("break_clients")
.long("break-clients")
.alias("yes-break-clients")
.action(SetTrue)
.help("Allow breaking changes when publishing to an existing database identity. This will force publish even if it will break existing clients, but will NOT force publish if it would cause deletion of any data in the database. See --yes and --delete-data for details.")
)
Expand Down Expand Up @@ -175,46 +176,26 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E
let domain = percent_encoding::percent_encode(name_or_identity.as_bytes(), encode_set);
let mut builder = client.put(format!("{database_host}/v1/database/{domain}"));

if clear_database != ClearMode::Always {
builder = apply_pre_publish_if_needed(
builder,
&client,
&database_host,
&domain.to_string(),
host_type,
&program_bytes,
&auth_header,
clear_database,
force_break_clients,
force,
)
.await?;
}
builder = apply_pre_publish_if_needed(
builder,
&client,
&database_host,
name_or_identity,
&domain.to_string(),
host_type,
&program_bytes,
&auth_header,
clear_database,
force_break_clients,
force,
)
.await?;

builder
} else {
client.post(format!("{database_host}/v1/database"))
};

if clear_database == ClearMode::Always || clear_database == ClearMode::OnConflict {
// Note: `name_or_identity` should be set, because it is `required` in the CLI arg config.
println!(
"This will DESTROY the current {} module, and ALL corresponding data.",
name_or_identity.unwrap()
);
if !y_or_n(
force,
format!(
"Are you sure you want to proceed? [deleting {}]",
name_or_identity.unwrap()
)
.as_str(),
)? {
println!("Aborting");
return Ok(());
}
builder = builder.query(&[("clear", true)]);
}
if let Some(n) = num_replicas {
eprintln!("WARNING: Use of unstable option `--num-replicas`.\n");
builder = builder.query(&[("num_replicas", *n)]);
Expand Down Expand Up @@ -334,6 +315,7 @@ async fn apply_pre_publish_if_needed(
mut builder: reqwest::RequestBuilder,
client: &reqwest::Client,
base_url: &str,
name_or_identity: &str,
domain: &String,
host_type: &str,
program_bytes: &[u8],
Expand Down Expand Up @@ -367,11 +349,35 @@ async fn apply_pre_publish_if_needed(
println!("{}", manual.reason);
println!("Proceeding with database clear due to --delete-data=always.");
}
println!(
"This will DESTROY the current {} module, and ALL corresponding data.",
name_or_identity
);
if !y_or_n(
force,
format!("Are you sure you want to proceed? [deleting {}]", name_or_identity).as_str(),
)? {
anyhow::bail!("Aborting");
}
builder = builder.query(&[("clear", true)]);
}
PrePublishResult::AutoMigrate(auto) => {
if clear_database == ClearMode::Always {
println!("Auto-migration, does NOT require clearing the database, but proceeding with database clear due to --delete-data=always.");
println!(
"This will DESTROY the current {} module, and ALL corresponding data.",
name_or_identity
);
if !y_or_n(
force,
format!("Are you sure you want to proceed? [deleting {}]", name_or_identity).as_str(),
)? {
anyhow::bail!("Aborting");
}
builder = builder.query(&[("clear", true)]);
return Ok(builder);
}
println!("{}", auto.migrate_plan);
// We only arrive here if you have not specified ClearMode::Always AND there was no
// conflict that required manual migration.
if auto.break_clients
&& !y_or_n(
force_break_clients || force,
Expand Down
6 changes: 5 additions & 1 deletion crates/cli/src/tasks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ pub fn build(
project_path: &Path,
lint_dir: Option<&Path>,
build_debug: bool,
features: Option<&std::ffi::OsString>,
) -> anyhow::Result<(PathBuf, &'static str)> {
let lang = util::detect_module_language(project_path)?;
if features.is_some() && lang != ModuleLanguage::Rust {
anyhow::bail!("The --features option is only supported for Rust modules.");
}
let output_path = match lang {
ModuleLanguage::Rust => build_rust(project_path, lint_dir, build_debug),
ModuleLanguage::Rust => build_rust(project_path, features, lint_dir, build_debug),
ModuleLanguage::Csharp => build_csharp(project_path, build_debug),
ModuleLanguage::Javascript => build_javascript(project_path, build_debug),
}?;
Expand Down
21 changes: 17 additions & 4 deletions crates/cli/src/tasks/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ fn cargo_cmd(subcommand: &str, build_debug: bool, args: &[&str]) -> duct::Expres
)
}

pub(crate) fn build_rust(project_path: &Path, lint_dir: Option<&Path>, build_debug: bool) -> anyhow::Result<PathBuf> {
pub(crate) fn build_rust(
project_path: &Path,
features: Option<&std::ffi::OsString>,
lint_dir: Option<&Path>,
build_debug: bool,
) -> anyhow::Result<PathBuf> {
// Make sure that we have the wasm target installed
if !has_wasm32_target() {
if has_rust_up() {
Expand Down Expand Up @@ -75,9 +80,17 @@ pub(crate) fn build_rust(project_path: &Path, lint_dir: Option<&Path>, build_deb
);
}

let reader = cargo_cmd("build", build_debug, &["--message-format=json-render-diagnostics"])
.dir(project_path)
.reader()?;
let mut args = if let Some(features) = features {
vec![format!("--features={}", features.to_string_lossy())]
} else {
vec![]
};
args.push("--message-format=json-render-diagnostics".to_string());

// Convert Vec<String> to Vec<&str>
let args_str: Vec<&str> = args.iter().map(|s| s.as_str()).collect();

let reader = cargo_cmd("build", build_debug, &args_str).dir(project_path).reader()?;

let mut artifact = None;
for message in Message::parse_stream(io::BufReader::new(reader)) {
Expand Down
Loading
Loading