apkbuild-gem-resolver.in 8.81 KB
Newer Older
1 2 3
#!/usr/bin/ruby

# APKBUILD dependency resolver for RubyGems
4
# Copyright (C) 2014-2016 Kaarle Ritvanen
5 6

require 'augeas'
7
require 'optparse'
8 9 10 11
require 'rubygems/dependency'
require 'rubygems/resolver'
require 'rubygems/spec_fetcher'

12 13
class Package
  @@packages = {}
14

15
  def self.initialize level
16 17 18 19 20 21 22 23
    @@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
24
    fail unless @@augeas.match("/augeas#{apath}//error").empty?
25

26 27
    repos = ['main', 'community', 'testing']
    repos = repos[0..repos.index(level)]
28 29 30
    for repo in repos
      for pkg in @@augeas.match "#{apath}/#{repo}/*"
        Aport.new(pkg) unless pkg.end_with? '/ruby'
31 32 33
      end
    end

34 35
    Subpackage.initialize @@augeas.get("#{apath}/main/ruby/APKBUILD/pkgver")

36 37 38
    @@packages.each_value do |pkg|
      pkg.depends do |dep|
        dep.add_user pkg
39 40 41 42 43
      end
    end
  end

  def self.get name
44 45 46
    pkg = @@packages[name]
    raise 'Invalid package name: ' + name unless pkg
    pkg
47 48
  end

49 50 51 52
  def self.save
    fail unless @@augeas.save
  end

53
  def initialize name
54 55 56
    @name = name
    @depends = []
    @users = []
57
    @@packages[name] = self
58 59 60
  end

  def add_dependency name
61
    @depends << name
62 63
  end

64
  attr_reader :name
65 66 67

  def depends
    for dep in @depends
68 69 70 71 72 73
      # 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]
74 75 76 77 78 79 80 81 82 83 84
      end
    end
  end

  def users
    for user in @users
      yield user
    end
  end

  def add_user user
85
    @users << user
86 87 88
  end
end

89
class Aport < Package
90
  def initialize path
91
    super path.split('/')[-1]
92 93 94

    @path = path[6..-1]
    @apath = path + '/APKBUILD/'
95

96
    for dep in `echo #{get_param 'depends'}`.split
97
      add_dependency dep
98 99
    end
  end
100

101 102
  attr_reader :path

103 104 105 106 107 108 109 110
  def gem
    get_param '_gemname'
  end

  def version
    get_param 'pkgver'
  end

111 112 113 114 115 116 117 118 119 120
  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

121 122 123
  private

  def get_param name
124
    value = @@augeas.get(@apath + name)
125 126 127
    raise name + ' not defined for ' + @name unless value
    value
  end
128 129

  def set_param name, value
130
    @@augeas.set(@apath + name, value)
131
  end
132 133
end

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
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']
167 168 169 170 171 172 173 174
    },
    '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']
175 176 177 178 179 180 181 182
    },
    '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']
183 184 185 186 187 188 189
    }
  }

  @@subpackages = []

  def self.initialize version
    for name, attrs in RUBY_SUBPACKAGES[version]
190
      new name, attrs
191 192 193 194 195 196 197 198
    end
  end

  def self.each
    for pkg in @@subpackages
      yield pkg
    end
  end
199 200 201 202 203 204 205 206 207 208 209

  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
210 211
end

212 213

class Update
214
  def initialize
215 216 217 218 219
    @gems = {}
    @deps = []
  end

  def require_version name, version
220
    gem = assign(Package.get(name).gem, name)
221
    @deps << gem.dependency if gem.require_version version
222 223 224
  end

  def resolve
225
    for pkg in Subpackage
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
      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?
247
        gem.package.users do |user|
248
          ugem = @gems[user.gem]
249
          if !ugem || ugem.package.name != user.name
250 251 252 253 254 255 256 257 258 259 260
            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|
261 262
      update = gem.update
      yield update if update
263 264 265
    end
  end

266 267
  def assign name, package
    pkg = Package.get package
268 269 270

    if @gems.has_key? name
      gem = @gems[name]
271 272
      return gem if pkg == gem.package
      raise "Conflicting packages for gem #{name}: #{gem.package.name} and #{pkg.name}"
273 274
    end

275
    gem = PackagedGem.new self, name, pkg
276 277 278 279 280 281
    @gems[name] = gem
    gem
  end

  private

282 283
  class PackagedGem
    def initialize update, name, package
284 285
      @update = update
      @name = name
286
      @package = package
287 288
    end

289
    attr_reader :package
290 291 292 293 294 295 296 297 298 299 300

    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
301
      @version || @package.version
302 303 304
    end

    def updated?
305
      version != @package.version
306 307 308 309 310 311 312 313
    end

    def dependency
      Gem::Dependency.new(@name, version)
    end

    def check_deps
      specs, errors = Gem::SpecFetcher::fetcher.spec_for_dependency(dependency)
314
      raise "Invalid gem: #{@name}-#{version}" if specs.empty?
315 316 317 318 319
      fail if specs.length > 1
      deps = specs[0][0].runtime_dependencies

      @obsolete_deps = []

320
      @package.depends do |dep|
321 322 323
        gem = @update.assign(dep.gem, dep.name)
        gem.check_deps
        unless deps.reject! { |sdep| sdep.match? dep.gem, gem.version }
324
          @obsolete_deps << dep.name
325 326 327
        end
      end

328
      unless deps.empty?
329
        raise 'Undeclared dependencies in ' + @package.name + deps.inject('') {
330 331 332 333
          |s, dep| "#{s}\n#{dep.name} #{dep.requirements_list.join ' '}"
        }
      end
    end
334 335

    def update
336
      updated? || !@obsolete_deps.empty? ? (
337
        {
338
          :name => @package.name,
339
          :version => version,
340 341
          :obsolete_deps => @obsolete_deps.clone,
          :path => @package.path
342 343 344
        }
      ) : nil
    end
345 346 347 348
  end
end


349
level = 'main'
350
update_files = nil
351
OptionParser.new do |opts|
352 353 354
  opts.on('-c', '--community') do |c|
    level = 'community'
  end
355
  opts.on('-t', '--testing') do |t|
356
    level = 'testing'
357
  end
358
  opts.on('-u', '--update') do |u|
359
    update_files = []
360
  end
361
end.parse! ARGV
362
Package.initialize level
363

364 365 366 367 368 369 370 371 372 373 374
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]
375
  update.require_version name, match[4] || latest[Package.get(name).gem]
376 377 378 379
end

update.resolve

380
for pkg in update
381
  obsolete = pkg[:obsolete_deps]
382

383 384
  obs = obsolete.empty? ?
          nil : " (obsolete dependencies: #{obsolete.join ', '})"
385
  puts "#{pkg[:name]}-#{pkg[:version]}#{obs}"
386

387
  if update_files
388 389 390 391 392
    package = Package.get(pkg[:name])
    package.version = pkg[:version]
    for dep in obsolete
      package.del_dependency dep
    end
393
    update_files << pkg[:path]
394 395 396
  end
end

397
if update_files
398
  Package.save
399 400 401 402 403 404

  for path in update_files
    Dir.chdir(path) do
      fail unless system('abuild checksum')
    end
  end
405
end