Skip to content

Commit 5a1e364

Browse files
KristofferCquinnj
andcommitted
Support a [sources] section in Project.toml for specifying paths and repo locations for dependencies (#3783)
* Support a `[sources]` section in Project.toml for specifying relative path and repo locations for dependencies --------- Co-authored-by: Jacob Quinn <[email protected]>
1 parent 99d1831 commit 5a1e364

File tree

16 files changed

+306
-35
lines changed

16 files changed

+306
-35
lines changed

Diff for: CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
Pkg v1.12 Release Notes
2+
=======================
3+
4+
- It is now possible to specify "sources" for packages in a `[sources]` section in Project.toml.
5+
This can be used to add non-registered normal or test dependencies.
6+
17
Pkg v1.11 Release Notes
28
=======================
39

Diff for: docs/src/toml-files.md

+14-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
9191
Typically it is not needed to manually add entries to the `[deps]` section; this is instead
9292
handled by Pkg operations such as `add`.
9393

94+
### The `[sources]` section
95+
96+
Specifiying a path or repo (+ branch) for a dependency is done in the `[sources]` section.
97+
These are especially useful for controlling unregistered dependencies without having to bundle a
98+
corresponding manifest file.
99+
100+
```toml
101+
[sources]
102+
Example = {url = "https://github.com/JuliaLang/Example.jl", rev = "custom_branch"}
103+
SomeDependency = {path = "deps/SomeDependency.jl"}
104+
```
105+
106+
Note that this information is only used when this environment is active, i.e. it is not used if this project is a package that is being used as a dependency.
94107

95108
### The `[compat]` section
96109

@@ -135,7 +148,7 @@ Julia will then preferentially use the version-specific manifest file if availab
135148
For example, if both `Manifest-v1.11.toml` and `Manifest.toml` exist, Julia 1.11 will prioritize using `Manifest-v1.11.toml`.
136149
However, Julia versions 1.10, 1.12, and all others will default to using `Manifest.toml`.
137150
This feature allows for easier management of different instantiated versions of dependencies for various Julia versions.
138-
Note that there can only be one `Project.toml` file. While `Manifest-v{major}.{minor}.toml` files are not automatically
151+
Note that there can only be one `Project.toml` file. While `Manifest-v{major}.{minor}.toml` files are not automatically
139152
created by Pkg, users can manually rename a `Manifest.toml` file to match
140153
the versioned format, and Pkg will subsequently maintain it through its operations.
141154

Diff for: src/API.jl

+35-2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Base.@kwdef struct ProjectInfo
101101
version::Union{Nothing,VersionNumber}
102102
ispackage::Bool
103103
dependencies::Dict{String,UUID}
104+
sources::Dict{String,Dict{String,String}}
104105
path::String
105106
end
106107

@@ -113,6 +114,7 @@ function project(env::EnvCache)::ProjectInfo
113114
version = pkg === nothing ? nothing : pkg.version::VersionNumber,
114115
ispackage = pkg !== nothing,
115116
dependencies = env.project.deps,
117+
sources = env.project.sources,
116118
path = env.project_file
117119
)
118120
end
@@ -181,6 +183,31 @@ for f in (:develop, :add, :rm, :up, :pin, :free, :test, :build, :status, :why, :
181183
end
182184
end
183185

186+
function update_source_if_set(project, pkg)
187+
source = get(project.sources, pkg.name, nothing)
188+
source === nothing && return
189+
# This should probably not modify the dicts directly...
190+
if pkg.repo.source !== nothing
191+
source["url"] = pkg.repo.source
192+
end
193+
if pkg.repo.rev !== nothing
194+
source["rev"] = pkg.repo.rev
195+
end
196+
if pkg.path !== nothing
197+
source["path"] = pkg.path
198+
end
199+
path, repo = get_path_repo(project, pkg.name)
200+
if path !== nothing
201+
pkg.path = path
202+
end
203+
if repo.source !== nothing
204+
pkg.repo.source = repo.source
205+
end
206+
if repo.rev !== nothing
207+
pkg.repo.rev = repo.rev
208+
end
209+
end
210+
184211
function develop(ctx::Context, pkgs::Vector{PackageSpec}; shared::Bool=true,
185212
preserve::PreserveLevel=Operations.default_preserve(), platform::AbstractPlatform=HostPlatform(), kwargs...)
186213
require_not_empty(pkgs, :develop)
@@ -212,13 +239,15 @@ function develop(ctx::Context, pkgs::Vector{PackageSpec}; shared::Bool=true,
212239

213240
new_git = handle_repos_develop!(ctx, pkgs, shared)
214241

242+
215243
for pkg in pkgs
216244
if Types.collides_with_project(ctx.env, pkg)
217245
pkgerror("package $(err_rep(pkg)) has the same name or UUID as the active project")
218246
end
219247
if length(findall(x -> x.uuid == pkg.uuid, pkgs)) > 1
220248
pkgerror("it is invalid to specify multiple packages with the same UUID: $(err_rep(pkg))")
221249
end
250+
update_source_if_set(ctx.env.project, pkg)
222251
end
223252

224253
Operations.develop(ctx, pkgs, new_git; preserve=preserve, platform=platform)
@@ -272,6 +301,7 @@ function add(ctx::Context, pkgs::Vector{PackageSpec}; preserve::PreserveLevel=Op
272301
if length(findall(x -> x.uuid == pkg.uuid, pkgs)) > 1
273302
pkgerror("it is invalid to specify multiple packages with the same UUID: $(err_rep(pkg))")
274303
end
304+
update_source_if_set(ctx.env.project, pkg)
275305
end
276306

277307
Operations.add(ctx, pkgs, new_git; preserve, platform, target)
@@ -311,12 +341,14 @@ end
311341
function append_all_pkgs!(pkgs, ctx, mode)
312342
if mode == PKGMODE_PROJECT || mode == PKGMODE_COMBINED
313343
for (name::String, uuid::UUID) in ctx.env.project.deps
314-
push!(pkgs, PackageSpec(name=name, uuid=uuid))
344+
path, repo = get_path_repo(ctx.env.project, name)
345+
push!(pkgs, PackageSpec(name=name, uuid=uuid, path=path, repo=repo))
315346
end
316347
end
317348
if mode == PKGMODE_MANIFEST || mode == PKGMODE_COMBINED
318349
for (uuid, entry) in ctx.env.manifest
319-
push!(pkgs, PackageSpec(name=entry.name, uuid=uuid))
350+
path, repo = get_path_repo(ctx.env.project, entry.name)
351+
push!(pkgs, PackageSpec(name=entry.name, uuid=uuid, path=path, repo=repo))
320352
end
321353
end
322354
return
@@ -347,6 +379,7 @@ function up(ctx::Context, pkgs::Vector{PackageSpec};
347379
manifest_resolve!(ctx.env.manifest, pkgs)
348380
ensure_resolved(ctx, ctx.env.manifest, pkgs)
349381
end
382+
350383
Operations.up(ctx, pkgs, level; skip_writing_project, preserve)
351384
return
352385
end

Diff for: src/Operations.jl

+78-22
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,15 @@ function load_direct_deps(env::EnvCache, pkgs::Vector{PackageSpec}=PackageSpec[]
7171
pkgs = copy(pkgs)
7272
for (name::String, uuid::UUID) in env.project.deps
7373
findfirst(pkg -> pkg.uuid == uuid, pkgs) === nothing || continue # do not duplicate packages
74+
path, repo = get_path_repo(env.project, name)
7475
entry = manifest_info(env.manifest, uuid)
7576
push!(pkgs, entry === nothing ?
76-
PackageSpec(;uuid=uuid, name=name) :
77+
PackageSpec(;uuid=uuid, name=name, path=path, repo=repo) :
7778
PackageSpec(;
7879
uuid = uuid,
7980
name = name,
80-
path = entry.path,
81-
repo = entry.repo,
81+
path = path === nothing ? entry.path : path,
82+
repo = repo == GitRepo() ? entry.repo : repo,
8283
pinned = entry.pinned,
8384
tree_hash = entry.tree_hash, # TODO should tree_hash be changed too?
8485
version = load_version(entry.version, isfixed(entry), preserve),
@@ -108,6 +109,19 @@ end
108109
function load_all_deps(env::EnvCache, pkgs::Vector{PackageSpec}=PackageSpec[];
109110
preserve::PreserveLevel=PRESERVE_ALL)
110111
pkgs = load_manifest_deps(env.manifest, pkgs; preserve=preserve)
112+
# Sources takes presedence over the manifest...
113+
for pkg in pkgs
114+
path, repo = get_path_repo(env.project, pkg.name)
115+
if path !== nothing
116+
pkg.path = path
117+
end
118+
if repo.source !== nothing
119+
pkg.repo.source = repo.source
120+
end
121+
if repo.rev !== nothing
122+
pkg.repo.rev = repo.rev
123+
end
124+
end
111125
return load_direct_deps(env, pkgs; preserve=preserve)
112126
end
113127

@@ -244,8 +258,9 @@ function collect_project(pkg::PackageSpec, path::String)
244258
pkgerror("julia version requirement from Project.toml's compat section not satisfied for package $(err_rep(pkg)) at `$path`")
245259
end
246260
for (name, uuid) in project.deps
261+
path, repo = get_path_repo(project, name)
247262
vspec = get_compat(project, name)
248-
push!(deps, PackageSpec(name, uuid, vspec))
263+
push!(deps, PackageSpec(name=name, uuid=uuid, version=vspec, path=path, repo=repo))
249264
end
250265
for (name, uuid) in project.weakdeps
251266
vspec = get_compat(project, name)
@@ -302,6 +317,11 @@ function collect_fixed!(env::EnvCache, pkgs::Vector{PackageSpec}, names::Dict{UU
302317
names[pkg.uuid] = pkg.name
303318
end
304319
for pkg in pkgs
320+
# add repo package if necessary
321+
if (pkg.repo.rev !== nothing || pkg.repo.source !== nothing) && pkg.tree_hash === nothing
322+
# ensure revved package is installed
323+
Types.handle_repo_add!(Types.Context(env=env), pkg)
324+
end
305325
path = project_rel_path(env, source_path(env.manifest_file, pkg))
306326
if !isdir(path)
307327
pkgerror("expected package $(err_rep(pkg)) to exist at path `$path`")
@@ -1135,7 +1155,7 @@ function build_versions(ctx::Context, uuids::Set{UUID}; verbose=false)
11351155
fancyprint && show_progress(ctx.io, bar)
11361156

11371157
let log_file=log_file
1138-
sandbox(ctx, pkg, source_path, builddir(source_path), build_project_override; preferences=build_project_preferences) do
1158+
sandbox(ctx, pkg, builddir(source_path), build_project_override; preferences=build_project_preferences) do
11391159
flush(ctx.io)
11401160
ok = open(log_file, "w") do log
11411161
std = verbose ? ctx.io : log
@@ -1226,6 +1246,9 @@ function rm(ctx::Context, pkgs::Vector{PackageSpec}; mode::PackageMode)
12261246
filter!(ctx.env.project.compat) do (name, _)
12271247
name == "julia" || name in keys(ctx.env.project.deps) || name in keys(ctx.env.project.extras) || name in keys(ctx.env.project.weakdeps)
12281248
end
1249+
filter!(ctx.env.project.sources) do (name, _)
1250+
name in keys(ctx.env.project.deps) || name in keys(ctx.env.project.extras)
1251+
end
12291252
deps_names = union(keys(ctx.env.project.deps), keys(ctx.env.project.extras))
12301253
filter!(ctx.env.project.targets) do (target, deps)
12311254
!isempty(filter!(in(deps_names), deps))
@@ -1238,8 +1261,8 @@ function rm(ctx::Context, pkgs::Vector{PackageSpec}; mode::PackageMode)
12381261
show_update(ctx.env, ctx.registries; io=ctx.io)
12391262
end
12401263

1241-
update_package_add(ctx::Context, pkg::PackageSpec, ::Nothing, is_dep::Bool) = pkg
1242-
function update_package_add(ctx::Context, pkg::PackageSpec, entry::PackageEntry, is_dep::Bool)
1264+
update_package_add(ctx::Context, pkg::PackageSpec, ::Nothing, source_path, source_repo, is_dep::Bool) = pkg
1265+
function update_package_add(ctx::Context, pkg::PackageSpec, entry::PackageEntry, source_path, source_repo, is_dep::Bool)
12431266
if entry.pinned
12441267
if pkg.version == VersionSpec()
12451268
println(ctx.io, "`$(pkg.name)` is pinned at `v$(entry.version)`: maintaining pinned version")
@@ -1382,7 +1405,8 @@ function add(ctx::Context, pkgs::Vector{PackageSpec}, new_git=Set{UUID}();
13821405
for (i, pkg) in pairs(pkgs)
13831406
entry = manifest_info(ctx.env.manifest, pkg.uuid)
13841407
is_dep = any(uuid -> uuid == pkg.uuid, [uuid for (name, uuid) in ctx.env.project.deps])
1385-
pkgs[i] = update_package_add(ctx, pkg, entry, is_dep)
1408+
source_path, source_repo = get_path_repo(ctx.env.project, pkg.name)
1409+
pkgs[i] = update_package_add(ctx, pkg, entry, source_path, source_repo, is_dep)
13861410
end
13871411

13881412
names = (p.name for p in pkgs)
@@ -1456,21 +1480,27 @@ end
14561480

14571481
# load version constraint
14581482
# if version isa VersionNumber -> set tree_hash too
1459-
up_load_versions!(ctx::Context, pkg::PackageSpec, ::Nothing, level::UpgradeLevel) = false
1460-
function up_load_versions!(ctx::Context, pkg::PackageSpec, entry::PackageEntry, level::UpgradeLevel)
1483+
up_load_versions!(ctx::Context, pkg::PackageSpec, ::Nothing, source_path, source_repo, level::UpgradeLevel) = false
1484+
function up_load_versions!(ctx::Context, pkg::PackageSpec, entry::PackageEntry, source_path, source_repo, level::UpgradeLevel)
1485+
# With [sources], `pkg` can have a path or repo here
14611486
entry.version !== nothing || return false # no version to set
14621487
if entry.pinned || level == UPLEVEL_FIXED
14631488
pkg.version = entry.version
14641489
pkg.tree_hash = entry.tree_hash
1465-
elseif entry.repo.source !== nothing # repo packages have a version but are treated special
1466-
pkg.repo = entry.repo
1490+
elseif entry.repo.source !== nothing || source_repo.source !== nothing # repo packages have a version but are treated specially
1491+
if source_repo.source !== nothing
1492+
pkg.repo = source_repo
1493+
else
1494+
pkg.repo = entry.repo
1495+
end
14671496
if level == UPLEVEL_MAJOR
14681497
# Updating a repo package is equivalent to adding it
14691498
new = Types.handle_repo_add!(ctx, pkg)
14701499
pkg.version = entry.version
14711500
if pkg.tree_hash != entry.tree_hash
14721501
# TODO parse find_installed and set new version
14731502
end
1503+
14741504
return new
14751505
else
14761506
pkg.version = entry.version
@@ -1490,8 +1520,12 @@ end
14901520
up_load_manifest_info!(pkg::PackageSpec, ::Nothing) = nothing
14911521
function up_load_manifest_info!(pkg::PackageSpec, entry::PackageEntry)
14921522
pkg.name = entry.name # TODO check name is same
1493-
pkg.repo = entry.repo # TODO check that repo is same
1494-
pkg.path = entry.path
1523+
if pkg.repo == GitRepo()
1524+
pkg.repo = entry.repo # TODO check that repo is same
1525+
end
1526+
if pkg.path === nothing
1527+
pkg.path = entry.path
1528+
end
14951529
pkg.pinned = entry.pinned
14961530
# `pkg.version` and `pkg.tree_hash` is set by `up_load_versions!`
14971531
end
@@ -1559,12 +1593,15 @@ function up(ctx::Context, pkgs::Vector{PackageSpec}, level::UpgradeLevel;
15591593
# TODO check all pkg.version == VersionSpec()
15601594
# set version constraints according to `level`
15611595
for pkg in pkgs
1562-
new = up_load_versions!(ctx, pkg, manifest_info(ctx.env.manifest, pkg.uuid), level)
1596+
source_path, source_repo = get_path_repo(ctx.env.project, pkg.name)
1597+
entry = manifest_info(ctx.env.manifest, pkg.uuid)
1598+
new = up_load_versions!(ctx, pkg, entry, source_path, source_repo, level)
15631599
new && push!(new_git, pkg.uuid) #TODO put download + push! in utility function
15641600
end
15651601
# load rest of manifest data (except for version info)
15661602
for pkg in pkgs
1567-
up_load_manifest_info!(pkg, manifest_info(ctx.env.manifest, pkg.uuid))
1603+
entry = manifest_info(ctx.env.manifest, pkg.uuid)
1604+
up_load_manifest_info!(pkg, entry)
15681605
end
15691606
if preserve !== nothing
15701607
pkgs, deps_map = targeted_resolve_up(ctx.env, ctx.registries, pkgs, preserve, ctx.julia_version)
@@ -1654,7 +1691,11 @@ end
16541691
# TODO: this is two technically different operations with the same name
16551692
# split into two subfunctions ...
16561693
function free(ctx::Context, pkgs::Vector{PackageSpec}; err_if_free=true)
1657-
foreach(pkg -> update_package_free!(ctx.registries, pkg, manifest_info(ctx.env.manifest, pkg.uuid), err_if_free), pkgs)
1694+
for pkg in pkgs
1695+
entry = manifest_info(ctx.env.manifest, pkg.uuid)
1696+
delete!(ctx.env.project.sources, pkg.name)
1697+
update_package_free!(ctx.registries, pkg, entry, err_if_free)
1698+
end
16581699

16591700
if any(pkg -> pkg.version == VersionSpec(), pkgs)
16601701
pkgs = load_direct_deps(ctx.env, pkgs)
@@ -1745,8 +1786,9 @@ function sandbox_preserve(env::EnvCache, target::PackageSpec, test_project::Stri
17451786
env.manifest.manifest_format = v"2.0"
17461787
end
17471788
# preserve important nodes
1789+
project = read_project(test_project)
17481790
keep = [target.uuid]
1749-
append!(keep, collect(values(read_project(test_project).deps)))
1791+
append!(keep, collect(values(project.deps)))
17501792
record_project_hash(env)
17511793
# prune and return
17521794
return prune_manifest(env.manifest, keep)
@@ -1761,8 +1803,17 @@ function abspath!(env::EnvCache, manifest::Manifest)
17611803
return manifest
17621804
end
17631805

1806+
function abspath!(env::EnvCache, project::Project)
1807+
for (key, entry) in project.sources
1808+
if haskey(entry, "path")
1809+
entry["path"] = project_rel_path(env, entry["path"])
1810+
end
1811+
end
1812+
return project
1813+
end
1814+
17641815
# ctx + pkg used to compute parent dep graph
1765-
function sandbox(fn::Function, ctx::Context, target::PackageSpec, target_path::String,
1816+
function sandbox(fn::Function, ctx::Context, target::PackageSpec,
17661817
sandbox_path::String, sandbox_project_override;
17671818
preferences::Union{Nothing,Dict{String,Any}} = nothing,
17681819
force_latest_compatible_version::Bool=false,
@@ -1783,16 +1834,20 @@ function sandbox(fn::Function, ctx::Context, target::PackageSpec, target_path::S
17831834
sandbox_project_override = Project()
17841835
end
17851836
end
1837+
abspath!(ctx.env, sandbox_project_override)
17861838
Types.write_project(sandbox_project_override, tmp_project)
17871839

17881840
# create merged manifest
17891841
# - copy over active subgraph
17901842
# - abspath! to maintain location of all deved nodes
1791-
working_manifest = abspath!(ctx.env, sandbox_preserve(ctx.env, target, tmp_project))
1843+
working_manifest = sandbox_preserve(ctx.env, target, tmp_project)
1844+
abspath!(ctx.env, working_manifest)
1845+
17921846
# - copy over fixed subgraphs from test subgraph
17931847
# really only need to copy over "special" nodes
17941848
sandbox_env = Types.EnvCache(projectfile_path(sandbox_path))
17951849
abspath!(sandbox_env, sandbox_env.manifest)
1850+
abspath!(sandbox_env, sandbox_env.project)
17961851
for (uuid, entry) in sandbox_env.manifest.deps
17971852
entry_working = get(working_manifest, uuid, nothing)
17981853
if entry_working === nothing
@@ -1897,6 +1952,7 @@ function gen_target_project(ctx::Context, pkg::PackageSpec, source_path::String,
18971952
source_env = EnvCache(projectfile_path(source_path))
18981953
# collect regular dependencies
18991954
test_project.deps = source_env.project.deps
1955+
test_project.sources = source_env.project.sources
19001956
# collect test dependencies
19011957
for name in get(source_env.project.targets, target, String[])
19021958
uuid = nothing
@@ -1976,11 +2032,11 @@ function test(ctx::Context, pkgs::Vector{PackageSpec};
19762032
end
19772033
# now we sandbox
19782034
printpkgstyle(ctx.io, :Testing, pkg.name)
1979-
sandbox(ctx, pkg, source_path, testdir(source_path), test_project_override; preferences=test_project_preferences, force_latest_compatible_version, allow_earlier_backwards_compatible_versions, allow_reresolve) do
2035+
sandbox(ctx, pkg, testdir(source_path), test_project_override; preferences=test_project_preferences, force_latest_compatible_version, allow_earlier_backwards_compatible_versions, allow_reresolve) do
19802036
test_fn !== nothing && test_fn()
19812037
sandbox_ctx = Context(;io=ctx.io)
19822038
status(sandbox_ctx.env, sandbox_ctx.registries; mode=PKGMODE_COMBINED, io=sandbox_ctx.io, ignore_indent = false, show_usagetips = false)
1983-
flags = gen_subprocess_flags(source_path; coverage, julia_args)
2039+
flags = gen_subprocess_flags(source_path; coverage,julia_args)
19842040

19852041
if should_autoprecompile()
19862042
cacheflags = Base.CacheFlags(parse(UInt8, read(`$(Base.julia_cmd()) $(flags) --eval 'show(ccall(:jl_cache_flags, UInt8, ()))'`, String)))

0 commit comments

Comments
 (0)