351351const Config = Pair{Cmd, Base. CacheFlags}
352352const PkgConfig = Tuple{PkgId,Config}
353353
354+ # name or parent → ext
355+ function full_name (exts:: Dict{PkgId, String} , pkg:: PkgId )
356+ if haskey (exts, pkg)
357+ return string (exts[pkg], " → " , pkg. name)
358+ else
359+ return pkg. name
360+ end
361+ end
362+
363+ function excluded_circular_deps_explanation (io:: IOContext{IO} , exts:: Dict{PkgId, String} , circular_deps, cycles)
364+ outer_deps = copy (circular_deps)
365+ cycles_names = " "
366+ for cycle in cycles
367+ filter! (! in (cycle), outer_deps)
368+ cycle_str = " "
369+ for (i, pkg) in enumerate (cycle)
370+ j = max (0 , i - 1 )
371+ if length (cycle) == 1
372+ line = " ─ "
373+ elseif i == 1
374+ line = " ┌ "
375+ elseif i < length (cycle)
376+ line = " │ " * " " ^ j
377+ else
378+ line = " └" * " ─" ^ j * " "
379+ end
380+ hascolor = get (io, :color , false ):: Bool
381+ line = _color_string (line, :light_black , hascolor) * full_name (exts, pkg) * " \n "
382+ cycle_str *= line
383+ end
384+ cycles_names *= cycle_str
385+ end
386+ plural1 = length (cycles) > 1 ? " these cycles" : " this cycle"
387+ plural2 = length (cycles) > 1 ? " cycles" : " cycle"
388+ msg = """ Circular dependency detected.
389+ Precompilation will be skipped for dependencies in $plural1 :
390+ $cycles_names """
391+ if ! isempty (outer_deps)
392+ msg *= " Precompilation will also be skipped for the following, which depend on the above $plural2 :\n "
393+ msg *= join ((" " * full_name (exts, pkg) for pkg in outer_deps), " \n " )
394+ end
395+ return msg
396+ end
397+
354398function precompilepkgs (pkgs:: Vector{String} = String[];
355399 internal_call:: Bool = false ,
356400 strict:: Bool = false ,
@@ -408,7 +452,7 @@ function _precompilepkgs(pkgs::Vector{String},
408452 pkg_exts_map = Dict {PkgId, Vector{PkgId}} ()
409453
410454 function describe_pkg (pkg:: PkgId , is_direct_dep:: Bool , flags:: Cmd , cacheflags:: Base.CacheFlags )
411- name = haskey (exts, pkg) ? string (exts[pkg], " → " , pkg . name) : pkg . name
455+ name = full_name (exts, pkg)
412456 name = is_direct_dep ? name : color_string (name, :light_black )
413457 if nconfigs > 1 && ! isempty (flags)
414458 config_str = join (flags, " " )
@@ -547,32 +591,51 @@ function _precompilepkgs(pkgs::Vector{String},
547591
548592
549593 # find and guard against circular deps
550- circular_deps = Base. PkgId[]
551- # Three states
552- # !haskey -> never visited
553- # true -> cannot be compiled due to a cycle (or not yet determined)
554- # false -> not depending on a cycle
594+ cycles = Vector{Base. PkgId}[]
595+ # For every scanned package, true if pkg found to be in a cycle
596+ # or depends on packages in a cycle and false otherwise.
555597 could_be_cycle = Dict {Base.PkgId, Bool} ()
598+ # temporary stack for the SCC-like algorithm below
599+ stack = Base. PkgId[]
556600 function scan_pkg! (pkg, dmap)
557- did_visit_dep = true
558- inpath = get! (could_be_cycle, pkg) do
559- did_visit_dep = false
560- return true
561- end
562- if did_visit_dep ? inpath : scan_deps! (pkg, dmap)
563- # Found a cycle. Delete this and all parents
564- return true
601+ if haskey (could_be_cycle, pkg)
602+ return could_be_cycle[pkg]
603+ else
604+ return scan_deps! (pkg, dmap)
565605 end
566- return false
567606 end
568607 function scan_deps! (pkg, dmap)
608+ push! (stack, pkg)
609+ cycle = nothing
569610 for dep in dmap[pkg]
570- scan_pkg! (dep, dmap) && return true
611+ if dep in stack
612+ # Created fresh cycle
613+ cycle′ = stack[findlast (== (dep), stack): end ]
614+ if cycle === nothing || length (cycle′) < length (cycle)
615+ cycle = cycle′ # try to report smallest cycle possible
616+ end
617+ elseif scan_pkg! (dep, dmap)
618+ # Reaches an existing cycle
619+ could_be_cycle[pkg] = true
620+ pop! (stack)
621+ return true
622+ end
623+ end
624+ pop! (stack)
625+ if cycle != = nothing
626+ push! (cycles, cycle)
627+ could_be_cycle[pkg] = true
628+ return true
571629 end
572630 could_be_cycle[pkg] = false
573631 return false
574632 end
633+ # set of packages that depend on a cycle (either because they are
634+ # a part of a cycle themselves or because they transitively depend
635+ # on a package in some cycle)
636+ circular_deps = Base. PkgId[]
575637 for pkg in keys (depsmap)
638+ @assert isempty (stack)
576639 if scan_pkg! (pkg, depsmap)
577640 push! (circular_deps, pkg)
578641 for pkg_config in keys (was_processed)
@@ -582,7 +645,7 @@ function _precompilepkgs(pkgs::Vector{String},
582645 end
583646 end
584647 if ! isempty (circular_deps)
585- @warn """ Circular dependency detected. Precompilation will be skipped for: \n $( join ( string .( circular_deps), " \n " )) """
648+ @warn excluded_circular_deps_explanation (io, exts, circular_deps, cycles)
586649 end
587650 @debug " precompile: circular dep check done"
588651
@@ -973,7 +1036,7 @@ function _precompilepkgs(pkgs::Vector{String},
9731036 else
9741037 join (split (err, " \n " ), color_string (" \n │ " , Base. warn_color ()))
9751038 end
976- name = haskey (exts, pkg) ? string (exts[pkg], " → " , pkg . name) : pkg . name
1039+ name = full_name (exts, pkg)
9771040 print (iostr, color_string (" \n ┌ " , Base. warn_color ()), name, color_string (" \n │ " , Base. warn_color ()), err, color_string (" \n └ " , Base. warn_color ()))
9781041 end
9791042 end
0 commit comments