postmodern / ruby-install

Installs Ruby, JRuby, TruffleRuby, or mruby
MIT License
1.9k stars 252 forks source link

Feature request / advice (architecture based installation location) #413

Closed tenderlove closed 1 year ago

tenderlove commented 2 years ago

I upgraded to an M1, but I need to work with Rosetta sometimes. In order to accomplish that I've installed two versions of Homebrew, one that targets arm64e and one that targets x86_64. Then I taught Fish to fix the path depending on the architecture.

I'd like to do something similar with ruby-install, namely automatically change the install directory based on the target architecture. I was hoping to do that with an environment variable because I don't want to have to remember to check the architecture and then pass the right thing to --install-dir. However, it doesn't look like I can control the installation directory via ENV var.

First, does the "separate path per architecture" approach make sense? If so, does adding an ENV var seem like an acceptable solution?

If not, I was thinking of building universal binaries, but I think that will be its own can of worms especially if I (for example) link against OpenSSL in one Homebrew installation, but then switch to a different architecture and try to use the same Ruby.

Thanks for your time!

tenderlove commented 2 years ago

Just to be more concrete, I was thinking of having something like $HOME/.rubies/x86_64/ruby-* and $HOME/.rubies/arm64e/ruby-*, then have my shell set an env var that points at the right place.

postmodern commented 2 years ago

~/.rubies/<arch>/ seems a little too bespoke, and would cause issues with listing rubies from the directory; all other ruby switchers would need to filter out known architectures or having some kind of branching-logic to check if ~/.rubies/$(uname -m) exists. The only other standards I am aware of for this are Debian's MultiArch Implementation, Ubuntu's MultiArch Spec, and Fedora's mention of /usr/<target>/, all of which only focus on /usr. While I have wanted to transition away from ~/.rubies/ and switch to the more standardized XDG directories (ex: ~/.local/share/), the XDG specification makes no mention of storing executable software within their directories; maybe in the future XDG might support ~/.<target>/local/share?

We could easily add RUBY_INSTALL_* env variables for globally configuring ruby-install's default settings; or maybe even a configuration file? Then you could setup your shell's configuration to set the appropriate RUBY_INSTALL_DEFAULT_INSTALL_DIR based on the current $MACHINETYPE or uname -m. That seems like the quickest way to support multi-arch environments.

tenderlove commented 2 years ago

~/.rubies/<arch>/ seems a little too bespoke, and would cause issues with listing rubies from the directory; all other ruby switchers would need to filter out known architectures or having some kind of branching-logic to check if ~/.rubies/$(uname -m) exists.

I was thinking, since there's a way to teach chruby about installations from "other" switchers, I could (in my shell init file) do something like:

if test (arch) = "i386"
  RUBIES += (~/.rubies/x86_64/*)
else
  RUBIES += (~/.rubies/arm/*)
end

Adding something like ^^^ to my shell init doesn't seem like a big deal since I have to do it for Homebrew anyway. Maybe I have to change the target install base directory away from ~/.rubies as not to confuse chruby, but I'd be OK with that.

We could easily add RUBY_INSTALL_* env variables for globally configuring ruby-install's default settings;

This would work for me!

or maybe even a configuration file?

A config file seems like overkill to me, but it's up to you 😄

postmodern commented 2 years ago

I was thinking, since there's a way to teach chruby about installations from "other" switchers, I could (in my shell init file) do something like:

if test (arch) = "i386"
  RUBIES += (~/.rubies/x86_64/*)
else
  RUBIES += (~/.rubies/arm/*)
end

I would probably change that to RUBIES=(~/.rubies/<arch>/*), since chruby.sh will pre-populate RUBIES based on the contents of ~/.rubies/; and currently doesn't filter out the sub-directories which lack a bin/ruby executable file.

postmodern commented 2 years ago

Finally got around to adding support for the RUBY_INSTALL_SRC_DIR and RUBY_INSTALL_RUBIES_DIR env variables. Will be released in version 0.9.0. 43e837b3dff60163b21b70dd3262fbd6e0f7f147

monfresh commented 1 year ago

I'm trying out @tenderlove's setup and it's working great for installing rubies in different folders based on the arch. However, I haven't found an easy way to get gems to work in both architectures at the same time for the same Ruby version.

Here's an example:

  1. Switch to an Intel shell
  2. ruby-install ruby-2.7.6 --install-dir "/Users/moncef/.rubies/i386/ruby-2.7.6"
  3. chruby 2.7.6
  4. gem env => works
  5. Switch to an arm64 shell
  6. ruby-install ruby-2.7.6 --install-dir "/Users/moncef/.rubies/arm64/ruby-2.7.6"
  7. chruby 2.7.6
  8. gem env => fails with this error:
    
    /Users/moncef/.rubies/arm64/ruby-2.7.6/lib/ruby/2.7.0/yaml.rb:3: warning: It seems your ruby installation
    is missing psych (for YAML output).
    To eliminate this warning, please install libyaml and reinstall your ruby.
    ...

dlopen(/Users/moncef/.gem/ruby/2.7.6/gems/strscan-3.0.4/lib/strscan.bundle, 0x0009): tried: '/Users/moncef/.gem/ruby/2.7.6/gems/strscan-3.0.4/lib/strscan.bundle' (mach-o file, but is an incompatible architecture (have (x86_64), need (arm64e))) - /Users/moncef/.gem/ruby/2.7.6/gems/strscan-3.0.4/lib/strscan.bundle (LoadError)


Which makes sense because `~/.gem/ruby/2.7.6/` was first created under Intel, and I guess it doesn't get overwritten when installing the same Ruby version again with a different arch.

So, the question is, is it possible to specify different `~/.gem/ruby/*` directories based on the arch? 

My current workaround is to  delete the `~/.gem/ruby/{version}` every time I switch from one arch to the other, which means reinstalling gems. @tenderlove how are you dealing with this issue?
postmodern commented 1 year ago

@monfresh this problem is due to the fact that chruby originally tried to share gems between ruby minor version families (e.g. upgrading from ruby-2.7.0 to ruby-2.7.1 would re-use the gems from ruby-2.7.0), and assumed you would only be using one architecture. However, this approach has ran into multiple problems over the years (ex: rubygems using explicit #!/path/to/ruby for installed gem executables, TruffleRuby ABI changes, and now multi-arch issues). In an ideal world, rubygems would automatically handle these edge-cases for us, regardless of where GEM_HOME is pointed or it's current state. I will need to find spare time to start incrementally adding new features to chruby to allow overriding how GEM_HOME or GEM_PATH are set (or even allow them not to be set). Although, I can't make breaking changes to how chruby behaves by default until 1.0.0 which will have to come after 0.4.0, 0.5.0, etc, where new additional features can be user tested. There are 0.4.0 and 1.0.0 branch branches where I've been experimenting with new API ideas. Currently, I'm trying to wrap up another big refactor on another project (down to just 62 remaining issues) that I want to wrap up before EoY so it doesn't drag on indefinitely.

eregon commented 1 year ago

FYI one way to avoid all these issues is to use this branch of my fork: https://github.com/eregon/chruby/tree/do-no-set-gem-home That does not set GEM_HOME, which means each ruby has its own separate gems and there is never invalid gem sharing. The only requirement is you install your Rubies in a user-writable directory.

monfresh commented 1 year ago

@postmodern Thanks for the details!

@eregon Thanks, I'll try your branch.

tenderlove commented 1 year ago

My current workaround is to delete the ~/.gem/ruby/{version} every time I switch from one arch to the other, which means reinstalling gems. @tenderlove how are you dealing with this issue?

I'm not dealing with it well. I just rm the ~/.gem directory once in a while 😆

I think @eregon's solution would work fine, and I should probably do that

postmodern commented 1 year ago

I'm not dealing with it well. I just rm the ~/.gem directory once in a while

So I'm curious, and maybe it's been mentioned before in other issue threads, but why doesn't rubygems simply allow you to run gem pristin --extensions to build the missing C exts for the alternate architecture? It would be a less extreme workaround than rm -r ~/.gem/. Also seems like rubygems could use a more helpful error message when the built C extensions are missing.

lougreenwood commented 1 year ago

Finally got around to adding support for the RUBY_INSTALL_SRC_DIR and RUBY_INSTALL_RUBIES_DIR env variables. Will be released in version 0.9.0. 43e837b

@postmodern That's great news - any idea when 0.9 might be available? Thanks!

postmodern commented 1 year ago

@lougreenwood maybe next week? I also wanted to add additional environment variables like RUBY_INSTALL_PKG_MANAGER or RUBY_INSTALL_DOWNLOADER (curl or wget) (I ended up deciding against this one env variable).

postmodern commented 1 year ago

Closing this as I prepare for a 0.9.0 release. The RUBY_INSTALL_RUBIES_DIR environment variable have been added for configuring the default installation directory. If we want to add additional logic to automatically detect architecture-specific sub-directories (ex: ~/.rubies/aarch64/) based on uname -m (or if there's a better standardized directory within $HOME for storing multi-arch software), that can be added in 0.10.0.