apkbuild-gem-resolver.in 7.17 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
class Package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
13
  RUBY_SUBPACKAGES = {
14 15 16 17 18
    '2.0.0_p353' => {
      'ruby-minitest' => ['minitest', '4.3.2'],
      'ruby-rake' => ['rake', '0.9.6'],
      'ruby-rdoc' => ['rdoc', '4.0.0', 'ruby-json']
    },
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
19 20 21 22 23 24
    '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' => {
25
      'ruby-json' => ['json', '1.8.1'],
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
26 27 28
      'ruby-minitest' => ['minitest', '4.7.5'],
      'ruby-rake' => ['rake', '10.1.0'],
      'ruby-rdoc' => ['rdoc', '4.1.0', 'ruby-json']
29 30 31 32 33 34 35 36 37
    },
    '2.2.1' => {
      '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'],
      'ruby-io-console' => [ 'io-console', '0.4.2'] # its actually 0.4.3 but 
      						    # that version is not
						    # published on network
38 39
    },
    '2.2.2' => {
40 41 42 43 44 45 46
      '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'],
      'ruby-io-console' => [ 'io-console', '0.4.2'] # its actually 0.4.3 but
						    # that version is not
						    # published on network
47
    }
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
48 49
  }

50
  @@packages = {}
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
51 52
  @@subpackages = []

53
  def self.initialize testing
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
54 55 56 57 58 59 60 61
    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

62
      repos = ['main']
63
      repos << 'testing' if testing
64
      for repo in repos
65 66
        for pkg in aug.match "#{apath}/#{repo}/*"
          Aport.new(aug, pkg) unless pkg.end_with? '/ruby'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
67 68 69 70 71 72 73
        end
      end

      for name, attrs in RUBY_SUBPACKAGES[
        aug.get("#{apath}/main/ruby/APKBUILD/pkgver")
      ]
        gem, version, *deps = attrs
74
        pkg = new name, gem, version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
75
        for dep in deps
76
          pkg.add_dependency dep
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
77
        end
78
        @@subpackages << pkg
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
79 80 81
      end
    end

82 83 84
    @@packages.each_value do |pkg|
      pkg.depends do |dep|
        dep.add_user pkg
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
85 86 87 88 89
      end
    end
  end

  def self.get name
90 91 92
    pkg = @@packages[name]
    raise 'Invalid package name: ' + name unless pkg
    pkg
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106
  end

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

  def initialize name, gem, version
    @name = name
    @gem = gem
    @version = version
    @depends = []
    @users = []
107
    @@packages[name] = self
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
108 109 110
  end

  def add_dependency name
111
    @depends << name
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
112 113 114 115 116 117
  end

  attr_reader :gem, :name, :version

  def depends
    for dep in @depends
118
      unless @@packages.has_key? dep
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
119 120
        raise "Dependency for #{@name} does not exist: #{dep}"
      end
121
      yield @@packages[dep]
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
122 123 124 125 126 127 128 129 130 131
    end
  end

  def users
    for user in @users
      yield user
    end
  end

  def add_user user
132
    @users << user
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
133 134 135
  end
end

136
class Aport < Package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
137 138 139 140 141 142 143 144 145 146 147 148
  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
149 150
      # ruby-gems: workaround for v2.6
      add_dependency dep if dep.start_with?('ruby-') && dep != 'ruby-gems'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
151 152 153 154 155 156 157 158 159 160 161 162
    end
  end
end


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

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

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

209 210
  def assign name, package
    pkg = Package.get package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
211 212 213

    if @gems.has_key? name
      gem = @gems[name]
214 215
      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
216 217
    end

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

  private

225 226
  class PackagedGem
    def initialize update, name, package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
227 228
      @update = update
      @name = name
229
      @package = package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
230 231
    end

232
    attr_reader :package
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
233 234 235 236 237 238 239 240 241 242 243

    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
244
      @version || @package.version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
245 246 247
    end

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

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

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

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


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

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

update.resolve

315 316 317 318 319
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
320
end