pindo 5.10.4 → 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 +5 -2
- data/lib/pindo/command/deploy/cert.rb +6 -7
- 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 +2 -2
- data/lib/pindo/command/dev/build.rb +2 -2
- 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 +7 -6
- data/lib/pindo/command/ios/autobuild.rb +25 -25
- data/lib/pindo/command/ios/build.rb +7 -6
- data/lib/pindo/command/ios/debug.rb +2 -1
- data/lib/pindo/command/ipa/autoresign.rb +2 -2
- data/lib/pindo/command/ipa/import.rb +3 -4
- data/lib/pindo/command/ipa/output.rb +3 -4
- data/lib/pindo/command/jps/upload.rb +6 -5
- data/lib/pindo/command/repo/login.rb +1 -1
- 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/device.rb +4 -4
- data/lib/pindo/command/utils/icon.rb +1 -1
- data/lib/pindo/command/utils/renewcert.rb +2 -3
- data/lib/pindo/command/utils/renewproj.rb +9 -10
- data/lib/pindo/command/web/autobuild.rb +3 -3
- 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 +6 -5
- data/lib/pindo/module/pgyer/pgyerhelper.rb +5 -5
- 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} +84 -2
- 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
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
require 'singleton'
|
|
2
|
-
require_relative 'base_helper'
|
|
3
|
-
require_relative 'gradle_helper'
|
|
4
|
-
require_relative 'so_helper'
|
|
5
|
-
require_relative 'apk_helper'
|
|
6
|
-
require 'fileutils'
|
|
7
|
-
|
|
8
|
-
module Pindo
|
|
9
|
-
class AndroidBuildHelper
|
|
10
|
-
include BaseAndroidHelper
|
|
11
|
-
include GradleHelper
|
|
12
|
-
include SoHelper
|
|
13
|
-
include ApkHelper
|
|
14
|
-
include Singleton
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class << self
|
|
19
|
-
def share_instance
|
|
20
|
-
instance
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# 该方法已迁移到 Pindo::AndroidBuildConfigHelper.add_test_scheme
|
|
25
|
-
# 保留代理方法以兼容旧调用
|
|
26
|
-
def add_test_scheme(project_dir:nil, scheme_name:nil)
|
|
27
|
-
puts "Warning: add_test_scheme已迁移,请使用Pindo::AndroidBuildConfigHelper.add_test_scheme"
|
|
28
|
-
require_relative 'android_build_config_helper'
|
|
29
|
-
Pindo::AndroidBuildConfigHelper.add_test_scheme(project_dir: project_dir, scheme_name: scheme_name)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def auto_build_apk(project_dir, debug = false, ignore_sub = false)
|
|
33
|
-
# 检查 gradle.properties 中是否设置了 Java Home
|
|
34
|
-
gradle_properties_path = File.join(project_dir, "gradle.properties")
|
|
35
|
-
if File.exist?(gradle_properties_path)
|
|
36
|
-
puts "检查 gradle.properties 中的 Java Home 设置..."
|
|
37
|
-
content = File.read(gradle_properties_path)
|
|
38
|
-
java_home_match = content.match(/org\.gradle\.java\.home\s*=\s*(.+)/)
|
|
39
|
-
if java_home_match && !java_home_match[1].empty?
|
|
40
|
-
java_home_path = java_home_match[1].strip
|
|
41
|
-
puts "找到 Java Home 路径: #{java_home_path}"
|
|
42
|
-
# 设置环境变量
|
|
43
|
-
ENV['JAVA_HOME'] = java_home_path
|
|
44
|
-
ENV['PATH'] = "#{java_home_path}/bin:#{ENV['PATH']}"
|
|
45
|
-
puts "已设置 JAVA_HOME 环境变量为: #{java_home_path}"
|
|
46
|
-
puts "Java 版本信息:"
|
|
47
|
-
system("java -version")
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# 检查
|
|
52
|
-
if !ignore_sub
|
|
53
|
-
sub_android_dir = find_android_subproject(project_dir)
|
|
54
|
-
if sub_android_dir
|
|
55
|
-
prepare_proj(sub_android_dir)
|
|
56
|
-
# 构建 AAB 文件
|
|
57
|
-
unless build_so_library(sub_android_dir)
|
|
58
|
-
raise RuntimeError, "编译SO库失败:"
|
|
59
|
-
end
|
|
60
|
-
copy_so_files(sub_android_dir, project_dir)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
prepare_proj(project_dir)
|
|
65
|
-
build_apk(project_dir, debug)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def get_application_id(project_path)
|
|
69
|
-
main_module = get_main_module(project_path)
|
|
70
|
-
return nil unless main_module
|
|
71
|
-
|
|
72
|
-
# 尝试从 build.gradle 获取
|
|
73
|
-
gradle_path = File.join(main_module, "build.gradle")
|
|
74
|
-
if File.exist?(gradle_path)
|
|
75
|
-
content = File.read(gradle_path)
|
|
76
|
-
if content =~ /applicationId\s+['"]([^'"]+)['"]/
|
|
77
|
-
return $1
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# 如果 build.gradle 中没有,尝试从 AndroidManifest.xml 获取
|
|
82
|
-
manifest_path = File.join(main_module, "src", "main", "AndroidManifest.xml")
|
|
83
|
-
if File.exist?(manifest_path)
|
|
84
|
-
require 'nokogiri'
|
|
85
|
-
doc = Nokogiri::XML(File.read(manifest_path))
|
|
86
|
-
package = doc.at_xpath('//manifest/@package')&.value
|
|
87
|
-
return package if package
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
nil
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def dsign(project_path, debug)
|
|
94
|
-
keystore_config = get_keystore_config(project_path, debug)
|
|
95
|
-
|
|
96
|
-
ks = keystore_config[:store_file]
|
|
97
|
-
# puts "读取 keystore path = #{ks}"
|
|
98
|
-
ks_pass = keystore_config[:store_password]
|
|
99
|
-
# puts "读取 keystore pass = #{ks_pass}"
|
|
100
|
-
key_alias = keystore_config[:key_alias]
|
|
101
|
-
# puts "读取 key alias = #{key_alias}"
|
|
102
|
-
key_pass = keystore_config[:key_password]
|
|
103
|
-
# puts "读取 key pass = #{key_pass}"
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
private
|
|
107
|
-
|
|
108
|
-
def prepare_proj(project_dir)
|
|
109
|
-
raise ArgumentError, "项目目录不能为空" if project_dir.nil?
|
|
110
|
-
raise ArgumentError, "项目目录不存在" unless File.directory?(project_dir)
|
|
111
|
-
|
|
112
|
-
check_gradle_files(project_dir)
|
|
113
|
-
if unity_android_project?(project_dir)
|
|
114
|
-
update_build_gradle(project_dir)
|
|
115
|
-
update_gradle_version(project_dir)
|
|
116
|
-
modify_il2cpp_config(project_dir)
|
|
117
|
-
remove_desktop_google_service(project_dir)
|
|
118
|
-
end
|
|
119
|
-
rescue StandardError => e
|
|
120
|
-
raise Informative, "准备项目失败: #{e.message}"
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# 相关辅助方法已迁移到 Pindo::AndroidBuildConfigHelper
|
|
124
|
-
# find_manifests, find_best_activity, check_scheme_exists等方法已被移除
|
|
125
|
-
# 请使用 AndroidBuildConfigHelper 中的相应方法
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
module Pindo
|
|
2
|
-
module SoHelper
|
|
3
|
-
|
|
4
|
-
def build_so_library(project_path)
|
|
5
|
-
# 编译so库
|
|
6
|
-
Dir.chdir(project_path) do
|
|
7
|
-
system("./gradlew unityLibrary:BuildIl2CppTask")
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def copy_so_files(source_path, target_path)
|
|
12
|
-
# 复制so文件到正确的目录
|
|
13
|
-
src_dir = File.join(source_path, "unityLibrary/src/main/assets")
|
|
14
|
-
dst_dir = File.join(target_path, "unityLibrary/src/main/assets")
|
|
15
|
-
FileUtils.cp_r(src_dir, dst_dir) if File.directory?(src_dir)
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require 'match'
|
|
4
|
-
require 'openssl'
|
|
5
|
-
require 'pindo/base/aeshelper'
|
|
6
|
-
require 'pindo/module/cert/keychainhelper'
|
|
7
|
-
require 'pindo/module/cert/provisioninghelper'
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
module Pindo
|
|
11
|
-
|
|
12
|
-
module CertHelper
|
|
13
|
-
# 密码缓存,避免重复获取相同URL的密码
|
|
14
|
-
@@password_cache = {}
|
|
15
|
-
|
|
16
|
-
# 获取密码的辅助方法,使用缓存避免重复获取
|
|
17
|
-
def self.get_cached_password(cert_url)
|
|
18
|
-
unless @@password_cache[cert_url]
|
|
19
|
-
puts "\e[33m[DEBUG] 密码缓存中未找到,从Keychain获取: #{cert_url}\e[0m" if ENV['PINDO_DEBUG']
|
|
20
|
-
@@password_cache[cert_url] = AESHelper.fetch_password(keychain_name: cert_url)
|
|
21
|
-
puts "\e[32m[DEBUG] 密码已缓存: #{cert_url}\e[0m" if ENV['PINDO_DEBUG']
|
|
22
|
-
else
|
|
23
|
-
puts "\e[32m[DEBUG] 从密码缓存获取: #{cert_url}\e[0m" if ENV['PINDO_DEBUG']
|
|
24
|
-
end
|
|
25
|
-
@@password_cache[cert_url]
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# 清除密码缓存
|
|
29
|
-
def self.clear_password_cache
|
|
30
|
-
@@password_cache.clear
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# 清除特定URL的密码缓存
|
|
34
|
-
def self.clear_password_cache_for_url(cert_url)
|
|
35
|
-
@@password_cache.delete(cert_url)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def get_cert_info(cer_certificate)
|
|
39
|
-
# can receive a certificate path or the file data
|
|
40
|
-
begin
|
|
41
|
-
if File.exist?(cer_certificate)
|
|
42
|
-
cer_certificate = File.binread(cer_certificate)
|
|
43
|
-
end
|
|
44
|
-
rescue ArgumentError
|
|
45
|
-
# cert strings have null bytes; suppressing output
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
cert = OpenSSL::X509::Certificate.new(cer_certificate)
|
|
49
|
-
|
|
50
|
-
# openssl output:
|
|
51
|
-
# subject= /UID={User ID}/CN={Certificate Name}/OU={Certificate User}/O={Organisation}/C={Country}
|
|
52
|
-
cert_info = cert.subject.to_s.gsub(/\s*subject=\s*/, "").tr("/", "\n")
|
|
53
|
-
out_array = cert_info.split("\n")
|
|
54
|
-
openssl_keys_to_readable_keys = {
|
|
55
|
-
'UID' => 'User ID',
|
|
56
|
-
'CN' => 'Common Name',
|
|
57
|
-
'OU' => 'Organisation Unit',
|
|
58
|
-
'O' => 'Organisation',
|
|
59
|
-
'C' => 'Country',
|
|
60
|
-
'notBefore' => 'Start Datetime',
|
|
61
|
-
'notAfter' => 'End Datetime'
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return out_array.map { |x| x.split(/=+/) if x.include?("=") }
|
|
65
|
-
.compact
|
|
66
|
-
.map { |k, v| [openssl_keys_to_readable_keys.fetch(k, k), v] }
|
|
67
|
-
.push([openssl_keys_to_readable_keys.fetch("notBefore"), cert.not_before])
|
|
68
|
-
.push([openssl_keys_to_readable_keys.fetch("notAfter"), cert.not_after])
|
|
69
|
-
rescue => ex
|
|
70
|
-
raise Informative, "get_cert_info: #{ex}"
|
|
71
|
-
return {}
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def select_cert_or_key(paths:)
|
|
75
|
-
cert_id_path = ENV['MATCH_CERTIFICATE_ID'] ? paths.find { |path| path.include?(ENV['MATCH_CERTIFICATE_ID']) } : nil
|
|
76
|
-
cert_id_path || paths.last
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def is_cert_valid?(cer_certificate_path)
|
|
80
|
-
cert = OpenSSL::X509::Certificate.new(File.binread(cer_certificate_path))
|
|
81
|
-
now = Time.now.utc
|
|
82
|
-
return (now <=> cert.not_after) == -1
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def isMac?
|
|
86
|
-
(/darwin/ =~ RUBY_PLATFORM) != nil
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def install_certs(cert_url:nil, certs_dir:nil, cert_type:nil, platform_type:nil)
|
|
90
|
-
|
|
91
|
-
cert_git_dir = cert_type.downcase
|
|
92
|
-
if platform_type.downcase.eql?("macos")
|
|
93
|
-
if cert_type.downcase.include?("development")
|
|
94
|
-
cert_git_dir = "development"
|
|
95
|
-
elsif cert_type.downcase.eql?("appstore")
|
|
96
|
-
cert_git_dir = "distribution"
|
|
97
|
-
else
|
|
98
|
-
cert_git_dir = "developer_id_application"
|
|
99
|
-
end
|
|
100
|
-
else
|
|
101
|
-
if !cert_type.downcase.include?("development")
|
|
102
|
-
cert_git_dir = "distribution"
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
certs = Dir[File.join(certs_dir, "certs", cert_git_dir.to_s, "*.cer")]
|
|
107
|
-
keys = Dir[File.join(certs_dir, "certs", cert_git_dir.to_s, "*.p12")]
|
|
108
|
-
|
|
109
|
-
if certs.count == 0 || keys.count == 0
|
|
110
|
-
raise Informative, "No certificates found in #{certs_dir}"
|
|
111
|
-
else
|
|
112
|
-
output_dir = Dir.mktmpdir
|
|
113
|
-
|
|
114
|
-
decrypt_password = CertHelper.get_cached_password(cert_url)
|
|
115
|
-
Funlog.instance.fancyinfo_start("正在安装证书...")
|
|
116
|
-
|
|
117
|
-
cert_path = AESHelper.decrypt_specific_file(src_file: certs.first, password:decrypt_password, output_dir: output_dir)
|
|
118
|
-
if cert_path.nil? || cert_path.empty? || !File.exist?(cert_path)
|
|
119
|
-
AESHelper.delete_password(keychain_name:cert_url)
|
|
120
|
-
# 清除内存中的密码缓存,避免重复使用错误密码
|
|
121
|
-
@@password_cache.delete(cert_url)
|
|
122
|
-
raise Informative, "证书解析失败,密码错误!"
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
key_path = AESHelper.decrypt_specific_file(src_file: keys.first, password:decrypt_password, output_dir: output_dir)
|
|
126
|
-
if key_path.nil? || key_path.empty? || !File.exist?(key_path)
|
|
127
|
-
AESHelper.delete_password(keychain_name:cert_url)
|
|
128
|
-
# 清除内存中的密码缓存,避免重复使用错误密码
|
|
129
|
-
@@password_cache.delete(cert_url)
|
|
130
|
-
raise Informative, "证书解析失败,密码错误!"
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
unless is_cert_valid?(cert_path)
|
|
134
|
-
raise Informative, "证书已经过期,请重新生产新证书!"
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if isMac?
|
|
139
|
-
|
|
140
|
-
keychain_name = "login.keychain"
|
|
141
|
-
if FastlaneCore::CertChecker.installed?(cert_path, in_keychain: nil)
|
|
142
|
-
Funlog.instance.fancyinfo_success("证书#{File.basename(cert_path)}已安装,无需重复安装!")
|
|
143
|
-
else
|
|
144
|
-
|
|
145
|
-
cert_password = Pindoconfig.instance.cert_key_password
|
|
146
|
-
keychain = 'login.keychain'
|
|
147
|
-
keychain_path = FastlaneCore::Helper.keychain_path(keychain)
|
|
148
|
-
|
|
149
|
-
KeychainHelper.import_file(cert_path, keychain_path, keychain_password: cert_password, certificate_password:'' )
|
|
150
|
-
KeychainHelper.import_file(key_path, keychain_path, keychain_password: cert_password, certificate_password: '')
|
|
151
|
-
|
|
152
|
-
Funlog.instance.fancyinfo_success("证书'#{File.basename(cert_path)}'安装完成!")
|
|
153
|
-
|
|
154
|
-
end
|
|
155
|
-
else
|
|
156
|
-
Funlog.instance.fancyinfo_error("非Mac电脑不支持安装证书!")
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def install_provisionfiles(cert_url:nil, certs_dir:nil, bundle_id_map:nil, cert_type:nil, platform_type:nil)
|
|
162
|
-
|
|
163
|
-
cert_sub_dir = cert_type.downcase
|
|
164
|
-
provision_start_name = "Development"
|
|
165
|
-
provision_extension_name = ".mobileprovision"
|
|
166
|
-
|
|
167
|
-
if platform_type.downcase.include?("macos")
|
|
168
|
-
provision_extension_name = ".provisionprofile"
|
|
169
|
-
|
|
170
|
-
if cert_type.downcase.include?("development")
|
|
171
|
-
provision_start_name = "Development"
|
|
172
|
-
cert_sub_dir = cert_type.downcase
|
|
173
|
-
elsif cert_type.downcase.eql?("appstore")
|
|
174
|
-
provision_start_name = "AppStore"
|
|
175
|
-
cert_sub_dir = "appstore"
|
|
176
|
-
else
|
|
177
|
-
provision_start_name = "Direct"
|
|
178
|
-
cert_sub_dir = "developer_id"
|
|
179
|
-
end
|
|
180
|
-
else
|
|
181
|
-
provision_extension_name = ".mobileprovision"
|
|
182
|
-
if cert_type.downcase.include?("development")
|
|
183
|
-
provision_start_name = "Development"
|
|
184
|
-
cert_sub_dir = cert_type.downcase
|
|
185
|
-
elsif cert_type.downcase.include?("adhoc")
|
|
186
|
-
provision_start_name = "Adhoc"
|
|
187
|
-
cert_sub_dir = "adhoc"
|
|
188
|
-
else
|
|
189
|
-
provision_start_name = "AppStore"
|
|
190
|
-
cert_sub_dir = "appstore"
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
Funlog.instance.fancyinfo_start("正在安装#{provision_start_name} #{platform_type} Provisioning Profiles...")
|
|
196
|
-
|
|
197
|
-
un_exist_files = []
|
|
198
|
-
provisioning_info_array = []
|
|
199
|
-
|
|
200
|
-
# 在循环外获取密码,避免重复添加到Keychain
|
|
201
|
-
decrypt_password = CertHelper.get_cached_password(cert_url)
|
|
202
|
-
|
|
203
|
-
bundle_id_map.each do |type, bundle_id_temp|
|
|
204
|
-
profile_filename = File.join(certs_dir, "profiles", cert_sub_dir, [provision_start_name.to_s, bundle_id_temp].join('_') + provision_extension_name)
|
|
205
|
-
unless File.exist?(profile_filename)
|
|
206
|
-
un_exist_files << profile_filename
|
|
207
|
-
next
|
|
208
|
-
end
|
|
209
|
-
# puts "正在安装 #{bundle_id_temp}..."
|
|
210
|
-
output_dir = Dir.mktmpdir
|
|
211
|
-
file_decrypt = AESHelper.decrypt_specific_file(src_file: profile_filename, password:decrypt_password, output_dir: output_dir)
|
|
212
|
-
destpath = Provisioninghelper.install(file_decrypt)
|
|
213
|
-
parsed_data = Provisioninghelper.parse(destpath)
|
|
214
|
-
|
|
215
|
-
provisioning_info = {}
|
|
216
|
-
provisioning_info['type'] = type
|
|
217
|
-
provisioning_info['bundle_id'] = bundle_id_temp
|
|
218
|
-
provisioning_info['profile_name'] = parsed_data['Name']
|
|
219
|
-
provisioning_info['profile_path'] = destpath
|
|
220
|
-
|
|
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
|
-
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
end
|
|
246
|
-
end
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
require 'open3'
|
|
3
|
-
require 'security'
|
|
4
|
-
|
|
5
|
-
module Pindo
|
|
6
|
-
|
|
7
|
-
module KeychainHelper
|
|
8
|
-
|
|
9
|
-
def self.import_file(path, keychain_path, keychain_password: nil, certificate_password: "", skip_set_partition_list: false, output: false)
|
|
10
|
-
# puts path
|
|
11
|
-
|
|
12
|
-
# puts "Could not find file '#{path}'" unless File.exist?(path)
|
|
13
|
-
|
|
14
|
-
password_part = " -P #{certificate_password.shellescape}"
|
|
15
|
-
|
|
16
|
-
command = "security import #{path.shellescape} -k '#{keychain_path.shellescape}'"
|
|
17
|
-
command << password_part
|
|
18
|
-
command << " -T /usr/bin/codesign" # to not be asked for permission when running a tool like `gym` (before Sierra)
|
|
19
|
-
command << " -T /usr/bin/security"
|
|
20
|
-
command << " -T /usr/bin/productbuild" # to not be asked for permission when using an installer cert for macOS
|
|
21
|
-
command << " -T /usr/bin/productsign" # to not be asked for permission when using an installer cert for macOS
|
|
22
|
-
command << " 1> /dev/null" unless output
|
|
23
|
-
|
|
24
|
-
# puts command
|
|
25
|
-
# sensitive_command = command.gsub(password_part, " -P ********")
|
|
26
|
-
|
|
27
|
-
# UI.command(sensitive_command) if output
|
|
28
|
-
Open3.popen3(command) do |stdin, stdout, stderr, thrd|
|
|
29
|
-
# UI.command_output(stdout.read.to_s) if output
|
|
30
|
-
# puts "123"
|
|
31
|
-
# Set partition list only if success since it can be a time consuming process if a lot of keys are installed
|
|
32
|
-
if thrd.value.success? && !skip_set_partition_list
|
|
33
|
-
# puts "安装成功!"
|
|
34
|
-
keychain_password ||= resolve_keychain_password(keychain_path)
|
|
35
|
-
set_partition_list(path, keychain_path, keychain_password: keychain_password, output: output)
|
|
36
|
-
else
|
|
37
|
-
# Output verbose if file is already installed since not an error otherwise we will show the whole error
|
|
38
|
-
err = stderr.read.to_s.strip
|
|
39
|
-
|
|
40
|
-
if err.include?("SecKeychainItemImport") && err.include?("The specified item already exists in the keychain")
|
|
41
|
-
# UI.verbose("'}' is already installed on this machine")
|
|
42
|
-
# puts "证书key已经存在,安装完成!"
|
|
43
|
-
else
|
|
44
|
-
# puts err
|
|
45
|
-
# raise Informative, err
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def self.help_backticks(command, print: true)
|
|
52
|
-
# UI.command(command) if print
|
|
53
|
-
result = `#{command}`
|
|
54
|
-
# UI.command_output(result) if print
|
|
55
|
-
return result
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def self.set_partition_list(path, keychain_path, keychain_password: nil, output: false)
|
|
59
|
-
# When security supports partition lists, also add the partition IDs
|
|
60
|
-
# See https://openradar.appspot.com/28524119
|
|
61
|
-
if help_backticks('security -h | grep set-key-partition-list', print: false).length > 0
|
|
62
|
-
password_part = " -k #{keychain_password.to_s.shellescape}"
|
|
63
|
-
|
|
64
|
-
command = "security set-key-partition-list"
|
|
65
|
-
command << " -S apple-tool:,apple:,codesign:"
|
|
66
|
-
command << " -s" # This is a needed in Catalina to prevent "security: SecKeychainItemCopyAccess: A missing value was detected."
|
|
67
|
-
command << password_part
|
|
68
|
-
command << " #{keychain_path.shellescape}"
|
|
69
|
-
command << " 1> /dev/null" # always disable stdout. This can be very verbose, and leak potentially sensitive info
|
|
70
|
-
|
|
71
|
-
# Showing loading indicator as this can take some time if a lot of keys installed
|
|
72
|
-
# Helper.show_loading_indicator("Setting key partition list... (this can take a minute if there are a lot of keys installed)")
|
|
73
|
-
|
|
74
|
-
# Strip keychain password from command output
|
|
75
|
-
# sensitive_command = command.gsub(password_part, " -k ********")
|
|
76
|
-
# puts command
|
|
77
|
-
# UI.command(sensitive_command) if output
|
|
78
|
-
Open3.popen3(command) do |stdin, stdout, stderr, thrd|
|
|
79
|
-
unless thrd.value.success?
|
|
80
|
-
err = stderr.read.to_s.strip
|
|
81
|
-
|
|
82
|
-
# Inform user when no/wrong password was used as its needed to prevent UI permission popup from Xcode when signing
|
|
83
|
-
if err.include?("SecKeychainItemSetAccessWithPassword")
|
|
84
|
-
keychain_name = File.basename(keychain_path, ".*")
|
|
85
|
-
# puts "password #{keychain_password}"
|
|
86
|
-
# puts "keychain #{keychain_name}"
|
|
87
|
-
# puts "security #{server_name(keychain_name)}"
|
|
88
|
-
Security::InternetPassword.delete(server: server_name(keychain_name))
|
|
89
|
-
|
|
90
|
-
# UI.important("")
|
|
91
|
-
# UI.important("Could not configure imported keychain item (certificate) to prevent UI permission popup when code signing\n" \
|
|
92
|
-
# "Check if you supplied the correct `keychain_password` for keychain: `#{keychain_path}`\n" \
|
|
93
|
-
# "#{err}")
|
|
94
|
-
# UI.important("")
|
|
95
|
-
# UI.important("Please look at the following docs to see how to set a keychain password:")
|
|
96
|
-
# UI.important(" - https://docs.fastlane.tools/actions/sync_code_signing")
|
|
97
|
-
# UI.important(" - https://docs.fastlane.tools/actions/get_certificates")
|
|
98
|
-
# puts "证书密码错误!"
|
|
99
|
-
else
|
|
100
|
-
puts err
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Hiding after Open3 finishes
|
|
106
|
-
# Helper.hide_loading_indicator
|
|
107
|
-
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# https://github.com/fastlane/fastlane/issues/14196
|
|
112
|
-
# Keychain password is needed to set the partition list to
|
|
113
|
-
# prevent Xcode from prompting dialog for keychain password when signing
|
|
114
|
-
# 1. Uses keychain password from login keychain if found
|
|
115
|
-
# 2. Prompts user for keychain password and stores it in login keychain for user later
|
|
116
|
-
def self.resolve_keychain_password(keychain_path)
|
|
117
|
-
keychain_name = File.basename(keychain_path, ".*")
|
|
118
|
-
server = server_name(keychain_name)
|
|
119
|
-
|
|
120
|
-
# Attempt to find password in keychain for keychain
|
|
121
|
-
item = Security::InternetPassword.find(server: server)
|
|
122
|
-
if item
|
|
123
|
-
keychain_password = item.password
|
|
124
|
-
# UI.important("Using keychain password from keychain item #{server} in #{keychain_path}")
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
if keychain_password.nil?
|
|
128
|
-
# if UI.interactive?
|
|
129
|
-
# UI.important("Enter the password for #{keychain_path}")
|
|
130
|
-
# UI.important("This passphrase will be stored in your local keychain with the name #{server} and used in future runs")
|
|
131
|
-
# UI.important("This prompt can be avoided by specifying the 'keychain_password' option or 'MATCH_KEYCHAIN_PASSWORD' environment variable")
|
|
132
|
-
keychain_password = FastlaneCore::Helper.ask_password(message: "Password for #{keychain_name} keychain: ", confirm: true, confirmation_message: "Type password for #{keychain_name} keychain again: ")
|
|
133
|
-
Security::InternetPassword.add(server, "", keychain_password)
|
|
134
|
-
# else
|
|
135
|
-
# 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")
|
|
136
|
-
# keychain_password = ""
|
|
137
|
-
# end
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
return keychain_password
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# server name used for accessing the macOS keychain
|
|
144
|
-
def self.server_name(keychain_name)
|
|
145
|
-
["pindo", "keychain", keychain_name].join("_")
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
private_class_method :server_name
|
|
149
|
-
end
|
|
150
|
-
end
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
module Pindo
|
|
3
|
-
|
|
4
|
-
module PemHelper
|
|
5
|
-
|
|
6
|
-
def create_certificate(bundle_id:nil, type:"prod", output_path:"")
|
|
7
|
-
|
|
8
|
-
if bundle_id.empty? || output_path.empty?
|
|
9
|
-
UI.user_error!("bundle id is nil or output_path is nil.")
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
puts "Creating a new push certificate for app '#{bundle_id}'. "
|
|
13
|
-
|
|
14
|
-
csr, pkey = Spaceship::Portal.certificate.create_certificate_signing_request
|
|
15
|
-
|
|
16
|
-
cert = nil
|
|
17
|
-
begin
|
|
18
|
-
if type == "dev"
|
|
19
|
-
cert = Spaceship::Portal.certificate.development_push.create!(csr: csr, bundle_id: bundle_id)
|
|
20
|
-
else
|
|
21
|
-
cert = Spaceship::Portal.certificate.production_push.create!(csr: csr, bundle_id: bundle_id)
|
|
22
|
-
end
|
|
23
|
-
rescue => ex
|
|
24
|
-
if ex.to_s.include?("You already have a current")
|
|
25
|
-
# That's the most common failure probably
|
|
26
|
-
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"
|
|
27
|
-
else
|
|
28
|
-
raise ex
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
x509_certificate = cert.download
|
|
34
|
-
certificate_type = (type == "dev" ? 'development' : 'production')
|
|
35
|
-
|
|
36
|
-
base_base = bundle_id.gsub('.', '_')
|
|
37
|
-
puts base_base
|
|
38
|
-
filename_base = base_base + '_' + type
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
private_key_path = File.join(output_path, "#{filename_base}.pkey")
|
|
42
|
-
File.write(private_key_path, pkey.to_pem)
|
|
43
|
-
puts "key: #{private_key_path}"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
p12_cert_path = File.join(output_path, "#{filename_base}.p12")
|
|
47
|
-
p12 = OpenSSL::PKCS12.create('goodcert1', certificate_type, pkey, x509_certificate)
|
|
48
|
-
File.write(p12_cert_path, p12.to_der)
|
|
49
|
-
puts "p12 : #{p12_cert_path}"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
x509_cert_path = File.join(output_path, "#{filename_base}.pem")
|
|
53
|
-
File.write(x509_cert_path, x509_certificate.to_pem + pkey.to_pem)
|
|
54
|
-
puts "pem : #{x509_cert_path}"
|
|
55
|
-
|
|
56
|
-
return x509_cert_path
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def login(apple_id:nil)
|
|
60
|
-
puts apple_id
|
|
61
|
-
Spaceship::Portal.login(apple_id.to_s)
|
|
62
|
-
Spaceship::Portal.select_team
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|