fruity_builder 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 16412bb7e55a1c0f1872c37d1a63609ca8efdcf0
4
- data.tar.gz: a63167548a0c1fecc2213a979f3b564568892b9f
3
+ metadata.gz: 8d8ae8d1b4b33450affa9d95e70c438bac84f250
4
+ data.tar.gz: 02aa4a49e613e1f7f330043e61dee0a09788ee45
5
5
  SHA512:
6
- metadata.gz: d9050248a0edf8da4341c705518246c5598a515f8664e0ecc69d47ae923ce6ec34812521de8459d8e574838515b246052568dfc4665a4790e0953dddd5b0b06b
7
- data.tar.gz: d57b536f65848d0aca184b6776435df42175a2b19a27da6aadaf80166f24e2e24bca64a9c4fde6c75e3d655d01a1497e49eb9e9f391c767e73cbbf7c412fdd3d
6
+ metadata.gz: cab3fe1d9d54fb4b81f2b694ff0bef41c24b0ea65aa5289669c173c4fe9c79185d1eba26e68b67a75076a9401dd81e92d841b1af3e84a04d07aa18a0d1dc656d
7
+ data.tar.gz: 2ca8656316a5268d2e216d3cee62a56dfe1fa8b5af58dbf14bfb7c26a197069f671274c7a17690a636db951add469f8b20232cefcc0bffca0a12355ada85d72a
@@ -1,6 +1,7 @@
1
1
  require 'fruity_builder/lib/sys_log'
2
2
  require 'fruity_builder/xcodebuild'
3
3
  require 'fruity_builder/build_properties'
4
+ require 'fruity_builder/signing'
4
5
 
5
6
 
6
7
  module FruityBuilder
@@ -1,50 +1,94 @@
1
1
  require 'fruity_builder/lib/execution'
2
+ require 'ox'
2
3
 
3
4
  module FruityBuilder
4
- module IOS
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]
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
- def self.get_bundle_id(options = {})
16
- xml = get_xml(options)
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
- raise PlistutilCommandError.new('No XML was passed') unless xml
27
+ raise PlistutilCommandError.new('No XML was passed') unless xml
19
28
 
20
- identifiers = xml.scan(/.*CFBundleIdentifier<\/key>\n\t<string>(.*?)<\/string>/)
21
- identifiers << xml.scan(/.*CFBundleName<\/key>\n\t<string>(.*?)<\/string>/)
29
+ replacements = xml.scan(/.*CFBundleIdentifier<\/key>\n\t<string>(.*?)<\/string>/)
30
+ replacements << xml.scan(/.*CFBundleName<\/key>\n\t<string>(.*?)<\/string>/)
22
31
 
23
- identifiers.flatten.uniq
32
+ replacements.flatten.uniq.each do |replacement|
33
+ xml = xml.gsub(replacement, options[:new_id])
24
34
  end
25
35
 
26
- def self.replace_bundle_id(options = {})
27
- xml = get_xml(options)
36
+ IO.write(options[:file], xml) if options.key?(:file)
37
+ xml
38
+ end
28
39
 
29
- raise PlistutilCommandError.new('No XML was passed') unless xml
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
- replacements = xml.scan(/.*CFBundleIdentifier<\/key>\n\t<string>(.*?)<\/string>/)
32
- replacements << xml.scan(/.*CFBundleName<\/key>\n\t<string>(.*?)<\/string>/)
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
- replacements.flatten.uniq.each do |replacement|
35
- xml = xml.gsub(replacement, options[:new_id])
36
- end
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
- IO.write(options[:file], xml) if options.key?(:file)
39
- xml
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
- # plistutil error class
44
- class PlistutilCommandError < StandardError
45
- def initialize(msg)
46
- super(msg)
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.1
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-03 00:00:00.000000000 Z
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: