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

[RFC 0033] [WIP] Deprecation #33

Closed
wants to merge 24 commits into from
Closed

Conversation

infinisil
Copy link
Member

@infinisil infinisil commented Sep 8, 2018

Adds a very powerful function for easy deprecation and potentially a handful of automated tools to support it in the future. Instead of removing an attribute which would result in an uninformative attribute missing error for users, one can use said function to deprecate the value instead, which will first warn the user with a message, and in a later release throw an error. It is also possible to define a future deprecation date, up to which point no warning messages will be issued by default, unless the user enables them. Additionally the user will be able to fully control which warnings should get shown. This can be used for the standard library, packages, aliases, functions, package sets, everything in nixpkgs. Here is an example use of the current implementation:

{
  foo = lib.deprecate {
    year = 2018;
    month = 8;
    attrPath = [ "foo" ];
    reason = "Was replaced with bar";
    value = "foo";
  };
}

Evaluating foo will evaluate to just "foo" in the 18.03 release, evaluate with a warning in the 18.09 release, and throw an error in any future releases. The warning will look like this:

trace: WARNING: pkgs.foo is deprecated since 18.09 and will be removed in ~19.03: Was replaced with bar
"foo"

Rendered

main implementation branch

extra implementation branch (see Extra section)

Currently looking for a co-author. @Ericson2314 has volunteered to be co-author \o/

@infinisil infinisil changed the title [RFC 0033] Deprecation (Looking for co-author) [RFC 0033] [WIP] Deprecation (Looking for co-author) Sep 8, 2018
@Ericson2314
Copy link
Member

I'll co-author (I'll be fairly busy until Sunday, just so you know).

@Ericson2314
Copy link
Member

A few notes in the phone.

  1. Also need a way to deprecate parameters, sort of the opposite.

  2. Should have structured field for replacement expression. It maybe cab be used by refactor tools someday.

  3. I am running into problems where on the top level nix-env and the tarball job spew warnings (and in the latter case fail) because the deprecated thing gets force. We do want to check that the deprecated thing evals, but we don't want to fail nixpkgs because it's meer existence (as opposed to use.)

The alias.nix ones could use this. @matthewbauer I believe tried to warn on them in the past.

config.allowAliases is neat. It would nice to make deprecated things into assertion failures or disappear entirely. But lib doesn't get config.

@infinisil
Copy link
Member Author

infinisil commented Sep 8, 2018

Also need a way to deprecate parameters, sort of the opposite.

Yeah I have this mentioned in it, I have the intention of doing this as well, but the main focus (for now) is attribute deprecations, as they are most common.

Should have structured field for replacement expression. It maybe cab be used by refactor tools someday.

See the first paragraph of the Future work section. That index I'm mentioning there could be including deprecation information of attributes.

I am running into problems where on the top level nix-env and the tarball job spew warnings (and in the latter case fail) because the deprecated thing gets force. We do want to check that the deprecated thing evals, but we don't want to fail nixpkgs because it's meer existence (as opposed to use.)

Doesn't it do some error handling? Maybe it runs into an abort, which can't be caught, see NixOS/nixpkgs#45637

config.allowAliases is neat. It would nice to make deprecated things into assertion failures or disappear entirely. But lib doesn't get config.

See https://github.com/Infinisil/rfcs/blob/deprecation/rfcs/0033-deprecation.md#configuration, I have implemented a way to pass config to lib (based on NixOS/nixpkgs#38522), implementation here

@infinisil
Copy link
Member Author

@Ericson2314 Oh and thanks for co-authoring! I think @samueldr was also showing interest, but considering he's already release manager he probably has enough to do with that :P

@samueldr
Copy link
Member

samueldr commented Sep 8, 2018

You're right, I was insisting this be contributed, and in another timeline I would probably have looked at co-authoring! Alas, time is a limited resource!

@infinisil infinisil changed the title [RFC 0033] [WIP] Deprecation (Looking for co-author) [RFC 0033] [WIP] Deprecation Sep 9, 2018
@samueldr
Copy link
Member

What could be done, is the prototype for the user-facing API can be made in nix. Once it feels right, it can be implemented as a language feature, and you get backward compatibility for the current nix versions by keeping the prototype implementation around until minver allows use of the new feature. (Unless the new language feature is backported to older nix releases for compatibility.)

@oxij
Copy link
Member

oxij commented Sep 11, 2018 via email

@Ekleog
Copy link
Member

Ekleog commented Sep 11, 2018

@oxij There appear to be more or less consensus on the $ operator here: NixOS/nix#1845 :) still needs implementation, though.

For your prototype of lib.deprecate, I must say I like it better than @infinisil's, just because naming releases makes more sense to me than raw dates.

For log functions… well, I think we should discuss that in a separate RFCs, as RFCs where everyone does not agree just never get merged, and the discussion is complex enough as it is :)

@infinisil
Copy link
Member Author

@oxij Thanks for the effort! While you have some points, and I admit that I might went overboard a bit, I raise some counterpoints:

and its implementation in Infinisil/nixpkgs@deprecation are overly complicated: you spend like 80% of the code there computing dates.

Yes indeed, it is a complicated implementation, but ultimately this doesn't matter much. What matters is usability and UX, the end user couldn't care less about code complexity.

I'll argue that the notion of "unsupported in releases after deprecation" makes a lot of sense, we want to warn for e.g. one release and generally don't really care about when that release happens. If this were date-based, we'd have to assume that nixpkgs always follows an exact 6-month release cycle, which wasn't always the case in the past and might not be always the case in the future. Have a look at the examples in this section as well.

Regarding using a year/date vs a release for specifying the deprecation time: There isn't any difference, both are release-based in the end. I just implemented it with integers because it reduces the number of invalid inputs. I could change that, but then it requires string parsing to get the same features.

My implementation also makes sure that every deprecation will fail eventually and the user always informed when that happens. The people adding the deprecation don't have to be bothered adding such a date themselves. That's one thing I find very important, it ensures that deprecation warnings actually have a meaning, users can't ignore them without eventually having to deal with it.

The issue with hydra and warnings is then trivial to solve by setting config.traceLevel = ERROR; for hydra.

That just fixes a symptom of the problem, the problem is that nix-env -qa outputs all traces, which is very ugly, see NixOS/nixpkgs@8e8e23de33#commitcomment-12972032. The fact that hydra is failing is just because there is a check in nixpkgs to prevent distributing a tarball that has nix-env -qa warnings, I linked it earlier. This problem isn't fixable through nixpkgs.

lib.configVersion (<= lib.trivial.version) is the version you want it feel like.

I don't think it makes sense to support this:

  • If one allows the user to emulate the evaluation at a future release, they might make adjustments for planned deprecations, because it looks like they're final. This would violate the idea of "once you see a deprecation warning, it's sure to be deprecated". Users might even think of setting this setting to something far in the future to always tell them all deprecations straight ahead, which would take all the magic out of planned deprecations.
  • It doesn't make sense to set this value to something in the past: Things in the past might not be there anymore. You can always go forward in time by removing deprecated values, but this doesn't work in the opposite direction.

On lower level, I think, Nixpkgs should simply get log levels, i.e. lib.log : Level -> Bool -> Message -> a -> a that inspects config.traceLevel and config.throwLevel before doing trace or throw, with over functions implemented in its terms like so:

I like the idea of having different log levels, but I don't think it makes sense to support the ERROR level. These are log levels, throw doesn't log anything and fails evaluation. The problem with this is that it allows users to ignore deprecation failures by just setting config.throwLevel = 2000. This doesn't make any sense, because failures should be there for signifying that the value is not accessible anymore. My implementation makes sure that there is no way to allow access to the value if the time of it being not supported anymore has come. This then allows removing the value.

So while I like the idea of log levels, the other things you suggested don't sound very thought through. I'm going very much into detail into the desired properties of deprecation in my RFC and make sure that those are consistent and make sense.

@infinisil
Copy link
Member Author

Really, deprecation has nothing to do with log levels though, and using log levels to show/hide deprecation warnings is just wrong. You wouldn't call gcc with -v to enable/disable deprecation warnings. Rather you set these --Wno-warn-foo flags.

@oxij
Copy link
Member

oxij commented Sep 11, 2018 via email

@infinisil
Copy link
Member Author

@oxij Sorry for the delay. My short opinions:

  • Implementation complexity: Agree, I'll make a future version not as complicated
  • Using version strings instead of dates: Agreed as above point
  • configVersion: Good idea for NixOS config, but I don't think it should be used for deprecation
  • planned deprecations: Agreed, not supporting them seems alright
  • Eventual deprecation: Disagree, this is one of the main points I want deprecation, to be able to eventually remove stuff without annoyed users, removing the cruft

I may went a bit overboard with the writing and the implementation though haha, it taught me a lot though, I've never thought so thoroughly about something, and I'll probably use these thoughts in future tools for nixpkgs (see bottom section), there's lots of cool stuff to do.

Will make another draft in the future. Might take a bit of time though, there's many things to do that are more interesting than this PR, and my free time is limited :).

Regarding NixOS/nixpkgs#46530, I'd appreciate if we could reach consensus here first before intending to make any changes, so it would be nice if you could mark that PR as non-final or something.

@oxij
Copy link
Member

oxij commented Oct 19, 2018 via email

@infinisil
Copy link
Member Author

Do you want another knob? Why? Can you elaborate?

I don't think it makes sense to allow this. Let's say that lib.trivial.version = "19.03", and foo is defined as follows since 19.03:

{
  foo = lib.deprecate "has been removed";
}

Now what should happen if I set configVersion = "18.09"? It can't travel back in time to get a previous value to be compatible.

So you say "we just don't remove it and keep it there to be backwards compatible", so let's do that:

{
  foo = lib.deprecate "has been removed" "but here's the value in order to stay backwards compatible";
}

So Paul has his configVersion set to 18.09 (because his config version is in that state), updates his nixpkgs to 19.03 and rebuilds it without problems, no warnings or errors appear because in version 18.09 foo wasn't deprecated. The problems with this:

  • We've deprecated something, but nobody even noticed
  • We need to keep all dependencies of the value there, which can get very problematic with e.g. Haskell stuff.

So how about "let's issue a warning only when it has been deprecated for a certain amount of time". Because configVersion doesn't really change automatically, we need to use the difference of the deprecation date and lib.trivial.version to implement that, at which point configVersion doesn't do anything. If we use configVersion for it then it doesn't really throw any warning unless the user updates it explicitly.

So you say "Let's make it such that the user can't lag too far behind with their configVersion so they have to update it and get the warnings". Okay we implement that by throwing an error at the start of nixpkgs evaluation with an assertion lib.trivial.version - confiVersion <= 12 months. With this what happens is that users "lag behind" on deprecations. When users update to "20.03", they'll first get an error that they need to update their configVersion to at least "19.03", which was "18.09" before. So they update it and then have to deal with the deprecation warnings from a year ago (but none of the newer ones). In essence this means configVersion would just be tugged along lib.trivial.version with some wiggle room. This means we can't deprecate something quickly, it always needs to go through a 12 month time where users are may be totally unaware of it, which doesn't seem like something desirable with deprecations.

I just don't see how this could work without giving weird and undesired behaviour all around.

A solution based on lib.trivial.version by going from silent to warning to error over time makes a lot of sense and doesn't have any of the problems mentioned above.

@oxij
Copy link
Member

oxij commented Oct 19, 2018 via email

@Profpatsch
Copy link
Member

I would be willing to be a co-author for this RFC (after it’s been simplified).

@shlevy
Copy link
Member

shlevy commented Jan 17, 2019

Closing all draft RFCs. Please feel free to reopen when this is ready for general community review and the RFC shepherd process.

@Profpatsch
Copy link
Member

This comes up again and again, here ofborg and nixos-unstable were broken by nix-env when adding a warning to a deprecation in aliases.nix:
NixOS/nixpkgs@9de29f6

We should find a solution.

@worldofpeace
Copy link
Contributor

Hehe, I'm here as well for this RFC 👋.
I've taken to even complain about the issue https://mastodon.social/web/statuses/104583330471946338
If someone isn't up for the kinda lengthy RFC process (I hope we can fix that someday to have a time constraint) I do have a tiny checklist of improvements that someone could handle outside of an RFC process https://gist.github.com/worldofpeace/4c8b238241ae6c79adce1707cddf0544 (the third point basically requires this RFC though). I find this to be a very very important situation to handle, and even the slightest improvement will be appreciated by anyone who chooses to create it for everyone ✨ For every gift given returns to the sender 10 fold.

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

Successfully merging this pull request may close these issues.