crew: Rubyize file conflict algorithm (#11734)

* crew: Rubyize conflict algorithm

Signed-off-by: SupeChicken666 <supechicken666@gmail.com>

* Update code comments

Signed-off-by: SupeChicken666 <supechicken666@gmail.com>

* const: Bump version

Signed-off-by: SupeChicken666 <supechicken666@gmail.com>

* Also check for essential files on removal

Signed-off-by: SupeChicken666 <supechicken666@gmail.com>

* Fix syntax

Signed-off-by: SupeChicken666 <supechicken666@gmail.com>

* Fix unit test

Signed-off-by: SupeChicken666 <supechicken666@gmail.com>

---------

Signed-off-by: SupeChicken666 <supechicken666@gmail.com>
This commit is contained in:
SupeChicken666
2025-04-19 02:24:04 +08:00
committed by GitHub
parent 42dd59052e
commit 02cd5c3fb6
5 changed files with 58 additions and 51 deletions

View File

@@ -766,29 +766,6 @@ def compress_doc(dir)
end
end
def determine_conflicts(dir, pkg)
conflicts = []
if File.file?("#{dir}/filelist")
if File.file?(File.join(CREW_META_PATH, "#{pkg}.filelist"))
puts 'Checking for conflicts with files from installed packages...'.orange
conflictscmd = `grep --exclude=#{File.join(CREW_META_PATH, "#{pkg}.filelist")} --exclude=#{CREW_META_PATH}/\\\*_build.filelist -Fxf #{dir}/filelist #{CREW_META_PATH}/*.filelist`
conflicts = conflictscmd.gsub(/(\.filelist|#{CREW_META_PATH})/, '').split("\n")
conflicts.reject!(&:empty?)
end
elsif File.file?(File.join(CREW_META_PATH, "#{pkg}.filelist"))
puts "Checking for conflicts of #{pkg} with files from installed packages...".orange
conflictscmd = `grep --exclude=#{File.join(CREW_META_PATH, "#{pkg}.filelist")} --exclude=#{CREW_META_PATH}/\\\*_build.filelist -Fxf #{File.join(CREW_META_PATH, "#{pkg}.filelist")} #{CREW_META_PATH}/*.filelist`
conflicts = conflictscmd.gsub(/(\.filelist|#{CREW_META_PATH})/, '').split("\n")
conflicts.reject!(&:empty?)
end
if conflicts.any?
puts 'There is a conflict with the same file in another package:'.orange
puts conflicts.to_s.orange
end
conflicts.map! { |x| x.to_s.partition(':').last }
return conflicts
end
def prepare_package(destdir)
# Create the destdir if it does not exist to avoid having to have
# this single line in no_compile_needed packages.
@@ -858,15 +835,24 @@ def prepare_package(destdir)
end
# check for conflicts with other installed files
conflicts = determine_conflicts(Dir.pwd, @pkg.name)
conflicts = ConvenienceFunctions.determine_conflicts(@pkg.name, File.join(Dir.pwd, 'filelist'), '_build', verbose: CREW_VERBOSE)
if conflicts.any?
if CREW_CONFLICTS_ONLY_ADVISORY || @pkg.conflicts_ok?
puts 'Warning: There is a conflict with the same file in another package.'.orange
puts "Warning: There is a conflict with the same file in another package:\n".orange
else
puts 'Error: There is a conflict with the same file in another package.'.lightred
puts "Error: There is a conflict with the same file in another package:\n".lightred
errors = true
end
puts conflicts
conflicts.each_pair do |pkgName, conflictFiles|
if errors
conflictFiles.each {|file| puts "#{pkgName}: #{file}".lightred }
else
conflictFiles.each {|file| puts "#{pkgName}: #{file}".orange }
end
puts
end
end
# abort if errors encountered

View File

@@ -50,38 +50,38 @@ class Command
# Remove the files and directories installed by the package.
unless pkg.is_fake?
Dir.chdir CREW_CONFIG_PATH do
# Remove all files installed by the package in CREW_PREFIX and
# HOME.
# Remove all files installed by the package in CREW_PREFIX and HOME.
#
# Exceptions:
# 1. The file exists in another installed package.
# 2. The file is in one of the filelists for packages CREW_ESSENTIAL_PACKAGES.
flist = File.join(CREW_META_PATH, "#{pkg.name}.filelist")
if File.file?(flist)
filelist_path = File.join(CREW_META_PATH, "#{pkg.name}.filelist")
if File.file?(filelist_path)
filelist = File.readlines(filelist_path, chomp: true)
overlap_files = ConvenienceFunctions.determine_conflicts(pkg.name, filelist_path, verbose: verbose)
essential_files = CREW_ESSENTIAL_PACKAGES.flat_map {|f| File.readlines(File.join(CREW_META_PATH, "#{f}.filelist"), chomp: true)}
overlap_essential_files = filelist & essential_files
files_to_remove = filelist - overlap_files.values.flatten - overlap_essential_files
# When searching for files to delete we exclude the files from CREW_ESSENTIAL_PACKAGES.
essential_packages_exclude_froms = ''
unless force
essential_packages_exclude_froms = CREW_ESSENTIAL_PACKAGES.map { |i| File.file?("#{File.join(CREW_META_PATH, i.to_s)}.filelist") ? "--exclude-from=#{File.join(CREW_META_PATH, i.to_s)}.filelist" : '' }.join(' ')
if overlap_essential_files.any?
warn "The following file(s) will not be deleted as they are required for Chromebrew to work properly:\n".orange
warn overlap_essential_files.join("\n").orange
warn "\n\n"
end
package_files = `grep -h #{essential_packages_exclude_froms} \"^#{CREW_PREFIX}\\|^#{HOME}\" #{flist}`.split("\n").uniq.sort
all_other_files = `grep -h --exclude #{pkg.name}.filelist \"^#{CREW_PREFIX}\\|^#{HOME}\" #{CREW_META_PATH}/*.filelist 2>/dev/null`.split("\n").uniq.sort
# We want the difference of these arrays.
unique_to_package_files = package_files - all_other_files
# We want the intersection of these arrays.
package_files_that_overlap = all_other_files & package_files
unless package_files_that_overlap.empty?
puts "The following file(s) in other packages will not be deleted during the removal of #{pkg.name}:".orange
puts package_files_that_overlap.join("\n").orange
if overlap_files.any?
warn "The following file(s) in other packages will not be deleted during the removal of #{pkg.name}:\n".orange
overlap_files.each_pair do |pkgName, files|
files.each {|file| puts "#{pkgName}: #{file}".orange }
end
puts
end
unique_to_package_files.each do |file|
files_to_remove.each do |file|
puts "Removing file #{file}".yellow if verbose
FileUtils.remove_file file, exception: false
end
FileUtils.remove_file flist
FileUtils.remove_file filelist_path
end
# Remove all directories installed by the package.

View File

@@ -3,7 +3,7 @@
require 'etc'
OLD_CREW_VERSION ||= defined?(CREW_VERSION) ? CREW_VERSION : '1.0'
CREW_VERSION ||= '1.58.2' unless defined?(CREW_VERSION) && CREW_VERSION == OLD_CREW_VERSION
CREW_VERSION ||= '1.58.3' unless defined?(CREW_VERSION) && CREW_VERSION == OLD_CREW_VERSION
# Kernel architecture.
KERN_ARCH ||= Etc.uname[:machine]

View File

@@ -7,6 +7,26 @@ require_relative 'crewlog'
require_relative 'downloader'
class ConvenienceFunctions
def self.determine_conflicts(pkgName, filelist = File.join(CREW_META_PATH, "#{pkgName}.filelist"), excludeSuffix = nil, verbose: false)
conflicts = {}
target_filelist = File.readlines(filelist, chomp: true)
puts 'Checking for conflicts with files from installed packages...'.orange if verbose
Dir[File.join(CREW_META_PATH, "*.filelist")].each do |filelist|
filelist_name = File.basename(filelist, ".filelist")
# skip filelist belongs to the same package/explicitly excluded
next if pkgName == filelist_name || (excludeSuffix && filelist_name.end_with?(excludeSuffix))
# find out identical file paths with intersection
conflict = (target_filelist & File.readlines(filelist, chomp: true)).reject(&:empty?)
conflicts[filelist_name] = conflict if conflict.any?
end
return conflicts
end
def self.load_symbolized_json
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

View File

@@ -55,6 +55,7 @@ class RemoveCommandTest < Minitest::Test
puts 'Testing the verbose removal of normal package xxd_standalone. This should succeed.'
expected_output = <<~EOT
Checking for conflicts with files from installed packages...
Removing file #{CREW_PREFIX}/bin/xxd
Removing file #{CREW_PREFIX}/share/man/man1/xxd.1.zst
Removing package xxd_standalone from device.json