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
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
16 17 18 19 20 21 22 23
    Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD) do |aug|
      dir = Dir.pwd
      aug.transform(:lens => 'Shellvars.lns', :incl => dir + '/*/ruby*/APKBUILD')
      aug.load

      apath = '/files' + dir
      fail if aug.match("/augeas#{apath}//error").length > 0

24
      repos = ['main']
25
      repos << 'testing' if testing
26
      for repo in repos
27 28
        for pkg in aug.match "#{apath}/#{repo}/*"
          Aport.new(aug, pkg) unless pkg.end_with? '/ruby'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
29 30 31
        end
      end

32
      Subpackage.initialize aug.get("#{apath}/main/ruby/APKBUILD/pkgver")
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
33 34
    end

35 36 37
    @@packages.each_value do |pkg|
      pkg.depends do |dep|
        dep.add_user pkg
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
38 39 40 41 42
      end
    end
  end

  def self.get name
43 44 45
    pkg = @@packages[name]
    raise 'Invalid package name: ' + name unless pkg
    pkg
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
46 47 48 49 50 51 52 53
  end

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

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

  attr_reader :gem, :name, :version

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

  def users
    for user in @users
      yield user
    end
  end

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

83
class Aport < Package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
84 85 86 87 88 89 90 91 92 93 94 95
  def initialize aug, path
    name = path.split('/')[-1]

    get = proc{ |param|
      res = aug.get(path + '/APKBUILD/' + param)
      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
96 97
      # ruby-gems: workaround for v2.6
      add_dependency dep if dep.start_with?('ruby-') && dep != 'ruby-gems'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
98 99 100 101
    end
  end
end

102 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
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
158 159 160 161 162 163 164 165

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

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

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

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

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

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

  private

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

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

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

    def updated?
251
      version != @package.version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
252 253 254 255 256 257 258 259 260 261 262 263 264 265
    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 = []

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

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

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


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

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

update.resolve

318 319 320 321 322
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
323
end