Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document "exported" preferences #48

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
59 changes: 54 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,35 @@

The `Preferences` package provides a convenient, integrated way for packages to store configuration switches to persistent TOML files, and use those pieces of information at both run time and compile time in Julia v1.6+.
This enables the user to modify the behavior of a package, and have that choice reflected in everything from run time algorithm choice to code generation at compile time.
Preferences are stored as TOML dictionaries and are, by default, stored within a `(Julia)LocalPreferences.toml` file next to the currently-active project.
If a preference is "exported", it is instead stored within the `(Julia)Project.toml`.
The intention is to allow shared projects to contain shared preferences, while allowing for users themselves to override those preferences with their own settings in the `LocalPreferences.toml` file, which should be `.gitignore`d as the name implies.

Note that the package can be installed on Julia v1.0+ but is only functional on Julia v1.6+.

## Project-specific vs package-wide preferences

Preferences are stored as TOML dictionaries and are, by default, stored within a `(Julia)LocalPreferences.toml` file next to the currently-active project. This results in *project-specific*
preferences, meaning that different projects making use of the same package can set up
different, non-conflicting preferences.

Preferences can be set with depot-wide defaults; if package `Foo` is installed within your global environment and it has preferences set, these preferences will apply as long as your global environment is part of your [`LOAD_PATH`](https://docs.julialang.org/en/v1/manual/code-loading/#Environment-stacks).
Preferences in environments higher up in the environment stack get overridden by the more proximal entries in the load path, ending with the currently active project.
This allows depot-wide preference defaults to exist, with active projects able to merge or even completely overwrite these inherited preferences.
See the docstring for `set_preferences!()` for the full details of how to set preferences to allow or disallow merging.

In contrast, *package-wide* preferences are stored within within the package's own `(Julia)Project.toml` file. Such preferences apply to all users of the package, regardless of the active project.

You can control which kind of preference you create; this is discussed in the API subsections below.

## Run-time vs compile-time preferences

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.

Note that the package can be installed on Julia v1.0+ but is only functional on Julia v1.6+.
If you call `load_preference` (or its macro variant `@load_preference`) from "top-level" in the package,
this is a compile-time preference. Otherwise (e.g., if it is "buried" inside a function, and that
function doesn't get executed at top-level), it is a run-time preference. See examples in the first API section below.
Comment on lines +38 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't exactly true; a preference can still be loaded from within a function and used at compile time if that function is invoked during precompilation time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"and that function doesn't get executed at top-level"? But obviously it's not clear.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yeah. I'm honestly not sure it matters much to draw a distinction between compile-time and non-compile-time preferences; perhaps we should just have a note that says "if you use a preference at compile time, you will have to restart Julia before the changes will be seen" and leave it at that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, perhaps we could instead have set_preferences() spit out a @warn() when it notices a compile-time preference has been changed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that idea!

We still might need to discuss this issue somewhere, otherwise people might be mystified about why it sometimes requires a restart and why it sometimes doesn't. (Might feel like a bug when it doesn't warn.)


## API
## API: Project-specific preferences

Preferences use is very simple; it is all based around four functions (which each have convenience macros): `@set_preferences!()`, `@load_preference()`, `@has_preference()`, and `@delete_preferences!()`.

Expand Down Expand Up @@ -70,6 +83,7 @@ end


# A non-compiletime preference
# These can change dynamically, and no Julia restart is needed.
function set_username(username::String)
@set_preferences!("username" => username)
end
Expand All @@ -80,6 +94,41 @@ end
end # module UsesPreferences
```

With the macros, all preferences are project-specific.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would be better to figure out how to pass kwargs to a macro, so we can have @set_preferences!("foo" => "bar"; export=true), or something like that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would make sense. A related issue: currently you have to read the docstring for set_preferences! in order to understand the force comment in the docstring for @set_preferences!. What do you want to do about that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I don't understand active_project_only well enough myself to even try describing it in the README (I could read the code, but...)


## API: package-wide preferences

To set preferences for *all* users of a package (across many different projects), use the functional form

```julia
set_preferences!(module, prefs...; export_prefs=true)
```

To use this approach, the example above might become

```julia
module AlsoUsesPreferences

function set_backend(new_backend::String)
if !(new_backend in ("OpenCL", "CUDA", "jlFPGA"))
throw(ArgumentError("Invalid backend: \"$(new_backend)\""))
end

# Set it in our runtime values, as well as saving it to disk
# Export it for all users of the package (export_prefs=true):
set_preferences!(@__MODULE__, "backend" => new_backend; export_prefs=true)
@info("New backend set; restart your Julia session for this change to take effect!")
end


end
```

You can use the explicit module name, `AlsoUsesPreferences`, as the first argument to `set_preferences!`, but consider using `@__MODULE__` instead, as it continues to work even if you decide to rename your package.

You can set preferences for another, unloaded package, using the package `UUID` in place of the module.

## Conditional Loading

To use `Preferences` with Julia 1.6 and later but falling back to a
Expand Down