Skip to content

Commit b26e7cc

Browse files
authored
allow specifying projects in a workspace more than one level under it (#59849)
1 parent 8fdd8fb commit b26e7cc

File tree

6 files changed

+54
-15
lines changed

6 files changed

+54
-15
lines changed

base/loading.jl

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -671,20 +671,45 @@ function env_project_file(env::String)::Union{Bool,String}
671671
end
672672

673673
function base_project(project_file)
674-
base_dir = abspath(joinpath(dirname(project_file), ".."))
675-
base_project_file = env_project_file(base_dir)
676-
base_project_file isa String || return nothing
677-
d = parsed_toml(base_project_file)
678-
workspace = get(d, "workspace", nothing)::Union{Dict{String, Any}, Nothing}
679-
if workspace === nothing
680-
return nothing
681-
end
682-
projects = get(workspace, "projects", nothing)::Union{Vector{String}, Nothing, String}
683-
projects === nothing && return nothing
684-
if projects isa Vector && basename(dirname(project_file)) in projects
685-
return base_project_file
674+
home_dir = abspath(homedir())
675+
project_dir = abspath(dirname(project_file))
676+
current_dir = project_dir
677+
# Only stop at home boundary if we started under home
678+
started_in_home = startswith(project_dir, home_dir)
679+
680+
while true
681+
parent_dir = dirname(current_dir)
682+
# Stop if we've reached root
683+
if parent_dir == current_dir
684+
return nothing
685+
end
686+
# Stop if we started in home and have now left it
687+
if started_in_home && !startswith(parent_dir, home_dir)
688+
return nothing
689+
end
690+
691+
base_project_file = env_project_file(parent_dir)
692+
if base_project_file isa String
693+
d = parsed_toml(base_project_file)
694+
workspace = get(d, "workspace", nothing)::Union{Dict{String, Any}, Nothing}
695+
if workspace !== nothing
696+
projects = get(workspace, "projects", nothing)::Union{Vector{String}, Nothing, String}
697+
if projects isa Vector
698+
# Check if any project in the workspace matches the original project
699+
workspace_root = dirname(base_project_file)
700+
for project in projects
701+
project_path = joinpath(workspace_root, project)
702+
if isdir(project_path)
703+
if samefile(project_path, project_dir)
704+
return base_project_file
705+
end
706+
end
707+
end
708+
end
709+
end
710+
end
711+
current_dir = parent_dir
686712
end
687-
return nothing
688713
end
689714

690715
function project_deps_get(env::String, name::String)::Union{Nothing,PkgId}

doc/src/manual/code-loading.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,9 @@ A project file can define a workspace by giving a set of projects that is part o
406406
projects = ["test", "benchmarks", "docs", "SomePackage"]
407407
```
408408

409-
Each subfolder contains its own `Project.toml` file, which may include additional dependencies and compatibility constraints. In such cases, the package manager gathers all dependency information from all the projects in the workspace generating a single manifest file that combines the versions of all dependencies.
409+
Each project listed in the `projects` array is specified by its relative path from the workspace root. This can be a direct child directory (e.g., `"test"`) or a nested subdirectory (e.g., `"nested/subdir/MyPackage"`). Each project contains its own `Project.toml` file, which may include additional dependencies and compatibility constraints. In such cases, the package manager gathers all dependency information from all the projects in the workspace generating a single manifest file that combines the versions of all dependencies.
410+
411+
When Julia loads a project, it searches upward through parent directories until it reaches the user's home directory to find a workspace that includes that project. This allows workspace projects to be nested at arbitrary depth within the workspace directory tree.
410412

411413
Furthermore, workspaces can be "nested", meaning a project defining a workspace can also be part of another workspace. In this scenario, a single manifest file is still utilized, stored alongside the "root project" (the project that doesn't have another workspace including it). An example file structure could look like this:
412414

test/loading.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,14 @@ end
16691669
@test isfile(Base.locate_package(id_dev))
16701670
@test Base.identify_package("Devved2") === nothing
16711671

1672+
# Test that workspace projects can be specified with subfolder paths
1673+
# and that base_project searches upward through multiple directory levels
1674+
empty!(LOAD_PATH)
1675+
push!(LOAD_PATH, joinpath(@__DIR__, "project", "SubProject", "nested", "deep"))
1676+
proj_file = joinpath(@__DIR__, "project", "SubProject", "nested", "deep", "Project.toml")
1677+
base_proj = Base.base_project(proj_file)
1678+
@test base_proj == joinpath(@__DIR__, "project", "SubProject", "Project.toml")
1679+
16721680
finally
16731681
copy!(LOAD_PATH, old_load_path)
16741682
end

test/project/SubProject/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "MyPkg"
22
uuid = "0cafdeb2-d7a2-40d0-8d22-4411fcc2c4ee"
33

44
[workspace]
5-
projects = ["sub", "PackageThatIsSub", "test"]
5+
projects = ["sub", "PackageThatIsSub", "test", "nested/deep"]
66

77
[deps]
88
Devved = "cbce3a6e-7a3d-4e84-8e6d-b87208df7599"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
name = "DeepNested"
2+
uuid = "d5e3a334-7f12-4e5f-9ab8-123456789abc"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module DeepNested
2+
end

0 commit comments

Comments
 (0)