pindo 5.10.6 → 5.10.8
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 +4 -4
- data/lib/pindo/base/funlog.rb +12 -1
- data/lib/pindo/base/pindocontext.rb +3 -0
- data/lib/pindo/command/android/autobuild.rb +62 -9
- data/lib/pindo/command/android/build.rb +6 -6
- data/lib/pindo/command/android/debug.rb +9 -9
- data/lib/pindo/command/deploy/build.rb +2 -4
- data/lib/pindo/command/deploy/cert.rb +4 -5
- data/lib/pindo/command/deploy/configproj.rb +3 -3
- data/lib/pindo/command/deploy/confusecode.rb +1 -1
- data/lib/pindo/command/deploy/confuseproj.rb +1 -1
- data/lib/pindo/command/deploy/pem.rb +3 -4
- data/lib/pindo/command/dev/autobuild.rb +1 -1
- data/lib/pindo/command/dev/build.rb +1 -1
- data/lib/pindo/command/dev/feishu.rb +1 -1
- data/lib/pindo/command/gplay/pullconfig.rb +48 -0
- data/lib/pindo/command/gplay.rb +6 -5
- data/lib/pindo/command/ios/adhoc.rb +6 -5
- data/lib/pindo/command/ios/autobuild.rb +24 -24
- data/lib/pindo/command/ios/build.rb +7 -6
- data/lib/pindo/command/ios/debug.rb +1 -0
- data/lib/pindo/command/ipa/import.rb +2 -3
- data/lib/pindo/command/ipa/output.rb +2 -3
- data/lib/pindo/command/jps/upload.rb +6 -5
- data/lib/pindo/command/unity/apk.rb +19 -2
- data/lib/pindo/command/unity/autobuild.rb +58 -63
- data/lib/pindo/command/unity/initpack.rb +1 -1
- data/lib/pindo/command/unity/ipa.rb +17 -14
- data/lib/pindo/command/unity/pack.rb +1 -1
- data/lib/pindo/command/unity/upload.rb +1 -1
- data/lib/pindo/command/unity/web.rb +2 -2
- data/lib/pindo/command/utils/icon.rb +1 -1
- data/lib/pindo/command/utils/renewcert.rb +1 -2
- data/lib/pindo/command/utils/renewproj.rb +9 -10
- data/lib/pindo/command/web/autobuild.rb +2 -2
- data/lib/pindo/command/web/run.rb +1 -1
- data/lib/pindo/command.rb +2 -1
- data/lib/pindo/module/android/android_build_config_helper.rb +267 -35
- data/lib/pindo/module/android/android_build_helper.rb +300 -0
- data/lib/pindo/module/android/android_project_helper.rb +279 -0
- data/lib/pindo/module/android/gp_compliance_helper.rb +33 -87
- data/lib/pindo/module/android/gradle_helper.rb +524 -255
- data/lib/pindo/module/android/java_env_helper.rb +633 -0
- data/lib/pindo/module/android/keystore_helper.rb +1118 -0
- data/lib/pindo/module/appselect.rb +109 -0
- data/lib/pindo/module/appstore/appstore_in_app_purchase.rb +1 -1
- data/lib/pindo/module/build/{buildhelper.rb → build_helper.rb} +1 -2
- data/lib/pindo/module/build/{commonconfuseproj.rb → confuse_xcodeproj.rb} +2 -2
- data/lib/pindo/module/cert/cert_helper.rb +245 -0
- data/lib/pindo/module/cert/keychain_helper.rb +152 -0
- data/lib/pindo/module/cert/pem_helper.rb +67 -0
- data/lib/pindo/module/cert/xcodecerthelper.rb +2 -2
- data/lib/pindo/module/{build/unityhelper.rb → unity/unity_helper.rb} +0 -17
- data/lib/pindo/module/xcode/{xcodereshandler.rb → res/xcode_res_handler.rb} +1 -1
- data/lib/pindo/module/xcode/{xcodebuildconfig.rb → xcode_build_config.rb} +7 -6
- data/lib/pindo/module/xcode/{xcodebuildhelper.rb → xcode_build_helper.rb} +4 -3
- data/lib/pindo/module/xcode/{xcodehelper.rb → xcode_project_helper.rb} +4 -2
- data/lib/pindo/module/xcode/{xcodereshelper.rb → xcode_res_helper.rb} +12 -9
- data/lib/pindo/version.rb +185 -7
- data/lib/pindo.rb +14 -9
- metadata +24 -23
- data/lib/pindo/module/android/apk_helper.rb +0 -138
- data/lib/pindo/module/android/base_helper.rb +0 -964
- data/lib/pindo/module/android/build_helper.rb +0 -128
- data/lib/pindo/module/android/so_helper.rb +0 -18
- data/lib/pindo/module/cert/certhelper.rb +0 -246
- data/lib/pindo/module/cert/keychainhelper.rb +0 -150
- data/lib/pindo/module/cert/pemhelper.rb +0 -65
- /data/lib/pindo/module/android/{androidreshelper.rb → android_res_helper.rb} +0 -0
- /data/lib/pindo/module/appstore/{iap_tier.json → appstore_iap_tier.json} +0 -0
- /data/lib/pindo/module/build/{swarkhelper.rb → swark_helper.rb} +0 -0
- /data/lib/pindo/module/build/{versionhelper.rb → version_helper.rb} +0 -0
- /data/lib/pindo/module/cert/{provisioninghelper.rb → provisioning_helper.rb} +0 -0
- /data/lib/pindo/module/unity/{nugethelper.rb → nuget_helper.rb} +0 -0
- /data/lib/pindo/module/xcode/{xcoderesconstant.rb → res/xcode_res_constant.rb} +0 -0
|
@@ -63,6 +63,33 @@ module Pindo
|
|
|
63
63
|
return sub_json
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
# Android Bundle Name 相关函数
|
|
67
|
+
def all_dev_bundle_name
|
|
68
|
+
bundle_names = []
|
|
69
|
+
begin
|
|
70
|
+
config = Pindoconfig.instance.pindo_common_config_json
|
|
71
|
+
if config && config['android_dev_bundle_array']
|
|
72
|
+
bundle_names = config['android_dev_bundle_array']
|
|
73
|
+
end
|
|
74
|
+
rescue => e
|
|
75
|
+
raise Informative, "注意: android_dev_bundle_array 配置不存在或无法加载"
|
|
76
|
+
end
|
|
77
|
+
return bundle_names
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def all_deploy_bundle_name
|
|
81
|
+
bundle_names = []
|
|
82
|
+
begin
|
|
83
|
+
config = Pindoconfig.instance.pindo_common_config_json
|
|
84
|
+
if config && config['android_deploy_bundle_array']
|
|
85
|
+
bundle_names = config['android_deploy_bundle_array']
|
|
86
|
+
end
|
|
87
|
+
rescue => e
|
|
88
|
+
raise Informative, "注意: android_deploy_bundle_array 配置不存在或无法加载"
|
|
89
|
+
end
|
|
90
|
+
return bundle_names
|
|
91
|
+
end
|
|
92
|
+
|
|
66
93
|
|
|
67
94
|
def all_itc_bundleid
|
|
68
95
|
all_bundleid = []
|
|
@@ -190,6 +217,88 @@ module Pindo
|
|
|
190
217
|
|
|
191
218
|
end
|
|
192
219
|
|
|
220
|
+
def get_selected_dev_bundle_name()
|
|
221
|
+
# 检查环境变量
|
|
222
|
+
env_bundle_name = ENV['PINDO_BUNDLE_NAME']
|
|
223
|
+
if env_bundle_name && !env_bundle_name.empty?
|
|
224
|
+
puts "\n使用环境变量指定的Bundle Name: #{env_bundle_name}"
|
|
225
|
+
puts
|
|
226
|
+
# 保存到缓存
|
|
227
|
+
require_relative '../base/pindocontext'
|
|
228
|
+
context = Pindo::PindoContext.instance
|
|
229
|
+
context.set_selection(Pindo::PindoContext::SelectionKey::ANDROID_BUNDLE_NAME, env_bundle_name)
|
|
230
|
+
return env_bundle_name
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# 检查缓存
|
|
234
|
+
require_relative '../base/pindocontext'
|
|
235
|
+
context = Pindo::PindoContext.instance
|
|
236
|
+
cached_bundle_name = context.get_selection(Pindo::PindoContext::SelectionKey::ANDROID_BUNDLE_NAME)
|
|
237
|
+
|
|
238
|
+
if cached_bundle_name
|
|
239
|
+
puts "\n使用之前选择的Bundle Name: #{cached_bundle_name}"
|
|
240
|
+
puts
|
|
241
|
+
return cached_bundle_name
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
all_bundle_names = all_dev_bundle_name()
|
|
245
|
+
|
|
246
|
+
if all_bundle_names.nil? || all_bundle_names.empty?
|
|
247
|
+
puts "\n未配置任何开发环境的Android Bundle Name"
|
|
248
|
+
return nil
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
cli = HighLine.new
|
|
252
|
+
menu_choice = "None"
|
|
253
|
+
puts
|
|
254
|
+
cli.choose do |menu|
|
|
255
|
+
menu.header = "可用的Android Bundle Name如下:"
|
|
256
|
+
menu.prompt = "请选择使用的Bundle Name,请输入选项(1/2/3...):"
|
|
257
|
+
for bundle_name in all_bundle_names do
|
|
258
|
+
menu.choice(bundle_name) do |details|
|
|
259
|
+
menu_choice = "#{details}"
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
puts
|
|
265
|
+
puts "选择的Bundle Name是: #{menu_choice}"
|
|
266
|
+
puts
|
|
267
|
+
|
|
268
|
+
# 保存选择到缓存
|
|
269
|
+
context.set_selection(Pindo::PindoContext::SelectionKey::ANDROID_BUNDLE_NAME, menu_choice)
|
|
270
|
+
|
|
271
|
+
return menu_choice
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def get_selected_deploy_bundle_name()
|
|
275
|
+
all_bundle_names = all_deploy_bundle_name()
|
|
276
|
+
|
|
277
|
+
if all_bundle_names.nil? || all_bundle_names.empty?
|
|
278
|
+
puts "\n未配置任何发布环境的Android Bundle Name"
|
|
279
|
+
return nil
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
cli = HighLine.new
|
|
283
|
+
menu_choice = "None"
|
|
284
|
+
puts
|
|
285
|
+
cli.choose do |menu|
|
|
286
|
+
menu.header = "可用的Android Bundle Name如下:"
|
|
287
|
+
menu.prompt = "请选择使用的Bundle Name,请输入选项(1/2/3...):"
|
|
288
|
+
for bundle_name in all_bundle_names do
|
|
289
|
+
menu.choice(bundle_name) do |details|
|
|
290
|
+
menu_choice = "#{details}"
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
puts
|
|
296
|
+
puts "选择的Bundle Name是: #{menu_choice}"
|
|
297
|
+
puts
|
|
298
|
+
|
|
299
|
+
return menu_choice
|
|
300
|
+
end
|
|
301
|
+
|
|
193
302
|
def get_deploy_setting_repo(tagname:nil)
|
|
194
303
|
sub_setting_json = deploy_build_setting_json()
|
|
195
304
|
return sub_setting_json[tagname]['git_repo_name']
|
|
@@ -9,7 +9,7 @@ module Pindo
|
|
|
9
9
|
|
|
10
10
|
def get_price_tier_dict
|
|
11
11
|
|
|
12
|
-
iap_tier_file = File.join(File.expand_path('./', __dir__), '
|
|
12
|
+
iap_tier_file = File.join(File.expand_path('./', __dir__), 'appstore_iap_tier.json')
|
|
13
13
|
iap_tier_json = JSON.parse(File.read(iap_tier_file))
|
|
14
14
|
|
|
15
15
|
return iap_tier_json
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
require 'singleton'
|
|
2
2
|
require 'fileutils'
|
|
3
3
|
require 'xcodeproj' # 用于iOS项目检查
|
|
4
|
-
require_relative '../android/
|
|
4
|
+
require_relative '../android/android_project_helper'
|
|
5
5
|
require_relative '../../base/githelper'
|
|
6
6
|
require_relative '../../base/pindocontext'
|
|
7
7
|
module Pindo
|
|
8
8
|
|
|
9
9
|
class BuildHelper
|
|
10
10
|
include Singleton
|
|
11
|
-
include BaseAndroidHelper
|
|
12
11
|
include Pindo::Githelper
|
|
13
12
|
|
|
14
13
|
# 临时缓存,只在内存中,不保存到文件
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
require 'match'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'pindo/base/aeshelper'
|
|
6
|
+
require 'pindo/module/cert/keychain_helper'
|
|
7
|
+
require 'pindo/module/cert/provisioning_helper'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
module Pindo
|
|
11
|
+
|
|
12
|
+
class CertHelper
|
|
13
|
+
# 密码缓存,避免重复获取相同URL的密码
|
|
14
|
+
@@password_cache = {}
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# 获取密码的辅助方法,使用缓存避免重复获取
|
|
18
|
+
def get_cached_password(cert_url)
|
|
19
|
+
unless @@password_cache[cert_url]
|
|
20
|
+
puts "\e[33m[DEBUG] 密码缓存中未找到,从Keychain获取: #{cert_url}\e[0m" if ENV['PINDO_DEBUG']
|
|
21
|
+
@@password_cache[cert_url] = AESHelper.fetch_password(keychain_name: cert_url)
|
|
22
|
+
puts "\e[32m[DEBUG] 密码已缓存: #{cert_url}\e[0m" if ENV['PINDO_DEBUG']
|
|
23
|
+
else
|
|
24
|
+
puts "\e[32m[DEBUG] 从密码缓存获取: #{cert_url}\e[0m" if ENV['PINDO_DEBUG']
|
|
25
|
+
end
|
|
26
|
+
@@password_cache[cert_url]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# 清除密码缓存
|
|
30
|
+
def clear_password_cache
|
|
31
|
+
@@password_cache.clear
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# 清除特定URL的密码缓存
|
|
35
|
+
def clear_password_cache_for_url(cert_url)
|
|
36
|
+
@@password_cache.delete(cert_url)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def get_cert_info(cer_certificate)
|
|
40
|
+
# can receive a certificate path or the file data
|
|
41
|
+
begin
|
|
42
|
+
if File.exist?(cer_certificate)
|
|
43
|
+
cer_certificate = File.binread(cer_certificate)
|
|
44
|
+
end
|
|
45
|
+
rescue ArgumentError
|
|
46
|
+
# cert strings have null bytes; suppressing output
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
cert = OpenSSL::X509::Certificate.new(cer_certificate)
|
|
50
|
+
|
|
51
|
+
# openssl output:
|
|
52
|
+
# subject= /UID={User ID}/CN={Certificate Name}/OU={Certificate User}/O={Organisation}/C={Country}
|
|
53
|
+
cert_info = cert.subject.to_s.gsub(/\s*subject=\s*/, "").tr("/", "\n")
|
|
54
|
+
out_array = cert_info.split("\n")
|
|
55
|
+
openssl_keys_to_readable_keys = {
|
|
56
|
+
'UID' => 'User ID',
|
|
57
|
+
'CN' => 'Common Name',
|
|
58
|
+
'OU' => 'Organisation Unit',
|
|
59
|
+
'O' => 'Organisation',
|
|
60
|
+
'C' => 'Country',
|
|
61
|
+
'notBefore' => 'Start Datetime',
|
|
62
|
+
'notAfter' => 'End Datetime'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return out_array.map { |x| x.split(/=+/) if x.include?("=") }
|
|
66
|
+
.compact
|
|
67
|
+
.map { |k, v| [openssl_keys_to_readable_keys.fetch(k, k), v] }
|
|
68
|
+
.push([openssl_keys_to_readable_keys.fetch("notBefore"), cert.not_before])
|
|
69
|
+
.push([openssl_keys_to_readable_keys.fetch("notAfter"), cert.not_after])
|
|
70
|
+
rescue => ex
|
|
71
|
+
raise Informative, "get_cert_info: #{ex}"
|
|
72
|
+
return {}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def select_cert_or_key(paths:)
|
|
76
|
+
cert_id_path = ENV['MATCH_CERTIFICATE_ID'] ? paths.find { |path| path.include?(ENV['MATCH_CERTIFICATE_ID']) } : nil
|
|
77
|
+
cert_id_path || paths.last
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def is_cert_valid?(cer_certificate_path)
|
|
81
|
+
cert = OpenSSL::X509::Certificate.new(File.binread(cer_certificate_path))
|
|
82
|
+
now = Time.now.utc
|
|
83
|
+
return (now <=> cert.not_after) == -1
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def isMac?
|
|
87
|
+
(/darwin/ =~ RUBY_PLATFORM) != nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def install_certs(cert_url:nil, certs_dir:nil, cert_type:nil, platform_type:nil)
|
|
91
|
+
|
|
92
|
+
cert_git_dir = cert_type.downcase
|
|
93
|
+
if platform_type.downcase.eql?("macos")
|
|
94
|
+
if cert_type.downcase.include?("development")
|
|
95
|
+
cert_git_dir = "development"
|
|
96
|
+
elsif cert_type.downcase.eql?("appstore")
|
|
97
|
+
cert_git_dir = "distribution"
|
|
98
|
+
else
|
|
99
|
+
cert_git_dir = "developer_id_application"
|
|
100
|
+
end
|
|
101
|
+
else
|
|
102
|
+
if !cert_type.downcase.include?("development")
|
|
103
|
+
cert_git_dir = "distribution"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
certs = Dir[File.join(certs_dir, "certs", cert_git_dir.to_s, "*.cer")]
|
|
108
|
+
keys = Dir[File.join(certs_dir, "certs", cert_git_dir.to_s, "*.p12")]
|
|
109
|
+
|
|
110
|
+
if certs.count == 0 || keys.count == 0
|
|
111
|
+
raise Informative, "No certificates found in #{certs_dir}"
|
|
112
|
+
else
|
|
113
|
+
output_dir = Dir.mktmpdir
|
|
114
|
+
|
|
115
|
+
decrypt_password = CertHelper.get_cached_password(cert_url)
|
|
116
|
+
Funlog.instance.fancyinfo_start("正在安装证书...")
|
|
117
|
+
|
|
118
|
+
cert_path = AESHelper.decrypt_specific_file(src_file: certs.first, password:decrypt_password, output_dir: output_dir)
|
|
119
|
+
if cert_path.nil? || cert_path.empty? || !File.exist?(cert_path)
|
|
120
|
+
AESHelper.delete_password(keychain_name:cert_url)
|
|
121
|
+
# 清除内存中的密码缓存,避免重复使用错误密码
|
|
122
|
+
@@password_cache.delete(cert_url)
|
|
123
|
+
raise Informative, "证书解析失败,密码错误!"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
key_path = AESHelper.decrypt_specific_file(src_file: keys.first, password:decrypt_password, output_dir: output_dir)
|
|
127
|
+
if key_path.nil? || key_path.empty? || !File.exist?(key_path)
|
|
128
|
+
AESHelper.delete_password(keychain_name:cert_url)
|
|
129
|
+
# 清除内存中的密码缓存,避免重复使用错误密码
|
|
130
|
+
@@password_cache.delete(cert_url)
|
|
131
|
+
raise Informative, "证书解析失败,密码错误!"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
unless is_cert_valid?(cert_path)
|
|
135
|
+
raise Informative, "证书已经过期,请重新生产新证书!"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if isMac?
|
|
140
|
+
|
|
141
|
+
keychain_name = "login.keychain"
|
|
142
|
+
if FastlaneCore::CertChecker.installed?(cert_path, in_keychain: nil)
|
|
143
|
+
Funlog.instance.fancyinfo_success("证书#{File.basename(cert_path)}已安装,无需重复安装!")
|
|
144
|
+
else
|
|
145
|
+
|
|
146
|
+
cert_password = Pindoconfig.instance.cert_key_password
|
|
147
|
+
keychain = 'login.keychain'
|
|
148
|
+
keychain_path = FastlaneCore::Helper.keychain_path(keychain)
|
|
149
|
+
|
|
150
|
+
KeychainHelper.import_file(cert_path, keychain_path, keychain_password: cert_password, certificate_password:'' )
|
|
151
|
+
KeychainHelper.import_file(key_path, keychain_path, keychain_password: cert_password, certificate_password: '')
|
|
152
|
+
|
|
153
|
+
Funlog.instance.fancyinfo_success("证书'#{File.basename(cert_path)}'安装完成!")
|
|
154
|
+
|
|
155
|
+
end
|
|
156
|
+
else
|
|
157
|
+
Funlog.instance.fancyinfo_error("非Mac电脑不支持安装证书!")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def install_provisionfiles(cert_url:nil, certs_dir:nil, bundle_id_map:nil, cert_type:nil, platform_type:nil)
|
|
164
|
+
|
|
165
|
+
cert_sub_dir = cert_type.downcase
|
|
166
|
+
provision_start_name = "Development"
|
|
167
|
+
provision_extension_name = ".mobileprovision"
|
|
168
|
+
|
|
169
|
+
if platform_type.downcase.include?("macos")
|
|
170
|
+
provision_extension_name = ".provisionprofile"
|
|
171
|
+
|
|
172
|
+
if cert_type.downcase.include?("development")
|
|
173
|
+
provision_start_name = "Development"
|
|
174
|
+
cert_sub_dir = cert_type.downcase
|
|
175
|
+
elsif cert_type.downcase.eql?("appstore")
|
|
176
|
+
provision_start_name = "AppStore"
|
|
177
|
+
cert_sub_dir = "appstore"
|
|
178
|
+
else
|
|
179
|
+
provision_start_name = "Direct"
|
|
180
|
+
cert_sub_dir = "developer_id"
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
provision_extension_name = ".mobileprovision"
|
|
184
|
+
if cert_type.downcase.include?("development")
|
|
185
|
+
provision_start_name = "Development"
|
|
186
|
+
cert_sub_dir = cert_type.downcase
|
|
187
|
+
elsif cert_type.downcase.include?("adhoc")
|
|
188
|
+
provision_start_name = "Adhoc"
|
|
189
|
+
cert_sub_dir = "adhoc"
|
|
190
|
+
else
|
|
191
|
+
provision_start_name = "AppStore"
|
|
192
|
+
cert_sub_dir = "appstore"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
Funlog.instance.fancyinfo_start("正在安装#{provision_start_name} #{platform_type} Provisioning Profiles...")
|
|
197
|
+
|
|
198
|
+
un_exist_files = []
|
|
199
|
+
provisioning_info_array = []
|
|
200
|
+
|
|
201
|
+
# 在循环外获取密码,避免重复添加到Keychain
|
|
202
|
+
decrypt_password = CertHelper.get_cached_password(cert_url)
|
|
203
|
+
|
|
204
|
+
bundle_id_map.each do |type, bundle_id_temp|
|
|
205
|
+
profile_filename = File.join(certs_dir, "profiles", cert_sub_dir, [provision_start_name.to_s, bundle_id_temp].join('_') + provision_extension_name)
|
|
206
|
+
unless File.exist?(profile_filename)
|
|
207
|
+
un_exist_files << profile_filename
|
|
208
|
+
next
|
|
209
|
+
end
|
|
210
|
+
# puts "正在安装 #{bundle_id_temp}..."
|
|
211
|
+
output_dir = Dir.mktmpdir
|
|
212
|
+
file_decrypt = AESHelper.decrypt_specific_file(src_file: profile_filename, password:decrypt_password, output_dir: output_dir)
|
|
213
|
+
destpath = Provisioninghelper.install(file_decrypt)
|
|
214
|
+
parsed_data = Provisioninghelper.parse(destpath)
|
|
215
|
+
|
|
216
|
+
provisioning_info = {}
|
|
217
|
+
provisioning_info['type'] = type
|
|
218
|
+
provisioning_info['bundle_id'] = bundle_id_temp
|
|
219
|
+
provisioning_info['profile_name'] = parsed_data['Name']
|
|
220
|
+
provisioning_info['profile_path'] = destpath
|
|
221
|
+
|
|
222
|
+
cert_info = get_cert_info(parsed_data["DeveloperCertificates"].first.string).to_h
|
|
223
|
+
provisioning_info['signing_identity'] = cert_info["Common Name"]
|
|
224
|
+
provisioning_info['team_id'] = parsed_data["TeamIdentifier"].first
|
|
225
|
+
|
|
226
|
+
# puts JSON.pretty_generate(provisioning_info)
|
|
227
|
+
provisioning_info_array << provisioning_info
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
Funlog.instance.fancyinfo_success("#{provision_start_name} #{platform_type} Provisioning Profiles文件安装完成!")
|
|
231
|
+
|
|
232
|
+
if un_exist_files.size > 0
|
|
233
|
+
Funlog.instance.fancyinfo_error("证书 #{provision_start_name} #{platform_type} Provisioning Profiles文件不存在!")
|
|
234
|
+
raise Informative, "The following profiles do not exist: #{un_exist_files.join(', ')}"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
return provisioning_info_array
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
end
|
|
245
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
|
|
2
|
+
require 'open3'
|
|
3
|
+
require 'security'
|
|
4
|
+
|
|
5
|
+
module Pindo
|
|
6
|
+
|
|
7
|
+
class KeychainHelper
|
|
8
|
+
class << self
|
|
9
|
+
|
|
10
|
+
def import_file(path, keychain_path, keychain_password: nil, certificate_password: "", skip_set_partition_list: false, output: false)
|
|
11
|
+
# puts path
|
|
12
|
+
|
|
13
|
+
# puts "Could not find file '#{path}'" unless File.exist?(path)
|
|
14
|
+
|
|
15
|
+
password_part = " -P #{certificate_password.shellescape}"
|
|
16
|
+
|
|
17
|
+
command = "security import #{path.shellescape} -k '#{keychain_path.shellescape}'"
|
|
18
|
+
command << password_part
|
|
19
|
+
command << " -T /usr/bin/codesign" # to not be asked for permission when running a tool like `gym` (before Sierra)
|
|
20
|
+
command << " -T /usr/bin/security"
|
|
21
|
+
command << " -T /usr/bin/productbuild" # to not be asked for permission when using an installer cert for macOS
|
|
22
|
+
command << " -T /usr/bin/productsign" # to not be asked for permission when using an installer cert for macOS
|
|
23
|
+
command << " 1> /dev/null" unless output
|
|
24
|
+
|
|
25
|
+
# puts command
|
|
26
|
+
# sensitive_command = command.gsub(password_part, " -P ********")
|
|
27
|
+
|
|
28
|
+
# UI.command(sensitive_command) if output
|
|
29
|
+
Open3.popen3(command) do |stdin, stdout, stderr, thrd|
|
|
30
|
+
# UI.command_output(stdout.read.to_s) if output
|
|
31
|
+
# puts "123"
|
|
32
|
+
# Set partition list only if success since it can be a time consuming process if a lot of keys are installed
|
|
33
|
+
if thrd.value.success? && !skip_set_partition_list
|
|
34
|
+
# puts "安装成功!"
|
|
35
|
+
keychain_password ||= resolve_keychain_password(keychain_path)
|
|
36
|
+
set_partition_list(path, keychain_path, keychain_password: keychain_password, output: output)
|
|
37
|
+
else
|
|
38
|
+
# Output verbose if file is already installed since not an error otherwise we will show the whole error
|
|
39
|
+
err = stderr.read.to_s.strip
|
|
40
|
+
|
|
41
|
+
if err.include?("SecKeychainItemImport") && err.include?("The specified item already exists in the keychain")
|
|
42
|
+
# UI.verbose("'}' is already installed on this machine")
|
|
43
|
+
# puts "证书key已经存在,安装完成!"
|
|
44
|
+
else
|
|
45
|
+
# puts err
|
|
46
|
+
# raise Informative, err
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def help_backticks(command, print: true)
|
|
53
|
+
# UI.command(command) if print
|
|
54
|
+
result = `#{command}`
|
|
55
|
+
# UI.command_output(result) if print
|
|
56
|
+
return result
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def set_partition_list(path, keychain_path, keychain_password: nil, output: false)
|
|
60
|
+
# When security supports partition lists, also add the partition IDs
|
|
61
|
+
# See https://openradar.appspot.com/28524119
|
|
62
|
+
if help_backticks('security -h | grep set-key-partition-list', print: false).length > 0
|
|
63
|
+
password_part = " -k #{keychain_password.to_s.shellescape}"
|
|
64
|
+
|
|
65
|
+
command = "security set-key-partition-list"
|
|
66
|
+
command << " -S apple-tool:,apple:,codesign:"
|
|
67
|
+
command << " -s" # This is a needed in Catalina to prevent "security: SecKeychainItemCopyAccess: A missing value was detected."
|
|
68
|
+
command << password_part
|
|
69
|
+
command << " #{keychain_path.shellescape}"
|
|
70
|
+
command << " 1> /dev/null" # always disable stdout. This can be very verbose, and leak potentially sensitive info
|
|
71
|
+
|
|
72
|
+
# Showing loading indicator as this can take some time if a lot of keys installed
|
|
73
|
+
# Helper.show_loading_indicator("Setting key partition list... (this can take a minute if there are a lot of keys installed)")
|
|
74
|
+
|
|
75
|
+
# Strip keychain password from command output
|
|
76
|
+
# sensitive_command = command.gsub(password_part, " -k ********")
|
|
77
|
+
# puts command
|
|
78
|
+
# UI.command(sensitive_command) if output
|
|
79
|
+
Open3.popen3(command) do |stdin, stdout, stderr, thrd|
|
|
80
|
+
unless thrd.value.success?
|
|
81
|
+
err = stderr.read.to_s.strip
|
|
82
|
+
|
|
83
|
+
# Inform user when no/wrong password was used as its needed to prevent UI permission popup from Xcode when signing
|
|
84
|
+
if err.include?("SecKeychainItemSetAccessWithPassword")
|
|
85
|
+
keychain_name = File.basename(keychain_path, ".*")
|
|
86
|
+
# puts "password #{keychain_password}"
|
|
87
|
+
# puts "keychain #{keychain_name}"
|
|
88
|
+
# puts "security #{server_name(keychain_name)}"
|
|
89
|
+
Security::InternetPassword.delete(server: server_name(keychain_name))
|
|
90
|
+
|
|
91
|
+
# UI.important("")
|
|
92
|
+
# UI.important("Could not configure imported keychain item (certificate) to prevent UI permission popup when code signing\n" \
|
|
93
|
+
# "Check if you supplied the correct `keychain_password` for keychain: `#{keychain_path}`\n" \
|
|
94
|
+
# "#{err}")
|
|
95
|
+
# UI.important("")
|
|
96
|
+
# UI.important("Please look at the following docs to see how to set a keychain password:")
|
|
97
|
+
# UI.important(" - https://docs.fastlane.tools/actions/sync_code_signing")
|
|
98
|
+
# UI.important(" - https://docs.fastlane.tools/actions/get_certificates")
|
|
99
|
+
# puts "证书密码错误!"
|
|
100
|
+
else
|
|
101
|
+
puts err
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Hiding after Open3 finishes
|
|
107
|
+
# Helper.hide_loading_indicator
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# https://github.com/fastlane/fastlane/issues/14196
|
|
113
|
+
# Keychain password is needed to set the partition list to
|
|
114
|
+
# prevent Xcode from prompting dialog for keychain password when signing
|
|
115
|
+
# 1. Uses keychain password from login keychain if found
|
|
116
|
+
# 2. Prompts user for keychain password and stores it in login keychain for user later
|
|
117
|
+
def resolve_keychain_password(keychain_path)
|
|
118
|
+
keychain_name = File.basename(keychain_path, ".*")
|
|
119
|
+
server = server_name(keychain_name)
|
|
120
|
+
|
|
121
|
+
# Attempt to find password in keychain for keychain
|
|
122
|
+
item = Security::InternetPassword.find(server: server)
|
|
123
|
+
if item
|
|
124
|
+
keychain_password = item.password
|
|
125
|
+
# UI.important("Using keychain password from keychain item #{server} in #{keychain_path}")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if keychain_password.nil?
|
|
129
|
+
# if UI.interactive?
|
|
130
|
+
# UI.important("Enter the password for #{keychain_path}")
|
|
131
|
+
# UI.important("This passphrase will be stored in your local keychain with the name #{server} and used in future runs")
|
|
132
|
+
# UI.important("This prompt can be avoided by specifying the 'keychain_password' option or 'MATCH_KEYCHAIN_PASSWORD' environment variable")
|
|
133
|
+
keychain_password = FastlaneCore::Helper.ask_password(message: "Password for #{keychain_name} keychain: ", confirm: true, confirmation_message: "Type password for #{keychain_name} keychain again: ")
|
|
134
|
+
Security::InternetPassword.add(server, "", keychain_password)
|
|
135
|
+
# else
|
|
136
|
+
# UI.important("Keychain password for #{keychain_path} was not specified and not found in your keychain. Specify the 'keychain_password' option to prevent the UI permission popup when code signing")
|
|
137
|
+
# keychain_password = ""
|
|
138
|
+
# end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
return keychain_password
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# server name used for accessing the macOS keychain
|
|
145
|
+
def server_name(keychain_name)
|
|
146
|
+
["pindo", "keychain", keychain_name].join("_")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private :server_name
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
module Pindo
|
|
3
|
+
|
|
4
|
+
class PemHelper
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
def create_certificate(bundle_id:nil, type:"prod", output_path:"")
|
|
8
|
+
|
|
9
|
+
if bundle_id.empty? || output_path.empty?
|
|
10
|
+
UI.user_error!("bundle id is nil or output_path is nil.")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
puts "Creating a new push certificate for app '#{bundle_id}'. "
|
|
14
|
+
|
|
15
|
+
csr, pkey = Spaceship::Portal.certificate.create_certificate_signing_request
|
|
16
|
+
|
|
17
|
+
cert = nil
|
|
18
|
+
begin
|
|
19
|
+
if type == "dev"
|
|
20
|
+
cert = Spaceship::Portal.certificate.development_push.create!(csr: csr, bundle_id: bundle_id)
|
|
21
|
+
else
|
|
22
|
+
cert = Spaceship::Portal.certificate.production_push.create!(csr: csr, bundle_id: bundle_id)
|
|
23
|
+
end
|
|
24
|
+
rescue => ex
|
|
25
|
+
if ex.to_s.include?("You already have a current")
|
|
26
|
+
# That's the most common failure probably
|
|
27
|
+
raise Informative, "You already have 2 active push profiles for this application/environment. You'll need to revoke an old certificate to make room for a new one"
|
|
28
|
+
else
|
|
29
|
+
raise ex
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
x509_certificate = cert.download
|
|
35
|
+
certificate_type = (type == "dev" ? 'development' : 'production')
|
|
36
|
+
|
|
37
|
+
base_base = bundle_id.gsub('.', '_')
|
|
38
|
+
puts base_base
|
|
39
|
+
filename_base = base_base + '_' + type
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
private_key_path = File.join(output_path, "#{filename_base}.pkey")
|
|
43
|
+
File.write(private_key_path, pkey.to_pem)
|
|
44
|
+
puts "key: #{private_key_path}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
p12_cert_path = File.join(output_path, "#{filename_base}.p12")
|
|
48
|
+
p12 = OpenSSL::PKCS12.create('goodcert1', certificate_type, pkey, x509_certificate)
|
|
49
|
+
File.write(p12_cert_path, p12.to_der)
|
|
50
|
+
puts "p12 : #{p12_cert_path}"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
x509_cert_path = File.join(output_path, "#{filename_base}.pem")
|
|
54
|
+
File.write(x509_cert_path, x509_certificate.to_pem + pkey.to_pem)
|
|
55
|
+
puts "pem : #{x509_cert_path}"
|
|
56
|
+
|
|
57
|
+
return x509_cert_path
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def login(apple_id:nil)
|
|
61
|
+
puts apple_id
|
|
62
|
+
Spaceship::Portal.login(apple_id.to_s)
|
|
63
|
+
Spaceship::Portal.select_team
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -346,13 +346,13 @@ module Pindo
|
|
|
346
346
|
|
|
347
347
|
|
|
348
348
|
keys = Dir[File.join(certs_dir, "certs", cert_sub_dir, "*.p12")]
|
|
349
|
-
decrypt_password = CertHelper.get_cached_password(cert_git_url)
|
|
349
|
+
decrypt_password = Pindo::CertHelper.get_cached_password(cert_git_url)
|
|
350
350
|
output_dir = Dir.mktmpdir
|
|
351
351
|
key_path = AESHelper.decrypt_specific_file(src_file: keys.first, password:decrypt_password, output_dir: output_dir)
|
|
352
352
|
if key_path.nil? || key_path.empty? || !File.exist?(key_path)
|
|
353
353
|
AESHelper.delete_password(keychain_name:cert_git_url)
|
|
354
354
|
# 清除内存中的密码缓存,避免重复使用错误密码
|
|
355
|
-
CertHelper.clear_password_cache_for_url(cert_git_url)
|
|
355
|
+
Pindo::CertHelper.clear_password_cache_for_url(cert_git_url)
|
|
356
356
|
raise Informative, "证书解析失败,密码错误!"
|
|
357
357
|
end
|
|
358
358
|
|
|
@@ -192,11 +192,6 @@ module Pindo
|
|
|
192
192
|
|
|
193
193
|
# 如果是 Android 平台,检查 Java 版本
|
|
194
194
|
platform = additional_args[:platform]
|
|
195
|
-
if platform == 'Android'
|
|
196
|
-
unless ensure_java_version_for_android
|
|
197
|
-
raise Informative, "Java 版本不符合要求,无法继续构建 Android 项目"
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
195
|
|
|
201
196
|
cmd_args = [
|
|
202
197
|
unity_exe_full_path,
|
|
@@ -887,18 +882,6 @@ module Pindo
|
|
|
887
882
|
File.exist?(File.join(project_settings_path, "ProjectSettings.asset"))
|
|
888
883
|
end
|
|
889
884
|
|
|
890
|
-
# 确保 Android 构建所需的 Java 版本
|
|
891
|
-
def ensure_java_version_for_android
|
|
892
|
-
# 动态加载 BaseAndroidHelper 模块
|
|
893
|
-
require_relative '../android/base_helper'
|
|
894
|
-
|
|
895
|
-
# 创建一个临时对象来调用方法
|
|
896
|
-
helper = Object.new
|
|
897
|
-
helper.extend(Pindo::BaseAndroidHelper)
|
|
898
|
-
|
|
899
|
-
# 调用 Java 版本检测方法
|
|
900
|
-
helper.ensure_java_version_compliance
|
|
901
|
-
end
|
|
902
885
|
end
|
|
903
886
|
end
|
|
904
887
|
end
|
|
@@ -33,7 +33,7 @@ module Pindo
|
|
|
33
33
|
if icon_path.nil? || icon_path.empty? || !File.exist?(icon_path)
|
|
34
34
|
icon_path_array = Dir.glob(File.join(project_dir, "**", "AppIcon.appiconset"))
|
|
35
35
|
if icon_path_array.size > 1
|
|
36
|
-
icon_path = icon_path_array.
|
|
36
|
+
icon_path = icon_path_array.find{ |filename| File.directory?(filename) && filename.include?("Assets.xcassets/AppIcon.appiconset")}
|
|
37
37
|
elsif icon_path_array.size == 1
|
|
38
38
|
icon_path = icon_path_array.first
|
|
39
39
|
end
|