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
6024end
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
87111end
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 )
100124end
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.
106130popfirst! (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