ios_build_kit 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +120 -0
- data/LICENSE +19 -0
- data/README.md +269 -0
- data/README.rdoc +25 -0
- data/Rakefile +9 -0
- data/bin/buildkit +17 -0
- data/ios_build_kit-0.0.1.gem +0 -0
- data/ios_build_kit.gemspec +31 -0
- data/lib/ios_build_kit.rb +12 -0
- data/lib/ios_build_kit/task.rb +24 -0
- data/lib/ios_build_kit/task_configuration.rb +13 -0
- data/lib/ios_build_kit/task_runner.rb +82 -0
- data/lib/ios_build_kit/tasks/create_ipa.rb +94 -0
- data/lib/ios_build_kit/tasks/decorate_icon.rb +106 -0
- data/lib/ios_build_kit/tasks/increment_version.rb +55 -0
- data/lib/ios_build_kit/tasks/run_tests.rb +54 -0
- data/lib/ios_build_kit/tasks/xcode_build.rb +57 -0
- data/lib/ios_build_kit/utils/assertions.rb +29 -0
- data/lib/ios_build_kit/utils/console.rb +28 -0
- data/lib/ios_build_kit/utils/plist_pal.rb +28 -0
- data/lib/ios_build_kit/utils/report_creator.rb +21 -0
- data/lib/ios_build_kit/utils/version_number.rb +29 -0
- data/lib/ios_build_kit/version.rb +3 -0
- metadata +181 -0
data/Rakefile
ADDED
data/bin/buildkit
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'ios_build_kit'
|
7
|
+
|
8
|
+
args = ARGV.dup
|
9
|
+
ARGV.clear
|
10
|
+
|
11
|
+
config_file = args[0]
|
12
|
+
|
13
|
+
system "clear"
|
14
|
+
|
15
|
+
BuildKit::Utilities::Console.terminate_with_err "BuildKit requires a configuration file to run, see: (http://github.com/adamwaite/iOS-Build-Kit)." if config_file.nil?
|
16
|
+
|
17
|
+
BuildKit.start_with_configuration config_file
|
Binary file
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require "ios_build_kit/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "ios_build_kit"
|
8
|
+
s.authors = ["Adam Waite"]
|
9
|
+
s.email = "adam@adamjwaite.co.uk"
|
10
|
+
s.homepage = "https://github.com/adamwaite/iOS-Build-Kit"
|
11
|
+
s.version = BuildKit::VERSION
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.summary = "iOS Build Kit"
|
14
|
+
s.description = "iOS Build Tool CLI - Increment version • Decorate icon • Build app • Run unit tests • Create ipa artefact"
|
15
|
+
|
16
|
+
s.add_dependency "commander", "~> 4.1"
|
17
|
+
s.add_dependency "json", "~> 1.8"
|
18
|
+
s.add_dependency "paint", "~> 0.8"
|
19
|
+
s.add_dependency "nomad-cli", "~> 0.0.2"
|
20
|
+
s.add_dependency "rmagick", '~> 2.13'
|
21
|
+
|
22
|
+
s.add_development_dependency "bundler"
|
23
|
+
s.add_development_dependency "rspec"
|
24
|
+
s.add_development_dependency "rake"
|
25
|
+
|
26
|
+
s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor|example|resources)/ }
|
27
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
28
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module BuildKit
|
2
|
+
|
3
|
+
Dir["#{File.dirname(__FILE__)}/ios_build_kit/**/*.rb"].each { |f| load(f) }
|
4
|
+
|
5
|
+
def self.start_with_configuration config
|
6
|
+
BuildKit::Utilities::Assertions.assert_files_exist [config]
|
7
|
+
BuildKit::Utilities::Console.header_msg "BuildKit Run Started"
|
8
|
+
runner = BuildKit::Runner::TaskRunner.new({ config_file: config })
|
9
|
+
runner.run_tasks!
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BuildKit
|
2
|
+
|
3
|
+
module Tasks
|
4
|
+
|
5
|
+
class BuildKitTask
|
6
|
+
|
7
|
+
attr_reader :runner, :config, :task_options
|
8
|
+
|
9
|
+
def initialize(attributes = {})
|
10
|
+
prepare_task_with_runner_and_options! attributes[:runner], attributes[:opts]
|
11
|
+
end
|
12
|
+
|
13
|
+
def prepare_task_with_runner_and_options! runner, opts
|
14
|
+
@runner = runner
|
15
|
+
@config = runner.config
|
16
|
+
@task_options = opts
|
17
|
+
assert_requirements
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module BuildKit
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Runner
|
6
|
+
|
7
|
+
class TaskRunner
|
8
|
+
|
9
|
+
attr_reader :config, :preferences, :tasks, :outputs, :store
|
10
|
+
|
11
|
+
def initialize(attributes = {})
|
12
|
+
passed = hash_from_yaml attributes[:config_file]
|
13
|
+
@config = TaskRunnerConfig.new(passed[:configuration]).freeze
|
14
|
+
@preferences = TaskRunnerConfig.new (passed[:preferences]).freeze
|
15
|
+
prepare_task_queue! passed[:tasks]
|
16
|
+
@store = {}
|
17
|
+
@outputs = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def run_tasks!
|
21
|
+
@tasks[:raw].each do |task_name, task_opts|
|
22
|
+
if task_opts[:run]
|
23
|
+
@tasks[:running] = task_name
|
24
|
+
BuildKit::Utilities::Console.header_msg "Running Task: #{task_name}"
|
25
|
+
BuildKit::Tasks.send task_name, self, task_opts[:options]
|
26
|
+
else
|
27
|
+
BuildKit::Utilities::Console.header_msg "Skipping Task: #{task_name}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
all_tasks_completed
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_completed_task? task
|
34
|
+
@tasks[:completed].include? task
|
35
|
+
end
|
36
|
+
|
37
|
+
def task_completed! task, success_message, output
|
38
|
+
@tasks[:completed] << task
|
39
|
+
BuildKit::Utilities::Console.success_msg success_message
|
40
|
+
@outputs[task] = output
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def hash_from_yaml file
|
46
|
+
YAML.load_file(file)
|
47
|
+
end
|
48
|
+
|
49
|
+
def prepare_task_queue! tasks
|
50
|
+
@tasks = { raw: {}, run: [], skip: [], completed: [], running: nil }
|
51
|
+
@tasks[:raw] = tasks.freeze
|
52
|
+
@tasks[:run] = tasks.select { |k, v| v[:run] == true }.map do |k, v|
|
53
|
+
t = Hash.new
|
54
|
+
t[k] = { run: v[:run], options: v[:options] }
|
55
|
+
t
|
56
|
+
end.freeze
|
57
|
+
@tasks[:skip] = tasks.select { |k, v| v[:run] == false }.map do |k, v|
|
58
|
+
t = Hash.new
|
59
|
+
t[k] = { run: v[:run], options: v[:options] }
|
60
|
+
t
|
61
|
+
end.freeze
|
62
|
+
@tasks[:run].freeze
|
63
|
+
@tasks[:skip].freeze
|
64
|
+
end
|
65
|
+
|
66
|
+
def all_tasks_completed
|
67
|
+
unless @preferences.reports.nil?
|
68
|
+
if File.exists? @preferences.reports
|
69
|
+
build_report = BuildKit::Utilities::Reporter::create_report self
|
70
|
+
BuildKit::Utilities::Console.success_msg "BuildKit run report created at: #{build_report}"
|
71
|
+
else
|
72
|
+
puts "Warning: Report not created. Invalid directory specified: #{@preferences.reports}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
BuildKit::Utilities::Console.success_msg "\n😃 BuildKit Run Completed! Ran #{@tasks[:completed].count} tasks successfully!\n\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module BuildKit
|
2
|
+
|
3
|
+
module Tasks
|
4
|
+
|
5
|
+
def self.create_ipa runner, task_opts
|
6
|
+
task = CreateIPATask.new({ runner: runner, opts: task_opts })
|
7
|
+
task.run!
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
class CreateIPATask < BuildKitTask
|
13
|
+
|
14
|
+
attr_accessor :artefact_filename
|
15
|
+
|
16
|
+
def initialize(attributes = {})
|
17
|
+
super
|
18
|
+
warn_about_missing_plist_config if @config.info_plist.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def run!
|
22
|
+
build_artefact_filename!
|
23
|
+
run_command!
|
24
|
+
complete_task!
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def assert_requirements
|
30
|
+
BuildKit::Utilities::Assertions.assert_tasks_completed [:xcode_build], @runner
|
31
|
+
BuildKit::Utilities::Assertions.assert_required_config [:code_sign, :provisioning_profile], @runner
|
32
|
+
BuildKit::Utilities::Assertions.assert_files_exist [@config.provisioning_profile]
|
33
|
+
end
|
34
|
+
|
35
|
+
def warn_about_missing_plist_config
|
36
|
+
BuildKit.warning_msg "Provide the :info_plist option in the config file to include the version number in the ipa file name"
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_artefact_filename!
|
40
|
+
name = @config.app_name.dup
|
41
|
+
if (!@config.info_plist.nil? and File.exists? @config.info_plist)
|
42
|
+
name << "-"
|
43
|
+
if @runner.has_completed_task? :increment_version
|
44
|
+
name << @runner.store[:new_version_number][:full]
|
45
|
+
else
|
46
|
+
name << BuildKit::Utilities::VersionNumber.plist_version_number[:full]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
name << "-"
|
50
|
+
name << Time.now.to_i.to_s
|
51
|
+
name << ".ipa"
|
52
|
+
@artefact_filename = name
|
53
|
+
end
|
54
|
+
|
55
|
+
def artefact_full_path
|
56
|
+
build_artefact_filename! if @artefact_filename.nil?
|
57
|
+
@config.build_dir + @artefact_filename
|
58
|
+
end
|
59
|
+
|
60
|
+
def existing_ipa_count
|
61
|
+
Dir["#{@build_dir}*.ipa"].length
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_command
|
65
|
+
workspace_arg = "-workspace \"#{@config.workspace}\""
|
66
|
+
sdk_arg = "-sdk #{@config.sdk}"
|
67
|
+
build_file_arg = "-v \"#{@config.build_dir}#{@config.app_name}.app\""
|
68
|
+
output_file_arg = "-o \"#{artefact_full_path}\""
|
69
|
+
code_sign_arg = "--sign #{@config.code_sign}"
|
70
|
+
provisioning_arg = "--embed \"#{@config.provisioning_profile}\""
|
71
|
+
"xcrun #{sdk_arg} PackageApplication #{build_file_arg} #{output_file_arg} #{provisioning_arg}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def run_command!
|
75
|
+
command = build_command
|
76
|
+
@output = %x[#{command}]
|
77
|
+
puts @output if @task_options[:log]
|
78
|
+
end
|
79
|
+
|
80
|
+
def ipa_created_successfully?
|
81
|
+
File.exists? artefact_full_path
|
82
|
+
end
|
83
|
+
|
84
|
+
def complete_task!
|
85
|
+
runner.store[:artefact_created] = artefact_full_path if ipa_created_successfully?
|
86
|
+
message = (ipa_created_successfully?) ? "create_ipa completed, ipa at: #{artefact_full_path}" : "create_ipa task completed but the ipa was not created"
|
87
|
+
runner.task_completed! :create_ipa, message, @output
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module BuildKit
|
2
|
+
|
3
|
+
require 'rmagick'
|
4
|
+
|
5
|
+
module Tasks
|
6
|
+
|
7
|
+
def self.decorate_icon runner, task_opts
|
8
|
+
task = DecorateIconTask.new({ runner: runner, opts: task_opts })
|
9
|
+
task.run!
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
class DecorateIconTask < BuildKitTask
|
15
|
+
|
16
|
+
attr_reader :decorated_icons
|
17
|
+
|
18
|
+
def initialize(attributes = {})
|
19
|
+
super
|
20
|
+
@decorated_icons = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def run!
|
24
|
+
decorate_icons!
|
25
|
+
complete_task!
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def assert_requirements
|
31
|
+
BuildKit::Utilities::Assertions.assert_required_config [:info_plist, :icon_dir], @runner
|
32
|
+
BuildKit::Utilities::Assertions.assert_files_exist [@config.info_plist, @config.icon_dir]
|
33
|
+
end
|
34
|
+
|
35
|
+
def version_number_to_draw
|
36
|
+
if @runner.has_completed_task? :increment_version
|
37
|
+
@runner.store[:new_version_number][:full]
|
38
|
+
else
|
39
|
+
BuildKit::Utilities::VersionNumber.plist_version_number(runner.config.info_plist)[:full]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def icon_files_to_decorate
|
44
|
+
to_decorate = []
|
45
|
+
Dir.glob("#{runner.config[:icon_dir]}*.png").each do |filename|
|
46
|
+
next if filename.include? "Decorated"
|
47
|
+
to_decorate << filename
|
48
|
+
end
|
49
|
+
to_decorate
|
50
|
+
end
|
51
|
+
|
52
|
+
def decorate_icons!
|
53
|
+
icon_files_to_decorate.each do |img_path|
|
54
|
+
@decorated_icons << create_decorated_version_of(img_path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_decorated_version_of icon_path
|
59
|
+
original = Magick::ImageList.new icon_path
|
60
|
+
decorated_icon = original.copy
|
61
|
+
|
62
|
+
icon_dimension = original.rows
|
63
|
+
|
64
|
+
background = Magick::Draw.new
|
65
|
+
background.fill_opacity(0.75)
|
66
|
+
background.rectangle(0, icon_dimension - (icon_dimension * 0.225), icon_dimension, icon_dimension)
|
67
|
+
background.draw decorated_icon
|
68
|
+
|
69
|
+
annotation_params = {
|
70
|
+
gravity: Magick::SouthGravity,
|
71
|
+
pointsize: icon_dimension * 0.11 ,
|
72
|
+
stroke: 'transparent',
|
73
|
+
fill: '#FFF',
|
74
|
+
font_family: "Helvetica CY",
|
75
|
+
font_weight: Magick::BoldWeight
|
76
|
+
}
|
77
|
+
|
78
|
+
version_text = Magick::Draw.new
|
79
|
+
version_text.annotate(decorated_icon, 0, 0, 0, icon_dimension * 0.05 , "#{version_number_to_draw}") do
|
80
|
+
self.gravity = annotation_params[:gravity]
|
81
|
+
self.pointsize = annotation_params[:pointsize]
|
82
|
+
self.stroke = annotation_params[:stroke]
|
83
|
+
self.fill = annotation_params[:fill]
|
84
|
+
self.font_family = annotation_params[:font_family]
|
85
|
+
self.font_weight = annotation_params[:font_weight]
|
86
|
+
end
|
87
|
+
|
88
|
+
original_img_filename = File.basename icon_path
|
89
|
+
new_img_filename = "Decorated-" + original_img_filename
|
90
|
+
new_img_full_path = icon_path.gsub(original_img_filename, new_img_filename)
|
91
|
+
|
92
|
+
decorated_icon.write(new_img_full_path)
|
93
|
+
|
94
|
+
new_img_full_path
|
95
|
+
end
|
96
|
+
|
97
|
+
def complete_task!
|
98
|
+
message = "Icons have been decorated with #{version_number_to_draw}. They are here: " + @decorated_icons.join(",")
|
99
|
+
@runner.task_completed! :decorate_icons, message, message
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module BuildKit
|
2
|
+
|
3
|
+
module Tasks
|
4
|
+
|
5
|
+
def self.increment_version runner, task_opts
|
6
|
+
task = IncrementVersionTask.new({ runner: runner, opts: task_opts })
|
7
|
+
task.run!
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
class IncrementVersionTask < BuildKitTask
|
13
|
+
|
14
|
+
attr_reader :existing_version_number, :new_version_number
|
15
|
+
|
16
|
+
def run!
|
17
|
+
read_existing_version!
|
18
|
+
calculate_new_version!
|
19
|
+
write_new_version!
|
20
|
+
complete_task!
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def assert_requirements
|
26
|
+
BuildKit::Utilities::Assertions.assert_files_exist [@config.info_plist]
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_existing_version!
|
30
|
+
@existing_version_number = BuildKit::Utilities::VersionNumber.plist_version_number @config.info_plist
|
31
|
+
@runner.store[:existing_version_number] = @existing_version_number
|
32
|
+
end
|
33
|
+
|
34
|
+
def calculate_new_version!
|
35
|
+
incremented_build_number = @existing_version_number[:build] + 1
|
36
|
+
@new_version_number = BuildKit::Utilities::VersionNumber.hash_from_version_and_build @existing_version_number[:major_minor_revision], incremented_build_number
|
37
|
+
@runner.store[:new_version_number] = @new_version_number
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_new_version!
|
41
|
+
BuildKit::Utilities::PlistPal.write_value_in_plist(@config.info_plist, "CFBundleShortVersionString", @new_version_number[:major_minor_revision] )
|
42
|
+
BuildKit::Utilities::PlistPal.write_value_in_plist(@config.info_plist, "CFBundleVersion", @new_version_number[:build] )
|
43
|
+
end
|
44
|
+
|
45
|
+
def complete_task!
|
46
|
+
output = "incremented version number from #{@existing_version_number[:full]} to #{@new_version_number[:full]}"
|
47
|
+
message = output + " and written to plist successfully"
|
48
|
+
@runner.task_completed! :increment_version, message, output
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|