XcodeCI 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3685b018822354139c0f9e88ce48a12c03bda5d9
4
+ data.tar.gz: e4c4cefe8216c212f72d2f843cc22cec7ad163bf
5
+ SHA512:
6
+ metadata.gz: 6baa29afb994b528fe693f47a97d7948faab1b34ee938d4d8186cc0657ca47f23e622ee7951db4b3cb44d1649748e91b85450259e228488e48306d6e919fdea8
7
+ data.tar.gz: fc1f157cfdab32f8f32d74a6f837932238922e65bbe76cc0b03d91e9540297be1cc2bc529b12361440930482b7b365761a40be25fea341e55fc5ed2fbfe26cee
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+ # gem 'colorize'
5
+ # gem 'cocoapods'
6
+ # gem 'ipa_reader'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ignazio Calò
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/xcodeci ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'xcodeci'
6
+
7
+ if ARGV[0] == 'clear_all'
8
+ puts "This command will remove all the working copies and any configuration file."
9
+ print "Are you sure ?[yn] "
10
+ STDOUT.flush
11
+ answer = STDIN.gets.chomp
12
+ if answer.downcase != 'y'
13
+ puts "Operation cancelled. Exit"
14
+ exit 0
15
+ end
16
+
17
+ FileUtils.rm_r(Xcodeci::HOME)
18
+ exit(0)
19
+ end
20
+
21
+ #Xcodeci::Command.run(*ARGV)
22
+
23
+ while true
24
+ Xcodeci::Command.run(*ARGV)
25
+ sleep 60 * 5
26
+ end
27
+
28
+
data/lib/xcodeci.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Xcodeci
2
+ require 'colorize'
3
+ require 'yaml'
4
+ require 'ipa_reader'
5
+ require 'open-uri'
6
+
7
+ autoload :Command, 'xcodeci/command'
8
+ autoload :Database, 'xcodeci/database'
9
+ autoload :GitUtils, 'xcodeci/gitutils'
10
+ autoload :BuildUtils, 'xcodeci/buildutils'
11
+ autoload :ArchiveUtils, 'xcodeci/archiveUtils'
12
+ autoload :Logger, 'xcodeci/logger'
13
+ autoload :HtmlReporter, 'xcodeci/html_reporter'
14
+ autoload :Configuration, 'xcodeci/configuration'
15
+
16
+ HOME = File.join(Dir.home, ".xcodeci")
17
+ TEMPLATE = File.expand_path("../../templates/", __FILE__)
18
+
19
+ end
@@ -0,0 +1,24 @@
1
+ module Xcodeci
2
+ class ArchiveUtils
3
+
4
+ def archive_ipa(workspace, scheme, path)
5
+ build_dir = %x(xctool -configuration Release -workspace "#{workspace}" -scheme "#{scheme}" -sdk iphoneos -showBuildSettings | grep -m1 TARGET_BUILD_DIR | cut -d'=' -f2 | xargs | tr -d '\n')
6
+ app_name = %x(xctool -configuration Release -workspace "#{workspace}" -scheme "#{scheme}" -sdk iphoneos -showBuildSettings | grep -m1 WRAPPER_NAME | cut -d'=' -f2 | xargs | tr -d '\n')
7
+
8
+ %x( xcrun -sdk iphoneos PackageApplication -v "#{build_dir}/#{app_name}" -o "#{path}" )#--sign "#{SIGN_COMPANY_NAME}" )#--embed "#{PROVISIONING_PATH}")
9
+ $?.exitstatus.zero?
10
+ end
11
+
12
+ def save_dsym(workspace, scheme, path)
13
+ build_dir = %x(xctool -configuration Release -workspace "#{workspace}" -scheme "#{scheme}" -sdk iphoneos -showBuildSettings | grep -m1 TARGET_BUILD_DIR | cut -d'=' -f2 | xargs | tr -d '\n')
14
+ app_name = %x(xctool -configuration Release -workspace "#{workspace}" -scheme "#{scheme}" -sdk iphoneos -showBuildSettings | grep -m1 WRAPPER_NAME | cut -d'=' -f2 | xargs | tr -d '\n')
15
+ result = 0
16
+ begin
17
+ FileUtils.cp_r "#{build_dir}/#{app_name}.dSYM", path , :verbose => false
18
+ rescue => e
19
+ result = 1
20
+ end
21
+ result.zero?
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module Xcodeci
2
+ class BuildUtils
3
+
4
+ def install_pod
5
+ %x(pod install)
6
+ $?.exitstatus.zero?
7
+ end
8
+
9
+ def run_build(repository_url, workspace, scheme)
10
+ %x(xcodebuild -configuration Release -workspace \"#{workspace}\" -scheme \"#{scheme}\" -sdk iphoneos clean build 2>&1)
11
+ $?.exitstatus.zero?
12
+ end
13
+
14
+ def run_test(repository_name, workspace, scheme)
15
+ %x(xcodebuild -configuration Release -workspace \"#{workspace}\" -scheme \"#{scheme}\" -sdk iphonesimulator8.0 -arch i386 clean test)
16
+ # %x(xctool -configuration Release -workspace \"#{workspace}\" -scheme \"#{scheme}\" -sdk iphonesimulator test > /dev/null)
17
+ $?.exitstatus.zero?
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,218 @@
1
+ module Xcodeci
2
+ class Command
3
+ def self.root_output_folder
4
+ File.join(@@dropbox_folder, "xcodeci")
5
+ end
6
+
7
+ def self.workspace_folder repo_url, prj_workspace
8
+ folder_name = repo_url[repo_url.rindex('/') + 1..-1].sub('.git', '')
9
+ folder_name = File.join(Xcodeci::HOME, folder_name)
10
+ path = Dir.glob("#{folder_name}/**/#{prj_workspace}").first
11
+ File.dirname(path)
12
+ end
13
+
14
+ def self.execute_in_workspace_folder (repository_url, workspace ,&block)
15
+ Dir.chdir workspace_folder repository_url, workspace
16
+ result = block.call
17
+ Dir.chdir "#{Xcodeci::HOME}"
18
+ result
19
+ end
20
+
21
+ def self.repository_name repo_url
22
+ repo_url[repo_url.rindex('/') + 1..-1].sub('.git', '')
23
+ end
24
+
25
+ def self.execute_in_repo_folder (repository_url, &block)
26
+ Dir.chdir "#{Xcodeci::HOME}/#{repository_name repository_url}"
27
+ result = block.call
28
+ Dir.chdir "#{Xcodeci::HOME}"
29
+ result
30
+ end
31
+
32
+
33
+ def self.run
34
+ # Create the folder for storing all the projects
35
+ unless File.exists?(Xcodeci::HOME)
36
+ Dir.mkdir File.join(Xcodeci::HOME)
37
+ FileUtils.cp_r File.join(Xcodeci::TEMPLATE, "xcodeci.conf.yaml"), File.join(Xcodeci::HOME, "xcodeci.conf.yaml") , :verbose => false
38
+ puts "A sample configuration file was created on your ~/.xcodeci folder.".red
39
+ exit 0
40
+ end
41
+
42
+ configuration = Configuration.new ( File.join(Xcodeci::HOME, "xcodeci.conf.yaml") )
43
+ unless configuration.is_ok?
44
+ puts "Your configuration file is doesn't contain any project.".red
45
+ exit(1)
46
+ end
47
+
48
+ @@dropbox_folder = configuration.app_config[:DROPBOX_FOLDER]
49
+ @@dropbox_user_id = configuration.app_config[:DROPBOX_USER_ID]
50
+
51
+
52
+ database = Database.new ( File.join(Xcodeci::HOME, "data.yaml") )
53
+ gitUtils = GitUtils.new
54
+ buildUtils = BuildUtils.new
55
+ archiveUtils = ArchiveUtils.new
56
+ l = Logger.new
57
+
58
+
59
+
60
+ #TODO check if the configuration is ok
61
+ puts "=== Start Loop ==="
62
+
63
+ configuration.each_project do | project |
64
+ puts "=== Start #{project[:APP_NAME]} ==="
65
+
66
+ # Create the dropbox shared folder
67
+ output_folder = File.join(root_output_folder, project[:APP_NAME])
68
+ FileUtils.mkdir_p(output_folder) unless File.exists?(output_folder)
69
+
70
+ # Cloning the repository
71
+ puts "> Cloning the repository #{project[:REPO_URL]}"
72
+ clone_result = gitUtils.cloning_repo project[:REPO_URL]
73
+ puts l.log_result clone_result, "Repository clone"
74
+
75
+ # Checkout, fetch and pull
76
+ puts "> Updating the repository"
77
+
78
+ last_commits = []
79
+ execute_in_repo_folder(project[:REPO_URL]) {
80
+ #Fetch the new commits
81
+ fetch_result = gitUtils.fetch_repo
82
+ puts l.log_result fetch_result, "Repository fetch"
83
+
84
+ # Checkout the target branch
85
+ checkout_result = gitUtils.checkout_repo project[:TARGET_BRANCH]
86
+ puts l.log_result checkout_result, "Repository checkout"
87
+
88
+ # Pull the repository
89
+ pull_result = gitUtils.pull_repo
90
+ puts l.log_result pull_result, "Repository pull"
91
+
92
+ # Get list of the five most recent commits
93
+ puts "> Getting list of commits"
94
+ last_commits = gitUtils.get_commit_lists
95
+ puts l.log_result true, "Found #{last_commits.length} commit(s)"
96
+ }
97
+
98
+
99
+ # Iterate on each commit to build
100
+ last_commits.each do |commit_line|
101
+ commit, email = commit_line.split(' ')
102
+
103
+ puts "> Commit #{commit}"
104
+ unless database.should_build_commit( project[:APP_NAME], commit)
105
+ puts l.log_result false, "Commit #{commit} skipped"
106
+ next
107
+ end
108
+
109
+ # Checkout del repository al commit specifico
110
+ puts "> Rollaback repo to #{commit}"
111
+ execute_in_repo_folder(project[:REPO_URL]) {
112
+ rollback_result = gitUtils.rollback_repo commit
113
+ puts l.log_result rollback_result, "Rollback"
114
+ }
115
+
116
+
117
+ # Run "Pod install" on the repository
118
+ puts "> Updating dependencies"
119
+ execute_in_workspace_folder(project[:REPO_URL], project[:WORKSPACE] ) {
120
+ pod_result = buildUtils.install_pod
121
+ puts l.log_result pod_result, "Pod installed"
122
+ }
123
+
124
+
125
+
126
+
127
+ # BUILD
128
+ puts "> Build"
129
+ build_result = false
130
+ execute_in_workspace_folder(project[:REPO_URL], project[:WORKSPACE] ) {
131
+ build_result = buildUtils.run_build(project[:REPO_URL], project[:WORKSPACE], project[:SCHEME])
132
+ puts l.log_result build_result, "Build"
133
+ }
134
+
135
+
136
+ unless build_result
137
+ test_result = false
138
+ puts l.log_result false, "Unit tests skipped"
139
+
140
+ ipa_result = false
141
+ puts l.log_result false, "IPA build skipped"
142
+
143
+ dsym_result = false
144
+ puts l.log_result false, "dSym copy skipped"
145
+
146
+ else
147
+ # Create the folder for storing the ipa files and the dSym file.
148
+ output_folder = File.join(root_output_folder , project[:APP_NAME], commit)
149
+ FileUtils.mkdir_p(output_folder) unless File.exists?(output_folder)
150
+
151
+ # Run unit test
152
+ puts "> Test"
153
+ execute_in_workspace_folder(project[:REPO_URL], project[:WORKSPACE] ) {
154
+ test_result = buildUtils.run_test(project[:REPO_URL], project[:WORKSPACE], project[:SCHEME])
155
+ puts l.log_result test_result, "Unit Test"
156
+ }
157
+
158
+ # Create ipa file
159
+ puts "> Archiving"
160
+ execute_in_workspace_folder(project[:REPO_URL], project[:WORKSPACE] ) {
161
+ ipa_result = archiveUtils.archive_ipa(project[:WORKSPACE], project[:SCHEME], "#{output_folder}/app.ipa")
162
+ puts l.log_result ipa_result, "Create Ipa"
163
+ }
164
+
165
+
166
+ # Copy dSym frile from the "derivedata" folder.
167
+ execute_in_workspace_folder(project[:REPO_URL], project[:WORKSPACE] ) {
168
+ dsym_result = archiveUtils.save_dsym(project[:WORKSPACE], project[:SCHEME], "#{output_folder}/")
169
+ puts l.log_result dsym_result, "Copy dSym"
170
+ }
171
+
172
+
173
+ # Extract info from the ipa file
174
+ ipa_file = IpaReader::IpaFile.new("#{output_folder}/app.ipa")
175
+ bundle_identifier = ipa_file.bundle_identifier
176
+ bundle_version = ipa_file.version
177
+
178
+ # Copy the manifest
179
+ manifest_template_path = File.join(Xcodeci::TEMPLATE, 'manifest.plist')
180
+ manifest_template = File.open(manifest_template_path, 'rb') { |f| f.read }
181
+ link = URI::encode("https://dl.dropboxusercontent.com/u/#{@@dropbox_user_id}/xcodeci/#{project[:APP_NAME]}/#{commit}/app.ipa")
182
+ manifest_template.gsub!('__IPA_URL_PLACEHOLDER__', link)
183
+ manifest_template.gsub!('__BUNDLE_IDENTIFIER_PLACEHOLDER__', bundle_identifier)
184
+ manifest_template.gsub!('__VERSION_PLACEHOLDER__', bundle_version)
185
+ manifest_template.gsub!("__APP_NAME_PLACEHOLDER__", project[:APP_NAME])
186
+
187
+
188
+ output = File.join(root_output_folder, project[:APP_NAME],commit,'manifest.plist')
189
+ File.open(output, 'w') { |file| file.write(manifest_template) }
190
+
191
+ end
192
+ commit_report = {
193
+ build: build_result,
194
+ test: test_result,
195
+ ipa: ipa_result,
196
+ dSym: dsym_result,
197
+ author: email,
198
+ date: Time.now
199
+ }
200
+
201
+ database.store_result project[:APP_NAME], commit, commit_report
202
+
203
+ end # <- end loop commit
204
+ puts "=== End #{project[:APP_NAME]} ==="
205
+ end # <- End single project
206
+ puts "=== End Loop ==="
207
+ if (File.exists?(File.join(Xcodeci::HOME, "data.yaml")))
208
+ # Create html report
209
+ reporter = HtmlReporter.new File.join(Xcodeci::HOME, "data.yaml") , @@dropbox_user_id
210
+ output = File.join(root_output_folder, 'index.html')
211
+ File.open(output, 'w') { |file| file.write(reporter.html_report) }
212
+ puts "You report it's available at: https://dl.dropboxusercontent.com/u/#{@@dropbox_user_id}/xcodeci/index.html"
213
+ %x(open -a Safari #{output})
214
+ end
215
+
216
+ end # <- end run method
217
+ end # <- end class
218
+ end # <- end module
@@ -0,0 +1,21 @@
1
+ module Xcodeci
2
+ class Configuration
3
+ def initialize path
4
+ @database = YAML.load_file(path)
5
+ end
6
+
7
+ def each_project
8
+ @database.each {|key, value|
9
+ yield(value) unless key == "App_Config"
10
+ }
11
+ end
12
+ def is_ok?
13
+ not @database.nil? and not @database.empty?
14
+ end
15
+
16
+ def app_config
17
+ @database['App_Config']
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ class Database
2
+ require "yaml/store"
3
+
4
+ def initialize (file_path = "data.yml")
5
+ @store = YAML::Store.new(file_path)
6
+ end
7
+
8
+ def should_build_commit appname, commit_hash
9
+ report = @store.transaction {
10
+ commits = @store[appname]
11
+ if commits.nil?
12
+ nil
13
+ else
14
+ @store[appname][commit_hash]
15
+ end
16
+ }
17
+ report.nil?
18
+ end
19
+
20
+ def store_result appname, commit_hash, result
21
+ @store.transaction do
22
+ @store[appname] ||= Hash.new
23
+ @store[appname][commit_hash] = result
24
+ @store.commit
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,48 @@
1
+ module Xcodeci
2
+ class GitUtils
3
+
4
+ def repository_name repo_url
5
+ repo_url[repo_url.rindex('/') + 1..-1].sub('.git', '')
6
+ end
7
+ # def execute_in_repo_folder (repository_url, &block)
8
+ # Dir.chdir "#{Xcodeci::HOME}/#{repository_name repository_url}"
9
+ # result = block.call
10
+ # Dir.chdir "#{Xcodeci::HOME}"
11
+ # result
12
+ # end
13
+
14
+ def cloning_repo repository_url
15
+ #Cloning the repository, it fails if the repository is already cloned
16
+ dest_folder = File.join(Xcodeci::HOME, (repository_name repository_url))
17
+ clone_result = %x(git clone #{repository_url} #{dest_folder} 2>&1)
18
+ $?.exitstatus.zero?
19
+ end
20
+
21
+ def fetch_repo
22
+ %x(git fetch 2>&1)
23
+ $?.exitstatus.zero?
24
+ end
25
+
26
+ def checkout_repo branch
27
+ %x(git checkout #{branch} 2>&1)
28
+ $?.exitstatus.zero?
29
+ end
30
+
31
+ def pull_repo
32
+ %x(git pull 2>&1)
33
+ $?.exitstatus.zero?
34
+ end
35
+
36
+ def get_commit_lists
37
+ last_commit = %x(git log -10 --date-order --pretty=format:"%h %ce").split(/\n/)
38
+ # a single line shoud be in this format "e2d4a86 ignaziocgmail.com"
39
+ last_commit
40
+ end
41
+
42
+ def rollback_repo commit
43
+ %x(git checkout "#{commit}" 2>&1)
44
+ $?.exitstatus.zero?
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,78 @@
1
+ module Xcodeci
2
+ class HtmlReporter
3
+ def initialize db_path, drobox_userid
4
+ @db_path = db_path
5
+ @user_id = drobox_userid
6
+ end
7
+
8
+ def table_cell_for_boolean value
9
+ klass = value ? 'positive' : 'negative'
10
+ text = value ? "✔︎" : "✘"
11
+ "<td class=\"#{klass}\";>#{text}</td>"
12
+ end
13
+
14
+ def manifest_link appname, commit
15
+ "https://dl.dropboxusercontent.com/u/#{@user_id}/xcodeci/#{appname}/#{commit}/manifest.plist"
16
+ end
17
+ def ipa_link appname, commit
18
+ "https://dl.dropboxusercontent.com/u/#{@user_id}/xcodeci/#{appname}/#{commit}/app.ipa"
19
+ end
20
+ def itunes_link appname, commit
21
+ "itms-services://?action=download-manifest&url=#{manifest_link appname, commit}"
22
+ end
23
+
24
+ def html_report
25
+ database = YAML.load_file(@db_path)
26
+ html_row = ""
27
+
28
+ database.each do |project, commits|
29
+ html_row << %[
30
+ <h1>#{project}</h1>
31
+ <table id="newspaper-a">
32
+ <thead>
33
+ <tr>
34
+ <th scope="col">Date</th>
35
+ <th scope="col">Commit</th>
36
+ <th scope="col">Email</th>
37
+ <th scope="col">Build</th>
38
+ <th scope="col">Test</th>
39
+ <th scope="col">IPA</th>
40
+ <th scope="col">dSym</th>
41
+ <th scople="col">Manifest</th>
42
+ <th scople="col">IPA</th>
43
+ <th scople="col">Install</th>
44
+ </tr>
45
+ </thead>
46
+ <tbody>
47
+ ]
48
+ sorted_commits = commits.keys.sort {|c1, c2|
49
+ commits[c1][:date] <=> commits[c2][:date]
50
+ }
51
+ sorted_commits.each do | commit |
52
+ report = commits[commit]
53
+ html_row << "<tr><td width=\"50px\">#{report[:date].strftime('%D')}</td>" \
54
+ "<td width=\"50px\">#{commit}</td>" \
55
+ "<td>#{report[:author]}</td>" \
56
+ "#{table_cell_for_boolean report[:build]}" \
57
+ "#{table_cell_for_boolean report[:test]}" \
58
+ "#{table_cell_for_boolean report[:ipa]}" \
59
+ "#{table_cell_for_boolean report[:dSym]}" \
60
+ "<td><a href=\"#{URI::encode(manifest_link project, commit)}\">Manifest</a></td>" \
61
+ "<td><a href=\"#{URI::encode(ipa_link project, commit)}\">IPA</a></td>" \
62
+ "<td><a href=\"#{URI::encode(itunes_link project, commit)}\">Install</a></td>" \
63
+ "</tr>"
64
+ end #end loop commits
65
+ html_row << "
66
+ </tbody>
67
+ </table>
68
+ "
69
+ end # <- End loo projects
70
+
71
+ html_template_path = File.join(Xcodeci::TEMPLATE, 'index.html')
72
+ html_template = File.open(html_template_path, 'rb') { |f| f.read }
73
+ html_template.gsub!('<!-- __TABLE_PLACEHOLDER__ -->', html_row)
74
+ html_template
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,12 @@
1
+ class Logger
2
+ def ok
3
+ "✔︎".green
4
+ end
5
+ def ko
6
+ "✘".red
7
+ end
8
+
9
+ def log_result(status, description)
10
+ status ? " [ #{ok} ] #{description}" : " [ #{ko} ] #{description}"
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Xcodeci
2
+ VERSION = "0.0.1"
3
+ end
data/readme.md ADDED
@@ -0,0 +1,95 @@
1
+ xcodeci
2
+ =======
3
+
4
+ Ruby script for build/test/archive/distribuite iOS application.
5
+
6
+ This script can be uses to create a simple continuous integration service for xcode.
7
+
8
+ The project is still in development, **any contribution will be appreciate.**
9
+
10
+ Basic info
11
+ ==========
12
+
13
+ The script runs in a loop and every five minutes it:
14
+
15
+ 1. Reads the configuration file on `~/.xcodeci/xcodeci.conf.yaml` and iterates on each listed project
16
+ 1. Clones the repository inside the folder `~/.xcodeci/[app_name]`
17
+ 2. Executes `git fetch`, `git pull` and `git checkout` on the specified branch
18
+ 3. For each commit on that branch it:
19
+ 1. Builds the application (only if it's necessarely, otherwise skip to the next commit)
20
+ 2. Runs the unit test
21
+ 3. Save the dSym file on the output folder[^1]
22
+ 4. Generates the ipa file and the manifest.plist on the output folder
23
+ 2. Generates a web page for the OTA distribution
24
+
25
+ [^1]: The current output folder is the dropbox folder specified on the configuration file.
26
+
27
+ How it's looks like
28
+ ===================
29
+ This is the output on console
30
+
31
+ ![console output](https://dl.dropboxusercontent.com/u/792862/Screenshot%202014-10-03%2011.45.47.png)
32
+
33
+ And this is the html report
34
+ ![web report](https://dl.dropboxusercontent.com/u/792862/Screenshot%202014-10-03%2011.47.41.png)
35
+
36
+ Basic usage
37
+ ===========
38
+
39
+ 1. Clone the script:
40
+
41
+ `git clone https://github.com/ignazioc/xcodeci`
42
+
43
+ 2. Install the required gems
44
+
45
+ `bundle install
46
+ `
47
+ 3. Install **xctool**
48
+
49
+ `brew install xctool`
50
+
51
+ for more info about brew command see the [brew website](http://brew.sh)
52
+
53
+ 4. Run the script
54
+
55
+ `./bin/xcodeci`
56
+
57
+ The first time you will run the script a sample configuration file will be created on `~/.xcodeci/xcodeci.conf.yaml`
58
+
59
+ 5. Edit your configuration file (see info below)
60
+
61
+ 6. Run the script again and wait :)
62
+
63
+
64
+ The configuration file
65
+ ======================
66
+
67
+ This is an example of the configuration file
68
+
69
+ ---
70
+ App_Config:
71
+ #The public dropbox folder on your machine
72
+ :DROPBOX_FOLDER: '/Users/username/Dropbox/Public/'
73
+
74
+ #This is your dropbox userid, you can find it when you share a file stored on the
75
+ #public folder like this one https://dl.dropboxusercontent.com/u/792862/avatar_grumpy.png
76
+ #In this example the user id is 792862
77
+ #The user id is used to crete the links on the html report for install the application.
78
+
79
+ :DROPBOX_USER_ID: '792862'
80
+ SampleApp1:
81
+ :REPO_URL: 'git@github.com:ignazioc/sample_repo_1.git'
82
+ :APP_NAME: 'SampleApp-1'
83
+ :TARGET_BRANCH: 'master'
84
+ :WORKSPACE: 'SampleApp 1.xcworkspace'
85
+ :SCHEME: 'SampleApp 1'
86
+ SampleApp2:
87
+ :REPO_URL: '.....'
88
+ :APP_NAME: '.....'
89
+ :TARGET_BRANCH: '.....'
90
+ :WORKSPACE: '.....'
91
+ :SCHEME: '.....'
92
+
93
+
94
+
95
+
@@ -0,0 +1,54 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <title>Report</title>
6
+ <style type="text/css">
7
+ body
8
+ {
9
+ line-height: 1.6em;
10
+ }
11
+
12
+ #newspaper-a
13
+ {
14
+ font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
15
+ font-size: 14px;
16
+ margin: 0px;
17
+ width: 100%;
18
+ text-align: left;
19
+ border-collapse: collapse;
20
+ border: 1px solid #69c;
21
+ }
22
+ #newspaper-a th
23
+ {
24
+ padding: 12px 17px 12px 17px;
25
+ font-weight: normal;
26
+ font-size: 14px;
27
+ color: #039;
28
+ border-bottom: 1px dashed #69c;
29
+ }
30
+ #newspaper-a td
31
+ {
32
+ padding: 7px 17px 7px 17px;
33
+ font-weight: bold;
34
+ /*color: #fff;*/
35
+ }
36
+ #newspaper-a td.positive {
37
+ color: #519548;
38
+ font-size: 18px;
39
+ }
40
+ #newspaper-a td.negative {
41
+ color: #DC0139;
42
+ font-size: 18px;
43
+ }
44
+ #newspaper-a tbody tr:hover td
45
+ {
46
+ /*color: #339;*/
47
+ /*background: #d0dafd;*/
48
+ }
49
+ </style>
50
+ </head>
51
+ <body>
52
+ <!-- __TABLE_PLACEHOLDER__ -->
53
+ </body>
54
+ </html>
@@ -0,0 +1,31 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>items</key>
6
+ <array>
7
+ <dict>
8
+ <key>assets</key>
9
+ <array>
10
+ <dict>
11
+ <key>kind</key>
12
+ <string>software-package</string>
13
+ <key>url</key>
14
+ <string>__IPA_URL_PLACEHOLDER__</string>
15
+ </dict>
16
+ </array>
17
+ <key>metadata</key>
18
+ <dict>
19
+ <key>bundle-identifier</key>
20
+ <string>__BUNDLE_IDENTIFIER_PLACEHOLDER__</string>
21
+ <key>bundle-version</key>
22
+ <string>__VERSION_PLACEHOLDER__</string>
23
+ <key>kind</key>
24
+ <string>software</string>
25
+ <key>title</key>
26
+ <string>__APP_NAME_PLACEHOLDER__</string>
27
+ </dict>
28
+ </dict>
29
+ </array>
30
+ </dict>
31
+ </plist>
@@ -0,0 +1,24 @@
1
+ ---
2
+ App_Config:
3
+ #The public dropbox folder on your machine
4
+ :DROPBOX_FOLDER: '/Users/username/Dropbox/Public/'
5
+
6
+ #You can find it when you share a file stored on the
7
+ #public folder like this one https://dl.dropboxusercontent.com/u/792862/avatar_grumpy.png
8
+ #In this example the user id is 792862
9
+ #The user id is used to crete the links on the html report for install the application.
10
+ :DROPBOX_USER_ID: '792862'
11
+
12
+
13
+ # SampleApp1: # <- This is just a mnemonic name for the project, don't use spaces.
14
+ # :REPO_URL: 'git@github.com:ignazioc/sample_repo_1.git' # <- This it the git repository
15
+ # :APP_NAME: 'SampleApp-1' # <- This is used as a name for the output forlder, don't use spaces
16
+ # :TARGET_BRANCH: 'master' # <- The branch that you want to build
17
+ # :WORKSPACE: 'SampleApp 1.xcworkspace' # <- The name of the workspace
18
+ # :SCHEME: 'SampleApp 1' # <- the name of the scheme that you want to build / test
19
+ # SampleApp2: # <- Add as many projects as you want
20
+ # :REPO_URL: 'git@github.com:ignazioc/sample_repo_2.git'
21
+ # :APP_NAME: 'SampleApp-2'
22
+ # :TARGET_BRANCH: 'master'
23
+ # :WORKSPACE: 'SampleApp 1.xcworkspace'
24
+ # :SCHEME: 'SampleApp 1'
data/xcodeci.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'xcodeci/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "XcodeCI"
8
+ spec.version = Xcodeci::VERSION
9
+ spec.authors = ["Ignazio Calò"]
10
+ spec.email = ["ignazioc@gmail.com"]
11
+ spec.summary = %q{This gem it's only a test.}
12
+ spec.description = %q{This gem it's only a test, please don't use it.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rspec"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_runtime_dependency "colorize"
25
+ spec.add_runtime_dependency "cocoapods"
26
+ spec.add_runtime_dependency "ipa_reader"
27
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: XcodeCI
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ignazio Calò
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: colorize
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: cocoapods
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: ipa_reader
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: This gem it's only a test, please don't use it.
98
+ email:
99
+ - ignazioc@gmail.com
100
+ executables:
101
+ - xcodeci
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - .gitignore
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - bin/xcodeci
109
+ - lib/xcodeci.rb
110
+ - lib/xcodeci/archiveutils.rb
111
+ - lib/xcodeci/buildutils.rb
112
+ - lib/xcodeci/command.rb
113
+ - lib/xcodeci/configuration.rb
114
+ - lib/xcodeci/database.rb
115
+ - lib/xcodeci/gitutils.rb
116
+ - lib/xcodeci/html_reporter.rb
117
+ - lib/xcodeci/logger.rb
118
+ - lib/xcodeci/version.rb
119
+ - readme.md
120
+ - templates/index.html
121
+ - templates/manifest.plist
122
+ - templates/xcodeci.conf.yaml
123
+ - xcodeci.gemspec
124
+ homepage: ''
125
+ licenses:
126
+ - MIT
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 2.0.14
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: This gem it's only a test.
148
+ test_files: []