Files
chromebrew/bin/crew
satmandu d1d2335361 Run postinstalls together, after all package installs, and run them in a tempdir instead of CREW_DEST_DIR (#5895)
* Do postinstalls after all packages are extracted.

* Use tempdir for postinstalls

* serialize postinstalls in resolve_dependencies_and_build

* Let a SKIP argument be used for sha256sums
2021-06-24 21:03:58 -05:00

1478 lines
49 KiB
Ruby
Executable File

#!/usr/bin/env ruby
require_relative '../lib/color'
# Disallow sudo
abort 'Chromebrew should not be run as root.'.lightred if Process.uid == 0
require 'find'
require 'net/http'
require 'uri'
require 'digest/sha2'
require 'json'
require 'fileutils'
require 'securerandom'
require 'tmpdir'
require_relative '../lib/const'
require_relative '../lib/util'
# Add lib to LOAD_PATH
$LOAD_PATH.unshift "#{CREW_LIB_PATH}lib"
DOC = <<DOCOPT
Chromebrew - Package manager for Chrome OS http://skycocker.github.io/chromebrew/
Usage:
crew build [options] [-k|--keep] <name> ...
crew const [options] [<name> ...]
crew deps [options] <name> ...
crew download [options] <name> ...
crew files [options] <name> ...
crew help [<command>]
crew install [options] [-k|--keep] [-s|--build-from-source] [-S|--recursive-build] <name> ...
crew list [options] (available|installed|compatible|incompatible)
crew postinstall [options] <name> ...
crew reinstall [options] [-k|--keep] [-s|--build-from-source] [-S|--recursive-build] <name> ...
crew remove [options] <name> ...
crew search [options] [<name> ...]
crew update [options] [<compatible>]
crew upgrade [options] [-k|--keep] [-s|--build-from-source] [<name> ...]
crew whatprovides [options] <pattern> ...
-c --color Use colors even if standard out is not a tty.
-d --no-color Disable colors even if standard out is a tty.
-k --keep Keep the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory.
-L --license Display the crew license.
-s --build-from-source Build from source even if pre-compiled binary exists.
-S --recursive-build Build from source, including all dependencies, even if pre-compiled binaries exist.
-v --verbose Show extra information.
-V --version Display the crew version.
-h --help Show this screen.
version #{CREW_VERSION}
DOCOPT
CREW_LICENSE = <<~LICENSESTRING
Copyright (C) 2021 Chromebrew Authors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see https://www.gnu.org/licenses/gpl-3.0.html.
LICENSESTRING
# Set XZ_OPT environment variable for build command.
# If CREW_XZ_OPT is defined, use it by default. Use `-7e`, otherwise.
ENV["XZ_OPT"] = ENV['CREW_XZ_OPT'] || "-7e -T #{CREW_NPROC}"
# If CURL environment variable exists use it in lieu of curl.
CURL = ENV['CURL'] || 'curl'
# All available crew commands.
@cmds = ["build", "const", "deps", "download", "files", "help", "install", "list", "postinstall", "reinstall", "remove", "search", "update", "upgrade", "whatprovides"]
# Parse arguments using docopt
require_relative '../lib/docopt'
begin
args = Docopt::docopt(DOC)
args['<name>'] = args['<name>'].map { |arg| arg.gsub('-','_') } if args['<name>']
rescue Docopt::Exit => e
if ARGV[0] then
case ARGV[0]
when '-V', '--version'
puts CREW_VERSION
exit 0
when '-L', '--license'
puts CREW_LICENSE
exit 0
end
if ARGV[0] != '-h' and ARGV[0] != '--help' then
puts "Could not understand \"crew #{ARGV.join(' ')}\".".lightred
# Looking for similar commands
if not @cmds.include?(ARGV[0]) then
similar = @cmds.select {|word| edit_distance(ARGV[0], word) < 4}
if not similar.empty? then
puts 'Did you mean?'
similar.each {|sug| puts " #{sug}"}
end
end
end
end
puts e.message
exit 1
end
String.use_color = args["--color"] || !args["--no-color"]
@opt_keep = args["--keep"]
@opt_verbose = args["--verbose"]
if @opt_verbose then
@fileutils_verbose = true
@verbose = 'v'
@short_verbose = '-v'
else
@fileutils_verbose = false
@verbose = ''
@short_verbose = ''
end
@opt_src = args["--build-from-source"]
@opt_recursive = args["--recursive-build"]
@device = JSON.parse(File.read(CREW_CONFIG_PATH + 'device.json'), symbolize_names: true)
#symbolize also values
@device.each do |key, elem|
@device[key] = @device[key].to_sym rescue @device[key]
end
def print_package(pkgPath, extra = false)
pkgName = File.basename pkgPath, '.rb'
begin
set_package pkgName, pkgPath
rescue => e
puts "Error with #{pkgName}.rb: #{e}".red unless e.to_s.include?('uninitialized constant')
end
print_current_package extra
end
def print_current_package(extra = false)
status = ''
status = 'installed' if @device[:installed_packages].any? do |elem| elem[:name] == @pkg.name end
status = 'incompatible' unless @device[:compatible_packages].any? do |elem| elem[:name] == @pkg.name end
case status
when 'installed'
print @pkg.name.lightgreen
when 'incompatible'
print @pkg.name.lightred
else
print @pkg.name.lightblue
end
print ": #{@pkg.description}".lightblue if @pkg.description
if extra
puts ""
puts @pkg.homepage if @pkg.homepage
puts "Version: #{@pkg.version}"
print "License: #{@pkg.license}" if @pkg.license
end
puts ""
end
def set_package(pkgName, pkgPath)
begin
require_relative pkgPath
rescue SyntaxError => e
puts "#{e.class}: #{e.message}".red
end
@pkg = Object.const_get(pkgName.capitalize)
@pkg.build_from_source = true if @opt_recursive
@pkg.name = pkgName
end
def list_packages
Dir[CREW_PACKAGES_PATH + '*.rb'].each do |filename|
print_package filename
end
end
def list_available
Dir[CREW_PACKAGES_PATH + '*.rb'].each do |filename|
notInstalled = true
pkgName = File.basename filename, '.rb'
notInstalled = false if File.exist? CREW_META_PATH + pkgName + '.filelist'
if notInstalled
begin
set_package pkgName, filename
rescue => e
puts "Error with #{pkgName}.rb: #{e}".red unless e.to_s.include?('uninitialized constant')
end
if @pkg.compatibility&.include? 'all' or @pkg.compatibility&.include? ARCH
puts pkgName
end
end
end
end
def list_installed
unless @opt_verbose
Dir[CREW_META_PATH + '*.directorylist'].sort.map do |f|
File.basename(f, '.directorylist').lightgreen
end
else
@installed_packages = []
@device[:installed_packages].each do |package|
search package[:name], true
@installed_packages.append(package[:name] + ' ' + package[:version].to_s)
end
@sorted_installed_packages = @installed_packages.sort
@sorted_installed_packages.unshift('======= =======')
@sorted_installed_packages.unshift('Package Version')
@first_col_width = @sorted_installed_packages.map(&:split).map(&:first).max_by(&:size).size + 2
@sorted_installed_packages.map(&:strip).each do |line|
puts "%-#{@first_col_width}s%s".lightgreen % line.split
end
puts
end
end
def list_compatible(compat = true)
Dir[CREW_PACKAGES_PATH + '*.rb'].each do |filename|
pkgName = File.basename filename, '.rb'
if @device[:compatible_packages].any? do |elem| elem[:name] == pkgName end
if compat
if ( File.exist? CREW_META_PATH + pkgName + '.filelist' )
puts pkgName.lightgreen
else
puts pkgName
end
end
else
puts pkgName.lightred unless compat
end
end
end
def generate_compatible
puts 'Generating compatible packages...'.orange if @opt_verbose
@device[:compatible_packages] = []
Dir[CREW_PACKAGES_PATH + '*.rb'].each do |filename|
pkgName = File.basename filename, '.rb'
begin
set_package pkgName, filename
rescue => e
puts "Error with #{pkgName}.rb: #{e}".red unless e.to_s.include?('uninitialized constant')
end
puts "Checking #{pkgName} for compatibility.".orange if @opt_verbose
# If compatibility property does not exist, check if a binary package
# exists, and if not, see if at least a source url exists.
@compatibility = true
@binary_url = ''
@url = ''
if @pkg.compatibility.nil?
@binary_url = @pkg.get_binary_url(@device[:architecture])
@url = @pkg.get_url(@device[:architecture])
if !@binary_url
puts "#{pkgName} is missing compatibility information".red
#puts "url: #{@url}".green
# If no source package is available, then package is not compatible.
@compatibility = false unless @url
puts "#{pkgName} compatibility is #{@compatibility}" if @opt_verbose
end
end
if ( @pkg.compatibility&.include? 'all' or @pkg.compatibility&.include? ARCH or @pkg.compatibility.nil? ) and @compatibility
#add to compatible packages
puts "Adding #{pkgName} to compatible packages.".lightgreen if @opt_verbose
@device[:compatible_packages].push(name: @pkg.name)
else
puts "#{pkgName} is not a compatible package.".lightred if @opt_verbose
end
end
File.open(CREW_CONFIG_PATH + 'device.json', 'w') do |file|
output = JSON.parse @device.to_json
file.write JSON.pretty_generate(output)
end
puts 'Generating compatible packages done.'.orange if @opt_verbose
end
def search (pkgName, silent = false)
pkgPath = CREW_PACKAGES_PATH + pkgName + '.rb'
begin
return set_package(pkgName, pkgPath) if File.exist?(pkgPath)
rescue => e
puts "Error with #{pkgName}.rb: #{e}".red unless e.to_s.include?('uninitialized constant')
end
abort "Package #{pkgName} not found. :(".lightred unless silent
end
def regexp_search(pkgPat)
re = Regexp.new(pkgPat, true)
results = Dir[CREW_PACKAGES_PATH + '*.rb'].sort \
.select { |f| File.basename(f, '.rb') =~ re } \
.each { |f| print_package(f, @opt_verbose) }
if results.empty?
Dir[CREW_PACKAGES_PATH + '*.rb'].each do |packagePath|
packageName = File.basename packagePath, '.rb'
begin
set_package packageName, packagePath
rescue => e
puts "Error with #{pkgName}.rb: #{e}".red unless e.to_s.include?('uninitialized constant')
end
if ( @pkg.description =~ /#{pkgPat}/i )
print_current_package @opt_verbose
results.push(packageName)
end
end
end
abort "Package #{pkgPat} not found. :(".lightred if results.empty?
end
def help(pkgName)
case pkgName
when "build"
puts "Build package(s)."
puts "Usage: crew build [-k|--keep] [-v|--verbose] <package1> [<package2> ...]"
puts "Build package(s) from source and place the archive and checksum in the current working directory."
puts "If `-k` or `--keep` is present, the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory will remain."
puts "If `-v` or `--verbose` is present, extra information will be displayed."
when "const"
puts "Display constant(s)."
puts "Usage: crew const [<const1> <const2> ...]"
puts "If no constants are provided, all constants will be displayed."
when "deps"
puts "Display dependencies of package(s)."
puts "Usage: crew deps <package1> [<package2> ...]"
when "download"
puts "Download package(s)."
puts "Usage: crew download [-v|--verbose] <package1> [<package2> ...]"
puts "Download package(s) to `CREW_BREW_DIR` (#{CREW_BREW_DIR}), but don't install."
puts "If `-v` or `--verbose` is present, extra information will be displayed."
when "files"
puts "Display installed files of package(s)."
puts "Usage: crew files <package1> [<package2> ...]"
puts "The package(s) must be currently installed."
when "install"
puts "Install package(s)."
puts "Usage: crew install [-k|--keep] [-s|--build-from-source] [-S|--recursive-build] [-v|--verbose] <package1> [<package2> ...]"
puts "The package(s) must have a valid name. Use `crew search <pattern>` to search for packages to install."
puts "If `-k` or `--keep` is present, the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory will remain."
puts "If `-s` or `--build-from-source` is present, the package(s) will be compiled instead of installed via binary."
puts "If `-S` or `--recursive-build` is present, the package(s), including all dependencies, will be compiled instead of installed via binary."
puts "If `-v` or `--verbose` is present, extra information will be displayed."
when "list"
puts "List packages"
puts "Usage: crew list [-v|--verbose] available|installed|compatible|incompatible"
when "postinstall"
puts "Display postinstall messages of package(s)."
puts "Usage: crew postinstall <package1> [<package2> ...]"
puts "The package(s) must be currently installed."
when "reinstall"
puts "Remove and install package(s)."
puts "Usage: crew reinstall [-k|--keep] [-s|--build-from-source] [-S|--recursive-build] [-v|--verbose] <package1> [<package2> ...]"
puts "If `-k` or `--keep` is present, the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory will remain."
puts "If `-s` or `--build-from-source` is present, the package(s) will be compiled instead of installed via binary."
puts "If `-S` or `--recursive-build` is present, the package(s), including all dependencies, will be compiled instead of installed via binary."
puts "If `-v` or `--verbose` is present, extra information will be displayed."
when "remove"
puts "Remove package(s)."
puts "Usage: crew remove [-v|--verbose] <package1> [<package2> ...]"
puts "The package(s) must be currently installed."
puts "If `-v` or `--verbose` is present, extra information will be displayed."
when "search"
puts "Look for package(s)."
puts "Usage: crew search [-v|--verbose] [<pattern> ...]"
puts "If <pattern> is omitted, all packages will be returned."
puts "If the package color is " + "green".lightgreen + ", it means the package is installed."
puts "If the package color is " + "red".lightred + ", it means the architecture is not supported."
puts "The <pattern> string can also contain regular expressions."
puts "If `-v` or `--verbose` is present, homepage, version and license will be displayed."
puts "Examples:"
puts " crew search ^lib".lightblue + " will display all packages that start with `lib`."
puts " crew search audio".lightblue + " will display all packages with `audio` in the name."
puts " crew search | grep -i audio".lightblue + " will display all packages with `audio` in the name or description."
puts " crew search git -v".lightblue + " will display packages with `git` in the name along with homepage, version and license."
when "update"
puts "Update crew."
puts "Usage: crew update"
puts "This only updates crew itself. Use `crew upgrade` to update packages."
puts "Usage: crew update compatible"
puts "This updates the crew package compatibility list."
when "upgrade"
puts "Update package(s)."
puts "Usage: crew upgrade [-v|--verbose] [-s|--build-from-source] <package1> [<package2> ...]"
puts "If package(s) are omitted, all packages will be updated. Otherwise, specific package(s) will be updated."
puts "Use `crew update` to update crew itself."
puts "If `-s` or `--build-from-source` is present, the package(s) will be compiled instead of upgraded via binary."
puts "If `-v` or `--verbose` is present, extra information will be displayed."
when "whatprovides"
puts "Determine which package(s) contains file(s)."
puts "Usage: crew whatprovides <pattern> ..."
puts "The <pattern> is a search string which can contain regular expressions."
else
puts "Available commands: #{@cmds.join(', ')}"
end
end
def const(var)
if var
value = eval(var)
puts "#{var}=#{value}"
else
@ruby_default_constants = %w[
ARGF
ARGV
CROSS_COMPILING
DOC
ENV
GC
IO
JSON
OpenSSL
Q
R
RUBY_COPYRIGHT
RUBY_DESCRIPTION
RUBY_ENGINE
RUBY_ENGINE_VERSION
RUBYGEMS_ACTIVATION_MONITOR
RUBY_PATCHLEVEL
RUBY_PLATFORM
RUBY_RELEASE_DATE
RUBY_REVISION
RUBY_VERSION
RubyVM
S
STDERR
STDIN
STDOUT
StringIO
TOPLEVEL_BINDING
URI
]
# Get all constants
@constants = Module.constants.select {|e| e =~ /[[:upper:]]$/}
# Reject all constants which match the default list
@constants = @constants.map(&:to_s).reject{ |e| @ruby_default_constants.find{ |f| /\A#{e}\z/ =~ f }}
# Print a sorted list of the remaining constants used by crew.
@constants.sort.each { |var|
value = eval(var.to_s)
puts "#{var}=#{value}"
}
end
end
def human_size (bytes)
kilobyte = 1024.0
megabyte = kilobyte * kilobyte
gigabyte = megabyte * kilobyte
if bytes < kilobyte
units = 'B'
size = bytes
end
if bytes >= kilobyte and bytes < megabyte
units = 'KB'
size = bytes / kilobyte
end
if bytes >= megabyte and bytes < gigabyte
units = 'MB'
size = bytes / megabyte
end
if bytes >= gigabyte
units = 'GB'
size = bytes / gigabyte
end
return sprintf('%.2f', size.to_s) + units
end
def files (pkgName)
filelist = "#{CREW_META_PATH}#{pkgName}.filelist"
if File.exist? filelist
system "sort #{filelist}"
lines = File.readlines(filelist).size
size = 0
File.readlines(filelist).each do |filename|
size += File.size(filename.chomp) if File.exist? filename.chomp
end
humansize = human_size(size)
puts "Total found: #{lines}".lightgreen
puts "Disk usage: #{humansize}".lightgreen
else
puts "Package #{pkgName} is not installed. :(".lightred
end
end
def whatprovides (regexPat)
# Use grep version command to ascertain whether we have a working grep.
unless system('grep -V > /dev/null 2>&1')
abort 'Grep is not working. Please install it with \'crew install grep\''.lightred
end
fileArray = []
@grepresults = %x[grep "#{regexPat}" #{CREW_META_PATH}*.filelist].chomp.gsub('.filelist','').gsub(':',': ').gsub(CREW_META_PATH,'').split(/$/).map(&:strip)
@grepresults.each { |fileLine| fileArray.push(fileLine) }
unless fileArray.empty?
fileArray.sort.each do |item|
puts item
end
puts "\nTotal found: #{fileArray.length}".lightgreen
end
end
def update
abort "'crew update' is used to update crew itself. Use 'crew upgrade <package1> [<package2> ...]' to update specific packages.".orange if @pkgName
@crew_testing_repo = ENV['CREW_TESTING_REPO']
@crew_testing_branch = ENV['CREW_TESTING_BRANCH']
@crew_testing = ENV['CREW_TESTING']
@crew_testing = 0 if @crew_testing_repo.nil? || @crew_testing_repo.empty?
@crew_testing = 0 if @crew_testing_branch.nil? || @crew_testing_branch.empty?
#update package lists
Dir.chdir CREW_LIB_PATH do
if @crew_testing == '1'
puts "Updating crew from testing repository..."
system "git remote add testing #{@crew_testing_repo} 2>/dev/null || \
git remote set-url testing #{@crew_testing_repo}"
system "git fetch testing #{@crew_testing_branch}"
system "git reset --hard testing/#{@crew_testing_branch}"
else
system 'git fetch origin master'
system 'git reset --hard origin/master'
end
end
puts 'Package lists, crew, and library updated.'
#update compatible packages
generate_compatible
#check for outdated installed packages
puts 'Checking for package updates...'
canBeUpdated = 0
@device[:installed_packages].each do |package|
search package[:name], true
if package[:version] != @pkg.version
canBeUpdated += 1
puts @pkg.name + ' could be updated from ' + package[:version] + ' to ' + @pkg.version
end
end
if canBeUpdated > 0
puts
puts "Run `crew upgrade` to update all packages or `crew upgrade <package1> [<package2> ...]` to update specific packages."
else
puts "Your software is up to date.".lightgreen
end
end
def upgrade
if @pkgName
currentVersion = nil
@device[:installed_packages].each do |package|
if package[:name] == @pkg.name
currentVersion = package[:version]
end
end
if currentVersion != @pkg.version
puts "Updating #{@pkg.name}..."
@pkg.in_upgrade = true
resolve_dependencies_and_install
@pkg.in_upgrade = false
else
puts "#{@pkg.name} is already up to date.".lightgreen
end
else
# Make an installed packages list belong to the dependency order
dependencies = []
@device[:installed_packages].each do |package|
# skip package if it is dependent other packages previously checked
next if dependencies.include? package[:name]
# add package itself
dependencies = [ package[:name] ].concat(dependencies)
# expand dependencies and add it to the dependencies list
search package[:name], true
@dependencies = []
exp_dep = expand_dependencies
dependencies = exp_dep.concat(dependencies)
end
dependencies.uniq!
# Check version number of installed package and make a target list
toBeUpdated = []
dependencies.each do |dep|
package = @device[:installed_packages].find {|pkg| pkg[:name] == dep}
next unless package
search package[:name], true
if package[:version] != @pkg.version
toBeUpdated.push(package[:name])
end
end
# Adjust package install ordering for upgrades.
CREW_FIRST_PACKAGES.each do |pkg|
# Add package to beginning.
toBeUpdated.insert(0,toBeUpdated.delete(pkg)) if toBeUpdated.include? pkg
end
CREW_LAST_PACKAGES.each do |pkg|
if toBeUpdated.include? pkg
# Add package to beginning.
toBeUpdated.insert(0,toBeUpdated.delete(pkg))
# Now rotate first package to last package.
toBeUpdated=toBeUpdated.rotate(1)
end
end
unless toBeUpdated.empty?
puts 'Updating packages...'
toBeUpdated.each do |package|
search package
print_current_package
puts "Updating " + @pkg.name + "..." if @opt_verbose
@pkg.in_upgrade = true
resolve_dependencies_and_install
@pkg.in_upgrade = false
end
puts "Packages have been updated.".lightgreen
else
puts "Your software is already up to date.".lightgreen
end
end
end
def download
url = @pkg.get_url(@device[:architecture])
source = @pkg.is_source?(@device[:architecture])
uri = URI.parse url
filename = File.basename(uri.path)
sha256sum = @pkg.get_sha256(@device[:architecture])
@extract_dir = @pkg.get_extract_dir
if !url
abort "No precompiled binary or source is available for #{@device[:architecture]}.".lightred
elsif !source
puts "Precompiled binary available, downloading..."
elsif @pkg.build_from_source
puts "Downloading source..."
elsif uri =~ /^SKIP$/i
puts "Skipping source download..."
else
puts "No precompiled binary available for your platform, downloading source..."
end
Dir.chdir CREW_BREW_DIR do
case File.basename(filename)
# Sources that download with curl
when /\.zip$/i, /\.(tar(\.(gz|bz2|xz|lz))?|tgz|tbz|tpxz|txz)$/i, /\.deb$/i, /\.AppImage$/i
# Recall file from cache if requested
if CREW_CACHE_ENABLED
puts "Looking for #{@pkg.name} archive in cache".orange if @opt_verbose
cachefile = CREW_CACHE_DIR + filename
if ! File.exist?(cachefile)
puts 'Cannot find cached archive. 😔 Will download.'.lightred
cachefile = ''
else
puts "#{@pkg.name.capitalize} archive file exists in cache".lightgreen if @opt_verbose
if Digest::SHA256.hexdigest( File.read(cachefile) ) == sha256sum || sha256sum =~ /^SKIP$/i
begin
# Hard link cached file if possible.
FileUtils.ln cachefile, CREW_BREW_DIR, force: true, verbose: @fileutils_verbose unless File.identical?(cachefile,CREW_BREW_DIR + '/' + filename)
puts "Archive hard linked from cache".green if @opt_verbose
rescue
# Copy cached file if hard link fails.
FileUtils.cp cachefile, CREW_BREW_DIR, verbose: @fileutils_verbose unless File.identical?(cachefile,CREW_BREW_DIR + '/' + filename)
puts "Archive copied from cache".green if @opt_verbose
end
puts "Archive found in cache".lightgreen
return {source: source, filename: filename}
else
puts 'Cached archive checksum mismatch. 😔 Will download.'.lightred
cachefile = ''
end
end
end
# Download file if not cached.
system "#{CURL} --retry 3 -#{@verbose}#LC - --insecure \'#{url}\' --output #{filename}"
abort 'Checksum mismatch. 😔 Try again.'.lightred unless
Digest::SHA256.hexdigest( File.read(filename) ) == sha256sum || sha256sum =~ /^SKIP$/i
puts "#{@pkg.name.capitalize} archive downloaded.".lightgreen
# Stow file in cache if requested (and if file is not from cache).
if CREW_CACHE_ENABLED and cachefile.to_s.empty?
begin
# Hard link to cache if possible.
FileUtils.ln filename, CREW_CACHE_DIR, verbose: @fileutils_verbose
puts "Archive hard linked to cache".green if @opt_verbose
rescue
# Copy to cache if hard link fails.
FileUtils.cp filename, CREW_CACHE_DIR, verbose: @fileutils_verbose
puts "Archive copied to cache".green if @opt_verbose
end
puts 'Archive copied to cache.'.lightgreen
end
return {source: source, filename: filename}
# Sources that download with git
when /\.git$/i
# Recall repository from cache if requested
if CREW_CACHE_ENABLED
if @pkg.git_branch.nil? || @pkg.git_branch.empty?
cachefile = CREW_CACHE_DIR + filename + @pkg.git_hashtag + '.tar.xz'
puts "cachefile is #{cachefile}".orange if @opt_verbose
else
# Use to the day granularity for a branch timestamp.
cachefile = CREW_CACHE_DIR + filename + @pkg.git_branch.gsub(/[^0-9A-Za-z.\-]/, '_') + Time.now.strftime("%m%d%Y") + '.tar.xz'
puts "cachefile is #{cachefile}".orange if @opt_verbose
end
if File.file?(cachefile)
if system "cd #{CREW_CACHE_DIR} && sha256sum -c #{cachefile}.sha256"
FileUtils.mkdir @extract_dir
system "tar x#{@verbose}f #{cachefile} -C #{@extract_dir}"
return {source: source, filename: filename}
else
puts 'Cached git repository checksum mismatch. 😔 Will download.'.lightred
end
else
puts 'Cannot find cached git repository. 😔 Will download.'.lightred
end
end
# Download via git
Dir.mkdir @extract_dir
Dir.chdir @extract_dir do
system 'git init'
system 'git config advice.detachedHead false'
system 'git config init.defaultBranch master'
system "git remote add origin #{@pkg.source_url}", exception: true
unless @pkg.git_branch.nil? || @pkg.git_branch.empty?
# Leave a message because this step can be slow.
puts "Downloading src from a git branch. This may take a while..."
system "git remote set-branches origin #{@pkg.git_branch}", exception: true
system "git fetch --progress origin #{@pkg.git_branch}", exception: true
system "git checkout #{@pkg.git_hashtag}", exception: true
else
system "git fetch --depth 1 origin #{@pkg.git_hashtag}", exception: true
system 'git checkout FETCH_HEAD'
end
system 'git submodule update --init --recursive'
puts 'Repository downloaded.'.lightgreen
end
# Stow file in cache if requested
if CREW_CACHE_ENABLED
puts 'Caching downloaded git repo...'
Dir.chdir "#{@extract_dir}" do
system "tar c#{@verbose}Jf #{cachefile} \
$(find -mindepth 1 -maxdepth 1 -printf '%P\n')"
end
system "sha256sum #{cachefile} > #{cachefile}.sha256"
puts 'Git repo cached.'.lightgreen
end
when /^SKIP$/i
Dir.mkdir @extract_dir
end
end
return {source: source, filename: filename}
end
def unpack(meta)
target_dir = nil
Dir.chdir CREW_BREW_DIR do
FileUtils.mkdir_p @extract_dir, verbose: @fileutils_verbose
case File.basename meta[:filename]
when /\.zip$/i
puts "Unpacking archive using 'unzip', this may take a while..."
_verbopt = @opt_verbose ? '-v' : '-qq'
system 'unzip', _verbopt, '-d', @extract_dir, meta[:filename], exception: true
when /\.(tar(\.(gz|bz2|xz|lz))?|tgz|tbz|txz)$/i
puts "Unpacking archive using 'tar', this may take a while..."
system "tar x#{@verbose}f #{meta[:filename]} -C #{@extract_dir}", exception: true
when /\.deb$/i
puts "Unpacking archive using 'ar', this may take a while..."
system "ar -p #{meta[:filename]} data.tar.xz | xz -dc#{@verbose} | tar x#{@verbose} -C #{@extract_dir}", exception: true
when /\.AppImage$/i
puts "Unpacking 'AppImage' archive, this may take a while..."
FileUtils.chmod 0o755, meta[:filename], verbose: @fileutils_verbose
Dir.chdir @extract_dir do
system "../#{meta[:filename]} --appimage-extract", exception: true
end
when /\.tpxz$/i
unless File.exist?("#{CREW_PREFIX}/bin/pixz")
abort 'Pixz is needed for this install. Please install it with \'crew install pixz\''.lightred
end
puts "Unpacking 'tpxz' archive using 'tar', this may take a while..."
system "tar -Ipixz -x#{@verbose}f #{meta[:filename]} -C #{@extract_dir}", exception: true
end
if meta[:source] == true
# Check the number of directories in the archive
entries = Dir["#{@extract_dir}/*"]
entries = Dir[@extract_dir] if entries.empty?
if entries.empty?
abort "Empty archive: #{meta[:filename]}".lightred
elsif entries.length == 1 && File.directory?(entries.first)
# Use `extract_dir/dir_in_archive` if there is only one directory.
target_dir = entries.first
else
# Use `extract_dir` otherwise
target_dir = @extract_dir
end
else
# Use `extract_dir` for binary distribution
target_dir = @extract_dir
end
end
return CREW_BREW_DIR + target_dir
end
def build_and_preconfigure(target_dir)
Dir.chdir target_dir do
puts 'Building from source, this may take a while...'
# Rename *.la files temporarily to *.la_tmp to avoid
# libtool: link: '*.la' is not a valid libtool archive.
# See https://gnunet.org/faq-la-files and
# https://stackoverflow.com/questions/42963653/libquadmath-la-is-not-a-valid-libtool-archive-when-configuring-openmpi-with-g
puts 'Rename all *.la files to *.la_tmp'.lightblue
system "find #{CREW_LIB_PREFIX} -type f -name *.la -print0 | xargs --null -I{} mv #{@short_verbose} {} {}_tmp"
@pkg.in_build = true
@pkg.patch
@pkg.prebuild
@pkg.build
@pkg.in_build = false
# wipe crew destdir
FileUtils.rm_rf Dir.glob("#{CREW_DEST_DIR}/*"), verbose: @fileutils_verbose
puts 'Preconfiguring package...'
@pkg.install
# Rename all *.la_tmp back to *.la to avoid
# cannot access '*.la': No such file or directory
puts 'Rename all *.la_tmp files back to *.la'.lightblue
system "find #{CREW_LIB_PREFIX} -type f -name '*.la_tmp' -exec sh -c 'mv #{@short_verbose} \"$1\" \"${1%.la_tmp}.la\"' _ {} \\;"
end
end
def pre_flight
puts 'Performing pre-flight checks...'
@pkg.preflight
end
def pre_install(dest_dir)
Dir.chdir dest_dir do
puts 'Performing pre-install...'
@pkg.preinstall
end
end
def post_install
Dir.mktmpdir do |post_install_tempdir|
Dir.chdir post_install_tempdir do
puts "Performing post-install for #{@pkg.name}...".lightblue
@pkg.postinstall
end
end
end
def compress_doc(dir)
# check whether crew should compress
return if CREW_NOT_COMPRESS || ENV['CREW_NOT_COMPRESS'] || !File.exist?("#{CREW_PREFIX}/bin/compressdoc")
if Dir.exist? dir
system "find #{dir} -type f ! -perm -200 | xargs -r chmod u+w"
system "compressdoc --gzip -9 #{@short_verbose} #{dir}"
end
end
def prepare_package(destdir)
Dir.chdir destdir do
# Avoid /usr/local/share/info/dir{.gz} file conflict:
# The install-info program maintains a directory of installed
# info documents in /usr/share/info/dir for the use of info
# readers. This file must not be included in packages other
# than install-info.
# https://www.debian.org/doc/debian-policy/ch-docs.html#info-documents
FileUtils.rm "#{CREW_DEST_PREFIX}/share/info/dir" if File.exist?("#{CREW_DEST_PREFIX}/share/info/dir")
# Remove all perl module files which will conflict
if @pkg.name =~ /^perl_/
puts "Removing .packlist and perllocal.pod files to avoid conflicts with other perl packages.".orange
system "find #{CREW_DEST_DIR} -type f \\( -name '.packlist' -o -name perllocal.pod \\) -delete"
end
# compress manual files
compress_doc "#{CREW_DEST_PREFIX}/man"
compress_doc "#{CREW_DEST_PREFIX}/info"
compress_doc "#{CREW_DEST_PREFIX}/share/man"
compress_doc "#{CREW_DEST_PREFIX}/share/info"
# create file list
system 'find . -type f > ../filelist'
system 'find . -type l >> ../filelist'
system 'cut -c2- ../filelist > filelist'
# check for conflicts with other installed files
puts "Checking for conflicts with files from installed packages..."
conflicts = []
@conflictscmd = "grep --exclude #{CREW_META_PATH}#{@pkg.name}.filelist \
-Fxf filelist #{CREW_META_PATH}*.filelist | tr ':' ' ' | \
sed 's,.filelist,,g' | sed 's,#{CREW_META_PATH},,g'"
conflicts << %x[#{@conflictscmd}].chomp.split(" ")
conflicts.reject!(&:empty?)
unless conflicts.empty?
puts "Unable to complete this build since there is a conflict with the same file in another package.".lightred
pp = ''
conflicts.each do |p, f|
puts "\n#{p} package conflicts below:".lightred if p != pp
puts f.lightred
pp = p
end
abort unless ENV['CREW_CONFLICTS_ONLY_ADVISORY'] == '1'
end
# create directory list
system 'find . -type d > ../dlist'
system 'cut -c2- ../dlist > dlistcut'
system 'tail -n +2 dlistcut > dlist'
# remove temporary files
FileUtils.rm_rf ['dlistcut', '../dlist', '../filelist'], verbose: @fileutils_verbose
strip_dir destdir
# make hard linked files symlinks and use upx on executables
shrink_dir destdir
end
end
def strip_find_files(find_cmd, strip_option = "")
# check whether crew should strip
return if CREW_NOT_STRIP || ENV['CREW_NOT_STRIP'] || !File.exist?("#{CREW_PREFIX}/bin/llvm-strip")
# run find_cmd and strip only ar or ELF files
system "#{find_cmd} | xargs -r chmod u+w"
system "#{find_cmd} | xargs -r sh -c 'for i in \"$0\" \"$@\"; do case \"$(head -c 4 $i)\" in ?ELF|\!?ar) echo \"$i\";; esac ; done' | xargs -r llvm-strip #{strip_option}"
end
def strip_dir(dir)
unless CREW_NOT_STRIP || ENV['CREW_NOT_STRIP']
Dir.chdir dir do
# Strip libraries with -S
puts "Stripping libraries..."
strip_find_files "find . -type f \\( -name 'lib*.a' -o -name 'lib*.so*' \\) -print", "-S"
# Strip binaries but not compressed archives
puts "Stripping binaries..."
extensions = [ 'bz2', 'gz', 'lha', 'lz', 'lzh', 'rar', 'tar', 'tbz', 'tgz', 'tpxz', 'txz', 'xz', 'Z', 'zip' ]
inames = extensions.join(' ! -iname *\.')
strip_find_files "find . -type f ! -iname *\.#{inames} -perm /111 -print | sed -e '/lib.*\.a$/d' -e '/lib.*\.so/d'"
end
end
end
def shrink_dir(dir)
# We might also want a package option to avoid using these tools
# on specific packages such as sommelier & xwayland.
if ENV['CREW_SHRINK_ARCHIVE'] == '1'
Dir.chdir dir do
if File.exist?("#{CREW_PREFIX}/bin/rdfind")
puts "Using rdfind to find duplicate or hard linked files."
system "#{CREW_PREFIX}/bin/rdfind -removeidentinode true -makesymlinks true -makeresultsfile false ."
end
if File.exist?("#{CREW_PREFIX}/bin/symlinks")
puts "Using symlinks tool to make absolute symlinks relative"
system 'symlinks -cr .' if File.exist?("#{CREW_PREFIX}/bin/symlinks")
end
# Issues with non-x86_64 in compressing libraries, so just compress
# non-libraries. Also note that one needs to use "upx -d" on a
# compressed file to use ldd.
# sommelier also isn't happy when sommelier and xwayland are compressed
# so don't compress those packages.
if File.exist?("#{CREW_PREFIX}/bin/upx")
# Logic here is to find executable binaries, compress after making
# a backup, then expand the compressed file with upx. If the
# expansion doesn't error out then it is ok to delete the backup.
@execfiles = %x[find . -executable -type f ! \\( -name \"*.so*\" -o -name \"*.a\" \\) -exec sh -c \"file -i \'{}\' | grep -q \'executable; charset=binary\'\" \\; -print].chomp
unless @execfiles.empty? or @execfiles.nil?
puts "Using upx to shrink binaries."
@execfiles.each_line do |execfile|
system "upx --best -k --overlay=skip #{execfile} && \
\( upx -t #{execfile} && rm #{execfile}.~ || mv #{execfile}.~ #{execfile}\)"
end
end
end
end
end
end
def install_package(pkgdir)
Dir.chdir pkgdir do
# install filelist, dlist and binary files
puts 'Performing install...'
FileUtils.mv 'dlist', CREW_META_PATH + @pkg.name + '.directorylist', verbose: @fileutils_verbose
FileUtils.mv 'filelist', CREW_META_PATH + @pkg.name + '.filelist', verbose: @fileutils_verbose
if Dir.exists? "#{pkgdir}/home" then
system "tar -c#{@verbose}f - ./usr/* ./home/* | (cd /; tar xp --keep-directory-symlink -f -)"
elsif Dir.exists? "#{pkgdir}/usr" then
system "tar -c#{@verbose}f - ./usr/* | (cd /; tar xp --keep-directory-symlink -f -)"
end
end
end
def resolve_dependencies_and_install
@resolve_dependencies_and_install = 1
unless @pkg.is_fake?
# Process preflight block to see if package should even
# be downloaded or installed.
pre_flight
end
begin
origin = @pkg.name
@to_postinstall = []
resolve_dependencies
search origin, true
install
@to_postinstall.append(@pkg.name)
@to_postinstall.each do |dep|
search dep
post_install
end
rescue InstallError => e
abort "#{@pkg.name} failed to install: #{e.to_s}".lightred
ensure
# cleanup
unless @opt_keep
FileUtils.rm_rf Dir.glob("#{CREW_BREW_DIR}/*")
FileUtils.mkdir_p "#{CREW_BREW_DIR}/dest" # this is a little ugly, feel free to find a better way
end
end
puts "#{@pkg.name.capitalize} installed!".lightgreen
@resolve_dependencies_and_install = 0
end
def expand_dependencies
def push_dependencies
if @pkg.is_binary?(@device[:architecture]) ||
(!@pkg.in_upgrade && !@pkg.build_from_source && @device[:installed_packages].any? { |pkg| pkg[:name] == @pkg.name })
# retrieve name of dependencies that doesn't contain :build tag
check_deps = @pkg.dependencies.select {|k, v| !v.include?(:build)}.map {|k, v| k}
else
# retrieve name of all dependencies
check_deps = @pkg.dependencies.map {|k, v| k}
end
# check all dependencies recursively
check_deps.each do |dep|
# build unique dependencies list
unless @dependencies&.include?(dep) || dep == @pkgName
@dependencies << dep
search dep, true
push_dependencies
end
end
end
push_dependencies
end
def resolve_dependencies
abort "Package #{@pkg.name} is not compatible with your device architecture (#{ARCH}) :/".lightred unless @device[:compatible_packages].any? do |elem| elem[:name] == @pkg.name end
@dependencies = []
if @pkg.build_from_source
# make sure all buildessential packages are installed
pkgname = @pkg.name
search 'buildessential', true
expand_dependencies
search pkgname, true
end
expand_dependencies
# leave only not installed packages in dependencies
@dependencies.select! {|name| @device[:installed_packages].none? {|pkg| pkg[:name] == name}}
return if @dependencies.empty?
puts 'The following packages also need to be installed: '
deps = @dependencies
# populate arrays with common elements
begin_packages = deps & CREW_FIRST_PACKAGES
end_packages = deps & CREW_LAST_PACKAGES
@dependencies.each do |dep|
depends = nil
File.open("#{CREW_PACKAGES_PATH}#{dep}.rb") do |f|
f.each_line do |line|
found = line[/depends_on/] if line.ascii_only?
if found
depends = true
break
end
end
end
# if a dependency package has no other dependencies, push to the front
begin_packages.push dep unless depends
end
# Remove elements in another array
deps -= begin_packages
deps -= end_packages
@dependencies = (begin_packages + deps + end_packages).uniq
@dependencies.each do |dep|
print dep + ' '
end
puts
print 'Do you agree? [Y/n] '
response = STDIN.getc
case response
when 'n'
abort 'No changes made.'
when "\n", "y", "Y"
puts 'Proceeding...'
proceed = true
else
puts "I don't understand `#{response}`. :(".lightred
abort 'No changes made.'
end
if proceed
@dependencies.each do |dep|
search dep
print_current_package
install
end
if @resolve_dependencies_and_install == 1 or @resolve_dependencies_and_build == 1
@to_postinstall = @dependencies
else
@dependencies.each do |dep|
search dep
post_install
end
end
end
end
def install
if !@pkg.in_upgrade && @device[:installed_packages].any? { |pkg| pkg[:name] == @pkg.name }
puts "Package #{@pkg.name} already installed, skipping...".lightgreen
return
end
unless @pkg.is_fake?
meta = download
target_dir = unpack meta
if meta[:source] == true
# build from source and place binaries at CREW_DEST_DIR
# CREW_DEST_DIR contains usr/local/... hierarchy
build_and_preconfigure target_dir
# prepare filelist and dlist at CREW_DEST_DIR
prepare_package CREW_DEST_DIR
# use CREW_DEST_DIR
dest_dir = CREW_DEST_DIR
else
# use extracted binary directory
dest_dir = target_dir
end
end
# remove it just before the file copy
if @pkg.in_upgrade
puts 'Removing since upgrade or reinstall...'
remove @pkg.name
end
unless @pkg.is_fake?
# perform pre-install process
pre_install dest_dir
# perform install process
install_package dest_dir
unless @resolve_dependencies_and_install == 1 or @resolve_dependencies_and_build == 1
# perform post-install process
post_install
end
end
#add to installed packages
@device[:installed_packages].push(name: @pkg.name, version: @pkg.version)
File.open(CREW_CONFIG_PATH + 'device.json', 'w') do |file|
output = JSON.parse @device.to_json
file.write JSON.pretty_generate(output)
end
# Update shared library cache after install is complete.
system "echo #{CREW_LIB_PREFIX} > #{CREW_PREFIX}/etc/ld.so.conf"
system "#{CREW_PREFIX}/sbin/ldconfig -f #{CREW_PREFIX}/etc/ld.so.conf -C #{CREW_PREFIX}/etc/ld.so.cache"
end
def resolve_dependencies_and_build
@resolve_dependencies_and_build = 1
@to_postinstall = []
begin
origin = @pkg.name
# mark current package as which is required to compile from source
@pkg.build_from_source = true
resolve_dependencies
@to_postinstall.each do |dep|
search dep
post_install
end
search origin, true
build_package Dir.pwd
rescue InstallError => e
abort "#{@pkg.name} failed to build: #{e.to_s}".lightred
ensure
#cleanup
unless @opt_keep
FileUtils.rm_rf Dir.glob("#{CREW_BREW_DIR}/*"), verbose: @fileutils_verbose
FileUtils.mkdir_p CREW_BREW_DIR + '/dest', verbose: @fileutils_verbose #this is a little ugly, feel free to find a better way
end
end
puts "#{@pkg.name} is built!".lightgreen
@resolve_dependencies_and_build = 0
end
def build_package(pwd)
abort 'It is not possible to build a fake package'.lightred if @pkg.is_fake?
abort 'It is not possible to build without source'.lightred if !@pkg.is_source?(@device[:architecture])
# download source codes and unpack it
meta = download
target_dir = unpack meta
# build from source and place binaries at CREW_DEST_DIR
build_and_preconfigure target_dir
# call check method here. this check method is called by this function only,
# therefore it is possible place time consuming tests in the check method.
if Dir.exist? target_dir
Dir.chdir target_dir do
puts 'Checking...'
@pkg.check
end
end
# prepare filelist and dlist at CREW_DEST_DIR
prepare_package CREW_DEST_DIR
# build package from filelist, dlist and binary files in CREW_DEST_DIR
puts 'Archiving...'
archive_package pwd
end
def archive_package(pwd)
unless ENV['CREW_USE_PIXZ'] == '0' || !File.exist?("#{CREW_PREFIX}/bin/pixz")
puts "Using pixz to compress archive."
pkg_name = "#{@pkg.name}-#{@pkg.version}-chromeos-#{@device[:architecture]}.tpxz"
Dir.chdir CREW_DEST_DIR do
# Use smaller blocks with "-f0.25" to make random access faster.
system "tar c#{@verbose} * | pixz -f0.25 -9 > #{pwd}/#{pkg_name}"
end
else
pkg_name = "#{@pkg.name}-#{@pkg.version}-chromeos-#{@device[:architecture]}.tar.xz"
Dir.chdir CREW_DEST_DIR do
system "tar c#{@verbose}Jf #{pwd}/#{pkg_name} *"
end
end
system "sha256sum #{pwd}/#{pkg_name} > #{pwd}/#{pkg_name}.sha256"
end
def remove(pkgName)
#make sure the package is actually installed
unless @device[:installed_packages].any? { |pkg| pkg[:name] == pkgName } || File.exist?("#{CREW_META_PATH}#{pkgName}.filelist")
puts "Package #{pkgName} isn't installed.".lightred
return
end
#if the filelist exists, remove the files and directories installed by the package
if File.file?("#{CREW_META_PATH}#{pkgName}.filelist")
Dir.chdir CREW_CONFIG_PATH do
#remove all files installed by the package
File.open("meta/#{pkgName}.filelist").each_line do |line|
begin
puts 'Removing file ' + line.chomp + ''.lightred if @opt_verbose
File.unlink line.chomp
rescue => exception #swallow exception
end
end
#remove all directories installed by the package
File.readlines("meta/#{pkgName}.directorylist").reverse.each do |line|
begin
puts 'Removing directory ' + line.chomp + ''.lightred if @opt_verbose
Dir.rmdir line.chomp
rescue => exception #swallow exception
end
end
#remove the file and directory list
File.unlink "meta/#{pkgName}.filelist"
File.unlink "meta/#{pkgName}.directorylist"
end
end
#remove from installed packages
puts 'Removing package ' + pkgName + "".lightred if @opt_verbose
@device[:installed_packages].each do |elem|
@device[:installed_packages].delete elem if elem[:name] == pkgName
end
#update the device manifest
File.open(CREW_CONFIG_PATH + 'device.json', 'w') do |file|
out = JSON.parse @device.to_json
file.write JSON.pretty_generate(out)
end
search pkgName, true
@pkg.remove
puts "#{pkgName.capitalize} removed!".lightgreen
end
def build_command(args)
args["<name>"].each do |name|
@pkgName = name
search @pkgName
print_current_package @opt_verbose
resolve_dependencies_and_build
end
end
def const_command(args)
unless args["<name>"].empty?
args["<name>"].each do |name|
const name
end
else
const nil
end
end
def deps_command(args)
args["<name>"].each do |name|
@dependencies = []
@pkgName = name
search @pkgName
print_current_package
expand_dependencies
puts @dependencies
end
end
def download_command(args)
args["<name>"].each do |name|
@pkgName = name
search @pkgName
print_current_package @opt_verbose
download
end
end
def files_command(args)
args["<name>"].each do |name|
@pkgName = name
search @pkgName
print_current_package
files name
end
end
def help_command(args)
if args["<command>"]
help args["<command>"]
else
puts "Usage: crew help <command>"
help nil
end
end
def install_command(args)
args["<name>"].each do |name|
@pkgName = name
search @pkgName
print_current_package true
@pkg.build_from_source = true if @opt_src or @opt_recursive
resolve_dependencies_and_install
end
end
def list_command(args)
if args['available']
list_available
elsif args['installed']
puts list_installed
elsif args['compatible']
list_compatible true
elsif args['incompatible']
list_compatible false
end
end
def postinstall_command(args)
args["<name>"].each do |name|
@pkgName = name
search @pkgName, true
if @device[:installed_packages].any? do |elem| elem[:name] == @pkgName end
@pkg.postinstall
else
puts "Package #{@pkgName} is not installed. :(".lightred
end
end
end
def reinstall_command(args)
args["<name>"].each do |name|
@pkgName = name
search @pkgName
print_current_package
@pkg.build_from_source = true if @opt_src or @opt_recursive
if @pkgName
@pkg.in_upgrade = true
resolve_dependencies_and_install
@pkg.in_upgrade = false
end
end
end
def remove_command(args)
args["<name>"].each do |name|
remove name
end
end
def search_command(args)
args["<name>"].each do |name|
regexp_search name
end.empty? and begin
list_packages
end
end
def update_command(args)
if args['<compatible>']
generate_compatible
else
update
end
end
def upgrade_command(args)
args["<name>"].each do |name|
@pkgName = name
search @pkgName
print_current_package
@pkg.build_from_source = true if @opt_src
upgrade
end.empty? and begin
upgrade
end
end
def whatprovides_command(args)
args["<pattern>"].each do |name|
whatprovides name
end
end
def is_command(name)
return false if name =~ /^[-<]/
return true
end
command_name = args.find { |k, v| v && is_command(k) } [0]
function = command_name + '_command'
send(function, args)