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

bundlerEnv: add platforms + support gemspec #23000

Closed
wants to merge 6 commits into from
Closed

Conversation

nyarly
Copy link
Contributor

@nyarly nyarly commented Feb 19, 2017

Motivation for this change

Ruby Gemfiles can use a gemspec directive, which includes the dependencies of the gem specification into the Gemfile. For this to work, the contents of the gem directory need to get copied into the gemfile-and-lockfile derivation so that bundler can determine those dependencies. (Yes, the gempsec files often include code from arbitrary places in the gem (e.g. to determine the version of the gem), so it has to be all the files.)

Furthermore, Gemfiles can specify that certain gems are only needed when running on certain Ruby VMs (e.g. rubysdl-* is needed on Rubinius), which can lead to broken Nix builds if we try to bring in all the gems mentioned in the Gemfile.

Finally, this supports the use of "path" gem sources. These should probably not be allowed in distributed gems but are very handy for gem development. They allow a Gemfile to refer to a gem on the local filesystem (including the actual "root" gem for the Gemfile - so this is a requirement of gemspec Gemfiles as well.) This is useful when developing two closely related gems e.g. a rspec or rails plugin, where tweaking the host gem is helpful for debugging.

Things done
  • Tested using sandboxing
    (nix.useSandbox on NixOS,
    or option build-use-sandbox in nix.conf
    on non-NixOS)
  • Built on platform(s)
    • NixOS
    • macOS
    • Linux
  • 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/)
  • Fits CONTRIBUTING.md.

@mention-bot
Copy link

@nyarly, thanks for your PR! By analyzing the history of the files in this pull request, we identified @abbradar, @zimbatm and @manveru to be potential reviewers.

@nyarly
Copy link
Contributor Author

nyarly commented Feb 19, 2017

I maybe chose a difficult first PR - bundlerEnv is (mostly) an intermediate builder for lots of gems, so e.g. it doesn't have any ./result/bin of its own, but plenty of other outs rely on it working correctly. I can confirm it continues to work with jekyll and now works with shex, and I'm interested in sticking around to see this merged.

Not every gem package uses pname, nor should it.
Copy link
Member

@zimbatm zimbatm left a comment

Choose a reason for hiding this comment

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

Great! I left a few comments but overall it looks good so don't get discouraged by them and don't feel obligated to implement all of them.

!(attrs ? "platforms") ||
builtins.any (platform:
platform.engine == ruby.rubyEngine &&
(!(platform ? "version") || platform.version == ruby.version.majMin)
Copy link
Member

Choose a reason for hiding this comment

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

a note to myself: make sure these are the right version that are being matched. On MRI is't 1-1 but not on jruby for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Certainly there'll need to be a mapping from Gemfile platform version to NixOS ruby.version.majMin. I'm not sure the two should be identical. At the moment, Bundix doesn't yet include this information in the gemset.nix (although that's what I'm up to now...) so it's not pressing.

I wonder if the best solution isn't to provide a mapping function in bunder-env itself?

gems = lib.flip lib.mapAttrs configuredGemset (name: attrs: buildGem name attrs);

maybeCopyAll = { usesGemspec ? false, ...}@main:
(if usesGemspec then ''
Copy link
Member

Choose a reason for hiding this comment

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

nit: parenthesis not needed


maybeCopyAll = { usesGemspec ? false, ...}@main:
(if usesGemspec then ''
cp -a ${gemdir}/* $out/
Copy link
Member

Choose a reason for hiding this comment

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

nit: this can be a single-line string

'';

envPaths = lib.attrValues gems ++ lib.optional (!hasBundler) bundler;

binPaths = if mainGem != null then [ mainGem ] else envPaths;
binPaths = if mainGem != null then [ mainGem ] ++ envPaths else envPaths;
Copy link
Member

Choose a reason for hiding this comment

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

can you explain the reasoning behind that change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The summary is: I think the requirements here are ambiguous and I needed this interpretation.

My primary motivation here has been in cases where I'm developing a gem or ruby app, and so I want to use bundlerEnv in shell.nix. In that case, the Gemfile often includes a bunch of gems that are incltuded for use in development - and if there's an executable in the main gem, I want to be able to run it as well.

This may be in tension with the case where we're installing a ruby app with nix-env, so we don't want to pull in e.g. rspec as a executable. (As an aside, IMO if you're distrubing a ruby executable, then you distribute a gem, so nixpkgs should use buildRubyGem... but that's not even 100% from a ruby-community standpoint.)

I haven't confirmed this, but there's also the significant use case of shipping Rails apps, and they absolutely assume a git repo checked out and bundled in place, so there's definitely a use case for bundlerEnv for applications and therefore avoiding collisions with rspec. That said though, those rails apps will have bin directories with the same set of names...

The upshot is probably that the conditional here shouldn't be "mainGem != null" - but I don't know what the conditional should be.

Copy link
Member

Choose a reason for hiding this comment

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

The issue is that this will link bundler into the bin/ folder. And this will introduce conflicts when two projects are going to be installed.

The runtime (or nix-shell) should have access to $GEM_HOME/bin where all the runtime dependencies are installed. Or we need another layer for the wrapped gem bins.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As I understand this better: why would we add any executable apart from the main gem's? If I were foolish enough to nix-env -i a library gem whose Gemfile brought in Rake or RSpec, we'd have the same problem, right?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah we shouldn't link any binaries unless they are part of the main gem.

There is a bit of transition issue because the pname attribute wasn't there initially so it was not possible to know what was the main gem.

});
in
if gemAttrs.type == "path" then pathDerivation gemAttrs
else buildRubyGem gemAttrs);
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice if this could be pushed into the gem derivation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I debated that. I'm still soft on my understanding of how path-gems should get handled, which led me to hedge a little.

out = res;
outputName = "out";
};
in res;
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit confused by this, shouldn't it be wrapped in a derivation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem being solved here is the use of gems that are on the local filesystem e.g. not in the nix store. So this wrapper is a kind of fake derivation to make that possible.

One thing I'm not sure how to do, but I think needs to be added is that these "derivations" need to taint their dependent - it can be used as a shell.nix, but there's no way it should be put into nixpkgs like this.

There's one exception here, and that's the mainGem, because its files (should) get copied into the nix store as part of gemfile-and-lockfile.


maybeCopyAll = main: if main == null then "" else copyIfUseGemspec main;

copyIfUseGemspec = { usesGemspec ? false, ...}@main:
Copy link
Member

Choose a reason for hiding this comment

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

This is also true in the cases where gem "name", path: "some/dir" is being used. I think we can make the assumption that the path should be under the same folder.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Re-reading this, I actually think that assumption is likely to be wrong - at least in my experience it would be. I've most needed to use the Gemfile :path => ... syntax when I've had two gems that were tightly coupled (e.g. rspec plugins) and needed to get them to "meet in the middle" on some issue.

However, in a "production" situation, path gems being siblings in a git repo is a very fair assumption.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Of course, if I'd read with a little more context...

Yes, any mainGem that has a path source is going to need everything copied in. Furthermore, because of how Bundler handles gemspec in the first place, the two kinds of gems are indistinguishable (Bundler parses a Gemfile/lock with gemspec as if it were gem "name", :path => ., effectively.)

@nyarly
Copy link
Contributor Author

nyarly commented Feb 19, 2017

@zimbatm Not discouraged by comments! And not unwilling to make changes, but I'd like to make sure I'm headed in the right direction with them.

`usesGemspec` no longer required to trigger the "copy everything into
gemfile-and-lock" behavior. If the mainGem is referred to by path,
that's sufficient.
@nyarly
Copy link
Contributor Author

nyarly commented Feb 21, 2017

@zimbatm - I'd like to hash out some of the details of this; I'm in UTC-8, though, and I'll be in transit tomorrow morning, which looks like our best overlap.

@zimbatm
Copy link
Member

zimbatm commented Feb 22, 2017

@nyarly yeah good idea. I will be away for a bit. Can you send me a calendar invite that would work between 10 am to 10 pm UTC-0 starting Wed next week?

@zimbatm
Copy link
Member

zimbatm commented Mar 10, 2017 via email

@nyarly
Copy link
Contributor Author

nyarly commented Mar 13, 2017 via email

@zimbatm
Copy link
Member

zimbatm commented Mar 23, 2017

Feel free to send me an email so we can setup a time :)

The bin stubs need to be built where there's access to /nix/store - so
it can't happen in a nix-shell run. Ergo, a shell.nix needs to be able
to signal to the build that all bins need to be built.
@nyarly
Copy link
Contributor Author

nyarly commented Apr 9, 2017

Following discussion with @zimbatm and some reflection of my own, I'm closing this in favor of a to-come PR.

We agreed that the existing bundlerEnv was too complex, primarily because it's trying to serve too many purposes. The use cases we identified were:

  • "tool" gems, which from a Nix user's perspective are just another executable, like TaskJuggler or Jekyll.
  • Rails apps, which need to set up an execution environment, but not put things into the path, like GitLab.
  • nix-shell development environments for either of the above, plus library gems.

All three of these general use cases were using the same bundlerEnv expression, and as a result it's accreted a lot of configuration over time.

Rather than contribute further to that weird configuration, the plan is to produce separate expressions for different use cases, ideally with common behavior split out into its own expression.

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

4 participants