-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Don't wrap non-ruby executables and explicit no-wrap requests. #4069
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -474,6 +474,26 @@ def generate_windows_script(filename, bindir) | |||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| ## | ||||||||||||||||||||||||||||||||||||||
| # Checks if +bin_path+ should be wrapped if we're generating wrappers | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # If the first line of the file is a shebang check if it mentions ruby and | ||||||||||||||||||||||||||||||||||||||
| # return false if it doesn't (don't wrap non-ruby executable) | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # If the first line is not a shebang, check if it contains the magic string: | ||||||||||||||||||||||||||||||||||||||
| # "rubygems: no-wrap" and return false if it does (don't wrap if no-wrap was | ||||||||||||||||||||||||||||||||||||||
| # requested) | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # Otherwise wrap | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def wrappable_executable?(bin_path) | ||||||||||||||||||||||||||||||||||||||
| File.open bin_path, 'r' do |f| | ||||||||||||||||||||||||||||||||||||||
| line1 = f.gets | ||||||||||||||||||||||||||||||||||||||
| break line1['ruby'] if line1.start_with?('#!') # if the line is a shebang check for ruby | ||||||||||||||||||||||||||||||||||||||
| !line1['rubygems: no-wrap'] # check for magic string | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+492
to
+493
|
||||||||||||||||||||||||||||||||||||||
| break line1['ruby'] if line1.start_with?('#!') # if the line is a shebang check for ruby | |
| !line1['rubygems: no-wrap'] # check for magic string | |
| break true if line1.nil? # empty file, preserve existing behavior: wrappable | |
| if line1.start_with?('#!') # if the line is a shebang check for ruby | |
| break !!line1['ruby'] | |
| end | |
| # if not a shebang, check for magic string "rubygems: no-wrap" | |
| break !line1['rubygems: no-wrap'] |
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for checking the magic string is inverted. When line1['rubygems: no-wrap'] finds the substring, it returns 'rubygems: no-wrap' (truthy), and !truthy becomes false, meaning "not wrappable" which is correct. However, when the substring is NOT found, it returns nil, and !nil becomes true, meaning "wrappable" which is also correct. While this works, using !line1.include?('rubygems: no-wrap') would be more readable and explicit about the intent.
| !line1['rubygems: no-wrap'] # check for magic string | |
| !line1.include?('rubygems: no-wrap') # check for magic string |
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using String#[] with a substring returns the substring if found, or nil if not found. However, the code is using this in a boolean context. When checking for 'ruby' in a shebang, if 'ruby' is NOT found, line1['ruby'] returns nil which is falsy, so it correctly returns false (not wrappable). But the logic seems backwards from the documentation comment. The comment says "return false if it doesn't [contain ruby]" but the code returns the result of line1['ruby'], which returns a truthy value (the substring) when ruby IS found. This actually works correctly but could be clearer. Consider using line1.include?('ruby') for better readability and intent.
| break line1['ruby'] if line1.start_with?('#!') # if the line is a shebang check for ruby | |
| !line1['rubygems: no-wrap'] # check for magic string | |
| break line1.include?('ruby') if line1.start_with?('#!') # if the line is a shebang check for ruby | |
| !line1.include?('rubygems: no-wrap') # check for magic string |
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The substring search for 'rubygems: no-wrap' is case-sensitive. If a developer writes 'Rubygems: no-wrap' or 'RUBYGEMS: NO-WRAP', it won't be recognized. Consider using a case-insensitive search or documenting the exact required format for the magic string.
| !line1['rubygems: no-wrap'] # check for magic string | |
| !line1.downcase['rubygems: no-wrap'] # check for magic string (case-insensitive) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using String#[] is not the best way to check if a string contains a substring, as it's not a boolean operation.
Also, the method should be aware that IO#gets may return nil if the file is empty.
You could use a regexp here.
# If the first line is a non-ruby shebang or contains a magic string, return false.
/\A#!(?!.*ruby)|rubygems: no-wrap/ !~ File.open(bin_path, &:gets)| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -524,6 +524,40 @@ def test_generate_bin_script_wrappers | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'real executable overwritten' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_no_wrap_non_ruby_executables | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skip "Symlinks not supported or not enabled" unless symlink_supported? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| installer = setup_base_installer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| installer.wrappers = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| util_make_exec @spec, "#!/usr/bin/env bash" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| installer.gem_dir = @spec.gem_dir | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| installer.generate_bin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_directory_exists util_inst_bindir | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| installed_exec = File.join util_inst_bindir, 'executable' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_equal true, File.symlink?(installed_exec) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| File.readlink(installed_exec)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+527
to
+542
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_no_wrap_non_ruby_executables_env_python | |
| skip "Symlinks not supported or not enabled" unless symlink_supported? | |
| installer = setup_base_installer | |
| installer.wrappers = true | |
| util_make_exec @spec, "#!/usr/bin/env python" | |
| installer.gem_dir = @spec.gem_dir | |
| installer.generate_bin | |
| assert_directory_exists util_inst_bindir | |
| installed_exec = File.join util_inst_bindir, 'executable' | |
| assert_equal true, File.symlink?(installed_exec) | |
| assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'), | |
| File.readlink(installed_exec)) | |
| end | |
| def test_no_wrap_non_ruby_executables_bin_sh | |
| skip "Symlinks not supported or not enabled" unless symlink_supported? | |
| installer = setup_base_installer | |
| installer.wrappers = true | |
| util_make_exec @spec, "#!/bin/sh" | |
| installer.gem_dir = @spec.gem_dir | |
| installer.generate_bin | |
| assert_directory_exists util_inst_bindir | |
| installed_exec = File.join util_inst_bindir, 'executable' | |
| assert_equal true, File.symlink?(installed_exec) | |
| assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'), | |
| File.readlink(installed_exec)) | |
| end | |
| def test_no_wrap_non_ruby_executables_perl | |
| skip "Symlinks not supported or not enabled" unless symlink_supported? | |
| installer = setup_base_installer | |
| installer.wrappers = true | |
| util_make_exec @spec, "#!/usr/bin/perl" | |
| installer.gem_dir = @spec.gem_dir | |
| installer.generate_bin | |
| assert_directory_exists util_inst_bindir | |
| installed_exec = File.join util_inst_bindir, 'executable' | |
| assert_equal true, File.symlink?(installed_exec) | |
| assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'), | |
| File.readlink(installed_exec)) | |
| end |
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no test coverage for the edge case where an executable file is completely empty. With the current implementation of wrappable_executable?, an empty file would cause line1 to be nil, which would cause a NoMethodError when calling start_with? on it. Consider adding a test for this edge case.
| def test_no_wrap_empty_executable | |
| skip "Symlinks not supported or not enabled" unless symlink_supported? | |
| installer = setup_base_installer | |
| installer.wrappers = true | |
| util_make_exec @spec, "" | |
| installer.gem_dir = @spec.gem_dir | |
| installer.generate_bin | |
| assert_directory_exists util_inst_bindir | |
| installed_exec = File.join util_inst_bindir, 'executable' | |
| assert_equal true, File.symlink?(installed_exec) | |
| assert_equal(File.join(@spec.gem_dir, 'bin', 'executable'), | |
| File.readlink(installed_exec)) | |
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The check for 'ruby' in the shebang line uses a simple substring search, which could produce false positives. For example, a shebang like '#!/usr/bin/groovy' would match because it contains 'ruby' as a substring. Consider using a more robust check such as matching word boundaries or checking for common ruby interpreter patterns like /ruby\d*$/ or /ruby\s/.