cocoapods-podfile-local 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8a2d1a17a228a1b6d6f1d73d24a9b85b66dd5492550d710a3bb373cea510ba3a
4
+ data.tar.gz: 39d97d0c86deb19c3eaf8f95bbc8f64a362b3b532949bb55c27eb6ba1b9ed964
5
+ SHA512:
6
+ metadata.gz: 96f8f2de20ce2f498e20a9d1468781baabbc9527e1cd037a5e4ed3f5c3a15dead86d9c9861db42e031901d9b8a181e40b808677f5bcd805e2bb8e9e805ff98b6
7
+ data.tar.gz: ac6653cab35a5938172d731abbb55c11e4e1a4f22d99f83743c3a01247b4ff8a85436b05fe926a8a92b7224af930dfb01e0ce6b688b2a2299fa15c42cc8f9fe1
data/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # cocoapods-podfile-local
2
+
3
+ 一个 CocoaPods 插件,让每个开发者通过 `Podfile.local` 文件覆盖 pod 的引入方式(git 分支、本地路径等),无需修改共享的 Podfile。
4
+
5
+ ## 使用方法
6
+
7
+ ### 1. 创建 Podfile.local
8
+
9
+ 在项目根目录创建 `Podfile.local`(已加入 `.gitignore`,不会被提交):
10
+
11
+ ```ruby
12
+ # 指向本地路径(联调开发最常用)
13
+ edit 'VKRouteKit', :path => '../VKRouteKit'
14
+
15
+ # 指向 git 仓库 + feature 分支
16
+ edit 'CustomLib', :git => 'git@gitlab.com:xxx/CustomLib.git', :branch => 'feature/login'
17
+
18
+ # 指向 git tag
19
+ edit 'SomeSDK', :git => 'git@gitlab.com:xxx/SomeSDK.git', :tag => 'v1.2.3'
20
+
21
+ # 指向特定 commit
22
+ edit 'SomeSDK', :git => 'git@gitlab.com:xxx/SomeSDK.git', :commit => 'abc123'
23
+
24
+ # 使用自定义 podspec
25
+ edit 'MyPod', :podspec => './local_specs/MyPod.podspec'
26
+
27
+ # 只覆盖非来源选项(保留原始版本/来源)
28
+ edit 'DebugTool', :configurations => ['Debug']
29
+
30
+ # 组合覆盖
31
+ edit 'VKWebBridge', :git => 'git@gitlab.com:xxx/VKWebBridge.git', :branch => 'hotfix/crash', :configurations => ['Debug']
32
+ ```
33
+
34
+ ### 2. 执行安装
35
+
36
+ ```bash
37
+ bundle exec pod install
38
+ ```
39
+
40
+ 控制台会输出覆盖日志:
41
+
42
+ ```
43
+ [Podfile.local] Loading /path/to/project/Podfile.local
44
+ [Podfile.local] Overriding 'VKRouteKit' with {:path=>"../VKRouteKit"}
45
+ [Podfile.local] Overriding 'CustomLib' with {:git=>"git@gitlab.com:xxx/CustomLib.git", :branch=>"feature/login"}
46
+ ```
47
+
48
+ ### 3. 恢复原始状态
49
+
50
+ 删除或清空 `Podfile.local`,重新 `pod install` 即可。
51
+
52
+ ## 互斥源自动清理
53
+
54
+ 覆盖来源时,插件会自动清除与之冲突的旧选项:
55
+
56
+ | edit 指定 | 自动清除 |
57
+ |---------------|---------------------------------------------|
58
+ | `:git` | `:path`, `:podspec` |
59
+ | `:path` | `:git`, `:branch`, `:tag`, `:commit`, `:podspec` |
60
+ | `:podspec` | `:git`, `:branch`, `:tag`, `:commit`, `:path` |
61
+
62
+ 例如原始 pod 用 `:git` + `:branch` 引入,你 edit 为 `:path`,插件会自动清除 `:git` 和 `:branch`,无需手动处理。
63
+
64
+ ## 工作原理
65
+
66
+ ### CocoaPods 生命周期
67
+
68
+ ```
69
+ pod install
70
+
71
+
72
+ ① 加载 gem(插件在此阶段加载) ← 插件读取 Podfile.local,拦截 pod 方法
73
+
74
+
75
+ ② 解析 Podfile(逐行执行 Ruby 代码) ← 拦截的 pod 方法在此阶段替换参数
76
+
77
+
78
+ ③ 解析依赖(resolver 计算版本来源) ← 此时 Kingfisher 已经是 :path 来源
79
+
80
+
81
+ ④ 下载 & 安装
82
+ ```
83
+
84
+ ### 核心机制
85
+
86
+ 插件在加载阶段(早于 Podfile 解析)完成两件事:
87
+
88
+ **1. 读取 Podfile.local,收集覆盖配置**
89
+
90
+ 创建一个独立的 `PodfileLocalLoader` 对象,用 `instance_eval` 执行 `Podfile.local` 内容。每个 `edit` 调用将覆盖选项注册到 `OverrideManager` 单例。
91
+
92
+ **2. 用 `prepend` 拦截 `Pod::Podfile::DSL#pod` 方法**
93
+
94
+ 在 `pod` 方法的查找链前面插入一个包装模块。当 Podfile 解析到 `pod 'Kingfisher', '~> 8.0'` 时:
95
+
96
+ 1. 包装方法检查 OverrideManager 中是否有 `'Kingfisher'` 的覆盖 → 有
97
+ 2. 从原始参数中分离版本号(`'~> 8.0'`)和选项 Hash
98
+ 3. 执行互斥源清理 + 选项合并
99
+ 4. 如果覆盖指定了 `:path` / `:git` / `:podspec`,移除版本号约束
100
+ 5. 用合并后的参数调用 `super`(原始 `pod` 方法)
101
+
102
+ 对 CocoaPods 来说,就好像 Podfile 里直接写的就是覆盖后的内容。
103
+
104
+ ### 时序图
105
+
106
+ ```mermaid
107
+ sequenceDiagram
108
+ participant User as pod install
109
+ participant CP as CocoaPods
110
+ participant Plugin as cocoapods-podfile-local
111
+ participant OM as OverrideManager
112
+ participant PF as Podfile
113
+
114
+ User->>CP: bundle exec pod install
115
+ CP->>Plugin: require cocoapods_plugin.rb
116
+ Plugin->>Plugin: setup!
117
+ Plugin->>Plugin: load_podfile_local!
118
+ Plugin->>OM: edit 'Kingfisher', path:... → register
119
+ Plugin->>Plugin: patch_pod_dsl! (prepend)
120
+
121
+ CP->>PF: 开始解析 Podfile
122
+ PF->>Plugin: pod 'Alamofire', '~> 5.9'
123
+ Plugin->>OM: 有覆盖? → 无
124
+ Plugin->>CP: super → 原样注册
125
+
126
+ PF->>Plugin: pod 'Kingfisher', '~> 8.0'
127
+ Plugin->>OM: 有覆盖? → 有!
128
+ OM->>OM: merge + 互斥源清理
129
+ Plugin->>CP: super('Kingfisher', path:'...') → 替换注册
130
+
131
+ CP->>CP: 解析依赖 (Kingfisher 已是 path 来源)
132
+ CP->>CP: 下载 & 安装
133
+ ```
134
+
135
+ ### 文件结构
136
+
137
+ ```
138
+ plugins/cocoapods-podfile-local/
139
+ ├── cocoapods-podfile-local.gemspec # gem 描述
140
+ ├── README.md # 本文档
141
+ └── lib/
142
+ ├── cocoapods_plugin.rb # CocoaPods 插件入口(硬性约定)
143
+ ├── cocoapods-podfile-local.rb # 主入口,require 各模块 + 调用 setup!
144
+ └── cocoapods_podfile_local/
145
+ ├── version.rb # 版本号
146
+ ├── dsl.rb # 注入 edit 方法到 Pod::Podfile
147
+ ├── override_manager.rb # 覆盖注册、互斥清理、选项合并
148
+ └── hook.rb # 加载 Podfile.local + prepend 拦截 pod 方法
149
+ ```
150
+
151
+ ### 关键设计决策
152
+
153
+ - **为什么不用 `pre_install` hook?** `pre_install` 在依赖解析之后才执行,此时修改 Dependency 对象已经不影响解析结果。必须在 Podfile 解析阶段拦截。
154
+
155
+ - **为什么用 `prepend` 而不是 `alias_method`?** `prepend` 是 Ruby 推荐的方法拦截方式,在方法查找链中插入模块,可以用 `super` 调用原始方法,不会污染命名空间。
156
+
157
+ - **为什么 Podfile.local 用独立 Loader 而不是在 Podfile 上下文中 eval?** 插件加载时 Podfile 实例尚不存在,无法在其上下文中执行。用独立的 `PodfileLocalLoader` 对象来收集配置,与 Podfile 解析过程解耦。
158
+
159
+ - **为什么 `cocoapods_plugin.rb` 是必须的?** CocoaPods 通过检查 gem 中是否存在 `lib/cocoapods_plugin.rb` 来判断是否为合法插件,这是硬性约定。
160
+
161
+ ## 注意事项
162
+
163
+ - `Podfile.local` 已加入 `.gitignore`,每个开发者独立维护
164
+ - edit 一个 Podfile 中不存在的 pod 会输出警告但不会中断安装
165
+ - 首次使用需要先 `bundle install` 安装插件
166
+ - 插件作为本地 gem 内嵌在项目中,无需发布到 RubyGems
@@ -0,0 +1,6 @@
1
+ require 'cocoapods_podfile_local/version'
2
+ require 'cocoapods_podfile_local/override_manager'
3
+ require 'cocoapods_podfile_local/dsl'
4
+ require 'cocoapods_podfile_local/hook'
5
+
6
+ CocoapodsPodfileLocal.setup!
@@ -0,0 +1 @@
1
+ require 'cocoapods-podfile-local'
@@ -0,0 +1,13 @@
1
+ module CocoapodsPodfileLocal
2
+ module DSL
3
+ def edit(pod_name, options = {})
4
+ CocoapodsPodfileLocal::OverrideManager.instance.register(pod_name, options)
5
+ end
6
+ end
7
+ end
8
+
9
+ module Pod
10
+ class Podfile
11
+ include CocoapodsPodfileLocal::DSL
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ module CocoapodsPodfileLocal
2
+ TAG = '[Podfile.local]'.freeze
3
+
4
+ class PodfileLocalLoader
5
+ def edit(pod_name, options = {})
6
+ OverrideManager.instance.register(pod_name, options)
7
+ end
8
+ end
9
+
10
+ class << self
11
+ def setup!
12
+ load_podfile_local!
13
+ return if OverrideManager.instance.empty?
14
+ patch_pod_dsl!
15
+ end
16
+
17
+ private
18
+
19
+ def load_podfile_local!
20
+ local_file = Pathname.pwd + 'Podfile.local'
21
+ return unless local_file.exist?
22
+
23
+ Pod::UI.message "#{TAG} Loading #{local_file}"
24
+ loader = PodfileLocalLoader.new
25
+ loader.instance_eval(local_file.read, local_file.to_s)
26
+ end
27
+
28
+ def patch_pod_dsl!
29
+ manager = OverrideManager.instance
30
+
31
+ Pod::Podfile::DSL.prepend(Module.new do
32
+ define_method(:pod) do |name = nil, *requirements|
33
+ if name && manager.overrides.key?(name)
34
+ override_opts = manager.overrides[name]
35
+
36
+ original_opts = requirements.last.is_a?(Hash) ? requirements.pop : {}
37
+ version_reqs = requirements
38
+
39
+ merged = manager.merge(original_opts, override_opts)
40
+
41
+ if merged.key?(:path) || merged.key?(:git) || merged.key?(:podspec)
42
+ version_reqs = []
43
+ end
44
+
45
+ Pod::UI.message "#{TAG} Overriding '#{name}' with #{merged}"
46
+
47
+ new_requirements = version_reqs
48
+ new_requirements << merged unless merged.empty?
49
+ super(name, *new_requirements)
50
+ else
51
+ super(name, *requirements)
52
+ end
53
+ end
54
+ end)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,51 @@
1
+ module CocoapodsPodfileLocal
2
+ class OverrideManager
3
+ SOURCE_KEYS = %i[git path podspec].freeze
4
+ GIT_EXTRAS = %i[branch tag commit].freeze
5
+
6
+ EXCLUSIVE_RULES = {
7
+ git: %i[path podspec],
8
+ path: %i[git branch tag commit podspec],
9
+ podspec: %i[git branch tag commit path],
10
+ }.freeze
11
+
12
+ def self.instance
13
+ @instance ||= new
14
+ end
15
+
16
+ def self.reset!
17
+ @instance = nil
18
+ end
19
+
20
+ def initialize
21
+ @overrides = {}
22
+ end
23
+
24
+ def register(pod_name, options)
25
+ @overrides[pod_name] = options
26
+ end
27
+
28
+ def overrides
29
+ @overrides.dup
30
+ end
31
+
32
+ def empty?
33
+ @overrides.empty?
34
+ end
35
+
36
+ # Merge override options into the original options, with exclusive-source cleanup.
37
+ def merge(original_options, override_options)
38
+ merged = original_options.dup
39
+
40
+ keys_to_remove = []
41
+ EXCLUSIVE_RULES.each do |source_key, conflicting_keys|
42
+ if override_options.key?(source_key)
43
+ keys_to_remove.concat(conflicting_keys)
44
+ end
45
+ end
46
+
47
+ keys_to_remove.uniq.each { |k| merged.delete(k) }
48
+ merged.merge(override_options)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module CocoapodsPodfileLocal
2
+ VERSION = '0.1.0'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cocoapods-podfile-local
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - dairuiquan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cocoapods
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '1.10'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ description: |
34
+ A CocoaPods plugin that lets each developer maintain a local Podfile.local
35
+ file (git-ignored) to override pod sources — pointing to git branches,
36
+ local paths, tags, commits, or custom podspecs — without modifying the
37
+ shared Podfile.
38
+ email:
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - README.md
44
+ - lib/cocoapods-podfile-local.rb
45
+ - lib/cocoapods_plugin.rb
46
+ - lib/cocoapods_podfile_local/dsl.rb
47
+ - lib/cocoapods_podfile_local/hook.rb
48
+ - lib/cocoapods_podfile_local/override_manager.rb
49
+ - lib/cocoapods_podfile_local/version.rb
50
+ homepage: https://rubygems.org/gems/cocoapods-podfile-local
51
+ licenses:
52
+ - MIT
53
+ metadata: {}
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '2.6'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubygems_version: 3.4.1
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: Override pod sources via Podfile.local without touching the shared Podfile.
73
+ test_files: []