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

wrap-gapps-hook.sh: only wrap links when required #54909

Merged

Conversation

tollb
Copy link
Contributor

@tollb tollb commented Jan 30, 2019

Unless dontWrapGapps is set, the wrap-gapps-hook.sh will currently
wrap all executables (and symbolic links to executables) found under
the target directories: bin and libexec.

As a result, if a symbolic link in a target directory points to an
executable in a target directory, both will get wrapped. This
causes an extra shell/exec when following the symbolic link,
as well as increasing the size of the final executable's environment.

To avoid wrapping a link to an already wrapped executable, this
commit splits the determination of what gets wrapped into two phases:

  1. All binaries under the target directories are wrapped and logged
    with "Wrapping program ..."

  2. All links to executables under the target directories are
    identified and checked to see if they reference an executable
    under one of the target directories.

    If yes, the required wrapping has already been performed on
    the associated binary (in phase 1), so no wrapping is done
    and "Not wrapping link: ... (already wrapped)" is logged.

    If no, the link points at an executable that hasn't been
    wrapped, so the link is wrapped and "Wrapping link: ..." is logged.

As an example, the yelp package has a bin directory that contains
an executable "yelp" and a symbolic link "gnome-help" -> "yelp".

Prior to this commit, the bin directory would contain these files
after wrapping:

  gnome-help          -- wrapper to exec .gnome-help-wrapped
  .gnome-help-wrapped -- a symbolic link to yelp
  yelp                -- wrapper to exec .yelp-wrapped
  .yelp-wrapped       -- the original yelp binary

After this commit, the bin directory will instead contain:

gnome-help          -- a symbolic link to yelp
 yelp                -- wrapper to exec .yelp-wrapped
 .yelp-wrapped       -- the original yelp binary

NOTE: The primary motivation for this commit is to avoid obscuring
the fact that two or more paths are simple aliases and expected to
behave identically. It also reduces the likelihood of hitting
limits related to environment variable size.

LIMITATION: The method used above is intended to be conservative
and will still wrap symbolic links to other symbolic links when
the ultimate target is outside of bin or libexec.

  • Tested using sandboxing (nix.useSandbox on NixOS, or option sandbox in nix.conf on non-NixOS)
  • 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 using nix-shell -p nox --run "nox-review wip"
  • 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)
  • Assured whether relevant documentation is up to date
  • Fits CONTRIBUTING.md.

NOTE: Tested with nix-review. Used grep on logs to find packages with symbolic links that would no longer be wrapped after this commit. Here's a list:

package                         symbolic link -- no longer wrapped
===========================     ===========================================
cozy                            cozy-0.6.7/bin/cozy
emacs25                         emacs-25.3/bin/emacs
emacs                           emacs-26.1/bin/emacs
gksu                            gksu-2.0.2/bin/gksudo
gnome3.gedit                    gedit-3.30.2/bin/gnome-text-editor
gnome3.gucharmap                gucharmap-11.0.3/bin/charmap
gnome3.gucharmap                gucharmap-11.0.3/bin/gnome-character-map
gnome3.yelp                     yelp-3.30.0/bin/gnome-help
gnumeric                        gnumeric-1.12.44/bin/gnumeric
libreoffice-fresh-unwrapped     libreoffice-6.1.4.2/bin/libreoffice
libreoffice-still-unwrapped     libreoffice-6.0.7.3/bin/libreoffice
lumina                          lumina-1.4.0-p1/bin/lte
mate.mate-calc                  mate-calc-1.20.3/bin/mate-calculator
mate.mate-utils                 mate-utils-1.20.2/bin/mate-panel-screenshot
xfce.thunar-bare                thunar-build-1.6.10/bin/Thunar

sgtpuzzles                      [ 39 symbolic links not wrapped]

You can build any of these packages to test this commit. Also, there is a default.nix that can be used to test link wrapping more comprehensively here:
https://gist.github.com/tollb/4a21b890058ca3ab7d44618e345d0e28

find "${targetDirsThatExist[@]}" -type l -xtype f -executable -print0 \
| while IFS= read -r -d '' linkPath; do
linkPathReal=$(realpath "${linkPath}")
for targetPath in "${targetDirsRealPath[@]}"; do
Copy link
Member

Choose a reason for hiding this comment

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

Making targetDirsRealPath an associative array would be preferable here to avoid
quadratic complexity.

if [[ -n ${targetDirsRealPath[$linkpath]} ]]; then
   echo "Not wrapping link: '$linkPath' (already wrapped)"
   continue
fi

Copy link
Member

Choose a reason for hiding this comment

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

The alternative would be to memoize each wrapped programs once.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Mic92 Thanks for the review and for the nix-review program which proved very helpful in testing.

I'm not sure if an associative array will help here as the comparison at line 63 is intended to match any linkPathReal that starts with the prefix targetPath:

if [[ "$linkPathReal" == "$targetPath"* ]]; then

In the context of this script, targetDirsRealPath should have at most two elements, so performance should remain linear with respect to iterating over the symbolic links.

That said, your comment prompted me to review the code again and I think that there is a bug if neither /bin or /libexec exists in a package. I'll need to fix that!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Mic92 Thanks, again, for the review. I made the fix for when neither /bin or /libexec are present. I also switched to the staging branch as per @FRidh with the assistance of @grahamc.

@grahamc
Copy link
Member

grahamc commented Jan 30, 2019

@GrahamcOfBorg eval

@FRidh
Copy link
Member

FRidh commented Jan 30, 2019

this needs to target staging

continue 2
fi
done
echo "Wrapping link: '$linkPath'"
Copy link
Contributor

Choose a reason for hiding this comment

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

The indentation here is off.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jtojnar Thanks for the review. I made the indentation fix.

@jtojnar
Copy link
Contributor

jtojnar commented Jan 31, 2019

Other than that, LGTM.

Unless dontWrapGapps is set, the wrap-gapps-hook.sh will currently
wrap all executables (and symbolic links to executables) found under
the target directories: bin and libexec.

As a result, if a symbolic link in a target directory points to an
executable in a target directory, both will get wrapped.  This
causes an extra shell/exec when following the symbolic link,
as well as increasing the size of the final executable's environment.

To avoid wrapping a link to an already wrapped executable, this
commit splits the determination of what gets wrapped into two phases:

1. All binaries under the target directories are wrapped and logged
   with "Wrapping program ..."

2. All links to executables under the target directories are
   identified and checked to see if they reference an executable
   under one of the target directories.

   If yes, the required wrapping has already been performed on
   the associated binary (in phase 1), so no wrapping is done
   and "Not wrapping link: ... (already wrapped)" is logged.

   If no, the link points at an executable that hasn't been
   wrapped, so the link is wrapped and "Wrapping link: ..." is logged.

As an example, the yelp package has a bin directory that contains
an executable "yelp" and a symbolic link "gnome-help" -> "yelp".

Prior to this commit, the bin directory would contain these files
after wrapping:

  gnome-help          -- wrapper to exec .gnome-help-wrapped
  .gnome-help-wrapped -- a symbolic link to yelp
  yelp                -- wrapper to exec .yelp-wrapped
  .yelp-wrapped       -- the original yelp binary

After this commit, the bin directory will instead contain:

  gnome-help          -- a symbolic link to yelp
  yelp                -- wrapper to exec .yelp-wrapped
  .yelp-wrapped       -- the original yelp binary

NOTE: The primary motivation for this commit is to avoid obscuring
the fact that two or more paths are simple aliases and expected to
behave identically. It also reduces the likelihood of hitting
limits related to environment variable size.

LIMITATION: The method used above is intended to be conservative
and will still wrap symbolic links to other symbolic links when
the ultimate target is outside of bin or libexec.
@tollb tollb force-pushed the fix/wrap-gapps-hook_unnecessary_symlink_wrap branch from 2ed1a3c to bbb2f93 Compare February 1, 2019 01:18
@jtojnar jtojnar merged commit d42ef37 into NixOS:staging Feb 1, 2019
@jtojnar
Copy link
Contributor

jtojnar commented Feb 1, 2019

Thank you.

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

Successfully merging this pull request may close these issues.

None yet

6 participants