-
-
Notifications
You must be signed in to change notification settings - Fork 269
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
Add Preferences
subsystem
#1835
Conversation
These functions provide a nice interface for determining the currently-running package's UUID and VersionNumber, identified by loaded `Module`. We explicitly do not include a lookup by name, in expectation of potential future naming clashes due to having multiple packages with the same name but different UUIDs.
I have some questions about this.
|
This PR doesn't set a user API, it's lower-level than that. These are the primitives that a package would use as a backing store, not a user-facing interactive toolkit. As an example, a package could include a Think of this as being just a simple datastore; a way for packages to store small, persistent |
This implements functionality and tests for a new `Scratch` subsystem in `Pkg`; analogous to the `Artifacts` added in 1.3, this provides an abstraction for a mutable datastore that can be explicitly lifecycled to an owning package, or shared among multiple packages. Closes #796
Very nice! Looks ideal for my MPI problems. |
5f498bb
to
3357746
Compare
Preferences provides a simple package configuration store; packages can store arbitrary configurations into `Dict` objects that get serialized into their active `Project.toml`. Depot-wide preferences can also be stored within the `prefs` folder of a Julia depot, allowing for default values to be passed down to new environments from the system admin.
3357746
to
5e2343f
Compare
I haven't reviewed this in detail but the biggest design consideration for preferences is layering. You want sysadmins and packages to be able to provide good defaults on shared systems, which calls for some kind of layering mechanism. But that leaves a number of questions:
I've thought about this for a while and I think that each layer should store a full set of choices along with whether that choice was inherited from a lower layer or set at this layer. That way if a choice at a lower layer changes, the user can be prompted for whether to keep the old choice or use the new default. This also helps address what should happen when a project moves to another system. You have a full snapshot of preferences which can be compared with the choices on the new system: if a value has a non-default value, you use that because it was already an explicit override; if a value has a default value that matches the inherited value on the new system then you can also safely continue to use that default; only when a value that is marked as default has a different inherited value do you need to decide what to do—this is much the same as if a default changes on the same system. In both cases, it makes sense to prompt the user or allow them to batch choose the project or the system. However, I'm not sure if this choice/inherited combination makes sense for all layers or only the last layer: i.e. the individual project. That's the level where choices get made and where reproducibility matters. Layering across depots versus layering across the load path feels like different kinds of preference inheritance. Depot patch inheritance is about inheriting choices that a sysadmin made for the system but allowing individual users to decide they want something else. Load path inheritance is about the same user deciding whether they want to use different preferences for different projects. |
I struggled back and forth with myself in responding to this, rewriting my comment 3-4 times before I really grasped the central difficulty I was having. We really shouldn't be packing all of this into the So far, I've avoided any kind of requirement for interactivity here. While packages can certainly define some kind of |
Is it usable at the top-level scope of packages? That is to say, can it be used as "build-time" (precompilation-time) option? If so, how does it handle precompilation cache invalidation? I actually have implemented a similar system in #1378. The biggest design decision was to make it nicely work with precompilation. Since cache invalidation works only per-file basis (via I think the best way to go might be to support more finer mechanism for calculating precompilation checksum than per-file. As TOML parser might be going into |
I decided to keep all compile-time stuff outside of this PR and instead put it here, as a separate package: https://github.com/staticfloat/CompileTimePreferences.jl |
It would be nice if you can explain the reasoning behind the decision. Or, if merging this into Pkg.jl at some point is in your pipeline, I'd appreciate it if you share your plan. I also note that the compile-time option is the main reason the preference system was requested: |
I like that separation. It's true to the nature of these two files. I'm a little unclear if that addresses changing inherited values (of so, how?) or if we still need to record whether something was an inherited value or not. I don't want to force interactivity, but I do think that when inherited defaults change, some choice generally needs to be made. We can have a default way to resolve that in non-interactive contexts, but users will generally want to choose these things. Otherwise you're permanently privileging either always following changes to inherited values or always ignoring them, neither of which seems like the right choice. |
Yes, I discussed it a bit on the slack, but I should record it here for permanence. The decision is based more in technical difficulties and ergonomics than in a philosophical dislike of compile-time preferences being a part of this. I'm totally open to merging the two if we can get a nice API meshing, but as it stands, it's difficult. Here's my thought process: My ideal API is one in which a package can mark some subtree of their preferences as used at compile-time. For instance, I could say something like
This design requires the system to maintain two separate pieces of information on disk; the
As I saw it, there were three options, none of them perfect:
I think the ideas of injecting dependencies into |
I think that if something is different between We can even tell if something has changed in the global environment by comparing preferences that we loaded straight from the
In this case, I think the mental model of "just a key/value datastore" breaks down a bit. This can quickly spiral out of control, but let's briefly touch on some options to see if we like other interfaces better:
# within Foo's Project.toml
# This defines preferences that belong to this package
[Preferences.libfoo_vendor]
description = "Choice of libfoo vendor"
valid_options = ["libfoo_jll", "custom_libfoo_jll"]
merge_policy = "prefer_depot"
type = "string"
default = "libfoo_jll"
[Preferences.frobulate]
description = "Enables frobulation. Obviously."
merge_policy = "prefer_project"
type = "boolean"
default = true
[Preferences.nuke_launch_threshold]
description = "Consult the documentation for a full description of this option"
merge_policy = "ask_user"
type = "float"
default = 13.7 # Within OverallProject.toml
[deps]
Foo = "$(foo_uuid)"
[Preferences.$(foo_uuid).libfoo_vendor]
value = "custom_libfoo_jll"
[Preferences.nuke_launch_threshold]
value = 1000.0 With such a system, a |
I've considered those options, too. I agree they are not perfect. That's why I suggested a different option in my first comment.
Why not integrate a subset of the preference system to the module loader in |
I can't comment on the feasibility of what we can do to change up what |
My understanding is that the stale check is mostly done in the Julia function |
We talked about this on the Pkg call, and we are going to be talking to the compiler team next week about adding a way for precompilation to take sub-trees of the preferences dict and use them in the invalidation process. |
We spoke with the compiler team, and we got the go-ahead to implement parsing of TOML files ( |
Could that be made more generic (perhaps a module-level The particular use-case is for MPI we typically want whatever MPI has been loaded via |
Hmmm. I don't think we can trigger recompilation after the module has already been loaded; this would be fixed-functionality, it's not running arbitrary code. What is |
No, it's a system like http://modules.sourceforge.net/, which modifies local environment variables (typically
depending which MPI build you wanted. If you configure MPI.jl with |
Is it too un-ergonomic to ask that a single top-level |
I would have to think about it more, but from a usability perspective, i would say yes. Basically, any preference that depends on some state that is mutable from outside the package manager is likely to have this problem, e.g. if I set my The second best option would be a way to have a function that lets you invalidate the cache from within const VERSION = libversion()
function __init__()
if libversion() != VERSION
Base.invalidatecache(@__MODULE__)
error("libfoo version has changed, please restart Julia")
end
end |
Wouldn't it be better if users just write down the modules and the project they use in their script? module load openmpi/4.0.3
export JULIA_PROJECT=PATH/TO/Project.toml It sounds like a good practice for reproducibility. But checking the library compatibility in |
Only if they have access to the same cluster |
Isn't it a good practice to record what you use in each cluster? |
@staticfloat Given that JuliaLang/julia#37595 is merged and we (will) have https://github.com/JuliaPackaging/Preferences.jl, I think we can close it now? |
There are a number of packages that are ad-hoc storing content within
~/.julia/prefs
. Storing some kind of "preferences" or "options" is a generally useful thing, and as we march closer and closer to a reality where we can set package directories themselves to be completely read-only, we will need a place where users can store user configuration that is specific to their own package.To that end, this introduces
Pkg.Preferences
, a simple API to serializeDict
objects to TOML and bring them back. This is a minimal interface that is meant to be used in a very straightforward manner, to be used by projects in doing things such as allowing the user to set which execution backend a project should use, etc...A logical extension of this would be to allow overlaying of the global depot preferences (which this PR implements) with per-project values. A
Project.toml
could contain a section that defines aDict
for any UUIDs within its manifest, then we recursively merge that dict into the base depot preferences dict. This would allow for powerful per-project customization of packages within the project.This is the last piece that I foresee us needing before we can contemplate turning package directories to be completely read-only. Between Artifacts, Scratch spaces and Preferences, I think we can build a very compelling story for controlled state management.