xcoder 0.1.15 → 0.1.18
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.
- 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
|