fruity_builder 1.0.1 → 1.0.2
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/fruity_builder.rb +1 -0
- data/lib/fruity_builder/plistutil.rb +75 -31
- data/lib/fruity_builder/signing.rb +118 -0
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d8ae8d1b4b33450affa9d95e70c438bac84f250
|
4
|
+
data.tar.gz: 02aa4a49e613e1f7f330043e61dee0a09788ee45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cab3fe1d9d54fb4b81f2b694ff0bef41c24b0ea65aa5289669c173c4fe9c79185d1eba26e68b67a75076a9401dd81e92d841b1af3e84a04d07aa18a0d1dc656d
|
7
|
+
data.tar.gz: 2ca8656316a5268d2e216d3cee62a56dfe1fa8b5af58dbf14bfb7c26a197069f671274c7a17690a636db951add469f8b20232cefcc0bffca0a12355ada85d72a
|
data/lib/fruity_builder.rb
CHANGED
@@ -1,50 +1,94 @@
|
|
1
1
|
require 'fruity_builder/lib/execution'
|
2
|
+
require 'ox'
|
2
3
|
|
3
4
|
module FruityBuilder
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
options[:xml]
|
12
|
-
end
|
5
|
+
class Plistutil < Execution
|
6
|
+
|
7
|
+
def self.get_xml(options = {})
|
8
|
+
if options.key?(:file)
|
9
|
+
IO.read(options[:file])
|
10
|
+
elsif options.key?(:xml)
|
11
|
+
options[:xml]
|
13
12
|
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.get_bundle_id(options = {})
|
16
|
+
xml = get_xml(options)
|
14
17
|
|
15
|
-
|
16
|
-
|
18
|
+
raise PlistutilCommandError.new('No XML was passed') unless xml
|
19
|
+
identifiers = xml.scan(/.*CFBundleIdentifier<\/key>\n\t<string>(.*?)<\/string>/)
|
20
|
+
identifiers << xml.scan(/.*CFBundleName<\/key>\n\t<string>(.*?)<\/string>/)
|
21
|
+
identifiers.flatten.uniq
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.replace_bundle_id(options = {})
|
25
|
+
xml = get_xml(options)
|
17
26
|
|
18
|
-
|
27
|
+
raise PlistutilCommandError.new('No XML was passed') unless xml
|
19
28
|
|
20
|
-
|
21
|
-
|
29
|
+
replacements = xml.scan(/.*CFBundleIdentifier<\/key>\n\t<string>(.*?)<\/string>/)
|
30
|
+
replacements << xml.scan(/.*CFBundleName<\/key>\n\t<string>(.*?)<\/string>/)
|
22
31
|
|
23
|
-
|
32
|
+
replacements.flatten.uniq.each do |replacement|
|
33
|
+
xml = xml.gsub(replacement, options[:new_id])
|
24
34
|
end
|
25
35
|
|
26
|
-
|
27
|
-
|
36
|
+
IO.write(options[:file], xml) if options.key?(:file)
|
37
|
+
xml
|
38
|
+
end
|
28
39
|
|
29
|
-
|
40
|
+
# Check to ensure that plistutil is available
|
41
|
+
# @return [Boolean] true if plistutil is available, false otherwise
|
42
|
+
def self.plistutil_available?
|
43
|
+
result = execute('which plistutil')
|
44
|
+
result.exit == 0
|
45
|
+
end
|
30
46
|
|
31
|
-
|
32
|
-
|
47
|
+
# Gets properties from the IPA and returns them in a hash
|
48
|
+
# @param [String] path path to the IPA/App
|
49
|
+
# @return [Hash] list of properties from the app
|
50
|
+
def self.get_bundle_id_from_app(path)
|
51
|
+
path = Signing.unpack_ipa(path) if Signing.is_ipa?(path)
|
52
|
+
get_bundle_id_from_plist("#{path}/Info.plist")
|
53
|
+
end
|
33
54
|
|
34
|
-
|
35
|
-
|
36
|
-
|
55
|
+
# Gets properties from the IPA and returns them in a hash
|
56
|
+
# @param [String] plist path to the plist
|
57
|
+
# @return [Hash] list of properties from the app
|
58
|
+
def self.get_bundle_id_from_plist(plist)
|
59
|
+
raise PlistutilCommandError.new('plistutil not found') unless plistutil_available?
|
60
|
+
result = execute("plistutil -i #{plist}")
|
61
|
+
raise PlistutilCommandError.new(result.stderr) if result.exit != 0
|
62
|
+
parse_xml(result.stdout)
|
63
|
+
end
|
37
64
|
|
38
|
-
|
39
|
-
|
65
|
+
def self.parse_xml(xml)
|
66
|
+
info = Ox.parse(xml)
|
67
|
+
nodes = info.locate('*/dict')
|
68
|
+
values = {}
|
69
|
+
last_key = nil
|
70
|
+
nodes.each do |node|
|
71
|
+
node.nodes.each do |child|
|
72
|
+
if child.value == 'key'
|
73
|
+
if child.nodes.first == 'get-task-allow'
|
74
|
+
values['get-task-allow'] = nodes.first.nodes[nodes.first.nodes.index(child)+1].value
|
75
|
+
next
|
76
|
+
end
|
77
|
+
last_key = child.nodes.first
|
78
|
+
elsif child.value == 'string'
|
79
|
+
values[last_key] = child.nodes.first
|
80
|
+
end
|
81
|
+
end
|
40
82
|
end
|
83
|
+
values
|
41
84
|
end
|
85
|
+
end
|
42
86
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
87
|
+
# plistutil error class
|
88
|
+
class PlistutilCommandError < StandardError
|
89
|
+
def initialize(msg)
|
90
|
+
super(msg)
|
48
91
|
end
|
49
92
|
end
|
50
|
-
end
|
93
|
+
end
|
94
|
+
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'fruity_builder/lib/execution'
|
2
|
+
require 'fruity_builder/plistutil'
|
3
|
+
|
4
|
+
module FruityBuilder
|
5
|
+
# Namespace for all methods encapsulating idevice calls
|
6
|
+
class Signing < Execution
|
7
|
+
|
8
|
+
# Check to see if the path is an IPA
|
9
|
+
def self.is_ipa?(path)
|
10
|
+
return true if (File.extname path).downcase == '.ipa'
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.unpack_ipa(path)
|
15
|
+
folder = File.dirname(path)
|
16
|
+
target = (File.basename path, (File.extname path))
|
17
|
+
|
18
|
+
# Check to see if the target has already been unzipped
|
19
|
+
return Dir["#{folder}/#{target}/Payload/*.app"].first if File.exists? ("#{folder}/#{target}/Payload/")
|
20
|
+
|
21
|
+
result = execute("unzip '#{path}' -d '#{folder}/#{target}'")
|
22
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
23
|
+
|
24
|
+
Dir["#{folder}/#{target}/Payload/*.app"].first
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.is_app_signed?(app_path)
|
28
|
+
app_path = unpack_ipa(app_path) if is_ipa?(app_path)
|
29
|
+
result = execute("codesign -d -vvvv '#{app_path}'")
|
30
|
+
|
31
|
+
if result.exit != 0
|
32
|
+
return false if /is not signed/.match(result.stderr)
|
33
|
+
raise SigningCommandError.new(result.stderr)
|
34
|
+
end
|
35
|
+
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.sign_app(options = {})
|
40
|
+
cert = options[:cert]
|
41
|
+
entitlements = options[:entitlements]
|
42
|
+
app = options[:app]
|
43
|
+
original_app = nil
|
44
|
+
|
45
|
+
if is_ipa?(app)
|
46
|
+
original_app = app
|
47
|
+
app = unpack_ipa(app)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Check to see if the entitlements passed in is a file or the XML
|
51
|
+
unless File.exists?(entitlements)
|
52
|
+
file = Tempfile.new('entitlements')
|
53
|
+
file.write(entitlements)
|
54
|
+
file.close
|
55
|
+
entitlements = file.path
|
56
|
+
end
|
57
|
+
|
58
|
+
result = execute("codesign --force --sign '#{cert}' --entitlements #{entitlements} '#{app}'")
|
59
|
+
|
60
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
61
|
+
|
62
|
+
zip_app(app, original_app) if original_app
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.zip_app(payload_path, ipa_path)
|
66
|
+
result = execute("cd #{File.dirname(payload_path)}; zip -r #{ipa_path} ../Payload ")
|
67
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
68
|
+
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.get_signing_certs
|
73
|
+
result = execute('security find-identity -p codesigning -v')
|
74
|
+
|
75
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
76
|
+
|
77
|
+
certs = []
|
78
|
+
result.stdout.split("\n").each do |line|
|
79
|
+
if /\)\s*(\S*)\s*"(.*)"/.match(line)
|
80
|
+
certs << { id: Regexp.last_match[1], name: Regexp.last_match[2] }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
certs
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.get_entitlements(app_path, raw = false)
|
87
|
+
app_path = unpack_ipa(app_path) if is_ipa?(app_path)
|
88
|
+
result = execute("codesign -d --entitlements - #{app_path}")
|
89
|
+
|
90
|
+
if result.exit != 0
|
91
|
+
raise SigningCommandError.new(result.stderr)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Clean up the result as it occasionally contains invalid UTF-8 characters
|
95
|
+
xml = result.stdout.to_s.encode('UTF-8', 'UTF-8', invalid: :replace)
|
96
|
+
xml = xml[xml.index('<')..xml.length]
|
97
|
+
|
98
|
+
return xml if raw
|
99
|
+
entitlements = Plistutil.parse_xml(xml)
|
100
|
+
return entitlements
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.enable_get_tasks(app_path)
|
104
|
+
entitlements = get_entitlements(app_path, raw: true)
|
105
|
+
|
106
|
+
return entitlements if entitlements.scan(/<key>get-task-allow<\/key>\\n.*<true\/>/).count > 0
|
107
|
+
|
108
|
+
entitlements.gsub('<false/>', '<true/>') if entitlements.scan(/<key>get-task-allow<\/key>\\n.*<false\/>/)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Signing error class
|
113
|
+
class SigningCommandError < StandardError
|
114
|
+
def initialize(msg)
|
115
|
+
super(msg)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fruity_builder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- BBC
|
@@ -9,8 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-02-
|
13
|
-
dependencies:
|
12
|
+
date: 2016-02-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ox
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 2.1.0
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 2.1.0
|
14
28
|
description: iOS code signing utilities - used to replace bundle IDs, development
|
15
29
|
teams and provisioning profiles programmatically
|
16
30
|
email:
|
@@ -25,6 +39,7 @@ files:
|
|
25
39
|
- lib/fruity_builder/lib/execution.rb
|
26
40
|
- lib/fruity_builder/lib/sys_log.rb
|
27
41
|
- lib/fruity_builder/plistutil.rb
|
42
|
+
- lib/fruity_builder/signing.rb
|
28
43
|
- lib/fruity_builder/xcodebuild.rb
|
29
44
|
homepage: https://github.com/bbc/fruity_builder
|
30
45
|
licenses:
|