Last active
August 29, 2015 13:56
-
-
Save kishikawakatsumi/8918124 to your computer and use it in GitHub Desktop.
Rakefile for testing, building and uploading to Testflight/Crittercism
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "rubygems/version" | |
require "rake/clean" | |
require "date" | |
require 'time' | |
require "json" | |
require "open3" | |
# Application info | |
APP_NAME = "Ubiregi2" | |
SDK = "iphoneos" | |
WORKSPACE = File.expand_path("#{APP_NAME}.xcworkspace") | |
SCHEME = "Ubiregi2-Release" | |
INFO_PLIST = File.expand_path("#{APP_NAME}/#{APP_NAME}-Info.plist") | |
# Code signing | |
CODE_SIGN_IDENTITY_APPSTORE = "iPhone Distribution: XXXXX Inc. (XXXXXXXXXX)"; | |
CODE_SIGN_IDENTITY_INHOUSE = "iPhone Distribution: XXXXX K.K."; | |
PROFILE_APPSTORE = "Ubiregi_App_Store.mobileprovision" | |
PROFILE_ADHOC = "Ubiregi_Ad_Hoc.mobileprovision" | |
PROFILE_INHOUSE = "Ubiregi_Beta.mobileprovision" | |
PROFILE_NAME_ADHOC = "Ubiregi Ad Hoc" | |
PROFILE_NAME_INHOUSE = "Ubiregi Beta" | |
PROFILE_DIR = "$HOME/Library/MobileDevice/Provisioning Profiles" | |
KEYCHAIN_NAME = "ios-build.keychain" | |
# Build paths | |
BUILD_DIR = File.expand_path("build") | |
APP_FILE = File.expand_path("#{BUILD_DIR}/#{APP_NAME}.app") | |
ARCHIVE_FILE = File.expand_path("#{BUILD_DIR}/#{APP_NAME}.xcarchive") | |
ARCHIVE_ZIP_FILE = File.expand_path("#{BUILD_DIR}/#{APP_NAME}.xcarchive.zip") | |
IPA_FILE = File.expand_path("#{BUILD_DIR}/#{APP_NAME}.ipa") | |
DSYM_FILE = File.expand_path("#{BUILD_DIR}/#{APP_NAME}.app.dSYM") | |
DSYM_ZIP_FILE = File.expand_path("#{BUILD_DIR}/#{APP_NAME}.app.dSYM.zip") | |
# Test info | |
DESTINATIONS = [ | |
"name=iPad 2,OS=7.0.3", | |
"name=iPad 2,OS=7.1", | |
"name=iPad 2,OS=8.0", | |
"name=iPad Air,OS=7.0.3", | |
"name=iPad Air,OS=7.1", | |
"name=iPad Air,OS=8.0", | |
"name=iPad Retina,OS=7.0.3", | |
"name=iPad Retina,OS=7.1", | |
"name=iPad Retina,OS=8.0", | |
"name=Resizable iPad,OS=8.0", | |
] | |
# Crittercism info | |
APP_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | |
API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | |
# TestFlight info | |
API_TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | |
TEAM_TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | |
CLEAN.include(BUILD_DIR) | |
CLOBBER.include(BUILD_DIR) | |
task :default => [:build] | |
desc "Setting up a development environment" | |
task :setup do | |
sh "bundle install" | |
sh "pod install" | |
end | |
desc "Test application" | |
task test: ["test:release"] | |
namespace :test do | |
task :release do | |
test(configuration: "Release") | |
end | |
task :debug do | |
test(configuration: "Debug") | |
end | |
task :adhoc do | |
test(configuration: "AdHoc") | |
end | |
end | |
def test(configuration: "Release") | |
options = build_options(sdk: "iphonesimulator", configuration: configuration) | |
# options << DESTINATIONS.map { |destination| %[-destination "#{destination}"] }.join(" ") | |
sh %[xcodebuild #{options} GCC_SYMBOLS_PRIVATE_EXTERN="NO" test | xcpretty -c; exit ${PIPESTATUS[0]}] | |
end | |
desc "Build application (Release)" | |
task build: ["build:release"] | |
namespace :build do | |
desc "Build application (Release)" | |
task :release do | |
build | |
end | |
desc "Build application (AdHoc)" | |
task :adhoc do | |
build(configuration: "AdHoc") | |
end | |
end | |
def build(configuration: "Release") | |
options = build_options(configuration: configuration) | |
settings = build_settings(configuration: configuration) | |
sh %[xcodebuild #{options} #{settings} build | xcpretty -c; exit ${PIPESTATUS[0]}] | |
end | |
desc "Build artifacts (.xcarchive, ipa) (Release)" | |
task archive: ["archive:release"] | |
namespace :archive do | |
desc "Build artifacts (.xcarchive, ipa) (Release)" | |
task :release do | |
archive | |
end | |
desc "Build artifacts (.xcarchive, ipa) (AdHoc)" | |
task :adhoc do | |
archive(configuration: "AdHoc") | |
end | |
end | |
def archive(configuration: "Release") | |
build_xcarchive(configuration: configuration) | |
clean_ipa | |
export_ipa(configuration: configuration) | |
zip_artifacts | |
if configuration == "Release" | |
upload_artifacts | |
end | |
end | |
def build_xcarchive(configuration: "Release") | |
options = build_options(configuration: configuration) | |
settings = build_settings(configuration: configuration) | |
sh %[xcodebuild #{options} #{settings} archive -archivePath #{ARCHIVE_FILE} | xcpretty -c; exit ${PIPESTATUS[0]}] | |
end | |
def clean_ipa | |
system "rm -f #{IPA_FILE}" | |
end | |
def export_ipa(configuration: "Release") | |
sh %[xcodebuild -exportArchive #{export_options(configuration: configuration)} | xcpretty -c; exit ${PIPESTATUS[0]}] | |
end | |
def export_options(configuration: "Release") | |
options = { | |
exportFormat: "IPA", | |
archivePath: ARCHIVE_FILE, | |
exportPath: IPA_FILE, | |
exportProvisioningProfile: configuration == "Release" ? PROFILE_NAME_ADHOC : PROFILE_NAME_INHOUSE, | |
} | |
join_options(options: options, prefix: "-", separator: " ") | |
end | |
def resign_ipa | |
sh %[unzip -o #{IPA_FILE} -d build 1>/dev/null] | |
sh %[rm -rf #{BUILD_DIR}/Payload/#{APP_NAME}.app/_CodeSignature/] | |
sh %[cp #{PROFILE_ADHOC} #{BUILD_DIR}/Payload/#{APP_NAME}.app/embedded.mobileprovision] | |
sh %[codesign --verify --force --sign "#{CODE_SIGN_IDENTITY_APPSTORE}" --resource-rules #{BUILD_DIR}/Payload/#{APP_NAME}.app/ResourceRules.plist #{BUILD_DIR}/Payload/#{APP_NAME}.app] | |
sh %[(cd #{BUILD_DIR}; zip -ryq #{APP_NAME}.ipa Payload)] | |
end | |
def zip_artifacts | |
sh %[(cd #{BUILD_DIR}; zip -ryq #{APP_NAME}.app.dSYM.zip #{APP_NAME}.app.dSYM)] | |
sh %[mv #{DSYM_FILE} #{BUILD_DIR}/#{APP_NAME}.xcarchive/dSYMs/#{APP_NAME}.app.dSYM] | |
sh %[(cd #{BUILD_DIR}; zip -ryq #{APP_NAME}.xcarchive.zip #{APP_NAME}.xcarchive)] | |
end | |
def upload_artifacts | |
owner = "ubiregiinc" | |
repo = "client-releases" | |
version = "#{InfoPlist.marketing_version}-#{InfoPlist.build_version}" | |
tag_name = "v#{version}_#{Time.now.utc.iso8601.gsub(':', '-')}" | |
branch = "master" | |
access_token = ENV["GITHUB_ACCESS_TOKEN"] | |
upload_date = DateTime.now.strftime("%Y/%m/%d %H:%M:%S") | |
build_version = InfoPlist.marketing_and_build_version | |
changelog = "Build: #{build_version}\nUploaded: #{upload_date}\n" | |
changelog << %x[git log --date=short --pretty=format:"- [%h](http://github.com/#{owner}/ubiregi-client/commit/%H) - %s _%cd_" --no-merges $(git describe --abbrev=0 --tags)..] | |
release_data = {tag_name: tag_name, target_commitish: branch, name: tag_name, body: changelog, draft: false, prerelease: false}.to_json | |
create_release_url = %[https://api.github.com/repos/#{owner}/#{repo}/releases?access_token=#{access_token}] | |
curl_command = ["curl", "-sSf", "-d", "#{release_data}", "#{create_release_url}"] | |
out, status = Open3.capture2(*(curl_command)) | |
release_id = JSON.parse(out)["id"] | |
upload_url = %[https://uploads.github.com/repos/#{owner}/#{repo}/releases/#{release_id}/assets?name=#{APP_NAME}-#{version}.xcarchive.zip] | |
sh %[curl -sSf -w "%{http_code} %{url_effective}\\n" -o /dev/null -X POST #{upload_url} -H "Accept: application/vnd.github.v3+json" -H "Authorization: token #{access_token}" -H "Content-Type: application/zip" --data-binary @"#{ARCHIVE_ZIP_FILE}"] | |
end | |
def build_options(configuration: "Release", **others) | |
options = { | |
sdk: SDK, | |
workspace: WORKSPACE, | |
scheme: SCHEME, | |
configuration: configuration, | |
}.merge others | |
join_options(options: options, prefix: "-", separator: " ") | |
end | |
def build_settings(configuration: "Release") | |
code_sign_identity = configuration == "Release" ? CODE_SIGN_IDENTITY_APPSTORE : CODE_SIGN_IDENTITY_INHOUSE | |
settings = { | |
CONFIGURATION_BUILD_DIR: BUILD_DIR, | |
CONFIGURATION_TEMP_DIR: "#{BUILD_DIR}/temp", | |
CODE_SIGN_IDENTITY: code_sign_identity, | |
} | |
settings = join_options(options: settings, prefix: "", separator: "=") | |
end | |
desc "Upload IPA file to TestFlight (Release)" | |
task distribute: ["distribute:release"] | |
namespace :distribute do | |
desc "Upload IPA file to TestFlight (Release)" | |
task :release => ["archive:release"] do | |
distribute | |
end | |
desc "Upload IPA file to TestFlight (AdHoc)" | |
task :adhoc => ["version:update_build_version", "archive:adhoc"] do | |
distribute | |
end | |
end | |
def distribute | |
crittercism | |
testflight | |
end | |
def crittercism | |
options = { | |
dsym: "@#{DSYM_ZIP_FILE}", | |
key: API_KEY, | |
} | |
upload_form("https://api.crittercism.com/api_beta/dsym/#{APP_ID}", options) | |
end | |
def testflight | |
pr_number = ENV["TRAVIS_PULL_REQUEST"] | |
branch_name = ENV["TRAVIS_BRANCH"] | |
release_date = DateTime.now.strftime("%Y/%m/%d %H:%M:%S") | |
build_version = InfoPlist.marketing_and_build_version | |
repo_url = "https://github.com/ubiregiinc/ubiregi-client" | |
release_notes = "Build: #{build_version}\nUploaded: #{release_date}\n" | |
if pull_request? | |
release_notes << "Branch: #{repo_url}/commits/#{branch_name}\n" | |
release_notes << "Pull Request: #{repo_url}/pull/#{pr_number}\n" | |
release_notes << %x[git log --date=short --pretty=format:"* %h - %s (%cd) <%an>" --no-merges #{branch_name}..] | |
else | |
commit_hash = %x[git rev-parse HEAD].strip | |
release_notes << "Branch: #{repo_url}/commits/#{branch_name}\n" | |
if branch_name == "master" | |
release_notes << %x[git log --date=short --pretty=format:"* %h - %s (%cd) <%an>" --no-merges $(git describe --abbrev=0 --tags)..] | |
else | |
release_notes << %x[git log --date=short --pretty=format:"* %h - %s (%cd) <%an>" --no-merges] | |
end | |
end | |
options = { | |
file: "@#{IPA_FILE}", | |
api_token: API_TOKEN, | |
team_token: TEAM_TOKEN, | |
notify: true, | |
replace: true, | |
distribution_lists: pull_request? ? "Dev" : "Internal", | |
notes: release_notes, | |
} | |
upload_form("http://testflightapp.com/api/builds.json", options) | |
end | |
def upload_form(url, options = {}) | |
curl_options = ["curl", "-sSf", "-w", "%{http_code} %{url_effective}\\n", "#{url}"] | |
form_fields = options.flat_map { |k, v| ["-F", "#{k}=#{v}"] } | |
output_options = ["-o", "/dev/null"] | |
sh *(curl_options + form_fields + output_options) | |
end | |
namespace :profile do | |
desc "Download provisioning profiles from Apple Developer Center" | |
task :download do | |
profile_download("[email protected]", "xxxxxxxxxxxxxxxxxx") | |
end | |
task :install do | |
profile_install | |
end | |
end | |
def profile_download(user, passowrd) | |
system "ios profiles:download:all -u #{user} -p #{password} --type distribution" | |
end | |
def profile_install | |
sh %[mkdir -p "#{PROFILE_DIR}"] | |
sh %[cp "#{PROFILE_INHOUSE}" "#{PROFILE_DIR}"] | |
sh %[cp "#{PROFILE_ADHOC}" "#{PROFILE_DIR}"] | |
end | |
namespace :certificate do | |
task :add do | |
add_certificates | |
end | |
task :remove do | |
remove_certificates | |
end | |
end | |
def add_certificates | |
passphrase = "xxxxxxxx" | |
sh "security create-keychain -p travis #{KEYCHAIN_NAME}" | |
sh "security import ./certificates/apple.cer -k #{KEYCHAIN_NAME} -T /usr/bin/codesign" | |
sh "security import ./certificates/appstore.cer -k #{KEYCHAIN_NAME} -T /usr/bin/codesign" | |
sh "security import ./certificates/appstore.p12 -k #{KEYCHAIN_NAME} -P #{passphrase} -T /usr/bin/codesign" | |
sh "security import ./certificates/inhouse.cer -k #{KEYCHAIN_NAME} -T /usr/bin/codesign" | |
sh "security import ./certificates/inhouse.p12 -k #{KEYCHAIN_NAME} -P #{passphrase} -T /usr/bin/codesign" | |
sh "security default-keychain -s #{KEYCHAIN_NAME}" | |
end | |
def remove_certificates | |
sh "security delete-keychain #{KEYCHAIN_NAME}" | |
end | |
def pull_request? | |
pr_number = ENV["TRAVIS_PULL_REQUEST"] | |
pr_number != nil && !pr_number.empty? && pr_number != "false" | |
end | |
def join_options(options: {}, prefix: "", separator: "") | |
options.map { |k, v| %(#{prefix}#{k}#{separator}"#{v}") }.join(" ") | |
end | |
namespace :version do | |
module InfoPlist | |
extend self | |
def [](key) | |
output = %x[/usr/libexec/PlistBuddy -c "Print #{key}" #{INFO_PLIST}].strip | |
raise "The key `#{key}' does not exist in `#{INFO_PLIST}'." if output.include?('Does Not Exist') | |
output | |
end | |
def set(key, value, file = "#{INFO_PLIST}") | |
%x[/usr/libexec/PlistBuddy -c 'Set :#{key} "#{value}"' '#{file}'].strip | |
end | |
def []=(key, value) | |
set(key, value) | |
end | |
def build_version | |
self['CFBundleVersion'] | |
end | |
def build_version=(revision) | |
self['CFBundleVersion'] = revision | |
end | |
def marketing_version | |
self['CFBundleShortVersionString'] | |
end | |
def marketing_version=(version) | |
self['CFBundleShortVersionString'] = version | |
end | |
def bump_marketing_version_segment(segment_index) | |
segments = Gem::Version.new(marketing_version).segments | |
segments[segment_index] = segments[segment_index].to_i + 1 | |
(segment_index+1..segments.size-1).each { |i| segments[i] = 0 } | |
version = segments.map(&:to_i).join('.') | |
puts "Setting marketing version to: #{version}" | |
self.marketing_version = version | |
system("git commit #{INFO_PLIST} -m 'Bump to #{marketing_and_build_version}'") | |
end | |
def marketing_and_build_version | |
"#{marketing_version} (#{build_version})" | |
end | |
def update_build_number | |
build_number = %x[git rev-list HEAD | wc -l | tr -d " "].strip.to_i | |
self.build_version = (build_number+1).to_s | |
end | |
def update_build_version | |
rev = "" | |
pr_number = ENV["TRAVIS_PULL_REQUEST"] | |
if pull_request? | |
rev << %x[git rev-parse --short HEAD^2].strip | |
rev << " ##{pr_number}" | |
else | |
rev << %x[git rev-parse --short HEAD].strip | |
end | |
puts "Setting build version to: #{rev}" | |
InfoPlist.build_version = rev | |
end | |
end | |
desc "Print the current version" | |
task :current do | |
puts InfoPlist.marketing_and_build_version | |
end | |
desc "Sets build version to last git commit (happens on each build)" | |
task :update_build_version do | |
InfoPlist.update_build_version | |
end | |
desc "Sets build number to last git commit count (happens on each build)" | |
task :update_build_number do | |
InfoPlist.update_build_number | |
end | |
namespace :bump do | |
desc "Bump patch version (0.0.X)" | |
task :patch do | |
InfoPlist.update_build_number | |
InfoPlist.bump_marketing_version_segment(2) | |
end | |
desc "Bump minor version (0.X.0)" | |
task :minor do | |
InfoPlist.update_build_number | |
InfoPlist.bump_marketing_version_segment(1) | |
end | |
desc "Bump major version (X.0.0)" | |
task :major do | |
InfoPlist.update_build_number | |
InfoPlist.bump_marketing_version_segment(0) | |
end | |
end | |
end | |
desc "Print the current version" | |
task :version => 'version:current' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment