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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c23dcfa65c98d07b1facb75c25c0b48c4899886f88397544887c38d95f1d695
4
- data.tar.gz: f5a1805ef912d83ce6ac2520b48a40543c76f02634bef505bab121910427fb1a
3
+ metadata.gz: 27602be6d9b287c0d9b46a5ce44325a4ab1ddfcb47e2b6f0c5d40c6ec59cb904
4
+ data.tar.gz: 39039a26b13a8504df307e15afe77bc439541baaa5a5d27cce7b39fa7f61fb87
5
5
  SHA512:
6
- metadata.gz: f2f2628e7f34e56c628f006cdf785fc38df4e37e0e86cd04c52fe155b8eacf398ec656172b9476871438807acf987e13f3894c19501ce99c6e0a7a9cb0bae488
7
- data.tar.gz: 81d85b61da2db4da471411f0bc4ec3d4f42513d45a5358a2211c8bd0e352f01e0d02b888c99ef4c2ddf0ae6d12e8248b624eb98dda78e60a8a07691528a270f5
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 to optimize "Generating Pods project".
4
+ # Monkey-patches Pod::Installer and PodsProjectGenerator for step 3/4 optimizations.
5
5
  #
6
- # Optimizations:
7
- # 1. Parallelize PodTargetIntegrator integration (multiple pods at once)
8
- # 2. Delegate install_pod_targets to original (safe, no threading issues)
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
- # Reference: CocoaPods — lib/cocoapods/installer/xcode/pods_project_generator.rb
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 (optimized integration)'
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
- # Reference: CocoaPods source — lib/cocoapods/installer/xcode/pods_project_generator/pods_project_writer.rb
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(IncrementalSave)
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 IncrementalSave
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.each do |project|
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
- next
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
- project.sort(:groups_position => :below) if needs_sort?(project)
35
- Pod::UI.message "- Writing Xcode project file to #{UI.path project.path}" do
36
- project.save
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
- # For projects with many user targets / aggregate targets, the integration
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
- # 3. Caches user_project references to avoid redundant project parsing
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
- # - lib/cocoapods/installer/user_project_integrator/target_integrator.rb
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
- # Override integrate_user_targets to use parallel execution
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
- # Override save_projects to use parallel saving
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.0
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
- and incremental project generation.
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/example/cocoapods-podgenerate
103
+ homepage: https://github.com/lengain/cocoapods-podgenerate
102
104
  licenses:
103
105
  - MIT
104
106
  metadata: {}