Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ New library features
* `Base.ScopedValues.LazyScopedValue{T}` is introduced for scoped values that compute their default using a
`OncePerProcess{T}` callback, allowing for lazy initialization of the default value. `AbstractScopedValue` is
now the abstract base type for both `ScopedValue` and `LazyScopedValue`. ([#59372])
* New `Base.active_manifest()` function to return the path of the active manifest, like `Base.active_project()`.
Also can return the manifest that would be used for a given project file ([#57937])

Standard library changes
------------------------
Expand Down
21 changes: 21 additions & 0 deletions base/initdefs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,27 @@ function set_active_project(projfile::Union{AbstractString,Nothing})
end
end

"""
active_manifest()
active_manifest(project_file::AbstractString)

Return the path of the active manifest file, or the manifest file that would be used for a given `project_file`.

In a stacked environment (where multiple environments exist in the load path), this returns the manifest
file for the primary (active) environment only, not the manifests from other environments in the stack.
See the manual section on [Environment stacks](@ref) for more details on how stacked environments work.

See [`Project environments`](@ref project-environments) for details on the difference between a project and a manifest, and the naming
options and their priority in package loading.

See also [`Base.active_project`](@ref), [`Base.set_active_project`](@ref).
"""
function active_manifest(project_file::Union{AbstractString,Nothing}=nothing; search_load_path::Bool=true)
# If `project_file` was specified, use that, otherwise get the active project:
project_file = !isnothing(project_file) ? project_file : active_project(search_load_path)
project_file === nothing && return nothing
return project_file_manifest_path(project_file)
end

"""
load_path()
Expand Down
1 change: 1 addition & 0 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,7 @@ function project_file_manifest_path(project_file::String)::Union{Nothing,String}
manifest_path === missing || return manifest_path
end
dir = abspath(dirname(project_file))
isfile_casesensitive(project_file) || return nothing
d = parsed_toml(project_file)
base_manifest = workspace_manifest(project_file)
if base_manifest !== nothing
Expand Down
1 change: 1 addition & 0 deletions base/public.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public
DL_LOAD_PATH,
load_path,
active_project,
active_manifest,

# Reflection and introspection
get_extension,
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ ans
err
Base.active_project
Base.set_active_project
Base.active_manifest
```

## [Keywords](@id Keywords)
Expand Down
2 changes: 1 addition & 1 deletion doc/src/manual/code-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Each kind of environment defines these three maps differently, as detailed in th
!!! note
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.

### Project environments
### [Project environments](@id project-environments)

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.

Expand Down
106 changes: 106 additions & 0 deletions test/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,112 @@ mktempdir() do dir
@test success(cmd)
end

function _with_empty_load_path(f::Function)
old_load_path = copy(Base.LOAD_PATH)
try
empty!(Base.LOAD_PATH)
f()
finally
append!(Base.LOAD_PATH, old_load_path)
end
end
old_act_proj = Base.ACTIVE_PROJECT[]
function _with_activate(f::Function, project_file::Union{AbstractString, Nothing})
try
Base.ACTIVE_PROJECT[] = project_file
f()
finally
Base.ACTIVE_PROJECT[] = old_act_proj
end
end
function _activate_and_get_active_manifest_noarg(project_file::Union{AbstractString, Nothing})
_with_activate(project_file) do
Base.active_manifest()
end
end

@testset "Base.active_manifest()" begin
test_dir = @__DIR__
test_cases = [
(joinpath(test_dir, "TestPkg", "Project.toml"), joinpath(test_dir, "TestPkg", "Manifest.toml")),
(joinpath(test_dir, "project", "Project.toml"), joinpath(test_dir, "project", "Manifest.toml")),
]

@testset "active_manifest() - no argument passed" begin
for (proj, expected_man) in test_cases
@test _activate_and_get_active_manifest_noarg(proj) == expected_man
# Base.active_manifest() should never return a file that doesn't exist:
@test isfile(_activate_and_get_active_manifest_noarg(proj))
end
mktempdir() do dir
proj = joinpath(dir, "Project.toml")

# If the project file doesn't exist, active_manifest() should return `nothing`:
@test _activate_and_get_active_manifest_noarg(proj) === nothing

# If the project file exists but the manifest file does not, active_manifest() should still return `nothing`:
touch(proj)
@test _activate_and_get_active_manifest_noarg(proj) === nothing

# If the project and manifest files both exist, active_manifest() should return the path to the manifest:
manif = joinpath(dir, "Manifest.toml")
touch(manif)
@test _activate_and_get_active_manifest_noarg(proj) == manif
# Base.active_manifest() should never return a file that doesn't exist:
@test isfile(_activate_and_get_active_manifest_noarg(proj))

# If the manifest file exists but the project file does not, active_manifest() should return `nothing`:
rm(proj)
@test _activate_and_get_active_manifest_noarg(proj) == nothing
end
end

@testset "active_manifest(proj::AbstractString)" begin
Base.ACTIVE_PROJECT[] = old_act_proj
for (proj, expected_man) in test_cases
@test Base.active_manifest(proj) == expected_man
# Base.active_manifest() should never return a file that doesn't exist:
@test isfile(Base.active_manifest(proj))
end
mktempdir() do dir
proj = joinpath(dir, "Project.toml")

# If the project file doesn't exist, active_manifest(proj) should return `nothing`:
@test Base.active_manifest(proj) === nothing

# If the project file exists but the manifest file does not, active_manifest(proj) should still return `nothing`:
touch(proj)
@test Base.active_manifest(proj) === nothing

# If the project and manifest files both exist, active_manifest(proj) should return the path to the manifest:
manif = joinpath(dir, "Manifest.toml")
touch(manif)
@test Base.active_manifest(proj) == manif
# Base.active_manifest() should never return a file that doesn't exist:
@test isfile(Base.active_manifest(proj))

# If the manifest file exists but the project file does not, active_manifest(proj) should return `nothing`:
rm(proj)
@test Base.active_manifest(proj) === nothing
end
end

@testset "ACTIVE_PROJECT[] is `nothing` => active_manifest() is nothing" begin
_with_activate(nothing) do; _with_empty_load_path() do
@test Base.active_manifest() === nothing
@test Base.active_manifest(nothing) === nothing
end; end
end

@testset "Project file does not exist => active_manifest() is nothing" begin
mktempdir() do dir
proj = joinpath(dir, "Project.toml")
@test Base.active_manifest(proj) === nothing
@test _activate_and_get_active_manifest_noarg(proj) === nothing
end
end
end

@testset "expansion of JULIA_LOAD_PATH" begin
s = Sys.iswindows() ? ';' : ':'
tmp = "/this/does/not/exist"
Expand Down