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

buildRubyGem: fix to support bundler install --redownload #104977

Merged
merged 1 commit into from Nov 30, 2020

Conversation

fzakaria
Copy link
Contributor

@fzakaria fzakaria commented Nov 26, 2020

Motivation for this change

The way in which Nixpks builds Ruby gems means that certain operations by bundler will not work, namely bundle install --redownload.

According to the source the cache/ directory should have been kept, however it seems through revisions to the file it has been purged.

Here was the comment from the original commit that introduced buildRubyGem (still present in the source)

# Note:
# We really do need to keep the $out/${ruby.gemPath}/cache.
# This is very important in order for many parts of RubyGems/Bundler to not blow up.
# See https://github.com/bundler/bundler/issues/3327
installPhase = attrs.installPhase or ''

Why is the /cache directory needed?

Bundler and RubyGems uses the cache as a source of truth. When bundler executes bundler install --redownload, any gems it
discovers in the GEM_PATH it assumes must have their .gem file present in the cache (unaware it was installed from Nix).

Rather than downloading the gem from RubyGems the bundler code forcibly re-installs the gem from the cache directory instead and fails if it does not exist.

I've opened rubygems/rubygems#4088 to see if this failure should be soft and not so explicit; or fallback to fetching the gem from scratch.

Without this change the following is the error:

[nix-shell:~/code/nix/playground/jruby-bundler-rake]$ bundle install --redownload
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.jruby.ext.openssl.SecurityHelper (file:/nix/store/fis6nzrpw9pmcivr84qh5byfgm07qn10-jruby-9.2.13.0/lib/ruby/stdlib/jopenssl.jar) to field java.security.MessageDigest.provider
WARNING: Please consider reporting this to the maintainers of org.jruby.ext.openssl.SecurityHelper
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Fetching gem metadata from https://rubygems.org/.
Using bundler 2.1.4
Installing hello-world 1.2.0
Bundler::GemNotFound: Could not find hello-world-1.2.0.gem for installation
An error occurred while installing hello-world (1.2.0), and Bundler
cannot continue.
Make sure that `gem install hello-world -v '1.2.0' --source
'https://rubygems.org/'` succeeds before bundling.

Wth the fix the following no error occurs:

[nix-shell:~/code/nix/playground/jruby-bundler-rake]$ bundle install --redownload
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.jruby.ext.openssl.SecurityHelper (file:/nix/store/69wjlj4yirp48rv1q03zxgd4xvf0150d-jruby-9.2.13.0/lib/ruby/stdlib/jopenssl.jar) to field java.security.MessageDigest.provider
WARNING: Please consider reporting this to the maintainers of org.jruby.ext.openssl.SecurityHelper
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Fetching gem metadata from https://rubygems.org/.
Using bundler 2.1.4
Installing hello-world 1.2.0
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

Here we can see the /cache directory hydrated.

[nix-shell:~/code/nix/playground/jruby-bundler-rake]$ ls -l /nix/store/cwl9n5073hqgpfhnw4wic13nrrgg9dn8-gem-env/lib/jruby/gems/2.5.0/cache/
total 8
lrwxrwxrwx 1 fmzakari primarygroup 102 Dec 31  1969 bundler-2.1.4.gem -> /nix/store/ifc8a0gsfkrhkv953rd4rz8bcspahi8y-bundler-2.1.4/lib/jruby/gems/2.5.0/cache/bundler-2.1.4.gem
lrwxrwxrwx 1 fmzakari primarygroup 110 Dec 31  1969 hello-world-1.2.0.gem -> /nix/store/xi9ln6n1mz2is5ppykjxqhhkpjq9zm6i-hello-world-1.2.0/lib/jruby/gems/2.5.0/cache/hello-world-1.2.0.gem

I have a minimal project that demonstrates this issue at https://github.com/fzakaria/jruby-bundler-nix-failure however it boils down to the following shell.nix file

let nixpkgs = import <nixpkgs> {};
in with nixpkgs;
with stdenv;
with stdenv.lib;
let gems = buildEnv {
  name = "gem-env";
  pathsToLink = ["/lib" "/bin" "nix-support"];
  paths = [
    (bundler.override {
      ruby = jruby;
    })
    (buildRubyGem rec {
      ruby = jruby;
      name = "${gemName}-${version}";
      gemName = "hello-world";
      version = "1.2.0";
      source.sha256 = "141r6pafbwjf8aczsilxxhdrdbbmdhimgbsq8m9qsvjm522ln15p";
    })

  ];
};
in
mkShell {
  name = "jruby-bundler-shell";
  buildInputs = [ jruby ];
  shellHook = ''
      export GEM_NIX_ENV=${gems}/${jruby.gemPath}
      export GEM_USER_DIR=$(pwd)/.gem
      export GEM_DEFAULT_DIR=$(${jruby}/bin/ruby -e 'puts Gem.default_dir')
      export GEM_PATH=$GEM_DEFAULT_DIR:$GEM_PATH:$GEM_NIX_ENV
      export GEM_HOME=$GEM_USER_DIR
      export PATH=$GEM_HOME/bin:$GEM_NIX_ENV/bin:$PATH
  '';
}
source "https://rubygems.org"

gem 'hello-world', '1.2.0'
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 using nix-shell -p nixpkgs-review --run "nixpkgs-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)
  • Ensured that relevant documentation is up to date
  • Fits CONTRIBUTING.md.

CC @cstrahan @zimbatm @alyssais @manveru

@RaghavSood
Copy link
Member

Please base this on staging instead of master, mass rebuilds should target staging so that they don't hold up channel builds

@fzakaria fzakaria changed the base branch from master to staging November 26, 2020 06:25
@fzakaria
Copy link
Contributor Author

fzakaria commented Nov 26, 2020

@RaghavSood thanks for the feedback; changed the target branch to staging

Others looking at the PR may wonder the motivation:
At my employer I use a mix of installing some gems via Nix through buildEnv however we still rely on bundler for many gems.
(incremental transition)

Gems which exist in the Gemfile (direct or transitive) that bundler wants to install that were also installed via Nix succumb to the error described in the description above.

@zimbatm
Copy link
Member

zimbatm commented Nov 27, 2020

I had a quick look and it makes sense. The main downside is that it will increase all the program's closure sizes, even those that are not being used for development. Can this be enabled with a flag instead?

@fzakaria
Copy link
Contributor Author

@zimbatm -- I can add a flag.
I feel inclined to make the case that the flag however should default to "yes".
The original comments and goal of buildRubyGem is to build the gems in a way that is consistent with Bundler.
Having it default yes aligns closer to that goal.

Copy link
Contributor Author

@fzakaria fzakaria left a comment

Choose a reason for hiding this comment

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

@zimbatm thank you for looking at this!

I've taken your advice and added a comment.
In the spirit of getting this merged faster I've kept the default "false" although my personal opinion is it should largely be true by default.

🙏

# such as `bundler install --redownload`.
# At the cost of increasing the store size, you can keep the gems to have closer
# alignment with what Bundler expects.
, keepGemCache ? false
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@zimbatm actually decided to keep it default false largely because I rather see this merged sooner
(trying to upstream my personal fixes).

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good

Comment on lines 216 to 221

# Do not remove the cache as this is needed to make some additional Bundler commands work
# such as `bundler install --redownload`
# https://github.com/rubygems/rubygems/issues/4088
# https://github.com/bundler/bundler/issues/3327
# rm -fvr cache
Copy link
Member

Choose a reason for hiding this comment

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

If you move this comment out of the bash script, it won't cause a mass rebuild and can target the master branch directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks -- took your suggestion.
I'm curious, how does staging branch make it to master?

The way in which Nixpks builds Ruby gems means that certain operations
by bundler *will not work*, namely `bundle install --redownload`.

According to the source the _cache/_ directory should have been kept,
however it seems through revisions to the file it has been purged.

Here was the comment from the original commit that introduced
buildRubyGem:
```
  # Note:
  #   We really do need to keep the $out/${ruby.gemPath}/cache.
  #   This is very important in order for many parts of RubyGems/Bundler to not blow up.
  #   See rubygems/bundler#3327
```

Why is the _cache_ directory needed?

Bundler and RubyGems uses the cache as a source of truth.
When bundler executes `bundler install --redownload`, any gems it
discovers in the _GEM_PATH_ it assums must have their _.gem_ file
present in the cache (unaware it was installed from Nix).

Rather than downloading the gem from RubyGems the bundler code forcibly
re-installs the gem from the cache directory instead and **fails** if it
does not exist.

I've opened rubygems/rubygems#4088 to see if
this failure should be soft and not so explicit; or fallback to fetching
the gem from scratch.

Without this change the following is the error:
```bash
> [nix-shell:~/code/nix/playground/jruby-bundler-rake]$ bundle install --force
[DEPRECATED] The `--force` option has been renamed to `--redownload`
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.jruby.ext.openssl.SecurityHelper (file:/nix/store/fis6nzrpw9pmcivr84qh5byfgm07qn10-jruby-9.2.13.0/lib/ruby/stdlib/jopenssl.jar) to field java.security.MessageDigest.provider
WARNING: Please consider reporting this to the maintainers of org.jruby.ext.openssl.SecurityHelper
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Fetching gem metadata from https://rubygems.org/.
Using bundler 2.1.4
Installing hello-world 1.2.0
Bundler::GemNotFound: Could not find hello-world-1.2.0.gem for installation
An error occurred while installing hello-world (1.2.0), and Bundler
cannot continue.
Make sure that `gem install hello-world -v '1.2.0' --source
'https://rubygems.org/'` succeeds before bundling.
```

Wth the fix the following no woccurs:
```bash
[nix-shell:~/code/nix/playground/jruby-bundler-rake]$ bundle install --redownload
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.jruby.ext.openssl.SecurityHelper (file:/nix/store/69wjlj4yirp48rv1q03zxgd4xvf0150d-jruby-9.2.13.0/lib/ruby/stdlib/jopenssl.jar) to field java.security.MessageDigest.provider
WARNING: Please consider reporting this to the maintainers of org.jruby.ext.openssl.SecurityHelper
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Fetching gem metadata from https://rubygems.org/.
Using bundler 2.1.4
Installing hello-world 1.2.0
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
```

```
[nix-shell:~/code/nix/playground/jruby-bundler-rake]$ ls -l /nix/store/cwl9n5073hqgpfhnw4wic13nrrgg9dn8-gem-env/lib/jruby/gems/2.5.0/cache/
total 8
lrwxrwxrwx 1 fmzakari primarygroup 102 Dec 31  1969 bundler-2.1.4.gem -> /nix/store/ifc8a0gsfkrhkv953rd4rz8bcspahi8y-bundler-2.1.4/lib/jruby/gems/2.5.0/cache/bundler-2.1.4.gem
lrwxrwxrwx 1 fmzakari primarygroup 110 Dec 31  1969 hello-world-1.2.0.gem -> /nix/store/xi9ln6n1mz2is5ppykjxqhhkpjq9zm6i-hello-world-1.2.0/lib/jruby/gems/2.5.0/cache/hello-world-1.2.0.gem
```

I have a minimal project that demonstrates this issue at https://github.com/fzakaria/jruby-bundler-nix-failure
@zimbatm zimbatm merged commit 4af8bc0 into NixOS:master Nov 30, 2020
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

3 participants