@@ -417,13 +417,16 @@ function _precompilepkgs(pkgs::Vector{String},
417
417
stale_cache = Dict {StaleCacheKey, Bool} ()
418
418
cachepath_cache = Dict {PkgId, Vector{String}} ()
419
419
420
- exts = Dict {PkgId, String} () # ext -> parent
421
- # make a flat map of each dep and its direct deps
422
- depsmap = Dict {PkgId, Vector{PkgId}} ()
423
- pkg_exts_map = Dict {PkgId, Vector{PkgId}} ()
420
+ # a map from packages/extensions to their direct deps
421
+ direct_deps = Dict {Base.PkgId, Vector{Base.PkgId}} ()
422
+ # a map from parent → extension, including all extensions that are loadable
423
+ # in the current environment (i.e. their triggers are present)
424
+ parent_to_exts = Dict {Base.PkgId, Vector{Base.PkgId}} ()
425
+ # inverse map of `parent_to_ext` above (ext → parent)
426
+ ext_to_parent = Dict {Base.PkgId, Base.PkgId} ()
424
427
425
428
function describe_pkg (pkg:: PkgId , is_direct_dep:: Bool , flags:: Cmd , cacheflags:: Base.CacheFlags )
426
- name = haskey (exts , pkg) ? string (exts [pkg], " → " , pkg. name) : pkg. name
429
+ name = haskey (ext_to_parent , pkg) ? string (ext_to_parent [pkg]. name , " → " , pkg. name) : pkg. name
427
430
name = is_direct_dep ? name : color_string (name, :light_black )
428
431
if nconfigs > 1 && ! isempty (flags)
429
432
config_str = join (flags, " " )
@@ -441,66 +444,101 @@ function _precompilepkgs(pkgs::Vector{String},
441
444
pkg = Base. PkgId (dep, env. names[dep])
442
445
Base. in_sysimage (pkg) && continue
443
446
deps = [Base. PkgId (x, env. names[x]) for x in deps]
444
- depsmap[pkg] = filter! (! Base. in_sysimage, deps)
445
- # add any extensions
446
- pkg_exts = Dict {Base.PkgId, Vector{Base.PkgId}} ()
447
- for (ext_name, extdep_uuids) in env. extensions[dep]
447
+ direct_deps[pkg] = filter! (! Base. in_sysimage, deps)
448
+ for (ext_name, trigger_uuids) in env. extensions[dep]
448
449
ext_uuid = Base. uuid5 (pkg. uuid, ext_name)
449
450
ext = Base. PkgId (ext_uuid, ext_name)
450
451
triggers[ext] = Base. PkgId[pkg] # depends on parent package
451
- all_extdeps_available = true
452
- for extdep_uuid in extdep_uuids
453
- extdep_name = env. names[extdep_uuid ]
454
- if extdep_uuid in keys (env. deps)
455
- push! (triggers[ext], Base. PkgId (extdep_uuid, extdep_name ))
452
+ all_triggers_available = true
453
+ for trigger_uuid in trigger_uuids
454
+ trigger_name = env. names[trigger_uuid ]
455
+ if trigger_uuid in keys (env. deps)
456
+ push! (triggers[ext], Base. PkgId (trigger_uuid, trigger_name ))
456
457
else
457
- all_extdeps_available = false
458
+ all_triggers_available = false
458
459
break
459
460
end
460
461
end
461
- all_extdeps_available || continue
462
- exts[ext] = pkg. name
463
- pkg_exts[ext] = depsmap[ext] = filter (! Base. in_sysimage, triggers[ext])
464
- end
465
- if ! isempty (pkg_exts)
466
- pkg_exts_map[pkg] = collect (keys (pkg_exts))
462
+ all_triggers_available || continue
463
+ ext_to_parent[ext] = pkg
464
+ direct_deps[ext] = filter (! Base. in_sysimage, triggers[ext])
465
+
466
+ if ! haskey (parent_to_exts, pkg)
467
+ parent_to_exts[pkg] = Base. PkgId[ext]
468
+ else
469
+ push! (parent_to_exts[pkg], ext)
470
+ end
467
471
end
468
472
end
469
473
470
- direct_deps = [
474
+ project_deps = [
471
475
Base. PkgId (uuid, name)
472
476
for (name, uuid) in env. project_deps if ! Base. in_sysimage (Base. PkgId (uuid, name))
473
477
]
474
478
475
- # consider exts of direct deps to be direct deps so that errors are reported
476
- append! (direct_deps , keys (filter (d-> last (d) in keys (env. project_deps), exts )))
479
+ # consider exts of project deps to be project deps so that errors are reported
480
+ append! (project_deps , keys (filter (d-> last (d). name in keys (env. project_deps), ext_to_parent )))
477
481
478
482
@debug " precompile: deps collected"
479
483
480
484
# An extension effectively depends on another extension if it has a strict superset of its triggers
481
- for ext_a in keys (exts )
482
- for ext_b in keys (exts )
485
+ for ext_a in keys (ext_to_parent )
486
+ for ext_b in keys (ext_to_parent )
483
487
if triggers[ext_a] ⊋ triggers[ext_b]
484
- push! (depsmap [ext_a], ext_b)
488
+ push! (direct_deps [ext_a], ext_b)
485
489
end
486
490
end
487
491
end
488
492
489
- # this loop must be run after the full depsmap has been populated
490
- for (pkg, pkg_exts) in pkg_exts_map
491
- # find any packages that depend on the extension(s)'s deps and replace those deps in their deps list with the extension(s),
492
- # basically injecting the extension into the precompile order in the graph, to avoid race to precompile extensions
493
- for (_pkg, deps) in depsmap # for each manifest dep
494
- if ! in (_pkg, keys (exts)) && pkg in deps # if not an extension and depends on pkg
495
- append! (deps, pkg_exts) # add the package extensions to deps
496
- filter! (! isequal (pkg), deps) # remove the pkg from deps
493
+ # A package depends on an extension if it (indirectly) depends on all extension triggers
494
+ function expand_indirect_dependencies (direct_deps)
495
+ function visit! (visited, node, all_deps)
496
+ if node in visited
497
+ return
498
+ end
499
+ push! (visited, node)
500
+ for dep in get (Set{Base. PkgId}, direct_deps, node)
501
+ if ! (dep in all_deps)
502
+ push! (all_deps, dep)
503
+ visit! (visited, dep, all_deps)
504
+ end
505
+ end
506
+ end
507
+
508
+ indirect_deps = Dict {Base.PkgId, Set{Base.PkgId}} ()
509
+ for package in keys (direct_deps)
510
+ # Initialize a set to keep track of all dependencies for 'package'
511
+ all_deps = Set {Base.PkgId} ()
512
+ visited = Set {Base.PkgId} ()
513
+ visit! (visited, package, all_deps)
514
+ # Update direct_deps with the complete set of dependencies for 'package'
515
+ indirect_deps[package] = all_deps
516
+ end
517
+ return indirect_deps
518
+ end
519
+
520
+ # this loop must be run after the full direct_deps map has been populated
521
+ indirect_deps = expand_indirect_dependencies (direct_deps)
522
+ for ext in keys (ext_to_parent)
523
+ ext_loadable_in_pkg = Dict {Base.PkgId,Bool} ()
524
+ for pkg in keys (direct_deps)
525
+ is_trigger = in (pkg, direct_deps[ext])
526
+ is_extension = in (pkg, keys (ext_to_parent))
527
+ has_triggers = issubset (direct_deps[ext], indirect_deps[pkg])
528
+ ext_loadable_in_pkg[pkg] = ! is_extension && has_triggers && ! is_trigger
529
+ end
530
+ for (pkg, ext_loadable) in ext_loadable_in_pkg
531
+ if ext_loadable && ! any ((dep)-> ext_loadable_in_pkg[dep], direct_deps[pkg])
532
+ # add an edge if the extension is loadable by pkg, and was not loadable in any
533
+ # of the pkg's dependencies
534
+ push! (direct_deps[pkg], ext)
497
535
end
498
536
end
499
537
end
500
538
@debug " precompile: extensions collected"
501
539
502
540
# return early if no deps
503
- if isempty (depsmap )
541
+ if isempty (direct_deps )
504
542
if isempty (pkgs)
505
543
return
506
544
elseif _from_loading
@@ -518,7 +556,7 @@ function _precompilepkgs(pkgs::Vector{String},
518
556
was_processed = Dict {PkgConfig,Base.Event} ()
519
557
was_recompiled = Dict {PkgConfig,Bool} ()
520
558
for config in configs
521
- for pkgid in keys (depsmap )
559
+ for pkgid in keys (direct_deps )
522
560
pkg_config = (pkgid, config)
523
561
started[pkg_config] = false
524
562
was_processed[pkg_config] = Base. Event ()
@@ -527,7 +565,6 @@ function _precompilepkgs(pkgs::Vector{String},
527
565
end
528
566
@debug " precompile: signalling initialized"
529
567
530
-
531
568
# find and guard against circular deps
532
569
circular_deps = Base. PkgId[]
533
570
# Three states
@@ -554,8 +591,8 @@ function _precompilepkgs(pkgs::Vector{String},
554
591
could_be_cycle[pkg] = false
555
592
return false
556
593
end
557
- for pkg in keys (depsmap )
558
- if scan_pkg! (pkg, depsmap )
594
+ for pkg in keys (direct_deps )
595
+ if scan_pkg! (pkg, direct_deps )
559
596
push! (circular_deps, pkg)
560
597
for pkg_config in keys (was_processed)
561
598
# notify all to allow skipping
@@ -570,33 +607,33 @@ function _precompilepkgs(pkgs::Vector{String},
570
607
571
608
if ! manifest
572
609
if isempty (pkgs)
573
- pkgs = [pkg. name for pkg in direct_deps ]
610
+ pkgs = [pkg. name for pkg in project_deps ]
574
611
end
575
612
# restrict to dependencies of given packages
576
- function collect_all_deps (depsmap , dep, alldeps= Set {Base.PkgId} ())
577
- for _dep in depsmap [dep]
613
+ function collect_all_deps (direct_deps , dep, alldeps= Set {Base.PkgId} ())
614
+ for _dep in direct_deps [dep]
578
615
if ! (_dep in alldeps)
579
616
push! (alldeps, _dep)
580
- collect_all_deps (depsmap , _dep, alldeps)
617
+ collect_all_deps (direct_deps , _dep, alldeps)
581
618
end
582
619
end
583
620
return alldeps
584
621
end
585
622
keep = Set {Base.PkgId} ()
586
- for dep in depsmap
623
+ for dep in direct_deps
587
624
dep_pkgid = first (dep)
588
625
if dep_pkgid. name in pkgs
589
626
push! (keep, dep_pkgid)
590
- collect_all_deps (depsmap , dep_pkgid, keep)
627
+ collect_all_deps (direct_deps , dep_pkgid, keep)
591
628
end
592
629
end
593
- for ext in keys (exts )
594
- if issubset (collect_all_deps (depsmap , ext), keep) # if all extension deps are kept
630
+ for ext in keys (ext_to_parent )
631
+ if issubset (collect_all_deps (direct_deps , ext), keep) # if all extension deps are kept
595
632
push! (keep, ext)
596
633
end
597
634
end
598
- filter! (d-> in (first (d), keep), depsmap )
599
- if isempty (depsmap )
635
+ filter! (d-> in (first (d), keep), direct_deps )
636
+ if isempty (direct_deps )
600
637
if _from_loading
601
638
# if called from loading precompilation it may be a package from another environment stack so
602
639
# don't error and allow serial precompilation to try
@@ -709,7 +746,7 @@ function _precompilepkgs(pkgs::Vector{String},
709
746
i = 1
710
747
last_length = 0
711
748
bar = MiniProgressBar (; indent= 0 , header = " Precompiling packages " , color = :green , percentage= false , always_reprint= true )
712
- n_total = length (depsmap ) * length (configs)
749
+ n_total = length (direct_deps ) * length (configs)
713
750
bar. max = n_total - n_already_precomp
714
751
final_loop = false
715
752
n_print_rows = 0
@@ -739,7 +776,7 @@ function _precompilepkgs(pkgs::Vector{String},
739
776
dep, config = pkg_config
740
777
loaded = warn_loaded && haskey (Base. loaded_modules, dep)
741
778
flags, cacheflags = config
742
- name = describe_pkg (dep, dep in direct_deps , flags, cacheflags)
779
+ name = describe_pkg (dep, dep in project_deps , flags, cacheflags)
743
780
line = if pkg_config in precomperr_deps
744
781
string (color_string (" ? " , Base. warn_color ()), name)
745
782
elseif haskey (failed_deps, pkg_config)
@@ -755,7 +792,7 @@ function _precompilepkgs(pkgs::Vector{String},
755
792
# Offset each spinner animation using the first character in the package name as the seed.
756
793
# If not offset, on larger terminal fonts it looks odd that they all sync-up
757
794
anim_char = anim_chars[(i + Int (dep. name[1 ])) % length (anim_chars) + 1 ]
758
- anim_char_colored = dep in direct_deps ? anim_char : color_string (anim_char, :light_black )
795
+ anim_char_colored = dep in project_deps ? anim_char : color_string (anim_char, :light_black )
759
796
waiting = if haskey (pkgspidlocked, pkg_config)
760
797
who_has_lock = pkgspidlocked[pkg_config]
761
798
color_string (" Being precompiled by $(who_has_lock) " , Base. info_color ())
@@ -791,10 +828,10 @@ function _precompilepkgs(pkgs::Vector{String},
791
828
if ! _from_loading
792
829
Base. LOADING_CACHE[] = Base. LoadingCache ()
793
830
end
794
- @debug " precompile: starting precompilation loop" depsmap direct_deps
831
+ @debug " precompile: starting precompilation loop" direct_deps project_deps
795
832
# # precompilation loop
796
833
797
- for (pkg, deps) in depsmap
834
+ for (pkg, deps) in direct_deps
798
835
cachepaths = get! (() -> Base. find_all_in_cache_path (pkg), cachepath_cache, pkg)
799
836
sourcepath = Base. locate_package (pkg)
800
837
single_requested_pkg = length (requested_pkgs) == 1 && only (requested_pkgs) == pkg. name
@@ -821,13 +858,13 @@ function _precompilepkgs(pkgs::Vector{String},
821
858
is_stale = ! Base. isprecompiled (pkg; ignore_loaded= true , stale_cache, cachepath_cache, cachepaths, sourcepath, flags= cacheflags)
822
859
if ! circular && is_stale
823
860
Base. acquire (parallel_limiter)
824
- is_direct_dep = pkg in direct_deps
861
+ is_project_dep = pkg in project_deps
825
862
826
863
# std monitoring
827
864
std_pipe = Base. link_pipe! (Pipe (); reader_supports_async= true , writer_supports_async= true )
828
865
t_monitor = @async monitor_std (pkg_config, std_pipe; single_requested_pkg)
829
866
830
- name = describe_pkg (pkg, is_direct_dep , flags, cacheflags)
867
+ name = describe_pkg (pkg, is_project_dep , flags, cacheflags)
831
868
lock (print_lock) do
832
869
if ! fancyprint && isempty (pkg_queue)
833
870
printpkgstyle (io, :Precompiling , something (target, " packages..." ))
@@ -850,7 +887,7 @@ function _precompilepkgs(pkgs::Vector{String},
850
887
keep_loaded_modules = false
851
888
# for extensions, any extension in our direct dependencies is one we have a right to load
852
889
# for packages, we may load any extension (all possible triggers are accounted for above)
853
- loadable_exts = haskey (exts , pkg) ? filter ((dep)-> haskey (exts , dep), depsmap [pkg]) : nothing
890
+ loadable_exts = haskey (ext_to_parent , pkg) ? filter ((dep)-> haskey (ext_to_parent , dep), direct_deps [pkg]) : nothing
854
891
Base. compilecache (pkg, sourcepath, std_pipe, std_pipe, keep_loaded_modules;
855
892
flags, cacheflags, loadable_exts)
856
893
end
@@ -965,7 +1002,7 @@ function _precompilepkgs(pkgs::Vector{String},
965
1002
else
966
1003
join (split (err, " \n " ), color_string (" \n │ " , Base. warn_color ()))
967
1004
end
968
- name = haskey (exts , pkg) ? string (exts [pkg], " → " , pkg. name) : pkg. name
1005
+ name = haskey (ext_to_parent , pkg) ? string (ext_to_parent [pkg]. name , " → " , pkg. name) : pkg. name
969
1006
print (iostr, color_string (" \n ┌ " , Base. warn_color ()), name, color_string (" \n │ " , Base. warn_color ()), err, color_string (" \n └ " , Base. warn_color ()))
970
1007
end
971
1008
end
@@ -981,7 +1018,7 @@ function _precompilepkgs(pkgs::Vector{String},
981
1018
n_direct_errs = 0
982
1019
for (pkg_config, err) in failed_deps
983
1020
dep, config = pkg_config
984
- if strict || (dep in direct_deps )
1021
+ if strict || (dep in project_deps )
985
1022
print (err_str, " \n " , dep. name, " " )
986
1023
for cfg in config[1 ]
987
1024
print (err_str, cfg, " " )
0 commit comments