apkbuild-gem-resolver.in 6.96 KB
Newer Older
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
1 2 3 4 5 6
#!/usr/bin/ruby

# APKBUILD dependency resolver for RubyGems
# Copyright (C) 2014 Kaarle Ritvanen

require 'augeas'
7
require 'optparse'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
8 9 10 11 12 13
require 'rubygems/dependency'
require 'rubygems/resolver'
require 'rubygems/spec_fetcher'

class Aport
  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 51 52
  }

  @@aports = {}
  @@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
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
65 66 67 68 69 70 71 72 73 74 75 76 77
        for aport in aug.match "#{apath}/#{repo}/*"
          FileAport.new(aug, aport) unless aport.end_with? '/ruby'
        end
      end

      for name, attrs in RUBY_SUBPACKAGES[
        aug.get("#{apath}/main/ruby/APKBUILD/pkgver")
      ]
        gem, version, *deps = attrs
        aport = new name, gem, version
        for dep in deps
          aport.add_dependency dep
        end
78
        @@subpackages << aport
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
79 80 81 82 83 84 85 86 87 88 89
      end
    end

    @@aports.each_value do |aport|
      aport.depends do |dep|
        dep.add_user aport
      end
    end
  end

  def self.get name
90 91 92
    aport = @@aports[name]
    raise 'Invalid package name: ' + name unless aport
    aport
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
  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 = []
    @@aports[name] = self
  end

  def add_dependency name
111
    @depends << name
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
  end

  attr_reader :gem, :name, :version

  def depends
    for dep in @depends
      unless @@aports.has_key? dep
        raise "Dependency for #{@name} does not exist: #{dep}"
      end
      yield @@aports[dep]
    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 136 137 138 139 140 141 142 143 144 145 146 147 148
  end
end

class FileAport < Aport
  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(Aport.get(name).gem, name)
164
    @deps << gem.dependency if gem.require_version version
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
  end

  def resolve
    Aport.ruby_subpkgs do |pkg|
      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?
        gem.aport.users do |user|
          ugem = @gems[user.gem]
          if !ugem || ugem.aport.name != user.name
            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|
      obs = gem.obsolete_deps
      obs = obs.length == 0 ? nil : " (obsolete dependencies: #{obs.join ', '})"

      if gem.updated? || obs
        yield "#{gem.aport.name}-#{gem.version}#{obs}"
      end
    end
  end

  def assign name, aport
    aport = Aport.get aport

    if @gems.has_key? name
      gem = @gems[name]
      return gem if aport == gem.aport
      raise "Conflicting packages for gem #{name}: #{gem.aport.name} and #{aport.name}"
    end

    gem = AportGem.new self, name, aport
    @gems[name] = gem
    gem
  end

  private

  class AportGem
    def initialize update, name, aport
      @update = update
      @name = name
      @aport = aport
    end

    attr_reader :aport, :obsolete_deps

    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
      @version || @aport.version
    end

    def updated?
      version != @aport.version
    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 = []

      @aport.depends do |dep|
        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 276 277 278 279 280 281 282 283 284
        end
      end

      if deps.length > 0
        raise 'Undeclared dependencies in ' + @aport.name + deps.inject('') {
          |s, dep| "#{s}\n#{dep.name} #{dep.requirements_list.join ' '}"
        }
      end
    end
  end
end


285 286 287 288 289 290 291 292
testing = false
OptionParser.new do |opts|
  opts.on('-t', '--testing') do |t|
    testing = t
  end
end.parse! ARGV
Aport.initialize testing

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
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]
  update.require_version name, match[4] || latest[Aport.get(name).gem]
end

update.resolve

for aport in update
  puts aport
end