├── .gitignore ├── .rubocop.yml ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── cocoapods-sled.gemspec ├── documents ├── QA.md └── usage.md ├── lib ├── cocoapods-sled.rb ├── cocoapods-sled │ ├── command.rb │ ├── command │ │ ├── install │ │ │ ├── device.rb │ │ │ └── simulator.rb │ │ └── options │ │ │ └── sled_options.rb │ ├── framework_cache.rb │ ├── gem_version.rb │ ├── installer_options.rb │ ├── integration.rb │ ├── podfile │ │ ├── dsl_ext.rb │ │ └── target_definition_ext.rb │ ├── sandbox │ │ └── path_list_ext.rb │ ├── target │ │ └── pod_target_ext.rb │ └── tool.rb └── cocoapods_plugin.rb └── resource └── QRCode.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | .DS_Store 10 | .idea/ 11 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.4 3 | 4 | Style/StringLiterals: 5 | Enabled: true 6 | EnforcedStyle: double_quotes 7 | 8 | Style/StringLiteralsInInterpolation: 9 | Enabled: true 10 | EnforcedStyle: double_quotes 11 | 12 | Layout/LineLength: 13 | Max: 120 14 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or 22 | advances of any kind 23 | * Trolling, insulting or derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or email 26 | address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a 28 | professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 35 | 36 | ## Scope 37 | 38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 39 | 40 | ## Enforcement 41 | 42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at TODO: Write your email address. All complaints will be reviewed and investigated promptly and fairly. 43 | 44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 45 | 46 | ## Enforcement Guidelines 47 | 48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 49 | 50 | ### 1. Correction 51 | 52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 53 | 54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 55 | 56 | ### 2. Warning 57 | 58 | **Community Impact**: A violation through a single incident or series of actions. 59 | 60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 61 | 62 | ### 3. Temporary Ban 63 | 64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 65 | 66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 67 | 68 | ### 4. Permanent Ban 69 | 70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 71 | 72 | **Consequence**: A permanent ban from any sort of public interaction within the community. 73 | 74 | ## Attribution 75 | 76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 77 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 78 | 79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 80 | 81 | [homepage]: https://www.contributor-covenant.org 82 | 83 | For answers to common questions about this code of conduct, see the FAQ at 84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 85 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in cocoapods-sled.gemspec 6 | gemspec 7 | 8 | gem "rake", "~> 13.0" 9 | 10 | gem "rubocop", "~> 1.7" 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 zhaoshouwen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | Cocoapods-sled 是一个简单易用的 Cocoapods 插件,通过缓存和复用Xcode编译结果完成二进制化。它的特性: 3 | 4 | 1. **低接入成本**:即插即用,没有预编译,也不用双私有源等基建,维护成本低。 5 | 2. **本地缓存**:给未匹配到二进制的 pod 插入同步脚本,当 Xcode 执行构建任务时将编译结果缓存到`~/Caches/CocoaPods/Frameworks`目录下。缓存区分真机和模拟器。 6 | 3. **二进制化处理**:自动将可复用的 pod 转换为二进制,源码和二进制丝滑切换。 7 | 4. **所有 Pod 都能复用**:Development Pods 编译结果也可以进行复用。 8 | 9 | Cocoapods-sled 致力于成为iOS项目构建优化的首选工具之一,帮助开发者以更低的成本实现更高效的开发流程。 10 | 11 | ## 实现思路 12 | Xcode 本身有自己的编译缓存 DerivedData,但经常会失效。Cocoapods-sled 插件会把 DerivedData 中的编译结果缓存,当 install 时匹配到缓存就改写 spec 把源码替换为二进制,从而避免不必要的重复编译。当执行`pod install [device | simulator]`时,大概执行了以下操作: 13 | 14 | 1. 原`pod install`流程,Downloading dependencies 执行完毕后插入二进制逻辑 15 | 2. 为每个 pod 生成缓存路径 16 | 3. 去对应的缓存路径下查找是否存在缓存 17 | - 缓存命中:把源码替换为二进制,并进行一些可选的操作,比如生产 Header Search Path 等 18 | - 未命中缓存:在 Pod Target 的 `Build Phases`中插入同步脚本,从 DerivedData 提取编译结果缓存到`~/Caches/CocoaPods/Frameworks`目录下,等待下次复用 19 | 4. 继续执行`pod install`流程,从 Generating Pods project 开始 20 | 21 | ## 安装 22 | Cocoapods-sled 的安装过程非常简洁。你可以通过以下两种方式之一进行安装: 23 | 24 | 1. 在应用程序的Gemfile中添加以下行: 25 | 26 | ```ruby 27 | gem 'cocoapods-sled' 28 | ``` 29 | 然后运行`bundle install` 30 | 31 | 2. 直接在终端中执行`gem install cocoapods-sled`命令进行安装。 32 | 33 | ## 使用方法 34 | 35 | [详见文档](./documents/usage.md) 36 | 37 | ## 常见问题 38 | 39 | [详见文档](./documents/QA.md) 40 | 41 | ## 开发计划 42 | - [ ] 支持 library 43 | - [ ] 支持服务器存储 44 | - [ ] 二进制调试? 45 | 46 | ## 补充 47 | 48 | 喜欢就star❤️一下吧 49 | 50 | QQ交流群:692296661 51 | 52 | 交流群 53 | 54 | ## Contributing 55 | 56 | Bug reports and pull requests are welcome on GitHub at https://github.com/git179979506/cocoapods-sled. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/git179979506/cocoapods-sled/blob/main/CODE_OF_CONDUCT.md). 57 | 58 | ## License 59 | 60 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 61 | 62 | ## Code of Conduct 63 | 64 | Everyone interacting in the Cocoapods::Sled project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/git179979506/cocoapods-sled/blob/main/CODE_OF_CONDUCT.md). 65 | 66 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rubocop/rake_task" 5 | 6 | RuboCop::RakeTask.new 7 | 8 | task default: :rubocop 9 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "cocoapods/sled" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require "irb" 15 | IRB.start(__FILE__) 16 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /cocoapods-sled.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/cocoapods-sled/gem_version.rb" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "cocoapods-sled" 7 | spec.version = Pod::Sled::VERSION 8 | spec.authors = ["赵守文"] 9 | spec.email = ["zsw19911017@163.com"] 10 | 11 | spec.summary = "Cocoapods-sled 是一个简单易用的 Cocoapods 插件,通过缓存和复用Xcode编译结果完成二进制化" 12 | spec.description = "Cocoapods-sled 是一个简单易用的 Cocoapods 插件,通过缓存和复用Xcode编译结果完成二进制化" 13 | spec.homepage = "https://github.com/git179979506/cocoapods-sled" 14 | spec.license = "MIT" 15 | spec.required_ruby_version = ">= 2.4.0" 16 | 17 | spec.metadata["homepage_uri"] = spec.homepage 18 | spec.metadata["source_code_uri"] = spec.homepage 19 | 20 | spec.files = Dir['lib/**/*'] 21 | spec.bindir = "exe" 22 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 23 | spec.require_paths = ["lib"] 24 | 25 | # Uncomment to register a new dependency of your gem 26 | # spec.add_dependency "example-gem", "~> 1.0" 27 | 28 | # For more information and examples about making a new gem, checkout our 29 | # guide at: https://bundler.io/guides/creating_gem.html 30 | 31 | spec.add_development_dependency 'bundler', '~> 1.3' 32 | spec.add_development_dependency 'rake' 33 | end 34 | -------------------------------------------------------------------------------- /documents/QA.md: -------------------------------------------------------------------------------- 1 | ## 1. ARC不对齐问题 2 | 3 | **描述**:参考[得物 iOS 工程演进之路](https://mp.weixin.qq.com/s/Lr6tDxacQKGZ19cKdmNg1w),全工程编译产物制作:利用编译缓存,从 Xcode 编译缓存 DerivedData 中取出组件。可能会产生ARC不对齐问题,导致 EXC\_BAD\_ACCESS Crash,具体原因请查看文档。 4 | 5 | **解决方案**:使用`--dep-check=[single|all]`参数,实际测试`--dep-check=single`可解决上述偶发问题,但受限于样本量较少,不保证测试结果准确性。 6 | 7 | ## 2. #import "" 方式导入的头文件找不到 8 | 9 | **解决方案**: 10 | 1. 命令行使用`--header-search-path`标记,所有 Pod 都会生成 Header Search Path 11 | 2. Podfile配置`sled_enable_generate_header_search_paths!`,所有 Pod 都会生成 Header Search Path 12 | 3. 单个 Pod 配置 `pod 'xxx', :hsp => true`,指定 Pod 生成 Header Search Path 13 | 14 | ## 3. The 'Pods-xxx' target has transitive dependencies that include statically linked binaries: 15 | 16 | **描述**:可能是在 Podfile 中重写了 `build_as_static_framework?` `build_as_dynamic_framework?`等方法,但是没有修改 `@build_type`属性,`verify_no_static_framework_transitive_dependencies` 执行时,需要编译的 pod 库都按照修改前的 build\_type 检查,导致判断有问题报错:动态库不能依赖静态库。 17 | 18 | **解决方案**:正确修改 `@build_type` 属性,或者使用 Cocoapods 提供的方法声明 Pod 为静态库或动态库,`use_frameworks! :linkage => :static` -------------------------------------------------------------------------------- /documents/usage.md: -------------------------------------------------------------------------------- 1 | ### 获取帮助文档 2 | ```bash 3 | bundle exec pod install --help 4 | bundle exec pod install device --help 5 | bundle exec pod install simulator --help 6 | ``` 7 | 8 | ### 开启二进制 9 | 10 | cocoapods-sled 给`pod install`添加了两个子命令`device`和`simulator`,分别对应真机和模拟器。 11 | > 由于没有进行预编译,二进制缓存是从日常开发产生的缓存 DerivedData 中提取的,无法保证真机和模拟器同时编译并进行包合并,需要手动选择真机或模拟器,这样实现基本满足大多数日常开发场景和打包机打包。 12 | 13 | **使用`device`子命令,真机调试和打包开启二进制** 14 | 15 | ```bash 16 | bundle exec pod install device 17 | ``` 18 | 19 | **使用`simulator`子命令,模拟器调试和打包开启二进制** 20 | 21 | ```bash 22 | bundle exec pod install simulator 23 | ``` 24 | 25 | **不使用子命令,则不触发二进制逻辑,使用源码(pod install 原逻辑)** 26 | 27 | ```bash 28 | bundle exec pod install 29 | ``` 30 | 31 | 使用上面的命令就可以开启二进制了,是不是很简单,当然插件还提供了丰富的参数和配置用于适配更正开发场景需求。 32 | 33 | ### 命令行参数说明 34 | 35 | - `--no-binary-pods=name`: 禁用指定的 Pod 二进制缓存,多个 Pod 名称用","分隔,优先级高于 `--all-binary` 36 | 37 | *日常开发中,可以和`--all-binary`配合使用,忽略 Podfile 配置,并指定 pod 使用源码进行开发,避免 Podfile 同时提交协作冲突* 38 | 39 | - `--binary-pods=pod1,pod2`: 只对指定的 Pod 进行二进制缓存,多个 Pod 名称用","分隔,优先级最高 40 | 41 | *白名单模式,优先级最高,只为特定 Pod 开启二进制缓存* 42 | 43 | - `--all-binary`: 强制使用二进制缓存,忽略 Podfile 中 `:binary => false` 设置 44 | 45 | *跟`--no-binary-pods=name`配合使用,或者在打包机中忽略 Podfile 配置强制开启二进制* 46 | 47 | - `--configuration=[Debug|Release|自定义]`: 编译配置用于生产缓存路径(Debug、Release、自定义),不传则不区分共用,一般用于打包机 48 | 49 | *对应 Xcode Build Configuration,支持自定义,当不传此参数时所有configuration产物共用,一般在打包机才需要区分不同 configuration,避免出现Debug、Release环境混乱。* 50 | 51 | - `--header-search-path`: 生成 Header Search Path,一般用于打包机 52 | 53 | *适配 OC`import ""`方式导入头文件,头文件找不到的情况,使用改标记表示所有pod头生产Header Search Path,也可以在 Podfile 中配置(支持全局生成或单个 pod 生成)* 54 | 55 | - `--project=name`: 工程名称,用于生成framework缓存目录,区分多个工程的缓存 56 | 57 | *区分多个工程,一般用于打包机,避免出现一些奇怪的问题* 58 | 59 | - `--no-dev-pod`: 关闭 Development Pods 二进制缓存,默认是开启的 60 | *默认情况 Development Pods 也是开启二进制的,用于适配部分三方库使用 Development Pods 导入的情况,需要注意的是:当 Development Pod 所在目录有变更时不会使用二进制。* 61 | 62 | - `--force-sync-dev-pod`: 强制缓存 Development Pods 编译结果,忽略本地修改检查,默认本地有修改时不缓存,一般用于打包机 63 | 64 | *Development Pods 开启二进制时,忽略本地变更检查,一般用于打包机,默认本地有修改时不缓存* 65 | 66 | - `--inhibit-all-warnings`: 强制关闭警告,忽略 Podfile 中的配置,一般用于打包机 67 | 68 | *关闭所有 pod 的编译警告,一般用于打包机,减少打包日志输出,方便排查打包失败原因* 69 | 70 | - `--cache-limit=num`: 指定每个 Pod 缓存存储上限数量,小于 3 无效,一般用于打包机 71 | 72 | *默认上限是 4 个,可按需调整,缓存数量上限越大则缓存命中率越高,反之越低,⚠️注意磁盘不要被撑爆* 73 | 74 | - `--dep-check=[single|all]`: 检查依赖库版本是否发生变更,single:只检查直接依赖,all:检查全部依赖,一般用于打包机 75 | 76 | *用于解决ARC不对齐问题,一般使用`--dep-check=single`即可,详见常见问题部分* 77 | 78 | - `--check-xcode-version`: 检查xcode版本,不同版本打包不复用,使用 `xcodebuild -version` 获取版本信息,一般用于打包机 79 | 80 | *用于区分不同版本Xcode打出的包,一般用于打包机* 81 | 82 | 83 | ### Podfile 配置说明 84 | 85 | 安装 `cocoapods-sled` 插件后,通过 `device` 和 `simulator` 子命令就可以触发二进制缓存复用逻辑,不配置 Podfile 也可以正常工作,在 Podfile 中的一些固定配置可以简化命令行参数。 86 | 87 | 比如可以配置生成 Header Search Paths:`sled_enable_generate_header_search_paths!`,这样命令行就可以省略参数 `--header-search-path`。 88 | 89 | Podfile 中的配置不会影响打包机打包,打包机上可通过参数 `--all-binary` 配置所有 pod 强制启用二进制。 90 | 91 | ```ruby 92 | # 声明需要使用插件 cocoapods-sled 93 | plugin 'cocoapods-sled' 94 | 95 | # 标记生成 HEADER SEARCH PATHS 96 | # 用于适配OC使用 #import "" 导入库头文件的情况,默认不生成 97 | # 单个库可通过 :hsp => true | false 设置 98 | sled_enable_generate_header_search_paths! 99 | 100 | # 关闭 Development Pod 二进制缓存,默认开启 101 | sled_disable_binary_cache_for_dev_pod! 102 | 103 | # 与 :binary => :ignore 等效,用于没有明确依赖的库 104 | sled_disable_binary_pods 'MOBFoundation', 'Bugly' 105 | 106 | # 白名单模式,优先级最高。只为 RxSwift 开启二进制缓存 107 | sled_enable_binary_pods 'RxSwift' 108 | 109 | pod 'RxSwift', :binary => false # 关闭二进制(标记 --all-binary 时忽略该值) 110 | pod 'RxCocoa', :binary => true # 开启二进制(默认为开启,可省略) 111 | pod 'Bugly', :binary => :ignore # 忽略,二进制不做处理(一般用于三方库本身就是二进制的情况,避免出现异常情况,优先级高于 --all-binary) 112 | 113 | pod 'QMUIKit', :hsp => true # 生成 HEADER SEARCH PATHS,默认不生成 114 | 115 | ``` 116 | 117 | ### 示例 118 | 119 | #### case1: 开发 SledLogin 和 SledRoute 组件库,使用真机调试 120 | 121 | ##### 方案一 122 | 123 | 1. 修改 Podfile,关闭 SledLogin 和 SledRoute 的二进制 124 | 125 | ```ruby 126 | 127 | pod 'SledLogin', :path => '../SledLogin', :binary => false # Development Pod 128 | 129 | pod 'SledRoute', :commit => 'f02079ae', :git => "#{BASE}/SledRoute", :binary => false # External Pod 130 | 131 | pod 'RxSwift', '6.7.0', :binary => false # Release Pod 132 | 133 | ``` 134 | 135 | 2. 执行命令查找真机缓存 136 | 137 | ```bash 138 | 139 | $ bundle exec pod install device 140 | 141 | ``` 142 | 143 | ##### 方案二(推荐) 144 | 145 | 每次修改 Podfile 比较麻烦,而且多人协作会互相影响,可以使用命令行参数规避这个问题。 146 | 147 | `--all-binary` 和 `--no-binary-pods` 组合使用,忽略 Podfile 中的 `:binary => false` 配置,指定 pod 库关闭二进制。 148 | 149 | ```bash 150 | 151 | $ bundle exec pod install device --all-binary --no-binary-pods=SledLogin,SledRoute 152 | 153 | ``` 154 | 155 | #### case2: 切换到模拟器调试 156 | 157 | 因为我们之前使用的是真机二进制缓存,所以需要重新执行命令手动切换到模拟器 158 | 159 | ```bash 160 | 161 | $ bundle exec pod install simulator --all-binary --no-binary-pods=SledLogin,SledRoute 162 | 163 | ``` 164 | 165 | #### case3: 打包机打包 166 | 167 | ```bash 168 | # 使用 bundler 保证版本统一 169 | # --all-binary: 强制开启 170 | # --configuration=[Debug|Release|自定义]: 区分不同configuration,避免环境混乱 171 | # --force-sync-dev-pod: 强制开启 Development Pods 二进制,忽略本地变更 172 | # --dep-check=single: 检查直接依赖变更,用于修复偶发的ARC不对齐问题 173 | # --check-xcode-version: 不同版本Xcode编译结果不共用 174 | # --cache-limit=12: 缓存上限数量改为12 175 | # --header-search-path: 全局生成Header Search Path,根据你的项目情况而定,最好不使用 176 | # --project=xxx: 区分不同项目的编译结果 177 | 178 | bundle exec pod install device --all-binary --configuration=[Debug|Release|自定义] --force-sync-dev-pod --dep-check=single --check-xcode-version --cache-limit=12 [--header-search-path] [--project=xxx] 179 | ``` 180 | -------------------------------------------------------------------------------- /lib/cocoapods-sled.rb: -------------------------------------------------------------------------------- 1 | require 'cocoapods-sled/gem_version' 2 | -------------------------------------------------------------------------------- /lib/cocoapods-sled/command.rb: -------------------------------------------------------------------------------- 1 | require 'cocoapods-sled/command/install/device' 2 | require 'cocoapods-sled/command/install/simulator' 3 | -------------------------------------------------------------------------------- /lib/cocoapods-sled/command/install/device.rb: -------------------------------------------------------------------------------- 1 | module Pod 2 | class Command 3 | class Install < Command 4 | class Device < Install 5 | require 'cocoapods-sled/installer_options' 6 | require 'cocoapods-sled/command/options/sled_options' 7 | 8 | include SledOptions 9 | 10 | self.summary = '查找真机二进制缓存' 11 | 12 | self.description = <<-DESC 13 | #{self.summary},缓存目录: `#{Config.instance.cache_root + 'Frameworks'}`. 14 | DESC 15 | 16 | def initialize(argv) 17 | super 18 | end 19 | 20 | def validate! 21 | super 22 | end 23 | 24 | def run 25 | Pod::Installer.sled_reuse_type = :device 26 | super 27 | end 28 | end 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /lib/cocoapods-sled/command/install/simulator.rb: -------------------------------------------------------------------------------- 1 | module Pod 2 | class Command 3 | class Install < Command 4 | class Simulator < Install 5 | require 'cocoapods-sled/installer_options' 6 | 7 | require 'cocoapods-sled/command/options/sled_options' 8 | 9 | include SledOptions 10 | 11 | self.summary = '查找模拟器二进制缓存' 12 | 13 | self.description = <<-DESC 14 | #{self.summary},缓存目录: `#{Config.instance.cache_root + 'Frameworks'}`. 15 | DESC 16 | 17 | def initialize(argv) 18 | super 19 | end 20 | 21 | def validate! 22 | super 23 | end 24 | 25 | def run 26 | Pod::Installer.sled_reuse_type = :simulator 27 | super 28 | end 29 | end 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /lib/cocoapods-sled/command/options/sled_options.rb: -------------------------------------------------------------------------------- 1 | require 'cocoapods-sled/podfile/dsl_ext' 2 | require 'cocoapods-sled/framework_cache' 3 | 4 | module Pod 5 | class Command 6 | module Options 7 | module SledOptions 8 | 9 | module Options 10 | def options 11 | [ 12 | ['--no-binary-pods=name', '禁用指定的 Pod 二进制缓存,多个 Pod 名称用","分隔, 优先级高于 --all-binary'], 13 | ['--binary-pods=name', '启用指定的 Pod 二进制缓存,多个 Pod 名称用","分隔, 优先级最高'], 14 | ['--all-binary', '强制使用二进制缓存,忽略 Podfile 中 `:binary` 设置'], 15 | ['--header-search-path', '生成 Header Search Path,一般用于打包机'], 16 | ['--project=name', '工程名称,用于生成framework缓存目录,区分多个工程的缓存'], 17 | ['--no-dev-pod', '关闭 Development Pods 二进制缓存,默认是开启的'], 18 | ['--force-sync-dev-pod', '强制缓存 Development Pods 编译结果,忽略本地修改检查,默认本地有修改时不缓存,一般用于打包机'], 19 | ['--inhibit-all-warnings', '强制关闭警告,忽略 Podfile 中的配置,一般用于打包机'], 20 | ['--cache-limit=num', '指定每个 Pod 缓存存储上限数量,小于 3 无效,一般用于打包机'], 21 | ['--dep-check=[single|all]', '检查依赖库版本是否发生变更,single:只检查直接依赖,all:检查全部依赖,一般用于打包机'], 22 | ['--check-xcode-version', '检查xcode版本,不同版本打包不复用,使用 xcodebuild -version 获取版本信息,一般用于打包机'], 23 | ['--configuration=[Debug|Release|自定义]', '编译配置用于生产缓存路径(Debug、Release、自定义),不传则不区分共用,一般用于打包机'] 24 | ].concat(super) 25 | end 26 | end 27 | 28 | def self.included(base) 29 | base.extend(Options) 30 | end 31 | 32 | def initialize(argv) 33 | Podfile::DSL.sled_disable_binary_pods = argv.option('no-binary-pods', '').split(',') 34 | Podfile::DSL.sled_enable_binary_pods = argv.option('binary-pods', '').split(',') 35 | count = argv.option('cache-limit', '').to_i 36 | if !count.nil? && count >= 3 37 | FrameworkCache.sled_cache_limit = count 38 | end 39 | 40 | check_type = argv.option('dep-check', '').to_sym 41 | if VALID_DEPENDENCY_CHECK_TYPE.include? check_type 42 | Podfile::DSL.dependency_check_type = check_type 43 | end 44 | 45 | Podfile::DSL.sled_configuration = argv.option('configuration', DEFAULT_CONFIGURATION).to_s 46 | 47 | Podfile::DSL.sled_project_name = argv.option('project', '') 48 | 49 | Installer.sled_force_binary = argv.flag?('all-binary', false) 50 | if argv.flag?('header-search-path', false) 51 | Podfile::DSL.sled_enable_generate_header_search_paths = true 52 | end 53 | unless argv.flag?('dev-pod', true) 54 | Podfile::DSL.sled_disable_binary_cache_for_dev_pod = true 55 | end 56 | if argv.flag?('force-sync-dev-pod', false) 57 | Installer.sled_force_rsync_local_pod = true 58 | end 59 | if argv.flag?('inhibit-all-warnings', false) 60 | Podfile::DSL.sled_inhibit_all_warnings = true 61 | end 62 | if argv.flag?('check-xcode-version', false) 63 | Podfile::DSL.sled_check_xcode_version = true 64 | end 65 | super 66 | end 67 | end 68 | end 69 | end 70 | end -------------------------------------------------------------------------------- /lib/cocoapods-sled/framework_cache.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'tmpdir' 3 | 4 | module Pod 5 | # The class responsible for managing Pod downloads, transparently caching 6 | # them in a cache directory. 7 | # 8 | class FrameworkCache 9 | 10 | class_attr_accessor :sled_cache_limit 11 | self.sled_cache_limit = 4 12 | 13 | # @return [Pathname] The root directory where this cache store its 14 | # downloads. 15 | # 16 | attr_reader :root 17 | 18 | # Initialize a new instance 19 | # 20 | # @param [Pathname,String] root 21 | # see {#root} 22 | # 23 | def initialize() 24 | @root = Pathname(Config.instance.cache_root + 'Frameworks') 25 | end 26 | 27 | def path_for_framework(slug) 28 | root + slug 29 | end 30 | 31 | def remove_size_exceeded_values(request) 32 | pod_cache_dir = root + request.sled_cache_subpath 33 | return unless pod_cache_dir.directory? 34 | 35 | array = pod_cache_dir.children.select { |pn| pn.directory? }.sort { |a, b| a.mtime <=> b.mtime } 36 | limit = FrameworkCache.sled_cache_limit 37 | if array.count > limit # 数量支持配置 38 | array.take(array.count - limit).each { |pn| `rm -dr #{pn.to_s}` } # 权限问题 39 | end 40 | end 41 | 42 | # @param [Request] request 43 | # the request to be downloaded. 44 | # 45 | # @return [Response] The download response for the given `request` that 46 | # was found in the download cache. 47 | # 48 | def cached_pod(request) 49 | cached_spec = cached_spec(request) 50 | path = path_for_pod(request) 51 | 52 | return unless cached_spec && path.directory? 53 | spec = request.spec || cached_spec 54 | Response.new(path, spec, request.params) 55 | end 56 | 57 | # Copies the `source` directory to `destination`, cleaning the directory 58 | # of any files unused by `spec`. 59 | # 60 | # @param [Pathname] source 61 | # 62 | # @param [Pathname] destination 63 | # 64 | # @param [Specification] spec 65 | # 66 | # @return [Void] 67 | # 68 | def copy_and_clean(source, destination, spec) 69 | specs_by_platform = group_subspecs_by_platform(spec) 70 | destination.parent.mkpath 71 | FileUtils.rm_rf(destination) 72 | FileUtils.cp_r(source, destination) 73 | Pod::Installer::PodSourcePreparer.new(spec, destination).prepare! 74 | Sandbox::PodDirCleaner.new(destination, specs_by_platform).clean! 75 | end 76 | 77 | def group_subspecs_by_platform(spec) 78 | specs_by_platform = {} 79 | [spec, *spec.recursive_subspecs].each do |ss| 80 | ss.available_platforms.each do |platform| 81 | specs_by_platform[platform] ||= [] 82 | specs_by_platform[platform] << ss 83 | end 84 | end 85 | specs_by_platform 86 | end 87 | 88 | # Writes the given `spec` to the given `path`. 89 | # 90 | # @param [Specification] spec 91 | # the specification to be written. 92 | # 93 | # @param [Pathname] path 94 | # the path the specification is to be written to. 95 | # 96 | # @return [Void] 97 | # 98 | def write_spec(spec, path) 99 | path.dirname.mkpath 100 | path.open('w') { |f| f.write spec.to_pretty_json } 101 | end 102 | 103 | # @return [Hash>] 104 | # A hash whose keys are the pod name 105 | # And values are a hash with the following keys: 106 | # :spec_file : path to the spec file 107 | # :name : name of the pod 108 | # :version : pod version 109 | # :release : boolean to tell if that's a release pod 110 | # :slug : the slug path where the pod cache is located 111 | # 112 | def cache_descriptors_per_pod 113 | specs_dir = root + 'Specs' 114 | release_specs_dir = specs_dir + 'Release' 115 | return {} unless specs_dir.exist? 116 | 117 | spec_paths = specs_dir.find.select { |f| f.fnmatch('*.podspec.json') } 118 | spec_paths.reduce({}) do |hash, spec_path| 119 | spec = Specification.from_file(spec_path) 120 | hash[spec.name] ||= [] 121 | is_release = spec_path.to_s.start_with?(release_specs_dir.to_s) 122 | request = Downloader::Request.new(:spec => spec, :released => is_release) 123 | hash[spec.name] << { 124 | :spec_file => spec_path, 125 | :name => spec.name, 126 | :version => spec.version, 127 | :release => is_release, 128 | :slug => root + request.slug, 129 | } 130 | hash 131 | end 132 | end 133 | end 134 | 135 | module Downloader 136 | class Request 137 | def sled_cache_subpath(name: self.name) 138 | 139 | if released_pod? 140 | "Release/#{name}" 141 | else 142 | "External/#{name}" 143 | end 144 | end 145 | end 146 | end 147 | end -------------------------------------------------------------------------------- /lib/cocoapods-sled/gem_version.rb: -------------------------------------------------------------------------------- 1 | module Pod 2 | module Sled 3 | # The version of the CocoaPods-sled plug-in. 4 | # 5 | VERSION = '0.0.2'.freeze unless defined? Pod::Sled::VERSION 6 | end 7 | end -------------------------------------------------------------------------------- /lib/cocoapods-sled/installer_options.rb: -------------------------------------------------------------------------------- 1 | require_relative 'tool' 2 | require_relative 'podfile/dsl_ext' 3 | 4 | module Pod 5 | class Installer 6 | 7 | class_attr_accessor :sled_force_rsync_local_pod 8 | self.sled_force_rsync_local_pod = false 9 | 10 | # MARK: force_binary 11 | class_attr_accessor :sled_force_binary 12 | self.sled_force_binary = false 13 | 14 | # MARK: sled_reuse_type 15 | # @return [Array] known reuse options. 16 | # 17 | KNOWN_REUSE_OPTIONS = %i(device simulator).freeze 18 | 19 | class_attr_accessor :sled_reuse_type 20 | self.sled_reuse_type = nil 21 | 22 | def self.sled_should_resure? 23 | sled_reuse_type && KNOWN_REUSE_OPTIONS.include?(sled_reuse_type) 24 | end 25 | 26 | def self.sled_reuse_type_name 27 | case sled_reuse_type 28 | when :device then 29 | "#{Podfile::DSL.sled_configuration}-iphoneos" 30 | when :simulator then 31 | "#{Podfile::DSL.sled_configuration}-iphonesimulator" 32 | else 33 | "Other" 34 | end 35 | end 36 | 37 | def self.sled_reuse_type_desc 38 | case sled_reuse_type 39 | when :device then 40 | "真机" 41 | when :simulator then 42 | "模拟器" 43 | else 44 | "Other" 45 | end 46 | end 47 | 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/cocoapods-sled/integration.rb: -------------------------------------------------------------------------------- 1 | require_relative 'framework_cache' 2 | require_relative 'tool' 3 | require_relative 'sandbox/path_list_ext' 4 | require_relative 'podfile/dsl_ext' 5 | require_relative 'target/pod_target_ext' 6 | 7 | module Pod 8 | class Target 9 | 10 | def sled_sepc_full_name 11 | name = specs.sort_by(&:name) 12 | .map { |spec| spec.name.sub("#{root_spec.name}/", "").gsub("/", "-") } 13 | .join("-") # 区分不同subspec打包的framework 14 | name = root_spec.name + "-" + name if !name.include?(root_spec.name) 15 | # File name too long 16 | if name.length > 50 17 | name = Digest::MD5.hexdigest(name) 18 | end 19 | name 20 | end 21 | 22 | # 如果 Podfile 中重写了 build_as_static_framework? build_as_dynamic_framework?,但是没有修改 @build_type 23 | # verify_no_static_framework_transitive_dependencies 执行时,需要编译的pod库都按照修改前的build_type检查 24 | # 导致判断有问题报错:动态库不能依赖静态库 25 | # The 'Pods-xxx' target has transitive dependencies that include statically linked binaries: 26 | def sled_adjust_build_type 27 | if build_as_static_framework? 28 | @build_type = BuildType.static_framework 29 | elsif build_as_dynamic_framework? 30 | @build_type = BuildType.dynamic_framework 31 | end 32 | end 33 | 34 | def sled_build_type_name 35 | "#{build_type.linkage}_#{build_type.packaging}" 36 | end 37 | 38 | def sled_framework_cache_subpath(reuse_type_name) 39 | "#{sled_build_type_name}/#{sled_sepc_full_name}/#{reuse_type_name}" 40 | end 41 | end 42 | end 43 | 44 | module Pod 45 | class Installer 46 | require 'cocoapods-sled/installer_options' 47 | 48 | SLED_VALID_PLATFORMS = Platform.all.freeze.map { |v| v.name.to_s } 49 | 50 | def f_cache 51 | @f_cache ||= FrameworkCache.new 52 | end 53 | 54 | old_install_pod_sources = instance_method(:download_dependencies) 55 | define_method(:download_dependencies) do 56 | old_install_pod_sources.bind(self).() 57 | 58 | # Installer.sled_reuse_type = :device 59 | return unless Installer.sled_should_resure? 60 | 61 | UI.section "查找#{Installer.sled_reuse_type_desc}二进制缓存" do 62 | sled_reuse_action 63 | end 64 | end 65 | 66 | def sled_reuse_action 67 | cache = [] 68 | 69 | sled_totalTargets = [] 70 | @sled_hits = [] 71 | @sled_miss = [] 72 | disable_binary_pod_targets = [] 73 | 74 | pod_targets.each do |target| 75 | # 修正build_type 76 | # target.sled_adjust_build_type 77 | 78 | disable_binary_pod_targets << target.name if Podfile::DSL.sled_disable_binary_pods.include?(target.root_spec.name) 79 | next if Podfile::DSL.sled_disable_binary_pods.include?(target.root_spec.name) 80 | 81 | disable_binary_pod_targets << target.name if target.seld_ignore_binary? 82 | next if target.seld_ignore_binary? 83 | 84 | disable_binary_pod_targets << target.name unless Pod::Installer.sled_force_binary || target.sled_enable_binary? 85 | next unless Pod::Installer.sled_force_binary || target.sled_enable_binary? 86 | 87 | if Podfile::DSL.sled_enable_binary_pods.count > 0 88 | disable_binary_pod_targets << target.name unless Podfile::DSL.sled_enable_binary_pods.include?(target.root_spec.name) 89 | next unless Podfile::DSL.sled_enable_binary_pods.include?(target.root_spec.name) 90 | end 91 | 92 | root_spec = target.root_spec 93 | 94 | 95 | sled_totalTargets << target.name 96 | 97 | if target.sled_local? 98 | next if Podfile::DSL.sled_disable_binary_cache_for_dev_pod 99 | sled_modify_pod_target(target) 100 | else 101 | sled_modify_pod_target(target) 102 | end 103 | end 104 | 105 | UI.puts " -> pod targets数量: #{pod_targets.count}".yellow 106 | UI.puts " -> binary disabled数量: #{disable_binary_pod_targets.count}".yellow 107 | disable_binary_pod_targets_text = disable_binary_pod_targets.join(", ") 108 | UI.puts " #{disable_binary_pod_targets_text}" unless disable_binary_pod_targets_text.empty? 109 | UI.puts " -> 处理targets数量: #{sled_totalTargets.count}".yellow 110 | UI.puts " -> 命中数量: #{@sled_hits.count}".yellow 111 | UI.message " #{@sled_hits.join(", ")}" 112 | UI.puts " -> 未命中数量: #{@sled_miss.count}".yellow 113 | sled_miss_text = @sled_miss.join(", ") 114 | UI.puts " #{sled_miss_text}" unless sled_miss_text.empty? 115 | end 116 | 117 | def xcode_version 118 | @xcode_version ||= `xcodebuild -version | grep "Build version" | awk '{print $NF}' | head -1`.strip 119 | end 120 | 121 | def sled_modify_pod_target(target) 122 | request = target.sled_download_request 123 | return if request.nil? 124 | 125 | root_spec = target.root_spec 126 | 127 | slug = target.sled_slug_current 128 | if Podfile::DSL.sled_check_xcode_version 129 | slug = "#{slug}-#{xcode_version}" 130 | end 131 | unless Podfile::DSL.sled_project_name.empty? 132 | slug = "#{slug}-#{Podfile::DSL.sled_project_name}" 133 | end 134 | 135 | path = f_cache.path_for_framework(slug) 136 | 137 | pod_root = sandbox.pod_dir(root_spec.name) 138 | 139 | framework_dir_path = path + target.sled_framework_cache_subpath(Installer.sled_reuse_type_name) 140 | framework_file_path = framework_dir_path + target.product_name 141 | 142 | if framework_file_path.directory? 143 | path.utime(Time.now, Time.now) 144 | 145 | @sled_hits << target.name 146 | 147 | # 1.12.x 相同 pod_root 重用了 path_list,这里会导致同目录下的podspec只能找到一个二进制 148 | target.file_accessors.map(&:path_list).each do |value| 149 | value.add_cache_root(framework_dir_path) 150 | end 151 | 152 | framework_dir_relative_path = framework_dir_path.relative_path_from(pod_root) 153 | framework_file_relative_path = framework_file_path.relative_path_from(pod_root) 154 | 155 | if target.specs && !target.specs.empty? 156 | target.specs.each do |spec| 157 | sled_empty_source_files(spec) 158 | sled_replace_resource_bundles(spec, framework_dir_relative_path.to_s) 159 | sled_add_header_search_paths(spec, target, framework_file_path.to_s) 160 | end 161 | 162 | sled_add_vendered_framework(target.specs.first, framework_file_relative_path.to_s) 163 | end 164 | else 165 | @sled_miss << target.name 166 | sled_add_script_phases(target, path) 167 | end 168 | 169 | f_cache.remove_size_exceeded_values(request) 170 | end 171 | 172 | def sled_add_header_search_paths(spec, pod_target, framework_file_path) 173 | return unless pod_target.sled_enable_generate_header_search_paths? 174 | 175 | spec.attributes_hash["user_target_xcconfig"] ||= {} 176 | spec.attributes_hash["user_target_xcconfig"]["HEADER_SEARCH_PATHS"] = "#{framework_file_path}/Headers/" 177 | # "${PODS_ROOT}/#{target.copy_resources_script_path_for_spec(spec).relative_path_from(target.sandbox.root)}" 178 | end 179 | 180 | def sled_replace_resource_bundles(spec, framework_dir_path) 181 | if spec.attributes_hash["resource_bundles"] 182 | bundle_names = spec.attributes_hash["resource_bundles"].keys 183 | spec.attributes_hash["resource_bundles"] = nil 184 | 185 | spec.attributes_hash["resources"] = Array(spec.attributes_hash["resources"]) 186 | spec.attributes_hash["resources"] += bundle_names.map{ |name| "#{framework_dir_path}/#{name}.bundle" } 187 | end 188 | end 189 | 190 | # TODO: 待完善 参考 add_vendered_framework 191 | def sled_add_vendered_framework(spec, framework_file_path) 192 | 193 | spec.attributes_hash["vendored_frameworks"] = Array(spec.attributes_hash["vendored_frameworks"]) 194 | spec.attributes_hash["vendored_frameworks"] += [framework_file_path] 195 | 196 | # spec.attributes_hash["source_files"] = framework_file_path + '/Headers/*.h' 197 | # spec.attributes_hash["public_header_files"] = framework_file_path + '/Headers/*.h' 198 | # spec.attributes_hash["subspecs"] = nil 199 | end 200 | 201 | # source_files 置空 202 | def sled_empty_source_files(spec) 203 | spec.attributes_hash["source_files"] = [] 204 | spec.attributes_hash["public_header_files"] = [] 205 | # VALID_PLATFORMS = Platform.all.freeze 206 | SLED_VALID_PLATFORMS.each do |plat| 207 | if spec.attributes_hash[plat] != nil 208 | spec.attributes_hash[plat]["source_files"] = [] 209 | spec.attributes_hash[plat]["public_header_files"] = [] 210 | end 211 | end 212 | end 213 | 214 | def sled_add_script_phases(pod_target, chche_dir) 215 | 216 | rsync_func = "" 217 | 218 | # Local Pod不强制同步时,检查本地代码变更 219 | if pod_target.sled_local? && !Installer.sled_force_rsync_local_pod 220 | pod_target.sled_local_pod_paths.each do |path| 221 | rsync_func << <<-SH.strip_heredoc 222 | status=`git -C #{path} status -s #{path}` 223 | 224 | if [[ $status != "" ]]; then 225 | echo "#{pod_target.name} 有变更,不同步编译结果" 226 | echo $status 227 | exit 0 228 | fi 229 | 230 | SH 231 | end 232 | end 233 | 234 | rsync_func << "#{Pod::Generator::ScriptPhaseConstants::RSYNC_PROTECT_TMP_FILES}" 235 | rsync_func << <<-SH.strip_heredoc 236 | 237 | framework_cache_path="#{chche_dir}/#{pod_target.sled_framework_cache_subpath("${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}")}" 238 | common_cache_path="#{chche_dir}/#{pod_target.sled_framework_cache_subpath("#{DEFAULT_CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}")}" 239 | 240 | echo "mkdir -p ${framework_cache_path}" || true 241 | mkdir -p ${framework_cache_path} 242 | 243 | exec_rsync() { 244 | echo "rsync -rLptgoDv --chmod=a=rwx "${RSYNC_PROTECT_TMP_FILES[@]}" ${CONFIGURATION_BUILD_DIR}/ ${framework_cache_path} &" 245 | 246 | # TARGET_BUILD_DIR 在打包机上有问题,包含所有Framework,使用CONFIGURATION_BUILD_DIR 247 | rsync -rLptgoDv --chmod=a=rwx "${RSYNC_PROTECT_TMP_FILES[@]}" "${CONFIGURATION_BUILD_DIR}/" "${framework_cache_path}" >/dev/null 2>>"#{chche_dir}/error.log" 248 | 249 | ln -sfn "${framework_cache_path}" "${common_cache_path}" 250 | } 251 | 252 | (exec_rsync) & 253 | SH 254 | 255 | spec = pod_target.specs.first 256 | value = Array(spec.attributes_hash['script_phases'] || spec.attributes_hash['script_phase']) 257 | value << { 258 | :name => "Rsync Framework And Bundle To Sled Cache", 259 | :script => rsync_func 260 | } 261 | spec.attributes_hash["script_phases"] = value 262 | end 263 | end 264 | end -------------------------------------------------------------------------------- /lib/cocoapods-sled/podfile/dsl_ext.rb: -------------------------------------------------------------------------------- 1 | require_relative '../tool' 2 | 3 | VALID_DEPENDENCY_CHECK_TYPE = %i(default single all).freeze 4 | DEFAULT_CONFIGURATION = "Sled-Common" 5 | 6 | module Pod 7 | class Podfile 8 | module DSL 9 | 10 | # def sled_enable_binary_cache! 11 | # DSL.sled_enable_binary_cache = true 12 | # end 13 | 14 | def sled_disable_binary_cache_for_dev_pod! 15 | DSL.sled_disable_binary_cache_for_dev_pod = true 16 | end 17 | 18 | def sled_enable_generate_header_search_paths! 19 | DSL.sled_enable_generate_header_search_paths = true 20 | end 21 | 22 | def sled_disable_binary_pods(*names) 23 | DSL.sled_disable_binary_pods ||= [] 24 | DSL.sled_disable_binary_pods.concat(names) 25 | end 26 | 27 | def sled_enable_binary_pods(*names) 28 | DSL.sled_enable_binary_pods ||= [] 29 | DSL.sled_enable_binary_pods.concat(names) 30 | end 31 | 32 | private 33 | # 开启二进制缓存(默认开启,暂时不支持修改) 34 | class_attr_accessor :sled_enable_binary_cache 35 | self.sled_enable_binary_cache = true 36 | 37 | # 禁用 Development pods 二进制缓存 38 | class_attr_accessor :sled_disable_binary_cache_for_dev_pod 39 | self.sled_disable_binary_cache_for_dev_pod = false 40 | 41 | # 生成 HEADER_SEARCH_PATHS 42 | class_attr_accessor :sled_enable_generate_header_search_paths 43 | self.sled_enable_generate_header_search_paths = false 44 | 45 | class_attr_accessor :sled_inhibit_all_warnings 46 | self.sled_inhibit_all_warnings = false 47 | 48 | class_attr_accessor :sled_disable_binary_pods 49 | self.sled_disable_binary_pods = [] 50 | 51 | class_attr_accessor :sled_enable_binary_pods 52 | self.sled_enable_binary_pods = [] 53 | 54 | class_attr_accessor :dependency_check_type 55 | self.dependency_check_type = :default 56 | 57 | # 检查 xcode 版本,使用 xcodebuild -version 获取版本信息 Build version 58 | class_attr_accessor :sled_check_xcode_version 59 | self.sled_check_xcode_version = false 60 | 61 | # 工程名称,用于生成缓存目录,区分多个工程的缓存 62 | class_attr_accessor :sled_project_name 63 | self.sled_project_name = '' 64 | 65 | # 编译配置,用于生成缓存目录,默认不区分不同的编译配置(Debug | Release | 自定义) 66 | class_attr_accessor :sled_configuration 67 | self.sled_configuration = DEFAULT_CONFIGURATION 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/cocoapods-sled/podfile/target_definition_ext.rb: -------------------------------------------------------------------------------- 1 | module Pod 2 | class Podfile 3 | class SLEDPodOptionsItem 4 | 5 | attr_reader :internal_hash 6 | attr_reader :key 7 | attr_reader :default 8 | 9 | def initialize(key, default) 10 | @internal_hash = {} 11 | @key = key 12 | @default = default 13 | end 14 | 15 | def value_for_pod(pod_name) 16 | value = internal_hash[pod_name] || [default] 17 | value.map do |v| 18 | v.nil? ? default : v 19 | end 20 | end 21 | 22 | # def sled_all_vlaue=(flag) 23 | # internal_hash['all'] = flag 24 | # end 25 | 26 | def set_value_for_pod(pod_name, value) 27 | internal_hash[pod_name] ||= [] 28 | internal_hash[pod_name] << value 29 | end 30 | 31 | def parse_options(name, requirements) 32 | options = requirements.last 33 | return requirements unless options.is_a?(Hash) 34 | 35 | should_enable = options.delete(key) 36 | pod_name = Specification.root_name(name) 37 | set_value_for_pod(pod_name, should_enable) 38 | 39 | requirements.pop if options.empty? 40 | end 41 | end 42 | 43 | class TargetDefinition 44 | def sled_option_binary 45 | @sled_option_binary = SLEDPodOptionsItem.new(:binary, nil) unless @sled_option_binary 46 | @sled_option_binary 47 | end 48 | 49 | def sled_option_header_search_paths 50 | @sled_option_header_search_paths = SLEDPodOptionsItem.new(:hsp, nil) unless @sled_option_header_search_paths 51 | @sled_option_header_search_paths 52 | end 53 | 54 | def sled_parse_sled_options(name, requirements) 55 | sled_option_binary.parse_options(name, requirements) 56 | sled_option_header_search_paths.parse_options(name, requirements) 57 | end 58 | 59 | def sled_inhibit_warnings_if_needed(name, requirements) 60 | return requirements unless Podfile::DSL.sled_inhibit_all_warnings 61 | options = requirements.last 62 | return requirements unless options.is_a?(Hash) 63 | options[:inhibit_warnings] = true 64 | end 65 | 66 | original_parse_inhibit_warnings = instance_method(:parse_inhibit_warnings) 67 | define_method(:parse_inhibit_warnings) do |name, requirements| 68 | sled_parse_sled_options(name, requirements) 69 | sled_inhibit_warnings_if_needed(name, requirements) 70 | original_parse_inhibit_warnings.bind(self).call(name, requirements) 71 | end 72 | end 73 | end 74 | end -------------------------------------------------------------------------------- /lib/cocoapods-sled/sandbox/path_list_ext.rb: -------------------------------------------------------------------------------- 1 | require_relative '../tool' 2 | 3 | module Pod 4 | class Sandbox 5 | class PathList 6 | attr_accessor :sled_framework_cache_roots 7 | 8 | old_read_file_system = instance_method(:read_file_system) 9 | define_method(:read_file_system) do 10 | old_read_file_system.bind(self).() 11 | 12 | return if sled_framework_cache_roots.nil? || sled_framework_cache_roots.empty? 13 | sled_framework_cache_roots.each do |root| 14 | sled_read_framework_file_system(root) 15 | end 16 | end 17 | 18 | def add_cache_root(root) 19 | @sled_framework_cache_roots ||= [] 20 | @sled_framework_cache_roots << root unless @sled_framework_cache_roots.include?(root) 21 | end 22 | 23 | def sled_read_framework_file_system(path) 24 | unless path.exist? 25 | # raise Informative, "Attempt to read non existent folder `#{root}`." 26 | Pod::UI.puts "⚠️⚠️⚠️ Attempt to read non existent folder `#{path}`." 27 | return 28 | end 29 | 30 | prefix = path.relative_path_from(root) 31 | 32 | dirs = [] 33 | files = [] 34 | root_length = path.cleanpath.to_s.length + File::SEPARATOR.length 35 | escaped_root = escape_path_for_glob(path) 36 | Dir.glob(escaped_root + '**/*', File::FNM_DOTMATCH).each do |f| 37 | directory = File.directory?(f) 38 | # Ignore `.` and `..` directories 39 | next if directory && f =~ /\.\.?$/ 40 | 41 | f = f.slice(root_length, f.length - root_length) 42 | next if f.nil? 43 | 44 | (directory ? dirs : files) << (prefix + f).to_s 45 | end 46 | 47 | dirs.sort_by!(&:upcase) 48 | files.sort_by!(&:upcase) 49 | 50 | @dirs = @dirs + dirs 51 | @files = @files + files 52 | end 53 | end 54 | end 55 | end -------------------------------------------------------------------------------- /lib/cocoapods-sled/target/pod_target_ext.rb: -------------------------------------------------------------------------------- 1 | require_relative '../podfile/target_definition_ext' 2 | require 'digest' 3 | 4 | module Pod 5 | class PodTarget < Target 6 | 7 | def sled_enable_generate_header_search_paths? 8 | return @sled_enable_generate_header_search_paths if defined? @sled_enable_generate_header_search_paths 9 | whitelists = target_definitions.map do |target_definition| 10 | target_definition.sled_option_header_search_paths.value_for_pod(root_spec.name) 11 | end.flatten.uniq.reject { |element| element == nil } 12 | 13 | if whitelists.empty? 14 | @sled_enable_generate_header_search_paths = Podfile::DSL.sled_enable_generate_header_search_paths 15 | elsif whitelists.count == 1 16 | @sled_enable_generate_header_search_paths = !!whitelists.first 17 | else 18 | value = whitelists.first 19 | tmp_td = target_definitions 20 | .detect { |obj| obj.sled_option_header_search_paths.value_for_pod(root_spec.name).first == value } 21 | target_name = tmp_td == nil ? "unknown" : tmp_td.name 22 | UI.warn "The pod `#{pod_name}` is linked to different targets " \ 23 | "(#{target_definitions.map { |td| "`#{td.name}`" }.to_sentence}), which contain different " \ 24 | 'settings to hsp. CocoaPods does not currently ' \ 25 | 'support different settings and will fall back to your preference ' \ 26 | "set in the `#{target_name}` target definition" \ 27 | " :hsp => #{value}." 28 | @sled_enable_generate_header_search_paths = !!value 29 | end 30 | @sled_enable_generate_header_search_paths 31 | end 32 | 33 | def seld_ignore_binary? 34 | sled_option_binary_value == :ignore 35 | end 36 | 37 | def sled_enable_binary? 38 | sled_option_binary_value == true 39 | end 40 | 41 | def sled_option_binary_value 42 | return @sled_option_binary_value if defined? @sled_option_binary_value 43 | whitelists = target_definitions.map do |target_definition| 44 | target_definition.sled_option_binary.value_for_pod(root_spec.name) 45 | end.flatten.uniq.reject { |element| element == nil } 46 | 47 | if whitelists.empty? 48 | @sled_option_binary_value = Podfile::DSL.sled_enable_binary_cache 49 | elsif whitelists.count == 1 50 | @sled_option_binary_value = whitelists.first 51 | else 52 | value = whitelists.first 53 | tmp_td = target_definitions 54 | .detect { |obj| obj.sled_option_binary.value_for_pod(root_spec.name).first == value } 55 | target_name = tmp_td == nil ? "unknown" : tmp_td.name 56 | UI.warn "The pod `#{pod_name}` is linked to different targets " \ 57 | "(#{target_definitions.map { |td| "`#{td.name}`" }.to_sentence}), which contain different " \ 58 | "settings to binary. CocoaPods does not currently " \ 59 | 'support different settings and will fall back to your preference ' \ 60 | "set in the `#{target_name}` target definition" \ 61 | " :binary => #{value}." 62 | @sled_option_binary_value = value 63 | end 64 | @sled_option_binary_value 65 | end 66 | 67 | def sled_predownloaded? 68 | sandbox.predownloaded_pods.include?(root_spec.name) 69 | end 70 | 71 | def sled_local? 72 | sandbox.local?(root_spec.name) 73 | end 74 | 75 | def sled_released? 76 | !sled_local? && !sled_predownloaded? && sandbox.specification(root_spec.name) != root_spec 77 | end 78 | 79 | def sled_local_pod_paths 80 | paths = [] 81 | paths =file_accessors.map(&:path_list).map(&:root).uniq if sled_local? 82 | paths 83 | end 84 | 85 | def sled_slug_current 86 | case Podfile::DSL.dependency_check_type 87 | when :single 88 | sled_plain_slug 89 | when :all 90 | sled_complex_slug 91 | else 92 | sled_slug 93 | end 94 | end 95 | 96 | # 不检查依赖 97 | def sled_slug 98 | @sled_slug ||= sled_download_request.slug 99 | @sled_slug 100 | end 101 | 102 | # 检查直接依赖 103 | def sled_plain_slug 104 | if dependent_targets.empty? 105 | @sled_plain_slug ||= sled_slug 106 | else 107 | if @sled_plain_slug.nil? 108 | tmp_slug = dependent_targets.map { |t| t.sled_slug }.join("-") 109 | @sled_plain_slug = "#{sled_slug}-#{Digest::MD5.hexdigest(tmp_slug)}" 110 | end 111 | end 112 | @sled_plain_slug 113 | end 114 | 115 | # 检查全部依赖 116 | def sled_complex_slug 117 | if dependent_targets.empty? 118 | @sled_complex_slug ||= sled_slug 119 | else 120 | if @sled_complex_slug.nil? 121 | tmp_slug = dependent_targets.map { |t| t.sled_complex_slug }.join("-") 122 | @sled_complex_slug = "#{sled_slug}-#{Digest::MD5.hexdigest(tmp_slug)}" 123 | end 124 | end 125 | @sled_complex_slug 126 | end 127 | 128 | def sled_download_request 129 | if sled_local? 130 | commit_id = file_accessors 131 | .map(&:path_list) 132 | .map(&:root).uniq 133 | .map do |path| 134 | `git -C #{path} log -1 --pretty=format:%H #{path} 2>/dev/null` 135 | end 136 | .join("") 137 | 138 | if commit_id.empty? 139 | request = nil 140 | else 141 | params = { :commit => commit_id } 142 | request = Downloader::Request.new( 143 | :name => root_spec.name, 144 | :params => params, 145 | ) 146 | end 147 | elsif sandbox.checkout_sources && sandbox.checkout_sources.keys.include?(root_spec.name) 148 | params = sandbox.checkout_sources[root_spec.name] 149 | 150 | request = Downloader::Request.new( 151 | :name => root_spec.name, 152 | :params => params, 153 | ) 154 | else 155 | request = Downloader::Request.new( 156 | :spec => root_spec, 157 | :released => sled_released?, 158 | ) 159 | end 160 | 161 | request 162 | end 163 | end 164 | end -------------------------------------------------------------------------------- /lib/cocoapods-sled/tool.rb: -------------------------------------------------------------------------------- 1 | # attr_accessor for class variable. 2 | # usage: 3 | # 4 | # ``` 5 | # class Pod 6 | # class_attr_accessor :is_prebuild_stage 7 | # end 8 | # ``` 9 | # 10 | def class_attr_accessor(symbol) 11 | self.class.send(:attr_accessor, symbol) 12 | end -------------------------------------------------------------------------------- /lib/cocoapods_plugin.rb: -------------------------------------------------------------------------------- 1 | require 'cocoapods-sled/command' 2 | require 'cocoapods-sled/integration' 3 | -------------------------------------------------------------------------------- /resource/QRCode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git179979506/cocoapods-sled/94ae4e45e1a0fd1893c9746d548fb8f158d7676b/resource/QRCode.jpg --------------------------------------------------------------------------------