summaryrefslogblamecommitdiff
path: root/apkbuild-gem-resolver.in
blob: 7ba57808388bde5a154c3d5f2d400cedee1a9747 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11


                                           
                                         

                
                  



                               
             
                      




                                                   





                                                   
                                       


                                                   








                                                                             

                






                                                                            
     

   
                 

                    
                             







                                                                                 
                      
                                   
                       

                                                          






                                                     
                                    
                       
                                
           
                            


         


                                  




                   


                                                    













                                   
                           


                         
                    





                                   
                                    

                                                              
                           









                      
                  


     
                     











                                                         

                                                                          




       






                                   
                                             
                                                          


             
                                 




















                                                                 
                                   
                                
                                                    










                                                                           

                            


       

                             


                          

                                                                                       

       
                                         





                     

                                        

                      
                        

       
                        










                                                                                 
                                  


                
                                 













                                                                               
                               


                                                                       
                                    



                        
                                                                               



                                                                        



                                               
                                 




                                                



     





                                   
                          
 










                                                                         
                                                                        



              




                                                                            
   
#!/usr/bin/ruby

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

require 'augeas'
require 'optparse'
require 'rubygems/dependency'
require 'rubygems/resolver'
require 'rubygems/spec_fetcher'

class 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' => {
      '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
    },
    '2.2.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'],
      'ruby-io-console' => [ 'io-console', '0.4.2'] # its actually 0.4.3 but
						    # that version is not
						    # published on network
    }
  }

  @@packages = {}
  @@subpackages = []

  def self.initialize testing
    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

      repos = ['main']
      repos << 'testing' if testing
      for repo in repos
        for pkg in aug.match "#{apath}/#{repo}/*"
          Aport.new(aug, pkg) unless pkg.end_with? '/ruby'
        end
      end

      for name, attrs in RUBY_SUBPACKAGES[
        aug.get("#{apath}/main/ruby/APKBUILD/pkgver")
      ]
        gem, version, *deps = attrs
        pkg = new name, gem, version
        for dep in deps
          pkg.add_dependency dep
        end
        @@subpackages << pkg
      end
    end

    @@packages.each_value do |pkg|
      pkg.depends do |dep|
        dep.add_user pkg
      end
    end
  end

  def self.get name
    pkg = @@packages[name]
    raise 'Invalid package name: ' + name unless pkg
    pkg
  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 = []
    @@packages[name] = self
  end

  def add_dependency name
    @depends << name
  end

  attr_reader :gem, :name, :version

  def depends
    for dep in @depends
      unless @@packages.has_key? dep
        raise "Dependency for #{@name} does not exist: #{dep}"
      end
      yield @@packages[dep]
    end
  end

  def users
    for user in @users
      yield user
    end
  end

  def add_user user
    @users << user
  end
end

class Aport < Package
  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
      # ruby-gems: workaround for v2.6
      add_dependency dep if dep.start_with?('ruby-') && dep != 'ruby-gems'
    end
  end
end


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

  def require_version name, version
    gem = assign(Package.get(name).gem, name)
    @deps << gem.dependency if gem.require_version version
  end

  def resolve
    Package.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.package.users do |user|
          ugem = @gems[user.gem]
          if !ugem || ugem.package.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|
      update = gem.update
      yield update if update
    end
  end

  def assign name, package
    pkg = Package.get package

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

    gem = PackagedGem.new self, name, pkg
    @gems[name] = gem
    gem
  end

  private

  class PackagedGem
    def initialize update, name, package
      @update = update
      @name = name
      @package = package
    end

    attr_reader :package

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

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

      @package.depends do |dep|
        gem = @update.assign(dep.gem, dep.name)
        gem.check_deps
        unless deps.reject! { |sdep| sdep.match? dep.gem, gem.version }
          @obsolete_deps << dep.name
        end
      end

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

    def update
      updated? || @obsolete_deps.length > 0 ? (
        {
          :name => @package.name,
          :version => version,
          :obsolete_deps => @obsolete_deps.clone
        }
      ) : nil
    end
  end
end


testing = false
OptionParser.new do |opts|
  opts.on('-t', '--testing') do |t|
    testing = t
  end
end.parse! ARGV
Package.initialize testing

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[Package.get(name).gem]
end

update.resolve

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

  puts "#{pkg[:name]}-#{pkg[:version]}#{obs}"
end