-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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 code loading support for Preferences
package
#37595
Conversation
bd9b73a
to
94efe75
Compare
base/loading.jl
Outdated
# check that project preferences match by first loading the Project.toml | ||
active_project_file = Base.active_project() | ||
if isfile(active_project_file) | ||
preferences = get(parsed_toml(cache, active_project_file), "preferences", Dict{String,Any}()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if I missed earlier discussion on this. But wouldn't this behave a bit confusingly when you stack environments?
Suppose I have LOAD_PATH = ["@", "@v#.#", "@stdlib"]
and some preferences for SomeREPLUtilities.jl in ~/.julia/environments/v1.6/Project.toml
. If I activate $PWD/Project.toml
and then evaluate using SomeREPLUtilities
, does it mean the preferences in ~/.julia/environments/v1.6/Project.toml
would be ignored? How about looking at all project files in Base.load_path()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should only check the project file where SomeREPLUtilities
is found I guess. If you need local preferences for that package you should probably add the package to the local project.
Edit: In fact, perhaps we should explicitly disallow adding preferences for packages not in the project?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, good point. I agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's purposeful that it only checks the top-level project file. We decided against any kind of merging for a few reasons:
-
Reproducibility; if a Project.toml somewhere high up in a chain is effecting things, then simply saving the
Project.toml
andManifest.toml
that you normally do would not be enough to guarantee identical results later on; we want all the configuration stored in one place. -
Performance; we don't want to have to touch a bunch of files. This can be ameliorated with caches, of course, but it's nice that we only have to read in one file.
-
Merging complexity; since we're storing full-on
Dict
's here, what happens if a higher-up and lower-down Projects have conflicts around key mappings?
I'm torn on adding preferences for packages not in the project; it makes sense to disallow it, but it's also harmless.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I favor disallowing the inclusion of preferences for packages not in the project simply because allowing it allows a[two] non-involved package[s] to carry preferences that may conflict were that[those] package[s] to appear in the project at a future time. It works against clear communication in collaboration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @tkf: If you need preferences on B
in Y
you can just add B
such that it exists in the Project.toml
file (#37595 (comment)).
I agree that it will be difficult to get a clean env though. But perhaps it is simple enough to play around with the LOAD_PATH then? I for one quite often use export JULIA_LOAD_PATH=$(mktemp)
when I need to play around with a clean env.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, it does become a bit messy; when you load A
and thus B
is loaded implicitly, should you really go look for deps for B
lower in the load path in a project that might have a different version of B
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this manner, the behavior of
using B
is already changing based on transitive dependencies.
Yeah, I agree that's an issue. But isn't it an issue somewhat orthogonal to preferences? I think it'd be better to only support the case where the manifests are compatible and explicitly state that otherwise, the behavior is undefined. I think it's better to solve the issue with incompatible manifests with something like #32906.
But I can see that combining two manifests can change the version of a package in a manner incompatible with the stored preferences.
If we can change how the package resolution works in a stacked environment, how about
- When looking for a package to load, first try stacked
Project.toml
files. This way, the package version explicitly added by the user takes precedence over the indirect dependencies in the environments above. - Allow preferences to be set only when the package is added by the user (i.e., in
deps
). - For a given package, use the first
Project.toml
that contains the package as a dependency. If there are no preferences, use the default. (This is my implementation in Add code loading support forPreferences
package #37595 (comment))
I think the combination of these three ingredients ensure that preferences and package version are compatible while allowing the scenario I described in #37595 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any update on this? I think falling back to the lower environment stack is a key feature. I think one of the important use-cases for CompilePreferences.jl is to capture machine-specific configuration such as system libraries and hardware properties. For this, ~/.julia/environments/v1.6/Project.toml
sounds like a good place to store such information. It doesn't make sense to share Project.toml
with machine-specific values.
I think I need this for PyPreferences.jl JuliaPy/PyCall.jl#835. If it is decided that it is not appropriate to add this to CompilePreferences.jl, I think I'll create SharedPreferences.jl to handle this use-case (e.g., by storing the preferences in ~/.julia/sharedpreferences/$UUID/v$major.toml
and call include_dependency
on it). I'll look up per-project preferences first via CompilePreferences.jl and then fallback to SharedPreferences.jl. The fact that it is implementable outside Base
could mean that not adding this in Base
/CompilePreferences.jl is a good idea. But having many ways to store preferences may be confusing. I'm not sure what's the best approach here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opened an issue for this discussion #37791
94efe75
to
cdd868f
Compare
There's another small design point I want to bring up; right now, all preferences stored will force recompilation. That may not be desirable in all cases, perhaps a package wants to store something that is only used at runtime (and hence does not need recompilation) but still wants it bundled into the One possibility is that we store all the compilation-time preferences under the |
IMO some system like that seem unnecessary complicated -- is it really so bad to reprecompile even though it will not change the result? |
Only if there is an undesired cost to that reprecompilation .. and the run-time only preferences are changing at a rate that exacerbates the disutility of that cost. Is that something that is likely to occur now and again? |
How about adding an interface like |
Well, I can imagine there are users that want to do things like store ML model hyperparameters, or plotting backend (I'm assuming changing that can be done completely at runtime), or whatever. There are all sorts of useful things to store, (and even ones that you might want to communicate to others with your An alternative approach would be to write a second package that just gives a nice interface to |
I just got another question from a user that wanted to know if they should use I suggest re-naming this to |
Maybe instead of |
For storing software states like a welcome message, I think it's not ideal to contaminate user's Project.toml file. Isn't Scratch.jl the best way to do it? But I think I agree separating compile-time and run-time key-value store is a clean design.
Brain-storming other names: |
Some other ideas: PkgConfig, PackageConfiguration, ProjectConfig[uration], ProjectSettings |
I argue the only reason anyone should ever use this stdlib is because they (as a package developer) need to worry about compiletime vs runtime. Anyone who doesn't care about the difference between runtime and compiletime either should use a different package (like I think keeping the term
I'm not so hot on |
I think |
cdd868f
to
c81d14f
Compare
We don't talk about that anymore. In fact, I don't even know what that is. Looks like some random file in a random non-special directory. ( ;) ) |
I also like to focus this package on compile-time preferences. However, some questions: For non-compile-time preferences, how does a user pass the preferences to the package? It's not via the project file so does the package have to write such an API then? Also, perhaps there are non-compile time preferences that you want to persist when you move your project to another machine. How would those be transferred? |
Sorry to mention the file-must-not-be-named :) If we don't want to talk about it, I think
I thought [deps]
PkgA = "342fba16-3e17-4664-b1bb-a60ccdbe268d"
[compile-preferences.342fba16-3e17-4664-b1bb-a60ccdbe268d]
compile_time_preference = "value"
[runtime-preferences.342fba16-3e17-4664-b1bb-a60ccdbe268d]
runtime_preference = "value" |
9bc5740
to
b0f5abe
Compare
Yes, I suggested this idea earlier and I think it's still my favorite idea. I've moved this PR to store things under the key |
466867b
to
e9e4d60
Compare
Alright, I think I've successfully implemented the desired functionality here; the preferences are now loaded from the first environment that knows what the given UUID is, and saving preferences goes to that same location, defaulting to the currently-active project in the event that you're using a UUID that you've never add'ed before. This seemed safer to me than throwing an error, as it means that if users start add'ing/removing packages they're less likely to get strange errors, and it enables the usage of things like preferences with |
Preferences
standard libraryCompilePreferences
standard library
Wouldn't a name like |
There will be another package named Even if the name |
I started playing with this branch to see if I can do what I had in mind for PyCall.jl. So far I think it's working as expected: Edit: I noticed that TOML file has strange formatting JuliaPy/PyCall.jl#835 (comment). It's not super important ATM but would be nice to fix it before 1.6 is out: # <blank line>
[compile-preferences.cc9521c6-0242-4dda-8d66-c47a9d9eec02]
python = "python3.8"
[deps]
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
PyPreferences = "cc9521c6-0242-4dda-8d66-c47a9d9eec02" i.e., a blank line before |
I didn't really understand why two packages are needed. In my opinion (and as was discussed on the triage call), all preference stuff can live in the same package, it doesn't have to be a stdlib, code loading just know that it has to recompile if things under |
I think many preference setup currently done with environment variables can be handled by "runtime-Preferences.jl" package. I don't think it's a good idea to re-compile packages when changing such runtime preferences. Grepping
(But depending on how the merging of runtime-Preferences.jl work, some of them may not be appropriate.) A potential benefit of using runtime-Preferences.jl is that we can provide much better UI for configuring these values (e.g., using TerminalMenus.jl).
I agree that CompilePreferences package itself does not have to be in stdlib. |
Totally, agree but why does that require two packages? Regarding your examples, I also don't think those really belong in a project file. They are more system-wide than that. Also, #2716 is highly relevant. |
Ah, I think I see what you are getting at. Yeah, it should be fine as long as you can determine whether a given preference in the project is for compile-time or not. It's more about toml file schema than the fronted package(s). |
The only slight annoyance is that the user has to know whether the preference is a compile-time or not. It would be nice if that is up to the package. |
Isn't the idea that you use API functions for setting the variables? E.g. the examples here: https://github.com/JuliaLang/julia/pull/37595/files#diff-153066f1b4b0aa6caf26959fc7f1ce57 |
keep it simple [while powerful and etc], so its easier for many to use |
as a transitive dependency) will be the one that is searched in for preferences. | ||
""" | ||
function load_preferences(uuid::UUID) | ||
prefs = Dict{String,Any}() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This variable seems to be unused.
Yeah, I think the API functions would be the primary way to tweak the preferences. If you have something like [compile-preferences.342fba16-3e17-4664-b1bb-a60ccdbe268d]
compile_time_preference = "value" figuring out the UUID by hand is tedious. |
Being super lazy and not checking myself, are "global" preferences also available, for example
which is then a shared namespace among the packages. |
I originally had those stored within a "global" UUID of |
This adds the calculation, serialization and verification of preferences hashes at code loading time. Preferences, as stored by the forthcoming `Preferences.jl` package within a top-level `Project.toml` file, are parsed by the `dump.c` and `loading.jl` code loading machinery and used to provide a compile-time preferences machinery.
e9e4d60
to
b4e4324
Compare
CompilePreferences
standard libraryPreferences
package
I've modified this PR to only include the As a quick taste, it has two sets of bindings, one through |
When preferences were first added, we originally did not have any preference merging across load path [0]. We later added that [1], but retained the requirement that for each individual element in the load path, preferences must have an entry in `Project.toml` listing the relevant package. This was partly on purpose (immediately binding the name to the UUID prevents confusion when a UUID<->name binding is not readily apparent) and partly just inheriting the way things worked back when preferences was written with just a single Project.toml in mind. This PR breaks this assumption to specifically allow an entry in the Julia load path that contains only a single `LocalPreferences.toml` file that sets preferences for packages that may or may not be in the current environment stack. The usecase and desire for this is well-motivated in [2], but basically boils down to "system admin provided preferences that should be used globally". In fact, one such convenient method that now works is to drop a `LocalPreferences.toml` file in the `stdlib` directory of your Julia distribution, as that is almost always a part of a Julia process's load path. [0] #37595 [1] #38044 [2] JuliaPackaging/Preferences.jl#33
EDIT: This PR has now changed to add only the code-loading portion.
This commit adds the
Preferences
standard library; a way to store aTOML-serializable dictionary into top-level
Project.toml
files, thenforce recompilation of child projects when the preferences are modified.
This pull request adds the
Preferences
standard library, which doesthe actual writing to
Project.toml
files, as well as modifies theloading code to check whether the preferences have changed.