Skip to content

Commit a5c6ddb

Browse files
Add Base.active_manifest() (#57937)
1 parent c2f42a6 commit a5c6ddb

File tree

7 files changed

+133
-1
lines changed

7 files changed

+133
-1
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ New library features
7878
* `Base.ScopedValues.LazyScopedValue{T}` is introduced for scoped values that compute their default using a
7979
`OncePerProcess{T}` callback, allowing for lazy initialization of the default value. `AbstractScopedValue` is
8080
now the abstract base type for both `ScopedValue` and `LazyScopedValue`. ([#59372])
81+
* New `Base.active_manifest()` function to return the path of the active manifest, like `Base.active_project()`.
82+
Also can return the manifest that would be used for a given project file ([#57937])
8183

8284
Standard library changes
8385
------------------------

base/initdefs.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,27 @@ function set_active_project(projfile::Union{AbstractString,Nothing})
371371
end
372372
end
373373

374+
"""
375+
active_manifest()
376+
active_manifest(project_file::AbstractString)
377+
378+
Return the path of the active manifest file, or the manifest file that would be used for a given `project_file`.
379+
380+
In a stacked environment (where multiple environments exist in the load path), this returns the manifest
381+
file for the primary (active) environment only, not the manifests from other environments in the stack.
382+
See the manual section on [Environment stacks](@ref) for more details on how stacked environments work.
383+
384+
See [`Project environments`](@ref project-environments) for details on the difference between a project and a manifest, and the naming
385+
options and their priority in package loading.
386+
387+
See also [`Base.active_project`](@ref), [`Base.set_active_project`](@ref).
388+
"""
389+
function active_manifest(project_file::Union{AbstractString,Nothing}=nothing; search_load_path::Bool=true)
390+
# If `project_file` was specified, use that, otherwise get the active project:
391+
project_file = !isnothing(project_file) ? project_file : active_project(search_load_path)
392+
project_file === nothing && return nothing
393+
return project_file_manifest_path(project_file)
394+
end
374395

375396
"""
376397
load_path()

base/loading.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,7 @@ function project_file_manifest_path(project_file::String)::Union{Nothing,String}
885885
manifest_path === missing || return manifest_path
886886
end
887887
dir = abspath(dirname(project_file))
888+
isfile_casesensitive(project_file) || return nothing
888889
d = parsed_toml(project_file)
889890
base_manifest = workspace_manifest(project_file)
890891
if base_manifest !== nothing

base/public.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public
5353
DL_LOAD_PATH,
5454
load_path,
5555
active_project,
56+
active_manifest,
5657

5758
# Reflection and introspection
5859
get_extension,

doc/src/base/base.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ ans
4444
err
4545
Base.active_project
4646
Base.set_active_project
47+
Base.active_manifest
4748
```
4849

4950
## [Keywords](@id Keywords)

doc/src/manual/code-loading.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Each kind of environment defines these three maps differently, as detailed in th
6464
!!! note
6565
For ease of understanding, the examples throughout this chapter show full data structures for roots, graph and paths. However, Julia's package loading code does not explicitly create these. Instead, it lazily computes only as much of each structure as it needs to load a given package.
6666

67-
### Project environments
67+
### [Project environments](@id project-environments)
6868

6969
A project environment is determined by a directory containing a project file called `Project.toml`, and optionally a manifest file called `Manifest.toml`. These files may also be called `JuliaProject.toml` and `JuliaManifest.toml`, in which case `Project.toml` and `Manifest.toml` are ignored. This allows for coexistence with other tools that might consider files called `Project.toml` and `Manifest.toml` significant. For pure Julia projects, however, the names `Project.toml` and `Manifest.toml` are preferred. However, from Julia v1.10.8 onwards, `(Julia)Manifest-v{major}.{minor}.toml` is recognized as a format to make a given julia version use a specific manifest file i.e. in the same folder, a `Manifest-v1.11.toml` would be used by v1.11 and `Manifest.toml` by any other julia version.
7070

test/loading.jl

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,112 @@ mktempdir() do dir
708708
@test success(cmd)
709709
end
710710

711+
function _with_empty_load_path(f::Function)
712+
old_load_path = copy(Base.LOAD_PATH)
713+
try
714+
empty!(Base.LOAD_PATH)
715+
f()
716+
finally
717+
append!(Base.LOAD_PATH, old_load_path)
718+
end
719+
end
720+
old_act_proj = Base.ACTIVE_PROJECT[]
721+
function _with_activate(f::Function, project_file::Union{AbstractString, Nothing})
722+
try
723+
Base.ACTIVE_PROJECT[] = project_file
724+
f()
725+
finally
726+
Base.ACTIVE_PROJECT[] = old_act_proj
727+
end
728+
end
729+
function _activate_and_get_active_manifest_noarg(project_file::Union{AbstractString, Nothing})
730+
_with_activate(project_file) do
731+
Base.active_manifest()
732+
end
733+
end
734+
735+
@testset "Base.active_manifest()" begin
736+
test_dir = @__DIR__
737+
test_cases = [
738+
(joinpath(test_dir, "TestPkg", "Project.toml"), joinpath(test_dir, "TestPkg", "Manifest.toml")),
739+
(joinpath(test_dir, "project", "Project.toml"), joinpath(test_dir, "project", "Manifest.toml")),
740+
]
741+
742+
@testset "active_manifest() - no argument passed" begin
743+
for (proj, expected_man) in test_cases
744+
@test _activate_and_get_active_manifest_noarg(proj) == expected_man
745+
# Base.active_manifest() should never return a file that doesn't exist:
746+
@test isfile(_activate_and_get_active_manifest_noarg(proj))
747+
end
748+
mktempdir() do dir
749+
proj = joinpath(dir, "Project.toml")
750+
751+
# If the project file doesn't exist, active_manifest() should return `nothing`:
752+
@test _activate_and_get_active_manifest_noarg(proj) === nothing
753+
754+
# If the project file exists but the manifest file does not, active_manifest() should still return `nothing`:
755+
touch(proj)
756+
@test _activate_and_get_active_manifest_noarg(proj) === nothing
757+
758+
# If the project and manifest files both exist, active_manifest() should return the path to the manifest:
759+
manif = joinpath(dir, "Manifest.toml")
760+
touch(manif)
761+
@test _activate_and_get_active_manifest_noarg(proj) == manif
762+
# Base.active_manifest() should never return a file that doesn't exist:
763+
@test isfile(_activate_and_get_active_manifest_noarg(proj))
764+
765+
# If the manifest file exists but the project file does not, active_manifest() should return `nothing`:
766+
rm(proj)
767+
@test _activate_and_get_active_manifest_noarg(proj) == nothing
768+
end
769+
end
770+
771+
@testset "active_manifest(proj::AbstractString)" begin
772+
Base.ACTIVE_PROJECT[] = old_act_proj
773+
for (proj, expected_man) in test_cases
774+
@test Base.active_manifest(proj) == expected_man
775+
# Base.active_manifest() should never return a file that doesn't exist:
776+
@test isfile(Base.active_manifest(proj))
777+
end
778+
mktempdir() do dir
779+
proj = joinpath(dir, "Project.toml")
780+
781+
# If the project file doesn't exist, active_manifest(proj) should return `nothing`:
782+
@test Base.active_manifest(proj) === nothing
783+
784+
# If the project file exists but the manifest file does not, active_manifest(proj) should still return `nothing`:
785+
touch(proj)
786+
@test Base.active_manifest(proj) === nothing
787+
788+
# If the project and manifest files both exist, active_manifest(proj) should return the path to the manifest:
789+
manif = joinpath(dir, "Manifest.toml")
790+
touch(manif)
791+
@test Base.active_manifest(proj) == manif
792+
# Base.active_manifest() should never return a file that doesn't exist:
793+
@test isfile(Base.active_manifest(proj))
794+
795+
# If the manifest file exists but the project file does not, active_manifest(proj) should return `nothing`:
796+
rm(proj)
797+
@test Base.active_manifest(proj) === nothing
798+
end
799+
end
800+
801+
@testset "ACTIVE_PROJECT[] is `nothing` => active_manifest() is nothing" begin
802+
_with_activate(nothing) do; _with_empty_load_path() do
803+
@test Base.active_manifest() === nothing
804+
@test Base.active_manifest(nothing) === nothing
805+
end; end
806+
end
807+
808+
@testset "Project file does not exist => active_manifest() is nothing" begin
809+
mktempdir() do dir
810+
proj = joinpath(dir, "Project.toml")
811+
@test Base.active_manifest(proj) === nothing
812+
@test _activate_and_get_active_manifest_noarg(proj) === nothing
813+
end
814+
end
815+
end
816+
711817
@testset "expansion of JULIA_LOAD_PATH" begin
712818
s = Sys.iswindows() ? ';' : ':'
713819
tmp = "/this/does/not/exist"

0 commit comments

Comments
 (0)