diff --git a/README.md b/README.md index e3e4d7a..4c82547 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 `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+. @@ -66,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 bdf3075..6466a73 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; 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 `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. + 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; disable_invalidation::Bool = false) # Re-use definition in `base/loading.jl` so as to not repeat code. d = Base.get_preferences(uuid) - if 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) - return load_preference(get_uuid(m), key, default) +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) +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) + return load_preference(uuid, key, default; disable_invalidation) 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; disable_invalidation = true) """ 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; disable_invalidation = false) 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 `disable_invalidation` 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; disable_invalidation::Bool = false) + value = load_preference(uuid, key, nothing; disable_invalidation) 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; disable_invalidation::Bool = false) + return has_preference(get_uuid(m), key; disable_invalidation) end -function has_preference(name::String, key::String) +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) + return has_preference(uuid, key; disable_invalidation) end """ diff --git a/test/UsesPreferences/src/UsesPreferences.jl b/test/UsesPreferences/src/UsesPreferences.jl index daefd03..10e1ad4 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"; disable_invalidation=true) + # An example that helps us to prove that things are happening at compile-time function do_computation() @static if backend == "OpenCL" @@ -32,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 bff52c2..ac681db 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -114,7 +114,25 @@ 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 non-compiletime preferences a bit + # 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 + 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 preferences with invalidation disabled activate_and_run(up_path, """ using UsesPreferences, Test, Preferences using Base: UUID