Skip to content

Instantly share code, notes, and snippets.

@kishikawakatsumi
Last active August 29, 2015 13:56
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save kishikawakatsumi/8918124 to your computer and use it in GitHub Desktop.
Save kishikawakatsumi/8918124 to your computer and use it in GitHub Desktop.
Rakefile for testing, building and uploading to Testflight/Crittercism
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("xxxxxxx@example.com", "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