-
Notifications
You must be signed in to change notification settings - Fork 39
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
Filter away unset output attributes #59
Conversation
…hello`) This in addition also strips away unset attributes set using `flake`. The core issue with IFD in this context, is that we want to strip away unused attributes. AFAICT, flake-parts did this in a buggy way before. Removing this entirely while fixing support for IFD was easier, but to keep support for filtering away unset attributes, a bit more complexity was needed. The issue is that we can't use `null` as a special value to mean "unset". The reason is that while filtering over `allSystems.${system}`, checking whether `packages` has been set, doing `null == packages` will cause `packages`, an attribute set, to be evaluated to WHNF. WHNF for an attribute set entails evaluating the _keys_, and if the keys are generated using IFD, then this will fail, even if we know that no matter what the key is, this check will return false. If the Nix evaluator is ever fixed, this feature can be somewhat cleaned up by using null checks rather than checking `isDefined`. To make use of this feature, you can not simply do ```nix packages = import drv; ``` You must do ```nix packages = mkOverride 100 (mkOrder 1000 (import drv)); ``` The reason is that the module system internally will try to check `._type`, but this of course will cause all of the other keys to also be evaluated. This would also be unnecessary if the Nix evaluator were fixed. Closes hercules-ci#57
Good analysis of the strictness issue.
The code was purely wishful thinking that I've glossed over. It didn't work, but I don't think it was a problem. By adding more
I don't think the operational semantics were specified to include laziness at such a fine granularity, so calling this change (whatever it is) a fix would be an exaggeration. |
I'm using this code in practice here https://github.com/mlabs-haskell/mlabs-tooling.nix/blob/las/work/flake.nix#L156 The extra filterAttrs that runs on the output of the module evaluation just evaluates all top-level attributes to WHNF, and I think that's fine since that should be system-independent in the first place. The older code not working wasn't a problem (ignoring IFD), but it seemed to me that you intended to not set attributes that the user didn't mean to set, and I think that would be a very useful features. For most flakes, exposing an empty nixosModules makes little sense. |
While that was my intent, I am not convinced that it is important. It wouldn't be the first time that a project starts with some bad, redundant code.
The other day, I answered a strictness question from someone who was doing something like I agree on the fact, but I can't act on it; especially because the infinite recursion errors are as bad as they are, and when the goal is for the project to be general. |
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 don't think I can make this change happen, but I hope you can appreciate the ideas in my comments.
# - Doesn't handle undefined options in submodules. | ||
# - Doesn't handle undefined options that have a prefix, e.g. `_module.args`. | ||
# TODO: Upstream a good version of this to Nixpkgs, probably as an extra result | ||
# from `evalModules`, e.g. `result.configFiltered`. |
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.
We should be careful about adding attributes. They're not like methods that have no per-object overhead.
# and only check whether they're defined. Hence we can't check for nullness, | ||
# because that will evaluate a lot of stuff unnecessarily and cause errors | ||
# with IFD. A more proper fix would perhaps be fixing the Nix evaluator to | ||
# short circuit more, specifically, in an expression like `null != { ${x} = ...; }`, |
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 requires a Value
to have an intermediate state between thunk and attrset, and it will cause make the !=
operator asymmetric. Neither is terrible in itself, but nix
has to strike a balance. If you want to make this happens, please contribute to the test suite or other quality control measures such as combining boehmgc and c++ sanitizers.
# short circuit more, specifically, in an expression like `null != { ${x} = ...; }`, | ||
# `x` should not be validated, since it's a set regardless. | ||
# | ||
# In general though, Nix's evaluator sucks. |
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.
Not very constructive, is it?
I don't think the code here makes it any worse than it was before. |
mkIfNonEmptySet | ||
(mapAttrs | ||
(k: v: v.apps) | ||
(filterAttrs | ||
(k: v: v ? apps) | ||
config.allSystems | ||
)); |
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 technique makes attributes such as devShell.x86_64-linux.default
depend on checks.aarch64-darwin
. (To pick a highly unrelated example)
You can see this when you define
systems = [ "x86_64-linux" "aarch64-darwin" ];
perSystem = { system, ... }: {
checks = if system == "aarch64-darwin" then throw "not ok" else {};
}
and then instantiate devShell.x86_64-linux.default
.
Usually checks.<system>
isn't too slow or fragile, but that's not an assumption we can make. Also strictness can lead to infinite recursions, which are horrible.
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.
Thanks, yes, optimally this shouldn't happen.
What is your reason to filter attributes with empty value? |
Not taking a stance on whether this should or shouldn't be handled upstream, but couldn't we already use the disabledModules = ["nixosConfigurations.nix" "nixosModules.nix" "darwinModules.nix" "legacyPackages.nix"]; for a project-specific dev shell that doesn't make sense for it to deal in NixOS/Darwin modules or nixpkgs-style output trees? |
@shanesveller That does seem possible, thanks for informing me. However, this still seems more ergonomic. @roberth Because it blows up the size of the output and adds a bunch of unnecessary information to e.g. nix flake show. Any time someone wants to type some output attribute, it bloats up every flake using flake parts more. |
The only remaining benefit of this is that the attributes are truly not there, meaning tab-completion in nix repl won't see them either. I'm not sure whether that's worth it. |
@shanesveller's suggestion still holds some potential. I think the |
…hello`)
This in addition also strips away unset attributes set using
flake
.The core issue with IFD in this context, is that we want to strip away unused attributes. AFAICT, flake-parts did this in a buggy way before. Removing this entirely while fixing support for IFD was easier, but to keep support for filtering away unset attributes, a bit more complexity was needed.
The issue is that we can't use
null
as a special value to mean "unset". The reason is that while filtering overallSystems.${system}
, checking whetherpackages
has been set, doingnull == packages
will causepackages
, an attribute set, to be evaluated to WHNF. WHNF for an attribute set entails evaluating the keys, and if the keys are generated using IFD, then this will fail, even if we know that no matter what the key is, this check will return false.If the Nix evaluator is ever fixed, this feature can be somewhat cleaned up by using null checks rather than checking
isDefined
.To make use of this feature, you can not simply do
You must do
The reason is that the module system internally will try to check
._type
, but this of course will cause all of the other keys to also be evaluated.This would also be unnecessary if the Nix evaluator were fixed.
Closes #57