Skip to content

Commit fcc0603

Browse files
committed
allow specifying projects in a workspace more than one level under it (#59849)
(cherry picked from commit b26e7cc)
1 parent ba1e628 commit fcc0603

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
@@ -667,20 +667,45 @@ function env_project_file(env::String)::Union{Bool,String}
667667
end
668668

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

686711
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
@@ -1658,6 +1658,14 @@ end
16581658
@test isfile(Base.locate_package(id_dev))
16591659
@test Base.identify_package("Devved2") === nothing
16601660

1661+
# Test that workspace projects can be specified with subfolder paths
1662+
# and that base_project searches upward through multiple directory levels
1663+
empty!(LOAD_PATH)
1664+
push!(LOAD_PATH, joinpath(@__DIR__, "project", "SubProject", "nested", "deep"))
1665+
proj_file = joinpath(@__DIR__, "project", "SubProject", "nested", "deep", "Project.toml")
1666+
base_proj = Base.base_project(proj_file)
1667+
@test base_proj == joinpath(@__DIR__, "project", "SubProject", "Project.toml")
1668+
16611669
finally
16621670
copy!(LOAD_PATH, old_load_path)
16631671
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)