Files
chromebrew/crew
Kazushi (Jam) Marukawa a1ddd9cbf0 Change crew to remove already installed packages and the target package from the dependencies.
Change crew to hide `buildessential` from dependencies list since it is a fake package.
Change crew to not check :build dependencies if a target is already installed.
Chagen crew to remove unnecessary push_dependency call.
Change package.rb to update comments
2017-06-08 21:58:48 +09:00

623 lines
17 KiB
Ruby
Executable File

#!/usr/bin/env ruby
require 'find'
require 'net/http'
require 'uri'
require 'digest/sha1'
require 'json'
require 'fileutils'
@command = ARGV[0]
@pkgName = ARGV[1]
CREW_PREFIX = '/usr/local'
CREW_LIB_PATH = CREW_PREFIX + '/lib/crew/'
CREW_CONFIG_PATH = CREW_PREFIX + '/etc/crew/'
CREW_BREW_DIR = CREW_PREFIX + '/tmp/crew/'
CREW_DEST_DIR = CREW_BREW_DIR + 'dest'
# Set CREW_NPROC from environment variable or `nproc`
if ENV["CREW_NPROC"].to_s == ''
CREW_NPROC = `nproc`.strip
else
CREW_NPROC = ENV["CREW_NPROC"]
end
# Set XZ_OPT environment variable for build command.
# If CREW_XZ_OPT is defined, use it by default. Use `-7e`, otherwise.
if ENV["CREW_XZ_OPT"].to_s == ''
ENV["XZ_OPT"] = "-7e"
else
ENV["XZ_OPT"] = ENV["CREW_XZ_OPT"]
end
ARCH = `uname -m`.strip
$LOAD_PATH.unshift "#{CREW_LIB_PATH}lib"
USER = `whoami`.chomp
#disallow sudo
abort "Chromebrew should not be run as root." if Process.uid == 0 && @command != "remove"
@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 set_package (pkgName, silent = false)
require CREW_LIB_PATH + 'packages/' + pkgName
@pkg = Object.const_get(pkgName.capitalize)
@pkg.name = pkgName
puts "Found #{pkgName}, version #{@pkg.version}" unless silent
end
def list_packages
Find.find (CREW_LIB_PATH + 'packages') do |filename|
Find.find(CREW_CONFIG_PATH + 'meta/') do |packageList|
packageName = File.basename filename, '.rb'
print '(i) ' if packageList == CREW_CONFIG_PATH + 'meta/' + packageName + '.filelist'
end
puts File.basename filename, '.rb' if File.extname(filename) == '.rb'
end
end
def search (pkgName, silent = false)
Find.find (CREW_LIB_PATH + 'packages') do |filename|
return set_package(pkgName, silent) if filename == CREW_LIB_PATH + 'packages/' + pkgName + '.rb'
end
abort "package #{pkgName} not found :("
end
def help (pkgName)
case pkgName
when "build"
puts "Build a package."
puts "Usage: crew build [package]"
puts "Build [package] from source and place the archive and checksum in the current working directory."
when "download"
puts "Download a package."
puts "Usage: crew download [package]"
puts "Download [package] to `CREW_BREW_DIR` (`/usr/local/tmp/crew` by default), but don't install it."
when "install"
puts "Install a package."
puts "Usage: crew install [package]"
puts "The [package] must be a valid name. Use `crew search [package]` to search for a package to install."
when "remove"
puts "Remove a package."
puts "Usage: crew remove [package]"
puts "The [package] must be currently installed."
when "search"
puts "Look for a package."
puts "Usage: crew search [package]"
puts "If [package] is omitted, all packages will be returned. (i) in front of the name means the package is installed."
puts "Tips:"
puts " crew search | grep '(i)' will return all installed packages."
puts " crew search | grep -v '(i)' will return all available packages not already installed."
when "update"
puts "Update crew."
puts "Usage: crew update"
puts "This only updates crew itself. Use 'crew upgrade' to update packages."
when "upgrade"
puts "Update package(s)."
puts "Usage: crew upgrade [package]"
puts "If [package] is omitted, all packages will be updated. Otherwise, a specific package will be updated."
puts "Use 'crew update' to update crew itself."
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: build, download, install, remove, search, update, upgrade, whatprovides"
end
end
def whatprovides (pkgName)
fileArray = []
Find.find (CREW_CONFIG_PATH + 'meta/') do |packageList|
if File.file? packageList
if packageList[/\.filelist$/]
packageName = File.basename packageList, '.filelist'
File.readlines(packageList).each do |line|
found = line[/#{Regexp.new(pkgName)}/]
if found
fileLine = packageName + ': ' + line
if not fileArray.include? fileLine
fileArray.push(fileLine)
end
end
end
end
end
end
if not fileArray.empty?
fileArray.sort.each do |item|
puts item
end
puts "\nTotal found: #{fileArray.length}"
end
end
def update
abort "'crew update' is used to update crew itself. Use 'crew upgrade <packageName> to upgrade a specific package." if @pkgName
#update package lists
Dir.chdir CREW_LIB_PATH do
system "git fetch origin master"
system "git reset --hard origin/master"
end
puts "Package lists, crew, and library updated."
#check for outdated installed packages
puts "Checking for package updates..."
puts ""
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 upgrade everything or 'crew upgrade <packageName>' to upgrade a specific package."
else
puts "Your software is up to date."
end
end
def upgrade
if @pkgName
search @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."
end
else
toBeUpdated = []
@device[:installed_packages].each do |package|
search package[:name], true
if package[:version] != @pkg.version
toBeUpdated.push(package[:name])
end
end
if toBeUpdated.length > 0
puts "Updating packages..."
toBeUpdated.each do |package|
search package
@pkg.in_upgrade = true
resolve_dependencies_and_install
@pkg.in_upgrade = false
end
puts "Packages have been updated."
else
puts "Your software is already up to date."
end
end
end
def download
url = @pkg.get_url(@device[:architecture])
source = @pkg.is_source?(@device[:architecture])
if !url
abort "No precompiled binary for #{@device[:architecture]} nor source is available."
elsif !source
puts "Precompiled binary available, downloading..."
elsif @pkg.build_from_source
puts "Downloading source..."
else
puts "No precompiled binary available for your platform, downloading source..."
end
uri = URI.parse url
filename = File.basename(uri.path)
if source
sha1sum = @pkg.source_sha1
else
sha1sum = @pkg.binary_sha1[@device[:architecture]]
end
Dir.chdir CREW_BREW_DIR do
system('wget', '--continue', '--no-check-certificate', url, '-O', filename)
abort 'Checksum mismatch :/ try again' unless Digest::SHA1.hexdigest( File.read("./#{filename}") ) == sha1sum
end
puts "Archive downloaded"
return {source: source, filename: filename}
end
def unpack (meta)
extract_dir = "#{meta[:filename]}.dir"
target_dir = nil
Dir.chdir CREW_BREW_DIR do
puts "Unpacking archive, this may take a while..."
Dir.mkdir("#{extract_dir}") unless Dir.exist?("#{extract_dir}")
if meta[:filename][-4,4] == ".zip"
system "unzip", "-qq", "-d", "#{extract_dir}", meta[:filename]
else
system "tar", "xf", meta[:filename], "-C", "#{extract_dir}"
end
if meta[:source] == true
# Check the number of directories in the archive
entries=Dir["#{extract_dir}/*"]
if entries.length == 0
abort "empty archive: #{meta[:filename]}"
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..."
@pkg.in_build = true
@pkg.build
@pkg.in_build = false
system "rm -rf #{CREW_DEST_DIR}/*" #wipe crew destdir
puts "Preconfiguring package..."
@pkg.install
end
end
def prepare_package (destdir)
Dir.chdir destdir do
#create directory list
system "find . -type f > ../filelist"
system "find . -type l >> ../filelist"
system "cut -c2- ../filelist > filelist"
#create file list
system "find . -type d > ../dlist"
system "cut -c2- ../dlist > dlistcut"
system "tail -n +2 dlistcut > dlist"
#remove temporary files
system "rm dlistcut ../dlist ../filelist"
end
end
def install_package (pkgdir)
Dir.chdir pkgdir do
FileUtils.mv 'dlist', CREW_CONFIG_PATH + "meta/#{@pkg.name}.directorylist"
FileUtils.mv 'filelist', CREW_CONFIG_PATH + "meta/#{@pkg.name}.filelist"
system "tar cf - ./usr/* | (cd /; tar xp --keep-directory-symlink -f -)"
end
end
def resolve_dependencies_and_install
begin
origin = @pkg.name
resolveDependencies
search origin, true
install
rescue InstallError => e
abort "#{@pkg.name} failed to install: #{e.to_s}"
ensure
#cleanup
unless ARGV[2] == 'keep'
Dir.chdir CREW_BREW_DIR do
system "rm -rf *"
system "mkdir dest" #this is a little ugly, feel free to find a better way
end
end
end
end
def resolveDependencies
@dependencies = []
# check source packages existance
@source_package = 0
def push_dependencies
if @pkg.is_binary?(@device[:architecture]) ||
(!@pkg.in_upgrade && @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}
elsif @pkg.is_fake?
# retrieve name of all dependencies
@check_deps = @pkg.dependencies.map {|k, v| k}
else
# retrieve name of all dependencies
@check_deps = @pkg.dependencies.map {|k, v| k}
# count the number of source packages to add buildessential into dependencies later
@source_package += 1
end
# remove a dependent package which is equal to the target
@check_deps.select! {|name| @pkgName != name}
# insert only not installed packages into dependencies
@dependencies = @check_deps.select {|name| @device[:installed_packages].none? {|pkg| pkg[:name] == name}}.concat(@dependencies)
# check all dependencies recursively
@check_deps.each do |dep|
search dep, true
push_dependencies
end
end
push_dependencies
# Add buildessential and solve its dependencies if any of dependent
# packages are made from source
if @source_package > 0
search 'buildessential', true
push_dependencies
end
return if @dependencies.empty?
puts "Following packages also need to be installed: "
@dependencies.uniq!
@dependencies.each do |dep|
print dep + " "
end
puts ""
puts "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}' :("
abort "No changes made."
end
if proceed
@dependencies.each do |dep|
search dep
install
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..."
return
end
unless @pkg.is_fake?
meta = download
target_dir = unpack meta
if meta[:source] == true
abort "You don't have a working C compiler. Run 'crew install buildessential' to get one and try again." unless system("gcc", "--version")
# 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
# remove it just before the file copy
if @pkg.in_upgrade
puts "Removing since upgrade..."
remove @pkg.name
end
# install filelist, dlist and binary files
puts "Installing..."
install_package dest_dir
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
puts "#{@pkg.name.capitalize} installed!"
end
def resolve_dependencies_and_build
begin
origin = @pkg.name
# mark current package as which is required to compile from source
@pkg.build_from_source = true
resolveDependencies
search origin, true
build_package Dir.pwd
rescue InstallError => e
abort "#{@pkg.name} failed to build: #{e.to_s}"
ensure
#cleanup
unless ARGV[2] == 'keep'
Dir.chdir CREW_BREW_DIR do
system "rm -rf *"
system "mkdir dest" #this is a little ugly, feel free to find a better way
end
end
end
end
def build_package (pwd)
abort "It is not possible to build fake package" if @pkg.is_fake?
abort "It is not possible to build without source" 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)
pkg_name = "#{@pkg.name}-#{@pkg.version}-chromeos-#{@device[:architecture]}.tar.xz"
Dir.chdir CREW_DEST_DIR do
system "tar cJf #{pwd}/#{pkg_name} *"
end
Dir.chdir pwd do
system "sha1sum #{pkg_name} > #{pkg_name}.sha1"
end
puts "#{pkg_name} is built!"
end
def remove (pkgName)
#make sure the package is actually installed
unless @device[:installed_packages].any? { |pkg| pkg[:name] == pkgName }
puts "Package #{pkgName} isn't installed."
return
end
#if the filelist exists, remove the files and directories installed by the package
if File.file?("#{CREW_CONFIG_PATH}meta/#{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
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
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
@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
puts "#{pkgName.capitalize} removed!"
end
case @command
when "help"
if @pkgName
help @pkgName
else
puts "Usage: crew help [command]"
help nil
end
when "search"
if @pkgName
search @pkgName
else
list_packages
end
when "whatprovides"
if @pkgName
whatprovides @pkgName
else
help "whatprovides"
end
when "download"
if @pkgName
search @pkgName
download
else
help "download"
end
when "update"
update
when "upgrade"
upgrade
when "install"
if @pkgName
search @pkgName
resolve_dependencies_and_install
else
help "install"
end
when "build"
if @pkgName
search @pkgName
resolve_dependencies_and_build
else
help "build"
end
when "remove"
if @pkgName
remove @pkgName
else
help "remove"
end
when nil
puts "Chromebrew, version 0.4.2"
puts "Usage: crew [command] [package]"
help nil
else
puts "I have no idea how to do #{@command} :("
help nil
end