406 lines
8.8 KiB
Ruby
406 lines
8.8 KiB
Ruby
#!/usr/bin/ruby
|
|
|
|
# APKBUILD dependency resolver for RubyGems
|
|
# Copyright (C) 2014-2016 Kaarle Ritvanen
|
|
|
|
require 'augeas'
|
|
require 'optparse'
|
|
require 'rubygems/dependency'
|
|
require 'rubygems/resolver'
|
|
require 'rubygems/spec_fetcher'
|
|
|
|
class Package
|
|
@@packages = {}
|
|
|
|
def self.initialize level
|
|
@@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', 'community', 'testing']
|
|
repos = repos[0..repos.index(level)]
|
|
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']
|
|
},
|
|
'2.2.3' => {
|
|
# 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.4' => {
|
|
# 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
|
|
|
|
|
|
level = 'main'
|
|
update_files = nil
|
|
OptionParser.new do |opts|
|
|
opts.on('-c', '--community') do |c|
|
|
level = 'community'
|
|
end
|
|
opts.on('-t', '--testing') do |t|
|
|
level = 'testing'
|
|
end
|
|
opts.on('-u', '--update') do |u|
|
|
update_files = []
|
|
end
|
|
end.parse! ARGV
|
|
Package.initialize level
|
|
|
|
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
|