app-info 2.8.2 → 3.0.0
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/.github/dependabot.yml +12 -7
- data/.github/workflows/ci.yml +7 -5
- data/.github/workflows/create_release.yml +15 -0
- data/.rubocop.yml +33 -11
- data/CHANGELOG.md +107 -1
- data/Gemfile +10 -5
- data/README.md +82 -15
- data/Rakefile +11 -0
- data/app_info.gemspec +14 -5
- data/lib/app_info/aab.rb +76 -110
- data/lib/app_info/android/signature.rb +114 -0
- data/lib/app_info/android/signatures/base.rb +53 -0
- data/lib/app_info/android/signatures/info.rb +158 -0
- data/lib/app_info/android/signatures/v1.rb +63 -0
- data/lib/app_info/android/signatures/v2.rb +121 -0
- data/lib/app_info/android/signatures/v3.rb +131 -0
- data/lib/app_info/android/signatures/v4.rb +18 -0
- data/lib/app_info/android.rb +181 -0
- data/lib/app_info/apk.rb +77 -112
- data/lib/app_info/apple.rb +192 -0
- data/lib/app_info/certificate.rb +176 -0
- data/lib/app_info/const.rb +76 -0
- data/lib/app_info/core_ext/object/try.rb +3 -1
- data/lib/app_info/core_ext/string/inflector.rb +2 -0
- data/lib/app_info/dsym/debug_info.rb +81 -0
- data/lib/app_info/dsym/macho.rb +62 -0
- data/lib/app_info/dsym.rb +35 -135
- data/lib/app_info/error.rb +3 -1
- data/lib/app_info/file.rb +49 -0
- data/lib/app_info/helper/archive.rb +37 -0
- data/lib/app_info/helper/file_size.rb +25 -0
- data/lib/app_info/helper/generate_class.rb +29 -0
- data/lib/app_info/helper/protobuf.rb +12 -0
- data/lib/app_info/helper/signatures.rb +229 -0
- data/lib/app_info/helper.rb +5 -128
- data/lib/app_info/info_plist.rb +66 -29
- data/lib/app_info/ipa/framework.rb +4 -4
- data/lib/app_info/ipa.rb +61 -135
- data/lib/app_info/macos.rb +54 -102
- data/lib/app_info/mobile_provision.rb +66 -48
- data/lib/app_info/pe.rb +322 -0
- data/lib/app_info/png_uncrush.rb +25 -5
- data/lib/app_info/proguard.rb +39 -22
- data/lib/app_info/protobuf/manifest.rb +22 -11
- data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
- data/lib/app_info/protobuf/models/README.md +8 -1
- data/lib/app_info/protobuf/models/Resources.proto +51 -0
- data/lib/app_info/protobuf/models/Resources_pb.rb +42 -0
- data/lib/app_info/protobuf/resources.rb +5 -5
- data/lib/app_info/version.rb +1 -1
- data/lib/app_info.rb +93 -43
- metadata +57 -37
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'macho'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'cfpropertylist'
|
7
|
+
|
8
|
+
module AppInfo
|
9
|
+
# Apple base parser for ipa and macos file
|
10
|
+
class Apple < File
|
11
|
+
extend Forwardable
|
12
|
+
include Helper::HumanFileSize
|
13
|
+
include Helper::Archive
|
14
|
+
|
15
|
+
# Export types
|
16
|
+
module ExportType
|
17
|
+
# debug configuration
|
18
|
+
DEBUG = :debug
|
19
|
+
# adhoc configuration (iOS only)
|
20
|
+
ADHOC = :adhoc
|
21
|
+
# enterprise configuration (iOS only)
|
22
|
+
ENTERPRISE = :enterprise
|
23
|
+
# release configuration
|
24
|
+
RELEASE = :release
|
25
|
+
# release configuration but download from App Store
|
26
|
+
APPSTORE = :appstore
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Symbol] {Manufacturer}
|
30
|
+
def manufacturer
|
31
|
+
Manufacturer::APPLE
|
32
|
+
end
|
33
|
+
|
34
|
+
# return file size
|
35
|
+
# @example Read file size in integer
|
36
|
+
# aab.size # => 3618865
|
37
|
+
#
|
38
|
+
# @example Read file size in human readabale
|
39
|
+
# aab.size(human_size: true) # => '3.45 MB'
|
40
|
+
#
|
41
|
+
# @param [Boolean] human_size Convert integer value to human readable.
|
42
|
+
# @return [Integer, String]
|
43
|
+
def size(human_size: false)
|
44
|
+
file_to_human_size(@file, human_size: human_size)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!method device
|
48
|
+
# @see InfoPlist#device
|
49
|
+
# @!method platform
|
50
|
+
# @see InfoPlist#platform
|
51
|
+
# @!method iphone?
|
52
|
+
# @see InfoPlist#iphone?
|
53
|
+
# @!method ipad?
|
54
|
+
# @see InfoPlist#ipad?
|
55
|
+
# @!method universal?
|
56
|
+
# @see InfoPlist#universal?
|
57
|
+
# @!method build_version
|
58
|
+
# @see InfoPlist#build_version
|
59
|
+
# @!method name
|
60
|
+
# @see InfoPlist#name
|
61
|
+
# @!method release_version
|
62
|
+
# @see InfoPlist#release_version
|
63
|
+
# @!method identifier
|
64
|
+
# @see InfoPlist#identifier
|
65
|
+
# @!method bundle_id
|
66
|
+
# @see InfoPlist#bundle_id
|
67
|
+
# @!method display_name
|
68
|
+
# @see InfoPlist#display_name
|
69
|
+
# @!method bundle_name
|
70
|
+
# @see InfoPlist#bundle_name
|
71
|
+
# @!method min_sdk_version
|
72
|
+
# @see InfoPlist#min_sdk_version
|
73
|
+
# @!method min_os_version
|
74
|
+
# @see InfoPlist#min_os_version
|
75
|
+
def_delegators :info, :device, :platform, :iphone?, :ipad?, :universal?, :macos?,
|
76
|
+
:build_version, :name, :release_version, :identifier, :bundle_id,
|
77
|
+
:display_name, :bundle_name, :min_sdk_version, :min_os_version
|
78
|
+
|
79
|
+
# @!method devices
|
80
|
+
# @see MobileProvision#devices
|
81
|
+
# @!method team_name
|
82
|
+
# @see MobileProvision#team_name
|
83
|
+
# @!method team_identifier
|
84
|
+
# @see MobileProvision#team_identifier
|
85
|
+
# @!method profile_name
|
86
|
+
# @see MobileProvision#profile_name
|
87
|
+
# @!method expired_date
|
88
|
+
# @see MobileProvision#expired_date
|
89
|
+
def_delegators :mobileprovision, :devices, :team_name, :team_identifier,
|
90
|
+
:profile_name, :expired_date
|
91
|
+
|
92
|
+
# @return [String, nil]
|
93
|
+
def distribution_name
|
94
|
+
"#{profile_name} - #{team_name}" if profile_name && team_name
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [String]
|
98
|
+
def release_type
|
99
|
+
if stored?
|
100
|
+
ExportType::APPSTORE
|
101
|
+
else
|
102
|
+
build_type
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# return iOS build configuration, not correct in macOS app.
|
107
|
+
# @return [String]
|
108
|
+
def build_type
|
109
|
+
if mobileprovision?
|
110
|
+
return ExportType::RELEASE if macos?
|
111
|
+
|
112
|
+
devices ? ExportType::ADHOC : ExportType::ENTERPRISE
|
113
|
+
else
|
114
|
+
ExportType::DEBUG
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [Array<MachO>, nil]
|
119
|
+
def archs
|
120
|
+
return unless ::File.exist?(binary_path)
|
121
|
+
|
122
|
+
file = MachO.open(binary_path)
|
123
|
+
case file
|
124
|
+
when MachO::MachOFile
|
125
|
+
[file.cpusubtype]
|
126
|
+
else
|
127
|
+
file.machos.each_with_object([]) do |arch, obj|
|
128
|
+
obj << arch.cpusubtype
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
alias architectures archs
|
133
|
+
|
134
|
+
# @abstract Subclass and override {#icons} to implement.
|
135
|
+
def icons(_uncrush: true)
|
136
|
+
not_implemented_error!(__method__)
|
137
|
+
end
|
138
|
+
|
139
|
+
# @abstract Subclass and override {#stored?} to implement.
|
140
|
+
def stored?
|
141
|
+
not_implemented_error!(__method__)
|
142
|
+
end
|
143
|
+
|
144
|
+
# force remove developer certificate data from {#mobileprovision} method
|
145
|
+
# @return [nil]
|
146
|
+
def hide_developer_certificates
|
147
|
+
mobileprovision.delete('DeveloperCertificates') if mobileprovision?
|
148
|
+
end
|
149
|
+
|
150
|
+
# @return [MobileProvision]
|
151
|
+
def mobileprovision
|
152
|
+
return unless mobileprovision?
|
153
|
+
|
154
|
+
@mobileprovision ||= MobileProvision.new(mobileprovision_path)
|
155
|
+
end
|
156
|
+
|
157
|
+
# @return [Boolean]
|
158
|
+
def mobileprovision?
|
159
|
+
::File.exist?(mobileprovision_path)
|
160
|
+
end
|
161
|
+
|
162
|
+
# @abstract Subclass and override {#mobileprovision_path} to implement.
|
163
|
+
def mobileprovision_path
|
164
|
+
not_implemented_error!(__method__)
|
165
|
+
end
|
166
|
+
|
167
|
+
# @return [InfoPlist]
|
168
|
+
def info
|
169
|
+
@info ||= InfoPlist.new(info_path)
|
170
|
+
end
|
171
|
+
|
172
|
+
# @abstract Subclass and override {#info_path} to implement.
|
173
|
+
def info_path
|
174
|
+
not_implemented_error!(__method__)
|
175
|
+
end
|
176
|
+
|
177
|
+
# @abstract Subclass and override {#app_path} to implement.
|
178
|
+
def app_path
|
179
|
+
not_implemented_error!(__method__)
|
180
|
+
end
|
181
|
+
|
182
|
+
# @abstract Subclass and override {#clear!} to implement.
|
183
|
+
def clear!
|
184
|
+
not_implemented_error!(__method__)
|
185
|
+
end
|
186
|
+
|
187
|
+
# @return [String] contents path of contents
|
188
|
+
def contents
|
189
|
+
@contents ||= unarchive(@file, prefix: format.to_s)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo
|
4
|
+
# Certificate wrapper for
|
5
|
+
# {https://docs.ruby-lang.org/en/3.0/OpenSSL/X509/Certificate.html OpenSSL::X509::Certifiate}.
|
6
|
+
class Certificate
|
7
|
+
# Parse Raw data into X509 cerificate wrapper
|
8
|
+
# @param [String] certificate raw data
|
9
|
+
# @return [AppInfo::Certificate]
|
10
|
+
def self.parse(data)
|
11
|
+
cert = OpenSSL::X509::Certificate.new(data)
|
12
|
+
new(cert)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [OpenSSL::X509::Certificate] certificate
|
16
|
+
def initialize(cert)
|
17
|
+
@cert = cert
|
18
|
+
end
|
19
|
+
|
20
|
+
# return version of certificate
|
21
|
+
# @param [String] prefix
|
22
|
+
# @param [Integer] base
|
23
|
+
# @return [String] version
|
24
|
+
def version(prefix: 'v', base: 1)
|
25
|
+
"#{prefix}#{raw.version + base}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# return serial of certificate
|
29
|
+
# @param [Integer] base
|
30
|
+
# @param [Symbol] transform avaiables in :lower, :upper
|
31
|
+
# @param [String] prefix
|
32
|
+
# @return [String] serial
|
33
|
+
def serial(base = 10, transform: :lower, prefix: nil)
|
34
|
+
serial = raw.serial.to_s(base)
|
35
|
+
serial = transform == :lower ? serial.downcase : serial.upcase
|
36
|
+
return serial unless prefix
|
37
|
+
|
38
|
+
"#{prefix}#{serial}"
|
39
|
+
end
|
40
|
+
|
41
|
+
# return issuer from DN, similar to {#subject}.
|
42
|
+
#
|
43
|
+
# Example:
|
44
|
+
#
|
45
|
+
# @param [Symbol] format avaiables in `:to_a`, `:to_s` and `:raw`
|
46
|
+
# @return [Array, String, OpenSSL::X509::Name] the object converted into the expected format.
|
47
|
+
def issuer(format: :raw)
|
48
|
+
convert_cert_name(raw.issuer, format: format)
|
49
|
+
end
|
50
|
+
|
51
|
+
# return subject from DN, similar to {#issuer}.
|
52
|
+
# @param [Symbol] format avaiables in `:to_a`, `:to_s` and `:raw`
|
53
|
+
# @return [Array, String, OpenSSL::X509::Name] the object converted into the expected format.
|
54
|
+
def subject(format: :raw)
|
55
|
+
convert_cert_name(raw.subject, format: format)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Time]
|
59
|
+
def created_at
|
60
|
+
raw.not_before
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Time]
|
64
|
+
def expired_at
|
65
|
+
raw.not_after
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Boolean]
|
69
|
+
def expired?
|
70
|
+
expired_at < Time.now.utc
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [String] format always be :x509.
|
74
|
+
def format
|
75
|
+
:x509
|
76
|
+
end
|
77
|
+
|
78
|
+
# return algorithm digest
|
79
|
+
#
|
80
|
+
# OpenSSL supported digests:
|
81
|
+
#
|
82
|
+
# -blake2b512 -blake2s256 -md4
|
83
|
+
# -md5 -md5-sha1 -mdc2
|
84
|
+
# -ripemd -ripemd160 -rmd160
|
85
|
+
# -sha1 -sha224 -sha256
|
86
|
+
# -sha3-224 -sha3-256 -sha3-384
|
87
|
+
# -sha3-512 -sha384 -sha512
|
88
|
+
# -sha512-224 -sha512-256 -shake128
|
89
|
+
# -shake256 -sm3 -ssl3-md5
|
90
|
+
# -ssl3-sha1 -whirlpool
|
91
|
+
def digest
|
92
|
+
signature_algorithm = raw.signature_algorithm
|
93
|
+
|
94
|
+
case signature_algorithm
|
95
|
+
when /md5/
|
96
|
+
:md5
|
97
|
+
when /sha1/
|
98
|
+
:sha1
|
99
|
+
when /sha224/
|
100
|
+
:sha224
|
101
|
+
when /sha256/
|
102
|
+
:sha256
|
103
|
+
when /sha512/
|
104
|
+
:sha512
|
105
|
+
else
|
106
|
+
# Android signature no need the others
|
107
|
+
signature_algorithm.to_sym
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# return algorithm name of public key
|
112
|
+
def algorithm
|
113
|
+
case public_key
|
114
|
+
when OpenSSL::PKey::RSA then :rsa
|
115
|
+
when OpenSSL::PKey::DSA then :dsa
|
116
|
+
when OpenSSL::PKey::DH then :dh
|
117
|
+
when OpenSSL::PKey::EC then :ec
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# return length of public key
|
122
|
+
# @return [Integer]
|
123
|
+
# @raise NotImplementedError
|
124
|
+
def length
|
125
|
+
case public_key
|
126
|
+
when OpenSSL::PKey::RSA
|
127
|
+
public_key.n.num_bits
|
128
|
+
when OpenSSL::PKey::DSA, OpenSSL::PKey::DH
|
129
|
+
public_key.p.num_bits
|
130
|
+
when OpenSSL::PKey::EC
|
131
|
+
raise NotImplementedError, "key length for #{public_key.inspect} not implemented"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
alias size length
|
135
|
+
|
136
|
+
# return fingerprint of certificate
|
137
|
+
# @return [String]
|
138
|
+
def fingerprint(name = :sha256, transform: :lower, delimiter: nil)
|
139
|
+
digest = OpenSSL::Digest.new(name.to_s.upcase)
|
140
|
+
digest.update(raw.to_der)
|
141
|
+
fingerprint = digest.to_s
|
142
|
+
fingerprint = fingerprint.upcase if transform.to_sym == :upper
|
143
|
+
return fingerprint unless delimiter
|
144
|
+
|
145
|
+
fingerprint.scan(/../).join(delimiter)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Orginal OpenSSL X509 certificate
|
149
|
+
# @return [OpenSSL::X509::Certificate]
|
150
|
+
def raw
|
151
|
+
@cert
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def convert_cert_name(name, format:)
|
157
|
+
data = name.to_a
|
158
|
+
case format
|
159
|
+
when :to_a
|
160
|
+
data.map { |k, v, _| [k, v] }
|
161
|
+
when :to_s
|
162
|
+
data.map { |k, v, _| "#{k}=#{v}" }.join(' ')
|
163
|
+
else
|
164
|
+
name
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def method_missing(method, *args, &block)
|
169
|
+
@cert.send(method.to_sym, *args, &block) || super
|
170
|
+
end
|
171
|
+
|
172
|
+
def respond_to_missing?(method, *args)
|
173
|
+
@cert.respond_to?(method.to_sym) || super
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo
|
4
|
+
# Full Format
|
5
|
+
module Format
|
6
|
+
# Apple
|
7
|
+
|
8
|
+
INFOPLIST = :infoplist
|
9
|
+
MOBILEPROVISION = :mobileprovision
|
10
|
+
DSYM = :dsym
|
11
|
+
|
12
|
+
# macOS
|
13
|
+
|
14
|
+
MACOS = :macos
|
15
|
+
|
16
|
+
# iOS
|
17
|
+
|
18
|
+
IPA = :ipa
|
19
|
+
|
20
|
+
# Android
|
21
|
+
|
22
|
+
APK = :apk
|
23
|
+
AAB = :aab
|
24
|
+
PROGUARD = :proguard
|
25
|
+
|
26
|
+
# Windows
|
27
|
+
|
28
|
+
PE = :pe
|
29
|
+
|
30
|
+
UNKNOWN = :unknown
|
31
|
+
end
|
32
|
+
|
33
|
+
# Manufacturer
|
34
|
+
module Manufacturer
|
35
|
+
APPLE = :apple
|
36
|
+
GOOGLE = :google
|
37
|
+
MICROSOFT = :microsoft
|
38
|
+
end
|
39
|
+
|
40
|
+
# Platform
|
41
|
+
module Platform
|
42
|
+
MACOS = :macos
|
43
|
+
IOS = :ios
|
44
|
+
ANDROID = :android
|
45
|
+
WINDOWS = :windows
|
46
|
+
end
|
47
|
+
|
48
|
+
# Apple Device Type
|
49
|
+
module Device
|
50
|
+
# macOS
|
51
|
+
MACOS = :macos
|
52
|
+
|
53
|
+
# Apple iPhone
|
54
|
+
IPHONE = :iphone
|
55
|
+
# Apple iPad
|
56
|
+
IPAD = :ipad
|
57
|
+
# Apple Watch
|
58
|
+
IWATCH = :iwatch # not implemented yet
|
59
|
+
# Apple Universal (iPhone and iPad)
|
60
|
+
UNIVERSAL = :universal
|
61
|
+
|
62
|
+
# Android Phone
|
63
|
+
PHONE = :phone
|
64
|
+
# Android Tablet (not implemented yet)
|
65
|
+
TABLET = :tablet
|
66
|
+
# Android Watch
|
67
|
+
WATCH = :watch
|
68
|
+
# Android TV
|
69
|
+
TELEVISION = :television
|
70
|
+
# Android Car Automotive
|
71
|
+
AUTOMOTIVE = :automotive
|
72
|
+
|
73
|
+
# Windows
|
74
|
+
WINDOWS = :windows
|
75
|
+
end
|
76
|
+
end
|
@@ -4,7 +4,8 @@
|
|
4
4
|
# Copy from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/try.rb
|
5
5
|
|
6
6
|
module AppInfo
|
7
|
-
|
7
|
+
# @!visibility private
|
8
|
+
module Tryable
|
8
9
|
##
|
9
10
|
# :method: try
|
10
11
|
#
|
@@ -107,6 +108,7 @@ module AppInfo
|
|
107
108
|
end
|
108
109
|
end
|
109
110
|
|
111
|
+
# @!visibility private
|
110
112
|
class Object
|
111
113
|
include AppInfo::Tryable
|
112
114
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AppInfo
|
4
|
+
# @!visibility private
|
4
5
|
module Inflector
|
5
6
|
def ai_snakecase
|
6
7
|
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
@@ -30,6 +31,7 @@ module AppInfo
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
34
|
+
# @!visibility private
|
33
35
|
class String
|
34
36
|
include AppInfo::Inflector
|
35
37
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_info/dsym/macho'
|
4
|
+
|
5
|
+
module AppInfo
|
6
|
+
class DSYM < File
|
7
|
+
# DSYM Debug Information Format Struct
|
8
|
+
class DebugInfo
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [String]
|
16
|
+
def object
|
17
|
+
@object ||= ::File.basename(bin_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [::MachO::MachOFile, ::MachO::FatFile]
|
21
|
+
def macho_type
|
22
|
+
@macho_type ||= ::MachO.open(bin_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Array<AppInfo::DSYM::MachO>]
|
26
|
+
def machos
|
27
|
+
@machos ||= case macho_type
|
28
|
+
when ::MachO::MachOFile
|
29
|
+
[MachO.new(macho_type, ::File.size(bin_path))]
|
30
|
+
else
|
31
|
+
size = macho_type.fat_archs.each_with_object([]) do |arch, obj|
|
32
|
+
obj << arch.size
|
33
|
+
end
|
34
|
+
|
35
|
+
machos = []
|
36
|
+
macho_type.machos.each_with_index do |file, i|
|
37
|
+
machos << MachO.new(file, size[i])
|
38
|
+
end
|
39
|
+
machos
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [String, nil]
|
44
|
+
def release_version
|
45
|
+
info.try(:[], 'CFBundleShortVersionString')
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [String, nil]
|
49
|
+
def build_version
|
50
|
+
info.try(:[], 'CFBundleVersion')
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String, nil]
|
54
|
+
def identifier
|
55
|
+
info.try(:[], 'CFBundleIdentifier').sub('com.apple.xcode.dsym.', '')
|
56
|
+
end
|
57
|
+
alias bundle_id identifier
|
58
|
+
|
59
|
+
# @return [CFPropertyList]
|
60
|
+
def info
|
61
|
+
return nil unless ::File.exist?(info_path)
|
62
|
+
|
63
|
+
@info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: info_path).value)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [String]
|
67
|
+
def info_path
|
68
|
+
@info_path ||= ::File.join(path, 'Contents', 'Info.plist')
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [String]
|
72
|
+
def bin_path
|
73
|
+
@bin_path ||= lambda {
|
74
|
+
dwarf_path = ::File.join(path, 'Contents', 'Resources', 'DWARF')
|
75
|
+
name = Dir.children(dwarf_path)[0]
|
76
|
+
::File.join(dwarf_path, name)
|
77
|
+
}.call
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'macho'
|
4
|
+
|
5
|
+
module AppInfo
|
6
|
+
class DSYM < File
|
7
|
+
# Mach-O Struct
|
8
|
+
class MachO
|
9
|
+
include Helper::HumanFileSize
|
10
|
+
|
11
|
+
def initialize(file, size = 0)
|
12
|
+
@file = file
|
13
|
+
@size = size
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String]
|
17
|
+
def cpu_name
|
18
|
+
@file.cpusubtype
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String]
|
22
|
+
def cpu_type
|
23
|
+
@file.cputype
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String]
|
27
|
+
def type
|
28
|
+
@file.filetype
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String, Integer]
|
32
|
+
def size(human_size: false)
|
33
|
+
return number_to_human_size(@size) if human_size
|
34
|
+
|
35
|
+
@size
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String]
|
39
|
+
def uuid
|
40
|
+
@file[:LC_UUID][0].uuid_string
|
41
|
+
end
|
42
|
+
alias debug_id uuid
|
43
|
+
|
44
|
+
# @return [::MachO::Headers]
|
45
|
+
def header
|
46
|
+
@header ||= @file.header
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Hash{Symbol => String, Integer}]
|
50
|
+
def to_h
|
51
|
+
{
|
52
|
+
uuid: uuid,
|
53
|
+
type: type,
|
54
|
+
cpu_name: cpu_name,
|
55
|
+
cpu_type: cpu_type,
|
56
|
+
size: size,
|
57
|
+
human_size: size(human_size: true)
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|