Skip to content

Commit 61a8447

Browse files
Add more autoprecompilation controls and Pkg.precompile() do to delay precompilation until after operations. (#4262)
1 parent 7f4e170 commit 61a8447

File tree

6 files changed

+172
-5
lines changed

6 files changed

+172
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Pkg v1.13 Release Notes
77
- `update` now shows a helpful tip when trying to upgrade a specific package that can be upgraded but is held back because it's part of a less optimal resolver solution ([#4266])
88
- `Pkg.status` now displays yanked packages with a `[yanked]` indicator and shows a warning when yanked packages are present. `Pkg.resolve` errors also display warnings about yanked packages that are not resolvable. ([#4310])
99
- Added `pkg> compat --current` command to automatically populate missing compat entries with the currently resolved package versions. Use `pkg> compat --current` for all packages or `pkg> compat Foo --current` for specific packages. ([#3266])
10+
- Added `Pkg.precompile() do` block syntax to delay autoprecompilation until after multiple operations complete, improving efficiency when performing several environment changes. ([#4262])
11+
- Added `Pkg.autoprecompilation_enabled(state::Bool)` to globally enable or disable automatic precompilation for Pkg operations. ([#4262])
1012

1113
Pkg v1.12 Release Notes
1214
=======================

docs/src/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Pkg.gc
3939
Pkg.status
4040
Pkg.compat
4141
Pkg.precompile
42+
Pkg.autoprecompilation_enabled
4243
Pkg.offline
4344
Pkg.why
4445
Pkg.dependencies

docs/src/environments.md

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,16 +190,53 @@ If a given package version errors during auto-precompilation, Pkg will remember
190190
automatically tries and will skip that package with a brief warning. Manual precompilation can be used to
191191
force these packages to be retried, as `pkg> precompile` will always retry all packages.
192192

193-
To disable the auto-precompilation, set `ENV["JULIA_PKG_PRECOMPILE_AUTO"]=0`.
194-
195193
The indicators next to the package names displayed during precompilation
196-
indicate the status of that package's precompilation.
194+
indicate the status of that package's precompilation.
197195

198196
- `[◐, ◓, ◑, ◒]` Animated "clock" characters indicate that the package is currently being precompiled.
199197
- `` A green checkmark indicates that the package has been successfully precompiled (after which that package will disappear from the list). If the checkmark is yellow it means that the package is currently loaded so the session will need to be restarted to access the version that was just precompiled.
200198
- `?` A question mark character indicates that a `PrecompilableError` was thrown, indicating that precompilation was disallowed, i.e. `__precompile__(false)` in that package.
201199
- `` A cross indicates that the package failed to precompile.
202200

201+
#### Controlling Auto-precompilation
202+
203+
Auto-precompilation can be controlled in several ways:
204+
205+
- **Environment variable**: Set `ENV["JULIA_PKG_PRECOMPILE_AUTO"]=0` to disable auto-precompilation globally.
206+
- **Programmatically**: Use `Pkg.autoprecompilation_enabled(false)` to disable auto-precompilation for the current session, or `Pkg.autoprecompilation_enabled(true)` to re-enable it.
207+
- **Scoped control**: Use `Pkg.precompile(f, args...; kwargs...)` to execute a function `f` with auto-precompilation temporarily disabled, then automatically trigger precompilation afterward if any packages were modified during the execution.
208+
209+
!!! compat "Julia 1.13"
210+
The `Pkg.autoprecompilation_enabled()` function and `Pkg.precompile()` do-block syntax require at least Julia 1.13.
211+
212+
For example, to add multiple packages without triggering precompilation after each one:
213+
214+
```julia-repl
215+
julia> Pkg.precompile() do
216+
Pkg.add("Example")
217+
Pkg.dev("JSON")
218+
Pkg.update("HTTP")
219+
end
220+
Resolving package versions...
221+
...
222+
Precompiling environment...
223+
14 dependencies successfully precompiled in 25 seconds
224+
```
225+
226+
Or to temporarily disable auto-precompilation:
227+
228+
```julia-repl
229+
julia> Pkg.autoprecompilation_enabled(false)
230+
false
231+
232+
julia> Pkg.add("Example") # No precompilation happens
233+
Resolving package versions...
234+
...
235+
236+
julia> Pkg.autoprecompilation_enabled(true)
237+
true
238+
```
239+
203240
### Precompiling new versions of loaded packages
204241

205242
If a package that has been updated is already loaded in the session, the precompilation process will go ahead and precompile

src/API.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import FileWatching
1212

1313
import Base: StaleCacheKey
1414

15-
import ..depots, ..depots1, ..logdir, ..devdir, ..printpkgstyle
15+
import ..depots, ..depots1, ..logdir, ..devdir, ..printpkgstyle, .._autoprecompilation_enabled_scoped
1616
import ..Operations, ..GitTools, ..Pkg, ..Registry
1717
import ..can_fancyprint, ..pathrepr, ..isurl, ..PREV_ENV_PATH, ..atomic_toml_write
1818
using ..Types, ..TOML
@@ -1185,6 +1185,13 @@ function precompile(ctx::Context, pkgs::Vector{PackageSpec}; internal_call::Bool
11851185
end
11861186
end
11871187

1188+
function precompile(f, args...; kwargs...)
1189+
Base.ScopedValues.@with _autoprecompilation_enabled_scoped => false begin
1190+
f()
1191+
Pkg.precompile(args...; kwargs...)
1192+
end
1193+
end
1194+
11881195
function tree_hash(repo::LibGit2.GitRepo, tree_hash::String)
11891196
try
11901197
return LibGit2.GitObject(repo, tree_hash)

src/Pkg.jl

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,20 @@ const PREV_ENV_PATH = Ref{String}("")
7070

7171
usable_io(io) = (io isa Base.TTY) || (io isa IOContext{IO} && io.io isa Base.TTY)
7272
can_fancyprint(io::IO) = (usable_io(io)) && (get(ENV, "CI", nothing) != "true")
73-
should_autoprecompile() = Base.JLOptions().use_compiled_modules == 1 && Base.get_bool_env("JULIA_PKG_PRECOMPILE_AUTO", true)
73+
74+
_autoprecompilation_enabled::Bool = true
75+
const _autoprecompilation_enabled_scoped = Base.ScopedValues.ScopedValue{Bool}(true)
76+
autoprecompilation_enabled(state::Bool) = (global _autoprecompilation_enabled = state)
77+
function should_autoprecompile()
78+
if Base.JLOptions().use_compiled_modules == 1 &&
79+
_autoprecompilation_enabled &&
80+
_autoprecompilation_enabled_scoped[] &&
81+
Base.get_bool_env("JULIA_PKG_PRECOMPILE_AUTO", true)
82+
return true
83+
else
84+
return false
85+
end
86+
end
7487

7588
"""
7689
in_repl_mode()
@@ -206,11 +219,21 @@ const add = API.add
206219
Pkg.precompile(; strict::Bool=false, timing::Bool=false)
207220
Pkg.precompile(pkg; strict::Bool=false, timing::Bool=false)
208221
Pkg.precompile(pkgs; strict::Bool=false, timing::Bool=false)
222+
Pkg.precompile(f, args...; kwargs...)
209223
210224
Precompile all or specific dependencies of the project in parallel.
211225
212226
Set `timing=true` to show the duration of the precompilation of each dependency.
213227
228+
To delay autoprecompilation of multiple Pkg actions until the end use.
229+
This may be most efficient while manipulating the environment in various ways.
230+
231+
```julia
232+
Pkg.precompile() do
233+
# Pkg actions here
234+
end
235+
```
236+
214237
!!! note
215238
Errors will only throw when precompiling the top-level dependencies, given that
216239
not all manifest dependencies may be loaded by the top-level dependencies on the given system.
@@ -228,6 +251,9 @@ Set `timing=true` to show the duration of the precompilation of each dependency.
228251
!!! compat "Julia 1.9"
229252
Timing mode requires at least Julia 1.9.
230253
254+
!!! compat "Julia 1.13"
255+
The `Pkg.precompile(f, args...; kwargs...)` do-block syntax requires at least Julia 1.13.
256+
231257
# Examples
232258
```julia
233259
Pkg.precompile()
@@ -237,6 +263,39 @@ Pkg.precompile(["Foo", "Bar"])
237263
"""
238264
const precompile = API.precompile
239265

266+
"""
267+
Pkg.autoprecompilation_enabled(state::Bool)
268+
269+
Enable or disable automatic precompilation for Pkg operations.
270+
271+
When `state` is `true` (default), Pkg operations that modify the project environment
272+
will automatically trigger precompilation of affected packages. When `state` is `false`,
273+
automatic precompilation is disabled and packages will only be precompiled when
274+
explicitly requested via [`Pkg.precompile`](@ref).
275+
276+
This setting affects the global state and persists across Pkg operations in the same
277+
Julia session. It can be used in combination with [`Pkg.precompile`](@ref) do-syntax
278+
for more fine-grained control over when precompilation occurs.
279+
280+
!!! compat "Julia 1.13"
281+
This function requires at least Julia 1.13.
282+
283+
# Examples
284+
```julia
285+
# Disable automatic precompilation
286+
Pkg.autoprecompilation_enabled(false)
287+
Pkg.add("Example") # Will not trigger auto-precompilation
288+
Pkg.precompile() # Manual precompilation
289+
290+
# Re-enable automatic precompilation
291+
Pkg.autoprecompilation_enabled(true)
292+
Pkg.add("AnotherPackage") # Will trigger auto-precompilation
293+
```
294+
295+
See also [`Pkg.precompile`](@ref).
296+
"""
297+
autoprecompilation_enabled
298+
240299
"""
241300
Pkg.rm(pkg::Union{String, Vector{String}}; mode::PackageMode = PKGMODE_PROJECT)
242301
Pkg.rm(pkg::Union{PackageSpec, Vector{PackageSpec}}; mode::PackageMode = PKGMODE_PROJECT)

test/api.jl

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,67 @@ import .FakeTerminals.FakeTerminal
158158
@test !occursin("Precompiling", String(take!(iob))) # test that the previous precompile was a no-op
159159
end
160160

161+
dep8_path = git_init_package(tmp, joinpath("packages", "Dep8"))
162+
function clear_dep8_cache()
163+
rm(joinpath(Pkg.depots1(), "compiled", "v$(VERSION.major).$(VERSION.minor)", "Dep8"), force=true, recursive=true)
164+
end
165+
@testset "delayed precompilation with do-syntax" begin
166+
iob = IOBuffer()
167+
# Test that operations inside Pkg.precompile() do block don't trigger auto-precompilation
168+
Pkg.precompile(io=iob) do
169+
Pkg.add(Pkg.PackageSpec(path=dep8_path))
170+
Pkg.rm("Dep8")
171+
clear_dep8_cache()
172+
Pkg.add(Pkg.PackageSpec(path=dep8_path))
173+
end
174+
175+
# The precompile should happen once at the end
176+
@test count(r"Precompiling", String(take!(iob))) == 1 # should only precompile once
177+
178+
# Verify it was precompiled by checking a second call is a no-op
179+
Pkg.precompile(io=iob)
180+
@test !occursin("Precompiling", String(take!(iob)))
181+
end
182+
183+
Pkg.rm("Dep8")
184+
185+
@testset "autoprecompilation_enabled global control" begin
186+
iob = IOBuffer()
187+
withenv("JULIA_PKG_PRECOMPILE_AUTO" => nothing) do
188+
original_state = Pkg._autoprecompilation_enabled
189+
try
190+
Pkg.autoprecompilation_enabled(false)
191+
@test Pkg._autoprecompilation_enabled == false
192+
193+
# Operations should not trigger autoprecompilation when globally disabled
194+
clear_dep8_cache()
195+
Pkg.add(Pkg.PackageSpec(path=dep8_path), io=iob)
196+
@test !occursin("Precompiling", String(take!(iob)))
197+
198+
# Manual precompile should still work
199+
@test Base.isprecompiled(Base.identify_package("Dep8")) == false
200+
Pkg.precompile(io=iob)
201+
@test occursin("Precompiling", String(take!(iob)))
202+
@test Base.isprecompiled(Base.identify_package("Dep8"))
203+
204+
# Re-enable autoprecompilation
205+
Pkg.autoprecompilation_enabled(true)
206+
@test Pkg._autoprecompilation_enabled == true
207+
208+
# Operations should now trigger autoprecompilation again
209+
Pkg.rm("Dep8", io=iob)
210+
clear_dep8_cache()
211+
Pkg.add(Pkg.PackageSpec(path=dep8_path), io=iob)
212+
@test Base.isprecompiled(Base.identify_package("Dep8"))
213+
@test occursin("Precompiling", String(take!(iob)))
214+
215+
finally
216+
# Restore original state
217+
Pkg.autoprecompilation_enabled(original_state)
218+
end
219+
end
220+
end
221+
161222
@testset "instantiate" begin
162223
iob = IOBuffer()
163224
Pkg.activate("packages/Dep7")

0 commit comments

Comments
 (0)