@@ -40,6 +40,34 @@ enum Command {
4040/// Sandbox-level flags come from `SandboxBuilder` via `#[clap(flatten)]`.
4141/// CLI-only flags (profile, timeout, image, etc.) and non-clap-friendly
4242/// sandbox fields (max_memory, fs_mount, env, gpu, cpu-cores) remain here.
43+ /// GPU visibility for `--gpu`: `all` for every present GPU, or explicit
44+ /// indices like `0,2`. Absence of the flag means no GPU access.
45+ #[ derive( Clone , Debug ) ]
46+ enum GpuSelection {
47+ All ,
48+ Devices ( Vec < u32 > ) ,
49+ }
50+
51+ impl std:: str:: FromStr for GpuSelection {
52+ type Err = String ;
53+
54+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
55+ if s. eq_ignore_ascii_case ( "all" ) {
56+ return Ok ( GpuSelection :: All ) ;
57+ }
58+ let devices = s
59+ . split ( ',' )
60+ . map ( |p| {
61+ let p = p. trim ( ) ;
62+ p. parse :: < u32 > ( ) . map_err ( |_| {
63+ format ! ( "invalid GPU index '{p}'; expected 'all' or indices like 0,2" )
64+ } )
65+ } )
66+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
67+ Ok ( GpuSelection :: Devices ( devices) )
68+ }
69+ }
70+
4371#[ derive( clap:: Args ) ]
4472struct RunArgs {
4573 // ── Sandbox flags (flattened from SandboxBuilder) ───────────────────────
@@ -78,9 +106,9 @@ struct RunArgs {
78106 #[ arg( long = "cpu-cores" , value_delimiter = ',' ) ]
79107 cpu_cores : Vec < u32 > ,
80108
81- /// GPU device indices visible to the sandbox (e.g. --gpu 0,2)
82- #[ arg( long = "gpu" , value_delimiter = ',' ) ]
83- gpu_devices : Vec < u32 > ,
109+ /// GPUs visible to the sandbox: `all` for every GPU, or indices (e.g. --gpu 0,2)
110+ #[ arg( long = "gpu" , value_name = "all|N,N,..." ) ]
111+ gpu : Option < GpuSelection > ,
84112
85113 // ── CLI-only flags ───────────────────────────────────────────────────────
86114 #[ arg( short = 't' , long) ]
@@ -466,7 +494,13 @@ async fn run_command(args: RunArgs) -> Result<i32> {
466494 } ;
467495 }
468496 if !args. cpu_cores . is_empty ( ) { builder = builder. cpu_cores ( args. cpu_cores . clone ( ) ) ; }
469- if !args. gpu_devices . is_empty ( ) { builder = builder. gpu_devices ( args. gpu_devices . clone ( ) ) ; }
497+ match & args. gpu {
498+ // `all` maps to an empty device list, which the core expands to every
499+ // present GPU; explicit indices pass through unchanged.
500+ Some ( GpuSelection :: All ) => builder = builder. gpu_devices ( Vec :: new ( ) ) ,
501+ Some ( GpuSelection :: Devices ( devs) ) => builder = builder. gpu_devices ( devs. clone ( ) ) ,
502+ None => { }
503+ }
470504 for spec in & args. env_vars {
471505 if let Some ( ( k, v) ) = spec. split_once ( '=' ) {
472506 builder = builder. env_var ( k, v) ;
@@ -694,7 +728,7 @@ fn validate_no_supervisor(args: &RunArgs) -> Result<()> {
694728 if args. max_disk . is_some ( ) { bad. push ( "--max-disk" ) ; }
695729 if pb. port_remap { bad. push ( "--port-remap" ) ; }
696730 if !args. cpu_cores . is_empty ( ) { bad. push ( "--cpu-cores" ) ; }
697- if ! args. gpu_devices . is_empty ( ) { bad. push ( "--gpu" ) ; }
731+ if args. gpu . is_some ( ) { bad. push ( "--gpu" ) ; }
698732 if args. dry_run { bad. push ( "--dry-run" ) ; }
699733 if args. status_fd . is_some ( ) { bad. push ( "--status-fd" ) ; }
700734 if !pb. fs_denied . is_empty ( ) { bad. push ( "--fs-deny" ) ; }
0 commit comments