Skip to content

Commit 3bf8f09

Browse files
authored
Fix --args and handle merging in @server and @client (#4212)
* Pass args after -- to the executable during run and serve * fix clippy * switch to accepting a string argument for subcommand compatibility * Handle merging with `@server` and `@client` * fix clippy * fix formatting * always build into the client package * fix clippy * more clippy fixes
1 parent 6fcb496 commit 3bf8f09

File tree

10 files changed

+290
-206
lines changed

10 files changed

+290
-206
lines changed

packages/cli/src/build/builder.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ impl AppBuilder {
417417
}
418418
}
419419

420+
#[allow(clippy::too_many_arguments)]
420421
pub(crate) async fn open(
421422
&mut self,
422423
devserver_ip: SocketAddr,
@@ -425,6 +426,7 @@ impl AppBuilder {
425426
open_browser: bool,
426427
always_on_top: bool,
427428
build_id: BuildId,
429+
args: &[String],
428430
) -> Result<()> {
429431
let krate = &self.build;
430432

@@ -501,7 +503,7 @@ impl AppBuilder {
501503
| Platform::MacOS
502504
| Platform::Windows
503505
| Platform::Linux
504-
| Platform::Liveview => self.open_with_main_exe(envs)?,
506+
| Platform::Liveview => self.open_with_main_exe(envs, args)?,
505507
};
506508

507509
self.builds_opened += 1;
@@ -732,12 +734,13 @@ impl AppBuilder {
732734
/// paths right now, but they will when we start to enable things like swift integration.
733735
///
734736
/// Server/liveview/desktop are all basically the same, though
735-
fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>) -> Result<()> {
737+
fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>, args: &[String]) -> Result<()> {
736738
let main_exe = self.app_exe();
737739

738740
tracing::debug!("Opening app with main exe: {main_exe:?}");
739741

740742
let mut child = Command::new(main_exe)
743+
.args(args)
741744
.envs(envs)
742745
.env_remove("CARGO_MANIFEST_DIR") // running under `dx` shouldn't expose cargo-only :
743746
.stderr(Stdio::piped())

packages/cli/src/build/request.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ pub(crate) struct BuildRequest {
374374
pub(crate) triple: Triple,
375375
pub(crate) device: bool,
376376
pub(crate) package: String,
377+
pub(crate) main_target: String,
377378
pub(crate) features: Vec<String>,
378379
pub(crate) extra_cargo_args: Vec<String>,
379380
pub(crate) extra_rustc_args: Vec<String>,
@@ -463,7 +464,11 @@ impl BuildRequest {
463464
///
464465
/// Note: Build requests are typically created only when the CLI is invoked or when significant
465466
/// changes are detected in the `Cargo.toml` (e.g., features added or removed).
466-
pub(crate) async fn new(args: &TargetArgs, workspace: Arc<Workspace>) -> Result<Self> {
467+
pub(crate) async fn new(
468+
args: &TargetArgs,
469+
main_target: Option<String>,
470+
workspace: Arc<Workspace>,
471+
) -> Result<Self> {
467472
let crate_package = workspace.find_main_package(args.package.clone())?;
468473

469474
let config = workspace
@@ -506,6 +511,10 @@ impl BuildRequest {
506511
})
507512
.unwrap_or(workspace.krates[crate_package].name.clone());
508513

514+
// Use the main_target for the client + server build if it is set, otherwise use the target name for this
515+
// specific build
516+
let main_target = main_target.unwrap_or(target_name.clone());
517+
509518
let crate_target = main_package
510519
.targets
511520
.iter()
@@ -727,6 +736,7 @@ impl BuildRequest {
727736
extra_cargo_args,
728737
release,
729738
package,
739+
main_target,
730740
skip_assets: args.skip_assets,
731741
base_path: args.base_path.clone(),
732742
wasm_split: args.wasm_split,
@@ -2737,7 +2747,7 @@ impl BuildRequest {
27372747
/// target/dx/build/app/web/server.exe
27382748
pub(crate) fn build_dir(&self, platform: Platform, release: bool) -> PathBuf {
27392749
self.internal_out_dir()
2740-
.join(self.executable_name())
2750+
.join(&self.main_target)
27412751
.join(if release { "release" } else { "debug" })
27422752
.join(platform.build_folder_name())
27432753
}
@@ -2748,7 +2758,7 @@ impl BuildRequest {
27482758
/// target/dx/bundle/app/public/
27492759
pub(crate) fn bundle_dir(&self, platform: Platform) -> PathBuf {
27502760
self.internal_out_dir()
2751-
.join(self.executable_name())
2761+
.join(&self.main_target)
27522762
.join("bundle")
27532763
.join(platform.build_folder_name())
27542764
}

packages/cli/src/cli/build.rs

Lines changed: 57 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
use crate::{cli::*, AppBuilder, BuildRequest, Workspace, PROFILE_SERVER};
1+
use crate::{cli::*, AppBuilder, BuildRequest, Workspace};
22
use crate::{BuildMode, Platform};
3-
use target_lexicon::Triple;
43

5-
use super::target::{TargetArgs, TargetCmd};
4+
use super::target::TargetArgs;
65

76
/// Build the Rust Dioxus app and all of its assets.
87
///
@@ -16,42 +15,9 @@ pub struct BuildArgs {
1615
#[clap(long)]
1716
pub(crate) fullstack: Option<bool>,
1817

19-
/// The feature to use for the client in a fullstack app [default: "web"]
20-
#[clap(long)]
21-
pub(crate) client_features: Vec<String>,
22-
23-
/// The feature to use for the server in a fullstack app [default: "server"]
24-
#[clap(long)]
25-
pub(crate) server_features: Vec<String>,
26-
27-
/// Build with custom profile for the fullstack server
28-
#[clap(long, default_value_t = PROFILE_SERVER.to_string())]
29-
pub(crate) server_profile: String,
30-
31-
/// The target to build for the server.
32-
///
33-
/// This can be different than the host allowing cross-compilation of the server. This is useful for
34-
/// platforms like Cloudflare Workers where the server is compiled to wasm and then uploaded to the edge.
35-
#[clap(long)]
36-
pub(crate) server_target: Option<Triple>,
37-
3818
/// Arguments for the build itself
3919
#[clap(flatten)]
4020
pub(crate) build_arguments: TargetArgs,
41-
42-
/// A list of additional targets to build.
43-
///
44-
/// Server and Client are special targets that integrate with `dx serve`, while `crate` is a generic.
45-
///
46-
/// ```sh
47-
/// dx serve \
48-
/// client --target aarch64-apple-darwin \
49-
/// server --target wasm32-unknown-unknown \
50-
/// crate --target aarch64-unknown-linux-gnu --package foo \
51-
/// crate --target x86_64-unknown-linux-gnu --package bar
52-
/// ```
53-
#[command(subcommand)]
54-
pub(crate) targets: Option<TargetCmd>,
5521
}
5622

5723
pub struct BuildTargets {
@@ -60,6 +26,40 @@ pub struct BuildTargets {
6026
}
6127

6228
impl BuildArgs {
29+
fn default_client(&self) -> &TargetArgs {
30+
&self.build_arguments
31+
}
32+
33+
fn default_server(&self, client: &BuildRequest) -> Option<&TargetArgs> {
34+
// Now resolve the builds that we need to.
35+
// These come from the args, but we'd like them to come from the `TargetCmd` chained object
36+
//
37+
// The process here is as follows:
38+
//
39+
// - Create the BuildRequest for the primary target
40+
// - If that BuildRequest is "fullstack", then add the client features
41+
// - If that BuildRequest is "fullstack", then also create a BuildRequest for the server
42+
// with the server features
43+
//
44+
// This involves modifying the BuildRequest to add the client features and server features
45+
// only if we can properly detect that it's a fullstack build. Careful with this, since
46+
// we didn't build BuildRequest to be generally mutable.
47+
let default_server = client.enabled_platforms.contains(&Platform::Server);
48+
49+
// Make sure we set the fullstack platform so we actually build the fullstack variant
50+
// Users need to enable "fullstack" in their default feature set.
51+
// todo(jon): fullstack *could* be a feature of the app, but right now we're assuming it's always enabled
52+
//
53+
// Now we need to resolve the client features
54+
let fullstack = ((default_server || client.fullstack_feature_enabled())
55+
|| self.fullstack.unwrap_or(false))
56+
&& self.fullstack != Some(false);
57+
58+
fullstack.then_some(&self.build_arguments)
59+
}
60+
}
61+
62+
impl CommandWithPlatformOverrides<BuildArgs> {
6363
pub async fn build(self) -> Result<StructuredOutput> {
6464
tracing::info!("Building project...");
6565

@@ -92,89 +92,30 @@ impl BuildArgs {
9292
// do some logging to ensure dx matches the dioxus version since we're not always API compatible
9393
workspace.check_dioxus_version_against_cli();
9494

95-
let mut server = None;
95+
let client_args = match &self.client {
96+
Some(client) => &client.build_arguments,
97+
None => self.shared.default_client(),
98+
};
99+
let client = BuildRequest::new(client_args, None, workspace.clone()).await?;
96100

97-
let client = match self.targets {
98-
// A simple `dx serve` command with no explicit targets
99-
None => {
100-
// Now resolve the builds that we need to.
101-
// These come from the args, but we'd like them to come from the `TargetCmd` chained object
102-
//
103-
// The process here is as follows:
104-
//
105-
// - Create the BuildRequest for the primary target
106-
// - If that BuildRequest is "fullstack", then add the client features
107-
// - If that BuildRequest is "fullstack", then also create a BuildRequest for the server
108-
// with the server features
109-
//
110-
// This involves modifying the BuildRequest to add the client features and server features
111-
// only if we can properly detect that it's a fullstack build. Careful with this, since
112-
// we didn't build BuildRequest to be generally mutable.
113-
let client = BuildRequest::new(&self.build_arguments, workspace.clone()).await?;
114-
let default_server = client
115-
.enabled_platforms
116-
.iter()
117-
.any(|p| *p == Platform::Server);
118-
119-
// Make sure we set the fullstack platform so we actually build the fullstack variant
120-
// Users need to enable "fullstack" in their default feature set.
121-
// todo(jon): fullstack *could* be a feature of the app, but right now we're assuming it's always enabled
122-
//
123-
// Now we need to resolve the client features
124-
let fullstack = ((default_server || client.fullstack_feature_enabled())
125-
|| self.fullstack.unwrap_or(false))
126-
&& self.fullstack != Some(false);
127-
128-
if fullstack {
129-
let mut build_args = self.build_arguments.clone();
130-
build_args.platform = Some(Platform::Server);
131-
132-
let _server = BuildRequest::new(&build_args, workspace.clone()).await?;
133-
134-
// ... todo: add the server features to the server build
135-
// ... todo: add the client features to the client build
136-
// // Make sure we have a server feature if we're building a fullstack app
137-
if self.fullstack.unwrap_or_default() && self.server_features.is_empty() {
138-
return Err(anyhow::anyhow!("Fullstack builds require a server feature on the target crate. Add a `server` feature to the crate and try again.").into());
139-
}
140-
141-
server = Some(_server);
142-
}
143-
144-
client
145-
}
146-
147-
// A command in the form of:
148-
// ```
149-
// dx serve \
150-
// client --package frontend \
151-
// server --package backend
152-
// ```
153-
Some(cmd) => {
154-
let mut client_args_ = None;
155-
let mut server_args_ = None;
156-
let mut cmd_outer = Some(Box::new(cmd));
157-
while let Some(cmd) = cmd_outer.take() {
158-
match *cmd {
159-
TargetCmd::Client(cmd_) => {
160-
client_args_ = Some(cmd_.inner);
161-
cmd_outer = cmd_.next;
162-
}
163-
TargetCmd::Server(cmd) => {
164-
server_args_ = Some(cmd.inner);
165-
cmd_outer = cmd.next;
166-
}
167-
}
168-
}
169-
170-
if let Some(server_args) = server_args_ {
171-
server = Some(BuildRequest::new(&server_args, workspace.clone()).await?);
172-
}
173-
174-
BuildRequest::new(&client_args_.unwrap(), workspace.clone()).await?
175-
}
101+
let server_args = match &self.server {
102+
Some(server) => Some(&server.build_arguments),
103+
None => self.shared.default_server(&client),
176104
};
177105

106+
let mut server = None;
107+
// If there is a server, make sure we output in the same directory as the client build so we use the server
108+
// to serve the web client
109+
if let Some(server_args) = server_args {
110+
// Copy the main target from the client to the server
111+
let main_target = client.main_target.clone();
112+
let mut server_args = server_args.clone();
113+
// The platform in the server build is always set to Server
114+
server_args.platform = Some(Platform::Server);
115+
server =
116+
Some(BuildRequest::new(&server_args, Some(main_target), workspace.clone()).await?);
117+
}
118+
178119
Ok(BuildTargets { client, server })
179120
}
180121
}

packages/cli/src/cli/bundle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub struct Bundle {
4141

4242
/// The arguments for the dioxus build
4343
#[clap(flatten)]
44-
pub(crate) args: BuildArgs,
44+
pub(crate) args: CommandWithPlatformOverrides<BuildArgs>,
4545
}
4646

4747
impl Bundle {

packages/cli/src/cli/check.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub(crate) struct Check {
1919

2020
/// Information about the target to check
2121
#[clap(flatten)]
22-
pub(crate) build_args: BuildArgs,
22+
pub(crate) build_args: CommandWithPlatformOverrides<BuildArgs>,
2323
}
2424

2525
impl Check {

packages/cli/src/cli/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub(crate) mod config;
88
pub(crate) mod create;
99
pub(crate) mod init;
1010
pub(crate) mod link;
11+
pub(crate) mod platform_override;
1112
pub(crate) mod run;
1213
pub(crate) mod serve;
1314
pub(crate) mod target;
@@ -20,6 +21,7 @@ pub(crate) use serve::*;
2021
pub(crate) use target::*;
2122
pub(crate) use verbosity::*;
2223

24+
use crate::platform_override::CommandWithPlatformOverrides;
2325
use crate::{error::Result, Error, StructuredOutput};
2426
use clap::builder::styling::{AnsiColor, Effects, Style, Styles};
2527
use clap::{Parser, Subcommand};
@@ -62,7 +64,7 @@ pub(crate) enum Commands {
6264

6365
/// Build the Dioxus project and all of its assets.
6466
#[clap(name = "build")]
65-
Build(build::BuildArgs),
67+
Build(CommandWithPlatformOverrides<build::BuildArgs>),
6668

6769
/// Run the project without any hotreloading.
6870
#[clap(name = "run")]

0 commit comments

Comments
 (0)