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

Package Options #458

Closed
ChrisRackauckas opened this issue Jul 3, 2018 · 17 comments
Closed

Package Options #458

ChrisRackauckas opened this issue Jul 3, 2018 · 17 comments

Comments

@ChrisRackauckas
Copy link
Member

There are many cases where a package developer may want a user to be able to set package-wide options. For example, look at the NaN handling in ForwardDiff.jl

http://www.juliadiff.org/ForwardDiff.jl/latest/user/advanced.html#Fixing-NaN/Inf-Issues-1

In order to preserve performance in the majority of use cases, ForwardDiff disables this check by default. If your code is affected by this NaN behvaior, you can enable ForwardDiff's NaN-safe mode by setting the NANSAFE_MODE_ENABLED constant to true in ForwardDiff's source.

As of right now, the method that people are using is telling users to dirty their repo and modify a constant in the package code. That's pretty far from ideal. Another way this is sometimes done is via an environment variable which then sets a constant in a build, like in PyCall.jl:

ENV["PYTHON"] = "... path of the python program you want ..."
Pkg.build("PyCall")

which then saves a const in deps.jl. It seems that one way for users to do the ForwardDiff thing and have it be more approachable would be to have a fake package build that saves environment variables.

The last thing that can be done is globals can be set in __init__() which are then used in the package. But those would either be non-const or if they are const they would be in a Ref. So it's not good for something that would change dispatches like a type.

Solution

Of course, all of these are less than ideal. It would be nice to have one pre-compile-time options setup for packages that is query-able and used across the ecosystem. For example:

Pkg.options("ForwardDiff") # Prints an options doc
Pkg.set_option("ForwardDiff",:NANSAFE_MODE_ENABLED,false)

which then sets a constant in an options.jl file.

The reason it would be nice to have this baked into the package system is because then packages can directly do this for dependencies in the same namespacing manner that is done for normal dependency handling. There could be a spot in the manifest to set specific options. In some sense this is kind of like the options one would set in a Makefile.

@StefanKarpinski
Copy link
Sponsor Member

I have a different, more declarative solution in mind, which I can write up and we can discuss.

@tpapp
Copy link
Contributor

tpapp commented Jul 3, 2018

Perhaps related: SciML/DiffEqBase.jl#94 which prompted #176. This could also be handled by some mutable metadata that is associated with a package.

Also, I think that the UI would also have to specify UUIDs, since package names no longer identify a package.

@ChrisRackauckas
Copy link
Member Author

Another thing that would be useful would be the ability to do it right before using. While most of the time I think doing it at the package and REQUIRE level is good, there are a few cases where I want to override such a mechanism. For example, I have a global setting in DiffEqBase.jl that allows someone to drastically reduce the amount of compilation time at the expense of specialization. I would like that to be an option. However, in OrdinaryDiffEq.jl's tests I would like to turn it off (but not in normal OrdinaryDiffEq.jl usage! At least not without the user's consent). So I would like to do:

Pkg.set_option("DiffEqBase",:RECOMPILE_MODE,false)
using DiffEqBase

in the tests (or whatever other syntax to do something similar).

@StefanKarpinski
Copy link
Sponsor Member

So the approach I had imagined was having [config.SomePackage] sections in the Project.toml file of an application, e.g.:

name = "MyApplication"

[deps]
SomePackage = "<uuid>"
OtherPackage = "<uuid>"

[config.SomePackage]
frobulate = true
tweak = 12.34

[config.OtherPackage]
backend = "Gtk"

This would not be allowed in packages since there are many packages and their configuration could conflict with each other whereas there is only one application (by definition, since it's the top-level project that's using packages). The open questions then would be how this configuration would be exposed to packages. There could also be an ability to do programmatic configuration in Julia code, but it seems better for configuration to be declarative as much as possible. Having a standard way to override configuration options from the command line would also be useful.

@tpapp
Copy link
Contributor

tpapp commented Jul 9, 2018

I find it nice to have a project-specific configuration. This would mean that different projects would be able to specify different setups.

I wonder about the following:

  1. I guess the API would expose this to packages, so that they could query the active configuration via some function in Pkg.

  2. Write access is not that important, but it would be nice if packages could acquire the path of the relevant Project.toml, so they could print messages like

    You have not set option Foo.barhandling, assuming Foo.barhandling = supersafe.
    Override this as
    
    [config.Foo]
    barhandling = notsosafe
    
    in path/to/Project.toml.
  3. For interactive use, would pkg> activate path/to/something establish new configuration bindings? Would they get merged to the current ones, or replace them? Also, this may invalidate earlier assumptions that some package made when it was loaded.

Hope these make sense, I don't know much about loading/Pkg3 internals.

@StefanKarpinski
Copy link
Sponsor Member

All good questions.

@tpapp
Copy link
Contributor

tpapp commented Jun 16, 2019

Reviving this issue: I am wondering about just putting configuration files in

joinpath(first(DEPOT_PATH), "config")

with a filename based on the package (to rule out clashes), eg

~/.julia/config/MyPackage.toml

This seems feasible (first depot should be writeable) and could emerge as a de facto solution until further refinements.

OTOH, some packages are using joinpath(DEPOT_PATH[1], "prefs"), eg IJulia with .julia/prefs/IJulia.

Generally, it would great to settle on something recommended soon, instead of ad-hoc solutions emerging.

@StefanKarpinski
Copy link
Sponsor Member

That’s roughly what I was thinking of as well. The questions in my mind are about putting additional configuration in projects and how configuration should be passed to packages. Via some global CONFIG variable? Via arguments to __init__?

@KristofferC
Copy link
Sponsor Member

For reproducibility, the configuration should ideally be in the Project file as proposed earlier. I guess there could be a global setting file but I wonder how easily it will be to accidentally depend on that.

@tpapp
Copy link
Contributor

tpapp commented Jun 17, 2019

@KristofferC: what happens if the authors change the Project.toml and I pull a new version? I tend to think of package project files as something that the user ideally would not edit.

Also, while reproducibility is a valid concern, I tend to think of this kind of configuration as storing preferences for utilities that may not need to be reproducible in the strict sense, or would error anyway. Eg the template to use in a package generation tool, the path of some binary, etc.

@StefanKarpinski: I guess it would be sufficient to say that all files matching $(DEPOT_PATH[1])/config/ThatPackage.* contain information related to a package which it manages as it pleases, and then defining an environment variable (up to a package, suggested: THATPACKAGE_CONFIG) can be used to override this.

Generally, I think some kind of a recommended best practice about the place of a user-writable file (that the package code can also update, eg by presenting an UI to the user) would be enough at this point.

@StefanKarpinski
Copy link
Sponsor Member

StefanKarpinski commented Jun 17, 2019

Package's cannot have any configuration in them—the point of a package is that it is reusable and if something is reusable, it can't configure other pieces of reusable code or there will be conflicts in configuration. Only one thing can have configuration which is the main application. That configuration could be a composite of configuration from all of the environments in the load path though, or it could be required that it all be explicitly in the main application project file.

@tpapp
Copy link
Contributor

tpapp commented Jun 17, 2019

Package's cannot have any configuration in them

Practically many do, either by

  1. various files in the filesystem (eg IJulia as mentioned above, or what I want to do),
  2. environment variables (paths etc)
  3. various settings in deps, usually autogenerated during build, but mutable if necessary.

it can't configure other pieces of reusable code or there will be conflicts in configuration

I am not sure I understand this. It does not need to configure other pieces of code, just itself. Eg think of the path of an external executable.

@StefanKarpinski
Copy link
Sponsor Member

A package configuring itself is just providing defaults. If the package is the only thing that can configure itself, then there's no configuration at all.

Eg think of the path of an external executable.

Yes, that's a good example. What happens if C has a path option and packages A and B both depend on C and try to configure C's path option but they configure it differently? Which one wins? That's why only the main application can provide configuration. Packages can provide their own defaults.

@tpapp
Copy link
Contributor

tpapp commented Jul 2, 2019

What happens if C has a path option and packages A and B both depend on C and try to configure C's path option but they configure it differently? Which one wins? That's why only the main application can provide configuration.

I thought about this and I see your point. But I still think that a "global", user-provided default makes sense.

To make things concrete, consider Julia packages using Stan via the stanc executable. We kind of semi-standardized on path being available in ENV["JULIA_CMDSTAN_HOME"], which most users initialize in their startup.jl. While one can imagine scenarios where one would want to use a specific version of Stan in various projects (eg breaking changes in Stan syntax), most users are fine with the latest one.

What I am asking for is a place to store user-provided, global defaults for information more complex than one would want to put in environment variables.

@StefanKarpinski
Copy link
Sponsor Member

If we don't want to sacrifice reproducibility, we would want to copy defaults into the project file when we add configurable dependencies and then allow people to overwrite that configuration. But there are some options that will probably need to be configured by the end-user on different systems.

@tkf
Copy link
Member

tkf commented Sep 14, 2019

FYI I implemented a proof-of-concept: #1378

@tkf
Copy link
Member

tkf commented Sep 28, 2020

Given that JuliaLang/julia#37595 is merged and we (will) have https://github.com/JuliaPackaging/Preferences.jl, I think we can close it now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants