cocoapods-privacy 0.0.1
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 +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +11 -0
- data/lib/cocoapods-privacy/command/install.rb +34 -0
- data/lib/cocoapods-privacy/command/privacy/config.rb +64 -0
- data/lib/cocoapods-privacy/command/privacy/install.rb +36 -0
- data/lib/cocoapods-privacy/command/privacy/spec.rb +27 -0
- data/lib/cocoapods-privacy/command/privacy.rb +20 -0
- data/lib/cocoapods-privacy/command.rb +13 -0
- data/lib/cocoapods-privacy/gem_version.rb +3 -0
- data/lib/cocoapods-privacy/privacy/PrivacyConfig.rb +28 -0
- data/lib/cocoapods-privacy/privacy/PrivacyHunter.rb +180 -0
- data/lib/cocoapods-privacy/privacy/PrivacyModule.rb +296 -0
- data/lib/cocoapods-privacy/privacy/PrivacyUtils.rb +124 -0
- data/lib/cocoapods-privacy/privacy/privacy_installer_hook.rb +123 -0
- data/lib/cocoapods-privacy/privacy/privacy_specification_hook.rb +53 -0
- data/lib/cocoapods-privacy.rb +1 -0
- data/lib/cocoapods_plugin.rb +1 -0
- data/spec/command/privacy_spec.rb +12 -0
- data/spec/spec_helper.rb +50 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 75f110acdd889169f011bfa5508d1941cdaccf78ce5dcb43bc51dd79236e793f
|
4
|
+
data.tar.gz: 9db44f1ca04a5991000862935336ba4d1ebbb96508c2e6b2d0877a5d21f5f1d5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: adca9245bb2e2feef01acf8f5215f933b3bd89a97cfd279494e9d0c0f6edc92b82d2daf1b86e8b439e5c7ae1ce0746cb3b67100fdcc23c390ee339bee56d8d60
|
7
|
+
data.tar.gz: a313ab80c11f3479da5f173de9179aae6498d677289e05e0674ffecb089aa6c7905856c061539a5cb1d9add0c418959362edef520a14c2386566ef64fb11a1cc
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2024 youhui <youhui@babybus.com>
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'cocoapods-privacy/command'
|
2
|
+
|
3
|
+
module Pod
|
4
|
+
class Config
|
5
|
+
attr_accessor :privacy_folds
|
6
|
+
attr_accessor :is_privacy
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Pod
|
11
|
+
class Command
|
12
|
+
class Install < Command
|
13
|
+
class << self
|
14
|
+
alias_method :origin_options, :options
|
15
|
+
def options
|
16
|
+
[
|
17
|
+
['--privacy', '使用该参数,会自动生成并更新PrivacyInfo.xcprivacy'],
|
18
|
+
['--privacy-folds=folds', '指定文件夹检索,多个文件夹使用逗号","分割'],
|
19
|
+
].concat(origin_options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :privacy_origin_initialize, :initialize
|
24
|
+
def initialize(argv)
|
25
|
+
privacy_folds = argv.option('privacy-folds', '').split(',')
|
26
|
+
is_privacy = argv.flag?('privacy',false)
|
27
|
+
privacy_origin_initialize(argv)
|
28
|
+
instance = Pod::Config.instance
|
29
|
+
instance.privacy_folds = privacy_folds
|
30
|
+
instance.is_privacy = is_privacy
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'cocoapods-privacy/command'
|
2
|
+
|
3
|
+
module Pod
|
4
|
+
class Command
|
5
|
+
class Privacy < Command
|
6
|
+
class Config < Privacy
|
7
|
+
self.summary = '初始化隐私清单配置'
|
8
|
+
|
9
|
+
self.description = <<-DESC
|
10
|
+
初始化隐私清单配置,包含必须的隐私api模版,和source 黑白名单等,配置文件格式详细见 #{"https://github.com/ymoyao/cocoapods-privacy"}
|
11
|
+
DESC
|
12
|
+
|
13
|
+
def initialize(argv)
|
14
|
+
@config = argv.shift_argument
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate!
|
19
|
+
super
|
20
|
+
help! 'A config url is required.' unless @config
|
21
|
+
raise Informative, "配置文件格式不是 JSON,请检查配置#{@config}" unless @config.end_with?(".json")
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
load_config_file()
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_config_file
|
29
|
+
# 检查 @config 是远程 URL 还是本地文件路径
|
30
|
+
if @config.start_with?('http')
|
31
|
+
download_remote_config
|
32
|
+
else
|
33
|
+
copy_local_config
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def download_remote_config
|
38
|
+
# 配置文件目录
|
39
|
+
cache_config_file = PrivacyUtils.cache_config_file
|
40
|
+
|
41
|
+
# 开始下载
|
42
|
+
system("curl -o #{cache_config_file} #{@config}")
|
43
|
+
|
44
|
+
if File.exist?(cache_config_file)
|
45
|
+
puts "配置文件已下载到: #{cache_config_file}"
|
46
|
+
else
|
47
|
+
raise Informative, "配置文件下载出错,请检查下载地址#{@config}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def copy_local_config
|
52
|
+
# 配置文件目录
|
53
|
+
cache_config_file = PrivacyUtils.cache_config_file
|
54
|
+
|
55
|
+
# 复制本地文件
|
56
|
+
FileUtils.cp(@config, cache_config_file)
|
57
|
+
|
58
|
+
puts "配置文件已复制到: #{cache_config_file}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Pod
|
2
|
+
class Command
|
3
|
+
class Privacy < Command
|
4
|
+
class Install < Privacy
|
5
|
+
self.summary = '在工程中创建对应隐私清单文件'
|
6
|
+
|
7
|
+
self.description = <<-DESC
|
8
|
+
1、在工程Resources 文件夹下创建隐私清单文件
|
9
|
+
2、搜索对应组件,补全隐私Api部分
|
10
|
+
3、只处理隐私Api部分,隐私权限相关需要自行处理!!!
|
11
|
+
DESC
|
12
|
+
|
13
|
+
def self.options
|
14
|
+
[
|
15
|
+
["--folds=folds", '传入自定义搜索文件夹,多个文件目录使用“,”分割'],
|
16
|
+
].concat(super)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(argv)
|
20
|
+
@folds = argv.option('folds', '').split(',')
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
installer = installer_for_config
|
26
|
+
installer.repo_update = false
|
27
|
+
installer.update = false
|
28
|
+
installer.deployment = false
|
29
|
+
installer.clean_install = false
|
30
|
+
installer.privacy_analysis(@folds)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Pod
|
2
|
+
class Command
|
3
|
+
class Privacy < Command
|
4
|
+
class Spec < Privacy
|
5
|
+
self.summary = '根据 podspec 创建对应隐私清单文件'
|
6
|
+
|
7
|
+
self.description = <<-DESC
|
8
|
+
根据podspec 创建对应隐私清单文件,并自动修改podspec文件,以映射对应隐私清单文件。
|
9
|
+
DESC
|
10
|
+
|
11
|
+
self.arguments = [
|
12
|
+
CLAide::Argument.new('podspec_file', false, true),
|
13
|
+
]
|
14
|
+
|
15
|
+
def initialize(argv)
|
16
|
+
@podspec_file = argv.arguments!.first
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
PrivacyModule.load_module(@podspec_file)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Pod
|
2
|
+
class Command
|
3
|
+
class Privacy < Command
|
4
|
+
|
5
|
+
def initialize(argv)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
if PrivacyUtils.isMainProject
|
11
|
+
puts "检测到#{PrivacyUtils.project_path || ""}工程文件, 请使用 pod privacy install 对工程进行隐私清单创建和自动检索"
|
12
|
+
elsif PrivacyUtils.podspec_file_path
|
13
|
+
puts "检测到#{PrivacyUtils.podspec_file_path || ""} 组件, 请使用 pod privacy spec 对组件进行隐私清单创建和自动检索"
|
14
|
+
else
|
15
|
+
puts "未检测到工程或podspec 文件, 请切换到工程或podspec文件目录下再次执行命令"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'cocoapods-privacy/command/privacy'
|
2
|
+
require 'cocoapods-privacy/command/privacy/config'
|
3
|
+
require 'cocoapods-privacy/command/privacy/install'
|
4
|
+
require 'cocoapods-privacy/command/privacy/spec'
|
5
|
+
require 'cocoapods-privacy/command/install'
|
6
|
+
|
7
|
+
require 'cocoapods-privacy/privacy/privacy_specification_hook'
|
8
|
+
require 'cocoapods-privacy/privacy/privacy_installer_hook'
|
9
|
+
require 'cocoapods-privacy/privacy/PrivacyUtils'
|
10
|
+
require 'cocoapods-privacy/privacy/PrivacyModule'
|
11
|
+
require 'cocoapods-privacy/privacy/PrivacyHunter'
|
12
|
+
require 'cocoapods-privacy/privacy/PrivacyConfig'
|
13
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'cocoapods-privacy/command'
|
2
|
+
|
3
|
+
module Privacy
|
4
|
+
class Config
|
5
|
+
|
6
|
+
def initialize()
|
7
|
+
config_content = File.read(PrivacyUtils.cache_config_file)
|
8
|
+
@json = JSON.parse(config_content)
|
9
|
+
end
|
10
|
+
|
11
|
+
def api_template_url
|
12
|
+
return @json['api.template.url'] || ""
|
13
|
+
end
|
14
|
+
|
15
|
+
def source_black_list
|
16
|
+
return @json['source.black.list'] || []
|
17
|
+
end
|
18
|
+
|
19
|
+
def source_white_list
|
20
|
+
return @json['source.white.list'] || []
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.instance
|
24
|
+
@instance ||= new
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'cocoapods-privacy/command'
|
3
|
+
|
4
|
+
##
|
5
|
+
# 功能介绍:
|
6
|
+
# 1、检测本地隐私协议清单模版是否最新,如果不存在或不是最新,那么下载远端隐私协议模版
|
7
|
+
# 2、使用模版对相关文件夹进行检索
|
8
|
+
# 3、检索到的内容转换成隐私协议格式写入 隐私清单文件 PrivacyInfo.xcprivacy
|
9
|
+
##
|
10
|
+
module PrivacyHunter
|
11
|
+
KTypes = "NSPrivacyAccessedAPITypes"
|
12
|
+
KType = "NSPrivacyAccessedAPIType"
|
13
|
+
KReasons = "NSPrivacyAccessedAPITypeReasons"
|
14
|
+
KAPI = "NSPrivacyAccessedAPI"
|
15
|
+
|
16
|
+
# source_files = ARGV[0]#传入source文件路径,如有多个使用 “,” 逗号分割
|
17
|
+
# privacyInfo_file = ARGV[1]#传入目标 PrivacyInfo.xcprivacy
|
18
|
+
|
19
|
+
def self.search_pricacy_apis(source_folders)
|
20
|
+
# #读取源文件,也就是搜索目标文件
|
21
|
+
# source_folders = source_files.split(",")
|
22
|
+
#模版数据源plist文件
|
23
|
+
template_plist_file = fetch_template_plist_file()
|
24
|
+
|
25
|
+
# 读取并解析 数据源 plist 文件
|
26
|
+
json_str = `plutil -convert json -o - "#{template_plist_file}"`.chomp
|
27
|
+
map = JSON.parse(json_str)
|
28
|
+
arr = map[KTypes]
|
29
|
+
|
30
|
+
#解析并按照API模版查询指定文件夹
|
31
|
+
privacyArr = []
|
32
|
+
arr.each do |value|
|
33
|
+
privacyDict = {}
|
34
|
+
type = value[KType]
|
35
|
+
reasons = []
|
36
|
+
apis = value[KAPI]
|
37
|
+
apis.each do |s_key, s_value|
|
38
|
+
if search_files(source_folders, s_key)
|
39
|
+
s_vlaue_split = s_value.split(',')
|
40
|
+
reasons += s_vlaue_split
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#按照隐私清单拼接数据
|
45
|
+
reasons = reasons.uniq
|
46
|
+
if !reasons.empty?
|
47
|
+
privacyDict[KType] = type
|
48
|
+
privacyDict[KReasons] = reasons
|
49
|
+
privacyArr.push(privacyDict)
|
50
|
+
end
|
51
|
+
# puts "type: #{type}"
|
52
|
+
# puts "reasons: #{reasons.uniq}"
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
# 打印出搜索结果
|
57
|
+
puts privacyArr
|
58
|
+
|
59
|
+
# 转换成 JSON 字符串
|
60
|
+
json_data = privacyArr.to_json
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def self.write_to_privacy(json_data,privacy_path)
|
65
|
+
# 转换 JSON 为 plist 格式
|
66
|
+
plist_data = `echo '#{json_data}' | plutil -convert xml1 - -o -`
|
67
|
+
|
68
|
+
# 创建临时文件
|
69
|
+
temp_plist = File.join(PrivacyUtils.cache_privacy_fold,"#{PrivacyUtils.to_md5(privacy_path)}.plist")
|
70
|
+
File.write(temp_plist, plist_data)
|
71
|
+
|
72
|
+
# 获取原先文件中的 NSPrivacyAccessedAPITypes 数据
|
73
|
+
origin_privacy_data = `/usr/libexec/PlistBuddy -c 'Print :NSPrivacyAccessedAPITypes' '#{privacy_path}' 2>/dev/null`
|
74
|
+
new_privacy_data = `/usr/libexec/PlistBuddy -c 'Print' '#{temp_plist}'`
|
75
|
+
|
76
|
+
# 检查新数据和原先数据是否一致
|
77
|
+
if origin_privacy_data.strip == new_privacy_data.strip
|
78
|
+
puts "NSPrivacyAccessedAPITypes 数据一致,无需插入。"
|
79
|
+
else
|
80
|
+
unless origin_privacy_data.strip.empty?
|
81
|
+
# 删除 :NSPrivacyAccessedAPITypes 键
|
82
|
+
system("/usr/libexec/PlistBuddy -c 'Delete :NSPrivacyAccessedAPITypes' '#{privacy_path}'")
|
83
|
+
end
|
84
|
+
|
85
|
+
# 添加 :NSPrivacyAccessedAPITypes 键并设置为数组
|
86
|
+
system("/usr/libexec/PlistBuddy -c 'Add :NSPrivacyAccessedAPITypes array' '#{privacy_path}'")
|
87
|
+
|
88
|
+
# 合并 JSON 数据到隐私文件
|
89
|
+
system("/usr/libexec/PlistBuddy -c 'Merge #{temp_plist} :NSPrivacyAccessedAPITypes' '#{privacy_path}'")
|
90
|
+
|
91
|
+
puts "NSPrivacyAccessedAPITypes 数据已插入。"
|
92
|
+
end
|
93
|
+
|
94
|
+
# 删除临时文件
|
95
|
+
File.delete(temp_plist)
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
|
103
|
+
def self.fetch_template_plist_file
|
104
|
+
|
105
|
+
unless File.exist?(PrivacyUtils.cache_config_file)
|
106
|
+
raise Informative, "无配置文件,run `pod privacy config config_file' 进行配置"
|
107
|
+
end
|
108
|
+
|
109
|
+
template_url = Privacy::Config.instance.api_template_url
|
110
|
+
unless template_url && !template_url.empty?
|
111
|
+
raise Informative, "配置文件中无 `api.template.url` 配置,请补全后再更新配置 `pod privacy config config_file` "
|
112
|
+
end
|
113
|
+
|
114
|
+
# 目标文件路径
|
115
|
+
local_file_path = File.join(PrivacyUtils.cache_privacy_fold, 'NSPrivacyAccessedAPITypes.plist')
|
116
|
+
|
117
|
+
# 获取远程文件更新时间
|
118
|
+
remote_file_time = remoteFileTime?(template_url)
|
119
|
+
|
120
|
+
# 判断本地文件的最后修改时间是否与远端文件一致,如果一致则不进行下载
|
121
|
+
if File.exist?(local_file_path) && file_identical?(local_file_path, remote_file_time)
|
122
|
+
puts "本地文件与远端文件一致,无需下载。文件路径: #{local_file_path}"
|
123
|
+
else
|
124
|
+
# 使用 curl 下载文件
|
125
|
+
system("curl -o #{local_file_path} #{template_url}")
|
126
|
+
puts "文件已下载到: #{local_file_path}"
|
127
|
+
|
128
|
+
# 同步远程文件时间到本地文件
|
129
|
+
syncFileTime?(local_file_path,remote_file_time)
|
130
|
+
end
|
131
|
+
|
132
|
+
local_file_path
|
133
|
+
end
|
134
|
+
|
135
|
+
# 获取远程文件更新时间
|
136
|
+
def self.remoteFileTime?(remote_url)
|
137
|
+
uri = URI.parse(remote_url)
|
138
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
139
|
+
http.use_ssl = (uri.scheme == 'https')
|
140
|
+
response = http.request_head(uri.path)
|
141
|
+
|
142
|
+
response['Last-Modified']
|
143
|
+
end
|
144
|
+
|
145
|
+
# 判断本地文件的最后修改时间与远端文件的最后修改时间是否一致
|
146
|
+
def self.file_identical?(local_file_path, remote_file_time)
|
147
|
+
remote_file_time && Time.parse(remote_file_time) == File.mtime(local_file_path)
|
148
|
+
end
|
149
|
+
|
150
|
+
# 同步远程文件时间到本地文件
|
151
|
+
def self.syncFileTime?(local_file_path, remote_file_time)
|
152
|
+
File.utime(File.atime(local_file_path), Time.parse(remote_file_time), local_file_path)
|
153
|
+
end
|
154
|
+
|
155
|
+
# 文件是否包含内容
|
156
|
+
def self.contains_keyword?(file_path, keyword)
|
157
|
+
File.read(file_path).include? keyword
|
158
|
+
end
|
159
|
+
|
160
|
+
#搜索所有子文件夹
|
161
|
+
def self.search_files(folder_paths, keyword)
|
162
|
+
|
163
|
+
# 获取文件夹下所有文件(包括子文件夹)
|
164
|
+
all_files = []
|
165
|
+
folder_paths.each do |folder|
|
166
|
+
allowed_extensions = ['m', 'c', 'swift', 'mm', 'hap', 'cpp']
|
167
|
+
pattern = File.join(folder, '**', '*.{'+allowed_extensions.join(',')+'}')
|
168
|
+
all_files += Dir.glob(pattern, File::FNM_DOTMATCH).reject { |file| File.directory?(file) }
|
169
|
+
end
|
170
|
+
# 遍历文件进行检索
|
171
|
+
all_files.uniq.each_with_index do |file_path, index|
|
172
|
+
if contains_keyword?(file_path, keyword)
|
173
|
+
puts "File #{file_path} contains the keyword '#{keyword}'."
|
174
|
+
return true
|
175
|
+
end
|
176
|
+
end
|
177
|
+
return false
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
@@ -0,0 +1,296 @@
|
|
1
|
+
require 'cocoapods-privacy/command'
|
2
|
+
require 'cocoapods-core/specification/dsl/attribute_support'
|
3
|
+
require 'cocoapods-core/specification/dsl/attribute'
|
4
|
+
require 'xcodeproj'
|
5
|
+
|
6
|
+
class BBRow
|
7
|
+
attr_accessor :content, :is_comment, :is_spec_start, :is_spec_end, :key, :value
|
8
|
+
|
9
|
+
def initialize(content, is_comment=false, is_spec_start=false, is_spec_end=false)
|
10
|
+
@content = content
|
11
|
+
@is_comment = is_comment
|
12
|
+
@is_spec_start = is_spec_start
|
13
|
+
@is_spec_end = is_spec_end
|
14
|
+
|
15
|
+
parse_key_value
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_key_value
|
19
|
+
# 在这里添加提取 key 和 value 的逻辑
|
20
|
+
if @content.include?('=')
|
21
|
+
key_value_split = @content.split('=')
|
22
|
+
@key = key_value_split[0]
|
23
|
+
@value = key_value_split[1..-1].join('=')
|
24
|
+
else
|
25
|
+
@key = nil
|
26
|
+
@value = nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class BBSpec
|
32
|
+
attr_accessor :name, :alias_name, :full_name, :rows, :privacy_sources, :privacy_file
|
33
|
+
|
34
|
+
def initialize(name,alias_name,full_name)
|
35
|
+
@rows = []
|
36
|
+
@privacy_sources = []
|
37
|
+
@name = name
|
38
|
+
@alias_name = alias_name
|
39
|
+
@full_name = full_name
|
40
|
+
@privacy_file = "Pod/Privacy/#{full_name}/PrivacyInfo.xcprivacy"
|
41
|
+
end
|
42
|
+
|
43
|
+
def privacy_handle(podspec_file_path)
|
44
|
+
@rows.each_with_index do |line, index|
|
45
|
+
if !line || line.is_a?(BBSpec) || !line.key || line.key.empty?
|
46
|
+
next
|
47
|
+
end
|
48
|
+
|
49
|
+
if !line.is_comment && line.key.include?(".resource_bundle")
|
50
|
+
@has_resource_bundle = true
|
51
|
+
elsif !line.is_comment && line.key.include?(".source_files")
|
52
|
+
spec = eval("Pod::Spec.new do |s|; s.source_files = #{line.value}; end;")
|
53
|
+
if spec && !spec.attributes_hash['source_files'].nil?
|
54
|
+
source_files_value = spec.attributes_hash['source_files']
|
55
|
+
if source_files_value.is_a?(String)
|
56
|
+
source_files_array = [source_files_value]
|
57
|
+
elsif source_files_value.is_a?(Array)
|
58
|
+
# 如果已经是数组,直接使用
|
59
|
+
source_files_array = source_files_value
|
60
|
+
else
|
61
|
+
# 其他情况,默认为空数组
|
62
|
+
source_files_array = []
|
63
|
+
end
|
64
|
+
|
65
|
+
@privacy_sources = source_files_array.map do |file_path|
|
66
|
+
File.join(File.dirname(podspec_file_path), file_path.strip)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
create_privacy_file_if_need(podspec_file_path)
|
72
|
+
modify_privacy_resource_bundle_if_need()
|
73
|
+
end
|
74
|
+
|
75
|
+
# 对应Spec新增隐私文件
|
76
|
+
def create_privacy_file_if_need(podspec_file_path)
|
77
|
+
if !@privacy_sources.empty?
|
78
|
+
PrivacyUtils.create_privacy_if_empty(File.join(File.dirname(podspec_file_path), @privacy_file))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# 把新增的隐私文件 映射给 podspec
|
83
|
+
def modify_privacy_resource_bundle_if_need
|
84
|
+
if !@privacy_sources.empty?
|
85
|
+
privacy_resource_bundle = { "#{full_name}.privacy" => @privacy_file }
|
86
|
+
if @has_resource_bundle
|
87
|
+
@rows.each_with_index do |line, index|
|
88
|
+
if !line || line.is_a?(BBSpec) || !line.key || line.key.empty?
|
89
|
+
next
|
90
|
+
end
|
91
|
+
|
92
|
+
if !line.is_comment && line.key.include?(".resource_bundle")
|
93
|
+
origin_resource_bundle = eval(line.value)
|
94
|
+
merged_resource_bundle = origin_resource_bundle.merge(privacy_resource_bundle)
|
95
|
+
|
96
|
+
@resource_bundle = merged_resource_bundle
|
97
|
+
line.value = merged_resource_bundle
|
98
|
+
line.content = "#{line.key}= #{line.value}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
else
|
102
|
+
space = PrivacyUtils.count_spaces_before_first_character(rows.first.content)
|
103
|
+
line = "#{alias_name}.resource_bundle = #{privacy_resource_bundle}"
|
104
|
+
line = PrivacyUtils.add_spaces_to_string(line,space + 2)
|
105
|
+
row = BBRow.new(line)
|
106
|
+
@rows.insert(1, row)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
module PrivacyModule
|
114
|
+
|
115
|
+
public
|
116
|
+
|
117
|
+
# 处理工程
|
118
|
+
def self.load_project(folds)
|
119
|
+
project_path = PrivacyUtils.project_path()
|
120
|
+
resources_folder_path = File.join(File.basename(project_path, File.extname(project_path)),'Resources')
|
121
|
+
privacy_file_path = File.join(resources_folder_path,PrivacyUtils.privacy_name)
|
122
|
+
# 如果隐私文件不存在,创建隐私协议模版
|
123
|
+
unless File.exist?(privacy_file_path)
|
124
|
+
PrivacyUtils.create_privacy_if_empty(privacy_file_path)
|
125
|
+
end
|
126
|
+
|
127
|
+
# 如果没有隐私文件,那么新建一个添加到工程中
|
128
|
+
# 打开 Xcode 项目,在Resources 下创建
|
129
|
+
project = Xcodeproj::Project.open(File.basename(project_path))
|
130
|
+
main_group = project.main_group
|
131
|
+
resources_group = PrivacyUtils.find_group_by_path(main_group,resources_folder_path)
|
132
|
+
if resources_group.nil?
|
133
|
+
resources_group = main_group.new_group('Resources',resources_folder_path)
|
134
|
+
end
|
135
|
+
|
136
|
+
# 如果不存在引用,创建新的引入xcode引用
|
137
|
+
if resources_group.find_file_by_path(PrivacyUtils.privacy_name).nil?
|
138
|
+
resources_group.new_reference(PrivacyUtils.privacy_name)
|
139
|
+
# resources_group.new_file(privacy_file_path)
|
140
|
+
end
|
141
|
+
|
142
|
+
project.save
|
143
|
+
|
144
|
+
# 开始检索api,并返回json 字符串数据
|
145
|
+
json_data = PrivacyHunter.search_pricacy_apis(folds)
|
146
|
+
|
147
|
+
# 将数据写入隐私清单文件
|
148
|
+
PrivacyHunter.write_to_privacy(json_data,privacy_file_path)
|
149
|
+
end
|
150
|
+
|
151
|
+
# 处理组件
|
152
|
+
def self.load_module(podspec_file)
|
153
|
+
podspec_file_path = podspec_file ? podspec_file : PrivacyUtils.podspec_file_path
|
154
|
+
unless podspec_file_path && !podspec_file_path.empty?
|
155
|
+
raise Informative, "no podspec file were found, please run `pod privacy podspec_file_path`"
|
156
|
+
end
|
157
|
+
|
158
|
+
privacy_hash = PrivacyModule.check(podspec_file_path)
|
159
|
+
privacy_hash.each do |privacy_file_path, source_files|
|
160
|
+
data = PrivacyHunter.search_pricacy_apis(source_files)
|
161
|
+
PrivacyHunter.write_to_privacy(data,privacy_file_path) unless data.empty?
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.check(podspec_file_path)
|
166
|
+
# Step 1: 读取podspec
|
167
|
+
lines = read_podspec(podspec_file_path)
|
168
|
+
|
169
|
+
# Step 2: 逐行解析并转位BBRow 模型
|
170
|
+
rows = parse_row(lines)
|
171
|
+
|
172
|
+
# Step 3.1:如果Row 是属于Spec 内,那么聚拢成BBSpec,
|
173
|
+
# Step 3.2:BBSpec 内使用数组存储其Spec 内的行
|
174
|
+
# Step 3.3 在合适位置给每个有效的spec都创建一个 隐私模版,并修改其podspec 引用
|
175
|
+
combin_sepcs_and_rows = combin_sepc_if_need(rows,podspec_file_path)
|
176
|
+
|
177
|
+
# Step 4: 展开修改后的Spec,重新转换成 BBRow
|
178
|
+
rows = unfold_sepc_if_need(combin_sepcs_and_rows)
|
179
|
+
|
180
|
+
# Step 5: 打开隐私模版,并修改其podspec文件,并逐行写入
|
181
|
+
File.open(podspec_file_path, 'w') do |file|
|
182
|
+
# 逐行写入 rows
|
183
|
+
rows.each do |row|
|
184
|
+
file.puts(row.content)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
# Step 6: 获取privacy 相关信息,传递给后续处理
|
190
|
+
privacy_hash = fetch_privacy_hash(combin_sepcs_and_rows,podspec_file_path)
|
191
|
+
filtered_privacy_hash = privacy_hash.reject { |_, value| value.empty? }
|
192
|
+
filtered_privacy_hash
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
def self.read_podspec(file_path)
|
197
|
+
File.readlines(file_path)
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.parse_row(lines)
|
201
|
+
rows = []
|
202
|
+
if_stack = [] #排除if end 干扰
|
203
|
+
lines.each do |line|
|
204
|
+
content = line.strip
|
205
|
+
is_comment = content.start_with?('#')
|
206
|
+
is_spec_start = !is_comment && (content.include?('Pod::Spec.new') || content.include?('.subspec'))
|
207
|
+
is_if = !is_comment && content.start_with?('if')
|
208
|
+
is_end = !is_comment && content.start_with?('end')
|
209
|
+
# 排除if end 对spec_end 的干扰
|
210
|
+
if_stack.push(true) if is_if
|
211
|
+
is_spec_end = if_stack.empty? && is_end
|
212
|
+
if_stack.pop if is_end
|
213
|
+
row = BBRow.new(line, is_comment, is_spec_start, is_spec_end)
|
214
|
+
rows << row
|
215
|
+
end
|
216
|
+
rows
|
217
|
+
end
|
218
|
+
|
219
|
+
# 数据格式:
|
220
|
+
# [
|
221
|
+
# BBRow
|
222
|
+
# BBRow
|
223
|
+
# BBSpec
|
224
|
+
# rows
|
225
|
+
# [
|
226
|
+
# BBRow
|
227
|
+
# BBSpec
|
228
|
+
# BBRow
|
229
|
+
# BBRow
|
230
|
+
# ]
|
231
|
+
# BBRow
|
232
|
+
# ......
|
233
|
+
# ]
|
234
|
+
# 合并Row -> Spec(会存在部分行不在Spec中:Spec new 之前的注释)
|
235
|
+
def self.combin_sepc_if_need(rows,podspec_file_path)
|
236
|
+
spec_stack = []
|
237
|
+
result_rows = []
|
238
|
+
default_name = File.basename(podspec_file_path, File.extname(podspec_file_path))
|
239
|
+
|
240
|
+
rows.each do |row|
|
241
|
+
if row.is_spec_start
|
242
|
+
# 创建 spec
|
243
|
+
name = row.content.split("'")[1]&.strip || default_name
|
244
|
+
alias_name = row.content.split("|")[1]&.strip
|
245
|
+
full_name = spec_stack.empty? ? name : "#{spec_stack.last.full_name}.#{name}"
|
246
|
+
spec = BBSpec.new(name,alias_name,full_name)
|
247
|
+
spec.rows << row
|
248
|
+
|
249
|
+
# 当存在 spec 时,存储在 spec.rows 中;不存在时,直接存储在外层
|
250
|
+
(spec_stack.empty? ? result_rows : spec_stack.last.rows) << spec
|
251
|
+
|
252
|
+
# spec 入栈
|
253
|
+
spec_stack.push(spec)
|
254
|
+
elsif row.is_spec_end
|
255
|
+
# 当前 spec 的 rows 加入当前行
|
256
|
+
spec_stack.last&.rows << row
|
257
|
+
|
258
|
+
#执行隐私协议修改
|
259
|
+
spec_stack.last.privacy_handle(podspec_file_path)
|
260
|
+
|
261
|
+
# spec 出栈
|
262
|
+
spec_stack.pop
|
263
|
+
else
|
264
|
+
# 当存在 spec 时,存储在 spec.rows 中;不存在时,直接存储在外层
|
265
|
+
(spec_stack.empty? ? result_rows : spec_stack.last.rows) << row
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
result_rows
|
270
|
+
end
|
271
|
+
|
272
|
+
# 把所有的spec中的rows 全部展开,拼接成一级数组【BBRow】
|
273
|
+
def self.unfold_sepc_if_need(rows)
|
274
|
+
result_rows = []
|
275
|
+
rows.each do |row|
|
276
|
+
if row.is_a?(BBSpec)
|
277
|
+
result_rows += unfold_sepc_if_need(row.rows)
|
278
|
+
else
|
279
|
+
result_rows << row
|
280
|
+
end
|
281
|
+
end
|
282
|
+
result_rows
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
def self.fetch_privacy_hash(rows,podspec_file_path)
|
287
|
+
privacy_hash = {}
|
288
|
+
filtered_rows = rows.select { |row| row.is_a?(BBSpec) }
|
289
|
+
filtered_rows.each do |spec|
|
290
|
+
privacy_hash[File.join(File.dirname(podspec_file_path),spec.privacy_file)] = spec.privacy_sources
|
291
|
+
privacy_hash.merge!(fetch_privacy_hash(spec.rows,podspec_file_path))
|
292
|
+
end
|
293
|
+
privacy_hash
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module PrivacyUtils
|
4
|
+
|
5
|
+
def self.privacy_name
|
6
|
+
'PrivacyInfo.xcprivacy'
|
7
|
+
end
|
8
|
+
|
9
|
+
# 通过是否包含podspec 来判断是否为主工程
|
10
|
+
def self.isMainProject
|
11
|
+
!(podspec_file_path && !podspec_file_path.empty)
|
12
|
+
end
|
13
|
+
|
14
|
+
# 查找podspec
|
15
|
+
def self.podspec_file_path
|
16
|
+
base_path = Pathname.pwd
|
17
|
+
matching_files = Dir.glob(File.join(base_path, '*.podspec'))
|
18
|
+
matching_files.first
|
19
|
+
end
|
20
|
+
|
21
|
+
# xcode工程地址
|
22
|
+
def self.project_path
|
23
|
+
matching_files = Dir[File.join(Pathname.pwd, '*.xcodeproj')].uniq
|
24
|
+
matching_files.first
|
25
|
+
end
|
26
|
+
|
27
|
+
# xcode工程主代码目录
|
28
|
+
def self.project_code_fold
|
29
|
+
projectPath = project_path
|
30
|
+
File.join(Pathname.pwd,File.basename(projectPath, File.extname(projectPath)))
|
31
|
+
end
|
32
|
+
|
33
|
+
# 使用正则表达式匹配第一个字符前的空格数量
|
34
|
+
def self.count_spaces_before_first_character(str)
|
35
|
+
match = str.match(/\A\s*/)
|
36
|
+
match ? match[0].length : 0
|
37
|
+
end
|
38
|
+
|
39
|
+
# 使用字符串乘法添加指定数量的空格
|
40
|
+
def self.add_spaces_to_string(str, num_spaces)
|
41
|
+
spaces = ' ' * num_spaces
|
42
|
+
"#{spaces}#{str}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.to_md5(string)
|
46
|
+
md5 = Digest::MD5.new
|
47
|
+
md5.update(string)
|
48
|
+
md5.hexdigest
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.cache_privacy_fold
|
52
|
+
# 本地缓存目录
|
53
|
+
cache_directory = File.expand_path('~/.cache')
|
54
|
+
|
55
|
+
# 目标文件夹路径
|
56
|
+
target_directory = File.join(cache_directory, 'cocoapods-privacy', 'privacy')
|
57
|
+
|
58
|
+
# 如果文件夹不存在,则创建
|
59
|
+
FileUtils.mkdir_p(target_directory) unless Dir.exist?(target_directory)
|
60
|
+
|
61
|
+
target_directory
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.cache_config_file
|
65
|
+
config_file = File.join(cache_privacy_fold, 'config.json')
|
66
|
+
end
|
67
|
+
|
68
|
+
# 创建默认隐私协议文件
|
69
|
+
def self.create_privacy_if_empty(file_path)
|
70
|
+
# 文件内容
|
71
|
+
file_content = <<~EOS
|
72
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
73
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
74
|
+
<plist version="1.0">
|
75
|
+
<dict>
|
76
|
+
<key>NSPrivacyTracking</key>
|
77
|
+
<false/>
|
78
|
+
<key>NSPrivacyTrackingDomains</key>
|
79
|
+
<array/>
|
80
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
81
|
+
<array/>
|
82
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
83
|
+
<array/>
|
84
|
+
</dict>
|
85
|
+
</plist>
|
86
|
+
EOS
|
87
|
+
|
88
|
+
isCreate = create_file_and_fold_if_no_exit(file_path,file_content)
|
89
|
+
if isCreate
|
90
|
+
puts "【隐私清单】(初始化)存放地址 => #{file_path}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# 创建文件,并写入默认值,文件路径不存在会自动创建
|
95
|
+
def self.create_file_and_fold_if_no_exit(file_path,file_content = nil)
|
96
|
+
folder_path = File.dirname(file_path)
|
97
|
+
FileUtils.mkdir_p(folder_path) unless File.directory?(folder_path)
|
98
|
+
|
99
|
+
# 创建文件(如果不存在/或为空)
|
100
|
+
if !File.exist?(file_path) || File.zero?(file_path)
|
101
|
+
File.open(file_path, 'w') do |file|
|
102
|
+
file.write(file_content)
|
103
|
+
end
|
104
|
+
return true
|
105
|
+
end
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
|
109
|
+
# 查询group 中是否有执行路径的子group
|
110
|
+
def self.find_group_by_path(group,path)
|
111
|
+
result = nil
|
112
|
+
sub_group = group.children
|
113
|
+
if sub_group && !sub_group.empty?
|
114
|
+
sub_group.each do |item|
|
115
|
+
if item.path == path
|
116
|
+
result = item
|
117
|
+
break
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
result
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'cocoapods/podfile'
|
4
|
+
require 'cocoapods-privacy/command'
|
5
|
+
|
6
|
+
module Pod
|
7
|
+
# The Installer is responsible of taking a Podfile and transform it in the
|
8
|
+
# Pods libraries. It also integrates the user project so the Pods
|
9
|
+
# libraries can be used out of the box.
|
10
|
+
#
|
11
|
+
# The Installer is capable of doing incremental updates to an existing Pod
|
12
|
+
# installation.
|
13
|
+
#
|
14
|
+
# The Installer gets the information that it needs mainly from 3 files:
|
15
|
+
#
|
16
|
+
# - Podfile: The specification written by the user that contains
|
17
|
+
# information about targets and Pods.
|
18
|
+
# - Podfile.lock: Contains information about the pods that were previously
|
19
|
+
# installed and in concert with the Podfile provides information about
|
20
|
+
# which specific version of a Pod should be installed. This file is
|
21
|
+
# ignored in update mode.
|
22
|
+
# - Manifest.lock: A file contained in the Pods folder that keeps track of
|
23
|
+
# the pods installed in the local machine. This files is used once the
|
24
|
+
# exact versions of the Pods has been computed to detect if that version
|
25
|
+
# is already installed. This file is not intended to be kept under source
|
26
|
+
# control and is a copy of the Podfile.lock.
|
27
|
+
#
|
28
|
+
# The Installer is designed to work in environments where the Podfile folder
|
29
|
+
# is under source control and environments where it is not. The rest of the
|
30
|
+
# files, like the user project and the workspace are assumed to be under
|
31
|
+
# source control.
|
32
|
+
#
|
33
|
+
class Installer
|
34
|
+
autoload :Analyzer, 'cocoapods/installer/analyzer'
|
35
|
+
autoload :InstallationOptions, 'cocoapods/installer/installation_options'
|
36
|
+
autoload :PostInstallHooksContext, 'cocoapods/installer/post_install_hooks_context'
|
37
|
+
autoload :PreInstallHooksContext, 'cocoapods/installer/pre_install_hooks_context'
|
38
|
+
autoload :BaseInstallHooksContext, 'cocoapods/installer/base_install_hooks_context'
|
39
|
+
autoload :PostIntegrateHooksContext, 'cocoapods/installer/post_integrate_hooks_context'
|
40
|
+
autoload :PreIntegrateHooksContext, 'cocoapods/installer/pre_integrate_hooks_context'
|
41
|
+
autoload :SourceProviderHooksContext, 'cocoapods/installer/source_provider_hooks_context'
|
42
|
+
autoload :PodfileValidator, 'cocoapods/installer/podfile_validator'
|
43
|
+
autoload :PodSourceDownloader, 'cocoapods/installer/pod_source_downloader'
|
44
|
+
autoload :PodSourceInstaller, 'cocoapods/installer/pod_source_installer'
|
45
|
+
autoload :PodSourcePreparer, 'cocoapods/installer/pod_source_preparer'
|
46
|
+
autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
|
47
|
+
autoload :Xcode, 'cocoapods/installer/xcode'
|
48
|
+
autoload :SandboxHeaderPathsInstaller, 'cocoapods/installer/sandbox_header_paths_installer'
|
49
|
+
autoload :SandboxDirCleaner, 'cocoapods/installer/sandbox_dir_cleaner'
|
50
|
+
autoload :ProjectCache, 'cocoapods/installer/project_cache/project_cache'
|
51
|
+
autoload :TargetUUIDGenerator, 'cocoapods/installer/target_uuid_generator'
|
52
|
+
|
53
|
+
|
54
|
+
# 直接执行 pod privacy 时调用
|
55
|
+
def privacy_analysis(custom_folds)
|
56
|
+
prepare
|
57
|
+
resolve_dependencies
|
58
|
+
clean_sandbox
|
59
|
+
|
60
|
+
privacy_handle(custom_folds)
|
61
|
+
end
|
62
|
+
|
63
|
+
# hook pod install 命令
|
64
|
+
alias_method :privacy_origin_install!, :install!
|
65
|
+
def install!
|
66
|
+
privacy_origin_install!()
|
67
|
+
|
68
|
+
if !(Pod::Config.instance.is_privacy || !Pod::Config.instance.privacy_folds.empty?)
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
privacy_handle(Pod::Config.instance.privacy_folds)
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def privacy_handle(custom_folds)
|
77
|
+
# 过滤出自身组件 && 自身没有隐私协议文件的spec
|
78
|
+
modules = @analysis_result.specifications.select {
|
79
|
+
|obj| obj.is_need_search_module && !obj.has_privacy
|
80
|
+
}
|
81
|
+
|
82
|
+
# 存储本地调试组件
|
83
|
+
development_folds = []
|
84
|
+
|
85
|
+
# 获取组件所在工程的pods 目录
|
86
|
+
pod_folds = modules.map{ |spec|
|
87
|
+
name = spec.name.split('/').first
|
88
|
+
fold = File.join(@sandbox.root,name)
|
89
|
+
if Dir.exist?(fold)
|
90
|
+
fold
|
91
|
+
else
|
92
|
+
development_pods = @sandbox.development_pods
|
93
|
+
if name && development_pods
|
94
|
+
podspec_file_path = development_pods[name]
|
95
|
+
if podspec_file_path && !podspec_file_path.empty?
|
96
|
+
podspec_fold_path = File.dirname(podspec_file_path)
|
97
|
+
source_files = spec.attributes_hash['source_files']
|
98
|
+
if source_files && !source_files.empty?
|
99
|
+
source_files.each do |file|
|
100
|
+
development_folds << File.join(podspec_fold_path,file)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
}.compact
|
108
|
+
|
109
|
+
|
110
|
+
pod_folds += development_folds # 拼接本地调试和远端的pod目录
|
111
|
+
pod_folds += [PrivacyUtils.project_code_fold].compact # 拼接工程同名主目录
|
112
|
+
pod_folds += custom_folds || [] # 拼接外部传入的自定义目录
|
113
|
+
pod_folds = pod_folds.uniq # 去重
|
114
|
+
|
115
|
+
if pod_folds.empty?
|
116
|
+
puts "无组件或工程目录, 请检查工程"
|
117
|
+
else
|
118
|
+
# 处理工程隐私协议
|
119
|
+
PrivacyModule.load_project(pod_folds)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
require 'cocoapods-core/specification/root_attribute_accessors'
|
3
|
+
|
4
|
+
module Pod
|
5
|
+
# The Specification provides a DSL to describe a Pod. A pod is defined as a
|
6
|
+
# library originating from a source. A specification can support detailed
|
7
|
+
# attributes for modules of code through subspecs.
|
8
|
+
#
|
9
|
+
# Usually it is stored in files with `podspec` extension.
|
10
|
+
#
|
11
|
+
class Specification
|
12
|
+
|
13
|
+
# 是否含有隐私协议文件
|
14
|
+
def has_privacy
|
15
|
+
resource_bundle = attributes_hash['resource_bundles']
|
16
|
+
resource_bundle && resource_bundle.to_s.include?('PrivacyInfo.xcprivacy')
|
17
|
+
end
|
18
|
+
|
19
|
+
# 是否为需要检索组件
|
20
|
+
def is_need_search_module
|
21
|
+
unless File.exist?(PrivacyUtils.cache_config_file)
|
22
|
+
raise Informative, "无配置文件,run `pod privacy config config_file` 进行配置"
|
23
|
+
end
|
24
|
+
|
25
|
+
#查找source(可能是subspec)
|
26
|
+
git_source = recursive_git_source(self)
|
27
|
+
unless git_source
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
|
31
|
+
# 判断域名白名单 和 黑名单,确保该组件是自己的组件,第三方sdk不做检索
|
32
|
+
config = Privacy::Config.instance
|
33
|
+
git_source_whitelisted = config.source_white_list.any? { |item| git_source.include?(item) }
|
34
|
+
git_source_blacklisted = config.source_black_list.any? { |item| git_source.include?(item) }
|
35
|
+
git_source_whitelisted && !git_source_blacklisted
|
36
|
+
end
|
37
|
+
|
38
|
+
# 返回resource_bundles
|
39
|
+
def bb_resource_bundles
|
40
|
+
hash_value['resource_bundles']
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def recursive_git_source(spec)
|
45
|
+
return nil unless spec
|
46
|
+
if spec.source && spec.source.key?(:git)
|
47
|
+
spec.source[:git]
|
48
|
+
else
|
49
|
+
recursive_git_source(spec.instance_variable_get(:@parent))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'cocoapods-privacy/gem_version'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'cocoapods-privacy/command'
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
ROOT = Pathname.new(File.expand_path('../../', __FILE__))
|
3
|
+
$:.unshift((ROOT + 'lib').to_s)
|
4
|
+
$:.unshift((ROOT + 'spec').to_s)
|
5
|
+
|
6
|
+
require 'bundler/setup'
|
7
|
+
require 'bacon'
|
8
|
+
require 'mocha-on-bacon'
|
9
|
+
require 'pretty_bacon'
|
10
|
+
require 'pathname'
|
11
|
+
require 'cocoapods'
|
12
|
+
|
13
|
+
Mocha::Configuration.prevent(:stubbing_non_existent_method)
|
14
|
+
|
15
|
+
require 'cocoapods_plugin'
|
16
|
+
|
17
|
+
#-----------------------------------------------------------------------------#
|
18
|
+
|
19
|
+
module Pod
|
20
|
+
|
21
|
+
# Disable the wrapping so the output is deterministic in the tests.
|
22
|
+
#
|
23
|
+
UI.disable_wrap = true
|
24
|
+
|
25
|
+
# Redirects the messages to an internal store.
|
26
|
+
#
|
27
|
+
module UI
|
28
|
+
@output = ''
|
29
|
+
@warnings = ''
|
30
|
+
|
31
|
+
class << self
|
32
|
+
attr_accessor :output
|
33
|
+
attr_accessor :warnings
|
34
|
+
|
35
|
+
def puts(message = '')
|
36
|
+
@output << "#{message}\n"
|
37
|
+
end
|
38
|
+
|
39
|
+
def warn(message = '', actions = [])
|
40
|
+
@warnings << "#{message}\n"
|
41
|
+
end
|
42
|
+
|
43
|
+
def print(message)
|
44
|
+
@output << message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
#-----------------------------------------------------------------------------#
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cocoapods-privacy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- youhui
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: A short description of cocoapods-privacy.
|
42
|
+
email:
|
43
|
+
- developer_yh@163.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- LICENSE.txt
|
49
|
+
- README.md
|
50
|
+
- lib/cocoapods-privacy.rb
|
51
|
+
- lib/cocoapods-privacy/command.rb
|
52
|
+
- lib/cocoapods-privacy/command/install.rb
|
53
|
+
- lib/cocoapods-privacy/command/privacy.rb
|
54
|
+
- lib/cocoapods-privacy/command/privacy/config.rb
|
55
|
+
- lib/cocoapods-privacy/command/privacy/install.rb
|
56
|
+
- lib/cocoapods-privacy/command/privacy/spec.rb
|
57
|
+
- lib/cocoapods-privacy/gem_version.rb
|
58
|
+
- lib/cocoapods-privacy/privacy/PrivacyConfig.rb
|
59
|
+
- lib/cocoapods-privacy/privacy/PrivacyHunter.rb
|
60
|
+
- lib/cocoapods-privacy/privacy/PrivacyModule.rb
|
61
|
+
- lib/cocoapods-privacy/privacy/PrivacyUtils.rb
|
62
|
+
- lib/cocoapods-privacy/privacy/privacy_installer_hook.rb
|
63
|
+
- lib/cocoapods-privacy/privacy/privacy_specification_hook.rb
|
64
|
+
- lib/cocoapods_plugin.rb
|
65
|
+
- spec/command/privacy_spec.rb
|
66
|
+
- spec/spec_helper.rb
|
67
|
+
homepage: https://github.com/ymoyao/cocoapods-privacy
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
metadata: {}
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubygems_version: 3.4.21
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: A longer description of cocoapods-privacy.
|
90
|
+
test_files:
|
91
|
+
- spec/command/privacy_spec.rb
|
92
|
+
- spec/spec_helper.rb
|