cocoapods-privacy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|