apkbuild-gem-resolver.in 7.83 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 92
    super path.split('/')[-1]
    @path = path + '/APKBUILD/'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
93

94
    for dep in `echo #{get_param 'depends'}`.split
95
      add_dependency dep
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
96 97
    end
  end
98 99 100 101 102 103 104 105 106

  def gem
    get_param '_gemname'
  end

  def version
    get_param 'pkgver'
  end

107 108 109 110 111 112 113 114 115 116
  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

117 118 119 120 121 122 123
  private

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

  def set_param name, value
    @@augeas.set(@path + name, value)
  end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
128 129
end

130 131 132 133 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
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]
170
      new name, attrs
171 172 173 174 175 176 177 178
    end
  end

  def self.each
    for pkg in @@subpackages
      yield pkg
    end
  end
179 180 181 182 183 184 185 186 187 188 189

  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
190 191
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
192 193 194 195 196 197 198 199

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

  def require_version name, version
200
    gem = assign(Package.get(name).gem, name)
201
    @deps << gem.dependency if gem.require_version version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
202 203 204
  end

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

246 247
  def assign name, package
    pkg = Package.get package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
248 249 250

    if @gems.has_key? name
      gem = @gems[name]
251 252
      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
253 254
    end

255
    gem = PackagedGem.new self, name, pkg
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
256 257 258 259 260 261
    @gems[name] = gem
    gem
  end

  private

262 263
  class PackagedGem
    def initialize update, name, package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
264 265
      @update = update
      @name = name
266
      @package = package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
267 268
    end

269
    attr_reader :package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
270 271 272 273 274 275 276 277 278 279 280

    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
281
      @version || @package.version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
282 283 284
    end

    def updated?
285
      version != @package.version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
286 287 288 289 290 291 292 293
    end

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

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

      @obsolete_deps = []

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

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

    def update
316
      updated? || !@obsolete_deps.empty? ? (
317
        {
318
          :name => @package.name,
319 320 321 322 323
          :version => version,
          :obsolete_deps => @obsolete_deps.clone
        }
      ) : nil
    end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
324 325 326 327
  end
end


328
testing = false
329
update = false
330 331 332 333
OptionParser.new do |opts|
  opts.on('-t', '--testing') do |t|
    testing = t
  end
334 335 336
  opts.on('-u', '--update') do |u|
    update = u
  end
337
end.parse! ARGV
338
Package.initialize testing
339

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

update.resolve

356
for pkg in update
357
  obsolete = pkg[:obsolete_deps]
358

359 360
  obs = obsolete.empty? ?
          nil : " (obsolete dependencies: #{obsolete.join ', '})"
361
  puts "#{pkg[:name]}-#{pkg[:version]}#{obs}"
362 363 364 365 366 367 368 369 370 371 372 373

  if update
    package = Package.get(pkg[:name])
    package.version = pkg[:version]
    for dep in obsolete
      package.del_dependency dep
    end
  end
end

if update
  Package.save
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
374
end