Skip to content

Commit

Permalink
Optimize cleaning dirs during installation
Browse files Browse the repository at this point in the history
  • Loading branch information
dnkoutso committed Nov 19, 2023
1 parent 202ce5d commit a3fb18f
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 20 deletions.
3 changes: 1 addition & 2 deletions lib/cocoapods/downloader.rb
Expand Up @@ -51,8 +51,7 @@ def self.download(
if target && result.location && target != result.location
UI.message "Copying #{request.name} from `#{result.location}` to #{UI.path target}", '> ' do
Cache.read_lock(result.location) do
FileUtils.rm_rf target
FileUtils.cp_r(result.location, target)
FileUtils.cp_r(result.location, target, :remove_destination => true)
end
end
end
Expand Down
86 changes: 73 additions & 13 deletions lib/cocoapods/downloader/cache.rb
Expand Up @@ -235,13 +235,38 @@ def cached_spec(request)
# was not found in the download cache.
#
def uncached_pod(request)
in_tmpdir do |target|
result, podspecs = download(request, target)
in_tmpdir do |tmp_dir|
result, podspecs = download(request, tmp_dir)
result.location = nil

podspecs.each do |name, spec|
# Split by pods that require a prepare command or not to speed up installation.
no_prep_cmd_specs, prep_cmd_specs = podspecs.partition { |_, spec| spec.prepare_command.nil? }.map(&:to_h)

# Pods with a prepare command currently copy the entire repo, run the prepare command against the whole
# repo and then clean it up. We configure those first to ensure the repo is pristine.
prep_cmd_specs.each do |name, spec|
destination = path_for_pod(request, :name => name, :params => result.checkout_options)
copy_and_clean(target, destination, spec)
copy_source_and_clean(tmp_dir, destination, spec)
write_spec(spec, path_for_spec(request, :name => name, :params => result.checkout_options))
if request.name == name
result.location = destination
end
end

specs_by_platform = group_subspecs_by_platform(no_prep_cmd_specs.values)

# Remaining pods without a prepare command can be optimized by cleaning the repo first
# and then copying only the files needed for all pods being downloaded.
pod_dir_cleaner = Sandbox::PodDirCleaner.new(tmp_dir, specs_by_platform)
Cache.write_lock(tmp_dir) do
pod_dir_cleaner.clean!
end

no_prep_cmd_specs.each do |name, spec|
destination = path_for_pod(request, :name => name, :params => result.checkout_options)
file_accessors = pod_dir_cleaner.file_accessors.select { |fa| fa.spec.name == spec.name }
files = Pod::Sandbox::FileAccessor.all_files(file_accessors).map(&:to_s)
copy_files(files, tmp_dir, destination, spec)
write_spec(spec, path_for_spec(request, :name => name, :params => result.checkout_options))
if request.name == name
result.location = destination
Expand Down Expand Up @@ -279,23 +304,58 @@ def in_tmpdir(&blk)
#
# @return [Void]
#
def copy_and_clean(source, destination, spec)
specs_by_platform = group_subspecs_by_platform(spec)
def copy_source_and_clean(source, destination, spec)
specs_by_platform = group_subspecs_by_platform([spec])
destination.parent.mkpath
Cache.write_lock(destination) do
FileUtils.rm_rf(destination)
FileUtils.cp_r(source, destination)
FileUtils.cp_r(source, destination, :remove_destination => true)
Pod::Installer::PodSourcePreparer.new(spec, destination).prepare!
Sandbox::PodDirCleaner.new(destination, specs_by_platform).clean!
end
end

def group_subspecs_by_platform(spec)
# Copies the `files` from the `source` directory to `destination`, _without_ cleaning the directory
# of any files unused by `spec`. This is a faster version used when installing pods without
# a prepare command.
#
# @param [Array<Pathname>] files
#
# @param [Pathname] source
#
# @param [Pathname] destination
#
# @param [Specification] spec
#
# @return [Void]
#
def copy_files(files, source, destination, spec)
destination.mkpath
files = files.reject { |f| !File.exist?(f) }
Cache.write_lock(destination) do
FileUtils.rm_rf(destination)
files_by_dir = files.group_by do |file|
relative_path = Pathname(file).relative_path_from(Pathname(source)).to_s
destination_path = File.join(destination, relative_path)
File.dirname(destination_path)
end

files_by_dir.each do |dir, files_to_copy|
FileUtils.mkdir_p(dir)
FileUtils.cp_r(files_to_copy, dir)
end

Pod::Installer::PodSourcePreparer.new(spec, destination).prepare!
end
end

def group_subspecs_by_platform(specs)
specs_by_platform = {}
[spec, *spec.recursive_subspecs].each do |ss|
ss.available_platforms.each do |platform|
specs_by_platform[platform] ||= []
specs_by_platform[platform] << ss
specs.each do |spec|
[spec, *spec.recursive_subspecs].each do |ss|
ss.available_platforms.each do |platform|
specs_by_platform[platform] ||= []
specs_by_platform[platform] << ss
end
end
end
specs_by_platform
Expand Down
6 changes: 3 additions & 3 deletions lib/cocoapods/sandbox/pod_dir_cleaner.rb
Expand Up @@ -15,11 +15,9 @@ def initialize(root, specs_by_platform)
# @return [void]
#
def clean!
clean_paths.each { |path| FileUtils.rm_rf(path) } if root.exist?
FileUtils.rm_rf(clean_paths) if root.exist?
end

private

# @return [Array<Sandbox::FileAccessor>] the file accessors for all the
# specifications on their respective platform.
#
Expand All @@ -29,6 +27,8 @@ def file_accessors
end
end

private

# @return [Sandbox::PathList] The path list for this Pod.
#
def path_list
Expand Down
4 changes: 2 additions & 2 deletions spec/unit/downloader/cache_spec.rb
Expand Up @@ -49,7 +49,7 @@ module Pod
end
end

@cache.send(:group_subspecs_by_platform, @spec).should == {
@cache.send(:group_subspecs_by_platform, [@spec]).should == {
Platform.new(:ios, '8.0') => [@spec.subspecs.first],
Platform.new(:ios, '6.0') => [@spec],
Platform.new(:osx, '10.7') => [@spec],
Expand Down Expand Up @@ -81,7 +81,7 @@ module Pod
end

it 'downloads the source' do
@cache.expects(:copy_and_clean).twice
@cache.expects(:copy_files).twice
response = @cache.download_pod(@unreleased_request)
response.should == Downloader::Response.new(@cache.root + @unreleased_request.slug, @spec, @spec.source)
end
Expand Down

0 comments on commit a3fb18f

Please sign in to comment.