xcodebuild-rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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.
@@ -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).
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/xcodebuild"
3
+ XcodeBuild.run(ARGV[0] || "")
@@ -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,6 @@
1
+ module XcodeBuild
2
+ module Formatters
3
+ end
4
+ end
5
+
6
+ require_relative "formatters/progress_formatter"
@@ -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