@@ -12,6 +12,8 @@ use nix::{
12
12
unistd:: { fork, ForkResult } ,
13
13
} ;
14
14
use normpath:: PathExt ;
15
+ use semver:: Version ;
16
+ use std:: io:: BufRead ;
15
17
#[ cfg( not( windows) ) ]
16
18
use std:: os:: unix:: process:: CommandExt ;
17
19
#[ cfg( windows) ]
@@ -300,6 +302,181 @@ fn get_override_channel(
300
302
}
301
303
}
302
304
305
+ fn get_project ( args : & Vec < String > ) -> Option < PathBuf > {
306
+ let mut project_arg: Option < String > = None ;
307
+ for ( _, arg) in args. iter ( ) . enumerate ( ) {
308
+ if arg. starts_with ( "--project=" ) {
309
+ project_arg = Some ( arg[ "--project=" . len ( ) ..] . to_string ( ) ) ;
310
+ // Note: You'd think this might work, but it's not actually supported.
311
+ // } else if arg == "--project" && i + 1 < args.len() {
312
+ // project_arg = Some(args[i + 1].clone());
313
+ } else if arg == "--" {
314
+ break ;
315
+ }
316
+ }
317
+ let project = if project_arg. is_some ( ) {
318
+ project_arg. unwrap ( )
319
+ } else if let Ok ( val) = std:: env:: var ( "JULIA_PROJECT" ) {
320
+ val
321
+ } else {
322
+ return None ;
323
+ } ;
324
+ if project == "@" {
325
+ return None ;
326
+ } else if project == "@." || project == "" {
327
+ let mut path = PathBuf :: from ( std:: env:: current_dir ( ) . unwrap ( ) ) ;
328
+ while !path. join ( "Project.toml" ) . exists ( ) && !path. join ( "JuliaProject.toml" ) . exists ( ) {
329
+ if !path. pop ( ) {
330
+ return None ;
331
+ }
332
+ }
333
+ return Some ( path) ;
334
+ } else if project == "@script" {
335
+ let mut program_file: Option < String > = None ;
336
+ let no_arg_short_switches = vec ! [ 'v' , 'h' , 'i' , 'q' ] ;
337
+ let no_arg_long_switches = vec ! [
338
+ "--version" ,
339
+ "--help" ,
340
+ "--help-hidden" ,
341
+ "--interactive" ,
342
+ "--quiet" ,
343
+ // Hidden options
344
+ "--lisp" ,
345
+ "--image-codegen" ,
346
+ "--rr-detach" ,
347
+ "--strip-metadata" ,
348
+ "--strip-ir" ,
349
+ "--permalloc-pkgimg" ,
350
+ "--heap-size-hint" ,
351
+ "--trim" ,
352
+ ] ;
353
+ // `args` represents [switches...] -- [programfile] [programargs...]
354
+ // We want to find the first non-switch argument or the first argument after `--`
355
+ let mut skip_next = false ;
356
+ for ( i, arg) in args. iter ( ) . skip ( 1 ) . enumerate ( ) {
357
+ if skip_next {
358
+ skip_next = false ;
359
+ } else if arg == "--" {
360
+ program_file = args. get ( i + 1 ) . cloned ( ) ;
361
+ break ;
362
+ } else if arg. starts_with ( "--" ) {
363
+ if !no_arg_long_switches. contains ( & arg. as_str ( ) ) && !arg. contains ( '=' ) {
364
+ skip_next = true ;
365
+ }
366
+ } else if arg. starts_with ( "-" ) {
367
+ let arg: Vec < char > = arg. chars ( ) . skip ( 1 ) . collect ( ) ;
368
+ if arg. iter ( ) . all ( |& c| no_arg_short_switches. contains ( & c) ) {
369
+ continue ;
370
+ }
371
+ for ( j, & c) in arg. iter ( ) . enumerate ( ) {
372
+ if no_arg_short_switches. contains ( & c) {
373
+ continue ;
374
+ } else if j < arg. len ( ) - 1 {
375
+ break ;
376
+ } else {
377
+ // `j == arg.len() - 1`
378
+ skip_next = true ;
379
+ }
380
+ }
381
+ } else {
382
+ program_file = Some ( arg. clone ( ) ) ;
383
+ break ;
384
+ }
385
+ }
386
+ if let Some ( program_file) = program_file {
387
+ let mut path = PathBuf :: from ( program_file) ;
388
+ path. pop ( ) ;
389
+ while !path. join ( "Project.toml" ) . exists ( ) && !path. join ( "JuliaProject.toml" ) . exists ( ) {
390
+ if !path. pop ( ) {
391
+ return None ;
392
+ }
393
+ }
394
+ return Some ( path) ;
395
+ } else {
396
+ return None ;
397
+ }
398
+ } else if project. starts_with ( '@' ) {
399
+ let depot = match std:: env:: var ( "JULIA_DEPOT_PATH" ) {
400
+ Ok ( val) => match val. split ( ':' ) . next ( ) {
401
+ Some ( p) => PathBuf :: from ( p) ,
402
+ None => dirs:: home_dir ( ) . unwrap ( ) . join ( ".julia" ) ,
403
+ } ,
404
+ _ => dirs:: home_dir ( ) . unwrap ( ) . join ( ".julia" ) ,
405
+ } ;
406
+ let path = depot. join ( "environments" ) . join ( & project[ 1 ..] ) ;
407
+ if path. exists ( ) {
408
+ return Some ( path) ;
409
+ } else {
410
+ return None ;
411
+ }
412
+ } else {
413
+ return Some ( PathBuf :: from ( project) ) ;
414
+ }
415
+ }
416
+
417
+ fn julia_version_from_manifest ( path : PathBuf ) -> Option < Version > {
418
+ let manifest = if path. join ( "Manifest.toml" ) . exists ( ) {
419
+ path. join ( "Manifest.toml" )
420
+ } else if path. join ( "JuliaManifest.toml" ) . exists ( ) {
421
+ path. join ( "JuliaManifest.toml" )
422
+ } else {
423
+ return None ;
424
+ } ;
425
+ let file = std:: fs:: File :: open ( manifest) . ok ( ) ?;
426
+ let reader = std:: io:: BufReader :: new ( file) ;
427
+ // This is a somewhat bootleg way to parse the manifest,
428
+ // but since we know the format it should be fine.
429
+ for line in reader. lines ( ) {
430
+ let line = line. ok ( ) ?;
431
+ if line. starts_with ( "julia_version = " ) {
432
+ return Version :: parse ( line[ "julia_version = " . len ( ) ..] . trim_matches ( '"' ) ) . ok ( ) ;
433
+ }
434
+ }
435
+ return None ;
436
+ }
437
+
438
+ fn get_julia_path_for_version (
439
+ config_data : & JuliaupConfig ,
440
+ juliaupconfig_path : & Path ,
441
+ version : & Version ,
442
+ ) -> Result < PathBuf > {
443
+ let mut best_match: Option < ( & String , Version ) > = None ;
444
+ for ( installed_version_str, path) in & config_data. installed_versions {
445
+ if let Ok ( installed_semver) = Version :: parse ( installed_version_str) {
446
+ if installed_semver. major != version. major || installed_semver. minor != version. minor {
447
+ continue ;
448
+ }
449
+ if let Some ( ( _, ref best_version) ) = best_match {
450
+ if installed_semver > * best_version {
451
+ best_match = Some ( ( & path. path , installed_semver) ) ;
452
+ }
453
+ } else {
454
+ best_match = Some ( ( & path. path , installed_semver) ) ;
455
+ }
456
+ }
457
+ }
458
+ if let Some ( ( path, _) ) = best_match {
459
+ let absolute_path = juliaupconfig_path
460
+ . parent ( )
461
+ . unwrap ( )
462
+ . join ( path)
463
+ . join ( "bin" )
464
+ . join ( format ! ( "julia{}" , std:: env:: consts:: EXE_SUFFIX ) )
465
+ . normalize ( )
466
+ . with_context ( || {
467
+ format ! (
468
+ "Failed to normalize path for Julia binary, starting from `{}`." ,
469
+ juliaupconfig_path. display( )
470
+ )
471
+ } ) ?;
472
+ return Ok ( absolute_path. into_path_buf ( ) ) ;
473
+ } else {
474
+ return Err ( anyhow ! (
475
+ "No installed version of Julia matches the requested version."
476
+ ) ) ;
477
+ }
478
+ }
479
+
303
480
fn run_app ( ) -> Result < i32 > {
304
481
if std:: io:: stdout ( ) . is_terminal ( ) {
305
482
// Set console title
@@ -329,6 +506,16 @@ fn run_app() -> Result<i32> {
329
506
}
330
507
}
331
508
509
+ let manifest_derived_julia_path = if channel_from_cmd_line. is_none ( ) {
510
+ get_project ( & args)
511
+ . and_then ( julia_version_from_manifest)
512
+ . and_then ( |ver| {
513
+ get_julia_path_for_version ( & config_file. data , & paths. juliaupconfig , & ver) . ok ( )
514
+ } )
515
+ } else {
516
+ None
517
+ } ;
518
+
332
519
let ( julia_channel_to_use, juliaup_channel_source) =
333
520
if let Some ( channel) = channel_from_cmd_line {
334
521
( channel, JuliaupChannelSource :: CmdLine )
@@ -344,19 +531,23 @@ fn run_app() -> Result<i32> {
344
531
) ) ;
345
532
} ;
346
533
347
- let ( julia_path, julia_args) = get_julia_path_from_channel (
348
- & versiondb_data,
349
- & config_file. data ,
350
- & julia_channel_to_use,
351
- & paths. juliaupconfig ,
352
- juliaup_channel_source,
353
- )
354
- . with_context ( || {
355
- format ! (
356
- "The Julia launcher failed to determine the command for the `{}` channel." ,
357
- julia_channel_to_use
534
+ let ( julia_path, julia_args) = if let Some ( path) = manifest_derived_julia_path {
535
+ ( path, Vec :: new ( ) )
536
+ } else {
537
+ get_julia_path_from_channel (
538
+ & versiondb_data,
539
+ & config_file. data ,
540
+ & julia_channel_to_use,
541
+ & paths. juliaupconfig ,
542
+ juliaup_channel_source,
358
543
)
359
- } ) ?;
544
+ . with_context ( || {
545
+ format ! (
546
+ "The Julia launcher failed to determine the command for the `{}` channel." ,
547
+ julia_channel_to_use
548
+ )
549
+ } ) ?
550
+ } ;
360
551
361
552
let mut new_args: Vec < String > = Vec :: new ( ) ;
362
553
0 commit comments