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

feat(forge): add ruby gems backend #1657

Draft
wants to merge 1,842 commits into
base: main
Choose a base branch
from
Draft

Conversation

andrewthauer
Copy link
Contributor

@andrewthauer andrewthauer commented Feb 11, 2024

This adds an experimental ruby gems forge backend as discussed in here.

NOTE: This requires some additional custom handling to create a wrapper/shim file in order run gem binaries since ruby needs to know where the gem dependencies are installed (GEM_HOME). This pattern is currently modelled after how homebrew does this .

Example directory structure for gem-cocoapods:

├── bin  (generated by GemForge and sets GEM_HOME then calls libexec/bin/...)
│   ├── fuzzy_match
│   ├── httpclient
│   ├── pod
│   ├── sandbox-pod
│   └── xcodeproj
└── libexec
    └── bin   (auto-generated shebang `#!/home/my-user/.local/share/mise/installs/ruby/3.2.3/bin/ruby)
        ├── fuzzy_match
        ├── httpclient
        ├── pod
        ├── sandbox-pod
        └── xcodeproj
    ├── build_info
    ├── cache
    ├── doc
    ├── extensions
    ├── gems
    ├── plugins
    └── specifications

A few potential issues that might need better handling:

  • The gem install generated {install_path}/libexec/bin/{gem_executable} has a shebag to the mise ruby version that is currently activated when installing the gem:foo. I tried using --env-shebang which would use the preferrable #!/usr/bin/env ruby, but this breaks for some gems (e.g. cocoapods) if they use native compiled extensions. I haven't looked into this in depth, but I believe these extensions are installed into the ruby install rather then the gem install path. There may be some flags, etc. to workaround but I'm not sure atm.
  • Do to the above, removing the ruby version that was used to install the gem will break the gem entirely. There could also be other ways to break this.
  • The current implementation will create a bin wrapper for every gem dependency that was installed and has executables. This probably is not ideal as it might pollute the PATH in unwanted ways. I suspect it would be possible to create a function that parses the gemspec of the "installed gem" to determine the executables that gem provides and only create bin wrappers for those.

If anyone has other approaches or ideas, open for suggestions. I'm also a rust newbie, so the code could likely be improved.

jdx and others added 30 commits January 11, 2024 04:01
* Use ui::table for better formatting
* Allow viewing a single env var with `mise ev FOO`
* Added --global flag

See jdx#1432
Some ASDF scripts need ASDF_PLUGIN_PATH to reference patches or
other scripts. For compatibility, it is added to the environment
variables provided to executed plugin scripts.

Signed-off-by: Ryan Egesdahl <deriamis@gmail.com>
* settings: read from config.toml

* [MegaLinter] Apply linters fixes

---------

Co-authored-by: jdx <jdx@users.noreply.github.com>
Use `settings set activate_aggressive 1` to disable this change.

Fixes jdx#863
This reverts commit 332648d.
* Update nushell.rs - Add explicit spread

Running mise with nushell in the latest release of nushell (currently [0.89.0](https://www.nushell.sh/blog/2024-01-09-nushell_0_89_0.html#spread-operator-for-commands)) results in the following warning:

```shell
Error:   × Automatically spreading lists is deprecated
    ╭─[/hREDACTED:35:1]
 35 │   } else {
 36 │     ^"rtx" $command $rest
    ·                     ──┬──
    ·                       ╰── Spreading lists automatically when calling external commands is deprecated and will be removed in 0.91.
 37 │   }
    ╰────
  help: Use the spread operator (put a '...' before the argument)
```

This change fixes the warning.

* updated snapshots

---------

Co-authored-by: Jeff Dickey <216188+jdx@users.noreply.github.com>
…|env.mise.path"

I am still unsure what the final syntax will be, so this removes the
deprecation warning on the old env_file|env_path syntax. It also
allows using "env._.file|env._.path" instead of "env.mise.file|env.mise.path".
* cargo: added forge arg

* forge: added forge arg parsing

* forge: get double_tool_condition to work with new ToolArg

* forge: added ForgeArg to allow different backends

* fixed tests
e.g.: `mise use -g npm-prettier@3.2.0`
* skip slow cargo test if TEST_ALL is not set

* Commit from GitHub Actions (test)

---------

Co-authored-by: mise[bot] <123107610+mise-en-dev@users.noreply.github.com>
jdx and others added 24 commits February 9, 2024 16:52
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
// NOTE: Use `#!/usr/bin/env ruby` may cause some gems to not work properly
// using a different ruby then they were installed with. Therefore we
// we avoid the use of `--env-shebang` for now. However, this means that
// uninstalling the ruby version used to install the gem will break the
Copy link
Owner

Choose a reason for hiding this comment

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

I think this is unfortunately a dealbreaker since mise up would break all the installed versions.

That said, I also haven't yet thought of a better solution.

Maybe if we had a good way to tie the shim to the specific minor version of ruby?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could re-write the shebang of the gem created binstub after the gem install. Although, I can see cases where this still wouldn't be stable. The only 2 things I can think of that might be more stable are:

  • install a full ruby version along side the specific gem tool. This is far from ideal from a storage perspective.
  • Create some sort of dependency relationship between the runtime and gem tool. Then mise can be more intelligent about pinning and upgrading the required runtime.

Copy link
Owner

@jdx jdx Feb 12, 2024

Choose a reason for hiding this comment

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

what if we just replaced #!/home/my-user/.local/share/mise/installs/ruby/3.2.3/bin/ruby with #!/home/my-user/.local/share/mise/installs/ruby/3.2/bin/ruby if we found it in the shim?

that seems safe to do. I imagine there may be some edge cases if the version is specified in other places but it's a start.

This may be something that is different on macos and linux too. I think in general with macos processes report symlinked paths but on linux it gets fully canonicalized. So at least on macos I think as long as we execute ~/.local/share/mise/installs/ruby/3.2/bin/ruby and not the 3.2.3 version macos installs should get a fuzzy-version shim.

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 need to do some more testing, but I believe the gem install that does native compilation follows symlinks and bakes the require link to the patch level ruby runtime. That said, I think if we could trick the gem install context to use a stable runtime version alias like home/my-user/.local/share/mise/installs/ruby/3 and update the binstub shebang to match, this might work. That said, since it seems to follow symlinks, I'm not sure how we would trick the install process.

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 spent some time trying to understand the lower level plumbing of what gem install is doing. It seems that when gem a does a native extension compile is uses rake-compile to create a binary library which to has an absolute path to the ruby so/dylibs used by the gem install. I haven't been able to find any way to specify options to override this yet.

Copy link
Owner

Choose a reason for hiding this comment

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

I am pretty certain that if I run gem install with homebrew ruby it will link to the minor version of ruby so that if I run brew upgrade ruby@3.2 previously installed gems still work, I wonder what they're doing

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 think it's because they are invoking the gem install from the "installed" version which is object just ruby or ruby@3.2, etc. I thought I tried this, but I'll see If I run ~/.local/share/mise/installs/ruby/3/bin/gem install ... what that results in.

However, I'm not sure how I would have the forge normalize the install path consistently. Relying on the path and how someone has configured their ruby versions globally, etc. would impact this.

Copy link

Choose a reason for hiding this comment

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

Thinking outside the box a bit, could changes be made to Rubygems itself that would likely be acceptable to the maintainers and would simplify this in the long run?

Copy link

@pboling pboling Apr 6, 2024

Choose a reason for hiding this comment

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

There is no expectation that RubyGems continue to work, or even continue to be installed, after a ruby upgrade, and when they are it can break things.

We used to have persistent gem installs, long ago, when we used System Ruby. But System Ruby is literally unusable now (can't install gems), so we now always deal with Ruby from some other source.

It's possible that people installing Ruby from source have a similar scenario where gems might live across Ruby patch upgrades that are installing into the same path, but that's not a common scenario.

In general, every version of Ruby must have a set of gems that are installed uniquely to it, and they are never expected to be inherited by other installed Ruby versions.

Indeed if they were it would result in many bugs as each Ruby version installed can be linked against different versions of base packages, e.g. openssl, so the gems should not be transferrable. Additionally, the isolation is a useful feature, as we can have apps and libraries linked to an ecosystem within a single version of Ruby, and other apps and libraries linked to a separate version of Ruby.

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

Successfully merging this pull request may close these issues.

None yet