Skip to content

Commit dbacddf

Browse files
Fix #256, adjust startup.jl to avoid unresolved environments (#257)
* Fix #256, adjust `startup.jl` to avoid unresolved environments * Move `Pkg` load to avoid world age errors, Julia 1.11+
1 parent 9e127da commit dbacddf

File tree

3 files changed

+69
-46
lines changed

3 files changed

+69
-46
lines changed

.github/workflows/CI.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ jobs:
3939
contents: read
4040
runs-on: ${{ matrix.os }}
4141
timeout-minutes: 90
42-
env:
43-
JULIA_CONDAPKG_BACKEND: "Null"
4442
strategy:
4543
fail-fast: false
4644
matrix:
@@ -101,10 +99,6 @@ jobs:
10199
with:
102100
version: pre-release
103101

104-
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
105-
with:
106-
python-version: '3.13'
107-
108102
- uses: julia-actions/julia-buildpkg@e3eb439fad4f9aba7da2667e7510e4a46ebc46e1 # v1.7.0
109103
- uses: julia-actions/julia-runtest@678da69444cd5f13d7e674a90cb4f534639a14f9 # v1.11.2
110104
with:

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Fixed
11+
12+
- Adjust notebook startup script to ensure all environments in `LOAD_PATH` are resolved [#257]
13+
1014
## [v0.13.0] - 2025-02-18
1115

1216
### Added
@@ -370,3 +374,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
370374
[#250]: https://github.com/PumasAI/QuartoNotebookRunner.jl/issues/250
371375
[#253]: https://github.com/PumasAI/QuartoNotebookRunner.jl/issues/253
372376
[#255]: https://github.com/PumasAI/QuartoNotebookRunner.jl/issues/255
377+
[#257]: https://github.com/PumasAI/QuartoNotebookRunner.jl/issues/257

src/startup.jl

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,6 @@
33

44
# Step 1:
55
#
6-
# Inject a sandbox environment into the `LOAD_PATH` such that it gets picked up
7-
# as the active project if there isn't one found in the rest of the
8-
# `LOAD_PATH`.
9-
#
10-
# It needs to appear ahead of the `QuartoNotebookWorker` environment so that it
11-
# shadows that environment since that is not a user-facing project directory,
12-
# and if a user was to perform `Pkg` operations they may affect that
13-
# environment. Instead we provide a temporary sandbox environment that gets
14-
# discarded when the notebook process exits.
15-
let sandbox = joinpath(mktempdir(), "QuartoSandbox")
16-
mkpath(sandbox)
17-
# The empty project file is key to making this the active environment if
18-
# noting else is available if the rest of the `LOAD_PATH`.
19-
touch(joinpath(sandbox, "Project.toml"))
20-
push!(LOAD_PATH, sandbox)
21-
end
22-
23-
# Step 2:
24-
#
25-
# This contains the worker package environment. It gets added to the LOAD_PATH
26-
# so that we can `require` it further down this file.
27-
push!(LOAD_PATH, ENV["QUARTONOTEBOOKWORKER_PACKAGE"])
28-
29-
# Step 3:
30-
#
31-
# We also need to ensure that `@stdlib` is available on the LOAD_PATH so that
32-
# requiring the worker package does not attempt to load stdlib packages from
33-
# the wrong `julia` version. The errors that get thrown when this happens look
34-
# like deserialization errors due to differences in struct definitions, or
35-
# method signatures between different versions of Julia. This happens with
36-
# running `Pkg.test`, which drops `@stdlib` from the load path, but does not
37-
# happen if using `TestEnv.jl` to run tests in a REPL session via `include`.
38-
pushfirst!(LOAD_PATH, "@stdlib")
39-
40-
# Step 4:
41-
#
426
# Writes the error and stacktrace to the parent-provided log file rather than
437
# stderr. An alternative would be to capture stderr from the parent when
448
# starting this process, but then that swallows all stderr from then on. We
@@ -59,7 +23,67 @@ function capture(func)
5923
end
6024
end
6125

62-
# Step 5:
26+
# Step 1:
27+
#
28+
# We need to ensure that `@stdlib` is available on the LOAD_PATH so that
29+
# requiring the worker package does not attempt to load stdlib packages from
30+
# the wrong `julia` version. The errors that get thrown when this happens look
31+
# like deserialization errors due to differences in struct definitions, or
32+
# method signatures between different versions of Julia. This happens with
33+
# running `Pkg.test`, which drops `@stdlib` from the load path, but does not
34+
# happen if using `TestEnv.jl` to run tests in a REPL session via `include`.
35+
pushfirst!(LOAD_PATH, "@stdlib")
36+
37+
# Step 2:
38+
#
39+
# Inject a sandbox environment into the `LOAD_PATH` such that it gets picked up
40+
# as the active project if there isn't one found in the rest of the
41+
# `LOAD_PATH`.
42+
#
43+
# It needs to appear ahead of the `QuartoNotebookWorker` environment so that it
44+
# shadows that environment since that is not a user-facing project directory,
45+
# and if a user was to perform `Pkg` operations they may affect that
46+
# environment. Instead we provide a temporary sandbox environment that gets
47+
# discarded when the notebook process exits.
48+
let temp = mktempdir()
49+
sandbox = joinpath(temp, "QuartoSandbox")
50+
mkpath(sandbox)
51+
# The empty project file is key to making this the active environment if
52+
# noting else is available if the rest of the `LOAD_PATH`.
53+
touch(joinpath(sandbox, "Project.toml"))
54+
push!(LOAD_PATH, sandbox)
55+
56+
# Step 2b:
57+
#
58+
# We also need to ensure that the `QuartoNotebookWorker` package is
59+
# available on the `LOAD_PATH`. This is done by creating another
60+
# environment alongside the sandbox environment. We `Pkg.develop` the
61+
# "local" `QuartoNotebookWorker` package into this environment. `Pkg`
62+
# operations are logged to the `pkg.log` file that the server process can
63+
# read to provide feedback to the user if needed.
64+
#
65+
# `Pkg` is loaded outside of this closure otherwise the methods required do
66+
# not exist in a new enough world age to be callable.
67+
Pkg = Base.require(Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg"))
68+
capture() do
69+
worker = joinpath(temp, "QuartoNotebookWorker")
70+
mkpath(worker)
71+
push!(LOAD_PATH, worker)
72+
open(joinpath(ENV["MALT_WORKER_TEMP_DIR"], "pkg.log"), "w") do io
73+
ap = Base.active_project()
74+
try
75+
Pkg.activate(worker; io)
76+
Pkg.develop(; path = ENV["QUARTONOTEBOOKWORKER_PACKAGE"], io)
77+
finally
78+
# Ensure that we switch the active project back afterwards.
79+
Pkg.activate(ap; io)
80+
end
81+
flush(io)
82+
end
83+
end
84+
end
85+
86+
# Step 3:
6387
#
6488
# The parent process needs some additional metadata about this `julia` process to
6589
# be able to provide relevant error messages to the user.
@@ -86,7 +110,7 @@ capture() do
86110
end
87111
end
88112

89-
# Step 6:
113+
# Step 4:
90114
#
91115
# Now load in the worker package. This may trigger package precompilation on
92116
# first load, hence it is run under a `capture` should it fail to run.
@@ -99,13 +123,13 @@ const QuartoNotebookWorker = capture() do
99123
)
100124
end
101125

102-
# Step 7:
126+
# Step 5:
103127
#
104128
# Ensures that the LOAD_PATH is returned to it's previous state without the
105129
# `@stdlib` that was pushed to it near the start of the file.
106130
popfirst!(LOAD_PATH)
107131

108-
# Step 8:
132+
# Step 6:
109133
#
110134
# This calls into the main socket server loop, which does not terminate until
111135
# the process is finished off and the notebook needs closing. So anything

0 commit comments

Comments
 (0)