diff --git a/base/precompilation.jl b/base/precompilation.jl index f681f0091c550..c822955f26bdd 100644 --- a/base/precompilation.jl +++ b/base/precompilation.jl @@ -351,6 +351,50 @@ end const Config = Pair{Cmd, Base.CacheFlags} const PkgConfig = Tuple{PkgId,Config} +# name or parent → ext +function full_name(exts::Dict{PkgId, String}, pkg::PkgId) + if haskey(exts, pkg) + return string(exts[pkg], " → ", pkg.name) + else + return pkg.name + end +end + +function excluded_circular_deps_explanation(io::IOContext{IO}, exts::Dict{PkgId, String}, circular_deps, cycles) + outer_deps = copy(circular_deps) + cycles_names = "" + for cycle in cycles + filter!(!in(cycle), outer_deps) + cycle_str = "" + for (i, pkg) in enumerate(cycle) + j = max(0, i - 1) + if length(cycle) == 1 + line = " ─ " + elseif i == 1 + line = " ┌ " + elseif i < length(cycle) + line = " │ " * " " ^j + else + line = " └" * "─" ^j * " " + end + hascolor = get(io, :color, false)::Bool + line = _color_string(line, :light_black, hascolor) * full_name(exts, pkg) * "\n" + cycle_str *= line + end + cycles_names *= cycle_str + end + plural1 = length(cycles) > 1 ? "these cycles" : "this cycle" + plural2 = length(cycles) > 1 ? "cycles" : "cycle" + msg = """Circular dependency detected. + Precompilation will be skipped for dependencies in $plural1: + $cycles_names""" + if !isempty(outer_deps) + msg *= "Precompilation will also be skipped for the following, which depend on the above $plural2:\n" + msg *= join((" " * full_name(exts, pkg) for pkg in outer_deps), "\n") + end + return msg +end + function precompilepkgs(pkgs::Vector{String}=String[]; internal_call::Bool=false, strict::Bool = false, @@ -408,7 +452,7 @@ function _precompilepkgs(pkgs::Vector{String}, pkg_exts_map = Dict{PkgId, Vector{PkgId}}() function describe_pkg(pkg::PkgId, is_direct_dep::Bool, flags::Cmd, cacheflags::Base.CacheFlags) - name = haskey(exts, pkg) ? string(exts[pkg], " → ", pkg.name) : pkg.name + name = full_name(exts, pkg) name = is_direct_dep ? name : color_string(name, :light_black) if nconfigs > 1 && !isempty(flags) config_str = join(flags, " ") @@ -547,32 +591,51 @@ function _precompilepkgs(pkgs::Vector{String}, # find and guard against circular deps - circular_deps = Base.PkgId[] - # Three states - # !haskey -> never visited - # true -> cannot be compiled due to a cycle (or not yet determined) - # false -> not depending on a cycle + cycles = Vector{Base.PkgId}[] + # For every scanned package, true if pkg found to be in a cycle + # or depends on packages in a cycle and false otherwise. could_be_cycle = Dict{Base.PkgId, Bool}() + # temporary stack for the SCC-like algorithm below + stack = Base.PkgId[] function scan_pkg!(pkg, dmap) - did_visit_dep = true - inpath = get!(could_be_cycle, pkg) do - did_visit_dep = false - return true - end - if did_visit_dep ? inpath : scan_deps!(pkg, dmap) - # Found a cycle. Delete this and all parents - return true + if haskey(could_be_cycle, pkg) + return could_be_cycle[pkg] + else + return scan_deps!(pkg, dmap) end - return false end function scan_deps!(pkg, dmap) + push!(stack, pkg) + cycle = nothing for dep in dmap[pkg] - scan_pkg!(dep, dmap) && return true + if dep in stack + # Created fresh cycle + cycle′ = stack[findlast(==(dep), stack):end] + if cycle === nothing || length(cycle′) < length(cycle) + cycle = cycle′ # try to report smallest cycle possible + end + elseif scan_pkg!(dep, dmap) + # Reaches an existing cycle + could_be_cycle[pkg] = true + pop!(stack) + return true + end + end + pop!(stack) + if cycle !== nothing + push!(cycles, cycle) + could_be_cycle[pkg] = true + return true end could_be_cycle[pkg] = false return false end + # set of packages that depend on a cycle (either because they are + # a part of a cycle themselves or because they transitively depend + # on a package in some cycle) + circular_deps = Base.PkgId[] for pkg in keys(depsmap) + @assert isempty(stack) if scan_pkg!(pkg, depsmap) push!(circular_deps, pkg) for pkg_config in keys(was_processed) @@ -582,7 +645,7 @@ function _precompilepkgs(pkgs::Vector{String}, end end if !isempty(circular_deps) - @warn """Circular dependency detected. Precompilation will be skipped for:\n $(join(string.(circular_deps), "\n "))""" + @warn excluded_circular_deps_explanation(io, exts, circular_deps, cycles) end @debug "precompile: circular dep check done" @@ -973,7 +1036,7 @@ function _precompilepkgs(pkgs::Vector{String}, else join(split(err, "\n"), color_string("\n│ ", Base.warn_color())) end - name = haskey(exts, pkg) ? string(exts[pkg], " → ", pkg.name) : pkg.name + name = full_name(exts, pkg) print(iostr, color_string("\n┌ ", Base.warn_color()), name, color_string("\n│ ", Base.warn_color()), err, color_string("\n└ ", Base.warn_color())) end end