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
51 changes: 38 additions & 13 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -671,20 +671,45 @@ function env_project_file(env::String)::Union{Bool,String}
end

function base_project(project_file)
base_dir = abspath(joinpath(dirname(project_file), ".."))
base_project_file = env_project_file(base_dir)
base_project_file isa String || return nothing
d = parsed_toml(base_project_file)
workspace = get(d, "workspace", nothing)::Union{Dict{String, Any}, Nothing}
if workspace === nothing
return nothing
end
projects = get(workspace, "projects", nothing)::Union{Vector{String}, Nothing, String}
projects === nothing && return nothing
if projects isa Vector && basename(dirname(project_file)) in projects
return base_project_file
home_dir = abspath(homedir())
project_dir = abspath(dirname(project_file))
current_dir = project_dir
# Only stop at home boundary if we started under home
started_in_home = startswith(project_dir, home_dir)

while true
parent_dir = dirname(current_dir)
# Stop if we've reached root
if parent_dir == current_dir
return nothing
end
# Stop if we started in home and have now left it
if started_in_home && !startswith(parent_dir, home_dir)
return nothing
end

base_project_file = env_project_file(parent_dir)
if base_project_file isa String
d = parsed_toml(base_project_file)
workspace = get(d, "workspace", nothing)::Union{Dict{String, Any}, Nothing}
if workspace !== nothing
projects = get(workspace, "projects", nothing)::Union{Vector{String}, Nothing, String}
if projects isa Vector
# Check if any project in the workspace matches the original project
workspace_root = dirname(base_project_file)
for project in projects
project_path = joinpath(workspace_root, project)
if isdir(project_path)
if samefile(project_path, project_dir)
return base_project_file
end
end
end
end
end
end
current_dir = parent_dir
end
return nothing
end

function project_deps_get(env::String, name::String)::Union{Nothing,PkgId}
Expand Down
4 changes: 3 additions & 1 deletion doc/src/manual/code-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,9 @@ A project file can define a workspace by giving a set of projects that is part o
projects = ["test", "benchmarks", "docs", "SomePackage"]
```

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.
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.

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.

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:

Expand Down
8 changes: 8 additions & 0 deletions test/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1669,6 +1669,14 @@ end
@test isfile(Base.locate_package(id_dev))
@test Base.identify_package("Devved2") === nothing

# Test that workspace projects can be specified with subfolder paths
# and that base_project searches upward through multiple directory levels
empty!(LOAD_PATH)
push!(LOAD_PATH, joinpath(@__DIR__, "project", "SubProject", "nested", "deep"))
proj_file = joinpath(@__DIR__, "project", "SubProject", "nested", "deep", "Project.toml")
base_proj = Base.base_project(proj_file)
@test base_proj == joinpath(@__DIR__, "project", "SubProject", "Project.toml")

finally
copy!(LOAD_PATH, old_load_path)
end
Expand Down
2 changes: 1 addition & 1 deletion test/project/SubProject/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name = "MyPkg"
uuid = "0cafdeb2-d7a2-40d0-8d22-4411fcc2c4ee"

[workspace]
projects = ["sub", "PackageThatIsSub", "test"]
projects = ["sub", "PackageThatIsSub", "test", "nested/deep"]

[deps]
Devved = "cbce3a6e-7a3d-4e84-8e6d-b87208df7599"
Expand Down
2 changes: 2 additions & 0 deletions test/project/SubProject/nested/deep/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name = "DeepNested"
uuid = "d5e3a334-7f12-4e5f-9ab8-123456789abc"
2 changes: 2 additions & 0 deletions test/project/SubProject/nested/deep/src/DeepNested.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module DeepNested
end