Skip to content

Commit 162634c

Browse files
authored
Add workspace feature (#3841)
This feature allows one to define a "workspace" in a project file by adding ```toml [workspace] projects = ["packageA", "docs", "test"] ``` to it. All projects in a workspace are resolved together into a single manifest file in the "root" project (the project that is not included by any other project in a workspace). The compat of all dependencies are intersected among the projects. Currently, every project in a workspace must be in a folder at the same level as the project that includes it into the workspace. If the test folder is included in the workspace, running `Pkg.test` is similar to just activating the test project and including the `runtest.jl` file. Many functions now take the `workspace` argument to decide if the function should operate on the active project or on the full workspace: - `Pkg.precompile` - `Pkg.status` - `Pkg.why` - `Pkg.instantiate`
1 parent a4ec712 commit 162634c

File tree

18 files changed

+558
-115
lines changed

18 files changed

+558
-115
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
version: 'nightly'
9797
- name: Generate docs
9898
run: |
99-
julia --project --color=yes -e 'using Pkg; Pkg.activate("docs"); Pkg.instantiate(); Pkg.develop(PackageSpec(path = pwd()))'
99+
julia --project --color=yes -e 'using Pkg; Pkg.activate("docs"); Pkg.instantiate();'
100100
julia --project=docs --color=yes docs/make.jl pdf
101101
env:
102102
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ Pkg v1.12 Release Notes
33

44
- It is now possible to specify "sources" for packages in a `[sources]` section in Project.toml.
55
This can be used to add non-registered normal or test dependencies.
6+
- Pkg now has support for "workspaces" which is a way to resolve multiple project files into a single manifest.
7+
The functions `Pkg.status`, `Pkg.why`, `Pkg.instantiate`, `Pkg.precompile` (and their REPL variants) have been updated
8+
to take a `workspace` option. Read more about this feature in the manual about the TOML-files.
69

710
Pkg v1.11 Release Notes
811
=======================

Project.toml

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ license = "MIT"
55
desc = "The next-generation Julia package manager."
66
version = "1.12.0"
77

8+
[workspace]
9+
projects = ["test", "docs"]
10+
811
[deps]
912
Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
1013
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
@@ -27,14 +30,3 @@ REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
2730

2831
[extensions]
2932
REPLExt = "REPL"
30-
31-
[compat]
32-
HistoricalStdlibVersions = "1.2"
33-
34-
[extras]
35-
HistoricalStdlibVersions = "6df8b67a-e8a0-4029-b4b7-ac196fe72102"
36-
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
37-
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
38-
39-
[targets]
40-
test = ["REPL", "Test", "Preferences", "HistoricalStdlibVersions"]

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[deps]
22
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
3+
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
34

45
[compat]
56
Documenter = "1"

docs/src/toml-files.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,24 @@ constraints in detail. It is also possible to list constraints on `julia` itself
128128
julia = "1.1"
129129
```
130130

131+
### The `[workspace]` section
132+
133+
A project file can define a workspace by giving a set of projects that is part of that workspace.
134+
Each project in a workspace can include their own dependencies, compatibility information, and even function as full packages.
135+
136+
When the package manager resolves dependencies, it considers the requirements of all the projects in the workspace. The compatible versions identified during this process are recorded in a single manifest file located next to the base project file.
137+
138+
A workspace is defined in the base project by giving a list of the projects in it:
139+
140+
```toml
141+
[workspace]
142+
projects = ["test", "docs", "benchmarks", "PrivatePackage"]
143+
```
144+
145+
This structure is particularly beneficial for developers using a monorepo approach, where a large number of unregistered packages may be involved. It's also useful for adding documentation or benchmarks to a package by including additional dependencies beyond those of the package itself.
146+
147+
Workspace can be nested, that ism a project that itself defines a workspace can also be part of another workspace.
148+
In this case, the workspaces are "merged" with a single manifest being stored alongside the "root project" (the project that doesn't have another workspace including it).
131149

132150
## `Manifest.toml`
133151

ext/REPLExt/REPLExt.jl

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,17 @@ function promptf()
6868
else
6969
project_name = projname(project_file)
7070
if project_name !== nothing
71-
if textwidth(project_name) > 30
72-
project_name = first(project_name, 27) * "..."
71+
root = Types.find_root_base_project(project_file)
72+
rootname = projname(root)
73+
if root !== project_file
74+
path_prefix = "/" * dirname(Types.relative_project_path(root, project_file))
75+
else
76+
path_prefix = ""
77+
end
78+
if textwidth(rootname) > 30
79+
rootname = first(rootname, 27) * "..."
7380
end
74-
prefix = "($(project_name)) "
81+
prefix = "($(rootname)$(path_prefix)) "
7582
prev_prefix = prefix
7683
prev_project_timestamp = mtime(project_file)
7784
prev_project_file = project_file

src/API.jl

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ end
8282

8383
dependencies() = dependencies(EnvCache())
8484
function dependencies(env::EnvCache)
85-
pkgs = Operations.load_all_deps(env)
85+
pkgs = Operations.load_all_deps_loadable(env)
8686
return Dict(pkg.uuid::UUID => package_info(env, pkg) for pkg in pkgs)
8787
end
8888
function dependencies(fn::Function, uuid::UUID)
@@ -1132,7 +1132,7 @@ end
11321132
function precompile(ctx::Context, pkgs::Vector{PackageSpec}; internal_call::Bool=false,
11331133
strict::Bool=false, warn_loaded = true, already_instantiated = false, timing::Bool = false,
11341134
_from_loading::Bool=false, configs::Union{Base.Precompilation.Config,Vector{Base.Precompilation.Config}}=(``=>Base.CacheFlags()),
1135-
kwargs...)
1135+
workspace::Bool=false, kwargs...)
11361136
Context!(ctx; kwargs...)
11371137
if !already_instantiated
11381138
instantiate(ctx; allow_autoprecomp=false, kwargs...)
@@ -1148,14 +1148,13 @@ function precompile(ctx::Context, pkgs::Vector{PackageSpec}; internal_call::Bool
11481148
io = ctx.io
11491149
if io isa UnstableIO
11501150
# precompile does quite a bit of output and using the UnstableIO can cause
1151-
# some slowdowns, the important part here is to not specialize the whole
1152-
# precompile function on the io
1151+
# some slowdowns
11531152
io = io.io
11541153
end
11551154

11561155
activate(dirname(ctx.env.project_file)) do
11571156
pkgs_name = String[pkg.name for pkg in pkgs]
1158-
return Base.Precompilation.precompilepkgs(pkgs_name; internal_call, strict, warn_loaded, timing, _from_loading, configs, io)
1157+
return Base.Precompilation.precompilepkgs(pkgs_name; internal_call, strict, warn_loaded, timing, _from_loading, configs, manifest=workspace, io)
11591158
end
11601159
end
11611160

@@ -1171,7 +1170,8 @@ end
11711170
instantiate(; kwargs...) = instantiate(Context(); kwargs...)
11721171
function instantiate(ctx::Context; manifest::Union{Bool, Nothing}=nothing,
11731172
update_registry::Bool=true, verbose::Bool=false,
1174-
platform::AbstractPlatform=HostPlatform(), allow_build::Bool=true, allow_autoprecomp::Bool=true, kwargs...)
1173+
platform::AbstractPlatform=HostPlatform(), allow_build::Bool=true, allow_autoprecomp::Bool=true,
1174+
workspace::Bool=false, kwargs...)
11751175
Context!(ctx; kwargs...)
11761176
if Registry.download_default_registries(ctx.io)
11771177
copy!(ctx.registries, Registry.reachable_registries())
@@ -1216,12 +1216,16 @@ function instantiate(ctx::Context; manifest::Union{Bool, Nothing}=nothing,
12161216
" Finally, run `Pkg.instantiate()` again.")
12171217
end
12181218
# check if all source code and artifacts are downloaded to exit early
1219-
if Operations.is_instantiated(ctx.env; platform)
1219+
if Operations.is_instantiated(ctx.env, workspace; platform)
12201220
allow_autoprecomp && Pkg._auto_precompile(ctx, already_instantiated = true)
12211221
return
12221222
end
12231223

1224-
pkgs = Operations.load_all_deps(ctx.env)
1224+
if workspace
1225+
pkgs = Operations.load_all_deps(ctx.env)
1226+
else
1227+
pkgs = Operations.load_all_deps_loadable(ctx.env)
1228+
end
12251229
try
12261230
# First try without updating the registry
12271231
Operations.check_registered(ctx.registries, pkgs)
@@ -1279,14 +1283,14 @@ end
12791283

12801284
@deprecate status(mode::PackageMode) status(mode=mode)
12811285

1282-
function status(ctx::Context, pkgs::Vector{PackageSpec}; diff::Bool=false, mode=PKGMODE_PROJECT, outdated::Bool=false, compat::Bool=false, extensions::Bool=false, io::IO=stdout_f())
1286+
function status(ctx::Context, pkgs::Vector{PackageSpec}; diff::Bool=false, mode=PKGMODE_PROJECT, workspace::Bool=false, outdated::Bool=false, compat::Bool=false, extensions::Bool=false, io::IO=stdout_f())
12831287
if compat
12841288
diff && pkgerror("Compat status has no `diff` mode")
12851289
outdated && pkgerror("Compat status has no `outdated` mode")
12861290
extensions && pkgerror("Compat status has no `extensions` mode")
12871291
Operations.print_compat(ctx, pkgs; io)
12881292
else
1289-
Operations.status(ctx.env, ctx.registries, pkgs; mode, git_diff=diff, io, outdated, extensions)
1293+
Operations.status(ctx.env, ctx.registries, pkgs; mode, git_diff=diff, io, outdated, extensions, workspace)
12901294
end
12911295
return nothing
12921296
end
@@ -1419,7 +1423,7 @@ compat(;kwargs...) = compat(Context(); kwargs...)
14191423
# why #
14201424
#######
14211425

1422-
function why(ctx::Context, pkgs::Vector{PackageSpec}; io::IO, kwargs...)
1426+
function why(ctx::Context, pkgs::Vector{PackageSpec}; io::IO, workspace::Bool=false, kwargs...)
14231427
require_not_empty(pkgs, :why)
14241428

14251429
manifest_resolve!(ctx.env.manifest, pkgs)
@@ -1435,9 +1439,17 @@ function why(ctx::Context, pkgs::Vector{PackageSpec}; io::IO, kwargs...)
14351439
end
14361440
end
14371441

1442+
project_deps = Set(values(ctx.env.project.deps))
1443+
1444+
if workspace
1445+
for (_, project) in ctx.env.workspace
1446+
union!(project_deps, values(project.deps))
1447+
end
1448+
end
1449+
14381450
function find_paths!(final_paths, current, path = UUID[])
14391451
push!(path, current)
1440-
current in values(ctx.env.project.deps) && push!(final_paths, path) # record once we've traversed to a project dep
1452+
current in project_deps && push!(final_paths, path) # record once we've traversed to a project dep
14411453
haskey(incoming, current) || return # but only return if we've reached a leaf that nothing depends on
14421454
for p in incoming[current]
14431455
if p in path

0 commit comments

Comments
 (0)