Files
chromebrew/lib/package.rb
chromebrew-actions[bot] 715cc6d87d git → 2.52.0 (#13537)
* Add unbuilt git to updater-git-2.52.0

* Adjust linker

Signed-off-by: Satadru Pramanik <satadru@gmail.com>

* Add llvm_dev dep to bring in lld.

Signed-off-by: Satadru Pramanik <satadru@gmail.com>

* Let CC_LD and CXX_LD use CREW_LINKER.

Signed-off-by: Satadru Pramanik <satadru@gmail.com>

* Use no_mold for armv7l.

Signed-off-by: Satadru Pramanik <satadru@gmail.com>

* Update workflows to cleanup diskspace in GitHub actions and adjust gh token usage.

Signed-off-by: Satadru Pramanik <satadru@gmail.com>

* Build git for arm without rust.

Signed-off-by: Satadru Pramanik <satadru@gmail.com>

* updater-git-2.52.0: Package File Update Run on linux/386 container.

* updater-git-2.52.0: Package File Update Run on linux/amd64 container.

* lint

Signed-off-by: Satadru Pramanik <satadru@gmail.com>

* bump version

Signed-off-by: Satadru Pramanik <satadru@gmail.com>

---------

Signed-off-by: Satadru Pramanik <satadru@gmail.com>
Co-authored-by: satmandu <satmandu@users.noreply.github.com>
Co-authored-by: Satadru Pramanik <satadru@gmail.com>
2025-11-20 23:14:04 +00:00

429 lines
19 KiB
Ruby

require 'English'
require 'json'
require_relative 'const'
require_relative 'color'
require_relative 'convenience_functions'
require_relative 'package_helpers'
require_relative 'require_gem'
require_relative 'selector'
require_gem 'highline'
require_gem 'timeout'
def agree_with_default(yes_or_no_question_msg, character = nil, default:)
yes_or_no_question = yes_or_no_question_msg.lightpurple
answer_type = ->(yn) { yn.downcase[0] == 'y' || (yn.empty? && default.downcase[0] == 'y') }
HighLine.ask(yes_or_no_question, answer_type) do |q|
q.validate = /\A(?:y(?:es)?|no?|)\Z/i
q.responses[:not_valid] = 'Please enter "yes" or "no".'
q.responses[:ask_on_error] = :question
q.character = character
q.completion = %w[yes no]
yield q if block_given?
end
end
class Package
boolean_property :arch_flags_override,
:cache_build, # Cache build to gitlab.
:conflicts_ok,
:git_clone_deep,
:git_fetchtags,
:gem_compile_needed,
:gnome,
:ignore_updater,
:is_fake,
:is_musl,
:is_static,
:no_binaries_needed,
:no_compile_needed,
:no_compress,
:no_env_options,
:no_fhs,
:no_filefix,
:no_git_submodules,
:no_links,
:no_lto,
:no_mold,
:no_patchelf,
:no_shrink,
:no_source_build,
:no_strip,
:no_upstream_update,
:no_zstd,
:patchelf,
:prerelease,
:print_source_bashrc,
:run_tests
property :description,
:homepage,
:version,
:license,
:compatibility,
:binary_compression,
:binary_url,
:binary_sha256,
:source_url,
:source_sha256,
:git_branch,
:git_hashtag,
:max_glibc,
:min_glibc,
:conflicts_with
create_placeholder :preflight, # Function for checks to see if install should occur.
:patch, # Function to perform patch operations prior to build from source.
:pre_cache_build, # Function to perform pre-build operations prior to starting & restarting a cached build.
:prebuild, # Function to perform pre-build operations prior to build from source.
:prebuild_config_and_report, # Function to add some reporting for buildsystems.
:build, # Function to perform build from source.
:postbuild, # Function to perform post-build for both source build and binary distribution.
:check, # Function to perform check from source build. (executes only during `crew build`)
:preinstall, # Function to perform pre-install operations prior to install.
:install, # Function to perform install from source build.
:postinstall, # Function to perform post-install for both source build and binary distribution.
:preremove, # Function to perform prior to package removal.
:remove, # Function to remove package.
:postremove # Function to perform after package removal.
class << self
attr_accessor :build_from_source,
:built,
:cached_build,
:in_build,
:in_install,
:in_upgrade,
:missing_binaries,
:name
end
def self.agree_default_no(message = nil)
# This defaults to false.
Timeout.timeout(CREW_AGREE_TIMEOUT_SECONDS) do
return agree_with_default("#{message} (yes/NO)? ", true, default: 'n')
end
rescue Timeout::Error
return false
end
def self.agree_default_yes(message = nil)
# This defaults to true.
Timeout.timeout(CREW_AGREE_TIMEOUT_SECONDS) do
return agree_with_default("#{message} (YES/no)? ", true, default: 'y')
end
rescue Timeout::Error
return true
end
def self.agree_to_remove(config_object = nil)
if File.file? config_object
identifier = 'file'
elsif File.directory? config_object
identifier = 'directory'
else
crewlog "Cannot remove #{config_object}."
return
end
if agree_default_no("Would you like to remove the config #{identifier}: #{config_object} ")
FileUtils.rm_rf config_object
puts "#{config_object} removed.".lightgreen
else
puts "#{config_object} saved.".lightgreen
end
end
def self.load_package(pkg_file, reload = nil)
reload = !reload.nil?
# self.load_package: load a package under 'Package' class scope
#
pkg_name = File.basename(pkg_file, '.rb')
class_name = pkg_name.capitalize
# Read and eval package script under 'Package' class, using the newest file available.
pkg_file = Dir["{#{CREW_LOCAL_REPO_ROOT}/packages,#{CREW_PACKAGES_PATH}}/#{pkg_name}.rb"].max { |a, b| File.mtime(a) <=> File.mtime(b) }
# If this package has been removed, it won't be found in either directory, so set it back to what it was before to get a nicer error.
pkg_file = "#{CREW_PACKAGES_PATH}/#{pkg_name}.rb" if pkg_file.nil?
class_eval(File.read(pkg_file, encoding: Encoding::UTF_8), pkg_file) unless const_defined?("Package::#{class_name}") && !reload
pkg_obj = const_get(class_name)
pkg_obj.name = pkg_name
@crew_current_package = @crew_current_package.nil? ? pkg_obj.name : @crew_current_package
return pkg_obj
end
def self.dependencies
# We need instance variable in derived class, so do not define it here,
# base class. Instead of defining it, we initialize it in a function
# called from derived classes.
@dependencies ||= {}
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)
# get_deps_list: get dependencies list of pkg_name (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`)
#
# include_build_deps: if set to true, force all build dependencies to be returned.
# if set to false, all build dependencies will not be returned
# if set to "auto" (default), return build dependencies if pre-built binaries not available
#
# include_self: include #{pkg_name} itself in returned result, only used in recursive calls (see `expanded_deps` below)
# highlight_build_deps: include corresponding symbols in return value, you can convert it to actual ascii color codes later
# exclude_buildessential: do not insert `buildessential` dependency automatically
#
# top_level: if set to true, return satisfied dependencies
# (dependencies that might be a sub-dependency of a dependency that checked before),
# always set to false if this function is called in recursive loop (see `expanded_deps` below)
#
@checked_list ||= {} # create @checked_list placeholder if not exist
# Add current package to @checked_list for preventing extra checks.
@checked_list.merge!({ pkg_name => pkg_tags })
pkg_obj = load_package(File.join(CREW_PACKAGES_PATH, "#{pkg_name}.rb"))
is_source = pkg_obj.source?(ARCH.to_sym) or pkg_obj.build_from_source
deps = pkg_obj.dependencies
# Append buildessential to deps if building from source is needed/specified.
if ((include_build_deps == true) || ((include_build_deps == 'auto') && is_source)) &&
!pkg_obj.no_compile_needed? &&
!exclude_buildessential &&
!@checked_list.keys.include?('buildessential')
deps = { 'buildessential' => [[:build]] }.merge(deps)
end
# Parse dependencies recursively.
expanded_deps = deps.uniq.map do |dep, (dep_tags, ver_check)|
# 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) ||
((include_build_deps == 'auto') && is_source && @crew_current_package == pkg_obj.name) ||
!dep_tags.include?(:build)
# Overwrite tags if parent dependency is a build dependency.
# (for build dependencies highlighting)
tags = pkg_tags.include?(:build) ? pkg_tags : dep_tags
if @checked_list.keys.none?(dep)
# Check dependency by calling this function recursively.
next \
send(
__method__, dep, pkg_tags: tags, ver_check:, include_self: true, top_level: false,
hash:, return_attr:, include_build_deps:, highlight_build_deps:, exclude_buildessential:
)
elsif hash && top_level
# Will be dropped here if current dependency is already checked and #{top_level} is set to true.
#
# The '+' symbol tell `print_deps_tree` (`bin/crew`) to color this package as a "satisfied dependency".
# The '*' symbol tell `print_deps_tree` (`bin/crew`) to color this package as a "build dependency".
if highlight_build_deps && tags.include?(:build)
next { "+*#{dep}*+" => [] }
elsif highlight_build_deps
next { "+#{dep}+" => [] }
else
next { dep => [] }
end
end
end.compact
if hash
# The '*' symbol tell `print_deps_tree` (`bin/crew`) to color this package as a "build dependency".
if highlight_build_deps && pkg_tags.include?(:build)
return { "*#{pkg_name}*" => expanded_deps }
else
return { pkg_name => expanded_deps }
end
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
else
return [expanded_deps, pkg_name].flatten
end
else
# If this function is called outside of this function, return parsed dependencies only.
return expanded_deps.flatten
end
end
def self.print_deps_tree(args)
warn 'Walking through dependencies recursively, this may take a while...', ''
# dep_hash: Hash object returned by @pkg.get_deps_list
dep_hash = get_deps_list(hash: true, include_build_deps: args['--include-build-deps'] || 'auto', exclude_buildessential: args['--exclude-buildessential'])
# convert returned hash to json and format it
json_view = JSON.pretty_generate(dep_hash)
# convert formatted json string to tree structure
tree_view = json_view.gsub(/\{\s*/m, '└─────').gsub(/[\[\]{},":]/, '').gsub(/^\s*$\n/, '').gsub(/\s*$/, '')
# add pipe char to connect endpoints and starting points, improve readability
# find the horizontal location of all arrow symbols
index_with_pipe_char = tree_view.lines.map { |line| line.index('└') }.compact.uniq
# determine whatever a pipe char should be added according to the horizontal location of arrow symbols
tree_view = tree_view.lines.each_with_index.map do |line, line_i|
index_with_pipe_char.each do |char_i|
# check if there have any non-space char (pkg_names) between starting point ([line_i][char_i]) and endpoint vertically ([next_arrow_line_offset][char_i])
# (used to determine if the starting point and endpoint are in same branch, use pipe char to connect them if true)
next_arrow_line_offset = tree_view.lines[line_i..].index { |l| l[char_i] == '└' }
have_line_with_non_empty_char = tree_view.lines[(line_i + 1)..(line_i + next_arrow_line_offset.to_i - 1)].any? { |l| l[char_i].nil? || l[char_i] =~ /\S/ }
line[char_i] = '│' if next_arrow_line_offset && (line[char_i] == ' ') && !have_line_with_non_empty_char
end
next line
end.join
# replace arrow symbols with a tee symbol on branch intersection
tree_view = tree_view.lines.each_with_index.map do |line, line_i|
# orig_arrow_index_connecter: the horizontal location of the arrow symbol used to connect parent branch
#
# example:
# └───┬─chrome
# └─────buildessential
# ^
orig_arrow_index_connecter = line.index('└')
# orig_arrow_index_newbranch: the horizontal location of the "box drawing char" symbol MIGHT be
# required to convert to tee char in order to connect child branch,
# located at 3 chars later of orig_arrow_index_connecter
#
# example:
# v
# └─────chrome
# └─────buildessential
#
# which might need to be convert to:
# └───┬─chrome
# └─────buildessential
orig_arrow_index_newbranch = orig_arrow_index_connecter + 4
# if the char under the processing arrow symbol (orig_arrow_index_connecter) is also arrow or pipe, change the processing char to tee symbol
line[orig_arrow_index_connecter] = '├' if orig_arrow_index_connecter && tree_view.lines[line_i + 1].to_s[orig_arrow_index_connecter] =~ /[└│]/
# if the char under the processing arrow symbol (orig_arrow_index_newbranch) is also arrow or pipe, change the processing char to tee symbol
line[orig_arrow_index_newbranch] = '┬' if orig_arrow_index_newbranch && tree_view.lines[line_i + 1].to_s[orig_arrow_index_newbranch] =~ /[└├]/
next line # return modified line
end.join
if String.use_color
puts <<~EOT, ''
\e[45m \e[0m: satisfied dependency
\e[46m \e[0m: build dependency
\e[47m \e[0m: runtime dependency
EOT
# (the first string in each #{} is used for commenting only, will not be included in output)
# replace special symbols returned by @pkg.get_deps_list to actual color code
tree_view.gsub!(/\*(.+)\*/, '\1'.lightcyan)
tree_view.gsub!(/\+(.+)\+/, "\e[45m\\1\e[0m")
end
puts tree_view
end
def self.depends_on(dependency, ver_range = nil)
@dependencies ||= {}
ver_check = nil
dep_tags = []
# Add element in "[ name, [ tag1, tag2, ... ] ]" format.
if dependency.is_a?(Hash)
# Parse "depends_on name => <tags: Symbol|Array>".
dep_name, tags = dependency.first
# Convert `tags` to array in case `tags` is a symbol.
dep_tags += [tags].flatten
else
# Parse "depends_on name".
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])
end
def self.binary?(architecture) = !@build_from_source && @binary_sha256&.key?(architecture)
def self.source?(architecture) = missing_binaries ? true : !(binary?(architecture) || is_fake?)
def self.system(*args, **opt_args)
crew_env_options_hash = if no_env_options?
{ 'CREW_DISABLE_ENV_OPTIONS' => '1' }
elsif no_lto?
CREW_ENV_FNO_LTO_OPTIONS_HASH.transform_values { |v| v.gsub('-flto=auto', '') }
else
CREW_ENV_OPTIONS_HASH
end
# Replace CREW_ARCH_FLAGS if @arch_flags_override is true.
crew_env_options_hash.transform_values! { |v| v.gsub(CREW_ARCH_FLAGS, CREW_ARCH_FLAGS_OVERRIDE) } if arch_flags_override
# Add exception option to opt_args.
opt_args.merge!(exception: true) unless opt_args.key?(:exception)
# Extract env hash.
if args[0].is_a?(Hash)
env = @no_mold ? crew_env_options_hash.merge(args[0]).transform_values { |v| v.gsub('-fuse-ld=mold', '') } : crew_env_options_hash.merge(args[0])
args.delete_at(0) # Remove env hash from args array.
else
env = @no_mold ? crew_env_options_hash.transform_values { |v| v.gsub('-fuse-ld=mold', '') } : crew_env_options_hash
end
env['CREW_PRELOAD_ENABLE_COMPILE_HACKS'] = opt_args.delete(:no_preload_hacks) ? '0' : '1'
# CC_LD and CXX_LD are needed by meson to override mold being
# detected and used by default.
env['CC_LD'] = if @no_mold
CREW_LINKER == 'mold' ? 'bfd' : CREW_LINKER
else
CREW_LINKER
end
env['CXX_LD'] = if @no_mold
CREW_LINKER == 'mold' ? 'bfd' : CREW_LINKER
else
CREW_LINKER
end
env['CREW_PRELOAD_NO_MOLD'] = @no_mold ? '1' : '0'
env['LD_PRELOAD'] = File.join(CREW_LIB_PREFIX, 'crew-preload.so') if File.exist?("#{CREW_LIB_PREFIX}/crew-preload.so")
# After removing the env hash, all remaining args must be command args.
begin
Kernel.system(env, *args, **opt_args)
rescue RuntimeError => e
# Print failed line number and error message.
puts "#{e.backtrace[1]}: #{e.message}".orange
raise InstallError, "`#{env.map { |k, v| "#{k}=\"#{v}\"" }.join(' ')} #{args.join(' ')}` exited with #{$CHILD_STATUS.exitstatus}".lightred
end
end
end