Skip to content

Commit e4a3a2a

Browse files
Fix precompiling when there's no manifest (#59212)
1 parent 46c2a5c commit e4a3a2a

File tree

3 files changed

+82
-11
lines changed

3 files changed

+82
-11
lines changed

base/loading.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2629,7 +2629,7 @@ function __require_prelocked(pkg::PkgId, env)
26292629
parallel_precompile_attempted = true
26302630
unlock(require_lock)
26312631
try
2632-
Precompilation.precompilepkgs([pkg.name]; _from_loading=true, ignore_loaded=false)
2632+
Precompilation.precompilepkgs([pkg]; _from_loading=true, ignore_loaded=false)
26332633
finally
26342634
lock(require_lock)
26352635
end

base/precompilation.jl

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ function ExplicitEnv(::Nothing, envpath::String="")
4141
end
4242
function ExplicitEnv(envpath::String)
4343
# Handle missing project file by creating an empty environment
44-
if !isfile(envpath)
44+
if !isfile(envpath) || project_file_manifest_path(envpath) === nothing
4545
envpath = abspath(envpath)
4646
return ExplicitEnv(nothing, envpath)
4747
end
@@ -471,7 +471,7 @@ function collect_all_deps(direct_deps, dep, alldeps=Set{Base.PkgId}())
471471
end
472472

473473

474-
function precompilepkgs(pkgs::Vector{String}=String[];
474+
function precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}}=String[];
475475
internal_call::Bool=false,
476476
strict::Bool = false,
477477
warn_loaded::Bool = true,
@@ -490,7 +490,7 @@ function precompilepkgs(pkgs::Vector{String}=String[];
490490
IOContext{IO}(io), fancyprint, manifest, ignore_loaded)
491491
end
492492

493-
function _precompilepkgs(pkgs::Vector{String},
493+
function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
494494
internal_call::Bool,
495495
strict::Bool,
496496
warn_loaded::Bool,
@@ -502,6 +502,23 @@ function _precompilepkgs(pkgs::Vector{String},
502502
manifest::Bool,
503503
ignore_loaded::Bool)
504504
requested_pkgs = copy(pkgs) # for understanding user intent
505+
pkg_names = pkgs isa Vector{String} ? copy(pkgs) : String[pkg.name for pkg in pkgs]
506+
if pkgs isa Vector{PkgId}
507+
requested_pkgids = copy(pkgs)
508+
else
509+
requested_pkgids = PkgId[]
510+
for name in pkgs
511+
pkgid = Base.identify_package(name)
512+
if pkgid === nothing
513+
if _from_loading
514+
return # leave it up to loading to handle this
515+
else
516+
throw(PkgPrecompileError("Unknown package: $name"))
517+
end
518+
end
519+
push!(requested_pkgids, pkgid)
520+
end
521+
end
505522

506523
time_start = time_ns()
507524

@@ -655,8 +672,7 @@ function _precompilepkgs(pkgs::Vector{String},
655672
# if called from loading precompilation it may be a package from another environment stack
656673
# where we don't have access to the dep graph, so just add as a single package and do serial
657674
# precompilation of its deps within the job.
658-
for pkg in requested_pkgs # In case loading asks for multiple packages
659-
pkgid = Base.identify_package(pkg)
675+
for pkgid in requested_pkgids # In case loading asks for multiple packages
660676
pkgid === nothing && continue
661677
if !haskey(direct_deps, pkgid)
662678
@debug "precompile: package `$(pkgid)` is outside of the environment, so adding as single package serial job"
@@ -704,6 +720,7 @@ function _precompilepkgs(pkgs::Vector{String},
704720
circular_deps = Base.PkgId[]
705721
for pkg in keys(direct_deps)
706722
@assert isempty(stack)
723+
pkg in serial_deps && continue # skip serial deps as we don't have their dependency graph
707724
if scan_pkg!(stack, could_be_cycle, cycles, pkg, direct_deps)
708725
push!(circular_deps, pkg)
709726
for pkg_config in keys(was_processed)
@@ -717,18 +734,31 @@ function _precompilepkgs(pkgs::Vector{String},
717734
end
718735
@debug "precompile: circular dep check done"
719736

737+
# If you have a workspace and want to precompile all projects in it, look through all packages in the manifest
738+
# instead of collecting from a project i.e. not filter out packages that are in the current project.
739+
# i.e. Pkg sets manifest to true for workspace precompile requests
740+
# TODO: rename `manifest`?
720741
if !manifest
721-
if isempty(pkgs)
722-
pkgs = [pkg.name for pkg in project_deps]
742+
if isempty(pkg_names)
743+
pkg_names = [pkg.name for pkg in project_deps]
723744
end
724745
keep = Set{Base.PkgId}()
725746
for dep in direct_deps
726747
dep_pkgid = first(dep)
727-
if dep_pkgid.name in pkgs
748+
if dep_pkgid.name in pkg_names
728749
push!(keep, dep_pkgid)
729750
collect_all_deps(direct_deps, dep_pkgid, keep)
730751
end
731752
end
753+
# Also keep packages that were explicitly requested as PkgIds (for extensions)
754+
if pkgs isa Vector{PkgId}
755+
for requested_pkgid in requested_pkgids
756+
if haskey(direct_deps, requested_pkgid)
757+
push!(keep, requested_pkgid)
758+
collect_all_deps(direct_deps, requested_pkgid, keep)
759+
end
760+
end
761+
end
732762
for ext in keys(ext_to_parent)
733763
if issubset(collect_all_deps(direct_deps, ext), keep) # if all extension deps are kept
734764
push!(keep, ext)
@@ -936,7 +966,8 @@ function _precompilepkgs(pkgs::Vector{String},
936966
for (pkg, deps) in direct_deps
937967
cachepaths = get!(() -> Base.find_all_in_cache_path(pkg), cachepath_cache, pkg)
938968
sourcepath = Base.locate_package(pkg)
939-
single_requested_pkg = length(requested_pkgs) == 1 && only(requested_pkgs) == pkg.name
969+
single_requested_pkg = length(requested_pkgs) == 1 &&
970+
(pkg in requested_pkgids || pkg.name in pkg_names)
940971
for config in configs
941972
pkg_config = (pkg, config)
942973
if sourcepath === nothing
@@ -1063,7 +1094,7 @@ function _precompilepkgs(pkgs::Vector{String},
10631094
str = sprint(context=io) do iostr
10641095
if !quick_exit
10651096
if fancyprint # replace the progress bar
1066-
what = isempty(requested_pkgs) ? "packages finished." : "$(join(requested_pkgs, ", ", " and ")) finished."
1097+
what = isempty(requested_pkgids) ? "packages finished." : "$(join((p.name for p in requested_pkgids), ", ", " and ")) finished."
10671098
printpkgstyle(iostr, :Precompiling, what)
10681099
end
10691100
plural = length(configs) > 1 ? "dependency configurations" : ndeps == 1 ? "dependency" : "dependencies"

test/precompile.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,6 +2496,46 @@ precompile_test_harness("Package top-level load itself") do load_path
24962496
end
24972497
end
24982498

2499+
precompile_test_harness("Package precompilation works without manifest") do load_path
2500+
pkg_dir = joinpath(load_path, "TestPkgNoManifest")
2501+
mkpath(pkg_dir)
2502+
2503+
# Create Project.toml with stdlib dependencies
2504+
write(joinpath(pkg_dir, "Project.toml"), """
2505+
name = "TestPkgNoManifest"
2506+
uuid = "f47a8e44-5f82-4c5c-9076-4b4e8b7e8e8e"
2507+
version = "0.1.0"
2508+
2509+
[deps]
2510+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2511+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
2512+
""")
2513+
2514+
# Create src directory and main module file
2515+
src_dir = joinpath(pkg_dir, "src")
2516+
mkpath(src_dir)
2517+
write(joinpath(src_dir, "TestPkgNoManifest.jl"), """
2518+
module TestPkgNoManifest
2519+
end
2520+
""")
2521+
2522+
old_active_project = Base.active_project()
2523+
try
2524+
# Activate the new package environment
2525+
Base.set_active_project(joinpath(pkg_dir, "Project.toml"))
2526+
2527+
# Ensure there's no manifest file (this is the key to the test)
2528+
manifest_path = joinpath(pkg_dir, "Manifest.toml")
2529+
isfile(manifest_path) && rm(manifest_path)
2530+
2531+
# This should work without errors - precompiling a package with no manifest
2532+
@eval using TestPkgNoManifest
2533+
finally
2534+
# Restore original load path and active project
2535+
Base.set_active_project(old_active_project)
2536+
end
2537+
end
2538+
24992539
# Verify that inference / caching was not performed for any macros in the sysimage
25002540
let m = only(methods(Base.var"@big_str"))
25012541
@test m.specializations === Core.svec() || !isdefined(m.specializations, :cache)

0 commit comments

Comments
 (0)