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

stdenv: make symlinks that refer to the same output relative #74253

Merged
merged 1 commit into from Jan 15, 2020

Conversation

andir
Copy link
Member

@andir andir commented Nov 26, 2019

Motivation for this change

While looking at the graph of all the outputs in my personal binary
cache it became obvious that we have a lot of self references within the
package set. That isn't an isssue by itself. However it increases the
size of the binary cache for every (reproducible) build of a package
that carries references to itself. You can no longer deduplicate the
outputs since they are all unique. One of the ways to get rid of (a few)
references is to rewrite all the symlinks that are currently used to be
relative symlinks. Two buildd of something that didn't really change but
carry each a self-reference can the be stored as the same NAR file again.

I quickly hacked together this change to see if that would yield and
success. My bash scripting skills are probably not great but so far it
seem to somewhat work.

Things done
  • Tested using sandboxing (nix.useSandbox on NixOS, or option sandbox in nix.conf on non-NixOS linux)
  • Built on platform(s)
    • NixOS
    • macOS
    • other Linux distributions
  • Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
  • Tested compilation of all pkgs that depend on this change (only all of those in the tested set)
  • Tested execution of all binary files (usually in ./result/bin/)
  • Determined the impact on package closure size (by running nix path-info -S before and after)
  • Ensured that relevant documentation is up to date
  • Fits CONTRIBUTING.md.
Notify maintainers

cc @Ericson2314

@andir
Copy link
Member Author

andir commented Nov 26, 2019

Are there valid use cases where this is not desirable? I was thinking of providing an escape hatch if there are any...

@7c6f434c
Copy link
Member

7c6f434c commented Nov 26, 2019 via email

Copy link
Contributor

@B4dM4n B4dM4n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concept sounds nice, but I found two issues in the bash script.

Are there valid use cases where this is not desirable? I was thinking of providing an escape hatch if there are any...

I can definitely see issues with scripts that expect an absolute path from readlink and will be broken by this change, so being able to disable this hook would be nice.

local relativeTarget
while IFS= read -r -d $'\0' f; do
symlinkTarget=$(readlink "$f")
if [ "${symlinkTarget:0:${#prefix}}" != "$prefix" ]; then
Copy link
Contributor

@B4dM4n B4dM4n Nov 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check has the potential to produce false negatives when two outputs share the same prefix:

/nix/store
├── 1111-out
│  └── file -> /nix/store/1111-out2/1234
└── 1111-out2
   └── 1234

In this case /nix/store/1111-out/file would be rewritten to file -> 1234.

Suggested change
if [ "${symlinkTarget:0:${#prefix}}" != "$prefix" ]; then
if [[ "$symlinkTarget"/ != "$prefix"/* ]]; then

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! While that technically should never happen within /nix/store that might be a good safeguard.

I'll add that to the next version.

Comment on lines 16 to 20
relativeTarget="${symlinkTarget:${#prefix}}"
echo "rewriting symlink $f to be relative to $prefix"

rm "$f"
ln -rs "$prefix/$relativeTarget" "$f"
Copy link
Contributor

@B4dM4n B4dM4n Nov 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ln destination should be "$prefix/$f".
Never mind, f is already absolute. Updated code block below.

To simplify the process this block can be replaced with these ln flags:

        echo "rewriting symlink $f to be relative to $prefix"
        ln -snrf "$symlinkTarget" "$f"

as ln -r produces the minimal possible relative link.

@7c6f434c
Copy link
Member

7c6f434c commented Nov 26, 2019 via email

@andir
Copy link
Member Author

andir commented Nov 26, 2019

If we are going to find all symlinks and resolve them, it would be nice to fail the build with a meaningful error message if the target (file or dir at the location a symlink points to) does not exist.
… and also to provide and ways way to opt out of this behaviour. I do have some uses for symlinking to nonexistent files; for example when something wants to write to some path that is in store (but can be redirected to a proper location via a symlink)

Please note that I am only checking for references into the same output. So in general those shouldn't be the problem. You can still create arbitrary symlinks to directories that do not exist as long as they do not point into the output. Would that work for you?

I feel like the enforcement of valid symlinks is yet another topic compared to what I wanted to accomplish here.

@7c6f434c
Copy link
Member

7c6f434c commented Nov 26, 2019 via email

@andir
Copy link
Member Author

andir commented Nov 27, 2019

I added some of the review comments. I removed the hard failure on dangling symlinks and just left the warning in there. Does that make sense?

I think failing on invalid symlinks is really a more complicated topic that we shouldn't try to solve right now. Warning is probably okay but failing is yet another different topic from this approach. We can add it to this hook in another PR once there is consensus about the desired behavior.

@andir
Copy link
Member Author

andir commented Nov 27, 2019

@GrahamcOfBorg build bash

@FRidh FRidh added this to Needs review in Staging Nov 30, 2019
@FRidh FRidh added this to the 20.03 milestone Dec 10, 2019
@FRidh
Copy link
Member

FRidh commented Jan 6, 2020

Can this go in?

While looking at the graph of all the outputs in my personal binary
cache it became obvious that we have a lot of self references within the
package set. That isn't an isuse by itself. However it increases the
size of the binary cache for every (reproducible) build of a package
that carries references to itself. You can no longer deduplicate the
outputs since they are all unique. One of the ways to get rid of (a few)
references is to rewrite all the symlinks that are currently used to be
relative symlinks. Two build of something that didn't really change but
carries a self-reference can the be store as the same NAR file again.

I quickly hacked together this change to see if that would yield and
success. My bash scripting skills are probably not great but so far it
seem to somewhat work.
@andir
Copy link
Member Author

andir commented Jan 6, 2020

@FRidh Looks good to me :-) If @Ericson2314 is now happy I see no reason why we can't merge this.

@FRidh FRidh requested a review from Ericson2314 January 6, 2020 12:06
@FRidh FRidh merged commit cb007e6 into NixOS:staging Jan 15, 2020
Staging automation moved this from Needs review to Done Jan 15, 2020
@andir
Copy link
Member Author

andir commented Jan 15, 2020

Thanks @FRidh !

@Ericson2314
Copy link
Member

Woo! Sorry I didn't have the time to vet this further myself. Thanks @FRidh and @andir

@@ -0,0 +1,28 @@
fixupOutputHooks+=(_makeSymlinksRelative)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wlroots has

wlroots> +++ find /nix/store/3a0xwszw8n5dzzhsgfnilvsqi4hk565s-wlroots-0.15.1-examples -type l -print0
wlroots> find: '/nix/store/3a0xwszw8n5dzzhsgfnilvsqi4hk565s-wlroots-0.15.1-examples': No such file or directory

because the examples output is created in postFixup while this hook runs in fixupPhase

if this is changed to postFixupHooks then we shouldn't get a error like that in many builds

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Successfully merging this pull request may close these issues.

None yet

6 participants