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(
|
||||
&mut self,
|
||||
devserver_ip: SocketAddr,
|
||||
@@ -425,6 +426,7 @@ impl AppBuilder {
|
||||
open_browser: bool,
|
||||
always_on_top: bool,
|
||||
build_id: BuildId,
|
||||
args: &[String],
|
||||
) -> Result<()> {
|
||||
let krate = &self.build;
|
||||
|
||||
@@ -501,7 +503,7 @@ impl AppBuilder {
|
||||
| Platform::MacOS
|
||||
| Platform::Windows
|
||||
| Platform::Linux
|
||||
| Platform::Liveview => self.open_with_main_exe(envs)?,
|
||||
| Platform::Liveview => self.open_with_main_exe(envs, args)?,
|
||||
};
|
||||
|
||||
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.
|
||||
///
|
||||
/// 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();
|
||||
|
||||
tracing::debug!("Opening app with main exe: {main_exe:?}");
|
||||
|
||||
let mut child = Command::new(main_exe)
|
||||
.args(args)
|
||||
.envs(envs)
|
||||
.env_remove("CARGO_MANIFEST_DIR") // running under `dx` shouldn't expose cargo-only :
|
||||
.stderr(Stdio::piped())
|
||||
|
||||
@@ -374,6 +374,7 @@ pub(crate) struct BuildRequest {
|
||||
pub(crate) triple: Triple,
|
||||
pub(crate) device: bool,
|
||||
pub(crate) package: String,
|
||||
pub(crate) main_target: String,
|
||||
pub(crate) features: Vec<String>,
|
||||
pub(crate) extra_cargo_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
|
||||
/// 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 config = workspace
|
||||
@@ -506,6 +511,10 @@ impl BuildRequest {
|
||||
})
|
||||
.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
|
||||
.targets
|
||||
.iter()
|
||||
@@ -727,6 +736,7 @@ impl BuildRequest {
|
||||
extra_cargo_args,
|
||||
release,
|
||||
package,
|
||||
main_target,
|
||||
skip_assets: args.skip_assets,
|
||||
base_path: args.base_path.clone(),
|
||||
wasm_split: args.wasm_split,
|
||||
@@ -2737,7 +2747,7 @@ impl BuildRequest {
|
||||
/// target/dx/build/app/web/server.exe
|
||||
pub(crate) fn build_dir(&self, platform: Platform, release: bool) -> PathBuf {
|
||||
self.internal_out_dir()
|
||||
.join(self.executable_name())
|
||||
.join(&self.main_target)
|
||||
.join(if release { "release" } else { "debug" })
|
||||
.join(platform.build_folder_name())
|
||||
}
|
||||
@@ -2748,7 +2758,7 @@ impl BuildRequest {
|
||||
/// target/dx/bundle/app/public/
|
||||
pub(crate) fn bundle_dir(&self, platform: Platform) -> PathBuf {
|
||||
self.internal_out_dir()
|
||||
.join(self.executable_name())
|
||||
.join(&self.main_target)
|
||||
.join("bundle")
|
||||
.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 target_lexicon::Triple;
|
||||
|
||||
use super::target::{TargetArgs, TargetCmd};
|
||||
use super::target::TargetArgs;
|
||||
|
||||
/// Build the Rust Dioxus app and all of its assets.
|
||||
///
|
||||
@@ -16,42 +15,9 @@ pub struct BuildArgs {
|
||||
#[clap(long)]
|
||||
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
|
||||
#[clap(flatten)]
|
||||
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 {
|
||||
@@ -60,6 +26,40 @@ pub struct BuildTargets {
|
||||
}
|
||||
|
||||
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> {
|
||||
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
|
||||
workspace.check_dioxus_version_against_cli();
|
||||
|
||||
let mut server = None;
|
||||
|
||||
let client = match self.targets {
|
||||
// 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_args = match &self.client {
|
||||
Some(client) => &client.build_arguments,
|
||||
None => self.shared.default_client(),
|
||||
};
|
||||
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 })
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ pub struct Bundle {
|
||||
|
||||
/// The arguments for the dioxus build
|
||||
#[clap(flatten)]
|
||||
pub(crate) args: BuildArgs,
|
||||
pub(crate) args: CommandWithPlatformOverrides<BuildArgs>,
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(crate) struct Check {
|
||||
|
||||
/// Information about the target to check
|
||||
#[clap(flatten)]
|
||||
pub(crate) build_args: BuildArgs,
|
||||
pub(crate) build_args: CommandWithPlatformOverrides<BuildArgs>,
|
||||
}
|
||||
|
||||
impl Check {
|
||||
|
||||
@@ -8,6 +8,7 @@ pub(crate) mod config;
|
||||
pub(crate) mod create;
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod link;
|
||||
pub(crate) mod platform_override;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod serve;
|
||||
pub(crate) mod target;
|
||||
@@ -20,6 +21,7 @@ pub(crate) use serve::*;
|
||||
pub(crate) use target::*;
|
||||
pub(crate) use verbosity::*;
|
||||
|
||||
use crate::platform_override::CommandWithPlatformOverrides;
|
||||
use crate::{error::Result, Error, StructuredOutput};
|
||||
use clap::builder::styling::{AnsiColor, Effects, Style, Styles};
|
||||
use clap::{Parser, Subcommand};
|
||||
@@ -62,7 +64,7 @@ pub(crate) enum Commands {
|
||||
|
||||
/// Build the Dioxus project and all of its assets.
|
||||
#[clap(name = "build")]
|
||||
Build(build::BuildArgs),
|
||||
Build(CommandWithPlatformOverrides<build::BuildArgs>),
|
||||
|
||||
/// Run the project without any hotreloading.
|
||||
#[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)]
|
||||
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.
|
||||
#[clap(long, default_missing_value = "2")]
|
||||
pub(crate) wsl_file_poll_interval: Option<u16>,
|
||||
@@ -76,8 +72,19 @@ pub(crate) struct ServeArgs {
|
||||
#[clap(long)]
|
||||
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)]
|
||||
pub(crate) targets: BuildArgs,
|
||||
|
||||
/// Additional arguments to pass to the executable
|
||||
#[clap(long, default_value = "")]
|
||||
pub(crate) args: String,
|
||||
}
|
||||
|
||||
impl ServeArgs {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::cli::*;
|
||||
use crate::Platform;
|
||||
use clap::{ArgMatches, Args, FromArgMatches, Subcommand};
|
||||
use target_lexicon::Triple;
|
||||
|
||||
/// A single target to build for
|
||||
@@ -96,77 +95,3 @@ pub(crate) struct TargetArgs {
|
||||
#[clap(long)]
|
||||
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 crate::{
|
||||
BuildArtifacts, BuildId, BuildMode, BuildTargets, Error, HotpatchModuleCache, Platform, Result,
|
||||
ServeArgs, TailwindCli, TraceSrc, Workspace,
|
||||
platform_override::CommandWithPlatformOverrides, BuildArtifacts, BuildId, BuildMode,
|
||||
BuildTargets, Error, HotpatchModuleCache, Platform, Result, ServeArgs, TailwindCli, TraceSrc,
|
||||
Workspace,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use dioxus_core::internal::{
|
||||
@@ -73,6 +74,11 @@ pub(crate) struct AppServer {
|
||||
pub(crate) proxied_port: Option<u16>,
|
||||
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
|
||||
pub(crate) tw_watcher: tokio::task::JoinHandle<Result<()>>,
|
||||
}
|
||||
@@ -93,6 +99,13 @@ impl AppServer {
|
||||
let force_sequential = args.force_sequential;
|
||||
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
|
||||
// We opt to use the manually specified args over the workspace settings
|
||||
let hot_reload = args
|
||||
@@ -125,7 +138,12 @@ impl AppServer {
|
||||
let (watcher_tx, watcher_rx) = futures_channel::mpsc::unbounded();
|
||||
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
|
||||
// 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,
|
||||
fullstack,
|
||||
tw_watcher,
|
||||
server_args,
|
||||
client_args,
|
||||
};
|
||||
|
||||
// Only register the hot-reload stuff if we're watching the filesystem
|
||||
@@ -546,6 +566,7 @@ impl AppServer {
|
||||
false,
|
||||
false,
|
||||
BuildId::SERVER,
|
||||
&self.server_args,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -560,6 +581,7 @@ impl AppServer {
|
||||
open_browser,
|
||||
self.always_on_top,
|
||||
BuildId::CLIENT,
|
||||
&self.client_args,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
Reference in new issue
Block a user