├── .gitignore ├── PKGBUILD ├── README ├── pacgem ├── pacgem.8 └── test /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | src 3 | ruby-* 4 | *.pkg.tar.xz 5 | *.src.tar.gz 6 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Daniel Mendler 2 | # Contributor: Daniel Mendler 3 | pkgname=pacgem 4 | pkgver=0.9.13 5 | pkgrel=1 6 | pkgdesc="Install Ruby Gems as Arch Linux packages" 7 | arch=('any') 8 | url="http://github.com/minad/pacgem" 9 | license=('GPL') 10 | depends=('ruby') 11 | source=('pacgem' 'pacgem.8') 12 | sha256sums=('8cd6d9985142cc689d0b9078e6790ec96e247acd9068116e0a9bd83b1f4fb1fb' 13 | '953f45fecbe08cb5cfd700aadfca9c5db548d1f52b69c35716d79092b03c25e0') 14 | conflicts=('pacgem-git') 15 | 16 | package() { 17 | install -D -m755 $srcdir/pacgem $pkgdir/usr/bin/pacgem 18 | install -D -m644 $srcdir/pacgem.8 $pkgdir/usr/share/man/man8/pacgem.8 19 | } 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | NAME 2 | pacgem - Install Ruby Gems using the Arch Linux Package Manager (pacman) 3 | 4 | SYNOPSIS 5 | pacgem [options] gems... 6 | 7 | DESCRIPTION 8 | Pacgem allows direct installation of ruby gems under Arch Linux. It generates a temporary package build script (PKGBUILD). 9 | `makepkg` is used to create a package which is then installed using `sudo pacman`. 10 | 11 | -d, --destdir DIR 12 | Destination directory for package files 13 | 14 | -c, --create 15 | Create package only, do not install 16 | 17 | -u, --update 18 | Update all installed gems 19 | 20 | -t, --test 21 | Test if there are any gems to update 22 | 23 | -r, --resolveonly 24 | Resolve the gem depencies and exit 25 | 26 | -n, --noresolve 27 | Do not resolve any dependencies, this is useful for generating (-create) a single package 28 | 29 | --noautodepends 30 | Disable automatic dependency generation for shared objects (*.so) 31 | 32 | --nonamcap 33 | Disable package checking with namcap 34 | 35 | --nocolor 36 | Disable colored output 37 | 38 | --trace 39 | Show a full traceback on error 40 | 41 | -h, --help 42 | Display help and exit 43 | 44 | -V, --version 45 | Display version and exit 46 | 47 | EXAMPLES 48 | pacgem --create slim Create ruby-slim package in the directory ./ruby-slim 49 | 50 | pacgem slim-1.0 Create temporary ruby-slim package and install it 51 | 52 | pacgem 'slim>1.0' Install ruby-slim version > 1.0 53 | 54 | pacgem thin 'slim~>1.0' Install ruby-thin and ruby-slim with version ~>1.0 55 | 56 | AUTHOR 57 | Daniel Mendler (mail at daniel-mendler.de) 58 | 59 | SEE ALSO 60 | pacman(8), makepkg(8), namcap(1), PKGBUILD(5) 61 | -------------------------------------------------------------------------------- /pacgem: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'optparse' 4 | 5 | module Pacgem 6 | VERSION = '0.9.12' 7 | 8 | module Util 9 | def which?(name) 10 | `which #{name.shellescape} 2>/dev/null` 11 | $?.success? 12 | end 13 | 14 | def reset_rubygems 15 | Gem::Specification.reset 16 | 17 | # Perform only operation on global gems 18 | Gem::Specification.dirs.reject! {|dir| dir !~ %r{\A/usr} } 19 | raise 'No system gem directory found - are you using local ruby installation (rvm, rbenv, ...)?' if Gem::Specification.dirs.empty? 20 | end 21 | 22 | def fetch_spec(dep) 23 | fetcher = Gem::SpecFetcher.fetcher 24 | if fetcher.respond_to? :fetch 25 | # Pre Ruby 2.0 gem spec fetcher 26 | fetcher.fetch(dep, true).last 27 | else 28 | # Ruby 2.0 and newer 29 | spec, source = fetcher.spec_for_dependency(dep, true).first.last 30 | spec && [spec, source.uri] 31 | end 32 | end 33 | 34 | def spew(file, content) 35 | File.open(file, 'w') {|f| f.write(content.to_s) } 36 | end 37 | 38 | def truncate(s, max, omission = '...') 39 | s = s.to_s 40 | s.length > max ? s[0...max] + omission : s 41 | end 42 | 43 | def ruby_package 44 | @ruby_package ||= 45 | if defined? JRUBY_VERSION 46 | 'jruby' 47 | elsif defined? RBX_VERSION 48 | 'rubinius' 49 | elsif RUBY_VERSION > '2.1' 50 | 'ruby' 51 | elsif RUBY_VERSION > '2.0' 52 | 'ruby2.0' 53 | elsif RUBY_VERSION > '1.9' 54 | 'ruby1.9' 55 | elsif Gem.ruby.include?('ruby-enterprise') 56 | 'ruby-enterprise' 57 | else 58 | 'ruby1.8' 59 | end 60 | end 61 | 62 | def pacman_parse(args) 63 | `LC_ALL=C pacman #{args} 2>/dev/null` 64 | end 65 | 66 | extend self 67 | end 68 | 69 | class Logger 70 | def initialize 71 | @color = STDOUT.isatty 72 | end 73 | 74 | def color? 75 | @color 76 | end 77 | 78 | def nocolor! 79 | @color = false 80 | end 81 | 82 | def msg(s) 83 | print('==> ', :green, :bold) 84 | puts(s, :bold) 85 | end 86 | 87 | def msg2(s) 88 | print(' -> ', :blue, :bold) 89 | puts(s, :bold) 90 | end 91 | 92 | def warning(s) 93 | print('==> WARNING: ', :yellow, :bold) 94 | puts(s, :bold) 95 | end 96 | 97 | def error(s) 98 | print('==> ERROR: ', :red, :bold) 99 | puts(s, :bold) 100 | end 101 | 102 | def print(s, *c) 103 | STDOUT.print(color(s, *c)) 104 | end 105 | 106 | def puts(s, *c) 107 | STDOUT.puts(color(s, *c)) 108 | end 109 | 110 | private 111 | 112 | COLORS = { 113 | :clear => 0, 114 | :bold => 1, 115 | :dark => 2, 116 | :italic => 3, # not widely implemented 117 | :underline => 4, 118 | :blink => 5, 119 | :rapid_blink => 6, # not widely implemented 120 | :reverse => 7, 121 | :concealed => 8, 122 | :strikethrough => 9, # not widely implemented 123 | :black => 30, 124 | :red => 31, 125 | :green => 32, 126 | :yellow => 33, 127 | :blue => 34, 128 | :magenta => 35, 129 | :cyan => 36, 130 | :white => 37, 131 | :bg_black => 40, 132 | :bg_red => 41, 133 | :bg_green => 42, 134 | :bg_yellow => 43, 135 | :bg_blue => 44, 136 | :bg_magenta => 45, 137 | :bg_cyan => 46, 138 | :bg_white => 47, 139 | } 140 | 141 | def color(s, *c) 142 | if color? 143 | res = '' 144 | c.each {|c| res << "\e[#{COLORS[c]}m" } 145 | res << "#{s}\e[0m" 146 | else 147 | s 148 | end 149 | end 150 | end 151 | 152 | class PkgBuild 153 | include Util 154 | 155 | def initialize 156 | @vars = [] 157 | @@build ||= DATA.read 158 | end 159 | 160 | def []=(key, val) 161 | @vars << [key, val] 162 | end 163 | 164 | def to_s 165 | lines = "# Generated by pacgem #{Pacgem::VERSION}\n" 166 | @vars.each do |(key,val)| 167 | if Array === val 168 | val = val.map {|v| v.inspect }.join("\n" + (' ' * (key.size + 2))) 169 | lines << "#{key}=(#{val})\n" 170 | else 171 | lines << "#{key}=#{val.inspect}\n" 172 | end 173 | end 174 | lines + @@build 175 | end 176 | 177 | def save 178 | spew('PKGBUILD', self) 179 | end 180 | end 181 | 182 | class Package 183 | include Util 184 | 185 | attr_reader :gemname, :name, :version, :uri 186 | attr_writer :explicit 187 | 188 | def initialize(name, version, uri) 189 | @gemname = name 190 | @name = build_name(name) 191 | @version, @uri = version.to_s, uri 192 | end 193 | 194 | def explicit? 195 | unless instance_variable_defined?(:@explicit) 196 | @explicit = pacman_parse("-Qqe #{name.shellescape}").chomp == name 197 | end 198 | @explicit 199 | end 200 | 201 | def find_installed(name = gemname) 202 | reset_rubygems 203 | spec = Gem::Dependency.new(name, nil).to_spec 204 | pkg = pacman_parse("-Qqo #{spec.loaded_from.shellescape}").chomp 205 | pkg.empty? ? nil : [pkg, spec.version.to_s] 206 | rescue Exception 207 | end 208 | 209 | def install(options, logger) 210 | FileUtils.mkpath(name) 211 | Dir.chdir(name) do 212 | gemfile = download 213 | gen_pkgbuild(gemfile, options) 214 | pkgfile = makepkg(options) 215 | if options[:nonamcap] 216 | logger.warning 'Skipping namcap checks.' 217 | else 218 | namcap(pkgfile, logger) 219 | end 220 | installpkg(pkgfile, logger) unless options[:create] 221 | end 222 | end 223 | 224 | private 225 | 226 | def build_name(gemname) 227 | "#{ruby_package}-#{gemname.downcase.sub(/^ruby-/, '').tr('_', '-')}" 228 | end 229 | 230 | def download 231 | gemfile = "#{gemname}-#{version}.gem" 232 | open("#{uri}gems/#{gemfile}") do |i| 233 | File.open(gemfile, 'w') do |o| 234 | FileUtils.copy_stream(i, o) 235 | end 236 | end 237 | gemfile 238 | end 239 | 240 | def gen_pkgbuild(gemfile, options) 241 | # Gem::Format is pre 2.0, Gem::Package is the new API 242 | spec = defined?(Gem::Format) ? Gem::Format.from_file_by_path(gemfile).spec : Gem::Package.new(gemfile).spec 243 | 244 | depends = [ruby_package] 245 | conflicts = [] 246 | spec.runtime_dependencies.each do |dep| 247 | owner_pkg, installed_version = find_installed(dep.name) 248 | pkgname = build_name(dep.name) 249 | if owner_pkg && owner_pkg != pkgname 250 | depends << owner_pkg 251 | else 252 | dep.requirement.requirements.each do |comp, ver| 253 | comp = '>=' if comp == '~>' 254 | if comp == '!=' 255 | depends << pkgname 256 | conflicts << "#{pkgname}=#{ver}" 257 | else 258 | depends << "#{pkgname}#{comp}#{ver}" 259 | end 260 | end 261 | end 262 | end 263 | 264 | optdepends = [] 265 | spec.development_dependencies.each do |dep| 266 | optspec, opturi = fetch_spec(dep) 267 | optdepends << "#{build_name dep.name}: #{truncate(optspec.summary, 80)} (Development dependency)" if optspec 268 | end 269 | 270 | depends.uniq! 271 | conflicts.uniq! 272 | optdepends.uniq! 273 | 274 | builder = %w(install man license fix) 275 | unless spec.extensions.empty? 276 | builder << 'cleanext' 277 | builder << 'autodepends' unless options[:noautodepends] 278 | end 279 | 280 | license, license_file = find_license(name, spec) 281 | 282 | pkg = PkgBuild.new 283 | pkg['_gemname'] = spec.name 284 | pkg['_gembuilder'] = builder 285 | pkg['_ruby'] = Gem.ruby 286 | pkg['_gem'] = File.join(File.dirname(Gem.ruby), 'gem') 287 | pkg['pkgname'] = name 288 | pkg['pkgver'] = spec.version.to_s 289 | pkg['pkgrel'] = 9999999 290 | pkg['pkgdesc'] = spec.summary 291 | pkg['arch'] = spec.extensions.empty? ? %w(any) : %w(i686 x86_64) 292 | pkg['url'] = spec.homepage 293 | pkg['license'] = license 294 | pkg['_licensefile'] = license_file 295 | pkg['groups'] = %w(pacgem) # Mark this package as installed by pacgem 296 | pkg['makedepends'] = %W(#{ruby_package} binutils) 297 | pkg['depends'] = depends 298 | pkg['conflicts'] = conflicts 299 | pkg['optdepends'] = optdepends 300 | pkg['source'] = %W(#{uri}gems/$_gemname-$pkgver.gem) 301 | pkg['sha256sums'] = [Digest::SHA2.file(gemfile).to_s] 302 | pkg['noextract'] = %w($_gemname-$pkgver.gem) 303 | pkg['options'] = %w(!emptydirs) 304 | pkg.save 305 | end 306 | 307 | def makepkg(options) 308 | ENV['PACKAGER'] = 'pacgem' 309 | system("makepkg -f #{options[:create] && '--nodeps'} #{options[:nocolor] && '--nocolor'}") 310 | Dir["#{name}-*.pkg.*"].first || raise("makepkg #{name} failed") 311 | end 312 | 313 | def namcap(pkgfile, logger) 314 | if which?('namcap') 315 | logger.msg "Checking #{pkgfile} with namcap..." 316 | system("namcap #{pkgfile.shellescape}") 317 | else 318 | logger.warning 'namcap is not installed' 319 | end 320 | end 321 | 322 | def installpkg(pkgfile, logger) 323 | logger.msg "Installing #{pkgfile} with pacman..." 324 | pacman_parse('-Qv') =~ /^Lock File\s+:\s+(.*)$/ 325 | lockfile = $1 326 | if File.exists?(lockfile) 327 | logger.msg2 'Pacman is currently in use, please wait.' 328 | sleep 1 while File.exists?(lockfile) 329 | end 330 | cmd = "pacman --as#{explicit? ? 'explicit' : 'deps'} -U #{pkgfile.shellescape}" 331 | if which?('sudo') 332 | system("sudo #{cmd}") 333 | else 334 | system("su -c #{cmd.shellescape}") 335 | end 336 | end 337 | 338 | def find_license(name, spec) 339 | custom = false 340 | licenses = 341 | if spec.licenses.empty? 342 | custom = true 343 | ["custom:#{name}"] 344 | else 345 | spec.licenses.map do |license| 346 | # Check if this a common license 347 | Dir['/usr/share/licenses/common/*'].map {|f| File.basename(f) }.find do |f| 348 | f.casecmp(license.gsub('-', '')) == 0 349 | end || 350 | %w(BSD MIT ZLIB Python).find {|f| f.casecmp(license) == 0 && (custom = true) } || 351 | (custom = "custom:#{license}") 352 | end 353 | end 354 | files = {} 355 | if custom 356 | spec.files.sort_by(&:size).each do |file| 357 | if %w(COPYING LICENSE COPYRIGHT).any? {|s| file =~ /#{s}/i } 358 | files[File.basename(file)] ||= file 359 | end 360 | end 361 | end 362 | [licenses, files.values] 363 | end 364 | end 365 | 366 | class Installer 367 | include Util 368 | 369 | def initialize(options, logger) 370 | @options, @logger = options, logger 371 | @list = [] 372 | @packages = {} 373 | end 374 | 375 | def run 376 | @list.each {|pkg| pkg.install(@options, @logger) } 377 | end 378 | 379 | def install(name, version = nil) 380 | resolve(Gem::Dependency.new(name, version)).explicit = true 381 | if @options[:resolveonly] 382 | exit 383 | end 384 | end 385 | 386 | def update 387 | reset_rubygems 388 | Gem::Specification.each do |spec| 389 | resolve(Gem::Dependency.new(spec.name, nil)) 390 | end 391 | end 392 | 393 | private 394 | 395 | def resolve(dep) 396 | @packages[dep.name] ||= 397 | begin 398 | spec, uri = fetch_spec(dep) 399 | raise "Gem #{dep} not found" unless spec 400 | pkg = Package.new(dep.name, spec.version, uri) 401 | install = @options[:create] 402 | owner_pkg, installed_version = pkg.find_installed 403 | if installed_version 404 | if owner_pkg != pkg.name || pacman_parse("-Qi #{pkg.name.shellescape}").match(/^Groups\s+:\s+pacgem$/).nil? 405 | @logger.msg2 "(Not installed with pacgem, part of #{owner_pkg}) #{pkg.gemname}-#{installed_version}: #{spec.summary}" 406 | elsif installed_version == pkg.version 407 | @logger.msg2 "(Up-to-date) #{spec.full_name}: #{spec.summary}" 408 | else 409 | @logger.msg2 "(Update from #{installed_version}) #{spec.full_name}: #{spec.summary}" 410 | install = true 411 | end 412 | else 413 | @logger.msg2 "(New) #{spec.full_name}: #{spec.summary}" 414 | install = true 415 | end 416 | spec.runtime_dependencies.each {|d| resolve(d) } if !@options[:noresolve] 417 | @list << pkg if install 418 | pkg 419 | end 420 | end 421 | end 422 | 423 | class Command 424 | include Util 425 | 426 | def initialize(args) 427 | @args = args 428 | @options = {} 429 | @logger = Logger.new 430 | end 431 | 432 | def run 433 | @opts = OptionParser.new(&method(:set_opts)) 434 | @opts.parse!(@args) 435 | process 436 | exit 0 437 | rescue OptionParser::ParseError => ex 438 | STDERR.puts ex.message 439 | STDERR.puts @opts 440 | exit 1 441 | rescue Exception => ex 442 | raise ex if @options[:trace] || SystemExit === ex 443 | @logger.error ex.message 444 | @logger.msg2 'Use --trace for backtrace.' 445 | exit 1 446 | end 447 | 448 | private 449 | 450 | def load_libraries 451 | require 'tmpdir' 452 | require 'rubygems' 453 | require 'rubygems/user_interaction' 454 | begin 455 | require 'rubygems/format' 456 | rescue LoadError 457 | require 'rubygems/package' 458 | end 459 | require 'shellwords' 460 | require 'open-uri' 461 | require 'digest/sha2' 462 | require 'fileutils' 463 | 464 | reset_rubygems 465 | end 466 | 467 | def process 468 | if @options[:update] || @options[:test] 469 | if !@args.empty? 470 | STDERR.puts 'Error: --update and --test accept no arguments.' 471 | exit 1 472 | end 473 | elsif @args.length < 1 474 | STDERR.puts 'Error: No operation specified (use -h for help)' 475 | exit 1 476 | end 477 | 478 | if Process.uid == 0 479 | STDERR.puts 'Error: You cannot perform this operation if you are root.' 480 | exit 1 481 | end 482 | 483 | trap :SIGINT do 484 | @logger.error 'Aborted by user! Exiting...' 485 | exit 1 486 | end 487 | 488 | load_libraries 489 | 490 | if @options[:destdir] 491 | dir = File.expand_path(@options[:destdir]) 492 | FileUtils.mkpath(dir) 493 | @logger.msg "Saving package files in #{dir}" 494 | else 495 | dir = Dir.mktmpdir('pacgem-') 496 | end 497 | 498 | begin 499 | Dir.chdir(dir) do 500 | installer = Installer.new(@options, @logger) 501 | @logger.msg 'Resolving gems...' 502 | if @options[:update] || @options[:test] 503 | installer.update 504 | if @options[:test] 505 | exit 506 | end 507 | else 508 | @args.each do |gem| 509 | if gem =~ /^([-\w]+)((?:[<>]=?|=|~>|-)\d+(?:\.\d+)*)?$/ 510 | name, version = $1, $2 511 | installer.install(name, version =~ /^-/ ? version[1..-1] : version) 512 | else 513 | installer.install(gem) 514 | end 515 | end 516 | end 517 | installer.run 518 | end 519 | ensure 520 | FileUtils.remove_entry_secure(dir) unless @options[:destdir] 521 | end 522 | end 523 | 524 | def set_opts(opts) 525 | opts.banner = 'Usage: pacgem [options] gems...' 526 | 527 | opts.separator %q{ 528 | Pacgem installs Ruby Gems using the Arch Linux Package Manager (pacman). 529 | 530 | Examples: 531 | pacgem --create slim Create ruby-slim package in the directory ./ruby-slim 532 | pacgem slim-1.0 Create temporary ruby-slim package and install it 533 | pacgem 'slim>1.0' Install ruby-slim version > 1.0 534 | pacgem thin 'slim~>1.0' Install ruby-thin and ruby-slim with version ~>1.0 535 | 536 | Options: 537 | } 538 | 539 | opts.on('-d DIR', '--destdir DIR', String, 'Destination directory for package files') do |dir| 540 | @options[:destdir] = dir 541 | end 542 | 543 | opts.on('-c', '--create', :NONE, 'Create package only, do not install') do 544 | @options[:create] = true 545 | @options[:destdir] = Dir.pwd 546 | end 547 | 548 | opts.on('-u', '--update', :NONE, 'Update all installed gems') do 549 | @options[:update] = true 550 | end 551 | 552 | opts.on('-t', '--test', :NONE, 'Check if there are any gems to update') do 553 | @options[:test] = true 554 | end 555 | 556 | opts.on('-r', '--resolveonly', :NONE, 'Resolve dependencies only, don\'t install anything') do 557 | @options[:resolveonly] = true 558 | end 559 | 560 | opts.on('-n', '--noresolve', :NONE, 'Do not resolve dependencies') do 561 | @options[:noresolve] = true 562 | end 563 | 564 | opts.on('--noautodepends', :NONE, 'Disable automatic dependency generation for shared objects (*.so)') do 565 | @options[:noautodepends] = true 566 | end 567 | 568 | opts.on('--nonamcap', :NONE, 'Disable package checking with namcap') do 569 | @options[:nonamcap] = true 570 | end 571 | 572 | opts.on('--nocolor', :NONE, 'Disable colored output') do 573 | @logger.nocolor! 574 | end 575 | 576 | opts.on('--trace', :NONE, 'Show a full traceback on error') do 577 | @options[:trace] = true 578 | end 579 | 580 | opts.on_tail('-h', '--help', 'Display help and exit') do 581 | puts opts 582 | exit 583 | end 584 | 585 | opts.on_tail('-V', '--version', 'Display version and exit') do 586 | puts %{Pacgem Version #{VERSION} 587 | (C) 2011 Daniel Mendler 588 | 589 | This program may be freely redistributed under 590 | the terms of the GNU General Public License.} 591 | exit 592 | end 593 | end 594 | end 595 | end 596 | 597 | Pacgem::Command.new(ARGV).run if $0 == __FILE__ 598 | 599 | __END__ 600 | 601 | _gem_install() { 602 | msg 'Installing gem...' 603 | 604 | # Install the gem 605 | install -d -m755 $_bindir $_gemdir 606 | $_gem install --no-ri --no-rdoc --ignore-dependencies --no-user-install \ 607 | --bindir $_bindir --install-dir $_gemdir "$srcdir/$_gemname-$pkgver.gem" 608 | } 609 | 610 | _gem_man() { 611 | msg 'Installing man pages...' 612 | 613 | # Find man pages and move them to the correct directory 614 | local mandir="$_gemdir/gems/$_gemname-$pkgver/man" 615 | if [[ -d $mandir ]]; then 616 | install -d -m755 $_mandir 617 | local file 618 | for file in $(find $mandir -type f -and -name *.[0-9]); do 619 | local dir=$_mandir/man${file##*.} 620 | install -d -m755 $dir 621 | mv $file $dir 622 | done 623 | rm -rf $mandir 624 | fi 625 | } 626 | 627 | _gem_license() { 628 | if [[ "${#_licensefile[@]}" -ne 0 ]]; then 629 | msg "Installing license $license..." 630 | install -d -m755 "$pkgdir/usr/share/licenses/$pkgname" 631 | local file 632 | for file in ${_licensefile[@]}; do 633 | ln -s "../../../..$_gemdestdir/gems/$_gemname-$pkgver/$file" "$pkgdir/usr/share/licenses/$pkgname/$(basename $file)" || true 634 | done 635 | fi 636 | } 637 | 638 | _gem_fix() { 639 | msg 'Fixing gem installation...' 640 | 641 | # Set mode of executables to 755 642 | [[ -d "$_gemdir/bin" ]] && find "$_gemdir/bin" -type f -exec chmod 755 -- '{}' ';' 643 | 644 | # Remove cached gem file 645 | rm -f "$_gemdir/cache/$_gemname-$pkgver.gem" 646 | 647 | # Sometimes there are files which are not world readable. Fix this. 648 | find $pkgdir -type f '!' -perm '-004' -exec chmod o+r -- '{}' ';' 649 | } 650 | 651 | _gem_cleanext() { 652 | msg 'Removing native build leftovers...' 653 | local extdir="$_gemdir/gems/$_gemname-$pkgver/ext" 654 | [[ -d $extdir ]] && find "$extdir" -name '*.o' -exec rm -f -- '{}' ';' 655 | } 656 | 657 | # Check if dependency is already satisfied 658 | _dependency_satisfied() { 659 | local dep=$1 deps="${depends[@]}" 660 | [[ $(type -t in_array) == 'function' ]] || error "in_array should be provided by makepkg" 661 | while true; do 662 | in_array $dep ${deps[@]} && return 0 663 | local found=0 pkg 664 | # Warning: This could break easily if the pacman output format changes. 665 | for pkg in $(LC_ALL=C pacman -Qi ${deps[@]} 2>/dev/null | sed '/Depends On/!d;s/.*: //;s/None\|[<>]=\?[^ ]*\|=[^ ]*//g'); do 666 | if ! in_array $pkg ${deps[@]}; then 667 | deps=(${deps[@]} $pkg) && found=1 668 | fi 669 | done 670 | (( $found )) || break 671 | done 672 | return 1 673 | } 674 | 675 | _gem_autodepends() { 676 | msg 'Automatic dependency resolution...' 677 | 678 | # Find all referenced shared libraries 679 | local deps=$(find $pkgdir -type f -name '*.so') 680 | [[ -n $deps ]] || return 0 681 | 682 | deps=$(readelf -d $deps | sed -n 's/.*Shared library: \[\(.*\)\].*/\1/p' | sort | uniq) 683 | 684 | # Find referenced libraries on the library search path 685 | local libs=() lib path 686 | for lib in $deps; do 687 | for path in /lib /usr/lib; do 688 | [[ -f "$path/$lib" ]] && libs=(${libs[@]} "$path/$lib") 689 | done 690 | done 691 | (( ${#libs} )) || return 0 692 | 693 | msg2 "Referenced libraries: ${libs[*]}" 694 | 695 | # Find matching packages with pacman -Qo 696 | # and add them to the depends array 697 | local pkg 698 | for pkg in $(pacman -Qqo ${libs[@]}); do 699 | _dependency_satisfied $pkg || depends=(${depends[@]} $pkg) 700 | done 701 | msg2 "Referenced packages: ${depends[*]}" 702 | } 703 | 704 | _rbconfig() { 705 | $_ruby -e "require 'rbconfig'; puts RbConfig::CONFIG['$1']" 706 | } 707 | 708 | package() { 709 | # Directories defined inside build() because if ruby is not installed on the system 710 | # makepkg will barf when sourcing the PKGBUILD 711 | _gemdestdir=$($_gem environment gemdir) 712 | _gemdir=$pkgdir$_gemdestdir 713 | _bindir=$pkgdir$(_rbconfig bindir) 714 | _mandir=$pkgdir$(_rbconfig mandir) 715 | 716 | local i 717 | for i in ${_gembuilder[@]}; do 718 | _gem_$i 719 | done 720 | } 721 | -------------------------------------------------------------------------------- /pacgem.8: -------------------------------------------------------------------------------- 1 | .TH pacgem 8 "July 2013" "Version 0.9.12" "Arch Linux" 2 | .SH NAME 3 | pacgem \- Install Ruby Gems using the Arch Linux Package Manager (pacman) 4 | .SH SYNOPSIS 5 | .B pacgem 6 | [\fIoptions\fR] \fIgems\fR... 7 | .SH DESCRIPTION 8 | Pacgem allows direct installation of ruby gems under Arch Linux. It generates a temporary package build script (PKGBUILD). `makepkg` is used to create a package which is then installed using `sudo pacman`. 9 | .TP 10 | \fB\-d\fR, \fB\-\-destdir\fR \fIDIR\fR 11 | Destination directory for package files 12 | .TP 13 | \fB\-c\fR, \fB\-\-create\fR 14 | Create package only, do not install 15 | .TP 16 | \fB\-u\fR, \fB\-\-update\fR 17 | Update all installed gems 18 | .TP 19 | \fB\-t\fR, \fB\-\-test\fR 20 | Test if there are any gems to update 21 | .TP 22 | \fB\-r\fR, \fB\-\-resolveonly\fR 23 | Resolve the gem depencies and exit 24 | .TP 25 | \fB\-n\fR, \fB\-\-noresolve\fR 26 | Do not resolve any dependencies, this is useful for generating (-create) a single package 27 | .TP 28 | \fB\-\-noautodepends\fR 29 | Disable automatic dependency generation for shared objects (*.so) 30 | .TP 31 | \fB\-\-nonamcap\fR 32 | Disable package checking with namcap 33 | .TP 34 | \fB\-\-nocolor\fR 35 | Disable colored output 36 | .TP 37 | \fB\-\-trace\fR 38 | Show a full traceback on error 39 | .TP 40 | \fB\-h\fR, \fB\-\-help\fR 41 | Display help and exit 42 | .TP 43 | \fB\-V\fR, \fB\-\-version\fR 44 | Display version and exit 45 | .SH EXAMPLES 46 | .TP 47 | pacgem --create slim Create ruby-slim package in the directory ./ruby-slim 48 | .PP 49 | pacgem slim-1.0 Create temporary ruby-slim package and install it 50 | .PP 51 | pacgem 'slim>1.0' Install ruby-slim version > 1.0 52 | .PP 53 | pacgem thin 'slim~>1.0' Install ruby-thin and ruby-slim with version ~>1.0 54 | .PP 55 | .SH AUTHOR 56 | Daniel Mendler (mail at daniel-mendler.de) 57 | .SH SEE ALSO 58 | \fBpacman\fR(8), \fBmakepkg\fR(8), \fBnamcap\fR(1), \fBPKGBUILD\fR(5) 59 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./pacgem -c rails=4.0.0 3 | ./pacgem -c rails=3.2.13 4 | ./pacgem -c thor 5 | ./pacgem -c rack 6 | ./pacgem -c tilt 7 | ./pacgem -c bundler 8 | ./pacgem -c rdoc 9 | ./pacgem -c nokogiri 10 | ./pacgem -c rspec 11 | ./pacgem -c ffi 12 | ./pacgem -c eventmachine 13 | ./pacgem -c thin 14 | ./pacgem -c unicorn 15 | ./pacgem -c sqlite3 16 | ./pacgem -c hpricot 17 | ./pacgem -c fastthread 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------