问题点
最近在履行完 pod install 之后,Pods 工程中的 target 头文件权限为 Project,导致编译失利。
要处理这个问题很简单,把这个头文件修改为 Public。但这个治标不治本,每次履行完 pod install 之后,这些文件又会变成 Project。
思路
想一劳永逸的处理这个问题的第一时间就想到了 cocoapods 供给了 hook 办法 post_install
,那么问题又来了,怎样找到这个这个 hook 的点呢?
网上 google 了一遍,无果,都是关于怎样修改 build_setting 或者 header search path 的。
想到 Xcode 工程所有信息都会存在 xcproject 文件中, 就打开的 pod.xcproject 文件查找相关头文件,发现了文件的描述最后有一个 ATTRIBUTE 关键字,测验修改为 Public 之后回来 Xcode 检查,果然变成了 Public。
再去 google 一下 cocoapods 怎样修改这个属性,仍是无果,再就想测验下看有没有文章系统讲下 post_install
怎样用,就找到了这片文章,www.jianshu.com/p/d8eb397b8…
尽管没有得到有用的结果,但得到了启示,installer 其实便是个目标,通过这种打印的方式太繁琐了,cocoapods 是开源的,能够直接去源码中查找
看源码
查找 public_header 相关信息,在 pod_target_installer 中找到 add_files_to_build_phases 办法
def add_files_to_build_phases(native_target, test_native_targets, app_native_targets)
target.file_accessors.each do |file_accessor|
consumer = file_accessor.spec_consumer
native_target = case consumer.spec.spec_type
when :library
native_target
when :test
test_native_target_from_spec(consumer.spec, test_native_targets)
when :app
app_native_targets[consumer.spec]
end
headers = file_accessor.headers
public_headers = file_accessor.public_headers.map(&:realpath)
private_headers = file_accessor.private_headers.map(&:realpath)
other_source_files = file_accessor.other_source_files
...
end
能够发现是通过 file_accessor 获取到的,那就看下 file_accessor 是怎样获取的
在 Sanbox::FileAccessor 中找到了如下办法
def public_headers(include_frameworks = false)
public_headers = public_header_files
private_headers = private_header_files
if public_headers.nil? || public_headers.empty?
header_files = headers
else
header_files = public_headers
end
header_files += vendored_frameworks_headers if include_frameworks
header_files - private_headers
end
再进一步检查办法调用看到逻辑比较深,就想先试试问题是不是出在这里,通过在 post_install 中打印出 public_headers 发现并无问题。
那就换个思路,看看这些文件是怎样写入到 project 文件中的,回到 pod_target_installer 的 add_files_to_build_phases 办法中
native_target.add_file_references(header_file_refs) do |build_file|
add_header(file_accessor, build_file, public_headers, private_headers, native_target)
end
看到了添加文件途径到 target 的引证中的代码,再继续检查 add_header 办法的实现
def add_header(file_accessor, build_file, public_headers, private_headers, native_target)
file_ref = build_file.file_ref
acl = if !target.build_as_framework? # Headers are already rooted at ${PODS_ROOT}/Headers/P*/[pod]/...
'Project'
elsif public_headers.include?(file_ref.real_path)
'Public'
elsif private_headers.include?(file_ref.real_path)
'Private'
else
'Project'
end
if target.build_as_framework? && !header_mappings_dir(file_accessor).nil? && acl != 'Project'
relative_path = if mapping_dir = header_mappings_dir(file_accessor)
file_ref.real_path.relative_path_from(mapping_dir)
else
file_ref.real_path.relative_path_from(file_accessor.path_list.root)
end
compile_build_phase_index = native_target.build_phases.index do |bp|
bp.is_a?(Xcodeproj::Project::Object::PBXSourcesBuildPhase)
end
sub_dir = relative_path.dirname
copy_phase_name = "Copy #{sub_dir} #{acl} Headers"
copy_phase = native_target.copy_files_build_phases.find { |bp| bp.name == copy_phase_name } ||
native_target.new_copy_files_build_phase(copy_phase_name)
native_target.build_phases.move(copy_phase, compile_build_phase_index - 1) unless compile_build_phase_index.nil?
copy_phase.symbol_dst_subfolder_spec = :products_directory
copy_phase.dst_path = "$(#{acl.upcase}_HEADERS_FOLDER_PATH)/#{sub_dir}"
copy_phase.add_file_reference(file_ref, true)
else
build_file.settings ||= {}
build_file.settings['ATTRIBUTES'] = [acl]
end
end
找到了设置 Public 仍是 Project 的关键代码 build_file.settings['ATTRIBUTES'] = [acl]
,需求获取到 native_target
,检查 native_target 的定义,发现其类为 PBXNativeTarget
,在项目中查找 class PBXNativeTarget 发现并无此类,此刻就陷入了窘境。
再想回到 installer 中看能否找到 target 的相关信息,意外的发现 pods_project 的类为 class Project < Xcodeproj::Project
,是另一 module Xcodeproj 中 Project 的子类,那么 PBXNativeTarget
是否也会定义在其中呢!
马上去 github 中查找 xcodeproj,发现的确有另一个项目 github.com/CocoaPods/X…
找到了 PBXNativeTarget,并检查了 add_file_references 的实现
def add_file_references(file_references, compiler_flags = {})
file_references.map do |file|
extension = File.extname(file.path).downcase
header_extensions = Constants::HEADER_FILES_EXTENSIONS
is_header_phase = header_extensions.include?(extension)
phase = is_header_phase ? headers_build_phase : source_build_phase
unless build_file = phase.build_file(file)
build_file = project.new(PBXBuildFile)
build_file.file_ref = file
phase.files << build_file
end
if compiler_flags && !compiler_flags.empty? && !is_header_phase
(build_file.settings ||= {}).merge!('COMPILER_FLAGS' => compiler_flags) do |_, old, new|
[old, new].compact.join(' ')
end
end
yield build_file if block_given?
build_file
end
end
发现 build_file 是定义在 PBXHeadersBuildPhase
中,而且找到了与 Xcode 中对应的几项配置
def headers_build_phase
find_or_create_build_phase_by_class(PBXHeadersBuildPhase)
end
def source_build_phase
find_or_create_build_phase_by_class(PBXSourcesBuildPhase)
end
def frameworks_build_phase
find_or_create_build_phase_by_class(PBXFrameworksBuildPhase)
end
...
至此,怎样通过 post_install 修改 Header 为 Public 就明了了
post_install do |installer|
installer.pods_project.native_targets.each do |native_target|
native_target.headers_build_phase.files.each do |file|
file.settings ||= {}
file.settings['ATTRIBUTES'] = ['Public']
end
end
end