apkbuild-gem-resolver.in 8.07 KB
Newer Older
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
1 2 3
#!/usr/bin/ruby

# APKBUILD dependency resolver for RubyGems
4
# Copyright (C) 2014-2015 Kaarle Ritvanen
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
5 6

require 'augeas'
7
require 'optparse'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
8 9 10 11
require 'rubygems/dependency'
require 'rubygems/resolver'
require 'rubygems/spec_fetcher'

12 13
class Package
  @@packages = {}
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
14

15
  def self.initialize testing
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 28 29 30

    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'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
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
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
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
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
47 48
  end

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

53
  def initialize name
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
54 55 56
    @name = name
    @depends = []
    @users = []
57
    @@packages[name] = self
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
58 59 60
  end

  def add_dependency name
61
    @depends << name
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
62 63
  end

64
  attr_reader :name
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
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]
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
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
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
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/'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
95

96
    for dep in `echo #{get_param 'depends'}`.split
97
      add_dependency dep
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
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
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
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 167 168 169 170 171 172 173
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]
174
      new name, attrs
175 176 177 178 179 180 181 182
    end
  end

  def self.each
    for pkg in @@subpackages
      yield pkg
    end
  end
183 184 185 186 187 188 189 190 191 192 193

  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
194 195
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
196 197 198 199 200 201 202 203

class Update
  def initialize 
    @gems = {}
    @deps = []
  end

  def require_version name, version
204
    gem = assign(Package.get(name).gem, name)
205
    @deps << gem.dependency if gem.require_version version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
206 207 208
  end

  def resolve
209
    for pkg in Subpackage
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
      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?
231
        gem.package.users do |user|
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
232
          ugem = @gems[user.gem]
233
          if !ugem || ugem.package.name != user.name
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
234 235 236 237 238 239 240 241 242 243 244
            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|
245 246
      update = gem.update
      yield update if update
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
247 248 249
    end
  end

250 251
  def assign name, package
    pkg = Package.get package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
252 253 254

    if @gems.has_key? name
      gem = @gems[name]
255 256
      return gem if pkg == gem.package
      raise "Conflicting packages for gem #{name}: #{gem.package.name} and #{pkg.name}"
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
257 258
    end

259
    gem = PackagedGem.new self, name, pkg
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
260 261 262 263 264 265
    @gems[name] = gem
    gem
  end

  private

266 267
  class PackagedGem
    def initialize update, name, package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
268 269
      @update = update
      @name = name
270
      @package = package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
271 272
    end

273
    attr_reader :package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
274 275 276 277 278 279 280 281 282 283 284

    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
285
      @version || @package.version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
286 287 288
    end

    def updated?
289
      version != @package.version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
290 291 292 293 294 295 296 297
    end

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

    def check_deps
      specs, errors = Gem::SpecFetcher::fetcher.spec_for_dependency(dependency)
298
      raise "Invalid gem: #{@name}-#{version}" if specs.empty?
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
299 300 301 302 303
      fail if specs.length > 1
      deps = specs[0][0].runtime_dependencies

      @obsolete_deps = []

304
      @package.depends do |dep|
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
305 306 307
        gem = @update.assign(dep.gem, dep.name)
        gem.check_deps
        unless deps.reject! { |sdep| sdep.match? dep.gem, gem.version }
308
          @obsolete_deps << dep.name
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
309 310 311
        end
      end

312
      unless deps.empty?
313
        raise 'Undeclared dependencies in ' + @package.name + deps.inject('') {
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
314 315 316 317
          |s, dep| "#{s}\n#{dep.name} #{dep.requirements_list.join ' '}"
        }
      end
    end
318 319

    def update
320
      updated? || !@obsolete_deps.empty? ? (
321
        {
322
          :name => @package.name,
323
          :version => version,
324 325
          :obsolete_deps => @obsolete_deps.clone,
          :path => @package.path
326 327 328
        }
      ) : nil
    end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
329 330 331 332
  end
end


333
testing = false
334
update_files = nil
335 336 337 338
OptionParser.new do |opts|
  opts.on('-t', '--testing') do |t|
    testing = t
  end
339
  opts.on('-u', '--update') do |u|
340
    update_files = []
341
  end
342
end.parse! ARGV
343
Package.initialize testing
344

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
345 346 347 348 349 350 351 352 353 354 355
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]
356
  update.require_version name, match[4] || latest[Package.get(name).gem]
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
357 358 359 360
end

update.resolve

361
for pkg in update
362
  obsolete = pkg[:obsolete_deps]
363

364 365
  obs = obsolete.empty? ?
          nil : " (obsolete dependencies: #{obsolete.join ', '})"
366
  puts "#{pkg[:name]}-#{pkg[:version]}#{obs}"
367

368
  if update_files
369 370 371 372 373
    package = Package.get(pkg[:name])
    package.version = pkg[:version]
    for dep in obsolete
      package.del_dependency dep
    end
374
    update_files << pkg[:path]
375 376 377
  end
end

378
if update_files
379
  Package.save
380 381 382 383 384 385

  for path in update_files
    Dir.chdir(path) do
      fail unless system('abuild checksum')
    end
  end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
386
end