rubygems-update 4.0.15 → 4.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/Manifest.txt +0 -2
- data/bundler/CHANGELOG.md +34 -0
- data/bundler/bundler.gemspec +2 -2
- data/bundler/lib/bundler/build_metadata.rb +1 -1
- data/bundler/lib/bundler/cli/gem.rb +1 -2
- data/bundler/lib/bundler/cli/open.rb +12 -2
- data/bundler/lib/bundler/compact_index_client/cache_file.rb +1 -1
- data/bundler/lib/bundler/definition.rb +8 -4
- data/bundler/lib/bundler/dsl.rb +4 -1
- data/bundler/lib/bundler/fetcher.rb +1 -1
- data/bundler/lib/bundler/installer/parallel_installer.rb +7 -1
- data/bundler/lib/bundler/lockfile_generator.rb +27 -4
- data/bundler/lib/bundler/runtime.rb +11 -11
- data/bundler/lib/bundler/self_manager.rb +6 -1
- data/bundler/lib/bundler/shared_helpers.rb +6 -1
- data/bundler/lib/bundler/source/git.rb +1 -1
- data/bundler/lib/bundler/source/rubygems.rb +1 -1
- data/bundler/lib/bundler/spec_set.rb +2 -2
- data/bundler/lib/bundler/vendored_tsort.rb +19 -2
- data/bundler/lib/bundler/version.rb +1 -1
- data/lib/rubygems/basic_specification.rb +18 -14
- data/lib/rubygems/commands/open_command.rb +12 -1
- data/lib/rubygems/commands/setup_command.rb +29 -14
- data/lib/rubygems/commands/update_command.rb +4 -1
- data/lib/rubygems/ext/builder.rb +20 -1
- data/lib/rubygems/ext/rake_builder.rb +4 -2
- data/lib/rubygems/source.rb +7 -0
- data/lib/rubygems/util.rb +3 -1
- data/lib/rubygems.rb +1 -1
- metadata +2 -4
- data/bundler/lib/bundler/vendor/tsort/LICENSE.txt +0 -22
- data/bundler/lib/bundler/vendor/tsort/lib/tsort.rb +0 -455
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6781781dd2ce13c49c2771fe518b3b13116d37285c607368a0e719e538aadd82
|
|
4
|
+
data.tar.gz: 0f6eb6c051bf17b55057b7c2e30027dd2119acf2dcabeaab6b70834f5d6a59c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c31326c1bcd80727a77f559b5d6c6c0b51ae74529d3c8b31a0768afe798ec906788ad1b91934e5b63770442822e71a8067dddebaa262327cee3ab2e8c71e8dd7
|
|
7
|
+
data.tar.gz: 184787705ecd74c6c19fe5382a4103ad69e5179672d5de14c5a84e88170ac97b3ad02c2432ba0f139b337a634a96b5d6b70f6431d757a6c4ab3fbc22702fcc4d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.17 / 2026-07-22
|
|
4
|
+
|
|
5
|
+
### Enhancements:
|
|
6
|
+
|
|
7
|
+
* Validate spec name before writing to the spec cache. Pull request [#9690](https://github.com/ruby/rubygems/pull/9690) by hsbt
|
|
8
|
+
* Installs bundler 4.0.17 as a default gem.
|
|
9
|
+
|
|
10
|
+
### Bug fixes:
|
|
11
|
+
|
|
12
|
+
* Unquote Gem.ruby when spawning it as a separate argv element. Pull request [#9695](https://github.com/ruby/rubygems/pull/9695) by hsbt
|
|
13
|
+
* Escape glob metacharacters in install paths when globbing. Pull request [#9687](https://github.com/ruby/rubygems/pull/9687) by hsbt
|
|
14
|
+
* Preserve Windows editor paths in gem open and bundle open. Pull request [#9694](https://github.com/ruby/rubygems/pull/9694) by hsbt
|
|
15
|
+
* Preserve Windows paths in MAKE and rake environment variables. Pull request [#9693](https://github.com/ruby/rubygems/pull/9693) by hsbt
|
|
16
|
+
* Fix `bundle` binstub broken by `gem update --system` on Homebrew ruby. Pull request [#9688](https://github.com/ruby/rubygems/pull/9688) by hsbt
|
|
17
|
+
|
|
18
|
+
## 4.0.16 / 2026-07-10
|
|
19
|
+
|
|
20
|
+
### Enhancements:
|
|
21
|
+
|
|
22
|
+
* Installs bundler 4.0.16 as a default gem.
|
|
23
|
+
|
|
24
|
+
### Bug fixes:
|
|
25
|
+
|
|
26
|
+
* Skip the make job server when using BSD make. Pull request [#9676](https://github.com/ruby/rubygems/pull/9676) by flavorjones
|
|
27
|
+
|
|
3
28
|
## 4.0.15 / 2026-06-24
|
|
4
29
|
|
|
5
30
|
### Enhancements:
|
data/Manifest.txt
CHANGED
|
@@ -323,8 +323,6 @@ bundler/lib/bundler/vendor/thor/lib/thor/shell/terminal.rb
|
|
|
323
323
|
bundler/lib/bundler/vendor/thor/lib/thor/shell/wrapped_printer.rb
|
|
324
324
|
bundler/lib/bundler/vendor/thor/lib/thor/util.rb
|
|
325
325
|
bundler/lib/bundler/vendor/thor/lib/thor/version.rb
|
|
326
|
-
bundler/lib/bundler/vendor/tsort/LICENSE.txt
|
|
327
|
-
bundler/lib/bundler/vendor/tsort/lib/tsort.rb
|
|
328
326
|
bundler/lib/bundler/vendor/uri/COPYING
|
|
329
327
|
bundler/lib/bundler/vendor/uri/lib/uri.rb
|
|
330
328
|
bundler/lib/bundler/vendor/uri/lib/uri/common.rb
|
data/bundler/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.17 / 2026-07-22
|
|
4
|
+
|
|
5
|
+
### Enhancements:
|
|
6
|
+
|
|
7
|
+
* Open compact index cache in binary mode when appending. Pull request [#9679](https://github.com/ruby/rubygems/pull/9679) by hsbt
|
|
8
|
+
|
|
9
|
+
### Bug fixes:
|
|
10
|
+
|
|
11
|
+
* Unquote Gem.ruby when spawning it as a separate argv element. Pull request [#9695](https://github.com/ruby/rubygems/pull/9695) by hsbt
|
|
12
|
+
* Escape glob metacharacters in install paths when globbing. Pull request [#9687](https://github.com/ruby/rubygems/pull/9687) by hsbt
|
|
13
|
+
* Avoid space-containing absolute path in RUBYOPT. Pull request [#9696](https://github.com/ruby/rubygems/pull/9696) by hsbt
|
|
14
|
+
* Preserve the locked Bundler checksum when the gem isn't cached. Pull request [#9658](https://github.com/ruby/rubygems/pull/9658) by rwstauner
|
|
15
|
+
|
|
16
|
+
### Documentation:
|
|
17
|
+
|
|
18
|
+
* Point Bundler gemspec metadata at the moved docs. Pull request [#9648](https://github.com/ruby/rubygems/pull/9648) by hsbt
|
|
19
|
+
|
|
20
|
+
## 4.0.16 / 2026-07-10
|
|
21
|
+
|
|
22
|
+
### Enhancements:
|
|
23
|
+
|
|
24
|
+
* Bundler: Fix Bundler::Fetcher for PQC support, adding integration connection tests. Pull request [#9637](https://github.com/ruby/rubygems/pull/9637) by junaruga
|
|
25
|
+
* Reuse RubyGems' vendored tsort in Bundler. Pull request [#9647](https://github.com/ruby/rubygems/pull/9647) by hsbt
|
|
26
|
+
|
|
27
|
+
### Bug fixes:
|
|
28
|
+
|
|
29
|
+
* Initialize the new gem's git repo without a subshell. Pull request [#9670](https://github.com/ruby/rubygems/pull/9670) by hsbt
|
|
30
|
+
* Preserve CRLF lockfile line endings on Windows. Pull request [#9669](https://github.com/ruby/rubygems/pull/9669) by hsbt
|
|
31
|
+
* Fix the gemspec error snippet on Windows drive-letter paths. Pull request [#9668](https://github.com/ruby/rubygems/pull/9668) by hsbt
|
|
32
|
+
|
|
33
|
+
### Documentation:
|
|
34
|
+
|
|
35
|
+
* Point Bundler gemspec metadata at the moved docs. Pull request [#9648](https://github.com/ruby/rubygems/pull/9648) by hsbt
|
|
36
|
+
|
|
3
37
|
## 4.0.15 / 2026-06-24
|
|
4
38
|
|
|
5
39
|
### Enhancements:
|
data/bundler/bundler.gemspec
CHANGED
|
@@ -24,9 +24,9 @@ Gem::Specification.new do |s|
|
|
|
24
24
|
|
|
25
25
|
s.metadata = {
|
|
26
26
|
"bug_tracker_uri" => "https://github.com/ruby/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler",
|
|
27
|
-
"changelog_uri" => "https://github.com/ruby/rubygems/blob/master/bundler
|
|
27
|
+
"changelog_uri" => "https://github.com/ruby/rubygems/blob/master/CHANGELOG-bundler.md",
|
|
28
28
|
"homepage_uri" => "https://bundler.io/",
|
|
29
|
-
"source_code_uri" => "https://github.com/ruby/rubygems
|
|
29
|
+
"source_code_uri" => "https://github.com/ruby/rubygems",
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
s.required_ruby_version = ">= 3.2.0"
|
|
@@ -256,8 +256,7 @@ module Bundler
|
|
|
256
256
|
|
|
257
257
|
if use_git
|
|
258
258
|
Bundler.ui.info "\nInitializing git repo in #{target}"
|
|
259
|
-
|
|
260
|
-
`git init #{target.to_s.shellescape}`
|
|
259
|
+
IO.popen(["git", "init", target.to_s], &:read)
|
|
261
260
|
|
|
262
261
|
config[:git_default_branch] = File.read("#{target}/.git/HEAD").split("/").last.chomp
|
|
263
262
|
end
|
|
@@ -18,12 +18,22 @@ module Bundler
|
|
|
18
18
|
Bundler.ui.info "Unable to open #{name} because it's a default gem, so the directory it would normally be installed to does not exist."
|
|
19
19
|
else
|
|
20
20
|
root_path = spec.full_gem_path
|
|
21
|
-
|
|
22
|
-
command = Shellwords.split(editor) << File.join([root_path, path].compact)
|
|
21
|
+
command = editor_command(editor) << File.join([root_path, path].compact)
|
|
23
22
|
Bundler.with_original_env do
|
|
24
23
|
system(*command, { chdir: root_path })
|
|
25
24
|
end || Bundler.ui.info("Could not run '#{command.join(" ")}'")
|
|
26
25
|
end
|
|
27
26
|
end
|
|
27
|
+
|
|
28
|
+
def editor_command(editor)
|
|
29
|
+
# On Windows an editor is often configured with a full path such as
|
|
30
|
+
# C:\Program Files\Microsoft VS Code\Code.exe, which shell splitting
|
|
31
|
+
# would corrupt. Take a value that names an existing file as a
|
|
32
|
+
# single word.
|
|
33
|
+
return [editor] if Gem.win_platform? && File.file?(editor)
|
|
34
|
+
|
|
35
|
+
require "shellwords"
|
|
36
|
+
Shellwords.split(editor)
|
|
37
|
+
end
|
|
28
38
|
end
|
|
29
39
|
end
|
|
@@ -103,7 +103,7 @@ module Bundler
|
|
|
103
103
|
# Returns false without appending when no digests since appending is too error prone to do without digests.
|
|
104
104
|
def append(data)
|
|
105
105
|
return false unless digests?
|
|
106
|
-
open("
|
|
106
|
+
open("ab") {|f| f.write data }
|
|
107
107
|
verify && commit
|
|
108
108
|
end
|
|
109
109
|
|
|
@@ -397,10 +397,6 @@ module Bundler
|
|
|
397
397
|
|
|
398
398
|
contents = to_lock
|
|
399
399
|
|
|
400
|
-
# Convert to \r\n if the existing lock has them
|
|
401
|
-
# i.e., Windows with `git config core.autocrlf=true`
|
|
402
|
-
contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n")
|
|
403
|
-
|
|
404
400
|
if @locked_bundler_version
|
|
405
401
|
locked_major = @locked_bundler_version.segments.first
|
|
406
402
|
current_major = bundler_version_to_lock.segments.first
|
|
@@ -421,6 +417,14 @@ module Bundler
|
|
|
421
417
|
return
|
|
422
418
|
end
|
|
423
419
|
|
|
420
|
+
# Convert to \r\n if the existing lock has them, i.e., Windows with
|
|
421
|
+
# `git config core.autocrlf=true`. Detect from the bytes on disk because
|
|
422
|
+
# reading in text mode strips carriage returns on Windows, which would
|
|
423
|
+
# otherwise defeat this check and rewrite a `\r\n` lockfile with `\n`.
|
|
424
|
+
if File.exist?(file) && SharedHelpers.filesystem_access(file, :read) {|p| File.binread(p).include?("\r\n") }
|
|
425
|
+
contents.gsub!(/\n/, "\r\n")
|
|
426
|
+
end
|
|
427
|
+
|
|
424
428
|
begin
|
|
425
429
|
SharedHelpers.filesystem_access(file) do |p|
|
|
426
430
|
File.open(p, "wb") {|f| f.puts(contents) }
|
data/bundler/lib/bundler/dsl.rb
CHANGED
|
@@ -602,8 +602,11 @@ module Bundler
|
|
|
602
602
|
|
|
603
603
|
trace_line = backtrace.find {|l| l.include?(dsl_path) } || trace_line
|
|
604
604
|
return m unless trace_line
|
|
605
|
-
|
|
605
|
+
# Match the line number right before `:in` or the end of the line so a
|
|
606
|
+
# Windows drive letter like `C:` does not get mistaken for the number.
|
|
607
|
+
line_number = trace_line[/:(\d+)(?::in\b|\z)/, 1]
|
|
606
608
|
return m unless line_number
|
|
609
|
+
line_number = line_number.to_i - 1
|
|
607
610
|
|
|
608
611
|
lines = contents.lines.to_a
|
|
609
612
|
indent = " # "
|
|
@@ -318,7 +318,7 @@ module Bundler
|
|
|
318
318
|
if ssl_client_cert
|
|
319
319
|
pem = File.read(ssl_client_cert)
|
|
320
320
|
con.cert = OpenSSL::X509::Certificate.new(pem)
|
|
321
|
-
con.key = OpenSSL::PKey
|
|
321
|
+
con.key = OpenSSL::PKey.read(pem)
|
|
322
322
|
end
|
|
323
323
|
|
|
324
324
|
con.read_timeout = Fetcher.api_timeout
|
|
@@ -125,7 +125,7 @@ module Bundler
|
|
|
125
125
|
# every native extension build with `fatal error U1065: invalid option
|
|
126
126
|
# '-'`. Skip the jobserver when nmake is in use. Other Windows toolchains
|
|
127
127
|
# such as mingw use GNU make and keep working through the inherited pipe.
|
|
128
|
-
return yield if nmake?
|
|
128
|
+
return yield if nmake? || bsd_make?
|
|
129
129
|
|
|
130
130
|
begin
|
|
131
131
|
r, w = IO.pipe
|
|
@@ -155,6 +155,12 @@ module Bundler
|
|
|
155
155
|
/\bnmake/i.match?(make.to_s)
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
+
def bsd_make?
|
|
159
|
+
return false unless Gem.freebsd_platform?
|
|
160
|
+
make = ENV["MAKE"] || ENV["make"] || "make"
|
|
161
|
+
!/\bgmake/i.match?(make)
|
|
162
|
+
end
|
|
163
|
+
|
|
158
164
|
def install_serially
|
|
159
165
|
until finished_installing?
|
|
160
166
|
raise "failed to find a spec to enqueue while installing serially" unless spec_install = @specs.find(&:ready_to_enqueue?)
|
|
@@ -103,17 +103,40 @@ module Bundler
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def bundler_checksum
|
|
106
|
+
# `.dev` versions and `SKIP_BUNDLER_CHECKSUM` are deliberate opt-outs (used
|
|
107
|
+
# by Bundler/RubyGems' own development and release tasks): never record a
|
|
108
|
+
# checksum for Bundler itself in those cases.
|
|
106
109
|
return [] if Bundler.gem_version.to_s.end_with?(".dev") || ENV["SKIP_BUNDLER_CHECKSUM"]
|
|
107
110
|
|
|
108
111
|
bundler_spec = definition.sources.metadata_source.specs.search(["bundler", Bundler.gem_version]).last
|
|
109
|
-
return [] unless File.exist?(bundler_spec.cache_file)
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
# Record a fresh checksum from the locally cached gem when it's available.
|
|
114
|
+
# When it isn't (e.g. a fresh checkout/CI that never downloaded the bundler
|
|
115
|
+
# gem), fall back to whatever checksum is already locked rather than
|
|
116
|
+
# dropping it, so the entry stays consistent across environments.
|
|
117
|
+
if File.exist?(bundler_spec.cache_file)
|
|
118
|
+
require "rubygems/package"
|
|
119
|
+
|
|
120
|
+
package = Gem::Package.new(bundler_spec.cache_file)
|
|
121
|
+
definition.sources.metadata_source.checksum_store.register(bundler_spec, Checksum.from_gem_package(package))
|
|
122
|
+
elsif bundled_with_changing?
|
|
123
|
+
# We can't compute a fresh checksum (the bundler gem isn't cached) and the
|
|
124
|
+
# BUNDLED WITH version is changing. Keeping the previously locked checksum
|
|
125
|
+
# would leave a `bundler (<old version>) sha256=...` entry that no longer
|
|
126
|
+
# matches the new BUNDLED WITH version, so drop it instead.
|
|
127
|
+
return []
|
|
128
|
+
end
|
|
112
129
|
|
|
113
|
-
|
|
114
|
-
definition.sources.metadata_source.checksum_store.register(bundler_spec, Checksum.from_gem_package(package))
|
|
130
|
+
return [] if definition.sources.metadata_source.checksum_store.missing?(bundler_spec)
|
|
115
131
|
|
|
116
132
|
[definition.sources.metadata_source.checksum_store.to_lock(bundler_spec)]
|
|
117
133
|
end
|
|
134
|
+
|
|
135
|
+
def bundled_with_changing?
|
|
136
|
+
locked_gems = definition.locked_gems
|
|
137
|
+
return false unless locked_gems
|
|
138
|
+
|
|
139
|
+
locked_gems.bundler_version != definition.bundler_version_to_lock
|
|
140
|
+
end
|
|
118
141
|
end
|
|
119
142
|
end
|
|
@@ -141,7 +141,7 @@ module Bundler
|
|
|
141
141
|
end
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
Gem::Util.glob_files_in_dir("*/.git", cache_path.to_s).each do |git_dir|
|
|
145
145
|
FileUtils.rm_rf(git_dir)
|
|
146
146
|
FileUtils.touch(File.expand_path("../.bundlecache", git_dir))
|
|
147
147
|
end
|
|
@@ -159,13 +159,13 @@ module Bundler
|
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
def clean(dry_run = false)
|
|
162
|
-
gem_bins =
|
|
163
|
-
git_dirs =
|
|
164
|
-
git_cache_dirs =
|
|
165
|
-
gem_dirs =
|
|
166
|
-
gem_files =
|
|
167
|
-
gemspec_files =
|
|
168
|
-
extension_dirs =
|
|
162
|
+
gem_bins = Gem::Util.glob_files_in_dir("bin/*", Gem.dir)
|
|
163
|
+
git_dirs = Gem::Util.glob_files_in_dir("bundler/gems/*", Gem.dir)
|
|
164
|
+
git_cache_dirs = Gem::Util.glob_files_in_dir("cache/bundler/git/*", Gem.dir)
|
|
165
|
+
gem_dirs = Gem::Util.glob_files_in_dir("gems/*", Gem.dir)
|
|
166
|
+
gem_files = Gem::Util.glob_files_in_dir("cache/*.gem", Gem.dir)
|
|
167
|
+
gemspec_files = Gem::Util.glob_files_in_dir("specifications/*.gemspec", Gem.dir)
|
|
168
|
+
extension_dirs = Gem::Util.glob_files_in_dir("extensions/*/*/*", Gem.dir) + Gem::Util.glob_files_in_dir("bundler/gems/extensions/*/*/*", Gem.dir)
|
|
169
169
|
spec_gem_paths = []
|
|
170
170
|
# need to keep git sources around
|
|
171
171
|
spec_git_paths = @definition.spec_git_paths
|
|
@@ -232,7 +232,7 @@ module Bundler
|
|
|
232
232
|
private
|
|
233
233
|
|
|
234
234
|
def prune_gem_cache(resolve, cache_path)
|
|
235
|
-
cached =
|
|
235
|
+
cached = Gem::Util.glob_files_in_dir("*.gem", cache_path.to_s)
|
|
236
236
|
|
|
237
237
|
cached = cached.delete_if do |path|
|
|
238
238
|
spec = Bundler.rubygems.spec_from_gem path
|
|
@@ -257,7 +257,7 @@ module Bundler
|
|
|
257
257
|
end
|
|
258
258
|
|
|
259
259
|
def prune_git_and_path_cache(resolve, cache_path)
|
|
260
|
-
cached =
|
|
260
|
+
cached = Gem::Util.glob_files_in_dir("*/.bundlecache", cache_path.to_s)
|
|
261
261
|
|
|
262
262
|
cached = cached.delete_if do |path|
|
|
263
263
|
name = File.basename(File.dirname(path))
|
|
@@ -283,7 +283,7 @@ module Bundler
|
|
|
283
283
|
# Add man/ subdirectories from activated bundles to MANPATH for man(1)
|
|
284
284
|
manuals = $LOAD_PATH.filter_map do |path|
|
|
285
285
|
man_subdir = path.sub(/lib$/, "man")
|
|
286
|
-
man_subdir unless Dir
|
|
286
|
+
man_subdir unless Dir.glob("man?/", base: man_subdir).empty?
|
|
287
287
|
end
|
|
288
288
|
|
|
289
289
|
return if manuals.empty?
|
|
@@ -75,7 +75,12 @@ module Bundler
|
|
|
75
75
|
|
|
76
76
|
argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0
|
|
77
77
|
cmd = [argv0, *ARGV]
|
|
78
|
-
|
|
78
|
+
unless File.executable?(argv0)
|
|
79
|
+
# Gem.ruby is quoted if it contains whitespace, so split it into argv
|
|
80
|
+
# elements to keep the quotes out of the exec'd command.
|
|
81
|
+
require "shellwords"
|
|
82
|
+
cmd.unshift(*Shellwords.split(Gem.ruby))
|
|
83
|
+
end
|
|
79
84
|
|
|
80
85
|
Bundler.with_original_env do
|
|
81
86
|
Kernel.exec(
|
|
@@ -346,7 +346,12 @@ module Bundler
|
|
|
346
346
|
|
|
347
347
|
def set_rubyopt
|
|
348
348
|
rubyopt = [ENV["RUBYOPT"]].compact
|
|
349
|
-
|
|
349
|
+
setup_path = File.expand_path("setup", __dir__)
|
|
350
|
+
# RUBYOPT is split on whitespace with no quoting mechanism, so an
|
|
351
|
+
# absolute path containing spaces would be torn apart. Fall back to
|
|
352
|
+
# requiring by feature name; set_rubylib puts our lib directory first
|
|
353
|
+
# on the child's load path.
|
|
354
|
+
setup_require = /\s/.match?(setup_path) ? "-rbundler/setup" : "-r#{setup_path}"
|
|
350
355
|
return if !rubyopt.empty? && rubyopt.first.include?(setup_require)
|
|
351
356
|
rubyopt.unshift setup_require
|
|
352
357
|
Bundler::SharedHelpers.set_env "RUBYOPT", rubyopt.join(" ")
|
|
@@ -320,7 +320,7 @@ module Bundler
|
|
|
320
320
|
|
|
321
321
|
def serialize_gemspecs_in(destination)
|
|
322
322
|
destination = destination.expand_path(Bundler.root) if destination.relative?
|
|
323
|
-
|
|
323
|
+
Gem::Util.glob_files_in_dir(@glob, destination.to_s).each do |spec_path|
|
|
324
324
|
# Evaluate gemspecs and cache the result. Gemspecs
|
|
325
325
|
# in git might require git or other dependencies.
|
|
326
326
|
# The gemspecs we cache should already be evaluated.
|
|
@@ -406,7 +406,7 @@ module Bundler
|
|
|
406
406
|
@cached_specs ||= begin
|
|
407
407
|
idx = Index.new
|
|
408
408
|
|
|
409
|
-
|
|
409
|
+
Gem::Util.glob_files_in_dir("*.gem", cache_path.to_s).each do |gemfile|
|
|
410
410
|
s ||= Bundler.rubygems.spec_from_gem(gemfile)
|
|
411
411
|
s.source = self
|
|
412
412
|
idx << s
|
|
@@ -5,7 +5,7 @@ require_relative "vendored_tsort"
|
|
|
5
5
|
module Bundler
|
|
6
6
|
class SpecSet
|
|
7
7
|
include Enumerable
|
|
8
|
-
include TSort
|
|
8
|
+
include Gem::TSort
|
|
9
9
|
|
|
10
10
|
def initialize(specs)
|
|
11
11
|
@specs = specs
|
|
@@ -315,7 +315,7 @@ module Bundler
|
|
|
315
315
|
|
|
316
316
|
def sorted
|
|
317
317
|
@sorted ||= ([@specs.find {|s| s.name == "rake" }] + tsort).compact.uniq
|
|
318
|
-
rescue TSort::Cyclic => error
|
|
318
|
+
rescue Gem::TSort::Cyclic => error
|
|
319
319
|
cgems = extract_circular_gems(error)
|
|
320
320
|
raise CyclicDependencyError, "Your bundle requires gems that depend" \
|
|
321
321
|
" on each other, creating an infinite loop. Please remove either" \
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# The defined? guard avoids reopening Gem::TSort when an old RubyGems has
|
|
4
|
+
# already loaded its own copy, e.g. through rubygems/request_set from
|
|
5
|
+
# Gem.activate_bin_path in binstubs.
|
|
6
|
+
#
|
|
7
|
+
unless defined?(Gem::TSort)
|
|
8
|
+
begin
|
|
9
|
+
require "rubygems/vendored_tsort"
|
|
10
|
+
rescue LoadError
|
|
11
|
+
begin
|
|
12
|
+
# RubyGems 3.4 and 3.5 ship the same file under its pre-3.6 name.
|
|
13
|
+
# Requiring the real tsort here instead would activate the tsort
|
|
14
|
+
# default gem, and `bundler/setup` must not activate any gems.
|
|
15
|
+
require "rubygems/tsort"
|
|
16
|
+
rescue LoadError
|
|
17
|
+
require "tsort"
|
|
18
|
+
Gem::TSort = TSort
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -306,9 +306,7 @@ class Gem::BasicSpecification
|
|
|
306
306
|
# Return all files in this gem that match for +glob+.
|
|
307
307
|
|
|
308
308
|
def matches_for_glob(glob) # TODO: rename?
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
Dir[glob]
|
|
309
|
+
Gem::Util.glob_files_in_dir(File.join(lib_dirs, glob), full_gem_path)
|
|
312
310
|
end
|
|
313
311
|
|
|
314
312
|
##
|
|
@@ -323,17 +321,7 @@ class Gem::BasicSpecification
|
|
|
323
321
|
# for this spec.
|
|
324
322
|
|
|
325
323
|
def lib_dirs_glob
|
|
326
|
-
|
|
327
|
-
if raw_require_paths.size > 1
|
|
328
|
-
"{#{raw_require_paths.join(",")}}"
|
|
329
|
-
else
|
|
330
|
-
raw_require_paths.first
|
|
331
|
-
end
|
|
332
|
-
else
|
|
333
|
-
"lib" # default value for require_paths for bundler/inline
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
"#{full_gem_path}/#{dirs}"
|
|
324
|
+
"#{full_gem_path}/#{lib_dirs}"
|
|
337
325
|
end
|
|
338
326
|
|
|
339
327
|
##
|
|
@@ -364,6 +352,22 @@ class Gem::BasicSpecification
|
|
|
364
352
|
|
|
365
353
|
private
|
|
366
354
|
|
|
355
|
+
##
|
|
356
|
+
# Returns the require_paths of this gem as a string usable in Dir.glob,
|
|
357
|
+
# relative to full_gem_path.
|
|
358
|
+
|
|
359
|
+
def lib_dirs
|
|
360
|
+
if raw_require_paths
|
|
361
|
+
if raw_require_paths.size > 1
|
|
362
|
+
"{#{raw_require_paths.join(",")}}"
|
|
363
|
+
else
|
|
364
|
+
raw_require_paths.first
|
|
365
|
+
end
|
|
366
|
+
else
|
|
367
|
+
"lib" # default value for require_paths for bundler/inline
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
367
371
|
def have_extensions?
|
|
368
372
|
!extensions.empty?
|
|
369
373
|
end
|
|
@@ -70,7 +70,18 @@ class Gem::Commands::OpenCommand < Gem::Command
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def open_editor(path)
|
|
73
|
-
system(
|
|
73
|
+
system(*editor_command(@editor), path, { chdir: path })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def editor_command(editor) # :nodoc:
|
|
77
|
+
# On Windows an editor is often configured with a full path such as
|
|
78
|
+
# C:\Program Files\Microsoft VS Code\Code.exe, which shell splitting
|
|
79
|
+
# would corrupt. Take a value that names an existing file as a
|
|
80
|
+
# single word.
|
|
81
|
+
return [editor] if Gem.win_platform? && File.file?(editor)
|
|
82
|
+
|
|
83
|
+
require "shellwords"
|
|
84
|
+
Shellwords.split(editor)
|
|
74
85
|
end
|
|
75
86
|
|
|
76
87
|
def spec_for(name)
|
|
@@ -356,16 +356,30 @@ By default, this RubyGems will install gem as:
|
|
|
356
356
|
loaded_from = current_default_spec.loaded_from
|
|
357
357
|
File.delete(loaded_from)
|
|
358
358
|
|
|
359
|
-
|
|
360
|
-
|
|
359
|
+
previous_specs_dir = File.dirname(loaded_from)
|
|
360
|
+
|
|
361
|
+
# Remove previous default gem executables if they were not shadowed by a
|
|
362
|
+
# regular gem. They live under the same root as the previous default
|
|
363
|
+
# gemspec, which is not necessarily default_dir.
|
|
364
|
+
if all_specs_current_version.size == 1
|
|
365
|
+
previous_root = File.dirname(File.dirname(previous_specs_dir))
|
|
366
|
+
FileUtils.rm_rf File.join(previous_root, "gems", current_default_spec.full_name)
|
|
367
|
+
end
|
|
361
368
|
|
|
362
|
-
|
|
369
|
+
previous_specs_dir
|
|
363
370
|
else
|
|
364
371
|
target_specs_dir = File.join(default_dir, "specifications", "default")
|
|
365
372
|
mkdir_p target_specs_dir, mode: 0o755
|
|
366
373
|
target_specs_dir
|
|
367
374
|
end
|
|
368
375
|
|
|
376
|
+
# Root directory that specs_dir belongs to. It may differ from default_dir
|
|
377
|
+
# when Gem.default_specifications_dir is customized to live under a
|
|
378
|
+
# different root, like Homebrew does. Executables must be extracted under
|
|
379
|
+
# the same root as the default gemspec, since that's where activation of
|
|
380
|
+
# the default gem will look for them.
|
|
381
|
+
bundler_install_dir = File.dirname(File.dirname(specs_dir))
|
|
382
|
+
|
|
369
383
|
new_bundler_spec = Dir.chdir("bundler") { Gem::Specification.load("bundler.gemspec") }
|
|
370
384
|
full_name = new_bundler_spec.full_name
|
|
371
385
|
gemspec_path = "#{full_name}.gemspec"
|
|
@@ -373,19 +387,20 @@ By default, this RubyGems will install gem as:
|
|
|
373
387
|
default_spec_path = File.join(specs_dir, gemspec_path)
|
|
374
388
|
Gem.write_binary(default_spec_path, new_bundler_spec.to_ruby)
|
|
375
389
|
|
|
376
|
-
bundler_spec = Gem::Specification.load(default_spec_path)
|
|
377
|
-
|
|
378
390
|
# Remove gemspec that was same version of vendored bundler.
|
|
379
391
|
normal_gemspec = File.join(default_dir, "specifications", gemspec_path)
|
|
380
392
|
if File.file? normal_gemspec
|
|
381
393
|
File.delete normal_gemspec
|
|
382
394
|
end
|
|
383
395
|
|
|
384
|
-
# Remove gem files that were same version of vendored bundler.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
396
|
+
# Remove gem files that were same version of vendored bundler. A regular
|
|
397
|
+
# gem lives under default_dir, which is not necessarily the same root as
|
|
398
|
+
# the default gemspec.
|
|
399
|
+
normal_gems_dir = File.join(default_dir, "gems")
|
|
400
|
+
if File.directory? normal_gems_dir
|
|
401
|
+
Dir.entries(normal_gems_dir).
|
|
402
|
+
select {|normal_gem| File.basename(normal_gem) == full_name }.
|
|
403
|
+
each {|normal_gem| rm_r File.join(normal_gems_dir, normal_gem) }
|
|
389
404
|
end
|
|
390
405
|
|
|
391
406
|
require_relative "../installer"
|
|
@@ -399,14 +414,14 @@ By default, this RubyGems will install gem as:
|
|
|
399
414
|
format_executable: options[:format_executable],
|
|
400
415
|
force: options[:force],
|
|
401
416
|
bin_dir: bin_dir,
|
|
402
|
-
install_dir:
|
|
417
|
+
install_dir: bundler_install_dir,
|
|
403
418
|
wrappers: true
|
|
404
419
|
)
|
|
405
|
-
# We need to install
|
|
406
|
-
# lib/bundler.rb and lib/bundler/* are
|
|
420
|
+
# We only need to install the executables here. The default spec was
|
|
421
|
+
# already written above, and lib/bundler.rb and lib/bundler/* are
|
|
422
|
+
# available under the site_ruby directory.
|
|
407
423
|
installer.extract_bin
|
|
408
424
|
installer.generate_bin
|
|
409
|
-
installer.write_default_spec
|
|
410
425
|
ensure
|
|
411
426
|
FileUtils.rm_f built_gem
|
|
412
427
|
end
|
|
@@ -183,7 +183,10 @@ command to remove old versions.
|
|
|
183
183
|
say "Installing RubyGems #{version}" unless options[:silent]
|
|
184
184
|
|
|
185
185
|
installed = preparing_gem_layout_for(version) do
|
|
186
|
-
|
|
186
|
+
# Gem.ruby is quoted if it contains whitespace, so split it into argv
|
|
187
|
+
# elements to keep the quotes out of the spawned command.
|
|
188
|
+
require "shellwords"
|
|
189
|
+
system(*Shellwords.split(Gem.ruby), "--disable-gems", "setup.rb", *args)
|
|
187
190
|
end
|
|
188
191
|
|
|
189
192
|
unless options[:silent]
|
data/lib/rubygems/ext/builder.rb
CHANGED
|
@@ -32,7 +32,7 @@ class Gem::Ext::Builder
|
|
|
32
32
|
target_rbconfig["configure_args"] =~ /with-make-prog\=(\w+)/
|
|
33
33
|
make_program_name = ENV["MAKE"] || ENV["make"] || $1
|
|
34
34
|
make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make"
|
|
35
|
-
make_program =
|
|
35
|
+
make_program = shellsplit_command(make_program_name)
|
|
36
36
|
|
|
37
37
|
is_nmake = /\bnmake/i.match?(make_program_name)
|
|
38
38
|
# The installation of the bundled gems is failed when DESTDIR is empty in mswin platform.
|
|
@@ -102,6 +102,10 @@ class Gem::Ext::Builder
|
|
|
102
102
|
require "open3"
|
|
103
103
|
# Set $SOURCE_DATE_EPOCH for the subprocess.
|
|
104
104
|
build_env = { "SOURCE_DATE_EPOCH" => Gem.source_date_epoch_string }.merge(env)
|
|
105
|
+
# A single-element command would be parsed as a shell command line,
|
|
106
|
+
# splitting an unquoted command path containing spaces. Use the
|
|
107
|
+
# [cmdname, argv0] form to keep exec semantics.
|
|
108
|
+
command = [[command.first, command.first]] if command.size == 1
|
|
105
109
|
output, status = begin
|
|
106
110
|
Open3.popen2e(build_env, *command, chdir: dir) do |stdin, stdouterr, wait_thread|
|
|
107
111
|
stdin.close
|
|
@@ -148,6 +152,21 @@ class Gem::Ext::Builder
|
|
|
148
152
|
Shellwords.split(command)
|
|
149
153
|
end
|
|
150
154
|
|
|
155
|
+
##
|
|
156
|
+
# Splits a command string such as ENV["MAKE"] into an argument list.
|
|
157
|
+
#
|
|
158
|
+
# On Windows, a value that names an existing file, such as
|
|
159
|
+
# <tt>C:\path\nmake.exe</tt>, is taken as a single word because POSIX
|
|
160
|
+
# shell splitting would consume the backslashes as escape characters.
|
|
161
|
+
# Any other value is split like a POSIX shell command line, where a
|
|
162
|
+
# path containing spaces must be quoted.
|
|
163
|
+
|
|
164
|
+
def self.shellsplit_command(command)
|
|
165
|
+
return [command] if Gem.win_platform? && File.file?(command)
|
|
166
|
+
|
|
167
|
+
shellsplit(command)
|
|
168
|
+
end
|
|
169
|
+
|
|
151
170
|
def self.shelljoin(command)
|
|
152
171
|
require "shellwords"
|
|
153
172
|
|
|
@@ -14,13 +14,15 @@ class Gem::Ext::RakeBuilder < Gem::Ext::Builder
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
if /mkrf_conf/i.match?(File.basename(extension))
|
|
17
|
-
|
|
17
|
+
# Gem.ruby is quoted if it contains whitespace, so split it into argv
|
|
18
|
+
# elements to keep the quotes out of the spawned command.
|
|
19
|
+
run([*shellsplit(Gem.ruby), File.basename(extension), *args], results, class_name, extension_dir)
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
rake = ENV["rake"]
|
|
21
23
|
|
|
22
24
|
if rake
|
|
23
|
-
rake =
|
|
25
|
+
rake = shellsplit_command(rake)
|
|
24
26
|
else
|
|
25
27
|
begin
|
|
26
28
|
rake = ruby << "-rrubygems" << Gem.bin_path("rake", "rake")
|
data/lib/rubygems/source.rb
CHANGED
|
@@ -109,6 +109,13 @@ class Gem::Source
|
|
|
109
109
|
|
|
110
110
|
spec_file_name = name_tuple.spec_name
|
|
111
111
|
|
|
112
|
+
# The name tuple comes from a remote index and is not otherwise
|
|
113
|
+
# validated, so refuse anything that would escape the spec cache
|
|
114
|
+
# directory when used as a path component.
|
|
115
|
+
if File.basename(spec_file_name) != spec_file_name
|
|
116
|
+
raise Gem::Exception, "malformed spec name: #{spec_file_name.inspect}"
|
|
117
|
+
end
|
|
118
|
+
|
|
112
119
|
source_uri = enforce_trailing_slash(uri) + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"
|
|
113
120
|
|
|
114
121
|
cache_dir = cache_dir source_uri
|
data/lib/rubygems/util.rb
CHANGED
|
@@ -76,7 +76,9 @@ module Gem::Util
|
|
|
76
76
|
|
|
77
77
|
##
|
|
78
78
|
# Globs for files matching +pattern+ inside of +directory+,
|
|
79
|
-
# returning absolute paths to the matching files.
|
|
79
|
+
# returning absolute paths to the matching files. Unlike a plain
|
|
80
|
+
# Dir.glob with an interpolated path, glob metacharacters in
|
|
81
|
+
# +base_path+ are not treated as part of the pattern.
|
|
80
82
|
|
|
81
83
|
def self.glob_files_in_dir(glob, base_path)
|
|
82
84
|
Dir.glob(glob, base: base_path).map! {|f| File.expand_path(f, base_path) }
|
data/lib/rubygems.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubygems-update
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0.
|
|
4
|
+
version: 4.0.17
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jim Weirich
|
|
@@ -406,8 +406,6 @@ files:
|
|
|
406
406
|
- bundler/lib/bundler/vendor/thor/lib/thor/shell/wrapped_printer.rb
|
|
407
407
|
- bundler/lib/bundler/vendor/thor/lib/thor/util.rb
|
|
408
408
|
- bundler/lib/bundler/vendor/thor/lib/thor/version.rb
|
|
409
|
-
- bundler/lib/bundler/vendor/tsort/LICENSE.txt
|
|
410
|
-
- bundler/lib/bundler/vendor/tsort/lib/tsort.rb
|
|
411
409
|
- bundler/lib/bundler/vendor/uri/COPYING
|
|
412
410
|
- bundler/lib/bundler/vendor/uri/lib/uri.rb
|
|
413
411
|
- bundler/lib/bundler/vendor/uri/lib/uri/common.rb
|
|
@@ -725,7 +723,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
725
723
|
- !ruby/object:Gem::Version
|
|
726
724
|
version: '0'
|
|
727
725
|
requirements: []
|
|
728
|
-
rubygems_version: 4.0.
|
|
726
|
+
rubygems_version: 4.0.16
|
|
729
727
|
specification_version: 4
|
|
730
728
|
summary: RubyGems is a package management framework for Ruby. This gem is downloaded
|
|
731
729
|
and installed by `gem update --system`, so that the `gem` CLI can update itself.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
|
|
2
|
-
|
|
3
|
-
Redistribution and use in source and binary forms, with or without
|
|
4
|
-
modification, are permitted provided that the following conditions
|
|
5
|
-
are met:
|
|
6
|
-
1. Redistributions of source code must retain the above copyright
|
|
7
|
-
notice, this list of conditions and the following disclaimer.
|
|
8
|
-
2. Redistributions in binary form must reproduce the above copyright
|
|
9
|
-
notice, this list of conditions and the following disclaimer in the
|
|
10
|
-
documentation and/or other materials provided with the distribution.
|
|
11
|
-
|
|
12
|
-
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
13
|
-
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
14
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
15
|
-
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
16
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
17
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
18
|
-
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
19
|
-
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
20
|
-
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
21
|
-
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
22
|
-
SUCH DAMAGE.
|
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# tsort.rb - provides a module for topological sorting and strongly connected components.
|
|
5
|
-
#++
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
# Bundler::TSort implements topological sorting using Tarjan's algorithm for
|
|
10
|
-
# strongly connected components.
|
|
11
|
-
#
|
|
12
|
-
# Bundler::TSort is designed to be able to be used with any object which can be
|
|
13
|
-
# interpreted as a directed graph.
|
|
14
|
-
#
|
|
15
|
-
# Bundler::TSort requires two methods to interpret an object as a graph,
|
|
16
|
-
# tsort_each_node and tsort_each_child.
|
|
17
|
-
#
|
|
18
|
-
# * tsort_each_node is used to iterate for all nodes over a graph.
|
|
19
|
-
# * tsort_each_child is used to iterate for child nodes of a given node.
|
|
20
|
-
#
|
|
21
|
-
# The equality of nodes are defined by eql? and hash since
|
|
22
|
-
# Bundler::TSort uses Hash internally.
|
|
23
|
-
#
|
|
24
|
-
# == A Simple Example
|
|
25
|
-
#
|
|
26
|
-
# The following example demonstrates how to mix the Bundler::TSort module into an
|
|
27
|
-
# existing class (in this case, Hash). Here, we're treating each key in
|
|
28
|
-
# the hash as a node in the graph, and so we simply alias the required
|
|
29
|
-
# #tsort_each_node method to Hash's #each_key method. For each key in the
|
|
30
|
-
# hash, the associated value is an array of the node's child nodes. This
|
|
31
|
-
# choice in turn leads to our implementation of the required #tsort_each_child
|
|
32
|
-
# method, which fetches the array of child nodes and then iterates over that
|
|
33
|
-
# array using the user-supplied block.
|
|
34
|
-
#
|
|
35
|
-
# require 'bundler/vendor/tsort/lib/tsort'
|
|
36
|
-
#
|
|
37
|
-
# class Hash
|
|
38
|
-
# include Bundler::TSort
|
|
39
|
-
# alias tsort_each_node each_key
|
|
40
|
-
# def tsort_each_child(node, &block)
|
|
41
|
-
# fetch(node).each(&block)
|
|
42
|
-
# end
|
|
43
|
-
# end
|
|
44
|
-
#
|
|
45
|
-
# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort
|
|
46
|
-
# #=> [3, 2, 1, 4]
|
|
47
|
-
#
|
|
48
|
-
# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components
|
|
49
|
-
# #=> [[4], [2, 3], [1]]
|
|
50
|
-
#
|
|
51
|
-
# == A More Realistic Example
|
|
52
|
-
#
|
|
53
|
-
# A very simple `make' like tool can be implemented as follows:
|
|
54
|
-
#
|
|
55
|
-
# require 'bundler/vendor/tsort/lib/tsort'
|
|
56
|
-
#
|
|
57
|
-
# class Make
|
|
58
|
-
# def initialize
|
|
59
|
-
# @dep = {}
|
|
60
|
-
# @dep.default = []
|
|
61
|
-
# end
|
|
62
|
-
#
|
|
63
|
-
# def rule(outputs, inputs=[], &block)
|
|
64
|
-
# triple = [outputs, inputs, block]
|
|
65
|
-
# outputs.each {|f| @dep[f] = [triple]}
|
|
66
|
-
# @dep[triple] = inputs
|
|
67
|
-
# end
|
|
68
|
-
#
|
|
69
|
-
# def build(target)
|
|
70
|
-
# each_strongly_connected_component_from(target) {|ns|
|
|
71
|
-
# if ns.length != 1
|
|
72
|
-
# fs = ns.delete_if {|n| Array === n}
|
|
73
|
-
# raise Bundler::TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}")
|
|
74
|
-
# end
|
|
75
|
-
# n = ns.first
|
|
76
|
-
# if Array === n
|
|
77
|
-
# outputs, inputs, block = n
|
|
78
|
-
# inputs_time = inputs.map {|f| File.mtime f}.max
|
|
79
|
-
# begin
|
|
80
|
-
# outputs_time = outputs.map {|f| File.mtime f}.min
|
|
81
|
-
# rescue Errno::ENOENT
|
|
82
|
-
# outputs_time = nil
|
|
83
|
-
# end
|
|
84
|
-
# if outputs_time == nil ||
|
|
85
|
-
# inputs_time != nil && outputs_time <= inputs_time
|
|
86
|
-
# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i
|
|
87
|
-
# block.call
|
|
88
|
-
# end
|
|
89
|
-
# end
|
|
90
|
-
# }
|
|
91
|
-
# end
|
|
92
|
-
#
|
|
93
|
-
# def tsort_each_child(node, &block)
|
|
94
|
-
# @dep[node].each(&block)
|
|
95
|
-
# end
|
|
96
|
-
# include Bundler::TSort
|
|
97
|
-
# end
|
|
98
|
-
#
|
|
99
|
-
# def command(arg)
|
|
100
|
-
# print arg, "\n"
|
|
101
|
-
# system arg
|
|
102
|
-
# end
|
|
103
|
-
#
|
|
104
|
-
# m = Make.new
|
|
105
|
-
# m.rule(%w[t1]) { command 'date > t1' }
|
|
106
|
-
# m.rule(%w[t2]) { command 'date > t2' }
|
|
107
|
-
# m.rule(%w[t3]) { command 'date > t3' }
|
|
108
|
-
# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' }
|
|
109
|
-
# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' }
|
|
110
|
-
# m.build('t5')
|
|
111
|
-
#
|
|
112
|
-
# == Bugs
|
|
113
|
-
#
|
|
114
|
-
# * 'tsort.rb' is wrong name because this library uses
|
|
115
|
-
# Tarjan's algorithm for strongly connected components.
|
|
116
|
-
# Although 'strongly_connected_components.rb' is correct but too long.
|
|
117
|
-
#
|
|
118
|
-
# == References
|
|
119
|
-
#
|
|
120
|
-
# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms",
|
|
121
|
-
# <em>SIAM Journal on Computing</em>, Vol. 1, No. 2, pp. 146-160, June 1972.
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
module Bundler::TSort
|
|
125
|
-
|
|
126
|
-
VERSION = "0.2.0"
|
|
127
|
-
|
|
128
|
-
class Cyclic < StandardError
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Returns a topologically sorted array of nodes.
|
|
132
|
-
# The array is sorted from children to parents, i.e.
|
|
133
|
-
# the first element has no child and the last node has no parent.
|
|
134
|
-
#
|
|
135
|
-
# If there is a cycle, Bundler::TSort::Cyclic is raised.
|
|
136
|
-
#
|
|
137
|
-
# class G
|
|
138
|
-
# include Bundler::TSort
|
|
139
|
-
# def initialize(g)
|
|
140
|
-
# @g = g
|
|
141
|
-
# end
|
|
142
|
-
# def tsort_each_child(n, &b) @g[n].each(&b) end
|
|
143
|
-
# def tsort_each_node(&b) @g.each_key(&b) end
|
|
144
|
-
# end
|
|
145
|
-
#
|
|
146
|
-
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
|
|
147
|
-
# p graph.tsort #=> [4, 2, 3, 1]
|
|
148
|
-
#
|
|
149
|
-
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
|
|
150
|
-
# p graph.tsort # raises Bundler::TSort::Cyclic
|
|
151
|
-
#
|
|
152
|
-
def tsort
|
|
153
|
-
each_node = method(:tsort_each_node)
|
|
154
|
-
each_child = method(:tsort_each_child)
|
|
155
|
-
Bundler::TSort.tsort(each_node, each_child)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Returns a topologically sorted array of nodes.
|
|
159
|
-
# The array is sorted from children to parents, i.e.
|
|
160
|
-
# the first element has no child and the last node has no parent.
|
|
161
|
-
#
|
|
162
|
-
# The graph is represented by _each_node_ and _each_child_.
|
|
163
|
-
# _each_node_ should have +call+ method which yields for each node in the graph.
|
|
164
|
-
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
|
|
165
|
-
#
|
|
166
|
-
# If there is a cycle, Bundler::TSort::Cyclic is raised.
|
|
167
|
-
#
|
|
168
|
-
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
|
|
169
|
-
# each_node = lambda {|&b| g.each_key(&b) }
|
|
170
|
-
# each_child = lambda {|n, &b| g[n].each(&b) }
|
|
171
|
-
# p Bundler::TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1]
|
|
172
|
-
#
|
|
173
|
-
# g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
|
|
174
|
-
# each_node = lambda {|&b| g.each_key(&b) }
|
|
175
|
-
# each_child = lambda {|n, &b| g[n].each(&b) }
|
|
176
|
-
# p Bundler::TSort.tsort(each_node, each_child) # raises Bundler::TSort::Cyclic
|
|
177
|
-
#
|
|
178
|
-
def self.tsort(each_node, each_child)
|
|
179
|
-
tsort_each(each_node, each_child).to_a
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# The iterator version of the #tsort method.
|
|
183
|
-
# <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but
|
|
184
|
-
# modification of _obj_ during the iteration may lead to unexpected results.
|
|
185
|
-
#
|
|
186
|
-
# #tsort_each returns +nil+.
|
|
187
|
-
# If there is a cycle, Bundler::TSort::Cyclic is raised.
|
|
188
|
-
#
|
|
189
|
-
# class G
|
|
190
|
-
# include Bundler::TSort
|
|
191
|
-
# def initialize(g)
|
|
192
|
-
# @g = g
|
|
193
|
-
# end
|
|
194
|
-
# def tsort_each_child(n, &b) @g[n].each(&b) end
|
|
195
|
-
# def tsort_each_node(&b) @g.each_key(&b) end
|
|
196
|
-
# end
|
|
197
|
-
#
|
|
198
|
-
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
|
|
199
|
-
# graph.tsort_each {|n| p n }
|
|
200
|
-
# #=> 4
|
|
201
|
-
# # 2
|
|
202
|
-
# # 3
|
|
203
|
-
# # 1
|
|
204
|
-
#
|
|
205
|
-
def tsort_each(&block) # :yields: node
|
|
206
|
-
each_node = method(:tsort_each_node)
|
|
207
|
-
each_child = method(:tsort_each_child)
|
|
208
|
-
Bundler::TSort.tsort_each(each_node, each_child, &block)
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# The iterator version of the Bundler::TSort.tsort method.
|
|
212
|
-
#
|
|
213
|
-
# The graph is represented by _each_node_ and _each_child_.
|
|
214
|
-
# _each_node_ should have +call+ method which yields for each node in the graph.
|
|
215
|
-
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
|
|
216
|
-
#
|
|
217
|
-
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
|
|
218
|
-
# each_node = lambda {|&b| g.each_key(&b) }
|
|
219
|
-
# each_child = lambda {|n, &b| g[n].each(&b) }
|
|
220
|
-
# Bundler::TSort.tsort_each(each_node, each_child) {|n| p n }
|
|
221
|
-
# #=> 4
|
|
222
|
-
# # 2
|
|
223
|
-
# # 3
|
|
224
|
-
# # 1
|
|
225
|
-
#
|
|
226
|
-
def self.tsort_each(each_node, each_child) # :yields: node
|
|
227
|
-
return to_enum(__method__, each_node, each_child) unless block_given?
|
|
228
|
-
|
|
229
|
-
each_strongly_connected_component(each_node, each_child) {|component|
|
|
230
|
-
if component.size == 1
|
|
231
|
-
yield component.first
|
|
232
|
-
else
|
|
233
|
-
raise Cyclic.new("topological sort failed: #{component.inspect}")
|
|
234
|
-
end
|
|
235
|
-
}
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
# Returns strongly connected components as an array of arrays of nodes.
|
|
239
|
-
# The array is sorted from children to parents.
|
|
240
|
-
# Each elements of the array represents a strongly connected component.
|
|
241
|
-
#
|
|
242
|
-
# class G
|
|
243
|
-
# include Bundler::TSort
|
|
244
|
-
# def initialize(g)
|
|
245
|
-
# @g = g
|
|
246
|
-
# end
|
|
247
|
-
# def tsort_each_child(n, &b) @g[n].each(&b) end
|
|
248
|
-
# def tsort_each_node(&b) @g.each_key(&b) end
|
|
249
|
-
# end
|
|
250
|
-
#
|
|
251
|
-
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
|
|
252
|
-
# p graph.strongly_connected_components #=> [[4], [2], [3], [1]]
|
|
253
|
-
#
|
|
254
|
-
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
|
|
255
|
-
# p graph.strongly_connected_components #=> [[4], [2, 3], [1]]
|
|
256
|
-
#
|
|
257
|
-
def strongly_connected_components
|
|
258
|
-
each_node = method(:tsort_each_node)
|
|
259
|
-
each_child = method(:tsort_each_child)
|
|
260
|
-
Bundler::TSort.strongly_connected_components(each_node, each_child)
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
# Returns strongly connected components as an array of arrays of nodes.
|
|
264
|
-
# The array is sorted from children to parents.
|
|
265
|
-
# Each elements of the array represents a strongly connected component.
|
|
266
|
-
#
|
|
267
|
-
# The graph is represented by _each_node_ and _each_child_.
|
|
268
|
-
# _each_node_ should have +call+ method which yields for each node in the graph.
|
|
269
|
-
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
|
|
270
|
-
#
|
|
271
|
-
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
|
|
272
|
-
# each_node = lambda {|&b| g.each_key(&b) }
|
|
273
|
-
# each_child = lambda {|n, &b| g[n].each(&b) }
|
|
274
|
-
# p Bundler::TSort.strongly_connected_components(each_node, each_child)
|
|
275
|
-
# #=> [[4], [2], [3], [1]]
|
|
276
|
-
#
|
|
277
|
-
# g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
|
|
278
|
-
# each_node = lambda {|&b| g.each_key(&b) }
|
|
279
|
-
# each_child = lambda {|n, &b| g[n].each(&b) }
|
|
280
|
-
# p Bundler::TSort.strongly_connected_components(each_node, each_child)
|
|
281
|
-
# #=> [[4], [2, 3], [1]]
|
|
282
|
-
#
|
|
283
|
-
def self.strongly_connected_components(each_node, each_child)
|
|
284
|
-
each_strongly_connected_component(each_node, each_child).to_a
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
# The iterator version of the #strongly_connected_components method.
|
|
288
|
-
# <tt><em>obj</em>.each_strongly_connected_component</tt> is similar to
|
|
289
|
-
# <tt><em>obj</em>.strongly_connected_components.each</tt>, but
|
|
290
|
-
# modification of _obj_ during the iteration may lead to unexpected results.
|
|
291
|
-
#
|
|
292
|
-
# #each_strongly_connected_component returns +nil+.
|
|
293
|
-
#
|
|
294
|
-
# class G
|
|
295
|
-
# include Bundler::TSort
|
|
296
|
-
# def initialize(g)
|
|
297
|
-
# @g = g
|
|
298
|
-
# end
|
|
299
|
-
# def tsort_each_child(n, &b) @g[n].each(&b) end
|
|
300
|
-
# def tsort_each_node(&b) @g.each_key(&b) end
|
|
301
|
-
# end
|
|
302
|
-
#
|
|
303
|
-
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
|
|
304
|
-
# graph.each_strongly_connected_component {|scc| p scc }
|
|
305
|
-
# #=> [4]
|
|
306
|
-
# # [2]
|
|
307
|
-
# # [3]
|
|
308
|
-
# # [1]
|
|
309
|
-
#
|
|
310
|
-
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
|
|
311
|
-
# graph.each_strongly_connected_component {|scc| p scc }
|
|
312
|
-
# #=> [4]
|
|
313
|
-
# # [2, 3]
|
|
314
|
-
# # [1]
|
|
315
|
-
#
|
|
316
|
-
def each_strongly_connected_component(&block) # :yields: nodes
|
|
317
|
-
each_node = method(:tsort_each_node)
|
|
318
|
-
each_child = method(:tsort_each_child)
|
|
319
|
-
Bundler::TSort.each_strongly_connected_component(each_node, each_child, &block)
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
# The iterator version of the Bundler::TSort.strongly_connected_components method.
|
|
323
|
-
#
|
|
324
|
-
# The graph is represented by _each_node_ and _each_child_.
|
|
325
|
-
# _each_node_ should have +call+ method which yields for each node in the graph.
|
|
326
|
-
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
|
|
327
|
-
#
|
|
328
|
-
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
|
|
329
|
-
# each_node = lambda {|&b| g.each_key(&b) }
|
|
330
|
-
# each_child = lambda {|n, &b| g[n].each(&b) }
|
|
331
|
-
# Bundler::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
|
|
332
|
-
# #=> [4]
|
|
333
|
-
# # [2]
|
|
334
|
-
# # [3]
|
|
335
|
-
# # [1]
|
|
336
|
-
#
|
|
337
|
-
# g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
|
|
338
|
-
# each_node = lambda {|&b| g.each_key(&b) }
|
|
339
|
-
# each_child = lambda {|n, &b| g[n].each(&b) }
|
|
340
|
-
# Bundler::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
|
|
341
|
-
# #=> [4]
|
|
342
|
-
# # [2, 3]
|
|
343
|
-
# # [1]
|
|
344
|
-
#
|
|
345
|
-
def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes
|
|
346
|
-
return to_enum(__method__, each_node, each_child) unless block_given?
|
|
347
|
-
|
|
348
|
-
id_map = {}
|
|
349
|
-
stack = []
|
|
350
|
-
each_node.call {|node|
|
|
351
|
-
unless id_map.include? node
|
|
352
|
-
each_strongly_connected_component_from(node, each_child, id_map, stack) {|c|
|
|
353
|
-
yield c
|
|
354
|
-
}
|
|
355
|
-
end
|
|
356
|
-
}
|
|
357
|
-
nil
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
# Iterates over strongly connected component in the subgraph reachable from
|
|
361
|
-
# _node_.
|
|
362
|
-
#
|
|
363
|
-
# Return value is unspecified.
|
|
364
|
-
#
|
|
365
|
-
# #each_strongly_connected_component_from doesn't call #tsort_each_node.
|
|
366
|
-
#
|
|
367
|
-
# class G
|
|
368
|
-
# include Bundler::TSort
|
|
369
|
-
# def initialize(g)
|
|
370
|
-
# @g = g
|
|
371
|
-
# end
|
|
372
|
-
# def tsort_each_child(n, &b) @g[n].each(&b) end
|
|
373
|
-
# def tsort_each_node(&b) @g.each_key(&b) end
|
|
374
|
-
# end
|
|
375
|
-
#
|
|
376
|
-
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
|
|
377
|
-
# graph.each_strongly_connected_component_from(2) {|scc| p scc }
|
|
378
|
-
# #=> [4]
|
|
379
|
-
# # [2]
|
|
380
|
-
#
|
|
381
|
-
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
|
|
382
|
-
# graph.each_strongly_connected_component_from(2) {|scc| p scc }
|
|
383
|
-
# #=> [4]
|
|
384
|
-
# # [2, 3]
|
|
385
|
-
#
|
|
386
|
-
def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes
|
|
387
|
-
Bundler::TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block)
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
# Iterates over strongly connected components in a graph.
|
|
391
|
-
# The graph is represented by _node_ and _each_child_.
|
|
392
|
-
#
|
|
393
|
-
# _node_ is the first node.
|
|
394
|
-
# _each_child_ should have +call+ method which takes a node argument
|
|
395
|
-
# and yields for each child node.
|
|
396
|
-
#
|
|
397
|
-
# Return value is unspecified.
|
|
398
|
-
#
|
|
399
|
-
# #Bundler::TSort.each_strongly_connected_component_from is a class method and
|
|
400
|
-
# it doesn't need a class to represent a graph which includes Bundler::TSort.
|
|
401
|
-
#
|
|
402
|
-
# graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
|
|
403
|
-
# each_child = lambda {|n, &b| graph[n].each(&b) }
|
|
404
|
-
# Bundler::TSort.each_strongly_connected_component_from(1, each_child) {|scc|
|
|
405
|
-
# p scc
|
|
406
|
-
# }
|
|
407
|
-
# #=> [4]
|
|
408
|
-
# # [2, 3]
|
|
409
|
-
# # [1]
|
|
410
|
-
#
|
|
411
|
-
def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes
|
|
412
|
-
return to_enum(__method__, node, each_child, id_map, stack) unless block_given?
|
|
413
|
-
|
|
414
|
-
minimum_id = node_id = id_map[node] = id_map.size
|
|
415
|
-
stack_length = stack.length
|
|
416
|
-
stack << node
|
|
417
|
-
|
|
418
|
-
each_child.call(node) {|child|
|
|
419
|
-
if id_map.include? child
|
|
420
|
-
child_id = id_map[child]
|
|
421
|
-
minimum_id = child_id if child_id && child_id < minimum_id
|
|
422
|
-
else
|
|
423
|
-
sub_minimum_id =
|
|
424
|
-
each_strongly_connected_component_from(child, each_child, id_map, stack) {|c|
|
|
425
|
-
yield c
|
|
426
|
-
}
|
|
427
|
-
minimum_id = sub_minimum_id if sub_minimum_id < minimum_id
|
|
428
|
-
end
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if node_id == minimum_id
|
|
432
|
-
component = stack.slice!(stack_length .. -1)
|
|
433
|
-
component.each {|n| id_map[n] = nil}
|
|
434
|
-
yield component
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
minimum_id
|
|
438
|
-
end
|
|
439
|
-
|
|
440
|
-
# Should be implemented by a extended class.
|
|
441
|
-
#
|
|
442
|
-
# #tsort_each_node is used to iterate for all nodes over a graph.
|
|
443
|
-
#
|
|
444
|
-
def tsort_each_node # :yields: node
|
|
445
|
-
raise NotImplementedError.new
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
# Should be implemented by a extended class.
|
|
449
|
-
#
|
|
450
|
-
# #tsort_each_child is used to iterate for child nodes of _node_.
|
|
451
|
-
#
|
|
452
|
-
def tsort_each_child(node) # :yields: child
|
|
453
|
-
raise NotImplementedError.new
|
|
454
|
-
end
|
|
455
|
-
end
|