xcoder 0.1.15 → 0.1.18
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rbenv-version +1 -0
- data/.rvmrc +1 -1
- data/Gemfile +10 -2
- data/README.md +110 -9
- data/Rakefile +2 -2
- data/bin/xcoder +74 -0
- data/lib/xcode/builder.rb +1 -2
- data/lib/xcode/builder/base_builder.rb +231 -102
- data/lib/xcode/builder/build_parser.rb +146 -0
- data/lib/xcode/builder/project_target_config_builder.rb +2 -2
- data/lib/xcode/builder/scheme_builder.rb +29 -12
- data/lib/xcode/buildspec.rb +286 -0
- data/lib/xcode/configuration_list.rb +24 -24
- data/lib/xcode/deploy/ftp.rb +56 -0
- data/lib/xcode/deploy/kickfolio.rb +18 -0
- data/lib/xcode/deploy/s3.rb +38 -0
- data/lib/xcode/deploy/ssh.rb +43 -0
- data/lib/xcode/deploy/templates/index.rhtml +22 -0
- data/lib/xcode/deploy/templates/manifest.rhtml +31 -0
- data/lib/xcode/deploy/testflight.rb +32 -27
- data/lib/xcode/deploy/web_assets.rb +39 -0
- data/lib/xcode/info_plist.rb +16 -0
- data/lib/xcode/keychain.rb +33 -10
- data/lib/xcode/platform.rb +65 -0
- data/lib/xcode/project.rb +7 -3
- data/lib/xcode/provisioning_profile.rb +38 -2
- data/lib/xcode/scheme.rb +44 -17
- data/lib/xcode/shell/command.rb +79 -5
- data/lib/xcode/terminal_output.rb +116 -0
- data/lib/xcode/test/formatters/junit_formatter.rb +7 -2
- data/lib/xcode/test/formatters/stdout_formatter.rb +34 -25
- data/lib/xcode/test/parsers/kif_parser.rb +87 -0
- data/lib/xcode/test/parsers/ocunit_parser.rb +3 -3
- data/lib/xcode/version.rb +1 -1
- data/lib/xcode/workspace.rb +13 -5
- data/lib/xcoder.rb +33 -31
- data/spec/TestProject/TestProject.xcodeproj/project.pbxproj +1627 -1015
- data/spec/TestWorkspace.xcworkspace/contents.xcworkspacedata +7 -0
- data/spec/builder_spec.rb +87 -71
- data/spec/deploy_spec.rb +63 -0
- data/spec/ocunit_parser_spec.rb +1 -1
- data/xcoder.gemspec +3 -1
- metadata +95 -19
- data/lib/xcode/buildfile.rb +0 -101
- data/lib/xcode/shell.rb +0 -26
- data/spec/deploy_testflight_spec.rb +0 -27
data/lib/xcode/info_plist.rb
CHANGED
@@ -34,6 +34,22 @@ module Xcode
|
|
34
34
|
@plist['CFBundleVersion'] = version.to_s
|
35
35
|
end
|
36
36
|
|
37
|
+
def identifier
|
38
|
+
@plist['CFBundleIdentifier']
|
39
|
+
end
|
40
|
+
|
41
|
+
def identifier=(identifier)
|
42
|
+
@plist['CFBundleIdentifier'] = identifier
|
43
|
+
end
|
44
|
+
|
45
|
+
def display_name
|
46
|
+
@plist['CFBundleDisplayName']
|
47
|
+
end
|
48
|
+
|
49
|
+
def display_name=(name)
|
50
|
+
@plist['CFBundleDisplayName'] = name
|
51
|
+
end
|
52
|
+
|
37
53
|
def save
|
38
54
|
File.open(@plist_location, 'w') {|f| f << @plist.to_plist}
|
39
55
|
end
|
data/lib/xcode/keychain.rb
CHANGED
@@ -1,18 +1,12 @@
|
|
1
1
|
module Xcode
|
2
2
|
|
3
3
|
module Keychains
|
4
|
-
|
4
|
+
|
5
5
|
#
|
6
6
|
# Yield when the keychain is in the search path and remove it when the block returns
|
7
7
|
#
|
8
8
|
def self.with_keychain_in_search_path(kc, &block)
|
9
|
-
|
10
|
-
begin
|
11
|
-
self.search_path = [kc] + keychains
|
12
|
-
yield
|
13
|
-
ensure
|
14
|
-
self.search_path = keychains
|
15
|
-
end
|
9
|
+
kc.in_search_path &block
|
16
10
|
end
|
17
11
|
|
18
12
|
|
@@ -45,6 +39,8 @@ module Xcode
|
|
45
39
|
end
|
46
40
|
|
47
41
|
class Keychain
|
42
|
+
include Xcode::TerminalOutput
|
43
|
+
|
48
44
|
attr_accessor :name, :path
|
49
45
|
|
50
46
|
TEMP_PASSWORD = "build_keychain_password"
|
@@ -61,6 +57,27 @@ module Xcode
|
|
61
57
|
|
62
58
|
yield(self) if block_given?
|
63
59
|
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
"Keychain(#{@name})"
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Installs this keychain in the head of teh search path and restores the original on
|
67
|
+
# completion of the block
|
68
|
+
#
|
69
|
+
# @param the block to be invoked with the modified search path
|
70
|
+
#
|
71
|
+
def in_search_path(&block)
|
72
|
+
keychains = Keychains.search_path
|
73
|
+
begin
|
74
|
+
Keychains.search_path = [self] + keychains
|
75
|
+
yield
|
76
|
+
ensure
|
77
|
+
Keychains.search_path = keychains
|
78
|
+
# print_task 'keychain', "Restored search path"
|
79
|
+
end
|
80
|
+
end
|
64
81
|
|
65
82
|
#
|
66
83
|
# Import the .p12 certificate file into the keychain using the provided password
|
@@ -88,8 +105,8 @@ module Xcode
|
|
88
105
|
cmd << "find-certificate"
|
89
106
|
cmd << "-a"
|
90
107
|
cmd << "\"#{@path}\""
|
91
|
-
|
92
|
-
|
108
|
+
cmd.show_output = false
|
109
|
+
cmd.execute.join("").scan /\s+"labl"<blob>="([^"]+)"/ do |m|
|
93
110
|
names << m[0]
|
94
111
|
end
|
95
112
|
names
|
@@ -132,6 +149,12 @@ module Xcode
|
|
132
149
|
cmd << "\"#{path}\""
|
133
150
|
cmd.execute
|
134
151
|
|
152
|
+
cmd = Xcode::Shell::Command.new "security"
|
153
|
+
cmd << "set-keychain-settings"
|
154
|
+
cmd << "-u"
|
155
|
+
cmd << "\"#{path}\""
|
156
|
+
cmd.execute
|
157
|
+
|
135
158
|
kc = Xcode::Keychain.new(path)
|
136
159
|
yield(kc) if block_given?
|
137
160
|
kc
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Xcode
|
2
|
+
|
3
|
+
module Platforms
|
4
|
+
@@platforms = []
|
5
|
+
|
6
|
+
def self.[] sdk_name
|
7
|
+
supported.find {|p| p.sdk==sdk_name}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.find platform, version = nil
|
11
|
+
platform = supported.sort do
|
12
|
+
|a,b| a.version.to_f <=> b.version.to_f
|
13
|
+
end.find do |p|
|
14
|
+
p.platform==platform and (version.nil? or p.version==version)
|
15
|
+
end
|
16
|
+
|
17
|
+
raise "Unable to find a platform #{platform},#{version} - available platforms are #{supported.map{|p| p.sdk}.join(', ')}" if platform.nil?
|
18
|
+
|
19
|
+
platform
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.supported
|
23
|
+
return @@platforms unless @@platforms.count==0
|
24
|
+
|
25
|
+
parsing = false
|
26
|
+
`xcodebuild -showsdks`.split("\n").each do |l|
|
27
|
+
l.strip!
|
28
|
+
if l=~/(.*)\s+SDKs:/
|
29
|
+
parsing = true
|
30
|
+
elsif l=~/^\s*$/
|
31
|
+
parsing = false
|
32
|
+
elsif parsing
|
33
|
+
l=~/([^\t]+)\t+\-sdk (.*)/
|
34
|
+
name = $1.strip
|
35
|
+
$2.strip=~/([a-zA-Z]+)(\d+\.\d+)/
|
36
|
+
|
37
|
+
platform = Platform.new name, $1, $2
|
38
|
+
@@platforms << platform
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@@platforms
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class Platform
|
48
|
+
attr_reader :platform, :name, :version
|
49
|
+
|
50
|
+
def initialize name, platform, version
|
51
|
+
@platform = platform
|
52
|
+
@name = name
|
53
|
+
@version = version
|
54
|
+
end
|
55
|
+
|
56
|
+
def sdk
|
57
|
+
"#{@platform}#{@version}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
"#{name} #{sdk}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/xcode/project.rb
CHANGED
@@ -294,7 +294,7 @@ module Xcode
|
|
294
294
|
|
295
295
|
target.name = name
|
296
296
|
|
297
|
-
build_configuration_list = @registry.add_object(ConfigurationList.
|
297
|
+
build_configuration_list = @registry.add_object(ConfigurationList.configuration_list)
|
298
298
|
target.build_configuration_list = build_configuration_list.identifier
|
299
299
|
|
300
300
|
target.project = self
|
@@ -341,8 +341,8 @@ module Xcode
|
|
341
341
|
end
|
342
342
|
schemes.each do |s|
|
343
343
|
puts " + scheme #{s.name}"
|
344
|
-
puts " +
|
345
|
-
puts " +
|
344
|
+
puts " + targets: #{s.build_targets.map{|t| t.name}}"
|
345
|
+
puts " + config: #{s.build_config}"
|
346
346
|
end
|
347
347
|
end
|
348
348
|
|
@@ -367,5 +367,9 @@ module Xcode
|
|
367
367
|
registry
|
368
368
|
end
|
369
369
|
|
370
|
+
def to_s
|
371
|
+
"#{name} (Project)"
|
372
|
+
end
|
373
|
+
|
370
374
|
end
|
371
375
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Xcode
|
2
2
|
class ProvisioningProfile
|
3
|
+
include Xcode::TerminalOutput
|
3
4
|
attr_reader :path, :name, :uuid, :identifiers, :devices, :appstore
|
4
5
|
def initialize(path)
|
5
6
|
|
@@ -9,6 +10,7 @@ module Xcode
|
|
9
10
|
@identifiers = []
|
10
11
|
@devices = []
|
11
12
|
@appstore = true
|
13
|
+
@enterprise = false
|
12
14
|
|
13
15
|
# TODO: im sure this could be done in a nicer way. maybe read out the XML-like stuff and use the plist -> json converter
|
14
16
|
uuid = nil
|
@@ -19,6 +21,10 @@ module Xcode
|
|
19
21
|
@appstore = false
|
20
22
|
end
|
21
23
|
|
24
|
+
if input=~/<key>ProvisionsAllDevices<\/key>/
|
25
|
+
@enterprise = true
|
26
|
+
end
|
27
|
+
|
22
28
|
if input=~/<key>ProvisionedDevices<\/key>.*?<array>(.*?)<\/array>/im
|
23
29
|
$1.split(/<string>/).each do |id|
|
24
30
|
next if id.nil? or id.strip==""
|
@@ -44,6 +50,10 @@ module Xcode
|
|
44
50
|
def appstore?
|
45
51
|
@appstore
|
46
52
|
end
|
53
|
+
|
54
|
+
def enterprise?
|
55
|
+
@enterprise
|
56
|
+
end
|
47
57
|
|
48
58
|
def self.profiles_path
|
49
59
|
File.expand_path "~/Library/MobileDevice/Provisioning\\ Profiles/"
|
@@ -54,11 +64,27 @@ module Xcode
|
|
54
64
|
end
|
55
65
|
|
56
66
|
def install
|
57
|
-
|
67
|
+
# Do not reinstall if profile is same and is already installed
|
68
|
+
return if (self.path == self.install_path.gsub(/\\ /, ' '))
|
69
|
+
|
70
|
+
ProvisioningProfile.installed_profiles.each do |installed|
|
71
|
+
if installed.identifiers==self.identifiers and installed.uuid==self.uuid
|
72
|
+
installed.uninstall
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# print_task "profile", "installing #{self.path} with uuid #{self.uuid}", :info
|
77
|
+
cmd = Xcode::Shell::Command.new 'cp'
|
78
|
+
cmd << self.path
|
79
|
+
cmd << self.install_path
|
80
|
+
cmd.execute
|
58
81
|
end
|
59
82
|
|
60
83
|
def uninstall
|
61
|
-
|
84
|
+
# print_task "profile", "removing #{self.install_path}", :info
|
85
|
+
cmd = Xcode::Shell::Command.new 'rm'
|
86
|
+
cmd << "-f #{self.install_path}"
|
87
|
+
cmd.execute
|
62
88
|
end
|
63
89
|
|
64
90
|
def self.installed_profiles
|
@@ -66,6 +92,16 @@ module Xcode
|
|
66
92
|
ProvisioningProfile.new(file)
|
67
93
|
end
|
68
94
|
end
|
95
|
+
|
96
|
+
def self.find_installed_by_uuid uuid
|
97
|
+
ProvisioningProfile.installed_profiles.each do |p|
|
98
|
+
return p if p.uuid == uuid
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.installed_profile(name)
|
103
|
+
self.installed_profiles.select {|p| p.name == name.to_s}.first;
|
104
|
+
end
|
69
105
|
|
70
106
|
end
|
71
107
|
end
|
data/lib/xcode/scheme.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
|
3
3
|
module Xcode
|
4
|
+
|
5
|
+
class SchemeAction
|
6
|
+
attr_accessor :config, :name
|
7
|
+
end
|
4
8
|
|
5
9
|
# Schemes are an XML file that describe build, test, launch and profile actions
|
6
10
|
# For the purposes of Xcoder, we want to be able to build and test
|
7
11
|
class Scheme
|
8
|
-
attr_reader :parent, :path, :name, :
|
12
|
+
attr_reader :parent, :path, :name, :build_targets, :test_targets
|
13
|
+
attr_accessor :build_config, :archive_config, :test_config
|
9
14
|
|
10
15
|
#
|
11
16
|
# Parse all the schemes given the current project.
|
@@ -19,21 +24,21 @@ module Xcode
|
|
19
24
|
#
|
20
25
|
def self.find_in_workspace(workspace)
|
21
26
|
schemes = find_in_path(workspace, workspace.path)
|
22
|
-
|
27
|
+
|
23
28
|
# Project level schemes
|
24
29
|
workspace.projects.each do |project|
|
25
|
-
schemes+=project.
|
30
|
+
schemes+=find_in_path(workspace, project.path)
|
26
31
|
end
|
27
|
-
|
32
|
+
|
28
33
|
schemes
|
29
34
|
end
|
30
|
-
|
35
|
+
|
31
36
|
# Parse all the scheme files that can be found at the given path. Schemes
|
32
37
|
# can be defined as `shared` schemes and then `user` specific schemes. Parsing
|
33
38
|
# the schemes will load the shared ones and then the current acting user's
|
34
39
|
# schemes.
|
35
40
|
#
|
36
|
-
#
|
41
|
+
#
|
37
42
|
# @param project or workspace in which the scheme is contained
|
38
43
|
# @return [Array<Scheme>] the shared schemes and user specific schemes found
|
39
44
|
# within the project/workspace at the path defined for schemes.
|
@@ -43,22 +48,35 @@ module Xcode
|
|
43
48
|
Xcode::Scheme.new(parent: parent, root: path, path: scheme_path)
|
44
49
|
end
|
45
50
|
end
|
46
|
-
|
51
|
+
|
47
52
|
def initialize(params={})
|
48
53
|
@parent = params[:parent]
|
49
54
|
@path = File.expand_path params[:path]
|
50
55
|
@root = File.expand_path(File.join(params[:root],'..'))
|
51
56
|
@name = File.basename(path).gsub(/\.xcscheme$/,'')
|
52
|
-
doc = Nokogiri::XML(open(@path))
|
53
|
-
|
57
|
+
doc = Nokogiri::XML(open(@path))
|
58
|
+
|
54
59
|
parse_build_actions(doc)
|
55
60
|
end
|
56
|
-
|
57
|
-
#
|
61
|
+
|
62
|
+
#
|
63
|
+
# @return a builder for building this scheme
|
64
|
+
#
|
58
65
|
def builder
|
59
66
|
Xcode::Builder::SchemeBuilder.new(self)
|
60
67
|
end
|
61
68
|
|
69
|
+
def to_s
|
70
|
+
"#{name} (Scheme) in #{parent}"
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# @return true if the scheme is testable, false otherwise
|
75
|
+
#
|
76
|
+
def testable?
|
77
|
+
!@test_config.nil?
|
78
|
+
end
|
79
|
+
|
62
80
|
private
|
63
81
|
|
64
82
|
#
|
@@ -84,25 +102,34 @@ module Xcode
|
|
84
102
|
def self.current_user_schemes_paths(root)
|
85
103
|
Dir["#{root}/xcuserdata/#{ENV['USER']}.xcuserdatad/xcschemes/*.xcscheme"]
|
86
104
|
end
|
87
|
-
|
105
|
+
|
88
106
|
def target_from_build_reference(buildableReference)
|
89
107
|
project_name = buildableReference['ReferencedContainer'].gsub(/^container:/,'')
|
90
108
|
target_name = buildableReference['BlueprintName']
|
91
|
-
project_path = File.join @root, project_name
|
92
|
-
project = Xcode.project project_path
|
109
|
+
project_path = File.join @root, project_name
|
110
|
+
project = Xcode.project project_path
|
93
111
|
project.target(target_name)
|
94
112
|
end
|
95
|
-
|
113
|
+
|
96
114
|
def parse_build_actions(doc)
|
97
115
|
# Build Config
|
98
116
|
@build_targets = []
|
117
|
+
@test_targets = []
|
99
118
|
|
100
119
|
@build_config = doc.xpath("//LaunchAction").first['buildConfiguration']
|
120
|
+
@archive_config = doc.xpath("//ArchiveAction").first['buildConfiguration']
|
121
|
+
|
122
|
+
if doc.xpath("//TestAction/Testables/TestableReference/BuildableReference").children.count>0
|
123
|
+
@test_config = doc.xpath("//TestAction").first['buildConfiguration']
|
124
|
+
doc.xpath("//TestAction/Testables/TestableReference/BuildableReference").each do |ref|
|
125
|
+
@test_targets << target_from_build_reference(ref)
|
126
|
+
end
|
127
|
+
end
|
101
128
|
|
102
129
|
build_action_entries = doc.xpath("//BuildAction//BuildableReference").each do |ref|
|
103
|
-
@build_targets << target_from_build_reference(ref)
|
130
|
+
@build_targets << target_from_build_reference(ref)
|
104
131
|
end
|
105
132
|
end
|
106
133
|
|
107
134
|
end
|
108
|
-
end
|
135
|
+
end
|
data/lib/xcode/shell/command.rb
CHANGED
@@ -1,14 +1,32 @@
|
|
1
1
|
require 'set'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'pty'
|
2
5
|
|
3
6
|
module Xcode
|
4
7
|
module Shell
|
8
|
+
|
9
|
+
class ExecutionError < StandardError;
|
10
|
+
attr_accessor :output
|
11
|
+
def initialize(message, output=nil)
|
12
|
+
super message
|
13
|
+
@output = output
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
5
17
|
class Command
|
6
|
-
|
18
|
+
include Xcode::TerminalOutput
|
19
|
+
attr_accessor :env, :cmd, :args, :show_output, :output_dir, :log_to_file, :output
|
7
20
|
|
8
21
|
def initialize(cmd, environment={})
|
9
|
-
@cmd = cmd
|
22
|
+
@cmd = cmd.to_s
|
10
23
|
@args = []
|
11
24
|
@env = environment
|
25
|
+
@show_output = true
|
26
|
+
@pipe = nil
|
27
|
+
@output = []
|
28
|
+
@output_dir = Dir.tmpdir
|
29
|
+
@log_to_file = false
|
12
30
|
end
|
13
31
|
|
14
32
|
def <<(arg)
|
@@ -32,9 +50,65 @@ module Xcode
|
|
32
50
|
# to_s==obj.to_s
|
33
51
|
Set.new(obj.to_a) == Set.new(self.to_a)
|
34
52
|
end
|
35
|
-
|
36
|
-
|
37
|
-
|
53
|
+
|
54
|
+
#
|
55
|
+
# Attach an output pipe.
|
56
|
+
#
|
57
|
+
# This can be any object which responds to puts and close
|
58
|
+
def attach(pipe)
|
59
|
+
@pipe = pipe
|
60
|
+
@show_output = false
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Execute the given command
|
65
|
+
#
|
66
|
+
def execute(&block) #:yield: output
|
67
|
+
print_output self.to_s, :debug
|
68
|
+
# print_task 'shell', self.to_s, :debug if show_output
|
69
|
+
begin
|
70
|
+
output_file_name = File.join(@output_dir, "xcoder-#{@cmd}-#{Time.now.strftime('%Y%m%d-%H%M%S')}")
|
71
|
+
|
72
|
+
File.open(output_file_name, "w") do |file|
|
73
|
+
PTY.spawn(to_s) do |r, w, child_pid|
|
74
|
+
r.sync
|
75
|
+
r.each_line do |line|
|
76
|
+
file << line
|
77
|
+
|
78
|
+
print_input line.gsub(/\n$/,''), :debug if @show_output
|
79
|
+
|
80
|
+
if @pipe.nil?
|
81
|
+
# DEPRECATED
|
82
|
+
yield(line) if block_given?
|
83
|
+
else
|
84
|
+
@pipe << line
|
85
|
+
end
|
86
|
+
|
87
|
+
@output << line
|
88
|
+
end
|
89
|
+
Process.wait(child_pid)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
raise ExecutionError.new("Error (#{$?.exitstatus}) executing '#{to_s}'", @output) if $?.exitstatus>0
|
94
|
+
|
95
|
+
if @log_to_file
|
96
|
+
print_system "Captured output to #{output_file_name}", :notice
|
97
|
+
else
|
98
|
+
File.delete(output_file_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
@output
|
102
|
+
rescue Xcode::Shell::ExecutionError => e
|
103
|
+
print_system "Captured output to #{output_file_name}", :notice
|
104
|
+
print_system "Cropped #{e.output.count - 10} lines", :notice if @output.count>10
|
105
|
+
@output.last(10).each do |line|
|
106
|
+
print_output line.strip, :error
|
107
|
+
end
|
108
|
+
raise e
|
109
|
+
ensure
|
110
|
+
@pipe.close unless @pipe.nil?
|
111
|
+
end
|
38
112
|
end
|
39
113
|
|
40
114
|
end
|