diff options
Diffstat (limited to 'apkbuild-gem-resolver.in')
-rw-r--r-- | apkbuild-gem-resolver.in | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/apkbuild-gem-resolver.in b/apkbuild-gem-resolver.in new file mode 100644 index 0000000..d6782e1 --- /dev/null +++ b/apkbuild-gem-resolver.in @@ -0,0 +1,386 @@ +#!/usr/bin/ruby + +# APKBUILD dependency resolver for RubyGems +# Copyright (C) 2014-2015 Kaarle Ritvanen + +require 'augeas' +require 'optparse' +require 'rubygems/dependency' +require 'rubygems/resolver' +require 'rubygems/spec_fetcher' + +class Package + @@packages = {} + + def self.initialize testing + @@augeas = Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD) + dir = Dir.pwd + @@augeas.transform( + :lens => 'Shellvars.lns', :incl => dir + '/*/ruby*/APKBUILD' + ) + @@augeas.load + + apath = '/files' + dir + fail unless @@augeas.match("/augeas#{apath}//error").empty? + + repos = ['main'] + repos << 'testing' if testing + for repo in repos + for pkg in @@augeas.match "#{apath}/#{repo}/*" + Aport.new(pkg) unless pkg.end_with? '/ruby' + end + end + + Subpackage.initialize @@augeas.get("#{apath}/main/ruby/APKBUILD/pkgver") + + @@packages.each_value do |pkg| + pkg.depends do |dep| + dep.add_user pkg + end + end + end + + def self.get name + pkg = @@packages[name] + raise 'Invalid package name: ' + name unless pkg + pkg + end + + def self.save + fail unless @@augeas.save + end + + def initialize name + @name = name + @depends = [] + @users = [] + @@packages[name] = self + end + + def add_dependency name + @depends << name + end + + attr_reader :name + + def depends + for dep in @depends + # ruby-gems: workaround for v2.6 + if dep.start_with?('ruby-') && dep != 'ruby-gems' + unless @@packages.has_key? dep + raise "Dependency for #{@name} does not exist: #{dep}" + end + yield @@packages[dep] + end + end + end + + def users + for user in @users + yield user + end + end + + def add_user user + @users << user + end +end + +class Aport < Package + def initialize path + super path.split('/')[-1] + + @path = path[6..-1] + @apath = path + '/APKBUILD/' + + for dep in `echo #{get_param 'depends'}`.split + add_dependency dep + end + end + + attr_reader :path + + def gem + get_param '_gemname' + end + + def version + get_param 'pkgver' + end + + def version= version + set_param 'pkgver', version + set_param 'pkgrel', '0' + end + + def del_dependency name + @depends.delete name + set_param 'depends', "\"#{@depends.join ' '}\"" + end + + private + + def get_param name + value = @@augeas.get(@apath + name) + raise name + ' not defined for ' + @name unless value + value + end + + def set_param name, value + @@augeas.set(@apath + name, value) + end +end + +class Subpackage < Package + RUBY_SUBPACKAGES = { + '2.0.0_p353' => { + 'ruby-minitest' => ['minitest', '4.3.2'], + 'ruby-rake' => ['rake', '0.9.6'], + 'ruby-rdoc' => ['rdoc', '4.0.0', 'ruby-json'] + }, + '2.0.0_p481' => { + 'ruby-minitest' => ['minitest', '4.3.2'], + 'ruby-rake' => ['rake', '0.9.6'], + 'ruby-rdoc' => ['rdoc', '4.0.0', 'ruby-json'] + }, + '2.1.5' => { + 'ruby-json' => ['json', '1.8.1'], + 'ruby-minitest' => ['minitest', '4.7.5'], + 'ruby-rake' => ['rake', '10.1.0'], + 'ruby-rdoc' => ['rdoc', '4.1.0', 'ruby-json'] + }, + '2.2.1' => { + # it's actually 0.4.3 but that version is not published on network + 'ruby-io-console' => ['io-console', '0.4.2'], + 'ruby-json' => ['json', '1.8.1'], + 'ruby-minitest' => ['minitest', '5.4.3'], + 'ruby-rake' => ['rake', '10.4.2'], + 'ruby-rdoc' => ['rdoc', '4.2.0', 'ruby-json'] + }, + '2.2.2' => { + # it's actually 0.4.3 but that version is not published on network + 'ruby-io-console' => ['io-console', '0.4.2'], + 'ruby-json' => ['json', '1.8.1'], + 'ruby-minitest' => ['minitest', '5.4.3'], + 'ruby-rake' => ['rake', '10.4.2'], + 'ruby-rdoc' => ['rdoc', '4.2.0', 'ruby-json'] + } + } + + @@subpackages = [] + + def self.initialize version + for name, attrs in RUBY_SUBPACKAGES[version] + new name, attrs + end + end + + def self.each + for pkg in @@subpackages + yield pkg + end + end + + def initialize name, attrs + super name + @gem, @version, *deps = attrs + for dep in deps + add_dependency dep + end + @@subpackages << self + end + + attr_reader :gem, :version +end + + +class Update + def initialize + @gems = {} + @deps = [] + end + + def require_version name, version + gem = assign(Package.get(name).gem, name) + @deps << gem.dependency if gem.require_version version + end + + def resolve + for pkg in Subpackage + require_version pkg.name, pkg.version unless @gems[pkg.gem] + end + + def check_deps + @gems.clone.each_value do |gem| + gem.check_deps + end + end + + check_deps + + for req in Gem::Resolver.new(@deps).resolve + spec = req.spec + gem = @gems[spec.name] + gem.require_version spec.version.version if gem + end + + check_deps + + for name, gem in @gems + if gem.updated? + gem.package.users do |user| + ugem = @gems[user.gem] + if !ugem || ugem.package.name != user.name + Gem::Resolver.new( + [gem.dependency, Gem::Dependency.new(user.gem, user.version)] + ).resolve + end + end + end + end + end + + def each + @gems.each_value do |gem| + update = gem.update + yield update if update + end + end + + def assign name, package + pkg = Package.get package + + if @gems.has_key? name + gem = @gems[name] + return gem if pkg == gem.package + raise "Conflicting packages for gem #{name}: #{gem.package.name} and #{pkg.name}" + end + + gem = PackagedGem.new self, name, pkg + @gems[name] = gem + gem + end + + private + + class PackagedGem + def initialize update, name, package + @update = update + @name = name + @package = package + end + + attr_reader :package + + def require_version version + if @version + return false if version == @version + raise "Conflicting versions for gem #{@name}: #{@version} and #{version}" + end + @version = version + true + end + + def version + @version || @package.version + end + + def updated? + version != @package.version + end + + def dependency + Gem::Dependency.new(@name, version) + end + + def check_deps + specs, errors = Gem::SpecFetcher::fetcher.spec_for_dependency(dependency) + raise "Invalid gem: #{@name}-#{version}" if specs.empty? + fail if specs.length > 1 + deps = specs[0][0].runtime_dependencies + + @obsolete_deps = [] + + @package.depends do |dep| + gem = @update.assign(dep.gem, dep.name) + gem.check_deps + unless deps.reject! { |sdep| sdep.match? dep.gem, gem.version } + @obsolete_deps << dep.name + end + end + + unless deps.empty? + raise 'Undeclared dependencies in ' + @package.name + deps.inject('') { + |s, dep| "#{s}\n#{dep.name} #{dep.requirements_list.join ' '}" + } + end + end + + def update + updated? || !@obsolete_deps.empty? ? ( + { + :name => @package.name, + :version => version, + :obsolete_deps => @obsolete_deps.clone, + :path => @package.path + } + ) : nil + end + end +end + + +testing = false +update_files = nil +OptionParser.new do |opts| + opts.on('-t', '--testing') do |t| + testing = t + end + opts.on('-u', '--update') do |u| + update_files = [] + end +end.parse! ARGV +Package.initialize testing + +latest = {} +for source, gems in Gem::SpecFetcher::fetcher.available_specs(:latest)[0] + for gem in gems + latest[gem.name] = gem.version.version + end +end + +update = Update.new +for arg in ARGV + match = /^(([^-]|-[^\d])+)(-(\d.*))?/.match arg + name = match[1] + update.require_version name, match[4] || latest[Package.get(name).gem] +end + +update.resolve + +for pkg in update + obsolete = pkg[:obsolete_deps] + + obs = obsolete.empty? ? + nil : " (obsolete dependencies: #{obsolete.join ', '})" + puts "#{pkg[:name]}-#{pkg[:version]}#{obs}" + + if update_files + package = Package.get(pkg[:name]) + package.version = pkg[:version] + for dep in obsolete + package.del_dependency dep + end + update_files << pkg[:path] + end +end + +if update_files + Package.save + + for path in update_files + Dir.chdir(path) do + fail unless system('abuild checksum') + end + end +end |