mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-20 13:06:33 +00:00
Add unix sockets for QEMU GDB in OSDK run
command
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
28127850aa
commit
e616eb3a4c
@ -71,7 +71,7 @@ pub enum OsdkSubcommand {
|
|||||||
Build(BuildArgs),
|
Build(BuildArgs),
|
||||||
#[command(about = "Run the kernel with a VMM")]
|
#[command(about = "Run the kernel with a VMM")]
|
||||||
Run(RunArgs),
|
Run(RunArgs),
|
||||||
#[command(about = "Debug the kernel with a VMM as a client or a server")]
|
#[command(about = "Debug a remote target via GDB")]
|
||||||
Debug(DebugArgs),
|
Debug(DebugArgs),
|
||||||
#[command(about = "Execute kernel mode unit test by starting a VMM")]
|
#[command(about = "Execute kernel mode unit test by starting a VMM")]
|
||||||
Test(TestArgs),
|
Test(TestArgs),
|
||||||
@ -115,14 +115,35 @@ pub struct RunArgs {
|
|||||||
pub cargo_args: CargoArgs,
|
pub cargo_args: CargoArgs,
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub osdk_args: OsdkArgs,
|
pub osdk_args: OsdkArgs,
|
||||||
#[arg(long, short = 'G', help = "Enable QEMU GDB server for debugging")]
|
#[command(flatten)]
|
||||||
pub gdb_server: bool,
|
pub gdb_server_args: GdbServerArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Args, Clone, Default)]
|
||||||
|
pub struct GdbServerArgs {
|
||||||
|
/// Whether to enable QEMU GDB server for debugging
|
||||||
|
#[arg(
|
||||||
|
long = "enable-gdb",
|
||||||
|
short = 'G',
|
||||||
|
help = "Enable QEMU GDB server for debugging",
|
||||||
|
default_value_t
|
||||||
|
)]
|
||||||
|
pub is_gdb_enabled: bool,
|
||||||
#[arg(
|
#[arg(
|
||||||
long = "vsc",
|
long = "vsc",
|
||||||
help = "Generate a '.vscode/launch.json' for debugging with Visual Studio Code \
|
help = "Generate a '.vscode/launch.json' for debugging with Visual Studio Code \
|
||||||
(only works when '--gdb-server' is enabled)"
|
(only works when '--enable-gdb' is enabled)",
|
||||||
|
default_value_t
|
||||||
)]
|
)]
|
||||||
pub vsc_launch_file: bool,
|
pub vsc_launch_file: bool,
|
||||||
|
#[arg(
|
||||||
|
long = "gdb-server-addr",
|
||||||
|
help = "The network address on which the GDB server listens, \
|
||||||
|
it can be either a path for the UNIX domain socket or a TCP port on an IP address.",
|
||||||
|
value_name = "ADDR",
|
||||||
|
default_value = ".aster-gdb-socket"
|
||||||
|
)]
|
||||||
|
pub gdb_server_addr: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "custom",
|
"request": "custom",
|
||||||
"targetCreateCommands": ["target create ${workspaceFolder}/target/x86_64-unknown-none/#PROFILE#/#BIN_NAME#"],
|
"targetCreateCommands": ["target create ${workspaceFolder}/target/x86_64-unknown-none/#PROFILE#/#BIN_NAME#"],
|
||||||
"processCreateCommands": ["gdb-remote 1234"]
|
"processCreateCommands": ["gdb-remote #ADDR_PORT#"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,11 @@ use super::{build::create_base_and_build, util::DEFAULT_TARGET_RELPATH};
|
|||||||
use crate::{
|
use crate::{
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
config_manager::{BuildConfig, RunConfig},
|
config_manager::{BuildConfig, RunConfig},
|
||||||
error::Errno,
|
|
||||||
error_msg,
|
|
||||||
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
|
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn execute_run_command(config: &RunConfig) {
|
pub fn execute_run_command(config: &RunConfig) {
|
||||||
if config.gdb_server {
|
if config.gdb_server_args.is_gdb_enabled {
|
||||||
use std::env;
|
use std::env;
|
||||||
env::set_var(
|
env::set_var(
|
||||||
"RUSTFLAGS",
|
"RUSTFLAGS",
|
||||||
@ -29,9 +27,55 @@ pub fn execute_run_command(config: &RunConfig) {
|
|||||||
let default_bundle_directory = osdk_target_directory.join(target_name);
|
let default_bundle_directory = osdk_target_directory.join(target_name);
|
||||||
let existing_bundle = Bundle::load(&default_bundle_directory);
|
let existing_bundle = Bundle::load(&default_bundle_directory);
|
||||||
|
|
||||||
|
let config = RunConfig {
|
||||||
|
manifest: {
|
||||||
|
if config.gdb_server_args.is_gdb_enabled {
|
||||||
|
let qemu_gdb_args: Vec<_> = {
|
||||||
|
let gdb_stub_addr = config.gdb_server_args.gdb_server_addr.as_str();
|
||||||
|
match gdb::stub_type_of(gdb_stub_addr) {
|
||||||
|
gdb::StubAddrType::Unix => {
|
||||||
|
let chardev = format!(
|
||||||
|
"-chardev socket,path={},server=on,wait=off,id=gdb0",
|
||||||
|
gdb_stub_addr
|
||||||
|
);
|
||||||
|
let stub = "-gdb chardev:gdb0".to_owned();
|
||||||
|
vec![chardev, stub, "-S".into()]
|
||||||
|
}
|
||||||
|
gdb::StubAddrType::Tcp => {
|
||||||
|
vec![
|
||||||
|
format!(
|
||||||
|
"-gdb tcp:{}",
|
||||||
|
gdb::tcp_addr_util::format_tcp_addr(gdb_stub_addr)
|
||||||
|
),
|
||||||
|
"-S".into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let qemu_gdb_args: Vec<_> = qemu_gdb_args
|
||||||
|
.into_iter()
|
||||||
|
.filter(|arg| !config.manifest.qemu.args.iter().any(|x| x == arg))
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect();
|
||||||
|
let mut manifest = config.manifest.clone();
|
||||||
|
manifest.qemu.args.extend(qemu_gdb_args);
|
||||||
|
manifest
|
||||||
|
} else {
|
||||||
|
config.manifest.clone()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
..config.clone()
|
||||||
|
};
|
||||||
|
let _vsc_launch_file = config.gdb_server_args.vsc_launch_file.then(|| {
|
||||||
|
vsc::check_gdb_config(&config.gdb_server_args);
|
||||||
|
let profile = super::util::profile_adapter(&config.cargo_args.profile);
|
||||||
|
vsc::VscLaunchConfig::new(profile, &config.gdb_server_args.gdb_server_addr)
|
||||||
|
});
|
||||||
|
|
||||||
// If the source is not since modified and the last build is recent, we can reuse the existing bundle.
|
// If the source is not since modified and the last build is recent, we can reuse the existing bundle.
|
||||||
if let Some(existing_bundle) = existing_bundle {
|
if let Some(existing_bundle) = existing_bundle {
|
||||||
if existing_bundle.can_run_with_config(config) {
|
if existing_bundle.can_run_with_config(&config) {
|
||||||
if let Ok(built_since) =
|
if let Ok(built_since) =
|
||||||
SystemTime::now().duration_since(existing_bundle.last_modified_time())
|
SystemTime::now().duration_since(existing_bundle.last_modified_time())
|
||||||
{
|
{
|
||||||
@ -42,7 +86,7 @@ pub fn execute_run_command(config: &RunConfig) {
|
|||||||
};
|
};
|
||||||
if get_last_modified_time(workspace_root) < existing_bundle.last_modified_time()
|
if get_last_modified_time(workspace_root) < existing_bundle.last_modified_time()
|
||||||
{
|
{
|
||||||
existing_bundle.run(config);
|
existing_bundle.run(&config);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,21 +94,8 @@ pub fn execute_run_command(config: &RunConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let manifest = if config.gdb_server {
|
|
||||||
let qemu_dbg_args: Vec<_> = vec!["-s", "-S"]
|
|
||||||
.into_iter()
|
|
||||||
.filter(|arg| !config.manifest.qemu.args.iter().any(|x| x == arg))
|
|
||||||
.map(|x| x.to_string())
|
|
||||||
.collect();
|
|
||||||
let mut manifest = config.manifest.clone();
|
|
||||||
manifest.qemu.args.extend(qemu_dbg_args);
|
|
||||||
manifest
|
|
||||||
} else {
|
|
||||||
config.manifest.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let required_build_config = BuildConfig {
|
let required_build_config = BuildConfig {
|
||||||
manifest: manifest.clone(),
|
manifest: config.manifest.clone(),
|
||||||
cargo_args: config.cargo_args.clone(),
|
cargo_args: config.cargo_args.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,22 +106,7 @@ pub fn execute_run_command(config: &RunConfig) {
|
|||||||
&required_build_config,
|
&required_build_config,
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
|
bundle.run(&config);
|
||||||
let _vsc_launch_file = config.vsc_launch_file.then(|| {
|
|
||||||
if !config.gdb_server {
|
|
||||||
error_msg!(
|
|
||||||
"No need for a VSCode launch file without launching GDB server,\
|
|
||||||
pass '-h' for help"
|
|
||||||
);
|
|
||||||
std::process::exit(Errno::ExecuteCommand as _);
|
|
||||||
}
|
|
||||||
let profile = super::util::profile_adapter(&config.cargo_args.profile);
|
|
||||||
vsc::VscLaunchConfig::new(profile)
|
|
||||||
});
|
|
||||||
bundle.run(&RunConfig {
|
|
||||||
manifest,
|
|
||||||
..config.clone()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_last_modified_time(path: impl AsRef<Path>) -> SystemTime {
|
fn get_last_modified_time(path: impl AsRef<Path>) -> SystemTime {
|
||||||
@ -111,46 +127,78 @@ fn get_last_modified_time(path: impl AsRef<Path>) -> SystemTime {
|
|||||||
last_modified
|
last_modified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod gdb {
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum StubAddrType {
|
||||||
|
Unix, // Unix Domain Socket
|
||||||
|
Tcp, // IP_ADDR:PORT
|
||||||
|
}
|
||||||
|
pub fn stub_type_of(stub: &str) -> StubAddrType {
|
||||||
|
if stub.split(':').last().unwrap().parse::<u16>().is_ok() {
|
||||||
|
return StubAddrType::Tcp;
|
||||||
|
}
|
||||||
|
StubAddrType::Unix
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod tcp_addr_util {
|
||||||
|
use crate::{error::Errno, error_msg};
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
fn strip_tcp_prefix(addr: &str) -> &str {
|
||||||
|
addr.strip_prefix("tcp:").unwrap_or(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_tcp_addr(addr: &str) -> (&str, u16) {
|
||||||
|
let addr = strip_tcp_prefix(addr);
|
||||||
|
if !addr.contains(':') {
|
||||||
|
error_msg!("Ambiguous GDB server address, use '[IP]:PORT' format");
|
||||||
|
exit(Errno::ParseMetadata as _);
|
||||||
|
}
|
||||||
|
let mut iter = addr.split(':');
|
||||||
|
let host = iter.next().unwrap();
|
||||||
|
let port = iter.next().unwrap().parse().unwrap();
|
||||||
|
(host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_tcp_addr(tcp_addr: &str) -> String {
|
||||||
|
let (host, port) = parse_tcp_addr(tcp_addr);
|
||||||
|
format!("{}:{}", host, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod vsc {
|
mod vsc {
|
||||||
use crate::{commands::util::bin_file_name, util::get_cargo_metadata};
|
use crate::{cli::GdbServerArgs, commands::util::bin_file_name, util::get_cargo_metadata};
|
||||||
|
|
||||||
use serde_json::{from_str, Value};
|
use serde_json::{from_str, Value};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::{read_to_string, write as write_file},
|
fs::{read_to_string, write as write_file},
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::gdb;
|
||||||
|
|
||||||
const VSC_DIR: &str = ".vscode";
|
const VSC_DIR: &str = ".vscode";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
struct Existence {
|
struct Existence {
|
||||||
vsc_dir: bool,
|
vsc_dir: bool,
|
||||||
launch_file: bool,
|
launch_file: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Existence {
|
|
||||||
fn default() -> Self {
|
|
||||||
Existence {
|
|
||||||
vsc_dir: false,
|
|
||||||
launch_file: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn workspace_root() -> String {
|
fn workspace_root() -> String {
|
||||||
get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap()["workspace_root"]
|
get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap()["workspace_root"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_owned()
|
.to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct VscLaunchConfig {
|
pub struct VscLaunchConfig {
|
||||||
existence: Existence,
|
existence: Existence,
|
||||||
backup_launch_path: Option<String>,
|
backup_launch_path: Option<String>,
|
||||||
}
|
}
|
||||||
impl VscLaunchConfig {
|
impl VscLaunchConfig {
|
||||||
pub fn new(profile: &str) -> Self {
|
pub fn new(profile: &str, addr: &str) -> Self {
|
||||||
let workspace = workspace_root();
|
let workspace = workspace_root();
|
||||||
let workspace = Path::new(&workspace);
|
let workspace = Path::new(&workspace);
|
||||||
let launch_file_path = workspace.join(VSC_DIR).join("launch.json");
|
let launch_file_path = workspace.join(VSC_DIR).join("launch.json");
|
||||||
@ -168,7 +216,7 @@ mod vsc {
|
|||||||
if !existence.vsc_dir {
|
if !existence.vsc_dir {
|
||||||
std::fs::create_dir(workspace.join(VSC_DIR)).unwrap();
|
std::fs::create_dir(workspace.join(VSC_DIR)).unwrap();
|
||||||
}
|
}
|
||||||
generate_vsc_launch_file(&workspace, profile).unwrap();
|
generate_vsc_launch_file(workspace, profile, addr).unwrap();
|
||||||
|
|
||||||
VscLaunchConfig {
|
VscLaunchConfig {
|
||||||
existence,
|
existence,
|
||||||
@ -176,7 +224,6 @@ mod vsc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for VscLaunchConfig {
|
impl Drop for VscLaunchConfig {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// remove generated files
|
// remove generated files
|
||||||
@ -207,13 +254,43 @@ mod vsc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Exit if the QEMU GDB server configuration is not valid
|
||||||
|
pub fn check_gdb_config(args: &GdbServerArgs) {
|
||||||
|
use crate::{error::Errno, error_msg};
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
if !args.is_gdb_enabled {
|
||||||
|
error_msg!(
|
||||||
|
"No need for a VSCode launch file without launching GDB server,\
|
||||||
|
pass '-h' for help"
|
||||||
|
);
|
||||||
|
exit(Errno::ParseMetadata as _);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check GDB server address
|
||||||
|
let gdb_stub_addr = args.gdb_server_addr.as_str();
|
||||||
|
if gdb_stub_addr.is_empty() {
|
||||||
|
error_msg!("GDB server address is required to generate a VSCode launch file");
|
||||||
|
exit(Errno::ParseMetadata as _);
|
||||||
|
}
|
||||||
|
if gdb::stub_type_of(gdb_stub_addr) != gdb::StubAddrType::Tcp {
|
||||||
|
error_msg!("Non-TCP GDB server address is not supported under '--vsc' currently");
|
||||||
|
exit(Errno::ParseMetadata as _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_vsc_launch_file(
|
fn generate_vsc_launch_file(
|
||||||
workspace: impl AsRef<Path>,
|
workspace: impl AsRef<Path>,
|
||||||
profile: &str,
|
profile: &str,
|
||||||
|
addr: &str,
|
||||||
) -> Result<(), std::io::Error> {
|
) -> Result<(), std::io::Error> {
|
||||||
let contents = include_str!("launch.json.template")
|
let contents = include_str!("launch.json.template")
|
||||||
.replace("#PROFILE#", profile)
|
.replace("#PROFILE#", profile)
|
||||||
.replace("#BIN_NAME#", &bin_file_name());
|
.replace("#BIN_NAME#", &bin_file_name())
|
||||||
|
.replace(
|
||||||
|
"#ADDR_PORT#",
|
||||||
|
gdb::tcp_addr_util::format_tcp_addr(addr).trim_start_matches(':'),
|
||||||
|
);
|
||||||
|
|
||||||
let original_items: Option<Value> = {
|
let original_items: Option<Value> = {
|
||||||
let launch_file_path = workspace.as_ref().join(VSC_DIR).join("launch.json");
|
let launch_file_path = workspace.as_ref().join(VSC_DIR).join("launch.json");
|
||||||
|
@ -5,6 +5,7 @@ use std::fs;
|
|||||||
use super::{build::do_build, util::DEFAULT_TARGET_RELPATH};
|
use super::{build::do_build, util::DEFAULT_TARGET_RELPATH};
|
||||||
use crate::{
|
use crate::{
|
||||||
base_crate::new_base_crate,
|
base_crate::new_base_crate,
|
||||||
|
cli::GdbServerArgs,
|
||||||
config_manager::{BuildConfig, RunConfig, TestConfig},
|
config_manager::{BuildConfig, RunConfig, TestConfig},
|
||||||
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
|
util::{get_cargo_metadata, get_current_crate_info, get_target_directory},
|
||||||
};
|
};
|
||||||
@ -73,8 +74,7 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?});
|
|||||||
let required_run_config = RunConfig {
|
let required_run_config = RunConfig {
|
||||||
manifest: required_build_config.manifest.clone(),
|
manifest: required_build_config.manifest.clone(),
|
||||||
cargo_args: required_build_config.cargo_args.clone(),
|
cargo_args: required_build_config.cargo_args.clone(),
|
||||||
gdb_server: false,
|
gdb_server_args: GdbServerArgs::default(),
|
||||||
vsc_launch_file: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bundle.run(&required_run_config);
|
bundle.run(&required_run_config);
|
||||||
|
@ -22,7 +22,7 @@ use self::{
|
|||||||
manifest::{OsdkManifest, TomlManifest},
|
manifest::{OsdkManifest, TomlManifest},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{BuildArgs, CargoArgs, DebugArgs, OsdkArgs, RunArgs, TestArgs},
|
cli::{BuildArgs, CargoArgs, DebugArgs, GdbServerArgs, OsdkArgs, RunArgs, TestArgs},
|
||||||
error::Errno,
|
error::Errno,
|
||||||
error_msg,
|
error_msg,
|
||||||
util::get_cargo_metadata,
|
util::get_cargo_metadata,
|
||||||
@ -31,7 +31,7 @@ use crate::{
|
|||||||
|
|
||||||
fn get_final_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManifest {
|
fn get_final_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManifest {
|
||||||
let mut manifest = load_osdk_manifest(cargo_args, osdk_args.select.as_ref());
|
let mut manifest = load_osdk_manifest(cargo_args, osdk_args.select.as_ref());
|
||||||
apply_cli_args(&mut manifest, &osdk_args);
|
apply_cli_args(&mut manifest, osdk_args);
|
||||||
try_fill_system_configs(&mut manifest);
|
try_fill_system_configs(&mut manifest);
|
||||||
manifest
|
manifest
|
||||||
}
|
}
|
||||||
@ -58,8 +58,7 @@ impl BuildConfig {
|
|||||||
pub struct RunConfig {
|
pub struct RunConfig {
|
||||||
pub manifest: OsdkManifest,
|
pub manifest: OsdkManifest,
|
||||||
pub cargo_args: CargoArgs,
|
pub cargo_args: CargoArgs,
|
||||||
pub gdb_server: bool,
|
pub gdb_server_args: GdbServerArgs,
|
||||||
pub vsc_launch_file: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunConfig {
|
impl RunConfig {
|
||||||
@ -68,8 +67,7 @@ impl RunConfig {
|
|||||||
Self {
|
Self {
|
||||||
manifest: get_final_manifest(&cargo_args, &args.osdk_args),
|
manifest: get_final_manifest(&cargo_args, &args.osdk_args),
|
||||||
cargo_args,
|
cargo_args,
|
||||||
gdb_server: args.gdb_server,
|
gdb_server_args: args.gdb_server_args.clone(),
|
||||||
vsc_launch_file: args.vsc_launch_file,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +81,7 @@ pub struct DebugConfig {
|
|||||||
impl DebugConfig {
|
impl DebugConfig {
|
||||||
pub fn parse(args: &DebugArgs) -> Self {
|
pub fn parse(args: &DebugArgs) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cargo_args: split_features(&args.cargo_args),
|
cargo_args: parse_cargo_args(&args.cargo_args),
|
||||||
remote: args.remote.clone(),
|
remote: args.remote.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user