apkbuild-gem-resolver.in 7.19 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 24 25 26 27 28 29 30
    @@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 if @@augeas.match("/augeas#{apath}//error").length > 0

    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 49 50 51 52 53 54
  end

  def initialize name, gem, version
    @name = name
    @gem = gem
    @version = version
    @depends = []
    @users = []
55
    @@packages[name] = self
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
56 57 58
  end

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

  attr_reader :gem, :name, :version

  def depends
    for dep in @depends
66
      unless @@packages.has_key? dep
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
67 68
        raise "Dependency for #{@name} does not exist: #{dep}"
      end
69
      yield @@packages[dep]
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
70 71 72 73 74 75 76 77 78 79
    end
  end

  def users
    for user in @users
      yield user
    end
  end

  def add_user user
80
    @users << user
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
81 82 83
  end
end

84
class Aport < Package
85
  def initialize path
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
86 87 88
    name = path.split('/')[-1]

    get = proc{ |param|
89
      res = @@augeas.get(path + '/APKBUILD/' + param)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
90 91 92 93 94 95 96
      raise param + ' not defined for ' + name unless res
      res
    }

    super name, get.call('_gemname'), get.call('pkgver')

    for dep in `echo #{get.call('depends')}`.split
97 98
      # ruby-gems: workaround for v2.6
      add_dependency dep if dep.start_with?('ruby-') && dep != 'ruby-gems'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
99 100 101 102
    end
  end
end

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 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
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]
      gem, version, *deps = attrs
      pkg = new name, gem, version
      for dep in deps
        pkg.add_dependency dep
      end
      @@subpackages << pkg
    end
  end

  def self.each
    for pkg in @@subpackages
      yield pkg
    end
  end
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
159 160 161 162 163 164 165 166

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

  def require_version name, version
167
    gem = assign(Package.get(name).gem, name)
168
    @deps << gem.dependency if gem.require_version version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
169 170 171
  end

  def resolve
172
    for pkg in Subpackage
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
      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?
194
        gem.package.users do |user|
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
195
          ugem = @gems[user.gem]
196
          if !ugem || ugem.package.name != user.name
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
197 198 199 200 201 202 203 204 205 206 207
            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|
208 209
      update = gem.update
      yield update if update
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
210 211 212
    end
  end

213 214
  def assign name, package
    pkg = Package.get package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
215 216 217

    if @gems.has_key? name
      gem = @gems[name]
218 219
      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
220 221
    end

222
    gem = PackagedGem.new self, name, pkg
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
223 224 225 226 227 228
    @gems[name] = gem
    gem
  end

  private

229 230
  class PackagedGem
    def initialize update, name, package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
231 232
      @update = update
      @name = name
233
      @package = package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
234 235
    end

236
    attr_reader :package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
237 238 239 240 241 242 243 244 245 246 247

    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
248
      @version || @package.version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
249 250 251
    end

    def updated?
252
      version != @package.version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
253 254 255 256 257 258 259 260 261 262 263 264 265 266
    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.length == 0
      fail if specs.length > 1
      deps = specs[0][0].runtime_dependencies

      @obsolete_deps = []

267
      @package.depends do |dep|
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
268 269 270
        gem = @update.assign(dep.gem, dep.name)
        gem.check_deps
        unless deps.reject! { |sdep| sdep.match? dep.gem, gem.version }
271
          @obsolete_deps << dep.name
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
272 273 274 275
        end
      end

      if deps.length > 0
276
        raise 'Undeclared dependencies in ' + @package.name + deps.inject('') {
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
277 278 279 280
          |s, dep| "#{s}\n#{dep.name} #{dep.requirements_list.join ' '}"
        }
      end
    end
281 282 283 284

    def update
      updated? || @obsolete_deps.length > 0 ? (
        {
285
          :name => @package.name,
286 287 288 289 290
          :version => version,
          :obsolete_deps => @obsolete_deps.clone
        }
      ) : nil
    end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
291 292 293 294
  end
end


295 296 297 298 299 300
testing = false
OptionParser.new do |opts|
  opts.on('-t', '--testing') do |t|
    testing = t
  end
end.parse! ARGV
301
Package.initialize testing
302

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
303 304 305 306 307 308 309 310 311 312 313
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]
314
  update.require_version name, match[4] || latest[Package.get(name).gem]
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
315 316 317 318
end

update.resolve

319 320 321 322 323
for pkg in update
  obs = pkg[:obsolete_deps]
  obs = obs.length == 0 ? nil : " (obsolete dependencies: #{obs.join ', '})"

  puts "#{pkg[:name]}-#{pkg[:version]}#{obs}"
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
324
end