cocoapods-podgenerate 0.1.1 → 0.1.3
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 +83 -0
- data/lib/cocoapods-podgenerate/patches/installer_patch.rb +76 -1
- data/lib/cocoapods-podgenerate/patches/multi_project_generator_patch.rb +69 -0
- data/lib/cocoapods-podgenerate/patches/project_writer_patch.rb +120 -2
- data/lib/cocoapods-podgenerate/patches/user_integrator_patch.rb +69 -10
- data/lib/cocoapods-podgenerate.rb +4 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aba68b23582a229140c6580a31d506a3298d8389b6946f432e6429e1fbfe5edd
|
|
4
|
+
data.tar.gz: 2cd7a8c3bf26165fcc425f00bc03a9bffd566946d58cf126ff6c0ee6e47d97aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d631266d74333c37296f116a0bedb11e31989ab3a6307d531ed4c59a2a55f7f7e8e553294c87cab2eceaa22006ceda20efaa733c123566f6cdf2314b3176e42a
|
|
7
|
+
data.tar.gz: 402636b400e0db9a80b5240730063d23215ac5c58ed87c3ac044a52839eb5a4a5d15600e48a71bf1f6363de3aa85aeae6aff9c466f512f2aa49d6de9522f5621
|
|
@@ -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,83 @@
|
|
|
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 = compute_cache_key(target, target_by_label)
|
|
41
|
+
mutex.synchronize { results[label] = key }
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
# Bug fix v0.1.3: compute fallback key synchronously to avoid nil
|
|
44
|
+
# entries that would crash ProjectCacheAnalyzer#analyze downstream
|
|
45
|
+
Pod::UI.warn "[cocoapods-podgenerate] Cache key computation error, retrying sync: #{e.message}"
|
|
46
|
+
fallback_key = compute_cache_key(target, target_by_label)
|
|
47
|
+
mutex.synchronize { results[label] = fallback_key }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
pool.shutdown
|
|
52
|
+
pool.wait_for_termination
|
|
53
|
+
results
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def compute_cache_key(target, target_by_label)
|
|
60
|
+
case target
|
|
61
|
+
when PodTarget
|
|
62
|
+
local = sandbox.local?(target.pod_name)
|
|
63
|
+
checkout_options = sandbox.checkout_sources[target.pod_name]
|
|
64
|
+
TargetCacheKey.from_pod_target(sandbox, target_by_label, target,
|
|
65
|
+
:is_local_pod => local,
|
|
66
|
+
:checkout_options => checkout_options)
|
|
67
|
+
when AggregateTarget
|
|
68
|
+
TargetCacheKey.from_aggregate_target(sandbox, target_by_label, target)
|
|
69
|
+
else
|
|
70
|
+
raise "[BUG] Unknown target type #{target}"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def compute_pool_size
|
|
75
|
+
[[Etc.nprocessors - 1, 2].max, 16].min
|
|
76
|
+
rescue NameError
|
|
77
|
+
4
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -8,14 +8,21 @@
|
|
|
8
8
|
# 2. Skip project generation entirely when nothing changed
|
|
9
9
|
# 3. Parallelize PodTargetIntegrator integration
|
|
10
10
|
#
|
|
11
|
+
# v0.1.2 Optimization:
|
|
12
|
+
# 4. Parallelize configure_schemes across projects
|
|
13
|
+
#
|
|
11
14
|
# Reference: CocoaPods — lib/cocoapods/installer.rb
|
|
15
|
+
# lib/cocoapods/installer/xcode/pods_project_generator.rb
|
|
16
|
+
|
|
17
|
+
require 'concurrent'
|
|
18
|
+
require 'etc'
|
|
12
19
|
|
|
13
20
|
module Pod
|
|
14
21
|
module PodGenerate
|
|
15
22
|
module Patches
|
|
16
23
|
module InstallerPatch
|
|
17
24
|
def self.apply
|
|
18
|
-
Pod::UI.message '[cocoapods-podgenerate] Applying InstallerPatch
|
|
25
|
+
Pod::UI.message '[cocoapods-podgenerate] Applying InstallerPatch v3'
|
|
19
26
|
Pod::Installer.prepend(ForceIncrementalInstall)
|
|
20
27
|
Pod::Installer::Xcode::PodsProjectGenerator.prepend(ParallelInstall)
|
|
21
28
|
end
|
|
@@ -57,6 +64,74 @@ module Pod
|
|
|
57
64
|
Pod::Installer::SandboxDirCleaner.new(sandbox, pod_targets, aggregate_targets).clean!
|
|
58
65
|
update_project_cache(cache_analysis_result, target_installation_results)
|
|
59
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
|
|
60
135
|
end
|
|
61
136
|
|
|
62
137
|
# ── Optimization 3: Parallelize PodTargetIntegrator ──
|
|
@@ -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
|
|
@@ -7,14 +7,21 @@
|
|
|
7
7
|
# 1. SHA256 digest — skip sort+save for unchanged projects
|
|
8
8
|
# 2. Parallel save — multiple xcodeproj files saved in threads
|
|
9
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
|
+
#
|
|
10
14
|
# Reference: CocoaPods — lib/cocoapods/installer/xcode/pods_project_generator/pods_project_writer.rb
|
|
11
15
|
|
|
16
|
+
require 'concurrent'
|
|
17
|
+
require 'etc'
|
|
18
|
+
|
|
12
19
|
module Pod
|
|
13
20
|
module PodGenerate
|
|
14
21
|
module Patches
|
|
15
22
|
module ProjectWriterPatch
|
|
16
23
|
def self.apply
|
|
17
|
-
Pod::UI.message '[cocoapods-podgenerate] Applying ProjectWriterPatch
|
|
24
|
+
Pod::UI.message '[cocoapods-podgenerate] Applying ProjectWriterPatch v3 (incremental + parallel save + parallel write steps)'
|
|
18
25
|
Pod::Installer::Xcode::PodsProjectWriter.prepend(IncrementalAndParallelSave)
|
|
19
26
|
end
|
|
20
27
|
|
|
@@ -27,7 +34,20 @@ module Pod
|
|
|
27
34
|
compute_initial_digests
|
|
28
35
|
end
|
|
29
36
|
|
|
30
|
-
# ──
|
|
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 ──
|
|
31
51
|
def save_projects(projects)
|
|
32
52
|
# Filter: skip projects whose pbxproj is unchanged
|
|
33
53
|
to_save = projects.select do |project|
|
|
@@ -68,6 +88,98 @@ module Pod
|
|
|
68
88
|
|
|
69
89
|
private
|
|
70
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 = begin
|
|
98
|
+
Concurrent::FixedThreadPool.new(pool_size)
|
|
99
|
+
rescue NameError
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
if pool
|
|
104
|
+
projects.each do |project|
|
|
105
|
+
pool.post do
|
|
106
|
+
cleanup_single_project(project)
|
|
107
|
+
rescue StandardError => e
|
|
108
|
+
Pod::UI.warn "[cocoapods-podgenerate] Cleanup error: #{e.message}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
pool.shutdown
|
|
112
|
+
pool.wait_for_termination
|
|
113
|
+
else
|
|
114
|
+
# Fallback: sequential (Concurrent not available)
|
|
115
|
+
projects.each { |p| cleanup_single_project(p) }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def cleanup_single_project(project)
|
|
120
|
+
[project.pods, project.support_files_group,
|
|
121
|
+
project.development_pods, project.dependencies_group].each do |group|
|
|
122
|
+
group.remove_from_project if group.respond_to?(:empty?) && group.empty?
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# ── Optimization 4: Parallel recreate_user_schemes ──
|
|
127
|
+
|
|
128
|
+
def parallel_recreate_user_schemes(projects)
|
|
129
|
+
library_product_types = [:framework, :dynamic_library, :static_library]
|
|
130
|
+
|
|
131
|
+
# Pre-build results_by_native_target once (shared read-only cache)
|
|
132
|
+
results_by_native_target = build_native_target_cache
|
|
133
|
+
|
|
134
|
+
pool_size = compute_pool_size
|
|
135
|
+
Pod::UI.message "- Recreating user schemes for #{projects.size} projects (pool: #{pool_size})"
|
|
136
|
+
|
|
137
|
+
pool = begin
|
|
138
|
+
Concurrent::FixedThreadPool.new(pool_size)
|
|
139
|
+
rescue NameError
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if pool
|
|
144
|
+
projects.each do |project|
|
|
145
|
+
pool.post do
|
|
146
|
+
recreate_schemes_for_project(project, library_product_types, results_by_native_target)
|
|
147
|
+
rescue StandardError => e
|
|
148
|
+
Pod::UI.warn "[cocoapods-podgenerate] Scheme recreation error: #{e.message}"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
pool.shutdown
|
|
152
|
+
pool.wait_for_termination
|
|
153
|
+
else
|
|
154
|
+
# Fallback: sequential (Concurrent not available)
|
|
155
|
+
projects.each do |project|
|
|
156
|
+
recreate_schemes_for_project(project, library_product_types, results_by_native_target)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def recreate_schemes_for_project(project, library_product_types, results_by_native_target)
|
|
162
|
+
project.recreate_user_schemes(false) do |scheme, target|
|
|
163
|
+
next unless target.respond_to?(:symbol_type)
|
|
164
|
+
next unless library_product_types.include?(target.symbol_type)
|
|
165
|
+
installation_result = results_by_native_target[target]
|
|
166
|
+
next unless installation_result
|
|
167
|
+
installation_result.test_native_targets.each do |test_native_target|
|
|
168
|
+
scheme.add_test_target(test_native_target)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def build_native_target_cache
|
|
174
|
+
cache = {}
|
|
175
|
+
@pod_target_installation_results.each do |_, result|
|
|
176
|
+
cache[result.native_target] = result if result.respond_to?(:native_target)
|
|
177
|
+
end
|
|
178
|
+
cache
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# ── Digest helpers (from v0.1.1) ──
|
|
182
|
+
|
|
71
183
|
def compute_initial_digests
|
|
72
184
|
@projects.each do |project|
|
|
73
185
|
update_digest(project)
|
|
@@ -115,6 +227,12 @@ module Pod
|
|
|
115
227
|
rescue StandardError
|
|
116
228
|
nil
|
|
117
229
|
end
|
|
230
|
+
|
|
231
|
+
def compute_pool_size
|
|
232
|
+
[[Etc.nprocessors - 1, 2].max, 16].min
|
|
233
|
+
rescue NameError
|
|
234
|
+
4
|
|
235
|
+
end
|
|
118
236
|
end
|
|
119
237
|
end
|
|
120
238
|
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,62 @@ 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
|
+
# Per-target xcconfig override check. Runs inside a thread pool slot.
|
|
122
|
+
# NOTE: `print_override_warning` is a private method on the original
|
|
123
|
+
# UserProjectIntegrator class. Ruby allows implicit-receiver calls to
|
|
124
|
+
# private methods from prepended modules (no explicit `self.` prefix).
|
|
125
|
+
def warn_single_target(aggregate_target)
|
|
126
|
+
aggregate_target.user_targets.each do |user_target|
|
|
127
|
+
user_target.build_configurations.each do |config|
|
|
128
|
+
xcconfig = aggregate_target.xcconfigs[config.name]
|
|
129
|
+
next unless xcconfig
|
|
130
|
+
|
|
131
|
+
(xcconfig.to_hash.keys - UserIntegratorPatch::IGNORED_KEYS).each do |key|
|
|
132
|
+
target_values = config.build_settings[key]
|
|
133
|
+
if target_values &&
|
|
134
|
+
!UserIntegratorPatch::INHERITED_FLAGS.any? { |flag| target_values.include?(flag) }
|
|
135
|
+
print_override_warning(aggregate_target, user_target, config, key)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def compute_pool_size
|
|
143
|
+
[[Etc.nprocessors - 1, 2].max, 16].min
|
|
144
|
+
rescue NameError
|
|
145
|
+
4
|
|
146
|
+
end
|
|
88
147
|
end
|
|
89
148
|
end
|
|
90
149
|
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.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- PodGenerate Team
|
|
@@ -93,7 +93,9 @@ 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
|