ios_build_kit 0.0.1
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.
- 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
|