summaryrefslogtreecommitdiffstats
path: root/apkbuild-gem-resolver.in
diff options
context:
space:
mode:
Diffstat (limited to 'apkbuild-gem-resolver.in')
-rw-r--r--apkbuild-gem-resolver.in386
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