1. 程式人生 > >原始碼篇(每次pod install之後,pods scheme 自動消失問題的解決)

原始碼篇(每次pod install之後,pods scheme 自動消失問題的解決)

前言


由於專案裡業務線很多,集成了很多第三方pod庫 和 私有pod庫,整個podproject體積非常大。預設的Xcode 編譯行為尋找依賴的project進行編譯,並且是並行的。



我們做了如下改動,加快主工程編譯速度:

.去除了主工程對 Pods target的依賴編譯
.取消上面兩個勾選
.在 Manage Scheme 裡勾選了Pods project,以便於可以手動選擇 Pods project進行編譯

如果對Pods庫更改了,我們可以手動選擇 Pods Scheme 進行編譯,然後再編譯主工程,這樣避免Pods不必要編譯。


問題

每次pod install之後,pods scheme 自動消失了,我們找到 xcschememanagement.plist

檔案

cd demo/Pods/Pods.xcodeproj/xcuserdata/Green.xcuserdatad/xcschemes
cat xcschememanagement.plist

# 輸出
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key
>
<dict> <key>Pods.xcscheme</key> <dict> <key>isShown</key> <false/> </dict> ...

可以看到:Pods.xcscheme isShown 是 false,這樣導致scheme 中沒有Pods

Pod Install 剖析

我們來看看 CocoaPods 原始碼

cd /Library/Ruby/Gems/2.0.0/gems/
#這裡有很多版本,我們只看0.38.2

當我們執行 pod install

,其實呼叫到Installer物件,

#檔案位置:`cocoapods-0.38.2/lib/cocoapods/command/project.rb`

#初始化 Installer 物件       
def run_install_with_update(update)
    installer = Installer.new(config.sandbox, config.podfile, config.lockfile)
    installer.update = update

    #install 方法
    installer.install!
  end
end
install!
#檔案位置:`cocoapods-0.38.2/lib/cocoapods/installer.rb`檔案,

  #install 方法
def install!
  prepare
  resolve_dependencies

  #下載依賴
  download_dependencies
  determine_dependency_product_types
  verify_no_duplicate_framework_names
  verify_no_static_framework_transitive_dependencies
  verify_framework_usage

  #合成 pods project
  generate_pods_project

  integrate_user_project if config.integrate_targets?
  perform_post_install_actions
end
download_dependencies
#下載pods 資源
def download_dependencies
  UI.section 'Downloading dependencies' do
    create_file_accessors
    install_pod_sources
    run_podfile_pre_install_hooks
    clean_pod_sources
  end
end
generate_pods_project
#合成 pods project
def generate_pods_project
  UI.section 'Generating Pods project' do
    prepare_pods_project
    install_file_references
    install_libraries
    set_target_dependencies

    #執行Podfile 的post_install 程式碼塊
    run_podfile_post_install_hooks

    #重新寫入pod project,就是在這裡修改了所有`pod.xcscheme` 的`isShown`為false
    write_pod_project

    share_development_pod_schemes
    write_lockfiles
  end
end
run post_install
#執行Podfile 的post_install 程式碼塊
def run_podfile_post_install_hooks
  UI.message '- Running post install hooks' do
    executed = run_podfile_post_install_hook
    UI.message '- Podfile' if executed
  end
end


def run_podfile_post_install_hook
    #執行 post_install,這個程式碼塊(block)可以在Podfile裡指定
  podfile.post_install!(self)
rescue => e
  raise Informative, 'An error occurred while processing the post-install ' \
    'hook of the Podfile.' \
    "\n\n#{e.message}\n\n#{e.backtrace * "\n"}"
end
post_install

我們經常在 Podfile 裡設定 @post_install 程式碼塊:

#檔案位置:`cocoapods-core-0.38.2/lib/cocoapods-core/podfile/dsl.rb` 檔案

  def post_install(&block)
    @post_install_callback = block
  end    
write_pod_project
# 重新寫入pod project,就是在這裡修改了所有`pod.xcscheme` 的`isShown`為false
def write_pod_project
  UI.message "- Writing Xcode project file to #{UI.path sandbox.project_path}" do
    pods_project.pods.remove_from_project if pods_project.pods.empty?
    pods_project.development_pods.remove_from_project if pods_project.development_pods.empty?
    pods_project.sort(:groups_position => :below)

    ##重新建立schemes 檔案,這裡更改了isShown
    pods_project.recreate_user_schemes(false)
    pods_project.predictabilize_uuids if config.deterministic_uuids?
    pods_project.save
  end
end  
recreate_user_schemes
  #重新建立schemes 檔案
def recreate_user_schemes(visible = true)
  schemes_dir = XCScheme.user_data_dir(path)
  FileUtils.rm_rf(schemes_dir)
  FileUtils.mkdir_p(schemes_dir)
  xcschememanagement = {}
  xcschememanagement['SchemeUserState'] = {}
  xcschememanagement['SuppressBuildableAutocreation'] = {}

  targets.each do |target|
    scheme = XCScheme.new
    scheme.add_build_target(target)
    scheme.save_as(path, target.name, false)
    xcschememanagement['SchemeUserState']["#{target.name}.xcscheme"] = {}

    #就是在這裡修改的。
    xcschememanagement['SchemeUserState']["#{target.name}.xcscheme"]['isShown'] = visible
  end

  xcschememanagement_path = schemes_dir + 'xcschememanagement.plist'
  Xcodeproj.write_plist(xcschememanagement, xcschememanagement_path)
end

這樣我們找到了根本問題,其實還是底層做了限制,每次 pod install 會重新生成 scheme 檔案,並且每個 pod target 的 isShown 都是 false 。

Podfile 剖析

Podfile 其實是個 Ruby 類,對應 Cocoapods 的 Podfile class,我們可以看看Podfile class原始碼:

# 檔案位置:/Library/Ruby/Gems/2.0.0/gems/cocoapods-core-0.38.2/lib/cocoapods-core/podfile/dsl.rb

module Pod 
 class Podfile
 module DSL
 ...
platform

指定 Pods target 的 platform

# @!group Target configuration
#   These settings are used to control the  CocoaPods generated project.
#
#   This starts out simply with stating what `platform` you are working  on. `xcodeproj` allows you to state specifically which project to link with.

# Specifies the platform for which a static library should be built.
#
# CocoaPods provides a default deployment target if one is not specified.
# The current default values are `4.3` for iOS, `10.6` for OS X and `2.0` for watchOS.
#
# If the deployment target requires it (iOS < `4.3`), `armv6` architecture will be added to `ARCHS`.
#
# @param    [Symbol] name
#           the name of platform, can be either `:osx` for OS X, `:ios`
#           for iOS or `:watchos` for watchOS.
#
# @param    [String, Version] target
#           The optional deployment.  If not provided a default value
#           according to the platform name will be assigned.
#
# @example  Specifying the platform
#
#           platform :ios, "4.0"
#           platform :ios
#
# @return   [void]

def platform(name, target = nil)
  # Support for deprecated options parameter
  target = target[:deployment_target] if target.is_a?(Hash)
  current_target_definition.set_platform(name, target)
end
xcodeproj

指定 Pods libraries 可以被哪個 project 連結。

# @Specifies the Xcode project that contains the target that the Pods library should be linked with.
# 
# @param    [String] path
#           the path of the project to link with
#
# @param    [Hash{String => symbol}] build_configurations
#           a hash where the keys are the name of the build
#           configurations in your Xcode project and the values are
#           Symbols that specify if the configuration should be based on
#           the `:debug` or `:release` configuration. If no explicit
#           mapping is specified for a configuration in your project, it
#           will default to `:release`.
#
# @example  Specifying the user project
#
#           # Look for target to link with in an Xcode project called
#           # `MyProject.xcodeproj`.
#           xcodeproj 'MyProject'
#
#           target :test do
#             # This Pods library links with a target in another project.
#             xcodeproj 'TestProject'
#           end
#
# @example  Using custom build configurations
#
#           xcodeproj 'TestProject', 'Mac App Store' => :release, 'Test' => :debug
#
#
# @return   [void]


def xcodeproj(path, build_configurations = {})
  current_target_definition.user_project_path = path
  current_target_definition.build_configurations = build_configurations
end
inhibit_all_warnings

指定 是否需要忽略警告

# @Inhibits **all** the warnings from the CocoaPods libraries.
#
#
# This attribute is inherited by child target definitions.
#
# If you would like to inhibit warnings per Pod you can use the following syntax:
#
#     pod 'SSZipArchive', :inhibit_warnings => true

def inhibit_all_warnings!
  current_target_definition.inhibit_all_warnings = true
end
use_frameworks

指定是否使用 framework

# @Use frameworks instead of static libraries for Pods.
#
# ------
#
# This attribute is inherited by child target definitions.
#    

def use_frameworks!(flag = true)
  current_target_definition.use_frameworks!(flag)
end
workspace

指定 合成的 workspace 路徑

# @!group Workspace
#
#   This group list the options to configure workspace and to set global settings.
# Specifies the Xcode workspace that should contain all the projects.
#
# -----
#
# If no explicit Xcode workspace is specified and only **one** project
# exists in the same directory as the Podfile, then the name of that project is used as the workspace’s name.
#
# @param    [String] path
#           path of the workspace.
#
# @example  Specifying a workspace
#
#           workspace 'MyWorkspace'
#
# @return   [void]


def workspace(path)
  set_hash_value('workspace', path.to_s)
end
source

指定 specs 倉庫源

# @!group Sources
#
#   The Podfile retrieves specs from a given list of sources (repositories).
#
#   Sources are __global__ and they are not stored per target definition.
# Specifies the location of specs
#
# -----
#
# Use this method to specify sources. The order of the sources is
# relevant. CocoaPods will use the highest version of a Pod of the first
# source which includes the Pod (regardless whether other sources have a
# higher version).
#
# @param    [String] source
#           The URL of a specs repository.
#
# @example  Specifying to first use the Artsy repository and then the
#           CocoaPods Master Repository
#
#           source 'https://github.com/artsy/Specs.git'
#           source 'https://github.com/CocoaPods/Specs.git'
#
# @return   [void]

def source(source)
  hash_sources = get_hash_value('sources') || []
  hash_sources << source
  set_hash_value('sources', hash_sources.uniq)
end
post_install

設定 installer 之後的執行的程式碼塊

# This hook allows you to make any last changes to the generated Xcode
# project before it is written to disk, or any other tasks you might want to perform.
#
# It receives the [`Pod::Installer`](http://rubydoc.info/gems/cocoapods/Pod/Installer/) as its only argument.
#
# @example  Customising the build settings of all targets
#
#   post_install do |installer|
#     installer.pods_project.targets.each do |target|
#       target.build_configurations.each do |config|
#         config.build_settings['GCC_ENABLE_OBJC_GC'] = 'supported'
#       end
#     end
#   end
#
# @return   [void]
#

def post_install(&block)
  @post_install_callback = block
end

Podfile 自定義

我們在Podfile中增加如下程式碼:

#設定 Podfile 物件 @post_install_callback 成員
self.post_install do |installer|
$KDPod_Project
begin
    $KDPod_Project=installer.project
    rescue
    puts "installer.project is delete"
    $KDPod_Project=installer.pods_project
end
installer.use_default_plugins = false
$KDPod_Project.targets.each do |target|
    #設定ORGANIZATIONNAME 、 CLASSPREFIX
    #target.project.root_object.attributes['ORGANIZATIONNAME']='xxxxx.xxx'
    #target.project.root_object.attributes['CLASSPREFIX']='xxxx'
    target.build_configurations.each do |config|

        #設定target的編譯後生成目錄
        config.build_settings['CONFIGURATION_TEMP_DIR'] = './build'
        config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO'
    end
end

# 延遲執行
def kdperformSelector(time)
    before=Time.now
    fork do
        sleep(1) until Time.now-before >= time
        yield
    end
end


#延遲3秒 更改pods scheme visabled
kdperformSelector(3){
    ##update pod visabled
    $schemes_dir = Xcodeproj::XCScheme.user_data_dir($KDPod_Project.path)
    $xcschememanagement_path = $schemes_dir + 'xcschememanagement.plist'
    $xcschememanagement_content = Xcodeproj.read_plist($xcschememanagement_path)

    #設定 isShown 屬性
    $xcschememanagement_content['SchemeUserState']["Pods.xcscheme"]['isShown'] = true
    FileUtils.rm_rf($xcschememanagement_path)
    Xcodeproj.write_plist($xcschememanagement_content,$xcschememanagement_path)
    #    puts $xcschememanagement_content
    #    $KDPod_Project.recreate_user_schemes(true)
    $KDPod_Project.save
    puts "KDPod_Project.save"
}
end

我們新for一個程序,在 download_dependencies 之後 延遲了3秒執行更新 pods.xschemeisShown 屬性。
這樣就可以顯示Pods Scheme了。

這裡只是做個例子,重要的是,瞭解了原始碼,我們就可以對project進行其他配置。