Skip to content

Commit 626ee12

Browse files
Julia 1.13+: Use safe_realpath(manifest_rel_path(...)) instead of project_rel_path(...) (#106)
* Copy the contents of `src/julia-1.11` to `src/julia-1.13`, and use `src/julia-1.13` for Julia 1.13+ * Julia 1.13+: Use `safe_realpath(manifest_rel_path(...))` instead of `project_rel_path(...)` * Better debug-log printing of exceptions Co-authored-by: Frames White <oxinabox@ucc.asn.au> * Add trailing newline character (for POSIX compliance) --------- Co-authored-by: Frames White <oxinabox@ucc.asn.au>
1 parent 5a7a4a6 commit 626ee12

5 files changed

Lines changed: 215 additions & 2 deletions

File tree

src/TestEnv.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ elseif VERSION < v"1.9-"
1616
include("julia-1.8/TestEnv.jl")
1717
elseif VERSION < v"1.11-"
1818
include("julia-1.9/TestEnv.jl")
19-
else
19+
elseif VERSION < v"1.13-"
2020
include("julia-1.11/TestEnv.jl")
21+
else
22+
include("julia-1.13/TestEnv.jl")
2123
end
2224

23-
end
25+
end

src/julia-1.13/TestEnv.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Pkg
2+
using Pkg: PackageSpec
3+
using Pkg.Types: Context, ensure_resolved, is_project_uuid, write_env, is_stdlib
4+
using Pkg.Types: Types, projectfile_path, manifestfile_path
5+
using Pkg.Operations: manifest_info, manifest_resolve!, project_deps_resolve!
6+
using Pkg.Operations: manifest_rel_path, project_resolve!
7+
using Pkg.Operations: sandbox, source_path, sandbox_preserve, abspath!
8+
using Pkg.Operations: gen_target_project, isfixed
9+
10+
11+
include("common.jl")
12+
include("activate_do.jl")
13+
include("activate_set.jl")

src/julia-1.13/activate_do.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""
2+
TestEnv.activate(f, [pkg]; allow_reresolve=true)
3+
4+
Activate the test enviroment of `pkg` (defaults to current enviroment), and run `f()`,
5+
then deactivate the enviroment.
6+
This is not useful for many people: Julia is not really designed to have the enviroment
7+
being changed while you are executing code.
8+
However, this *is* useful for anyone doing something like making a alternative to
9+
`Pkg.test()`.
10+
Indeed this is basically extracted from what `Pkg.test()` does.
11+
"""
12+
function activate(f, pkg::AbstractString=current_pkg_name(); allow_reresolve=true)
13+
ctx, pkgspec = ctx_and_pkgspec(pkg)
14+
15+
test_project_override = maybe_gen_project_override!(ctx, pkgspec)
16+
path = pkgspec.path::String
17+
return sandbox(ctx, pkgspec, joinpath(path, "test"), test_project_override; allow_reresolve) do
18+
flush(stdout)
19+
f()
20+
end
21+
end

src/julia-1.13/activate_set.jl

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
2+
# Originally from Pkg.Operations.sandbox
3+
4+
"""
5+
TestEnv.activate([pkg]; allow_reresolve=true)
6+
7+
Activate the test enviroment of `pkg` (defaults to current enviroment).
8+
"""
9+
function activate(pkg::AbstractString=current_pkg_name(); allow_reresolve=true)
10+
ctx, pkgspec = ctx_and_pkgspec(pkg)
11+
# This needs to be first as `gen_target_project` fixes `pkgspec.path` if it is nothing
12+
sandbox_project_override = maybe_gen_project_override!(ctx, pkgspec)
13+
14+
sandbox_path = joinpath(pkgspec.path::String, "test")
15+
sandbox_project = projectfile_path(sandbox_path)
16+
17+
tmp = mktempdir()
18+
tmp_project = projectfile_path(tmp)
19+
tmp_manifest = manifestfile_path(tmp)
20+
21+
# Copy env info over to temp env
22+
if sandbox_project_override !== nothing
23+
Types.write_project(sandbox_project_override, tmp_project)
24+
elseif isfile(sandbox_project)
25+
cp(sandbox_project, tmp_project)
26+
chmod(tmp_project, 0o600)
27+
end
28+
# create merged manifest
29+
# - copy over active subgraph
30+
# - abspath! to maintain location of all deved nodes
31+
working_manifest = abspath!(ctx.env, sandbox_preserve(ctx.env, pkgspec, tmp_project))
32+
33+
# - copy over fixed subgraphs from test subgraph
34+
# really only need to copy over "special" nodes
35+
sandbox_env = Types.EnvCache(projectfile_path(sandbox_path))
36+
sandbox_manifest = abspath!(sandbox_env, sandbox_env.manifest)
37+
for (name, uuid) in sandbox_env.project.deps
38+
entry = get(sandbox_manifest, uuid, nothing)
39+
if entry !== nothing && isfixed(entry)
40+
# Signature changed when workspaces were introduced to Pkg in v1.12 (see Pkg.jl#3841)
41+
subgraph = Pkg.Operations.prune_manifest(sandbox_manifest, VERSION < v"1.12.0-" ? [uuid] : Set([uuid]))
42+
for (uuid, entry) in subgraph
43+
if haskey(working_manifest, uuid)
44+
Pkg.Operations.pkgerror("can not merge projects")
45+
end
46+
working_manifest[uuid] = entry
47+
end
48+
end
49+
end
50+
51+
Types.write_manifest(working_manifest, tmp_manifest)
52+
53+
Base.ACTIVE_PROJECT[] = tmp_project
54+
55+
temp_ctx = Context()
56+
temp_ctx.env.project.deps[pkgspec.name] = pkgspec.uuid
57+
58+
# A hack to get [sources] with relative paths working. We just dive into the project
59+
# context and replace all the relative '{path = ".."}' instances with the corresponding
60+
# absolute paths. `pkgspec.path` is assumed to be relative to the Project.toml file
61+
# that we are activating and has the relative paths.
62+
for source in values(temp_ctx.env.project.sources)
63+
isa(source, Dict) || continue
64+
haskey(source, "path") || continue
65+
base_path = test_dir_has_project_file(temp_ctx, pkgspec) ? joinpath(pkgspec.path, "test") : pkgspec.path
66+
if !isabspath(source["path"])
67+
source["path"] = joinpath(base_path, source["path"])
68+
end
69+
end
70+
71+
try
72+
Pkg.resolve(temp_ctx; io=devnull)
73+
@debug "Using _parent_ dep graph"
74+
catch err# TODO
75+
allow_reresolve || rethrow()
76+
@debug exception=err
77+
@warn "Could not use exact versions of packages in manifest, re-resolving"
78+
temp_ctx.env.manifest.deps = Dict(
79+
uuid => entry for
80+
(uuid, entry) in temp_ctx.env.manifest.deps if isfixed(entry)
81+
)
82+
Pkg.resolve(temp_ctx; io=devnull)
83+
@debug "Using _clean_ dep graph"
84+
end
85+
86+
write_env(temp_ctx.env; update_undo=false)
87+
88+
return Base.active_project()
89+
end

src/julia-1.13/common.jl

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
struct TestEnvError <: Exception
2+
msg::AbstractString
3+
end
4+
5+
function Base.showerror(io::IO, ex::TestEnvError, bt; backtrace=true)
6+
printstyled(io, ex.msg, color=Base.error_color())
7+
end
8+
9+
function current_pkg_name()
10+
ctx = Context()
11+
ctx.env.pkg === nothing && throw(TestEnvError("trying to activate test environment of an unnamed project"))
12+
return ctx.env.pkg.name
13+
end
14+
15+
"""
16+
ctx, pkgspec = ctx_and_pkgspec(pkg::AbstractString)
17+
18+
For a given package name `pkg`, instantiate a `Context` for it, and return that `Context`,
19+
and it's `PackageSpec`.
20+
"""
21+
function ctx_and_pkgspec(pkg::AbstractString)
22+
pkgspec = deepcopy(PackageSpec(pkg))
23+
ctx = Context()
24+
isinstalled!(ctx, pkgspec) || throw(TestEnvError("$pkg not installed 👻"))
25+
Pkg.instantiate(ctx)
26+
return ctx, pkgspec
27+
end
28+
29+
"""
30+
isinstalled!(ctx::Context, pkgspec::Pkg.Types.PackageSpec)
31+
32+
Checks if the package is installed by using `ensure_resolved` from `Pkg/src/Types.jl`.
33+
This function fails if the package is not installed, but here we wrap it in a
34+
try-catch as we may want to test another package after the one that isn't installed.
35+
"""
36+
function isinstalled!(ctx::Context, pkgspec::Pkg.Types.PackageSpec)
37+
project_resolve!(ctx.env, [pkgspec])
38+
project_deps_resolve!(ctx.env, [pkgspec])
39+
manifest_resolve!(ctx.env.manifest, [pkgspec])
40+
41+
try
42+
ensure_resolved(ctx, ctx.env.manifest, [pkgspec])
43+
catch err
44+
err isa MethodError && rethrow()
45+
return false
46+
end
47+
return true
48+
end
49+
50+
51+
function test_dir_has_project_file(ctx, pkgspec)
52+
test_dir = get_test_dir(ctx, pkgspec)
53+
test_dir === nothing && return false
54+
return isfile(joinpath(test_dir, "Project.toml"))
55+
end
56+
57+
"""
58+
get_test_dir(ctx::Context, pkgspec::Pkg.Types.PackageSpec)
59+
60+
Gets the testfile path of the package. Code for each Julia version mirrors that found
61+
in `Pkg/src/Operations.jl`.
62+
"""
63+
function get_test_dir(ctx::Context, pkgspec::Pkg.Types.PackageSpec)
64+
if is_project_uuid(ctx.env, pkgspec.uuid)
65+
pkgspec.path = dirname(ctx.env.project_file)
66+
pkgspec.version = ctx.env.pkg.version
67+
else
68+
is_stdlib(pkgspec.uuid::Base.UUID) && return
69+
entry = manifest_info(ctx.env.manifest, pkgspec.uuid)
70+
pkgspec.version = entry.version
71+
pkgspec.tree_hash = entry.tree_hash
72+
pkgspec.repo = entry.repo
73+
pkgspec.path = entry.path
74+
pkgspec.pinned = entry.pinned
75+
pkgspec.path = Pkg.safe_realpath(manifest_rel_path(ctx.env, source_path(ctx.env.project_file, pkgspec)::String))
76+
end
77+
pkgfilepath = source_path(ctx.env.project_file, pkgspec)::String
78+
return joinpath(pkgfilepath, "test")
79+
end
80+
81+
82+
function maybe_gen_project_override!(ctx, pkgspec)
83+
if !test_dir_has_project_file(ctx, pkgspec)
84+
gen_target_project(ctx, pkgspec, pkgspec.path::String, "test")
85+
else
86+
nothing
87+
end
88+
end

0 commit comments

Comments
 (0)