xcoder 0.1.15 → 0.1.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +6 -0
  2. data/.rbenv-version +1 -0
  3. data/.rvmrc +1 -1
  4. data/Gemfile +10 -2
  5. data/README.md +110 -9
  6. data/Rakefile +2 -2
  7. data/bin/xcoder +74 -0
  8. data/lib/xcode/builder.rb +1 -2
  9. data/lib/xcode/builder/base_builder.rb +231 -102
  10. data/lib/xcode/builder/build_parser.rb +146 -0
  11. data/lib/xcode/builder/project_target_config_builder.rb +2 -2
  12. data/lib/xcode/builder/scheme_builder.rb +29 -12
  13. data/lib/xcode/buildspec.rb +286 -0
  14. data/lib/xcode/configuration_list.rb +24 -24
  15. data/lib/xcode/deploy/ftp.rb +56 -0
  16. data/lib/xcode/deploy/kickfolio.rb +18 -0
  17. data/lib/xcode/deploy/s3.rb +38 -0
  18. data/lib/xcode/deploy/ssh.rb +43 -0
  19. data/lib/xcode/deploy/templates/index.rhtml +22 -0
  20. data/lib/xcode/deploy/templates/manifest.rhtml +31 -0
  21. data/lib/xcode/deploy/testflight.rb +32 -27
  22. data/lib/xcode/deploy/web_assets.rb +39 -0
  23. data/lib/xcode/info_plist.rb +16 -0
  24. data/lib/xcode/keychain.rb +33 -10
  25. data/lib/xcode/platform.rb +65 -0
  26. data/lib/xcode/project.rb +7 -3
  27. data/lib/xcode/provisioning_profile.rb +38 -2
  28. data/lib/xcode/scheme.rb +44 -17
  29. data/lib/xcode/shell/command.rb +79 -5
  30. data/lib/xcode/terminal_output.rb +116 -0
  31. data/lib/xcode/test/formatters/junit_formatter.rb +7 -2
  32. data/lib/xcode/test/formatters/stdout_formatter.rb +34 -25
  33. data/lib/xcode/test/parsers/kif_parser.rb +87 -0
  34. data/lib/xcode/test/parsers/ocunit_parser.rb +3 -3
  35. data/lib/xcode/version.rb +1 -1
  36. data/lib/xcode/workspace.rb +13 -5
  37. data/lib/xcoder.rb +33 -31
  38. data/spec/TestProject/TestProject.xcodeproj/project.pbxproj +1627 -1015
  39. data/spec/TestWorkspace.xcworkspace/contents.xcworkspacedata +7 -0
  40. data/spec/builder_spec.rb +87 -71
  41. data/spec/deploy_spec.rb +63 -0
  42. data/spec/ocunit_parser_spec.rb +1 -1
  43. data/xcoder.gemspec +3 -1
  44. metadata +95 -19
  45. data/lib/xcode/buildfile.rb +0 -101
  46. data/lib/xcode/shell.rb +0 -26
  47. data/spec/deploy_testflight_spec.rb +0 -27
@@ -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
@@ -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
- keychains = self.search_path
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
- data = cmd.execute(false).join("")
92
- data.scan /\s+"labl"<blob>="([^"]+)"/ do |m|
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
@@ -294,7 +294,7 @@ module Xcode
294
294
 
295
295
  target.name = name
296
296
 
297
- build_configuration_list = @registry.add_object(ConfigurationList.configration_list)
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 " + Launch action => target:#{s.launch.target.name}, config:#{s.launch.name}" unless s.launch.nil?
345
- puts " + Test action => target:#{s.test.target.name}, config:#{s.test.name}" unless s.test.nil?
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
- Xcode::Shell.execute("cp #{self.path} #{self.install_path}")
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
- Xcode::Shell.execute("rm -f #{self.install_path}")
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
@@ -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, :build_config, :build_targets
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.schemes
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
- # Returns a builder for building this scheme
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
@@ -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
- attr_accessor :env, :cmd, :args
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
- def execute(show_output=true, &block) #:yield: output
37
- Xcode::Shell.execute(self, show_output, &block)
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