xcodebuild-rb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.md +59 -0
- data/bin/rbxcb +3 -0
- data/lib/xcode_build.rb +27 -0
- data/lib/xcode_build/build_action.rb +72 -0
- data/lib/xcode_build/build_step.rb +47 -0
- data/lib/xcode_build/formatters.rb +6 -0
- data/lib/xcode_build/formatters/progress_formatter.rb +99 -0
- data/lib/xcode_build/output_translator.rb +82 -0
- data/lib/xcode_build/reporter.rb +22 -0
- data/lib/xcode_build/reporting/build_reporting.rb +61 -0
- data/lib/xcode_build/reporting/clean_reporting.rb +59 -0
- data/lib/xcode_build/tasks/build_task.rb +84 -0
- data/lib/xcode_build/translations.rb +20 -0
- data/lib/xcode_build/translations/building.rb +101 -0
- data/lib/xcode_build/translations/cleaning.rb +96 -0
- data/lib/xcode_build/utilities/colorize.rb +45 -0
- data/lib/xcodebuild.rb +1 -0
- data/spec/build_task_spec.rb +142 -0
- data/spec/output_translator_spec.rb +52 -0
- data/spec/reporting/build_reporting_spec.rb +305 -0
- data/spec/reporting/clean_reporting_spec.rb +276 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/translations/building_translations_spec.rb +167 -0
- data/spec/translations/cleaning_translations_spec.rb +144 -0
- metadata +164 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2012 Luke Redpath
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# xcodebuild-rb: Building Xcode projects with Rake
|
2
|
+
|
3
|
+
[![Build Status](https://secure.travis-ci.org/lukeredpath/xcodebuild-rb.png)](http://travis-ci.org/lukeredpath/xcodebuild-rb])
|
4
|
+
|
5
|
+
xcodebuild-rb is a RubyGem that provides a Ruby interface to the `xcodebuild` utility that ships with Xcode in the form of a series of Rake tasks. This gem only supports Xcode 4 (you can try it with 3.x, but YMMV). It makes it simple to run your builds from the command line, especially on remote machines such as Continuous Integration servers.
|
6
|
+
|
7
|
+
In addition, it provides configurable output parsing that enables better formatting of Xcode build results and eventually, test results. This is done through a series of output translations, reporters and formatters. All of this can be extended with a bit of Ruby knowledge (more on that soon).
|
8
|
+
|
9
|
+
This library is still under development but it should be more or less usable out of the box.
|
10
|
+
|
11
|
+
## Getting started
|
12
|
+
|
13
|
+
After installing the gem, you need to create a `Rakefile` in the root of your project if you don't already have one. If you aren't familiar with `rake`, you can think of it as a Ruby equivalent to `make`. You can find out more about `rake` [here](http://rake.rubyforge.org/).
|
14
|
+
|
15
|
+
A simple Rakefile will look like this:
|
16
|
+
|
17
|
+
require 'rubygems'
|
18
|
+
require 'xcodebuild-rb'
|
19
|
+
|
20
|
+
XcodeBuild::Tasks::BuildTask.new
|
21
|
+
|
22
|
+
With only those three lines, you will now have access to a variety of tasks such as clean and build. A full list of tasks can be viewed by running `rake -T`:
|
23
|
+
|
24
|
+
$ rake -T
|
25
|
+
rake xcode:build # Builds the specified target(s).
|
26
|
+
rake xcode:clean # Cleans the build using the same build settings.
|
27
|
+
rake xcode:cleanbuild # Builds the specified target(s) from a clean slate.
|
28
|
+
|
29
|
+
## Configuring your tasks
|
30
|
+
|
31
|
+
When you run `rake xcode:build`, `xcodebuild` will be invoked without any arguments, which will in turn cause the default build to run. For this to be more useful, we need to configure the task. We could, for instance, configure the target and configuration:
|
32
|
+
|
33
|
+
XcodeBuild::Tasks::BuildTask.new do |t|
|
34
|
+
t.target = "MyApp"
|
35
|
+
t.configuration = "Release"
|
36
|
+
end
|
37
|
+
|
38
|
+
When you run the rake tasks provided, the default behaviour is to simply output the exact output from `xcodebuild`. However, `xcodebuild-rb` can go one better and allow you to configure custom formatters that change the way the build output is displayed. Some formatters are built-in, or you can write your own.
|
39
|
+
|
40
|
+
For instance, we could use the "progress" formatter that ships with `xcodebuild-rb`. Anybody who is used to the output of Ruby's Test::Unit library or RSpec library will be familiar with this.
|
41
|
+
|
42
|
+
XcodeBuild::Tasks::BuildTask.new do |t|
|
43
|
+
t.formatter = XcodeBuild::Formatters::ProgressFormatter.new
|
44
|
+
end
|
45
|
+
|
46
|
+
Now when you run your build, your output will look something like this:
|
47
|
+
|
48
|
+
Building target: ExampleProject (in ExampleProject.xcproject)
|
49
|
+
=============================================================
|
50
|
+
|
51
|
+
Configuration: Release
|
52
|
+
..............
|
53
|
+
|
54
|
+
Finished in 2.226883 seconds.
|
55
|
+
Build succeeded.
|
56
|
+
|
57
|
+
## License
|
58
|
+
|
59
|
+
This library is licensed under the [MIT license](http://en.wikipedia.org/wiki/MIT_License).
|
data/bin/rbxcb
ADDED
data/lib/xcode_build.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module XcodeBuild
|
2
|
+
def self.run(args = "", output_buffer = STDOUT)
|
3
|
+
IO.popen("xcodebuild #{args} 2>&1") do |io|
|
4
|
+
begin
|
5
|
+
while line = io.readline
|
6
|
+
begin
|
7
|
+
output_buffer << line
|
8
|
+
rescue StandardError => e
|
9
|
+
puts "Error from output buffer: #{e.inspect}"
|
10
|
+
puts e.backtrace
|
11
|
+
end
|
12
|
+
end
|
13
|
+
rescue EOFError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require_relative "xcode_build/build_action"
|
20
|
+
require_relative "xcode_build/build_step"
|
21
|
+
require_relative 'xcode_build/output_translator'
|
22
|
+
require_relative 'xcode_build/reporter'
|
23
|
+
require_relative 'xcode_build/formatters'
|
24
|
+
|
25
|
+
# configure the default translations for general use
|
26
|
+
XcodeBuild::OutputTranslator.use_translation(:building)
|
27
|
+
XcodeBuild::OutputTranslator.use_translation(:cleaning)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'state_machine'
|
2
|
+
|
3
|
+
require_relative "build_step"
|
4
|
+
|
5
|
+
module XcodeBuild
|
6
|
+
class BuildAction
|
7
|
+
attr_reader :steps_completed
|
8
|
+
attr_writer :finished_at
|
9
|
+
|
10
|
+
def initialize(metadata)
|
11
|
+
@steps_completed = []
|
12
|
+
@metadata = metadata
|
13
|
+
@started_at = Time.now
|
14
|
+
super()
|
15
|
+
end
|
16
|
+
|
17
|
+
state_machine :state, :initial => :running do
|
18
|
+
event :success do
|
19
|
+
transition :running => :successful
|
20
|
+
end
|
21
|
+
|
22
|
+
event :failure do
|
23
|
+
transition :running => :failed
|
24
|
+
end
|
25
|
+
|
26
|
+
after_transition :running => [:successful, :failed] do |build|
|
27
|
+
build.finished_at = Time.now
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_step(params)
|
32
|
+
@steps_completed << BuildStep.new(params)
|
33
|
+
end
|
34
|
+
|
35
|
+
def failed_steps
|
36
|
+
@steps_completed.select { |a| a.failed? }
|
37
|
+
end
|
38
|
+
|
39
|
+
def step_with_params(params)
|
40
|
+
@steps_completed.detect { |a| a == BuildStep.new(params) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def last_step
|
44
|
+
@steps_completed.last
|
45
|
+
end
|
46
|
+
|
47
|
+
def finished?
|
48
|
+
successful? || failed?
|
49
|
+
end
|
50
|
+
|
51
|
+
def duration
|
52
|
+
return nil unless finished?
|
53
|
+
@finished_at - @started_at
|
54
|
+
end
|
55
|
+
|
56
|
+
def project_name
|
57
|
+
@metadata[:project]
|
58
|
+
end
|
59
|
+
|
60
|
+
def target
|
61
|
+
@metadata[:target]
|
62
|
+
end
|
63
|
+
|
64
|
+
def configuration
|
65
|
+
@metadata[:configuration]
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_configuration?
|
69
|
+
@metadata[:default]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
|
3
|
+
module XcodeBuild
|
4
|
+
class BuildStep
|
5
|
+
attr_accessor :failed
|
6
|
+
attr_reader :errors
|
7
|
+
|
8
|
+
def initialize(metadata)
|
9
|
+
@metadata = metadata
|
10
|
+
@errors = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_error(params)
|
14
|
+
@errors << Error.new(params)
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_errors?
|
18
|
+
@errors.any?
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other_action)
|
22
|
+
(other_action.type == type &&
|
23
|
+
other_action.arguments == arguments)
|
24
|
+
end
|
25
|
+
|
26
|
+
def type
|
27
|
+
@metadata[:type]
|
28
|
+
end
|
29
|
+
|
30
|
+
def arguments
|
31
|
+
@metadata[:arguments]
|
32
|
+
end
|
33
|
+
|
34
|
+
def failed?
|
35
|
+
@failed
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
[type, arguments]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
class Error < OpenStruct
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative '../utilities/colorize'
|
3
|
+
|
4
|
+
module XcodeBuild
|
5
|
+
module Formatters
|
6
|
+
class ProgressFormatter
|
7
|
+
include XcodeBuild::Utilities::Colorize
|
8
|
+
|
9
|
+
def initialize(output = STDOUT)
|
10
|
+
@output = output
|
11
|
+
end
|
12
|
+
|
13
|
+
def clean_started(clean)
|
14
|
+
report_started("Cleaning", clean)
|
15
|
+
end
|
16
|
+
|
17
|
+
def clean_step_finished(step)
|
18
|
+
report_step_finished(step)
|
19
|
+
end
|
20
|
+
|
21
|
+
def clean_finished(clean)
|
22
|
+
report_finished("Clean", clean)
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_started(build)
|
26
|
+
report_started("Building", build)
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_step_finished(step)
|
30
|
+
report_step_finished(step)
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_finished(build)
|
34
|
+
report_finished("Build", build)
|
35
|
+
end
|
36
|
+
|
37
|
+
def report_finished(type, object)
|
38
|
+
puts
|
39
|
+
puts
|
40
|
+
puts "Finished in #{object.duration} seconds."
|
41
|
+
|
42
|
+
if object.successful?
|
43
|
+
puts green("#{type} succeeded.")
|
44
|
+
else
|
45
|
+
puts red("#{type} failed.")
|
46
|
+
puts
|
47
|
+
puts "Failed #{type.downcase} steps:"
|
48
|
+
puts
|
49
|
+
error_counter = 1
|
50
|
+
object.steps_completed.each do |step|
|
51
|
+
next unless step.has_errors?
|
52
|
+
|
53
|
+
puts indent("#{error_counter}) #{step.type} #{step.arguments.join(" ")}")
|
54
|
+
|
55
|
+
step.errors.each do |err|
|
56
|
+
puts indent(" #{red(err.message)}")
|
57
|
+
puts indent(cyan(" in #{err.file}:#{err.line.to_s}")) if err.file
|
58
|
+
puts
|
59
|
+
end
|
60
|
+
|
61
|
+
error_counter += 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
puts
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def puts(str = "")
|
70
|
+
@output.puts( str)
|
71
|
+
end
|
72
|
+
|
73
|
+
def color_enabled?
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def indent(string)
|
78
|
+
" #{string}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def report_started(type, object)
|
82
|
+
puts
|
83
|
+
banner = "#{type} target: #{object.target} (in #{object.project_name}.xcproject)"
|
84
|
+
puts bold(banner)
|
85
|
+
puts banner.length.times.map { "=" }.join
|
86
|
+
puts
|
87
|
+
puts "Configuration: #{object.configuration}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def report_step_finished(step)
|
91
|
+
if step.has_errors?
|
92
|
+
print red("F")
|
93
|
+
else
|
94
|
+
print green(".")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative 'translations'
|
3
|
+
|
4
|
+
module XcodeBuild
|
5
|
+
class OutputTranslator
|
6
|
+
attr_reader :translations, :delegate
|
7
|
+
|
8
|
+
def initialize(delegate, options = {})
|
9
|
+
@delegate = delegate
|
10
|
+
@translation_modules = []
|
11
|
+
@translations = []
|
12
|
+
OutputTranslator.prepare_instance(self) unless options[:ignore_global_translations]
|
13
|
+
end
|
14
|
+
|
15
|
+
def <<(line)
|
16
|
+
notify_delegate(:beginning_translation_of_line, args: line)
|
17
|
+
translations.each { |translation| translation.attempt_to_translate(line) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def use_translation(translation_module)
|
21
|
+
unless translation_module.nil? || @translation_modules.include?(translation_module)
|
22
|
+
@translation_modules << translation_module
|
23
|
+
@translations << ConcreteTranslation.new(@delegate, translation_module)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def use_translation(translation_module_or_key)
|
29
|
+
if translation_module_or_key.is_a?(Symbol)
|
30
|
+
translation_module = Translations.registered_translation(translation_module_or_key)
|
31
|
+
else
|
32
|
+
translation_module = translation_module_or_key
|
33
|
+
end
|
34
|
+
|
35
|
+
@any_instance_translations ||= []
|
36
|
+
@any_instance_translations << translation_module
|
37
|
+
end
|
38
|
+
|
39
|
+
def prepare_instance(translator)
|
40
|
+
@any_instance_translations.each do |translation|
|
41
|
+
translator.use_translation(translation)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class MissingDelegateMethodError < StandardError
|
47
|
+
def initialize(method)
|
48
|
+
@method = method
|
49
|
+
end
|
50
|
+
|
51
|
+
def message
|
52
|
+
"Delegate must implement the #{@method.to_sym} method."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
module TranslationHelpers
|
59
|
+
def notify_delegate(message, options = {args: []})
|
60
|
+
if @delegate.respond_to?(message)
|
61
|
+
@delegate.send(message, *options[:args])
|
62
|
+
else
|
63
|
+
if options[:required]
|
64
|
+
raise MissingDelegateMethodError.new(message)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class ConcreteTranslation
|
71
|
+
include TranslationHelpers
|
72
|
+
|
73
|
+
def initialize(delegate, translation)
|
74
|
+
@delegate = delegate
|
75
|
+
extend translation
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
include TranslationHelpers
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative "reporting/build_reporting"
|
2
|
+
require_relative "reporting/clean_reporting"
|
3
|
+
|
4
|
+
module XcodeBuild
|
5
|
+
class Reporter
|
6
|
+
include Reporting::BuildReporting
|
7
|
+
include Reporting::CleanReporting
|
8
|
+
|
9
|
+
attr_accessor :delegate
|
10
|
+
|
11
|
+
def initialize(delegate = nil)
|
12
|
+
@delegate = delegate
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def notify(event, *args)
|
18
|
+
return unless @delegate
|
19
|
+
@delegate.send(event, *args) if @delegate.respond_to?(event)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|