diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..db1d8e9 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,4 @@ +changelog: + exclude: + labels: + - dependencies # Added by Dependabot diff --git a/.rubocop.yml b/.rubocop.yml index 67680fa..4be2af1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -9,6 +9,8 @@ AllCops: - pkg/**/* - tmp/**/* - bundler/tmp/**/* + - lib/compact_index.rb + - lib/compact_index/**/* - lib/rubygems/resolver/molinillo/**/* - lib/rubygems/tsort/**/* - lib/rubygems/optparse/**/* diff --git a/Manifest.txt b/Manifest.txt index d915241..39a745a 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -4,6 +4,11 @@ LICENSE.txt MIT.txt Manifest.txt README.md +lib/compact_index.rb +lib/compact_index/dependency.rb +lib/compact_index/gem.rb +lib/compact_index/gem_version.rb +lib/compact_index/versions_file.rb lib/rubygems/commands/generate_index_command.rb lib/rubygems/indexer.rb lib/rubygems_plugin.rb diff --git a/Rakefile b/Rakefile index 86ad355..087ea7c 100644 --- a/Rakefile +++ b/Rakefile @@ -56,3 +56,29 @@ task :check_manifest do abort "Manifest is out of date. Run `rake update_manifest` to sync it" end end + +namespace :vendor do + desc "Sync vendored compact_index from rubygems/rubygems.org (REF=master)" + task :compact_index do + require "open-uri" + require "fileutils" + + ref = ENV["REF"] || "master" + repo = "rubygems/rubygems.org" + paths = %w[ + lib/compact_index.rb + lib/compact_index/dependency.rb + lib/compact_index/gem.rb + lib/compact_index/gem_version.rb + lib/compact_index/versions_file.rb + ] + + paths.each do |path| + url = "https://raw.githubusercontent.com/#{repo}/#{ref}/#{path}" + puts "Fetching #{url}" + content = URI.parse(url).open(&:read) + FileUtils.mkdir_p(File.dirname(path)) + File.write(path, content) + end + end +end diff --git a/lib/compact_index.rb b/lib/compact_index.rb new file mode 100644 index 0000000..899c577 --- /dev/null +++ b/lib/compact_index.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Vendored from https://github.com/rubygems/compact_index v0.15.0 + +require_relative "compact_index/gem" +require_relative "compact_index/gem_version" +require_relative "compact_index/dependency" + +require_relative "compact_index/versions_file" + +module CompactIndex + def self.names(gem_names) + gem_names.join("\n").prepend("---\n") << "\n" + end + + def self.versions(versions_file, gems = nil, args = {}) + versions_file.contents(gems, args) + end + + def self.info(versions) + versions.inject(+"---\n") do |output, version| + output << version.to_line << "\n" + end + end +end diff --git a/lib/compact_index/dependency.rb b/lib/compact_index/dependency.rb new file mode 100644 index 0000000..bb3ba1b --- /dev/null +++ b/lib/compact_index/dependency.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module CompactIndex + Dependency = Struct.new(:gem, :version, :platform, :checksum) do + def version_and_platform + if platform.nil? || platform == "ruby" + version + else + "#{version}-#{platform}" + end + end + end +end diff --git a/lib/compact_index/gem.rb b/lib/compact_index/gem.rb new file mode 100644 index 0000000..7358052 --- /dev/null +++ b/lib/compact_index/gem.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module CompactIndex + Gem = Struct.new(:name, :versions) do + def <=>(other) + name <=> other.name + end + end +end diff --git a/lib/compact_index/gem_version.rb b/lib/compact_index/gem_version.rb new file mode 100644 index 0000000..f083b3f --- /dev/null +++ b/lib/compact_index/gem_version.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module CompactIndex + GemVersion = Struct.new(:number, :platform, :checksum, :info_checksum, + :dependencies, :ruby_version, :rubygems_version) do + def number_and_platform + if platform.nil? || platform == "ruby" + number + else + "#{number}-#{platform}" + end + end + + def <=>(other) + number_comp = number <=> other.number + + if number_comp.zero? + [number, platform].compact <=> [other.number, other.platform].compact + else + number_comp + end + end + + def to_line + line = "#{number_and_platform} #{deps_line}|checksum:#{checksum}" + line << ",ruby:#{ruby_version_line}" if ruby_version && ruby_version != ">= 0" + line << ",rubygems:#{rubygems_version_line}" if rubygems_version && rubygems_version != ">= 0" + line + end + + private + + def ruby_version_line + join_multiple(ruby_version) + end + + def rubygems_version_line + join_multiple(rubygems_version) + end + + def deps_line + return "" if dependencies.nil? + + dependencies.map do |d| + [d[:gem], join_multiple(d.version_and_platform)].join(":") + end.join(",") + end + + def join_multiple(requirements) + requirements = requirements.split(", ") + requirements.sort! + requirements.join("&") + end + end +end diff --git a/lib/compact_index/versions_file.rb b/lib/compact_index/versions_file.rb new file mode 100644 index 0000000..77425c4 --- /dev/null +++ b/lib/compact_index/versions_file.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "time" +require "date" +require "digest" + +module CompactIndex + class VersionsFile + def initialize(file = nil) + @path = file || "/versions.list" + end + + def contents(gems = nil, args = {}) + gems = calculate_info_checksums(gems) if args.delete(:calculate_info_checksums) { false } + + raise ArgumentError, "Unknown options: #{args.keys.join(', ')}" unless args.empty? + + File.read(@path).tap do |out| + out << gem_lines(gems) if gems + end + end + + def updated_at + created_at_header(@path) || Time.at(0).utc.to_datetime + end + + def create(gems, timestamp = Time.now.iso8601) + gems.sort! + + File.open(@path, "w") do |io| + io.write "created_at: #{timestamp}\n---\n" + io.write gem_lines(gems) + end + end + + private + + def gem_lines(gems) + gems.reduce(+"") do |lines, gem| + version_numbers = gem.versions.map(&:number_and_platform).join(",") + lines << gem.name << + " " << version_numbers << + " #{gem.versions.last.info_checksum}\n" + end + end + + def calculate_info_checksums(gems) + gems.each do |gem| + info_checksum = Digest::MD5.hexdigest(CompactIndex.info(gem[:versions])) + gem[:versions].last[:info_checksum] = info_checksum + end + end + + def created_at_header(path) + return unless File.exist? path + + File.open(path) do |file| + file.each_line do |line| + line.match(/created_at: (.*)\n|---\n/) do |match| + return match[1] && DateTime.parse(match[1]) + end + end + end + + nil + end + end +end diff --git a/lib/rubygems/indexer.rb b/lib/rubygems/indexer.rb index dccaa58..4ff7c79 100644 --- a/lib/rubygems/indexer.rb +++ b/lib/rubygems/indexer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "compact_index" +require_relative "../compact_index" require "rubygems" require "rubygems/package" require "tmpdir" diff --git a/rubygems-generate_index.gemspec b/rubygems-generate_index.gemspec index b5ceb5c..b9b0946 100644 --- a/rubygems-generate_index.gemspec +++ b/rubygems-generate_index.gemspec @@ -28,6 +28,4 @@ Gem::Specification.new do |s| s.required_ruby_version = Gem::Requirement.new(">= 3.0.0") s.required_rubygems_version = Gem::Requirement.new(">= 0") - - s.add_dependency "compact_index", "~> 0.15.0" end