apkbuild-gem-resolver.in 8.39 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
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
    }
  }

  @@subpackages = []

  def self.initialize version
    for name, attrs in RUBY_SUBPACKAGES[version]
182
      new name, attrs
183 184 185 186 187 188 189 190
    end
  end

  def self.each
    for pkg in @@subpackages
      yield pkg
    end
  end
191 192 193 194 195 196 197 198 199 200 201

  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
202 203
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
204 205 206 207 208 209 210 211

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

  def require_version name, version
212
    gem = assign(Package.get(name).gem, name)
213
    @deps << gem.dependency if gem.require_version version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
214 215 216
  end

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

258 259
  def assign name, package
    pkg = Package.get package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
260 261 262

    if @gems.has_key? name
      gem = @gems[name]
263 264
      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
265 266
    end

267
    gem = PackagedGem.new self, name, pkg
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
268 269 270 271 272 273
    @gems[name] = gem
    gem
  end

  private

274 275
  class PackagedGem
    def initialize update, name, package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
276 277
      @update = update
      @name = name
278
      @package = package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
279 280
    end

281
    attr_reader :package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
282 283 284 285 286 287 288 289 290 291 292

    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
293
      @version || @package.version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
294 295 296
    end

    def updated?
297
      version != @package.version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
298 299 300 301 302 303 304 305
    end

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

    def check_deps
      specs, errors = Gem::SpecFetcher::fetcher.spec_for_dependency(dependency)
306
      raise "Invalid gem: #{@name}-#{version}" if specs.empty?
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
307 308 309 310 311
      fail if specs.length > 1
      deps = specs[0][0].runtime_dependencies

      @obsolete_deps = []

312
      @package.depends do |dep|
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
313 314 315
        gem = @update.assign(dep.gem, dep.name)
        gem.check_deps
        unless deps.reject! { |sdep| sdep.match? dep.gem, gem.version }
316
          @obsolete_deps << dep.name
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
317 318 319
        end
      end

320
      unless deps.empty?
321
        raise 'Undeclared dependencies in ' + @package.name + deps.inject('') {
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
322 323 324 325
          |s, dep| "#{s}\n#{dep.name} #{dep.requirements_list.join ' '}"
        }
      end
    end
326 327

    def update
328
      updated? || !@obsolete_deps.empty? ? (
329
        {
330
          :name => @package.name,
331
          :version => version,
332 333
          :obsolete_deps => @obsolete_deps.clone,
          :path => @package.path
334 335 336
        }
      ) : nil
    end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
337 338 339 340
  end
end


341
testing = false
342
update_files = nil
343 344 345 346
OptionParser.new do |opts|
  opts.on('-t', '--testing') do |t|
    testing = t
  end
347
  opts.on('-u', '--update') do |u|
348
    update_files = []
349
  end
350
end.parse! ARGV
351
Package.initialize testing
352

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
353 354 355 356 357 358 359 360 361 362 363
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]
364
  update.require_version name, match[4] || latest[Package.get(name).gem]
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
365 366 367 368
end

update.resolve

369
for pkg in update
370
  obsolete = pkg[:obsolete_deps]
371

372 373
  obs = obsolete.empty? ?
          nil : " (obsolete dependencies: #{obsolete.join ', '})"
374
  puts "#{pkg[:name]}-#{pkg[:version]}#{obs}"
375

376
  if update_files
377 378 379 380 381
    package = Package.get(pkg[:name])
    package.version = pkg[:version]
    for dep in obsolete
      package.del_dependency dep
    end
382
    update_files << pkg[:path]
383 384 385
  end
end

386
if update_files
387
  Package.save
388 389 390 391 392 393

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