mirror of
https://github.com/chromebrew/chromebrew.git
synced 2026-01-09 15:37:56 -05:00
crew: Check free disk space before install (#12562)
* crew: Show disk space before install Signed-off-by: SupeChicken666 <supechicken666@gmail.com> * Remove ver_check support Signed-off-by: SupeChicken666 <supechicken666@gmail.com> * Fix workflow error Signed-off-by: SupeChicken666 <supechicken666@gmail.com> * Bump version Signed-off-by: SupeChicken666 <supechicken666@gmail.com> * Make rubocop happy Signed-off-by: SupeChicken666 <supechicken666@gmail.com> * Add color for new prompts Signed-off-by: SupeChicken666 <supechicken666@gmail.com> * Chomp prompt Signed-off-by: SupeChicken666 <supechicken666@gmail.com> --------- Signed-off-by: SupeChicken666 <supechicken666@gmail.com>
This commit is contained in:
245
bin/crew
245
bin/crew
@@ -352,6 +352,7 @@ def upgrade(*pkgs, build_from_source: false)
|
||||
end
|
||||
|
||||
to_be_upgraded = []
|
||||
extra_deps = []
|
||||
|
||||
if pkgs.any?
|
||||
# check for specific package(s)
|
||||
@@ -372,6 +373,12 @@ def upgrade(*pkgs, build_from_source: false)
|
||||
return true
|
||||
end
|
||||
|
||||
# Check if there are any new dependencies
|
||||
to_be_upgraded.each do |pkg_name|
|
||||
search(pkg_name)
|
||||
extra_deps += resolve_dependencies
|
||||
end
|
||||
|
||||
# Eventually, we should have the upgrade order generated based upon an
|
||||
# analysis of the dependency hierarchy, to make sure that earlier
|
||||
# dependencies get upgraded first.
|
||||
@@ -387,10 +394,40 @@ def upgrade(*pkgs, build_from_source: false)
|
||||
rerun_upgrade = true
|
||||
end
|
||||
|
||||
puts <<~EOT
|
||||
|
||||
The following package(s) will be upgraded:
|
||||
|
||||
#{to_be_upgraded.join(' ')}
|
||||
|
||||
EOT
|
||||
|
||||
puts <<~EOT if extra_deps.any?
|
||||
The following package(s) also need to be installed:
|
||||
|
||||
#{extra_deps.join(' ')}
|
||||
|
||||
EOT
|
||||
|
||||
if @opt_force
|
||||
puts 'Proceeding with package upgrade...'.orange
|
||||
elsif !Package.agree_default_yes('Proceed')
|
||||
abort 'No changes made.'
|
||||
end
|
||||
|
||||
# install new dependencies (if any)
|
||||
to_be_upgraded.each do |pkg_name|
|
||||
search(pkg_name)
|
||||
resolve_dependencies
|
||||
extra_deps.each do |dep_to_install|
|
||||
search dep_to_install
|
||||
print_current_package
|
||||
install(skip_postinstall: true)
|
||||
end
|
||||
|
||||
puts 'Performing post-install for new dependencies...'.lightblue
|
||||
|
||||
# do post-install for new dependencies
|
||||
extra_deps.each do |dep_to_postinstall|
|
||||
search dep_to_postinstall
|
||||
post_install
|
||||
end
|
||||
|
||||
puts 'Updating packages...'
|
||||
@@ -398,13 +435,12 @@ def upgrade(*pkgs, build_from_source: false)
|
||||
# upgrade packages
|
||||
to_be_upgraded.each do |pkg_name|
|
||||
search(pkg_name)
|
||||
print_current_package
|
||||
@pkg.build_from_source = (build_from_source || CREW_BUILD_FROM_SOURCE)
|
||||
|
||||
puts "Updating #{@pkg.name}..." if CREW_VERBOSE
|
||||
|
||||
@pkg.in_upgrade = true
|
||||
resolve_dependencies_and_install
|
||||
resolve_dependencies_and_install(no_advisory: true)
|
||||
end
|
||||
|
||||
if rerun_upgrade
|
||||
@@ -1067,24 +1103,59 @@ def install_package(pkgdir)
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_dependencies_and_install
|
||||
@resolve_dependencies_and_install = 1
|
||||
|
||||
def resolve_dependencies_and_install(no_advisory: false)
|
||||
# Process preflight block to see if package should even
|
||||
# be downloaded or installed.
|
||||
pre_flight
|
||||
|
||||
begin
|
||||
origin = @pkg.name
|
||||
to_install = resolve_dependencies + [@pkg.name]
|
||||
free_space = `df --output=avail #{CREW_PREFIX}`.lines(chomp: true).last.to_i * 1024
|
||||
install_size = to_install.sum do |pkg|
|
||||
filelist = "#{CREW_LIB_PATH}/manifest/#{ARCH}/#{pkg[0]}/#{pkg}.filelist"
|
||||
if File.exist?(filelist)
|
||||
ConvenienceFunctions.read_filelist(filelist)[0]
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
@to_postinstall = []
|
||||
resolve_dependencies
|
||||
if free_space < install_size
|
||||
abort <<~EOT.chomp.lightred
|
||||
#{@pkg.name.capitalize} needs #{MiscFunctions.human_size(install_size)} of disk space to install.
|
||||
|
||||
search origin, silent: true
|
||||
install
|
||||
@to_postinstall.append(@pkg.name)
|
||||
@to_postinstall.each do |dep|
|
||||
search dep
|
||||
However, only #{MiscFunctions.human_size(free_space)} of free disk space is available currently.
|
||||
EOT
|
||||
end
|
||||
|
||||
unless no_advisory
|
||||
puts <<~EOT
|
||||
|
||||
The following package(s) will be installed:
|
||||
|
||||
#{to_install.join(' ')}
|
||||
|
||||
After installation, #{MiscFunctions.human_size(install_size)} of extra disk space will be taken. (#{MiscFunctions.human_size(free_space)} of free disk space available)
|
||||
|
||||
EOT
|
||||
|
||||
if @opt_force
|
||||
puts 'Proceeding with package installation...'.orange
|
||||
elsif !Package.agree_default_yes('Proceed')
|
||||
abort 'No changes made.'
|
||||
end
|
||||
end
|
||||
|
||||
to_install.each do |pkg_to_install|
|
||||
search pkg_to_install
|
||||
print_current_package
|
||||
install(skip_postinstall: true)
|
||||
end
|
||||
|
||||
puts 'Performing post-install for packages...'.lightblue
|
||||
|
||||
to_install.each do |pkg_to_postinstall|
|
||||
search pkg_to_postinstall
|
||||
post_install
|
||||
end
|
||||
rescue InstallError => e
|
||||
@@ -1135,80 +1206,44 @@ def resolve_dependencies_and_install
|
||||
end
|
||||
end
|
||||
puts "#{@pkg.name.capitalize} installed!".lightgreen
|
||||
@resolve_dependencies_and_install = 0
|
||||
end
|
||||
|
||||
def resolve_dependencies
|
||||
@dependencies = @pkg.get_deps_list(return_attr: true)
|
||||
package_copy_prompt = <<~EOT.chomp
|
||||
The package file for %s, which is a required dependency to build #{@pkg.name} only exists in #{CREW_LOCAL_REPO_ROOT}/packages/ .
|
||||
Is it ok to copy it to #{CREW_PACKAGES_PATH} so that the build can continue?
|
||||
EOT
|
||||
|
||||
# compare dependency version with required range (if installed)
|
||||
@dependencies.each do |dep|
|
||||
dep_name = dep.keys[0]
|
||||
dep_info = @device[:installed_packages].select { |pkg| pkg[:name] == dep_name }[0]
|
||||
|
||||
# skip if dependency is not installed
|
||||
next unless dep_info
|
||||
|
||||
_tags, version_check = dep.values[0]
|
||||
installed_version = dep_info[:version]
|
||||
|
||||
next unless version_check
|
||||
|
||||
# abort if the range is not fulfilled
|
||||
abort unless version_check.call(installed_version)
|
||||
end
|
||||
dependencies = @pkg.get_deps_list(return_attr: true)
|
||||
|
||||
# leave only dependency names (remove all package attributes returned by @pkg.get_deps_list)
|
||||
@dependencies.map!(&:keys).flatten!
|
||||
dependencies.map!(&:keys).flatten!
|
||||
|
||||
# abort & identify incompatible dependencies.
|
||||
@dependencies.each do |dep|
|
||||
dependencies.each do |dep|
|
||||
abort "Some dependencies e.g., #{dep}, are not compatible with your device architecture (#{ARCH}). Unable to continue.".lightred unless PackageUtils.compatible?(Package.load_package("#{CREW_PACKAGES_PATH}/#{dep}.rb"))
|
||||
end
|
||||
|
||||
# leave only not installed packages in dependencies
|
||||
@dependencies.reject! { |dep_name| @device[:installed_packages].any? { |pkg| pkg[:name] == dep_name } }
|
||||
dependencies.reject! { |dep_name| @device[:installed_packages].any? { |pkg| pkg[:name] == dep_name } }
|
||||
|
||||
# run preflight check for dependencies
|
||||
@dependencies.each do |dep_name|
|
||||
Package.load_package(File.join(CREW_PACKAGES_PATH, "#{dep_name}.rb")).preflight
|
||||
end
|
||||
dependencies.each do |dep|
|
||||
dep_file = File.join(CREW_PACKAGES_PATH, "#{dep}.rb")
|
||||
|
||||
return if @dependencies.empty?
|
||||
|
||||
puts 'The following packages also need to be installed: '
|
||||
|
||||
@dependencies.each do |dep|
|
||||
FileUtils.cp "#{CREW_LOCAL_REPO_ROOT}/packages/#{dep}.rb", CREW_PACKAGES_PATH if !File.file?(File.join(CREW_PACKAGES_PATH, "#{dep}.rb")) && File.file?(File.join(CREW_LOCAL_REPO_ROOT, "packages/#{dep}.rb")) && (@opt_force || Package.agree_default_yes("The package file for #{dep}, which is a required dependency to build #{@pkg.name} only exists in #{CREW_LOCAL_REPO_ROOT}/packages/ . Is it ok to copy it to #{CREW_PACKAGES_PATH} so that the build can continue?"))
|
||||
abort "Dependency #{dep} for #{@pkg.name} was not found.".lightred unless File.file?(File.join(CREW_PACKAGES_PATH, "#{dep}.rb"))
|
||||
end
|
||||
|
||||
puts @dependencies.join(' ')
|
||||
|
||||
if @opt_force
|
||||
puts 'Proceeding with dependency package installation...'.orange
|
||||
elsif !Package.agree_default_yes('Proceed')
|
||||
abort 'No changes made.'
|
||||
end
|
||||
|
||||
@dependencies.each do |dep|
|
||||
search dep
|
||||
print_current_package
|
||||
install
|
||||
end
|
||||
if @resolve_dependencies_and_install.eql?(1) || @resolve_dependencies_and_build.eql?(1)
|
||||
@to_postinstall = @dependencies
|
||||
else
|
||||
# Make sure the sommelier postinstall happens last so the messages
|
||||
# from that are not missed by users.
|
||||
@dependencies.partition { |v| v != 'sommelier' }.reduce(:+)
|
||||
@dependencies.each do |dep|
|
||||
search dep
|
||||
post_install
|
||||
# copy package script from CREW_LOCAL_REPO_ROOT if necessary
|
||||
unless File.exist?(dep_file)
|
||||
if File.exist?("#{CREW_LOCAL_REPO_ROOT}/packages/#{dep}.rb") && (@opt_force || Package.agree_default_yes(package_copy_prompt % dep))
|
||||
FileUtils.cp "#{CREW_LOCAL_REPO_ROOT}/packages/#{dep}.rb", dep_file
|
||||
elsif !File.exist?(dep_file)
|
||||
abort "Dependency #{dep} for #{@pkg.name} was not found.".lightred
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return dependencies
|
||||
end
|
||||
|
||||
def install
|
||||
def install(skip_postinstall: false)
|
||||
@pkg.in_install = true
|
||||
if !@pkg.in_upgrade && PackageUtils.installed?(@pkg.name) && !@pkg.superclass.to_s == 'RUBY'
|
||||
puts "Package #{@pkg.name} already installed, skipping...".lightgreen
|
||||
@@ -1281,10 +1316,8 @@ def install
|
||||
install_package dest_dir
|
||||
end
|
||||
|
||||
unless (@resolve_dependencies_and_install == 1) || (@resolve_dependencies_and_build == 1)
|
||||
# perform post-install process
|
||||
post_install
|
||||
end
|
||||
# perform post-install process
|
||||
post_install unless skip_postinstall
|
||||
end
|
||||
|
||||
install_end_time = Time.now.to_i
|
||||
@@ -1302,20 +1335,63 @@ def install
|
||||
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
|
||||
|
||||
dependencies = resolve_dependencies
|
||||
free_space = `df --output=avail #{CREW_PREFIX}`.lines(chomp: true).last.to_i * 1024
|
||||
install_size = dependencies.sum do |pkg|
|
||||
filelist = "#{CREW_LIB_PATH}/manifest/#{ARCH}/#{pkg[0]}/#{pkg}.filelist"
|
||||
if File.exist?(filelist)
|
||||
ConvenienceFunctions.read_filelist(filelist)[0]
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
if free_space < install_size
|
||||
abort <<~EOT.chomp.lightred
|
||||
#{@pkg.name.capitalize} needs #{MiscFunctions.human_size(install_size)} of disk space to install.
|
||||
|
||||
However, only #{MiscFunctions.human_size(free_space)} of free disk space is available currently.
|
||||
EOT
|
||||
end
|
||||
|
||||
puts <<~EOT
|
||||
|
||||
In order to build #{origin}, the following package(s) also need to be installed:
|
||||
|
||||
#{dependencies.join(' ')}
|
||||
|
||||
After installation, #{MiscFunctions.human_size(install_size)} of extra disk space will be taken. (#{MiscFunctions.human_size(free_space)} of free disk space available)
|
||||
|
||||
EOT
|
||||
|
||||
if @opt_force
|
||||
puts 'Proceeding with dependency installation...'.orange
|
||||
elsif !Package.agree_default_yes('Proceed')
|
||||
abort 'No changes made.'
|
||||
end
|
||||
|
||||
# install dependencies
|
||||
dependencies.each do |dep_to_install|
|
||||
search dep_to_install
|
||||
print_current_package
|
||||
install(skip_postinstall: true)
|
||||
end
|
||||
|
||||
puts 'Performing post-install for dependencies...'.lightblue
|
||||
|
||||
# run postinstall for dependencies
|
||||
dependencies.each do |dep_to_postinstall|
|
||||
search dep_to_postinstall
|
||||
post_install
|
||||
end
|
||||
search origin, silent: true
|
||||
|
||||
search @pkg.name, silent: true
|
||||
build_package CREW_LOCAL_BUILD_DIR
|
||||
rescue InstallError => e
|
||||
abort "#{@pkg.name} failed to build: #{e}".lightred
|
||||
@@ -1327,7 +1403,6 @@ def resolve_dependencies_and_build
|
||||
end
|
||||
end
|
||||
puts "#{@pkg.name.capitalize} is built!".lightgreen
|
||||
@resolve_dependencies_and_build = 0
|
||||
end
|
||||
|
||||
def build_package(crew_archive_dest)
|
||||
|
||||
@@ -4,7 +4,7 @@ require 'etc'
|
||||
require 'open3'
|
||||
|
||||
OLD_CREW_VERSION ||= defined?(CREW_VERSION) ? CREW_VERSION : '1.0'
|
||||
CREW_VERSION ||= '1.65.1' unless defined?(CREW_VERSION) && CREW_VERSION == OLD_CREW_VERSION
|
||||
CREW_VERSION ||= '1.65.2' unless defined?(CREW_VERSION) && CREW_VERSION == OLD_CREW_VERSION
|
||||
|
||||
# Kernel architecture.
|
||||
KERN_ARCH ||= Etc.uname[:machine]
|
||||
|
||||
@@ -40,6 +40,17 @@ class ConvenienceFunctions
|
||||
return JSON.load_file(File.join(CREW_CONFIG_PATH, 'device.json'), symbolize_names: true).transform_values! { |val| val.is_a?(String) ? val.to_sym : val }
|
||||
end
|
||||
|
||||
def self.read_filelist(path)
|
||||
filelist = File.readlines(path, chomp: true)
|
||||
|
||||
if filelist.first.start_with?('# Total size')
|
||||
total_size, *contents = filelist
|
||||
return [total_size[/Total size: (\d+)/, 1].to_i, contents]
|
||||
else
|
||||
return [0, *filelist]
|
||||
end
|
||||
end
|
||||
|
||||
def self.save_json(json_object)
|
||||
crewlog 'Saving device.json...'
|
||||
begin
|
||||
|
||||
@@ -122,10 +122,10 @@ class Package
|
||||
end
|
||||
|
||||
def self.get_deps_list(pkg_name = name, return_attr: false, hash: false, include_build_deps: 'auto', include_self: false,
|
||||
pkg_tags: [], ver_check: nil, highlight_build_deps: true, exclude_buildessential: false, top_level: true)
|
||||
pkg_tags: [], highlight_build_deps: true, exclude_buildessential: false, top_level: true)
|
||||
# get_deps_list: get dependencies list of pkg_name (current package by default)
|
||||
#
|
||||
# pkg_name: package to check dependencies, current package by default
|
||||
# pkg_name: package to check dependencies, current package by default
|
||||
# return_attr: return package attribute (tags and version lambda) also
|
||||
# hash: return result in nested hash, used by `print_deps_tree` (`bin/crew`)
|
||||
#
|
||||
@@ -160,7 +160,7 @@ class Package
|
||||
end
|
||||
|
||||
# Parse dependencies recursively.
|
||||
expanded_deps = deps.uniq.map do |dep, (dep_tags, ver_check)|
|
||||
expanded_deps = deps.uniq.map do |dep, dep_tags|
|
||||
# Check build dependencies only if building from source is needed/specified.
|
||||
# Do not recursively find :build based build dependencies.
|
||||
next unless (include_build_deps == true && @crew_current_package == pkg_obj.name) ||
|
||||
@@ -175,7 +175,7 @@ class Package
|
||||
# Check dependency by calling this function recursively.
|
||||
next \
|
||||
send(
|
||||
__method__, dep, pkg_tags: tags, ver_check:, include_self: true, top_level: false,
|
||||
__method__, dep, pkg_tags: tags, include_self: true, top_level: false,
|
||||
hash:, return_attr:, include_build_deps:, highlight_build_deps:, exclude_buildessential:
|
||||
)
|
||||
elsif hash && top_level
|
||||
@@ -203,7 +203,7 @@ class Package
|
||||
elsif include_self
|
||||
# Return pkg_name itself if this function is called as a recursive loop (see `expanded_deps`).
|
||||
if return_attr
|
||||
return [expanded_deps, { pkg_name => [pkg_tags, ver_check] }].flatten
|
||||
return [expanded_deps, { pkg_name => pkg_tags }].flatten
|
||||
else
|
||||
return [expanded_deps, pkg_name].flatten
|
||||
end
|
||||
@@ -288,10 +288,9 @@ class Package
|
||||
puts tree_view
|
||||
end
|
||||
|
||||
def self.depends_on(dependency, ver_range = nil)
|
||||
def self.depends_on(dependency)
|
||||
@dependencies ||= {}
|
||||
ver_check = nil
|
||||
dep_tags = []
|
||||
dep_tags = []
|
||||
|
||||
# Add element in "[ name, [ tag1, tag2, ... ] ]" format.
|
||||
if dependency.is_a?(Hash)
|
||||
@@ -305,30 +304,7 @@ class Package
|
||||
dep_name = dependency
|
||||
end
|
||||
|
||||
# Process dependency version range if specified.
|
||||
# example:
|
||||
# depends_on name, '>= 1.0'
|
||||
#
|
||||
# Operators can be: '>=', '==', '<=', '<', or '>'
|
||||
if ver_range
|
||||
operator, target_ver = ver_range.split(' ', 2)
|
||||
|
||||
# lambda for comparing the given range with installed version
|
||||
ver_check = lambda do |installed_ver|
|
||||
unless Gem::Version.new(installed_ver).send(operator.to_sym, Gem::Version.new(target_ver))
|
||||
# Print error if the range is not fulfilled.
|
||||
warn <<~EOT.lightred
|
||||
Package #{name} depends on '#{dep_name}' (#{operator} #{target_ver}), however version '#{installed_ver}' is currently installed :/
|
||||
|
||||
Run `crew update && crew upgrade` and try again?
|
||||
EOT
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
@dependencies.store(dep_name, [dep_tags, ver_check])
|
||||
@dependencies.store(dep_name, dep_tags)
|
||||
end
|
||||
|
||||
def self.binary?(architecture) = !@build_from_source && @binary_sha256&.key?(architecture)
|
||||
|
||||
Reference in New Issue
Block a user