From 63f97651d50be45cb65006891bfca10356412abb Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Wed, 11 Feb 2026 10:12:22 +0100 Subject: [PATCH 1/2] Add support for not triggering invalidation in `load_preference()` In some cases it's desirable to use Preferences.jl as a generic preferences mechanism without wanting it to affect precompilation. --- README.md | 1 + src/Preferences.jl | 44 +++++++++++++-------- test/UsesPreferences/src/UsesPreferences.jl | 3 ++ test/runtests.jl | 18 +++++++++ 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e3e4d7a..17a6a15 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ See the docstring for `set_preferences!()` for the full details of how to set pr Preferences that are accessed during compilation are automatically marked as compile-time preferences, and any change recorded to these preferences will cause the Julia compiler to recompile any cached precompilation `.ji` files for that module. This allows preferences to be used to influence code generation. When your package sets a compile-time preference, it is usually best to suggest to the user that they should restart Julia, to allow recompilation to occur. +It is possible to disable this marking by passing `compiletime=false` to `load_preference()`. Note that the package can be installed on Julia v1.0+ but is only functional on Julia v1.6+. diff --git a/src/Preferences.jl b/src/Preferences.jl index bdf3075..e0e7cd0 100644 --- a/src/Preferences.jl +++ b/src/Preferences.jl @@ -15,39 +15,51 @@ export load_preference, @load_preference, include("utils.jl") """ - load_preference(uuid_or_module_or_name, key, default = nothing) + load_preference(uuid_or_module_or_name, key, default = nothing; compiletime = true) Load a particular preference from the `Preferences.toml` file, shallowly merging keys as it walks the hierarchy of load paths, loading preferences from all environments that list the given UUID as a direct dependency. +If `compiletime` is `true` (the default) and Julia is currently precompiling, the +preference is recorded as a compile-time dependency so that the precompiled cache is +automatically invalidated when the preference changes. Set `compiletime` to `false` to +opt out of this: the preference will still be loaded during precompilation, but changes +to it will **not** cause the package to be recompiled. Be careful when setting +this. If a preference is somehow cached in the module (e.g. as a global +variable) changing it will **not** invalidate the precompiled cache. + Most users should use the `@load_preference` convenience macro which auto-determines the calling `Module`. """ function load_preference end -function load_preference(uuid::UUID, key::String, default = nothing) +function load_preference(uuid::UUID, key::String, default = nothing; compiletime::Bool = true) # Re-use definition in `base/loading.jl` so as to not repeat code. d = Base.get_preferences(uuid) - if currently_compiling() + if compiletime && currently_compiling() Base.record_compiletime_preference(uuid, key) end return drop_clears(get(d, key, default)) end -function load_preference(m::Module, key::String, default = nothing) - return load_preference(get_uuid(m), key, default) +function load_preference(m::Module, key::String, default = nothing; compiletime::Bool = true) + return load_preference(get_uuid(m), key, default; compiletime) end -function load_preference(name::String, key::String, default = nothing) +function load_preference(name::String, key::String, default = nothing; compiletime::Bool = true) uuid = get_uuid(name) if uuid === nothing package_lookup_error(name) end - return load_preference(uuid, key, default) + return load_preference(uuid, key, default; compiletime) end """ - @load_preference(key) + @load_preference(key, default = nothing) Convenience macro to call `load_preference()` for the current package. + +To opt out of compile-time preference tracking, use the function form directly: + + load_preference(@__MODULE__, key, default; compiletime = false) """ macro load_preference(key, default = nothing) return quote @@ -56,26 +68,26 @@ macro load_preference(key, default = nothing) end """ - has_preference(uuid_or_module_or_name, key) + has_preference(uuid_or_module_or_name, key; compiletime = true) Return `true` if the particular preference is found, and `false` otherwise. -See the `has_preference` docstring for more details. +See the `load_preference` docstring for details on the `compiletime` keyword. """ function has_preference end -function has_preference(uuid::UUID, key::String) - value = load_preference(uuid, key, nothing) +function has_preference(uuid::UUID, key::String; compiletime::Bool = true) + value = load_preference(uuid, key, nothing; compiletime) return !(value isa Nothing) end -function has_preference(m::Module, key::String) - return has_preference(get_uuid(m), key) +function has_preference(m::Module, key::String; compiletime::Bool = true) + return has_preference(get_uuid(m), key; compiletime) end -function has_preference(name::String, key::String) +function has_preference(name::String, key::String; compiletime::Bool = true) uuid = get_uuid(name) if uuid === nothing package_lookup_error(name) end - return has_preference(uuid, key) + return has_preference(uuid, key; compiletime) end """ diff --git a/test/UsesPreferences/src/UsesPreferences.jl b/test/UsesPreferences/src/UsesPreferences.jl index daefd03..7f14e14 100644 --- a/test/UsesPreferences/src/UsesPreferences.jl +++ b/test/UsesPreferences/src/UsesPreferences.jl @@ -18,6 +18,9 @@ end const backend = @load_preference("backend", "OpenCL") +# A compile-time preference that does NOT trigger recompilation when changed +const backend_noncached = load_preference(@__MODULE__, "backend_noncached", "OpenCL"; compiletime=false) + # An example that helps us to prove that things are happening at compile-time function do_computation() @static if backend == "OpenCL" diff --git a/test/runtests.jl b/test/runtests.jl index bff52c2..997859a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -114,6 +114,24 @@ up_path = joinpath(@__DIR__, "UsesPreferences") output = activate_and_run(up_path, cuda_test_didnt_precompile; env=Dict("JULIA_DEBUG" => "loading")) @test !did_precompile(output) + # Test that changing a preference loaded with `compiletime=false` does not + # trigger recompilation, even though it was read at module-load time. + activate_and_run(up_path, """ + using Preferences + using Base: UUID + set_preferences!($(repr(up_uuid)), "backend_noncached" => "CUDA"; force=true) + """) + noncached_test = """ + using Test + # Make sure `UsesPreferences` is already precompiled + isdefined(Base, :isprecompiled) && @test Base.isprecompiled(Base.identify_package("UsesPreferences")) + using UsesPreferences + isdefined(Base, :isprecompiled) && @test Base.isprecompiled(Base.identify_package("UsesPreferences")) + @test UsesPreferences.backend_noncached == "OpenCL" # stale cached value + """ + output = activate_and_run(up_path, noncached_test; env=Dict("JULIA_DEBUG" => "loading")) + @test !did_precompile(output) + # Test non-compiletime preferences a bit activate_and_run(up_path, """ using UsesPreferences, Test, Preferences From 1f1788130add1ae1e6f7a736e3cd8d657cfd222f Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Tue, 24 Feb 2026 18:44:22 +0100 Subject: [PATCH 2/2] fixup! Add support for not triggering invalidation in `load_preference()` --- README.md | 4 +- src/Preferences.jl | 42 ++++++++++----------- test/UsesPreferences/src/UsesPreferences.jl | 4 +- test/runtests.jl | 4 +- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 17a6a15..4c82547 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ See the docstring for `set_preferences!()` for the full details of how to set pr Preferences that are accessed during compilation are automatically marked as compile-time preferences, and any change recorded to these preferences will cause the Julia compiler to recompile any cached precompilation `.ji` files for that module. This allows preferences to be used to influence code generation. When your package sets a compile-time preference, it is usually best to suggest to the user that they should restart Julia, to allow recompilation to occur. -It is possible to disable this marking by passing `compiletime=false` to `load_preference()`. +It is possible to disable this marking by passing `disable_invalidation=true` to `load_preference()`. Note that the package can be installed on Julia v1.0+ but is only functional on Julia v1.6+. @@ -67,7 +67,7 @@ function do_computation() end -# A non-compiletime preference +# A preference that does not trigger invalidation function set_username(username::String) @set_preferences!("username" => username) end diff --git a/src/Preferences.jl b/src/Preferences.jl index e0e7cd0..6466a73 100644 --- a/src/Preferences.jl +++ b/src/Preferences.jl @@ -15,17 +15,17 @@ export load_preference, @load_preference, include("utils.jl") """ - load_preference(uuid_or_module_or_name, key, default = nothing; compiletime = true) + load_preference(uuid_or_module_or_name, key, default = nothing; disable_invalidation = false) Load a particular preference from the `Preferences.toml` file, shallowly merging keys as it walks the hierarchy of load paths, loading preferences from all environments that list the given UUID as a direct dependency. -If `compiletime` is `true` (the default) and Julia is currently precompiling, the -preference is recorded as a compile-time dependency so that the precompiled cache is -automatically invalidated when the preference changes. Set `compiletime` to `false` to -opt out of this: the preference will still be loaded during precompilation, but changes -to it will **not** cause the package to be recompiled. Be careful when setting +If `disable_invalidation` is `false` (the default) and Julia is currently precompiling, +the preference is recorded as a compile-time dependency so that the precompiled cache is +automatically invalidated when the preference changes. Set `disable_invalidation` to +`true` to opt out of this: the preference will still be loaded during precompilation, but +changes to it will **not** cause the package to be recompiled. Be careful when setting this. If a preference is somehow cached in the module (e.g. as a global variable) changing it will **not** invalidate the precompiled cache. @@ -33,23 +33,23 @@ Most users should use the `@load_preference` convenience macro which auto-determ calling `Module`. """ function load_preference end -function load_preference(uuid::UUID, key::String, default = nothing; compiletime::Bool = true) +function load_preference(uuid::UUID, key::String, default = nothing; disable_invalidation::Bool = false) # Re-use definition in `base/loading.jl` so as to not repeat code. d = Base.get_preferences(uuid) - if compiletime && currently_compiling() + if !disable_invalidation && currently_compiling() Base.record_compiletime_preference(uuid, key) end return drop_clears(get(d, key, default)) end -function load_preference(m::Module, key::String, default = nothing; compiletime::Bool = true) - return load_preference(get_uuid(m), key, default; compiletime) +function load_preference(m::Module, key::String, default = nothing; disable_invalidation::Bool = false) + return load_preference(get_uuid(m), key, default; disable_invalidation) end -function load_preference(name::String, key::String, default = nothing; compiletime::Bool = true) +function load_preference(name::String, key::String, default = nothing; disable_invalidation::Bool = false) uuid = get_uuid(name) if uuid === nothing package_lookup_error(name) end - return load_preference(uuid, key, default; compiletime) + return load_preference(uuid, key, default; disable_invalidation) end """ @@ -59,7 +59,7 @@ Convenience macro to call `load_preference()` for the current package. To opt out of compile-time preference tracking, use the function form directly: - load_preference(@__MODULE__, key, default; compiletime = false) + load_preference(@__MODULE__, key, default; disable_invalidation = true) """ macro load_preference(key, default = nothing) return quote @@ -68,26 +68,26 @@ macro load_preference(key, default = nothing) end """ - has_preference(uuid_or_module_or_name, key; compiletime = true) + has_preference(uuid_or_module_or_name, key; disable_invalidation = false) Return `true` if the particular preference is found, and `false` otherwise. -See the `load_preference` docstring for details on the `compiletime` keyword. +See the `load_preference` docstring for details on the `disable_invalidation` keyword. """ function has_preference end -function has_preference(uuid::UUID, key::String; compiletime::Bool = true) - value = load_preference(uuid, key, nothing; compiletime) +function has_preference(uuid::UUID, key::String; disable_invalidation::Bool = false) + value = load_preference(uuid, key, nothing; disable_invalidation) return !(value isa Nothing) end -function has_preference(m::Module, key::String; compiletime::Bool = true) - return has_preference(get_uuid(m), key; compiletime) +function has_preference(m::Module, key::String; disable_invalidation::Bool = false) + return has_preference(get_uuid(m), key; disable_invalidation) end -function has_preference(name::String, key::String; compiletime::Bool = true) +function has_preference(name::String, key::String; disable_invalidation::Bool = false) uuid = get_uuid(name) if uuid === nothing package_lookup_error(name) end - return has_preference(uuid, key; compiletime) + return has_preference(uuid, key; disable_invalidation) end """ diff --git a/test/UsesPreferences/src/UsesPreferences.jl b/test/UsesPreferences/src/UsesPreferences.jl index 7f14e14..10e1ad4 100644 --- a/test/UsesPreferences/src/UsesPreferences.jl +++ b/test/UsesPreferences/src/UsesPreferences.jl @@ -19,7 +19,7 @@ end const backend = @load_preference("backend", "OpenCL") # A compile-time preference that does NOT trigger recompilation when changed -const backend_noncached = load_preference(@__MODULE__, "backend_noncached", "OpenCL"; compiletime=false) +const backend_noncached = load_preference(@__MODULE__, "backend_noncached", "OpenCL"; disable_invalidation=true) # An example that helps us to prove that things are happening at compile-time function do_computation() @@ -35,7 +35,7 @@ function do_computation() end -# A non-compiletime preference +# A preference that does not trigger invalidation function set_username(username::String) @set_preferences!("username" => username) end diff --git a/test/runtests.jl b/test/runtests.jl index 997859a..ac681db 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -114,7 +114,7 @@ up_path = joinpath(@__DIR__, "UsesPreferences") output = activate_and_run(up_path, cuda_test_didnt_precompile; env=Dict("JULIA_DEBUG" => "loading")) @test !did_precompile(output) - # Test that changing a preference loaded with `compiletime=false` does not + # Test that changing a preference loaded with `disable_invalidation=true` does not # trigger recompilation, even though it was read at module-load time. activate_and_run(up_path, """ using Preferences @@ -132,7 +132,7 @@ up_path = joinpath(@__DIR__, "UsesPreferences") output = activate_and_run(up_path, noncached_test; env=Dict("JULIA_DEBUG" => "loading")) @test !did_precompile(output) - # Test non-compiletime preferences a bit + # Test preferences with invalidation disabled activate_and_run(up_path, """ using UsesPreferences, Test, Preferences using Base: UUID