Skip to content

Instantly share code, notes, and snippets.

@dblandin
Forked from ParkinT/Rakefile
Last active June 17, 2016 15:30
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dblandin/9650678 to your computer and use it in GitHub Desktop.
Save dblandin/9650678 to your computer and use it in GitHub Desktop.
RubyMotion Configuration and Deploy/Release
class AppProperties
COMPANY = 'name'
NAME = 'name'
VERSION = '1.0.0'
def company
COMPANY
end
def name
NAME
end
def version
VERSION
end
def seed_id
ENV['APPLE_SEED_ID']
end
def dev_version
[version, short_git_hash].join('.')
end
def short_git_hash
`git log --pretty=format:'%h' -n 1`
end
def frameworks
%w[Accelerate AVFoundation CFNetwork CoreData CoreGraphics
CoreImage CoreMedia CoreVideo CoreText CoreTelephony
MobileCoreServices AdSupport Foundation MessageUI
OpenGLES QuartzCore Security StoreKit SystemConfiguration
UIKit AssetsLibrary]
end
def libraries
%w[/usr/lib/libsqlite3.0.dylib /usr/lib/libz.1.2.5.dylib
/usr/lib/libxml2.dylib /usr/lib/libicucore.dylib]
end
def dev_certificate
ENV['DEVELOPMENT_CERTIFICATE_NAME']
end
def dev_profile_path
ENV['DEVELOPMENT_PROVISIONING_PROFILE_PATH']
end
def adhoc_certificate
ENV['ADHOC_CERTIFICATE_NAME']
end
def adhoc_profile_path
ENV['ADHOC_PROVISIONING_PROFILE_PATH']
end
def release_certificate
ENV['RELEASE_CERTIFICATE_NAME']
end
def release_profile_path
ENV['RELEASE_PROVISIONING_PROFILE_PATH']
end
def dev_entitlements
entitlements = {
'aps-environment' => 'development',
'get-task-allow' => true
}
unless ENV['TRAVIS']
entitlements = entitlements.merge('keychain-access-groups' => [keychain_access_group])
end
entitlements
end
def adhoc_entitlements
release_entitlements
end
def release_entitlements
{ 'aps-environment' => 'production',
'get-task-allow' => false,
'keychain-access-groups' => [keychain_access_group] }
end
def keychain_access_group
[seed_id, identifier].join('.')
end
def identifier
[company, name].join('.')
end
def sdk_version
'7.1'
end
def deployment_target
'7.0'
end
def prerendered_icon
true
end
def devices
%i[iphone]
end
def orientations
%i[portrait]
end
def info_plist
{
'CFBundleDevelopmentRegion' => 'en',
'CFBundleURLTypes' => [url_scheme],
'UIAppFonts' => fonts,
'UILaunchImageFile' => 'Default',
'UIStatusBarHidden' => true,
'UIViewControllerBasedStatusBarAppearance' => false
}
end
def fonts
%w[helveticaneue-light.otf icons.ttf]
end
def url_scheme
{ 'CFBundleURLName' => identifier,
'CFBundleURLSchemes' => [name] }
end
def icons
Dir.glob('resources/Icon*.png').map{ |icon| icon.split('/').last }
end
end
require 'net/http'
require 'net/http/post/multipart'
namespace 'crittercism' do
desc 'Submit .dSYM to Crittercism'
task upload: ['deploy:zip_dsym'] do
p 'Submitting to Crittercism...'
crittercism = App.config.crittercism
App.fail 'A value for app.crittercism.app_id is mandatory' unless crittercism.app_id
App.fail 'A value for app.crittercism.api_key is mandatory' unless crittercism.api_key
base_url = 'https://app.crittercism.com/api_beta'
endpoint_url = "#{base_url}/dsym/#{crittercism.app_id}"
app_dsym_zip = "#{App.config.app_bundle('iPhoneOS').sub(/\.app$/, '.dSYM')}.zip"
uri = URI.parse(endpoint_url)
File.open(app_dsym_zip) do |dsym|
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post::Multipart.new(uri.path,
'dsym' => UploadIO.new(dsym, 'application/zip', 'dsym.zip'),
'key' => crittercism.api_key
)
response = http.request(request)
p "Crittercism: #{response.code} #{response.message}"
p response.body unless /^2\d+/.match("#{response.code}")
end
end
end
# usage: rake deploy [notes="Some notes"] [replace="True|False"] [notify="True|False"] [quiet=1|true|"anything"]
# rake release
desc 'Deploy to TestFlight/Crittercism'
task :deploy do
['slack:starting', 'deploy:archive', 'testflight:upload', 'crittercism:upload', 'slack:finished'].each do |task|
Rake::Task[task].invoke
raise "#{task} failed!" if $?.exitstatus.nonzero?
end
end
desc 'Release to App Store'
task :release do
['deploy:archive:distribution', 'crittercism:upload'].each do |task|
Rake::Task[task].invoke
raise "#{task} failed!" if $?.exitstatus.nonzero?
end
end
namespace :deploy do
desc 'Update release notes'
task :notes do
file = File.new('notes', 'a+')
system("$EDITOR #{file.path}")
end
desc 'Prepare archive'
task :archive do
p 'Preparing archive...'
App.config_without_setup.deploy_mode = true
Rake::Task['archive'].invoke
end
namespace :archive do
desc 'Prepare release archive'
task :distribution do
p 'Preparing release archive...'
App.config_without_setup.deploy_mode = true
Rake::Task['archive:distribution'].invoke
end
end
desc 'Zip .dSYM file'
task :zip_dsym do
p 'Zipping .dSYM file...'
App.config_without_setup.deploy_mode = true
app_dsym = App.config.app_bundle('iPhoneOS').sub(/\.app$/, '.dSYM')
app_dsym_zip = app_dsym + '.zip'
if !File.exist?(app_dsym_zip) or File.mtime(app_dsym) > File.mtime(app_dsym_zip)
Dir.chdir(File.dirname(app_dsym)) do
sh "/usr/bin/zip -q -r '#{File.basename(app_dsym)}.zip' '#{File.basename(app_dsym)}'"
end
end
end
end
class TestFlightConfig
attr_accessor :api_token, :team_token, :app_token, :distribution_lists
end
class CrittercismConfig
attr_accessor :app_id, :api_key
end
module Motion; module Project; class Config
attr_accessor :deploy_mode
variable :testflight, :crittercism
def testflight
yield testflight_config if block_given? && deploy?
testflight_config
end
def crittercism
yield crittercism_config if block_given? && deploy?
crittercism_config
end
def testflight_config
@testflight_config ||= TestFlightConfig.new
end
def crittercism_config
@crittercism_config ||= CrittercismConfig.new
end
def deploy?
!!@deploy_mode
end
end; end; end
# -*- coding: utf-8 -*-
$:.unshift('/Library/RubyMotion/lib')
require 'motion/project/template/ios'
require 'bundler'
Dir.glob('./config/*.rb').each { |file| require file }
if ARGV.join(' ') =~ /spec/
Bundler.require :default, :development, :spec
elsif ARGV.join(' ') =~ /testflight/
Bundler.require :default, :development
elsif ARGV.join(' ') =~ /archive/
Bundler.require :default
else
Bundler.require :default, :development
require 'sugarcube'
require 'sugarcube-repl'
end
Dir.glob('lib/tasks/*.rake').each { |file| import file }
environment_variables = Dotenv.load
Motion::Require.all(Dir.glob('lib/**/*.rb') + Dir.glob('app/**/*.rb'))
Motion::Project::App.setup do |app|
properties = AppProperties.new
app.detect_dependencies = true
app.name = properties.name
app.seed_id = properties.seed_id
app.identifier = properties.identifier
app.short_version = properties.version
app.sdk_version = properties.sdk_version
app.deployment_target = properties.deployment_target
app.interface_orientations = properties.orientations
app.testflight do |config|
config.api_token = ENV['TESTFLIGHT_API_TOKEN']
config.team_token = ENV['TESTFLIGHT_TEAM_TOKEN']
config.app_token = ENV['TESTFLIGHT_APP_TOKEN']
config.distribution_lists = %w[app internal]
end
app.development do
app.version = properties.dev_version
if app.deploy?
p 'Setting testflight options...'
app.info_plist['ENV_DEPLOY'] = 'True'
app.codesign_certificate = properties.adhoc_certificate
app.provisioning_profile = properties.adhoc_profile_path
app.crittercism do |config|
config.app_id = ENV['DEVELOPMENT_CRITTERCISM_APP_ID']
config.api_key = ENV['DEVELOPMENT_CRITTERCISM_API_KEY']
end
properties.adhoc_entitlements.each { |key, value| app.entitlements[key] = value }
else
p 'Setting development options...'
app.codesign_certificate = properties.dev_certificate
app.provisioning_profile = properties.dev_profile_path
properties.dev_entitlements.each { |key, value| app.entitlements[key] = value }
end
end
app.release do
p 'Setting release options...'
app.version = properties.version
app.codesign_certificate = properties.release_certificate
app.provisioning_profile = properties.release_profile_path
app.crittercism do |config|
config.app_id = ENV['PRODUCTION_CRITTERCISM_APP_ID']
config.api_key = ENV['PRODUCTION_CRITTERCISM_API_KEY']
end
properties.release_entitlements.each { |key, value| app.entitlements[key] = value }
end
app.icons = properties.icons
app.prerendered_icon = properties.prerendered_icon
app.archs['iPhoneOS'] = %w[armv7]
app.libs = properties.libraries
app.frameworks = properties.frameworks
# Extra plist
properties.info_plist.each { |key, value| app.info_plist[key] = value }
environment_variables.each { |key, value| app.info_plist["ENV_#{key}"] = value }
app.pods do
# Logging
pod 'CocoaLumberjack', '~> 1.8.1'
# Networking
pod 'AFNetworking', '~> 1.3.3'
pod 'Reachability', '~> 3.1.1'
pod 'RestKit', '~> 0.22.0'
pod 'SDWebImage', '~> 3.5.4'
# Analytics / Crash Reporting
pod 'CrittercismSDK', '~> 4.3.1'
# Other
pod 'MapBox', '~> 1.1.0'
pod 'SZTextView', '~> 1.1.1'
pod 'SSKeychain', '~> 1.2.1'
pod 'TSMessages', '~> 0.9.6'
pod 'MultiDelegate', '~> 0.0.2'
pod 'UIImage-Resize', '~> 1.0.1'
pod 'GHMarkdownParser', '~> 0.0.1'
pod 'NSDate+TimeAgo', git: 'git@github.com:kevinlawler/NSDate-TimeAgo.git', :tag => 'v1.0.3'
pod 'JazzHands', '~> 0.0.4'
pod 'Slash', '~> 0.1.2'
pod 'Harpy', '~> 2.5.2'
end
app.vendor_project 'vendor/lumberjack_patch', :static
app.vendor_project 'vendor/OpenInChrome', :static
app.vendor_project 'vendor/DACircularProgress', :static
app.vendor_project 'vendor/Pods/CrittercismSDK/CrittercismSDK', :static
app.vendor_project 'vendor/Pods/MapBox/Proj4', :static, products: %w[libProj4.a]
end
Motion::SettingsBundle.setup do |app|
properties = AppProperties.new
app.toggle 'Awesomeness', key: 'settings-awesomeness', default: true
app.toggle 'Coolness', key: 'settings-coolness', default: true
app.title 'app', key: 'settings-version', default: properties.version
end
require 'json'
require 'net/http'
namespace :slack do
@application = ENV['APP_NAME']
@branch = `git rev-parse --abbrev-ref HEAD`.chomp
@deployer = `git config user.name`.chomp
@git_sha = `git rev-parse --short HEAD`.chomp
@repo_url = ENV['GIT_REPO_URL']
def post_to_slack(payload)
return if ENV['quiet']
slack_subdomain = ENV['SLACK_SUBDOMAIN']
slack_token = ENV['SLACK_WEBHOOKS_TOKEN']
uri = URI.parse("https://#{slack_subdomain}.slack.com/services/hooks/incoming-webhook?token=#{slack_token}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data(payload: payload.merge(default_payload).to_json)
response = http.request(request)
p "Slack: #{response.code} #{response.message}"
p response.body unless /^2\d+/.match("#{response.code}")
end
def default_payload
{ channel: '#general', username: 'deploybot', icon_emoji: ':rocket:' }
end
def release_notes
{ fallback: 'Releases Notes', fields: [{ title: 'Release Notes', value: notes }] }
end
def notes
@notes ||= File.read('notes') if File.exists?('notes')
end
def sha_link
"#{@repo_url}/commit/#{@git_sha}"
end
def branch_link
"#{@repo_url}/tree/#{@branch}"
end
desc 'Begin deploy notifications to Slack'
task starting: ['deploy:notes'] do
p "Sending Slack 'Deploying' notification..."
payload = { text: "#{@deployer} is deploying #{@application} (<#{branch_link}|#{@branch}> / <#{sha_link}|#{@git_sha}>) to TestFlight" }
post_to_slack(payload)
@start_time = Time.now
end
task :finished do
p "Sending Slack 'Deployed' notification..."
elapsed_time = Time.now.to_i - @start_time.to_i
payload = { text: "#{@deployer} deployed #{@application} successfully in #{elapsed_time} seconds." }
payload.merge!(attachments: [release_notes]) if notes
post_to_slack(payload)
end
end
require 'net/http'
require 'net/http/post/multipart'
namespace :testflight do
desc 'Submit archive to TestFlight'
task upload: ['deploy:notes', 'deploy:zip_dsym'] do
p 'Submitting to TestFlight...'
testflight = App.config.testflight
App.fail 'A value for app.testflight.api_token is mandatory' unless testflight.api_token
App.fail 'A value for app.testflight.team_token is mandatory' unless testflight.team_token
distribution_lists = (testflight.distribution_lists ? testflight.distribution_lists.join(',') : nil)
notes = File.read('notes') if File.exists?('notes')
notify = ENV['notify'] || 'False'
replace = ENV['replace'] || 'False'
App.fail 'Submission notes must be provided.' if notes.nil? || notes.empty?
endpoint_url = 'https://testflightapp.com/api/builds.json'
app_dsym_zip = "#{App.config.app_bundle('iPhoneOS').sub(/\.app$/, '.dSYM')}.zip"
archive = App.config.archive
uri = URI.parse(endpoint_url)
File.open(app_dsym_zip) do |dsym|
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post::Multipart.new(uri.path,
'file' => UploadIO.new(archive, 'application/octet-stream', 'archive.ipa'),
'dsym' => UploadIO.new(dsym, 'application/zip', 'dsym.zip'),
'api_token' => testflight.api_token,
'team_token' => testflight.team_token,
'notes' => notes,
'notify' => notify,
'replace' => replace,
'distribution_lists' => distribution_lists
)
response = http.request(request)
p "TestFlight: #{response.code} #{response.message}"
p response.body unless /^2\d+/.match("#{response.code}")
end
end
end
@pbraswell
Copy link

This looks like it will definitely work for what I want, but I'm a little unsure of exactly how all this gets assembled. The Rake file obviously replaces the one generated by the 'motion' command to start a new project. Does the rest go in /lib? What environmental variables need to be set and how is that best done for a ruby in motion project?

TIA!,
Peter

@gnestor
Copy link

gnestor commented Jan 15, 2015

This is great! I just created one for New Relic (which I will share once I test it).

However I have the same question as @pbraswell, where do we put these files?

@theCrab
Copy link

theCrab commented Jun 17, 2016

@dblandin what is the folder structure for this awesome util

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment