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
This commit is contained in:
1 parent
6fcb496fd5
commit
3bf8f09c65
10 files changed
+291
-207
No files matched your search
@@ -417,6 +417,7 @@ impl AppBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) async fn open(
|
pub(crate) async fn open(
|
||||||
&mut self,
|
&mut self,
|
||||||
devserver_ip: SocketAddr,
|
devserver_ip: SocketAddr,
|
||||||
@@ -425,6 +426,7 @@ impl AppBuilder {
|
|||||||
open_browser: bool,
|
open_browser: bool,
|
||||||
always_on_top: bool,
|
always_on_top: bool,
|
||||||
build_id: BuildId,
|
build_id: BuildId,
|
||||||
|
args: &[String],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let krate = &self.build;
|
let krate = &self.build;
|
||||||
|
|
||||||
@@ -501,7 +503,7 @@ impl AppBuilder {
|
|||||||
| Platform::MacOS
|
| Platform::MacOS
|
||||||
| Platform::Windows
|
| Platform::Windows
|
||||||
| Platform::Linux
|
| Platform::Linux
|
||||||
| Platform::Liveview => self.open_with_main_exe(envs)?,
|
| Platform::Liveview => self.open_with_main_exe(envs, args)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.builds_opened += 1;
|
self.builds_opened += 1;
|
||||||
@@ -732,12 +734,13 @@ impl AppBuilder {
|
|||||||
/// paths right now, but they will when we start to enable things like swift integration.
|
/// paths right now, but they will when we start to enable things like swift integration.
|
||||||
///
|
///
|
||||||
/// Server/liveview/desktop are all basically the same, though
|
/// Server/liveview/desktop are all basically the same, though
|
||||||
fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>) -> Result<()> {
|
fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>, args: &[String]) -> Result<()> {
|
||||||
let main_exe = self.app_exe();
|
let main_exe = self.app_exe();
|
||||||
|
|
||||||
tracing::debug!("Opening app with main exe: {main_exe:?}");
|
tracing::debug!("Opening app with main exe: {main_exe:?}");
|
||||||
|
|
||||||
let mut child = Command::new(main_exe)
|
let mut child = Command::new(main_exe)
|
||||||
|
.args(args)
|
||||||
.envs(envs)
|
.envs(envs)
|
||||||
.env_remove("CARGO_MANIFEST_DIR") // running under `dx` shouldn't expose cargo-only :
|
.env_remove("CARGO_MANIFEST_DIR") // running under `dx` shouldn't expose cargo-only :
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ pub(crate) struct BuildRequest {
|
|||||||
pub(crate) triple: Triple,
|
pub(crate) triple: Triple,
|
||||||
pub(crate) device: bool,
|
pub(crate) device: bool,
|
||||||
pub(crate) package: String,
|
pub(crate) package: String,
|
||||||
|
pub(crate) main_target: String,
|
||||||
pub(crate) features: Vec<String>,
|
pub(crate) features: Vec<String>,
|
||||||
pub(crate) extra_cargo_args: Vec<String>,
|
pub(crate) extra_cargo_args: Vec<String>,
|
||||||
pub(crate) extra_rustc_args: Vec<String>,
|
pub(crate) extra_rustc_args: Vec<String>,
|
||||||
@@ -463,7 +464,11 @@ impl BuildRequest {
|
|||||||
///
|
///
|
||||||
/// Note: Build requests are typically created only when the CLI is invoked or when significant
|
/// Note: Build requests are typically created only when the CLI is invoked or when significant
|
||||||
/// changes are detected in the `Cargo.toml` (e.g., features added or removed).
|
/// changes are detected in the `Cargo.toml` (e.g., features added or removed).
|
||||||
pub(crate) async fn new(args: &TargetArgs, workspace: Arc<Workspace>) -> Result<Self> {
|
pub(crate) async fn new(
|
||||||
|
args: &TargetArgs,
|
||||||
|
main_target: Option<String>,
|
||||||
|
workspace: Arc<Workspace>,
|
||||||
|
) -> Result<Self> {
|
||||||
let crate_package = workspace.find_main_package(args.package.clone())?;
|
let crate_package = workspace.find_main_package(args.package.clone())?;
|
||||||
|
|
||||||
let config = workspace
|
let config = workspace
|
||||||
@@ -506,6 +511,10 @@ impl BuildRequest {
|
|||||||
})
|
})
|
||||||
.unwrap_or(workspace.krates[crate_package].name.clone());
|
.unwrap_or(workspace.krates[crate_package].name.clone());
|
||||||
|
|
||||||
|
// Use the main_target for the client + server build if it is set, otherwise use the target name for this
|
||||||
|
// specific build
|
||||||
|
let main_target = main_target.unwrap_or(target_name.clone());
|
||||||
|
|
||||||
let crate_target = main_package
|
let crate_target = main_package
|
||||||
.targets
|
.targets
|
||||||
.iter()
|
.iter()
|
||||||
@@ -727,6 +736,7 @@ impl BuildRequest {
|
|||||||
extra_cargo_args,
|
extra_cargo_args,
|
||||||
release,
|
release,
|
||||||
package,
|
package,
|
||||||
|
main_target,
|
||||||
skip_assets: args.skip_assets,
|
skip_assets: args.skip_assets,
|
||||||
base_path: args.base_path.clone(),
|
base_path: args.base_path.clone(),
|
||||||
wasm_split: args.wasm_split,
|
wasm_split: args.wasm_split,
|
||||||
@@ -2737,7 +2747,7 @@ impl BuildRequest {
|
|||||||
/// target/dx/build/app/web/server.exe
|
/// target/dx/build/app/web/server.exe
|
||||||
pub(crate) fn build_dir(&self, platform: Platform, release: bool) -> PathBuf {
|
pub(crate) fn build_dir(&self, platform: Platform, release: bool) -> PathBuf {
|
||||||
self.internal_out_dir()
|
self.internal_out_dir()
|
||||||
.join(self.executable_name())
|
.join(&self.main_target)
|
||||||
.join(if release { "release" } else { "debug" })
|
.join(if release { "release" } else { "debug" })
|
||||||
.join(platform.build_folder_name())
|
.join(platform.build_folder_name())
|
||||||
}
|
}
|
||||||
@@ -2748,7 +2758,7 @@ impl BuildRequest {
|
|||||||
/// target/dx/bundle/app/public/
|
/// target/dx/bundle/app/public/
|
||||||
pub(crate) fn bundle_dir(&self, platform: Platform) -> PathBuf {
|
pub(crate) fn bundle_dir(&self, platform: Platform) -> PathBuf {
|
||||||
self.internal_out_dir()
|
self.internal_out_dir()
|
||||||
.join(self.executable_name())
|
.join(&self.main_target)
|
||||||
.join("bundle")
|
.join("bundle")
|
||||||
.join(platform.build_folder_name())
|
.join(platform.build_folder_name())
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-117
@@ -1,8 +1,7 @@
|
|||||||
use crate::{cli::*, AppBuilder, BuildRequest, Workspace, PROFILE_SERVER};
|
use crate::{cli::*, AppBuilder, BuildRequest, Workspace};
|
||||||
use crate::{BuildMode, Platform};
|
use crate::{BuildMode, Platform};
|
||||||
use target_lexicon::Triple;
|
|
||||||
|
|
||||||
use super::target::{TargetArgs, TargetCmd};
|
use super::target::TargetArgs;
|
||||||
|
|
||||||
/// Build the Rust Dioxus app and all of its assets.
|
/// Build the Rust Dioxus app and all of its assets.
|
||||||
///
|
///
|
||||||
@@ -16,42 +15,9 @@ pub struct BuildArgs {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) fullstack: Option<bool>,
|
pub(crate) fullstack: Option<bool>,
|
||||||
|
|
||||||
/// The feature to use for the client in a fullstack app [default: "web"]
|
|
||||||
#[clap(long)]
|
|
||||||
pub(crate) client_features: Vec<String>,
|
|
||||||
|
|
||||||
/// The feature to use for the server in a fullstack app [default: "server"]
|
|
||||||
#[clap(long)]
|
|
||||||
pub(crate) server_features: Vec<String>,
|
|
||||||
|
|
||||||
/// Build with custom profile for the fullstack server
|
|
||||||
#[clap(long, default_value_t = PROFILE_SERVER.to_string())]
|
|
||||||
pub(crate) server_profile: String,
|
|
||||||
|
|
||||||
/// The target to build for the server.
|
|
||||||
///
|
|
||||||
/// This can be different than the host allowing cross-compilation of the server. This is useful for
|
|
||||||
/// platforms like Cloudflare Workers where the server is compiled to wasm and then uploaded to the edge.
|
|
||||||
#[clap(long)]
|
|
||||||
pub(crate) server_target: Option<Triple>,
|
|
||||||
|
|
||||||
/// Arguments for the build itself
|
/// Arguments for the build itself
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) build_arguments: TargetArgs,
|
pub(crate) build_arguments: TargetArgs,
|
||||||
|
|
||||||
/// A list of additional targets to build.
|
|
||||||
///
|
|
||||||
/// Server and Client are special targets that integrate with `dx serve`, while `crate` is a generic.
|
|
||||||
///
|
|
||||||
/// ```sh
|
|
||||||
/// dx serve \
|
|
||||||
/// client --target aarch64-apple-darwin \
|
|
||||||
/// server --target wasm32-unknown-unknown \
|
|
||||||
/// crate --target aarch64-unknown-linux-gnu --package foo \
|
|
||||||
/// crate --target x86_64-unknown-linux-gnu --package bar
|
|
||||||
/// ```
|
|
||||||
#[command(subcommand)]
|
|
||||||
pub(crate) targets: Option<TargetCmd>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BuildTargets {
|
pub struct BuildTargets {
|
||||||
@@ -60,6 +26,40 @@ pub struct BuildTargets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BuildArgs {
|
impl BuildArgs {
|
||||||
|
fn default_client(&self) -> &TargetArgs {
|
||||||
|
&self.build_arguments
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_server(&self, client: &BuildRequest) -> Option<&TargetArgs> {
|
||||||
|
// Now resolve the builds that we need to.
|
||||||
|
// These come from the args, but we'd like them to come from the `TargetCmd` chained object
|
||||||
|
//
|
||||||
|
// The process here is as follows:
|
||||||
|
//
|
||||||
|
// - Create the BuildRequest for the primary target
|
||||||
|
// - If that BuildRequest is "fullstack", then add the client features
|
||||||
|
// - If that BuildRequest is "fullstack", then also create a BuildRequest for the server
|
||||||
|
// with the server features
|
||||||
|
//
|
||||||
|
// This involves modifying the BuildRequest to add the client features and server features
|
||||||
|
// only if we can properly detect that it's a fullstack build. Careful with this, since
|
||||||
|
// we didn't build BuildRequest to be generally mutable.
|
||||||
|
let default_server = client.enabled_platforms.contains(&Platform::Server);
|
||||||
|
|
||||||
|
// Make sure we set the fullstack platform so we actually build the fullstack variant
|
||||||
|
// Users need to enable "fullstack" in their default feature set.
|
||||||
|
// todo(jon): fullstack *could* be a feature of the app, but right now we're assuming it's always enabled
|
||||||
|
//
|
||||||
|
// Now we need to resolve the client features
|
||||||
|
let fullstack = ((default_server || client.fullstack_feature_enabled())
|
||||||
|
|| self.fullstack.unwrap_or(false))
|
||||||
|
&& self.fullstack != Some(false);
|
||||||
|
|
||||||
|
fullstack.then_some(&self.build_arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandWithPlatformOverrides<BuildArgs> {
|
||||||
pub async fn build(self) -> Result<StructuredOutput> {
|
pub async fn build(self) -> Result<StructuredOutput> {
|
||||||
tracing::info!("Building project...");
|
tracing::info!("Building project...");
|
||||||
|
|
||||||
@@ -92,88 +92,29 @@ impl BuildArgs {
|
|||||||
// do some logging to ensure dx matches the dioxus version since we're not always API compatible
|
// do some logging to ensure dx matches the dioxus version since we're not always API compatible
|
||||||
workspace.check_dioxus_version_against_cli();
|
workspace.check_dioxus_version_against_cli();
|
||||||
|
|
||||||
let mut server = None;
|
let client_args = match &self.client {
|
||||||
|
Some(client) => &client.build_arguments,
|
||||||
let client = match self.targets {
|
None => self.shared.default_client(),
|
||||||
// A simple `dx serve` command with no explicit targets
|
|
||||||
None => {
|
|
||||||
// Now resolve the builds that we need to.
|
|
||||||
// These come from the args, but we'd like them to come from the `TargetCmd` chained object
|
|
||||||
//
|
|
||||||
// The process here is as follows:
|
|
||||||
//
|
|
||||||
// - Create the BuildRequest for the primary target
|
|
||||||
// - If that BuildRequest is "fullstack", then add the client features
|
|
||||||
// - If that BuildRequest is "fullstack", then also create a BuildRequest for the server
|
|
||||||
// with the server features
|
|
||||||
//
|
|
||||||
// This involves modifying the BuildRequest to add the client features and server features
|
|
||||||
// only if we can properly detect that it's a fullstack build. Careful with this, since
|
|
||||||
// we didn't build BuildRequest to be generally mutable.
|
|
||||||
let client = BuildRequest::new(&self.build_arguments, workspace.clone()).await?;
|
|
||||||
let default_server = client
|
|
||||||
.enabled_platforms
|
|
||||||
.iter()
|
|
||||||
.any(|p| *p == Platform::Server);
|
|
||||||
|
|
||||||
// Make sure we set the fullstack platform so we actually build the fullstack variant
|
|
||||||
// Users need to enable "fullstack" in their default feature set.
|
|
||||||
// todo(jon): fullstack *could* be a feature of the app, but right now we're assuming it's always enabled
|
|
||||||
//
|
|
||||||
// Now we need to resolve the client features
|
|
||||||
let fullstack = ((default_server || client.fullstack_feature_enabled())
|
|
||||||
|| self.fullstack.unwrap_or(false))
|
|
||||||
&& self.fullstack != Some(false);
|
|
||||||
|
|
||||||
if fullstack {
|
|
||||||
let mut build_args = self.build_arguments.clone();
|
|
||||||
build_args.platform = Some(Platform::Server);
|
|
||||||
|
|
||||||
let _server = BuildRequest::new(&build_args, workspace.clone()).await?;
|
|
||||||
|
|
||||||
// ... todo: add the server features to the server build
|
|
||||||
// ... todo: add the client features to the client build
|
|
||||||
// // Make sure we have a server feature if we're building a fullstack app
|
|
||||||
if self.fullstack.unwrap_or_default() && self.server_features.is_empty() {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
server = Some(_server);
|
|
||||||
}
|
|
||||||
|
|
||||||
client
|
|
||||||
}
|
|
||||||
|
|
||||||
// A command in the form of:
|
|
||||||
// ```
|
|
||||||
// dx serve \
|
|
||||||
// client --package frontend \
|
|
||||||
// server --package backend
|
|
||||||
// ```
|
|
||||||
Some(cmd) => {
|
|
||||||
let mut client_args_ = None;
|
|
||||||
let mut server_args_ = None;
|
|
||||||
let mut cmd_outer = Some(Box::new(cmd));
|
|
||||||
while let Some(cmd) = cmd_outer.take() {
|
|
||||||
match *cmd {
|
|
||||||
TargetCmd::Client(cmd_) => {
|
|
||||||
client_args_ = Some(cmd_.inner);
|
|
||||||
cmd_outer = cmd_.next;
|
|
||||||
}
|
|
||||||
TargetCmd::Server(cmd) => {
|
|
||||||
server_args_ = Some(cmd.inner);
|
|
||||||
cmd_outer = cmd.next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(server_args) = server_args_ {
|
|
||||||
server = Some(BuildRequest::new(&server_args, workspace.clone()).await?);
|
|
||||||
}
|
|
||||||
|
|
||||||
BuildRequest::new(&client_args_.unwrap(), workspace.clone()).await?
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
let client = BuildRequest::new(client_args, None, workspace.clone()).await?;
|
||||||
|
|
||||||
|
let server_args = match &self.server {
|
||||||
|
Some(server) => Some(&server.build_arguments),
|
||||||
|
None => self.shared.default_server(&client),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut server = None;
|
||||||
|
// If there is a server, make sure we output in the same directory as the client build so we use the server
|
||||||
|
// to serve the web client
|
||||||
|
if let Some(server_args) = server_args {
|
||||||
|
// Copy the main target from the client to the server
|
||||||
|
let main_target = client.main_target.clone();
|
||||||
|
let mut server_args = server_args.clone();
|
||||||
|
// The platform in the server build is always set to Server
|
||||||
|
server_args.platform = Some(Platform::Server);
|
||||||
|
server =
|
||||||
|
Some(BuildRequest::new(&server_args, Some(main_target), workspace.clone()).await?);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(BuildTargets { client, server })
|
Ok(BuildTargets { client, server })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ pub struct Bundle {
|
|||||||
|
|
||||||
/// The arguments for the dioxus build
|
/// The arguments for the dioxus build
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) args: BuildArgs,
|
pub(crate) args: CommandWithPlatformOverrides<BuildArgs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bundle {
|
impl Bundle {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub(crate) struct Check {
|
|||||||
|
|
||||||
/// Information about the target to check
|
/// Information about the target to check
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) build_args: BuildArgs,
|
pub(crate) build_args: CommandWithPlatformOverrides<BuildArgs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Check {
|
impl Check {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub(crate) mod config;
|
|||||||
pub(crate) mod create;
|
pub(crate) mod create;
|
||||||
pub(crate) mod init;
|
pub(crate) mod init;
|
||||||
pub(crate) mod link;
|
pub(crate) mod link;
|
||||||
|
pub(crate) mod platform_override;
|
||||||
pub(crate) mod run;
|
pub(crate) mod run;
|
||||||
pub(crate) mod serve;
|
pub(crate) mod serve;
|
||||||
pub(crate) mod target;
|
pub(crate) mod target;
|
||||||
@@ -20,6 +21,7 @@ pub(crate) use serve::*;
|
|||||||
pub(crate) use target::*;
|
pub(crate) use target::*;
|
||||||
pub(crate) use verbosity::*;
|
pub(crate) use verbosity::*;
|
||||||
|
|
||||||
|
use crate::platform_override::CommandWithPlatformOverrides;
|
||||||
use crate::{error::Result, Error, StructuredOutput};
|
use crate::{error::Result, Error, StructuredOutput};
|
||||||
use clap::builder::styling::{AnsiColor, Effects, Style, Styles};
|
use clap::builder::styling::{AnsiColor, Effects, Style, Styles};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
@@ -62,7 +64,7 @@ pub(crate) enum Commands {
|
|||||||
|
|
||||||
/// Build the Dioxus project and all of its assets.
|
/// Build the Dioxus project and all of its assets.
|
||||||
#[clap(name = "build")]
|
#[clap(name = "build")]
|
||||||
Build(build::BuildArgs),
|
Build(CommandWithPlatformOverrides<build::BuildArgs>),
|
||||||
|
|
||||||
/// Run the project without any hotreloading.
|
/// Run the project without any hotreloading.
|
||||||
#[clap(name = "run")]
|
#[clap(name = "run")]
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
use clap::parser::ValueSource;
|
||||||
|
use clap::{ArgMatches, Args, CommandFactory, FromArgMatches, Parser, Subcommand};
|
||||||
|
|
||||||
|
/// Wraps a component with the subcommands `@server` and `@client` which will let you override the
|
||||||
|
/// base arguments for the client and server instances.
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct CommandWithPlatformOverrides<T> {
|
||||||
|
/// The arguments that are shared between the client and server
|
||||||
|
pub shared: T,
|
||||||
|
/// The merged arguments for the server
|
||||||
|
pub server: Option<T>,
|
||||||
|
/// The merged arguments for the client
|
||||||
|
pub client: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> CommandWithPlatformOverrides<T> {
|
||||||
|
pub(crate) fn with_client_or_shared<'a, O>(&'a self, f: impl FnOnce(&'a T) -> O) -> O {
|
||||||
|
match &self.client {
|
||||||
|
Some(client) => f(client),
|
||||||
|
None => f(&self.shared),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_server_or_shared<'a, O>(&'a self, f: impl FnOnce(&'a T) -> O) -> O {
|
||||||
|
match &self.server {
|
||||||
|
Some(server) => f(server),
|
||||||
|
None => f(&self.shared),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: CommandFactory + Args> Parser for CommandWithPlatformOverrides<T> {}
|
||||||
|
|
||||||
|
impl<T: CommandFactory + Args> CommandFactory for CommandWithPlatformOverrides<T> {
|
||||||
|
fn command() -> clap::Command {
|
||||||
|
T::command()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_for_update() -> clap::Command {
|
||||||
|
T::command_for_update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Args for CommandWithPlatformOverrides<T>
|
||||||
|
where
|
||||||
|
T: Args,
|
||||||
|
{
|
||||||
|
fn augment_args(cmd: clap::Command) -> clap::Command {
|
||||||
|
T::augment_args(cmd).defer(|cmd| {
|
||||||
|
PlatformOverrides::<Self>::augment_subcommands(cmd.disable_help_subcommand(true))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn augment_args_for_update(_cmd: clap::Command) -> clap::Command {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_matches<T: Args>(base: &ArgMatches, platform: &ArgMatches) -> Result<T, clap::Error> {
|
||||||
|
let mut base = T::from_arg_matches(base)?;
|
||||||
|
|
||||||
|
let mut platform = platform.clone();
|
||||||
|
let original_ids: Vec<_> = platform.ids().cloned().collect();
|
||||||
|
for arg_id in original_ids {
|
||||||
|
let arg_name = arg_id.as_str();
|
||||||
|
// Remove any default values from the platform matches
|
||||||
|
if platform.value_source(arg_name) == Some(ValueSource::DefaultValue) {
|
||||||
|
_ = platform.try_clear_id(arg_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then merge the stripped platform matches into the base matches
|
||||||
|
base.update_from_arg_matches(&platform)?;
|
||||||
|
|
||||||
|
Ok(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> FromArgMatches for CommandWithPlatformOverrides<T>
|
||||||
|
where
|
||||||
|
T: Args,
|
||||||
|
{
|
||||||
|
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, clap::Error> {
|
||||||
|
let mut client = None;
|
||||||
|
let mut server = None;
|
||||||
|
let mut subcommand = matches.subcommand();
|
||||||
|
while let Some((name, sub_matches)) = subcommand {
|
||||||
|
match name {
|
||||||
|
"@client" => {
|
||||||
|
client = Some(sub_matches);
|
||||||
|
}
|
||||||
|
"@server" => {
|
||||||
|
server = Some(sub_matches);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
subcommand = sub_matches.subcommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
let shared = T::from_arg_matches(matches)?;
|
||||||
|
let client = client
|
||||||
|
.map(|client| merge_matches::<T>(matches, client))
|
||||||
|
.transpose()?;
|
||||||
|
let server = server
|
||||||
|
.map(|server| merge_matches::<T>(matches, server))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
shared,
|
||||||
|
server,
|
||||||
|
client,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_from_arg_matches(&mut self, _matches: &ArgMatches) -> Result<(), clap::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Chain together multiple target commands
|
||||||
|
#[derive(Debug, Subcommand, Clone)]
|
||||||
|
#[command(subcommand_precedence_over_arg = true)]
|
||||||
|
pub(crate) enum PlatformOverrides<T: Args> {
|
||||||
|
/// Specify the arguments for the client build
|
||||||
|
#[clap(name = "@client")]
|
||||||
|
Client(ChainedCommand<T, PlatformOverrides<T>>),
|
||||||
|
|
||||||
|
/// Specify the arguments for the server build
|
||||||
|
#[clap(name = "@server")]
|
||||||
|
Server(ChainedCommand<T, PlatformOverrides<T>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/clap-rs/clap/issues/2222#issuecomment-2524152894
|
||||||
|
//
|
||||||
|
//
|
||||||
|
/// `[Args]` wrapper to match `T` variants recursively in `U`.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ChainedCommand<T, U> {
|
||||||
|
/// Specific Variant.
|
||||||
|
pub inner: T,
|
||||||
|
|
||||||
|
/// Enum containing `Self<T>` variants, in other words possible follow-up commands.
|
||||||
|
pub next: Option<Box<U>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Args for ChainedCommand<T, U>
|
||||||
|
where
|
||||||
|
T: Args,
|
||||||
|
U: Subcommand,
|
||||||
|
{
|
||||||
|
fn augment_args(cmd: clap::Command) -> clap::Command {
|
||||||
|
// We use the special `defer` method which lets us recursively call `augment_args` on the inner command
|
||||||
|
// and thus `from_arg_matches`
|
||||||
|
T::augment_args(cmd).defer(|cmd| U::augment_subcommands(cmd.disable_help_subcommand(true)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn augment_args_for_update(_cmd: clap::Command) -> clap::Command {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> FromArgMatches for ChainedCommand<T, U>
|
||||||
|
where
|
||||||
|
T: Args,
|
||||||
|
U: Subcommand,
|
||||||
|
{
|
||||||
|
fn from_arg_matches(_: &ArgMatches) -> Result<Self, clap::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_from_arg_matches(&mut self, _matches: &ArgMatches) -> Result<(), clap::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,10 +50,6 @@ pub(crate) struct ServeArgs {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) cross_origin_policy: bool,
|
pub(crate) cross_origin_policy: bool,
|
||||||
|
|
||||||
/// Additional arguments to pass to the executable
|
|
||||||
#[clap(long)]
|
|
||||||
pub(crate) args: Vec<String>,
|
|
||||||
|
|
||||||
/// Sets the interval in seconds that the CLI will poll for file changes on WSL.
|
/// Sets the interval in seconds that the CLI will poll for file changes on WSL.
|
||||||
#[clap(long, default_missing_value = "2")]
|
#[clap(long, default_missing_value = "2")]
|
||||||
pub(crate) wsl_file_poll_interval: Option<u16>,
|
pub(crate) wsl_file_poll_interval: Option<u16>,
|
||||||
@@ -76,8 +72,19 @@ pub(crate) struct ServeArgs {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) force_sequential: bool,
|
pub(crate) force_sequential: bool,
|
||||||
|
|
||||||
|
/// Platform-specific arguments for the build
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub(crate) platform_args: CommandWithPlatformOverrides<PlatformServeArgs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Parser)]
|
||||||
|
pub(crate) struct PlatformServeArgs {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) targets: BuildArgs,
|
pub(crate) targets: BuildArgs,
|
||||||
|
|
||||||
|
/// Additional arguments to pass to the executable
|
||||||
|
#[clap(long, default_value = "")]
|
||||||
|
pub(crate) args: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServeArgs {
|
impl ServeArgs {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::cli::*;
|
use crate::cli::*;
|
||||||
use crate::Platform;
|
use crate::Platform;
|
||||||
use clap::{ArgMatches, Args, FromArgMatches, Subcommand};
|
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
/// A single target to build for
|
/// A single target to build for
|
||||||
@@ -96,77 +95,3 @@ pub(crate) struct TargetArgs {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) base_path: Option<String>,
|
pub(crate) base_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chain together multiple target commands
|
|
||||||
#[derive(Debug, Subcommand, Clone)]
|
|
||||||
#[command(subcommand_precedence_over_arg = true)]
|
|
||||||
pub(crate) enum TargetCmd {
|
|
||||||
/// Specify the arguments for the client build
|
|
||||||
#[clap(name = "@client")]
|
|
||||||
Client(ChainedCommand<TargetArgs, TargetCmd>),
|
|
||||||
|
|
||||||
/// Specify the arguments for the server build
|
|
||||||
#[clap(name = "@server")]
|
|
||||||
Server(ChainedCommand<TargetArgs, TargetCmd>),
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/clap-rs/clap/issues/2222#issuecomment-2524152894
|
|
||||||
//
|
|
||||||
//
|
|
||||||
/// `[Args]` wrapper to match `T` variants recursively in `U`.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ChainedCommand<T, U> {
|
|
||||||
/// Specific Variant.
|
|
||||||
pub inner: T,
|
|
||||||
|
|
||||||
/// Enum containing `Self<T>` variants, in other words possible follow-up commands.
|
|
||||||
pub next: Option<Box<U>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> Args for ChainedCommand<T, U>
|
|
||||||
where
|
|
||||||
T: Args,
|
|
||||||
U: Subcommand,
|
|
||||||
{
|
|
||||||
fn augment_args(cmd: clap::Command) -> clap::Command {
|
|
||||||
// We use the special `defer` method which lets us recursively call `augment_args` on the inner command
|
|
||||||
// and thus `from_arg_matches`
|
|
||||||
T::augment_args(cmd).defer(|cmd| U::augment_subcommands(cmd.disable_help_subcommand(true)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn augment_args_for_update(_cmd: clap::Command) -> clap::Command {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> FromArgMatches for ChainedCommand<T, U>
|
|
||||||
where
|
|
||||||
T: Args,
|
|
||||||
U: Subcommand,
|
|
||||||
{
|
|
||||||
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, clap::Error> {
|
|
||||||
// Parse the first command before we try to parse the next one.
|
|
||||||
let inner = T::from_arg_matches(matches)?;
|
|
||||||
|
|
||||||
// Try to parse the remainder of the command as a subcommand.
|
|
||||||
let next = match matches.subcommand() {
|
|
||||||
// Subcommand skips into the matched .subcommand, hence we need to pass *outer* matches, ignoring the inner matches
|
|
||||||
// (which in the average case should only match enumerated T)
|
|
||||||
//
|
|
||||||
// Here, we might want to eventually enable arbitrary names of subcommands if they're prefixed
|
|
||||||
// with a prefix like "@" ie `dx serve @dog-app/backend --args @dog-app/frontend --args`
|
|
||||||
//
|
|
||||||
// we are done, since sub-sub commands are matched in U::
|
|
||||||
Some(_) => Some(Box::new(U::from_arg_matches(matches)?)),
|
|
||||||
|
|
||||||
// no subcommand matched, we are done
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self { inner, next })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_from_arg_matches(&mut self, _matches: &ArgMatches) -> Result<(), clap::Error> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
use super::{AppBuilder, ServeUpdate, WebServer};
|
use super::{AppBuilder, ServeUpdate, WebServer};
|
||||||
use crate::{
|
use crate::{
|
||||||
BuildArtifacts, BuildId, BuildMode, BuildTargets, Error, HotpatchModuleCache, Platform, Result,
|
platform_override::CommandWithPlatformOverrides, BuildArtifacts, BuildId, BuildMode,
|
||||||
ServeArgs, TailwindCli, TraceSrc, Workspace,
|
BuildTargets, Error, HotpatchModuleCache, Platform, Result, ServeArgs, TailwindCli, TraceSrc,
|
||||||
|
Workspace,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use dioxus_core::internal::{
|
use dioxus_core::internal::{
|
||||||
@@ -73,6 +74,11 @@ pub(crate) struct AppServer {
|
|||||||
pub(crate) proxied_port: Option<u16>,
|
pub(crate) proxied_port: Option<u16>,
|
||||||
pub(crate) cross_origin_policy: bool,
|
pub(crate) cross_origin_policy: bool,
|
||||||
|
|
||||||
|
// The arguments that should be forwarded to the client app when it is opened
|
||||||
|
pub(crate) client_args: Vec<String>,
|
||||||
|
// The arguments that should be forwarded to the server app when it is opened
|
||||||
|
pub(crate) server_args: Vec<String>,
|
||||||
|
|
||||||
// Additional plugin-type tools
|
// Additional plugin-type tools
|
||||||
pub(crate) tw_watcher: tokio::task::JoinHandle<Result<()>>,
|
pub(crate) tw_watcher: tokio::task::JoinHandle<Result<()>>,
|
||||||
}
|
}
|
||||||
@@ -93,6 +99,13 @@ impl AppServer {
|
|||||||
let force_sequential = args.force_sequential;
|
let force_sequential = args.force_sequential;
|
||||||
let cross_origin_policy = args.cross_origin_policy;
|
let cross_origin_policy = args.cross_origin_policy;
|
||||||
|
|
||||||
|
// Find the launch args for the client and server
|
||||||
|
let split_args = |args: &str| args.split(' ').map(|s| s.to_string()).collect::<Vec<_>>();
|
||||||
|
let server_args = args.platform_args.with_server_or_shared(|c| &c.args);
|
||||||
|
let server_args = split_args(server_args);
|
||||||
|
let client_args = args.platform_args.with_client_or_shared(|c| &c.args);
|
||||||
|
let client_args = split_args(client_args);
|
||||||
|
|
||||||
// These come from the args but also might come from the workspace settings
|
// These come from the args but also might come from the workspace settings
|
||||||
// We opt to use the manually specified args over the workspace settings
|
// We opt to use the manually specified args over the workspace settings
|
||||||
let hot_reload = args
|
let hot_reload = args
|
||||||
@@ -125,7 +138,12 @@ impl AppServer {
|
|||||||
let (watcher_tx, watcher_rx) = futures_channel::mpsc::unbounded();
|
let (watcher_tx, watcher_rx) = futures_channel::mpsc::unbounded();
|
||||||
let watcher = create_notify_watcher(watcher_tx.clone(), wsl_file_poll_interval as u64);
|
let watcher = create_notify_watcher(watcher_tx.clone(), wsl_file_poll_interval as u64);
|
||||||
|
|
||||||
let BuildTargets { client, server } = args.targets.into_targets().await?;
|
let target_args = CommandWithPlatformOverrides {
|
||||||
|
shared: args.platform_args.shared.targets,
|
||||||
|
server: args.platform_args.server.map(|s| s.targets),
|
||||||
|
client: args.platform_args.client.map(|c| c.targets),
|
||||||
|
};
|
||||||
|
let BuildTargets { client, server } = target_args.into_targets().await?;
|
||||||
|
|
||||||
// All servers will end up behind us (the devserver) but on a different port
|
// All servers will end up behind us (the devserver) but on a different port
|
||||||
// This is so we can serve a loading screen as well as devtools without anything particularly fancy
|
// This is so we can serve a loading screen as well as devtools without anything particularly fancy
|
||||||
@@ -187,6 +205,8 @@ impl AppServer {
|
|||||||
cross_origin_policy,
|
cross_origin_policy,
|
||||||
fullstack,
|
fullstack,
|
||||||
tw_watcher,
|
tw_watcher,
|
||||||
|
server_args,
|
||||||
|
client_args,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only register the hot-reload stuff if we're watching the filesystem
|
// Only register the hot-reload stuff if we're watching the filesystem
|
||||||
@@ -546,6 +566,7 @@ impl AppServer {
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
BuildId::SERVER,
|
BuildId::SERVER,
|
||||||
|
&self.server_args,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -560,6 +581,7 @@ impl AppServer {
|
|||||||
open_browser,
|
open_browser,
|
||||||
self.always_on_top,
|
self.always_on_top,
|
||||||
BuildId::CLIENT,
|
BuildId::CLIENT,
|
||||||
|
&self.client_args,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user