cocoapods-podgenerate 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/cocoapods-podgenerate/benchmark/profiler.rb +48 -5
- data/lib/cocoapods-podgenerate/patches/cache_analyzer_patch.rb +77 -0
- data/lib/cocoapods-podgenerate/patches/installer_patch.rb +123 -7
- data/lib/cocoapods-podgenerate/patches/multi_project_generator_patch.rb +69 -0
- data/lib/cocoapods-podgenerate/patches/project_writer_patch.rb +146 -13
- data/lib/cocoapods-podgenerate/patches/user_integrator_patch.rb +65 -10
- data/lib/cocoapods-podgenerate.rb +4 -2
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 27602be6d9b287c0d9b46a5ce44325a4ab1ddfcb47e2b6f0c5d40c6ec59cb904
|
|
4
|
+
data.tar.gz: 39039a26b13a8504df307e15afe77bc439541baaa5a5d27cce7b39fa7f61fb87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 74077cdbefbb30767526f247b41f17d98ae02e43d644b03bdc8993fd40c1c7b799e4c4b48bb25a18e7b5a40146d0af9dae093161839eaf019ab9970631295f60
|
|
7
|
+
data.tar.gz: 0306e59218825aae0a218cd756ef04339c53f79c3a2ab69ed255d03b8c84c13900c2cd51f9314a2cb01c1cf8eb7c4eec14e06eada6230009ab0df4b2a68d9247
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# [cocoapods-podgenerate]
|
|
4
4
|
# Performance profiler. Hooks into Pod::Installer to time each phase.
|
|
5
|
-
# Output: per-phase wall-clock timing breakdown.
|
|
5
|
+
# Output: per-phase wall-clock timing breakdown with sub-step detail.
|
|
6
6
|
|
|
7
7
|
module Pod
|
|
8
8
|
module PodGenerate
|
|
@@ -23,16 +23,13 @@ module Pod
|
|
|
23
23
|
def install
|
|
24
24
|
return unless enabled?
|
|
25
25
|
Pod::Installer.prepend(ProfilerHooks)
|
|
26
|
+
Pod::Installer.prepend(ProfilerSubSteps)
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def record_phase(name, duration)
|
|
29
30
|
@phase_timings << [name, duration]
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
def swap_or_default(phase_name)
|
|
33
|
-
# Called from hooks: returns a timing helper or nil
|
|
34
|
-
end
|
|
35
|
-
|
|
36
33
|
def report
|
|
37
34
|
return if @phase_timings.empty?
|
|
38
35
|
total = @phase_timings.map(&:last).sum
|
|
@@ -43,9 +40,11 @@ module Pod
|
|
|
43
40
|
end
|
|
44
41
|
Pod::UI.puts " #{'─' * 50}"
|
|
45
42
|
Pod::UI.puts " #{format('%-35s', 'TOTAL')} #{format('%.2f', total)}s"
|
|
43
|
+
@phase_timings.clear
|
|
46
44
|
end
|
|
47
45
|
end
|
|
48
46
|
|
|
47
|
+
# Top-level step hooks (v0.1.0)
|
|
49
48
|
module ProfilerHooks
|
|
50
49
|
def install!
|
|
51
50
|
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -88,6 +87,50 @@ module Pod
|
|
|
88
87
|
Profiler.record_phase(' Integrate user project', elapsed)
|
|
89
88
|
end
|
|
90
89
|
end
|
|
90
|
+
|
|
91
|
+
# Sub-step timing hooks (v0.1.2)
|
|
92
|
+
module ProfilerSubSteps
|
|
93
|
+
def stage_sandbox(sandbox, pod_targets)
|
|
94
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
95
|
+
super
|
|
96
|
+
ensure
|
|
97
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
|
|
98
|
+
Profiler.record_phase(' Stage sandbox', elapsed) if elapsed > 0.01
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def analyze_project_cache
|
|
102
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
103
|
+
result = super
|
|
104
|
+
ensure
|
|
105
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
|
|
106
|
+
Profiler.record_phase(' Analyze project cache', elapsed) if elapsed > 0.01
|
|
107
|
+
result
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def create_and_save_projects(*args)
|
|
111
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
112
|
+
super
|
|
113
|
+
ensure
|
|
114
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
|
|
115
|
+
Profiler.record_phase(' Create and save projects', elapsed) if elapsed > 0.01
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def update_project_cache(cache_analysis_result, target_installation_results)
|
|
119
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
120
|
+
super
|
|
121
|
+
ensure
|
|
122
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
|
|
123
|
+
Profiler.record_phase(' Update project cache', elapsed) if elapsed > 0.01
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def write_lockfiles
|
|
127
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
128
|
+
super
|
|
129
|
+
ensure
|
|
130
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
|
|
131
|
+
Profiler.record_phase(' Write lockfiles', elapsed)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
91
134
|
end
|
|
92
135
|
end
|
|
93
136
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# [cocoapods-podgenerate]
|
|
4
|
+
# Monkey-patches ProjectCacheAnalyzer to parallelize cache key computation.
|
|
5
|
+
#
|
|
6
|
+
# v0.1.2 Optimization:
|
|
7
|
+
# 1. Parallel MD5 cache key computation across all pod targets
|
|
8
|
+
#
|
|
9
|
+
# TargetCacheKey.from_pod_target computes MD5 checksums of build settings
|
|
10
|
+
# and collects resource dependencies per target. Each computation is fully
|
|
11
|
+
# independent and can run in parallel.
|
|
12
|
+
#
|
|
13
|
+
# Reference: CocoaPods — lib/cocoapods/installer/project_cache/project_cache_analyzer.rb
|
|
14
|
+
|
|
15
|
+
require 'concurrent'
|
|
16
|
+
require 'etc'
|
|
17
|
+
|
|
18
|
+
module Pod
|
|
19
|
+
module PodGenerate
|
|
20
|
+
module Patches
|
|
21
|
+
module CacheAnalyzerPatch
|
|
22
|
+
def self.apply
|
|
23
|
+
Pod::UI.message '[cocoapods-podgenerate] Applying CacheAnalyzerPatch (parallel cache key computation)'
|
|
24
|
+
Pod::Installer::ProjectCache::ProjectCacheAnalyzer.prepend(ParallelCacheKeyComputation)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module ParallelCacheKeyComputation
|
|
28
|
+
# Override create_cache_key_mappings to parallelize MD5 computation
|
|
29
|
+
# The original iterates target_by_label sequentially, computing cache keys.
|
|
30
|
+
# Since each target is independent, we use a thread pool.
|
|
31
|
+
def create_cache_key_mappings(target_by_label)
|
|
32
|
+
UI.message '- Creating cache key mappings (parallel)' do
|
|
33
|
+
pool_size = compute_pool_size
|
|
34
|
+
pool = Concurrent::FixedThreadPool.new(pool_size)
|
|
35
|
+
mutex = Mutex.new
|
|
36
|
+
results = {}
|
|
37
|
+
|
|
38
|
+
target_by_label.each do |label, target|
|
|
39
|
+
pool.post do
|
|
40
|
+
key = case target
|
|
41
|
+
when PodTarget
|
|
42
|
+
local = sandbox.local?(target.pod_name)
|
|
43
|
+
checkout_options = sandbox.checkout_sources[target.pod_name]
|
|
44
|
+
TargetCacheKey.from_pod_target(sandbox, target_by_label, target,
|
|
45
|
+
:is_local_pod => local,
|
|
46
|
+
:checkout_options => checkout_options)
|
|
47
|
+
when AggregateTarget
|
|
48
|
+
TargetCacheKey.from_aggregate_target(sandbox, target_by_label, target)
|
|
49
|
+
else
|
|
50
|
+
raise "[BUG] Unknown target type #{target}"
|
|
51
|
+
end
|
|
52
|
+
mutex.synchronize { results[label] = key }
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
mutex.synchronize do
|
|
55
|
+
Pod::UI.warn "[cocoapods-podgenerate] Cache key computation error: #{e.message}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
pool.shutdown
|
|
61
|
+
pool.wait_for_termination
|
|
62
|
+
results
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def compute_pool_size
|
|
69
|
+
[[Etc.nprocessors - 1, 2].max, 16].min
|
|
70
|
+
rescue NameError
|
|
71
|
+
4
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -1,29 +1,145 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# [cocoapods-podgenerate]
|
|
4
|
-
# Monkey-patches PodsProjectGenerator
|
|
4
|
+
# Monkey-patches Pod::Installer and PodsProjectGenerator for step 3/4 optimizations.
|
|
5
5
|
#
|
|
6
|
-
# Optimizations:
|
|
7
|
-
# 1.
|
|
8
|
-
# 2.
|
|
6
|
+
# v0.1.1 Optimizations:
|
|
7
|
+
# 1. Force-enable incremental_installation + generate_multiple_pod_projects
|
|
8
|
+
# 2. Skip project generation entirely when nothing changed
|
|
9
|
+
# 3. Parallelize PodTargetIntegrator integration
|
|
9
10
|
#
|
|
10
|
-
#
|
|
11
|
+
# v0.1.2 Optimization:
|
|
12
|
+
# 4. Parallelize configure_schemes across projects
|
|
13
|
+
#
|
|
14
|
+
# Reference: CocoaPods — lib/cocoapods/installer.rb
|
|
15
|
+
# lib/cocoapods/installer/xcode/pods_project_generator.rb
|
|
16
|
+
|
|
17
|
+
require 'concurrent'
|
|
18
|
+
require 'etc'
|
|
11
19
|
|
|
12
20
|
module Pod
|
|
13
21
|
module PodGenerate
|
|
14
22
|
module Patches
|
|
15
23
|
module InstallerPatch
|
|
16
24
|
def self.apply
|
|
17
|
-
Pod::UI.message '[cocoapods-podgenerate] Applying InstallerPatch
|
|
25
|
+
Pod::UI.message '[cocoapods-podgenerate] Applying InstallerPatch v3'
|
|
26
|
+
Pod::Installer.prepend(ForceIncrementalInstall)
|
|
18
27
|
Pod::Installer::Xcode::PodsProjectGenerator.prepend(ParallelInstall)
|
|
19
28
|
end
|
|
20
29
|
|
|
30
|
+
# ── Optimization 1: Force-enable incremental_installation ──
|
|
31
|
+
module ForceIncrementalInstall
|
|
32
|
+
def install!
|
|
33
|
+
installation_options.incremental_installation = true
|
|
34
|
+
installation_options.generate_multiple_pod_projects = true
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# ── Optimization 2: Skip project generation when nothing changed ──
|
|
39
|
+
def generate_pods_project
|
|
40
|
+
stage_sandbox(sandbox, pod_targets)
|
|
41
|
+
|
|
42
|
+
cache_analysis_result = analyze_project_cache
|
|
43
|
+
ptg = cache_analysis_result.pod_targets_to_generate
|
|
44
|
+
atg = cache_analysis_result.aggregate_targets_to_generate
|
|
45
|
+
|
|
46
|
+
if ptg.empty? && (atg.nil? || atg.empty?)
|
|
47
|
+
Pod::UI.puts "[cocoapods-podgenerate] No changes — skipping project generation"
|
|
48
|
+
@generated_aggregate_targets = aggregate_targets
|
|
49
|
+
@generated_pod_targets = []
|
|
50
|
+
Pod::Installer::SandboxDirCleaner.new(sandbox, pod_targets, aggregate_targets).clean!
|
|
51
|
+
update_project_cache(cache_analysis_result,
|
|
52
|
+
Pod::Installer::Xcode::PodsProjectGenerator::InstallationResults.new({}, {}))
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Normal path
|
|
57
|
+
ptg.each do |pod_target|
|
|
58
|
+
pod_target.build_headers.implode_path!(pod_target.headers_sandbox)
|
|
59
|
+
sandbox.public_headers.implode_path!(pod_target.headers_sandbox)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
create_and_save_projects(ptg, atg,
|
|
63
|
+
cache_analysis_result.build_configurations, cache_analysis_result.project_object_version)
|
|
64
|
+
Pod::Installer::SandboxDirCleaner.new(sandbox, pod_targets, aggregate_targets).clean!
|
|
65
|
+
update_project_cache(cache_analysis_result, target_installation_results)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# ── Optimization 3+4: create_and_save_projects with parallel configure_schemes ──
|
|
69
|
+
def create_and_save_projects(pod_targets_to_generate, aggregate_targets_to_generate,
|
|
70
|
+
build_configurations, project_object_version)
|
|
71
|
+
UI.section 'Generating Pods project' do
|
|
72
|
+
generator = create_generator(pod_targets_to_generate, aggregate_targets_to_generate,
|
|
73
|
+
build_configurations, project_object_version,
|
|
74
|
+
installation_options.generate_multiple_pod_projects)
|
|
75
|
+
|
|
76
|
+
pod_project_generation_result = generator.generate!
|
|
77
|
+
@target_installation_results = pod_project_generation_result.target_installation_results
|
|
78
|
+
@pods_project = pod_project_generation_result.project
|
|
79
|
+
@pod_target_subprojects = pod_project_generation_result.projects_by_pod_targets.keys
|
|
80
|
+
@generated_projects = ([pods_project] + pod_target_subprojects || []).compact
|
|
81
|
+
@generated_pod_targets = pod_targets_to_generate
|
|
82
|
+
@generated_aggregate_targets = aggregate_targets_to_generate || []
|
|
83
|
+
projects_by_pod_targets = pod_project_generation_result.projects_by_pod_targets
|
|
84
|
+
|
|
85
|
+
predictabilize_uuids(generated_projects) if installation_options.deterministic_uuids?
|
|
86
|
+
stabilize_target_uuids(generated_projects)
|
|
87
|
+
|
|
88
|
+
projects_writer = Pod::Installer::Xcode::PodsProjectWriter.new(sandbox, generated_projects,
|
|
89
|
+
target_installation_results.pod_target_installation_results,
|
|
90
|
+
installation_options)
|
|
91
|
+
projects_writer.write! do
|
|
92
|
+
run_podfile_post_install_hooks
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Parallel configure_schemes (each project is independent)
|
|
96
|
+
pods_project_pod_targets = pod_targets_to_generate - projects_by_pod_targets.values.flatten
|
|
97
|
+
all_projects_by_pod_targets = {}
|
|
98
|
+
if pods_project
|
|
99
|
+
all_projects_by_pod_targets[pods_project] = pods_project_pod_targets
|
|
100
|
+
end
|
|
101
|
+
all_projects_by_pod_targets.merge!(projects_by_pod_targets) if projects_by_pod_targets
|
|
102
|
+
|
|
103
|
+
if all_projects_by_pod_targets.size > 1
|
|
104
|
+
parallel_configure_schemes(all_projects_by_pod_targets, generator, pod_project_generation_result)
|
|
105
|
+
else
|
|
106
|
+
all_projects_by_pod_targets.each do |project, pts|
|
|
107
|
+
generator.configure_schemes(project, pts, pod_project_generation_result)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def parallel_configure_schemes(projects_by_pod_targets, generator, generation_result)
|
|
116
|
+
pool_size = [[Etc.nprocessors - 1, 2].max, 16].min
|
|
117
|
+
Pod::UI.message "- Configuring schemes across #{projects_by_pod_targets.size} projects (pool: #{pool_size})"
|
|
118
|
+
|
|
119
|
+
pool = Concurrent::FixedThreadPool.new(pool_size)
|
|
120
|
+
projects_by_pod_targets.each do |project, pts|
|
|
121
|
+
pool.post do
|
|
122
|
+
generator.configure_schemes(project, pts, generation_result)
|
|
123
|
+
rescue StandardError => e
|
|
124
|
+
Pod::UI.warn "[cocoapods-podgenerate] Scheme configuration error: #{e.message}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
pool.shutdown
|
|
128
|
+
pool.wait_for_termination
|
|
129
|
+
rescue NameError
|
|
130
|
+
# Fallback: sequential
|
|
131
|
+
projects_by_pod_targets.each do |project, pts|
|
|
132
|
+
generator.configure_schemes(project, pts, generation_result)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# ── Optimization 3: Parallelize PodTargetIntegrator ──
|
|
21
138
|
module ParallelInstall
|
|
22
139
|
def install_pod_targets(project, pod_targets)
|
|
23
140
|
super
|
|
24
141
|
end
|
|
25
142
|
|
|
26
|
-
# Override integrate_targets to run in parallel
|
|
27
143
|
def integrate_targets(pod_target_installation_results)
|
|
28
144
|
pods_to_integrate = pod_target_installation_results.values.select do |result|
|
|
29
145
|
target = result.target
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# [cocoapods-podgenerate]
|
|
4
|
+
# Monkey-patches MultiPodsProjectGenerator to parallelize pod target installation.
|
|
5
|
+
#
|
|
6
|
+
# v0.1.2 Optimization:
|
|
7
|
+
# 1. Parallel install_all_pod_targets — each pod target's install is independent I/O
|
|
8
|
+
#
|
|
9
|
+
# Architecture:
|
|
10
|
+
# Instead of overriding create_pods_project (which has Ruby constant resolution issues
|
|
11
|
+
# in prepended modules for PodsProjectGenerator's inner classes), we keep the original
|
|
12
|
+
# project creation + file references sequential, and only parallelize the pod target
|
|
13
|
+
# installation step which is the heaviest I/O operation.
|
|
14
|
+
#
|
|
15
|
+
# Reference: CocoaPods — lib/cocoapods/installer/xcode/multi_pods_project_generator.rb
|
|
16
|
+
# lib/cocoapods/installer/xcode/pods_project_generator.rb
|
|
17
|
+
|
|
18
|
+
require 'concurrent'
|
|
19
|
+
require 'etc'
|
|
20
|
+
|
|
21
|
+
module Pod
|
|
22
|
+
module PodGenerate
|
|
23
|
+
module Patches
|
|
24
|
+
module MultiProjectGeneratorPatch
|
|
25
|
+
def self.apply
|
|
26
|
+
Pod::UI.message '[cocoapods-podgenerate] Applying MultiProjectGeneratorPatch (parallel pod target install)'
|
|
27
|
+
Pod::Installer::Xcode::MultiPodsProjectGenerator.prepend(ParallelMultiProjectGenerator)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module ParallelMultiProjectGenerator
|
|
31
|
+
# ── Optimization: Parallel install_all_pod_targets ──
|
|
32
|
+
# Each project has independent pod targets (different xcodeproj directories),
|
|
33
|
+
# so PodTargetInstaller operations can run concurrently without locking.
|
|
34
|
+
def install_all_pod_targets(projects_by_pod_targets)
|
|
35
|
+
UI.message '- Installing Pod Targets (parallel)' do
|
|
36
|
+
pool_size = compute_pool_size
|
|
37
|
+
mutex = Mutex.new
|
|
38
|
+
all_results = {}
|
|
39
|
+
|
|
40
|
+
pool = Concurrent::FixedThreadPool.new(pool_size)
|
|
41
|
+
projects_by_pod_targets.each do |project, pts|
|
|
42
|
+
pool.post do
|
|
43
|
+
target_results = install_pod_targets(project, pts)
|
|
44
|
+
mutex.synchronize { all_results.merge!(target_results) }
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
mutex.synchronize do
|
|
47
|
+
Pod::UI.warn "[cocoapods-podgenerate] Pod target install: #{e.message}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
pool.shutdown
|
|
53
|
+
pool.wait_for_termination
|
|
54
|
+
all_results
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def compute_pool_size
|
|
61
|
+
[[Etc.nprocessors - 1, 2].max, 16].min
|
|
62
|
+
rescue NameError
|
|
63
|
+
4
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# [cocoapods-podgenerate]
|
|
4
|
-
# Monkey-patches PodsProjectWriter to support incremental project saves.
|
|
5
|
-
# Uses project.pbxproj file for change detection via SHA256 digest.
|
|
4
|
+
# Monkey-patches PodsProjectWriter to support incremental + parallel project saves.
|
|
6
5
|
#
|
|
7
|
-
#
|
|
6
|
+
# v0.1.1 Optimizations:
|
|
7
|
+
# 1. SHA256 digest — skip sort+save for unchanged projects
|
|
8
|
+
# 2. Parallel save — multiple xcodeproj files saved in threads
|
|
9
|
+
#
|
|
10
|
+
# v0.1.2 Optimizations:
|
|
11
|
+
# 3. Parallel cleanup_projects — empty group removal across projects
|
|
12
|
+
# 4. Parallel recreate_user_schemes — scheme file creation across projects
|
|
13
|
+
#
|
|
14
|
+
# Reference: CocoaPods — lib/cocoapods/installer/xcode/pods_project_generator/pods_project_writer.rb
|
|
15
|
+
|
|
16
|
+
require 'concurrent'
|
|
17
|
+
require 'etc'
|
|
8
18
|
|
|
9
19
|
module Pod
|
|
10
20
|
module PodGenerate
|
|
11
21
|
module Patches
|
|
12
22
|
module ProjectWriterPatch
|
|
13
23
|
def self.apply
|
|
14
|
-
Pod::UI.message '[cocoapods-podgenerate] Applying ProjectWriterPatch (incremental save)'
|
|
15
|
-
Pod::Installer::Xcode::PodsProjectWriter.prepend(
|
|
24
|
+
Pod::UI.message '[cocoapods-podgenerate] Applying ProjectWriterPatch v3 (incremental + parallel save + parallel write steps)'
|
|
25
|
+
Pod::Installer::Xcode::PodsProjectWriter.prepend(IncrementalAndParallelSave)
|
|
16
26
|
end
|
|
17
27
|
|
|
18
|
-
module
|
|
28
|
+
module IncrementalAndParallelSave
|
|
19
29
|
def initialize(sandbox, projects, pod_target_installation_results, installation_options)
|
|
20
30
|
super
|
|
21
31
|
@project_digests = {}
|
|
@@ -24,23 +34,141 @@ module Pod
|
|
|
24
34
|
compute_initial_digests
|
|
25
35
|
end
|
|
26
36
|
|
|
37
|
+
# ── Optimizations 3+4+2: Parallel write! with parallel cleanup + schemes + save ──
|
|
38
|
+
def write!
|
|
39
|
+
# Parallel cleanup (each project is independent)
|
|
40
|
+
parallel_cleanup_projects(@projects)
|
|
41
|
+
|
|
42
|
+
# Parallel recreate_user_schemes (each project is independent)
|
|
43
|
+
parallel_recreate_user_schemes(@projects)
|
|
44
|
+
|
|
45
|
+
yield if block_given?
|
|
46
|
+
|
|
47
|
+
save_projects(@projects)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# ── Optimization 1: SHA256 skip + parallel save ──
|
|
27
51
|
def save_projects(projects)
|
|
28
|
-
projects
|
|
52
|
+
# Filter: skip projects whose pbxproj is unchanged
|
|
53
|
+
to_save = projects.select do |project|
|
|
29
54
|
if project_unchanged?(project)
|
|
30
55
|
Pod::UI.message "- Skipping unchanged project #{UI.path project.path}"
|
|
31
|
-
|
|
56
|
+
false
|
|
57
|
+
else
|
|
58
|
+
true
|
|
32
59
|
end
|
|
60
|
+
end
|
|
61
|
+
return if to_save.empty?
|
|
62
|
+
|
|
63
|
+
# Sort each project
|
|
64
|
+
to_save.each { |p| p.sort(:groups_position => :below) if needs_sort?(p) }
|
|
33
65
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
66
|
+
# Parallel save (safe: each xcodeproj is an independent directory)
|
|
67
|
+
if to_save.size > 1
|
|
68
|
+
Pod::UI.message "- Saving #{to_save.size} projects in parallel"
|
|
69
|
+
threads = to_save.map do |project|
|
|
70
|
+
Thread.new do
|
|
71
|
+
begin
|
|
72
|
+
Pod::UI.message "- Writing Xcode project file to #{UI.path project.path}"
|
|
73
|
+
project.save
|
|
74
|
+
update_digest(project)
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
Pod::UI.warn "[cocoapods-podgenerate] Parallel save error: #{e.message}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
threads.each(&:join)
|
|
81
|
+
else
|
|
82
|
+
Pod::UI.message "- Writing Xcode project file to #{UI.path to_save.first.path}" do
|
|
83
|
+
to_save.first.save
|
|
84
|
+
update_digest(to_save.first)
|
|
37
85
|
end
|
|
38
|
-
update_digest(project)
|
|
39
86
|
end
|
|
40
87
|
end
|
|
41
88
|
|
|
42
89
|
private
|
|
43
90
|
|
|
91
|
+
# ── Optimization 3: Parallel cleanup_projects ──
|
|
92
|
+
|
|
93
|
+
def parallel_cleanup_projects(projects)
|
|
94
|
+
pool_size = compute_pool_size
|
|
95
|
+
Pod::UI.message "- Cleaning up #{projects.size} projects (pool: #{pool_size})"
|
|
96
|
+
|
|
97
|
+
pool = Concurrent::FixedThreadPool.new(pool_size)
|
|
98
|
+
projects.each do |project|
|
|
99
|
+
pool.post do
|
|
100
|
+
cleanup_single_project(project)
|
|
101
|
+
rescue StandardError => e
|
|
102
|
+
Pod::UI.warn "[cocoapods-podgenerate] Cleanup error: #{e.message}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
pool.shutdown
|
|
106
|
+
pool.wait_for_termination
|
|
107
|
+
rescue NameError
|
|
108
|
+
cleanup_projects(projects)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def cleanup_single_project(project)
|
|
112
|
+
[project.pods, project.support_files_group,
|
|
113
|
+
project.development_pods, project.dependencies_group].each do |group|
|
|
114
|
+
group.remove_from_project if group.respond_to?(:empty?) && group.empty?
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# ── Optimization 4: Parallel recreate_user_schemes ──
|
|
119
|
+
|
|
120
|
+
def parallel_recreate_user_schemes(projects)
|
|
121
|
+
library_product_types = [:framework, :dynamic_library, :static_library]
|
|
122
|
+
|
|
123
|
+
# Pre-build results_by_native_target once (shared read-only cache)
|
|
124
|
+
results_by_native_target = build_native_target_cache
|
|
125
|
+
|
|
126
|
+
pool_size = compute_pool_size
|
|
127
|
+
Pod::UI.message "- Recreating user schemes for #{projects.size} projects (pool: #{pool_size})"
|
|
128
|
+
|
|
129
|
+
pool = Concurrent::FixedThreadPool.new(pool_size)
|
|
130
|
+
projects.each do |project|
|
|
131
|
+
pool.post do
|
|
132
|
+
project.recreate_user_schemes(false) do |scheme, target|
|
|
133
|
+
next unless target.respond_to?(:symbol_type)
|
|
134
|
+
next unless library_product_types.include?(target.symbol_type)
|
|
135
|
+
installation_result = results_by_native_target[target]
|
|
136
|
+
next unless installation_result
|
|
137
|
+
installation_result.test_native_targets.each do |test_native_target|
|
|
138
|
+
scheme.add_test_target(test_native_target)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
rescue StandardError => e
|
|
142
|
+
Pod::UI.warn "[cocoapods-podgenerate] Scheme recreation error: #{e.message}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
pool.shutdown
|
|
146
|
+
pool.wait_for_termination
|
|
147
|
+
rescue NameError
|
|
148
|
+
# Fallback: sequential
|
|
149
|
+
projects.each do |project|
|
|
150
|
+
project.recreate_user_schemes(false) do |scheme, target|
|
|
151
|
+
next unless target.respond_to?(:symbol_type)
|
|
152
|
+
next unless library_product_types.include?(target.symbol_type)
|
|
153
|
+
installation_result = results_by_native_target[target]
|
|
154
|
+
next unless installation_result
|
|
155
|
+
installation_result.test_native_targets.each do |test_native_target|
|
|
156
|
+
scheme.add_test_target(test_native_target)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def build_native_target_cache
|
|
163
|
+
cache = {}
|
|
164
|
+
@pod_target_installation_results.each do |_, result|
|
|
165
|
+
cache[result.native_target] = result if result.respond_to?(:native_target)
|
|
166
|
+
end
|
|
167
|
+
cache
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# ── Digest helpers (from v0.1.1) ──
|
|
171
|
+
|
|
44
172
|
def compute_initial_digests
|
|
45
173
|
@projects.each do |project|
|
|
46
174
|
update_digest(project)
|
|
@@ -74,7 +202,6 @@ module Pod
|
|
|
74
202
|
def pbxproj_path(project)
|
|
75
203
|
path = project.path
|
|
76
204
|
return nil unless path
|
|
77
|
-
# .xcodeproj is a directory; the actual content is in project.pbxproj
|
|
78
205
|
if path.to_s.end_with?('.xcodeproj')
|
|
79
206
|
File.join(path.to_s, 'project.pbxproj')
|
|
80
207
|
else
|
|
@@ -89,6 +216,12 @@ module Pod
|
|
|
89
216
|
rescue StandardError
|
|
90
217
|
nil
|
|
91
218
|
end
|
|
219
|
+
|
|
220
|
+
def compute_pool_size
|
|
221
|
+
[[Etc.nprocessors - 1, 2].max, 16].min
|
|
222
|
+
rescue NameError
|
|
223
|
+
4
|
|
224
|
+
end
|
|
92
225
|
end
|
|
93
226
|
end
|
|
94
227
|
end
|
|
@@ -4,39 +4,43 @@
|
|
|
4
4
|
# Monkey-patches UserProjectIntegrator to parallelize and optimize the
|
|
5
5
|
# "Integrating client project" step (step 4 of pod install).
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
# step runs serially. This patch:
|
|
7
|
+
# v0.1.1:
|
|
9
8
|
# 1. Parallelizes integrate_user_targets using threads
|
|
10
9
|
# 2. Parallelizes save_projects using threads
|
|
11
|
-
#
|
|
10
|
+
#
|
|
11
|
+
# v0.1.2:
|
|
12
|
+
# 3. Parallelizes warn_about_xcconfig_overrides using threads
|
|
12
13
|
#
|
|
13
14
|
# Reference: CocoaPods source
|
|
14
15
|
# - lib/cocoapods/installer/user_project_integrator.rb
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
require 'concurrent'
|
|
18
|
+
require 'etc'
|
|
16
19
|
|
|
17
20
|
module Pod
|
|
18
21
|
module PodGenerate
|
|
19
22
|
module Patches
|
|
20
23
|
module UserIntegratorPatch
|
|
24
|
+
IGNORED_KEYS = %w(CODE_SIGN_IDENTITY).freeze
|
|
25
|
+
INHERITED_FLAGS = %w($(inherited) ${inherited}).freeze
|
|
26
|
+
|
|
21
27
|
def self.apply
|
|
22
|
-
Pod::UI.message '[cocoapods-podgenerate] Applying UserIntegratorPatch (parallel client integration)'
|
|
28
|
+
Pod::UI.message '[cocoapods-podgenerate] Applying UserIntegratorPatch v2 (parallel client integration + xcconfig warnings)'
|
|
23
29
|
Pod::Installer::UserProjectIntegrator.prepend(ParallelIntegration)
|
|
24
30
|
end
|
|
25
31
|
|
|
26
32
|
module ParallelIntegration
|
|
27
|
-
#
|
|
33
|
+
# ── Optimization 1: Parallel integrate_user_targets ──
|
|
28
34
|
def integrate_user_targets
|
|
29
35
|
target_integrators = targets_to_integrate.sort_by(&:name).map do |target|
|
|
30
36
|
Pod::Installer::UserProjectIntegrator::TargetIntegrator.new(target, :use_input_output_paths => use_input_output_paths?)
|
|
31
37
|
end
|
|
32
38
|
|
|
33
39
|
if target_integrators.size <= 1
|
|
34
|
-
# Single target — no need for threads
|
|
35
40
|
target_integrators.each(&:integrate!)
|
|
36
41
|
return
|
|
37
42
|
end
|
|
38
43
|
|
|
39
|
-
# Multiple targets — integrate in parallel
|
|
40
44
|
Pod::UI.message "- Integrating #{target_integrators.size} targets in parallel"
|
|
41
45
|
threads = target_integrators.map do |integrator|
|
|
42
46
|
Thread.new do
|
|
@@ -50,7 +54,7 @@ module Pod
|
|
|
50
54
|
threads.each(&:join)
|
|
51
55
|
end
|
|
52
56
|
|
|
53
|
-
#
|
|
57
|
+
# ── Optimization 2: Parallel save_projects ──
|
|
54
58
|
def save_projects(projects)
|
|
55
59
|
projects = projects.uniq
|
|
56
60
|
|
|
@@ -65,7 +69,6 @@ module Pod
|
|
|
65
69
|
return
|
|
66
70
|
end
|
|
67
71
|
|
|
68
|
-
# Save multiple projects in parallel
|
|
69
72
|
Pod::UI.message "- Saving #{projects.size} user projects in parallel"
|
|
70
73
|
mutex = Mutex.new
|
|
71
74
|
threads = projects.map do |project|
|
|
@@ -85,6 +88,58 @@ module Pod
|
|
|
85
88
|
end
|
|
86
89
|
threads.each(&:join)
|
|
87
90
|
end
|
|
91
|
+
|
|
92
|
+
# ── Optimization 3: Parallel warn_about_xcconfig_overrides ──
|
|
93
|
+
# Overrides the original method by prepend — called automatically from integrate!
|
|
94
|
+
def warn_about_xcconfig_overrides
|
|
95
|
+
targets = targets_to_integrate
|
|
96
|
+
return if targets.empty?
|
|
97
|
+
|
|
98
|
+
if targets.size <= 1
|
|
99
|
+
warn_single_target(targets.first)
|
|
100
|
+
return
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
pool_size = compute_pool_size
|
|
104
|
+
Pod::UI.message "- Checking xcconfig overrides for #{targets.size} targets (pool: #{pool_size})"
|
|
105
|
+
pool = Concurrent::FixedThreadPool.new(pool_size)
|
|
106
|
+
targets.each do |aggregate_target|
|
|
107
|
+
pool.post do
|
|
108
|
+
warn_single_target(aggregate_target)
|
|
109
|
+
rescue StandardError => e
|
|
110
|
+
Pod::UI.warn "[cocoapods-podgenerate] Xcconfig warning error: #{e.message}"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
pool.shutdown
|
|
114
|
+
pool.wait_for_termination
|
|
115
|
+
rescue NameError
|
|
116
|
+
targets.each { |t| warn_single_target(t) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def warn_single_target(aggregate_target)
|
|
122
|
+
aggregate_target.user_targets.each do |user_target|
|
|
123
|
+
user_target.build_configurations.each do |config|
|
|
124
|
+
xcconfig = aggregate_target.xcconfigs[config.name]
|
|
125
|
+
next unless xcconfig
|
|
126
|
+
|
|
127
|
+
(xcconfig.to_hash.keys - UserIntegratorPatch::IGNORED_KEYS).each do |key|
|
|
128
|
+
target_values = config.build_settings[key]
|
|
129
|
+
if target_values &&
|
|
130
|
+
!UserIntegratorPatch::INHERITED_FLAGS.any? { |flag| target_values.include?(flag) }
|
|
131
|
+
print_override_warning(aggregate_target, user_target, config, key)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def compute_pool_size
|
|
139
|
+
[[Etc.nprocessors - 1, 2].max, 16].min
|
|
140
|
+
rescue NameError
|
|
141
|
+
4
|
|
142
|
+
end
|
|
88
143
|
end
|
|
89
144
|
end
|
|
90
145
|
end
|
|
@@ -5,6 +5,8 @@ require 'cocoapods-podgenerate/patches/project_patch'
|
|
|
5
5
|
require 'cocoapods-podgenerate/patches/project_writer_patch'
|
|
6
6
|
require 'cocoapods-podgenerate/patches/analyzer_patch'
|
|
7
7
|
require 'cocoapods-podgenerate/patches/user_integrator_patch'
|
|
8
|
+
require 'cocoapods-podgenerate/patches/multi_project_generator_patch'
|
|
9
|
+
require 'cocoapods-podgenerate/patches/cache_analyzer_patch'
|
|
8
10
|
require 'cocoapods-podgenerate/parallel/thread_pool'
|
|
9
11
|
require 'cocoapods-podgenerate/parallel/batch_processor'
|
|
10
12
|
require 'cocoapods-podgenerate/benchmark/profiler'
|
|
@@ -18,6 +20,8 @@ module Pod
|
|
|
18
20
|
Pod::PodGenerate::Patches::ProjectWriterPatch.apply
|
|
19
21
|
Pod::PodGenerate::Patches::AnalyzerPatch.apply
|
|
20
22
|
Pod::PodGenerate::Patches::UserIntegratorPatch.apply
|
|
23
|
+
Pod::PodGenerate::Patches::MultiProjectGeneratorPatch.apply
|
|
24
|
+
Pod::PodGenerate::Patches::CacheAnalyzerPatch.apply
|
|
21
25
|
|
|
22
26
|
# Install hook for profiler
|
|
23
27
|
Pod::PodGenerate::Benchmark::Profiler.install
|
|
@@ -28,8 +32,6 @@ module Pod
|
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
# Auto-activate when loaded via `plugin` directive in Podfile
|
|
31
|
-
# The :pre_install hook is set up by hooks.rb, but we also support
|
|
32
|
-
# direct `plugin` activation which defers to when CocoaPods is loaded.
|
|
33
35
|
if defined?(Pod::HooksManager)
|
|
34
36
|
Pod::PodGenerate.activate
|
|
35
37
|
else
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cocoapods-podgenerate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- PodGenerate Team
|
|
@@ -81,7 +81,7 @@ dependencies:
|
|
|
81
81
|
version: '3.0'
|
|
82
82
|
description: A CocoaPods plugin that accelerates pod install for large-scale projects
|
|
83
83
|
with 200+ pods by introducing parallel processing, optimized dependency analysis,
|
|
84
|
-
|
|
84
|
+
incremental project generation, and multi-project parallel saving.
|
|
85
85
|
executables: []
|
|
86
86
|
extensions: []
|
|
87
87
|
extra_rdoc_files: []
|
|
@@ -93,12 +93,14 @@ files:
|
|
|
93
93
|
- lib/cocoapods-podgenerate/parallel/batch_processor.rb
|
|
94
94
|
- lib/cocoapods-podgenerate/parallel/thread_pool.rb
|
|
95
95
|
- lib/cocoapods-podgenerate/patches/analyzer_patch.rb
|
|
96
|
+
- lib/cocoapods-podgenerate/patches/cache_analyzer_patch.rb
|
|
96
97
|
- lib/cocoapods-podgenerate/patches/installer_patch.rb
|
|
98
|
+
- lib/cocoapods-podgenerate/patches/multi_project_generator_patch.rb
|
|
97
99
|
- lib/cocoapods-podgenerate/patches/project_patch.rb
|
|
98
100
|
- lib/cocoapods-podgenerate/patches/project_writer_patch.rb
|
|
99
101
|
- lib/cocoapods-podgenerate/patches/user_integrator_patch.rb
|
|
100
102
|
- lib/cocoapods_plugin.rb
|
|
101
|
-
homepage: https://github.com/
|
|
103
|
+
homepage: https://github.com/lengain/cocoapods-podgenerate
|
|
102
104
|
licenses:
|
|
103
105
|
- MIT
|
|
104
106
|
metadata: {}
|