Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Pkg v1.13 Release Notes
- `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])
- `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])
- 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])
- Added `Pkg.precompile() do` block syntax to delay autoprecompilation until after multiple operations complete, improving efficiency when performing several environment changes. ([#4262])
- Added `Pkg.autoprecompilation_enabled(state::Bool)` to globally enable or disable automatic precompilation for Pkg operations. ([#4262])

Pkg v1.12 Release Notes
=======================
Expand Down
1 change: 1 addition & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Pkg.gc
Pkg.status
Pkg.compat
Pkg.precompile
Pkg.autoprecompilation_enabled
Pkg.offline
Pkg.why
Pkg.dependencies
Expand Down
43 changes: 40 additions & 3 deletions docs/src/environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,53 @@ If a given package version errors during auto-precompilation, Pkg will remember
automatically tries and will skip that package with a brief warning. Manual precompilation can be used to
force these packages to be retried, as `pkg> precompile` will always retry all packages.

To disable the auto-precompilation, set `ENV["JULIA_PKG_PRECOMPILE_AUTO"]=0`.

The indicators next to the package names displayed during precompilation
indicate the status of that package's precompilation.
indicate the status of that package's precompilation.

- `[◐, ◓, ◑, ◒]` Animated "clock" characters indicate that the package is currently being precompiled.
- `✓` 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.
- `?` A question mark character indicates that a `PrecompilableError` was thrown, indicating that precompilation was disallowed, i.e. `__precompile__(false)` in that package.
- `✗` A cross indicates that the package failed to precompile.

#### Controlling Auto-precompilation

Auto-precompilation can be controlled in several ways:

- **Environment variable**: Set `ENV["JULIA_PKG_PRECOMPILE_AUTO"]=0` to disable auto-precompilation globally.
- **Programmatically**: Use `Pkg.autoprecompilation_enabled(false)` to disable auto-precompilation for the current session, or `Pkg.autoprecompilation_enabled(true)` to re-enable it.
- **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.

!!! compat "Julia 1.13"
The `Pkg.autoprecompilation_enabled()` function and `Pkg.precompile()` do-block syntax require at least Julia 1.13.

For example, to add multiple packages without triggering precompilation after each one:

```julia-repl
julia> Pkg.precompile() do
Pkg.add("Example")
Pkg.dev("JSON")
Pkg.update("HTTP")
end
Resolving package versions...
...
Precompiling environment...
14 dependencies successfully precompiled in 25 seconds
```

Or to temporarily disable auto-precompilation:

```julia-repl
julia> Pkg.autoprecompilation_enabled(false)
false

julia> Pkg.add("Example") # No precompilation happens
Resolving package versions...
...

julia> Pkg.autoprecompilation_enabled(true)
true
```

### Precompiling new versions of loaded packages

If a package that has been updated is already loaded in the session, the precompilation process will go ahead and precompile
Expand Down
9 changes: 8 additions & 1 deletion src/API.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import FileWatching

import Base: StaleCacheKey

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

function precompile(f, args...; kwargs...)
Base.ScopedValues.@with _autoprecompilation_enabled_scoped => false begin
f()
Pkg.precompile(args...; kwargs...)
end
end

function tree_hash(repo::LibGit2.GitRepo, tree_hash::String)
try
return LibGit2.GitObject(repo, tree_hash)
Expand Down
61 changes: 60 additions & 1 deletion src/Pkg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,20 @@ const PREV_ENV_PATH = Ref{String}("")

usable_io(io) = (io isa Base.TTY) || (io isa IOContext{IO} && io.io isa Base.TTY)
can_fancyprint(io::IO) = (usable_io(io)) && (get(ENV, "CI", nothing) != "true")
should_autoprecompile() = Base.JLOptions().use_compiled_modules == 1 && Base.get_bool_env("JULIA_PKG_PRECOMPILE_AUTO", true)

_autoprecompilation_enabled::Bool = true
const _autoprecompilation_enabled_scoped = Base.ScopedValues.ScopedValue{Bool}(true)
autoprecompilation_enabled(state::Bool) = (global _autoprecompilation_enabled = state)
function should_autoprecompile()
if Base.JLOptions().use_compiled_modules == 1 &&
_autoprecompilation_enabled &&
_autoprecompilation_enabled_scoped[] &&
Base.get_bool_env("JULIA_PKG_PRECOMPILE_AUTO", true)
return true
else
return false
end
end

"""
in_repl_mode()
Expand Down Expand Up @@ -206,11 +219,21 @@ const add = API.add
Pkg.precompile(; strict::Bool=false, timing::Bool=false)
Pkg.precompile(pkg; strict::Bool=false, timing::Bool=false)
Pkg.precompile(pkgs; strict::Bool=false, timing::Bool=false)
Pkg.precompile(f, args...; kwargs...)

Precompile all or specific dependencies of the project in parallel.

Set `timing=true` to show the duration of the precompilation of each dependency.

To delay autoprecompilation of multiple Pkg actions until the end use.
This may be most efficient while manipulating the environment in various ways.

```julia
Pkg.precompile() do
# Pkg actions here
end
```

!!! note
Errors will only throw when precompiling the top-level dependencies, given that
not all manifest dependencies may be loaded by the top-level dependencies on the given system.
Expand All @@ -228,6 +251,9 @@ Set `timing=true` to show the duration of the precompilation of each dependency.
!!! compat "Julia 1.9"
Timing mode requires at least Julia 1.9.

!!! compat "Julia 1.13"
The `Pkg.precompile(f, args...; kwargs...)` do-block syntax requires at least Julia 1.13.

# Examples
```julia
Pkg.precompile()
Expand All @@ -237,6 +263,39 @@ Pkg.precompile(["Foo", "Bar"])
"""
const precompile = API.precompile

"""
Pkg.autoprecompilation_enabled(state::Bool)

Enable or disable automatic precompilation for Pkg operations.

When `state` is `true` (default), Pkg operations that modify the project environment
will automatically trigger precompilation of affected packages. When `state` is `false`,
automatic precompilation is disabled and packages will only be precompiled when
explicitly requested via [`Pkg.precompile`](@ref).

This setting affects the global state and persists across Pkg operations in the same
Julia session. It can be used in combination with [`Pkg.precompile`](@ref) do-syntax
for more fine-grained control over when precompilation occurs.

!!! compat "Julia 1.13"
This function requires at least Julia 1.13.

# Examples
```julia
# Disable automatic precompilation
Pkg.autoprecompilation_enabled(false)
Pkg.add("Example") # Will not trigger auto-precompilation
Pkg.precompile() # Manual precompilation

# Re-enable automatic precompilation
Pkg.autoprecompilation_enabled(true)
Pkg.add("AnotherPackage") # Will trigger auto-precompilation
```

See also [`Pkg.precompile`](@ref).
"""
autoprecompilation_enabled

"""
Pkg.rm(pkg::Union{String, Vector{String}}; mode::PackageMode = PKGMODE_PROJECT)
Pkg.rm(pkg::Union{PackageSpec, Vector{PackageSpec}}; mode::PackageMode = PKGMODE_PROJECT)
Expand Down
61 changes: 61 additions & 0 deletions test/api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,67 @@ import .FakeTerminals.FakeTerminal
@test !occursin("Precompiling", String(take!(iob))) # test that the previous precompile was a no-op
end

dep8_path = git_init_package(tmp, joinpath("packages", "Dep8"))
function clear_dep8_cache()
rm(joinpath(Pkg.depots1(), "compiled", "v$(VERSION.major).$(VERSION.minor)", "Dep8"), force=true, recursive=true)
end
@testset "delayed precompilation with do-syntax" begin
iob = IOBuffer()
# Test that operations inside Pkg.precompile() do block don't trigger auto-precompilation
Pkg.precompile(io=iob) do
Pkg.add(Pkg.PackageSpec(path=dep8_path))
Pkg.rm("Dep8")
clear_dep8_cache()
Pkg.add(Pkg.PackageSpec(path=dep8_path))
end

# The precompile should happen once at the end
@test count(r"Precompiling", String(take!(iob))) == 1 # should only precompile once

# Verify it was precompiled by checking a second call is a no-op
Pkg.precompile(io=iob)
@test !occursin("Precompiling", String(take!(iob)))
end

Pkg.rm("Dep8")

@testset "autoprecompilation_enabled global control" begin
iob = IOBuffer()
withenv("JULIA_PKG_PRECOMPILE_AUTO" => nothing) do
original_state = Pkg._autoprecompilation_enabled
try
Pkg.autoprecompilation_enabled(false)
@test Pkg._autoprecompilation_enabled == false

# Operations should not trigger autoprecompilation when globally disabled
clear_dep8_cache()
Pkg.add(Pkg.PackageSpec(path=dep8_path), io=iob)
@test !occursin("Precompiling", String(take!(iob)))

# Manual precompile should still work
@test Base.isprecompiled(Base.identify_package("Dep8")) == false
Pkg.precompile(io=iob)
@test occursin("Precompiling", String(take!(iob)))
@test Base.isprecompiled(Base.identify_package("Dep8"))

# Re-enable autoprecompilation
Pkg.autoprecompilation_enabled(true)
@test Pkg._autoprecompilation_enabled == true

# Operations should now trigger autoprecompilation again
Pkg.rm("Dep8", io=iob)
clear_dep8_cache()
Pkg.add(Pkg.PackageSpec(path=dep8_path), io=iob)
@test Base.isprecompiled(Base.identify_package("Dep8"))
@test occursin("Precompiling", String(take!(iob)))

finally
# Restore original state
Pkg.autoprecompilation_enabled(original_state)
end
end
end

@testset "instantiate" begin
iob = IOBuffer()
Pkg.activate("packages/Dep7")
Expand Down