Skip to content

Instantly share code, notes, and snippets.

@SteveBenner
Last active September 19, 2022 01:13
Show Gist options
  • Save SteveBenner/b601700b96e6a445cb78 to your computer and use it in GitHub Desktop.
Save SteveBenner/b601700b96e6a445cb78 to your computer and use it in GitHub Desktop.
MyDentalCompanion build system

MyDentalCompanion Build System

MyDentalCompanion is a SaaS app for the Dental industry that I was responsible for designing and developing as part of a startup. The collection of Ruby scripts and YAML files accompanying this README represents the system I created to completely automate the app's production and distribution pipeline. MDC was my first project of any scale, and being the sole developer, it was especially important to establish a flexible workflow with minimal overhead.

I found Rake to be the most suitable tool of choice for this, not only because the project's ecosystem was Ruby-based, but particularly because it utilizes extremely simple abstractions. Using Rake tasks, I was able to rapidly establish a clean separation of concerns for the components of app production, and automate key processes. This resulted in the ability to, with a single command, convert my most recent source code into a native iOS app delivered straight into the hands of our testers... And more!

The original system, which included all these components, was something I put together in 24 hours. I was quite impressed with how something like that can be done, thanks to Ruby. The files here represent considerable refactoring and improvement over the original single Rakefile, but the initial version delivered the same capability for deployment with just one keypress.

Components / Phases

Multi-context configuration merging

Producing a cross-platform RhoMobile application is a highly involved process. Altogether, there are several unique configuration files essential to the process, some affecting multiple components. It significantly complicates the matter to consider these values may change depending upon 3 contexts:

  • Deployment Platform

    From the app source, RhoMobile is capable of compiling native applications for a variety of mobile platforms. We only produced iOS builds, but Android was planned as well, and cross-platform deployment was a priority for the project.

  • Operating System

    Multiple operating systems may be considered as deploy targets on each platform, and each requires a unique config. Even if a single OS is chosen for production, they are rapidly updated; during our project it was necessary to implement considerable updates during the transition between iOS 6 and iOS 7, as many changes were introduced by the new OS.

  • Environment Setting

    Configurations are also distinguished by separate values for the three typical environments of Development, Testing, and Production.

All configuration data is merged in a single clean step, in which data from the YAML documents is loaded into a single global object. The data can be arbitrarily nested--a custom algorithm is used to merge it recursively, while preventing nil values from overwriting existing ones. The $config object is used throughout the Rake tasks as a single point of reference that can be dynamically updated as appropriate.

Accessing Xcode / iOS data

For convenience, the iOS Provisioning Profiles necessary for building iOS apps are validated and scanned from the local directory where they're kept, and contained data is opened up for use in configuration and use in other steps such as TestFlight distribution.

Asset compilation and installation

Most of the app's assets are transpiled in real time during development and testing, which I handle using the guard gem and plugins. There are some assets which must be generated as part of the build process, however, such as the formerly mentioned configuration files, and ERB templates converted from Slim. Also, Ruby libraries and extensions included by the app must be copied into the build manually at this point.

Leveraging Rhodes Rakefile system to build and simulate native app

Rhodes comes with its own self-contained Rake tasks for building and compiling the app to native platforms, as well as launching simulators. It would be ideal to load the whole system into my own Rakefile to take advantage of the Rake API, but unfortunately the Rhodes codebase is horrible and this is totally impossible. In my build system, tasks such as companion:build basically wrap an external call to the Rhodes Rakefile, passing in relevant CLI arguments/flags for desired action.

Beta distribution via TestFlight

TestFlight (now bought and incorporated into Apple) was a fantastic service that allowed for centralized management of app beta distribution for iOS development teams on a free-to-use basis. Members of your team or anonymous invitees could use the TestFlight app to download and install builds of your app straight into their own device, featuring email notifications and release notes with Markdown support.

In the release phase of this build system, the IPA file for specified app build is uploaded to TestFlight for distribution via their API. A release note is created in Markdown, which contains a changelog generated from the development CHANGELOG.md, a link where users can submit feedback, and a custom note with any comments about the build.

Archiving app builds

The final stage of each release is to create and store a compressed archive of the build product. This includes the IPA binary, and other files such as CHANGELOG and the build log generated from output of the process executing the Rhodes Rake task. Initially, the system utilized the backup gem to create archives and upload them to an AWS S3 endpoint automatically (the necessary backup config file is generated with specific values for current build and config).

This was ideal, but unfortunately as of Ruby 2.0 the backup gem stopped functioning. So I wrote a new task which simply created tar archives and stored them in local directory which synced to our team's Google Drive (Business) account in the cloud.

Technical Notes

  • There is no file hierarchy so for purposes of understanding the code, assume the following:
    • The root is assumed to be the MDC Rhodes app directory
    • All files with .rake extension are assumed to be stored in subdirectory rakelib
    • All files with .yml extension are assumed to be stored in subdirectory config
  • I make heavy use of custom data structure Hashugar in the Rakefiles, which provides dot-notation access similar to OpenStruct. Make sure not to mix up Hashugar and Hash objects in the code.
# Copyright (C) Jun 11, 2015 Stephen C. Benner
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
require 'bundler'
# require 'tempfile'
Bundler.require :default
ARCHIVE_DEST = File.join %W[/usr local google-drive backup mdc-builds #{$config.app.url} #{$platform}-#{$os}]
ARCHIVE_FILE = File.join ARCHIVE_DEST, "build-#{APP_VERSION}.tgz"
namespace :backup do
# Create a gzipped TAR archive of the application IPA and assets
task :archive do
# Specify archive contents via `tar`s exclusion patterns (of type shell glob, not regular expression)
EXCLUDED_FILES = ['*.app', '*app_info.txt']
IO.write '/tmp/exclusions', EXCLUDED_FILES.join($/)
Dir.chdir DISTRIBUTION_DIR
if system("tar -czv . -X /tmp/exclusions -f #{ARCHIVE_FILE}")
puts 'Successfully created backup archive!'
else
abort 'Failed to create backup!'
end
end
# NOTE: the `backup` gem is unavailable for use in Ruby 2.0!!!
desc 'Create a compressed archive and upload to Amazon S3 for backup.'
task :backup do
model_path = File.join %W[#{HOME} Backup models build_#{APP_VERSION.to_s}.rb]
# Generate a `model` configuration file for `backup` gem (for iOS app builds)
File.open(model_path, 'w') do |f|
f.puts <<-RUBY
Backup::Model.new(:"build_#{APP_VERSION}", 'Companion mobile device app builds') do
split_into_chunks_of 200
archive :"build_#{APP_VERSION}" do |archive|
archive.root "#{APP_DIR}"
archive.add "#{File.join APP_DIR, PLATFORM_DIR}"
archive.add "build-log.txt"
end
store_with S3 do |s3|
s3.access_key_id = ENV[MDC_S3_KEY]
s3.secret_access_key = ENV[MDC_S3_SECRET]
s3.region = 'us-west-2'
s3.bucket = 'companion-backup'
s3.path = "/#{$platform}"
s3.encryption = :aes256
end
compress_with Gzip
end
RUBY
f.path
end
raise 'File creation failed.' unless File.exists?(model_path)
raise 'BACKUP FAILED.' unless system "backup perform -t build_#{APP_VERSION}"
end
end
# Copyright (C) Jun 11, 2015 Stephen C. Benner
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
require 'bundler'
require 'json' # for parsing/creating config files
require 'yaml' # for parsing/creating config files
require 'tempfile' # used to create HTML file for TestFlight note
require 'fileutils' # file manipulation (duh)
require 'open3' # DARK MAGIC
Bundler.require :default, :util, :ios
# TODO: add a logging component
desc "Starts guard in project directory: #{APP_DIR}"
task :guard do
exec "bundle exec --verbose guard -d -w #{APP_DIR}"
end
namespace :companion do
# Clean the Rhodes app log file 'rholog.txt'
task :rm_rholog do
begin
IO.write(File.join(APP_DIR, 'rhosimulator', 'rholog.txt'), '')
IO.write(File.join(APP_DIR, 'rhosimulator', 'rholog.txt_pos'), '')
puts 'rholog.txt truncated.'
rescue
puts 'ERROR - Failed to clean rholog.txt'
end
end
# Compile SASS/SCSS assets using compass executable
task :compile_css do
unless system "compass compile #{File.join APP_DIR, 'public'} --output-style compressed --force"
abort 'ERROR: Compass failed to compile SASS/SCSS sources.'
end
end
# Copy Ruby libraries to app directory before build
task :copy_libs, :libs do |t, args|
args[:libs].each do |lib|
libname = Dir.entries(APP_LIB_SOURCE).detect { |f| f =~ /#{lib}(-\d+\.\d+\.\d+)?/ }
FileUtils.cp_r File.join(APP_LIB_SOURCE, libname, 'lib', '.'), APP_LIB_DIR
end
end
desc 'Configure the app for specified environment, platform, and operating system'
task :config, [:environment, :platform, :os_version] do |t, args|
env, platform, os = %i[environment platform os_version].collect do |context|
# Validate arguments against values defined in the project configuration
if args[context]
supported_contexts = PROJECT["#{context}s"]
error_message = "Invalid #{context} given. Expecting one of #{supported_contexts}, found #{args[context]}."
abort error_message unless supported_contexts.include? args[context]
end
# Initialize contexts from default values if absent from arg list
(args[context] || DEFAULT[context]).to_sym
end
puts "Configuring app for #{env}..."
# Parse all configuration files into global `$config` object according to the 3-context schema:
# In addition to 'defaults', configs may define context-specific values under top-level mappings
# that correspond to supported context values (environments, platforms, os versions).
FileList.new("#{APP_DIR}/config/*.yml").exclude('**/main.yml').each do |config_file|
configurations = YAML.load_file(config_file).deep_symbolize_keys
# Files without a `defaults` section are converted to the schema described above
configurations = {defaults: configurations} unless configurations[:defaults]
# Configuration data is merged destructively, meaning existing values will be overwritten by those
# in given object. I use my own merge algorithm which prevents nil values from being written.
$config[File.basename(config_file, '.yml').to_sym] = configurations[:defaults]
.recursive_merge(configurations[env] || {})
.recursive_merge(configurations[platform] || {})
.recursive_merge(configurations[os] || {})
.to_hashugar
end
Rake::Task['companion:copy_libs'].invoke $config.build.gems
# Data defined in the `$config.settings` object will be made available within the app (see `settings.yml`)
$config.settings.app.update({name: APP_NAME, appversion: APP_VERSION})
# Set values for Rhodes app build-time configuration
profile_data = $config.ios.provisioning_profile
$config.build = $config.build.to_h.recursive_merge({
version: APP_VERSION,
iphone: {
provisionprofile: profile_data.uuid,
BundleIdentifier: profile_data.identifier,
sdk: $config.build.iphone.sdk + $ios_sdk
}
}).to_hashugar
# Environment-specific configuration
case env
when :development
when :testing
when :production
$config.build.iphone.delete :emulator # simulator stuff used for dev/testing only
$config.build.iphone.delete :emulatortarget #
else
end
#
# GENERATE CONFIGURATION FILES (existing files are overwritten!)
#
Dir.chdir APP_DIR do |working_dir|
# Create iOS entitlements plist file (specifies which device capabilities are enabled)
IO.write 'Entitlements.plist', $config.entitlements.to_h.to_plist
# Write out Rhodes build-time configuration file `build.yml`
File.open('build.yml', 'w') do |f|
f.puts '# THIS FILE WAS GENERATED AUTOMATICALLY!'
f.write $config.build.to_h.deep_stringify_keys.to_yaml
end
# Generate the Rhodes runtime configuration file `rhoconfig.txt`
File.open('rhoconfig.txt', 'w') do |f|
$config.rhoconfig.each do |property, value|
val = case value
when Array then value.map { |p| "'#{p}'" }.join(', ')
when String then "'#{value}'"
else value
end
end
f.puts "#{property.to_s} = #{val}"
end
# Produce JSON files from configuration data, which are included in build and loaded by the app
%W[settings couchdb].each do |file|
IO.write "app/#{file}.json", JSON.dump($config[file].to_h.deep_stringify_keys)
end
end
puts "Successfully configured app for #{env}."
end
desc 'Build iOS app using RhoMobile.'
task :build => 'xcode:update_profiles' do
Rake::Task['companion:config'].invoke :production
puts 'Building...'
Dir.chdir Pathname $config.project.path
cmd = 'bundle exec rake device:iphone:production --trace'
# Create a text file to capture output of the Rhodes build process, which is extensive
f = File.open(Pathname($config.project.path).expand_path + 'build-log.txt', 'w')
# Launch an external process that calls the Rhodes Rake task responsible for building the app
status = Open3.capture2e(cmd, {[:out, :err] => [f]})
p status
# puts external_cmd "bundle exec rake device:iphone:production --trace", APP_DIR
# puts $?.success? ? "Successfully built app #{RELEASE} #{$app.version_string}" : "Failed to build app: #{$?.exitstatus}"
# Once the build is complete, reconfigure the environment for development
Rake::Task['companion:config'].invoke :development
end
desc 'Configure and build iOS app, distribute to TestFlight, then backup assets to Google Drive.'
task :release, :version do |task, args|
app_version = args[:version] || APP_VERSION
puts "Releasing #{APP_NAME} #{$release} #{app_version} for the #{$platform} v#{$ios_sdk}..."
Rake::Task['companion:build'].invoke
Rake::Task['testflight:distribute'].invoke
puts "#{APP_NAME} #{app_version} successfully released!"
Rake::Task['backup:archive'].invoke
end
desc 'Run the app using RhoSimulator'
task :rhosim => :rm_rholog do
Rake::Task['companion:config'].invoke :development
puts external_cmd 'bundle exec rake run:rhosimulator --trace', APP_DIR
end
desc 'Run the app using iPhone simulator.'
task :sim do
Rake::Task['companion:config'].invoke :testing
Dir.chdir APP_DIR
puts 'Failed to launch Apple iPhone simulator.' unless system 'rake run:iphone'
end
end
# This file is used to generate `build.yml`.
# NOTE: some entries are intentionally left blank (as placeholders).
defaults:
# sdk: # set in `main.yml`
# sdkversion: # set in `main.yml`
name: MyDentalCompanion
vendor: xfactorapplications
applog: rholog.txt
iphone:
configuration: Distribution
provisionprofile: # updated dynamically and then read from 'ios.yml'
sdk: iphonesimulator
codesignidentity: iPhone Developer # automatically selects best choice in XCode
BundleIdentifier: # if setting manually, make sure to check against actual profiles
BundleURLScheme: mydentalcompanion
emulatortarget: iphone
# production: # settings written to XCode plist
# app_plist_title: Companion
# app_plist_subtitle: My Dental Companion
# app_plist_icon_url:
# app_plist_ipa_url:
# ipa_itunesartwork_image:
extensions:
- json
- coreapi # standard Rhodes API support
- net-http # standard net lib
# - rhoconnect-client # RhoConnect API and client adapters
capabilities:
- phone
- push
# Path to RubyMobile package libraries
libdir: ~/bitbucket/rubymobile/lib/package
# RubyMobile app package libraries to include
libs:
# Gems to copy into app packaged libraries
gems:
- hashugar
# Environment-specific settings
development: # using RhoSimulator and iPhone simulator
iphone:
sdk: iphonesimulator
configuration: Debug
testing:
iphone:
sdk: iphonesimulator
configuration: Release # App development release
production:
profiler: 0
iphone:
sdk: iphoneos
configuration: Distribution # Apple App Store release
# OS-specific settings
ios7:
# Enable the ios7 transparent iphone info/status bar UI element
iphone_use_new_ios7_status_bar_style: 1
# This file is used in a rake task to generate '/app/couchdb.json'
#
# CouchBase Lite configuration settings
# Based on CouchRest::Model - http://www.couchrest.info/model/configuring.html
#
defaults: &defaults
protocol: 'http'
host: 127.0.0.1
port: 5984
prefix:
suffix:
username:
password:
# Environment-specific settings
development:
protocol: 'https'
host: 127.0.0.1
prefix: test-db
testing:
protocol: 'http'
host:
prefix: test-db
production:
protocol: 'http'
host:
port: 5984
prefix: data
application-identifier: $(AppIdentifierPrefix)$(CFBundleIdentifier)
get-task-allow: false
keychain-access-groups:
- $(AppIdentifierPrefix)$(CFBundleIdentifier)
# Copyright (C) Jun 11, 2015 Stephen C. Benner
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
require 'bundler'
Bundler.require :default, :ios
$platforms = %w[iphone ipad] # list of platforms to build for
$simulator_sdk = $ios_sdk
begin
XCODE_DIR = `xcode-select -p`.chomp # prints the Xcode developer tools path in OS X 10.9
INSTALLED_SDKS = `ls #{XCODE_DIR}/Platforms/iPhoneOS.platform/Developer/SDKs`.chomp
rescue
abort 'Unable to find Xcode developer tools. Make sure Xcode is installed correctly and available on your $PATH.'
end
PROVISIONING_PROFILES_DIR = Pathname($config.ios.provisioning_profiles_dir).expand_path
PROVISIONING_PROFILE = $config.ios.provisioning_profile
namespace :xcode do
desc "Parse data from Xcode provisioning profiles into 'ios.yml' config file."
task :update_profiles do
# Scan the Xcode directory where provisioning profiles are kept
files = PROVISIONING_PROFILES_DIR.children
abort "No provisioning profiles found in #{PROVISIONING_PROFILES_DIR}" if files.empty?
# Verify and parse all provisioning profiles files into Plists using openssl,
# and extract relevant data into a Hash
profiles = files.inject({}) do |list, profile|
filename = Shellwords.shellescape PROVISIONING_PROFILES_DIR.join(profile)
plist = Plist::parse_xml(`openssl smime -in #{filename} -inform der -verify -noverify`)
list[plist['Name']] = {
uuid: plist['UUID'],
developer_account: plist['TeamName'],
creation_date: plist['CreationDate'].to_s,
devices: plist['ProvisionedDevices'],
device_count: plist['ProvisionedDevices'].count,
identifier: plist['Entitlements']['application-identifier'].split('.').drop(1).join('.')
}
list
end
# Write out the updated data to YAML config file
ios_config = $config.ios.update({provisioning_profiles: profiles}).deep_stringify_keys
IO.write("#{APP_DIR}/config/ios.yml", ios_config.to_yaml)
end
end
---
provisioning-profile: mdc-alpha-ad-hoc
provisioning-profiles-dir: ~/Library/MobileDevice/Provisioning Profiles
provisioning_profiles:
'iOS Team Provisioning Profile: com.xfactorapplications.my-dental-companion':
uuid: some-apple-developer-uuid
developer_account: Stephen Benner
creation_date: 'some-iso8601-date'
devices:
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
device_count: 17
identifier: com.xfactorapplications.my-dental-companion
companion-alpha-testers:
uuid: some-apple-developer-uuid
developer_account: Stephen Benner
creation_date: 'some-iso8601-date'
devices:
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
device_count: 16
identifier: "*"
mdc-alpha-ad-hoc:
uuid: some-apple-developer-uuid
developer_account: Stephen Benner
creation_date: 'some-iso8601-date'
devices:
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
device_count: 18
identifier: com.xfactorapplications.my-dental-companion
'iOS Team Provisioning Profile: *':
uuid: some-apple-developer-uuid
developer_account: Stephen Benner
creation_date: 'some-iso8601-date'
devices:
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
- some-ios-device-uuid
device_count: 17
identifier: "*"
# This is the main configuration file for the MDC project
rhomobile:
version: 4.2.0 # the version of RhoMobile/Rhodes used in this project
path: ~/bitbucket/rhomobile # local path to the Rhodes gem used
project:
path: ~/github-xFactorApplications/companion-app
ruby-manager: chruby
environments: [development, testing, production]
platforms: [ios, android, wm7, wm8] # platforms supported by Rhodes
devices: [iphone, ipad, ipod]
os-versions: [7.1]
app:
name: MyDentalCompanion
version: # values are merged into a single string
major: 0
minor: 4
patch: 0
url: my-dental-companion
defaults:
environment: development
build: distribution
release: alpha
platform: ios
device: iphone
os-version: 7.1
# This file is used to generate `rhoconfig.txt`
# @see http://docs.rhomobile.com/en/${RHODES_VERSION}/guide/runtime_config
defaults:
start_path: /app/Login # startup page for the application
options_path: /app/Settings # path to the options page (in this case handled by javascript)
# Logging
MinSeverity: 0 # Logging level - 0: trace, 1: info (app level), 2: warnings, 4: errors
LogToOutput: 1 # enable copy log messages to standard output, useful for debugging (1 = yes)
LogCategories: '*' # comma-separated list of categories to log (asterisk indicates all)
ExcludeLogCategories: [Sync, RhoRuby] # categories to exclude from logging
MaxLogFileSize: 102400 # max log file size in bytes (set to 0 for unlimited)
#net_trace: 1 # turn on local http server traces, off by default
#logserver: # Destination URL for data sent with 'RhoConfg.send_log' and log menu action
logname: mdc-app # log file prefix - contain human-readable text
KeepTrackOfLastVisitedPage: 0 # Keep track of the last visited page
LastVisitedPage: ''
#geo_location_inactivity_timeout: 30 # (in seconds)
# Network/Web settings
net_timeout: 15 # timeout of network requests in seconds (30 by default)
# port of the local (embedded) HTTP server. This parameter is mainly for debug purposes.
# if not specified, application will dynamically select one.
# WARNING!!! Remove this parameter before put application to production.
local_server_port: 1080
# Interface settings
full_screen: 0 # open rhodes app in fullscreen mode (defaults to 0 if not set)
enable_screen_zoom: false
disable_screen_rotation: 1 # disable possible for ANDROID and iPhone ONLY (defaults to 'true')
jqtouch_mode: 1 # hides forward button and animates back button transition
splash_screen: 'delay=0;center' # loading screen duration and positioning
# ???
#debug_host: 'localhost' # enable local debugger
# Database settings
sync_poll_interval: 0 # disable automatic sync
# bulksync_state: 1 # set to 0 to reset bulksync_state and trigger a bulk sync the next time rhodes synchronizes
disable_default_database: 1 # disable loading of the standard SQLite database
### Environment-specific settings
development:
net_trace: 0
testing:
net_trace: 0
local_server_port:
production:
MinSeverity: 3
LogToOutput: 0
net_trace: 0
log_skip_post: 0
local_server_port:
### Platform-specific settings
android:
disable_loading_indication: 0 # disable the Android page loading progress bar
### Operating-system-specific settings
ios7:
full_screen: 0 # iOS7 doesn't support fullscreen mode!
# Copyright (C) Jun 11, 2015 Stephen C. Benner
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
require 'bundler'
Bundler.require :default
GEM_SOURCE = 'localhost:9292' # Geminabox server
namespace :rhodes do
desc 'Install the RhoMobile software suite.'
task :install do
puts 'Installing RhoMobile Suite...'
# For some reason the install order of these gems is very important
GEMS = %w[
rhomobile-debug
rhodes-[0-9]*.gem
rhoelements-[0-9]*.gem
rhoconnect-[0-9]*.gem
rhoconnect-adapters-[0-9]*.gem
rhoconnect-client-[0-9]*.gem
rhohub-[0-9]*.gem
rhodes-translator
]
GEMS.each do |gem|
puts "Installing #{gem.sub(/-\[0-9\]\*\.gem/, '')}..."
abort 'ERROR: Failed to install gem.' unless `gem install #{gem}`
puts 'RhoMobile Suite has been successfully installed!'
end
end
end
# The data in this file is provided directly to the app in the form of a global object.
# It is converted to JSON and included in the app build, under the `app` directory.
#
defaults:
# Device properties
device:
id: # device property
# Database configuration
db:
response-time: 10000 # timeout in milliseconds
# WebView settings
webview:
# layout configuration
viewport: # "content" attribute contents for <meta name="viewport">
width: device-width # device width.
# height: 568
initial-scale: 1.0 # initial interface scale
maximum-scale: 1.0 # max interface scale (zoom)
user-scalable: 0 # set to 1 to enable zooming the interface
# RhoSeat configuration
rhoseat:
model-type-key: doctype # default is 'type'
# Application settings
app:
# Environment-specific settings
development:
testing:
production:
# Copyright (C) Jun 11, 2015 Stephen C. Benner
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
require 'bundler'
require 'net/http'
#require 'openssl'
Bundler.require :default, :testflight
#OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
CHANGELOG = $config.project.path + 'CHANGELOG.md'
$config.testflight = {release_note: "'#{$build}: #{$release} #{APP_VERSION}'"}
namespace :testflight do
# 'Compile changelog into markdown for use in TestFlight release note'
task :compile_changelog do
# The release note is obtained by using a regex to parse the text within 'CHANGELOG.md'.
# A 'note' is delineated by a markdown header (pound sign) followed by a 3-digit version number and text.
release_note_markdown = IO.read(CHANGELOG).scan(/(# \d\.\d\.\d.+?)^(# \d\.\d\.\d)/m).first.first
options = {autolink: true, space_after_headers: false}
$release_note_html = Redcarpet::Markdown.new(Redcarpet::Render::HTML, options).render release_note_markdown
end
desc 'Distribute packaged iOS app to testeres using TestFlight service.'
task :distribute => :compile_changelog do
puts 'Distributing to TestFlight...'
# TestFlight form based on upload API (as of 2014-03-04)
# Official docs at: https://testflightapp.com/api/doc/
DISTRIBUTION_LISTS = ['dev']
$testflight_data = {
# [REQUIRED] get token here: https://testflightapp.com/account/#api
api_token: ENV['MDC_TESTFLIGHT_API_TOKEN'],
# [REQUIRED]
team_token: ENV['MDC_TESTFLIGHT_TEAM_TOKEN'],
# [REQUIRED] 'ipa' file for the build, created by Xcode
file: File.open("#{RELEASE_DIR}/#{APP_NAME}.ipa"),
# [REQUIRED] release note for the latest build (HTML rendered from markdown)
notes: $release_note_html || $config.testflight.release_note,
# [OPTIONAL] comma-separated distribution lists
distribution_lists: DISTRIBUTION_LISTS.join(','),
# [OPTIONAL] notify permitted teammates to install the build, defaults to FALSE
notify: false,
# [OPTIONAL] replace existing build, defaults to FALSE
replace: false
}
# The TestFlight API specifies including the data format as the file extension in URI
UPLOAD_EXTENSION = '.json'
# Note: TestFlight currently enforces HTTPS protocol, although no credentials are required
url = URI::HTTPS::build(host: 'testflightapp.com', path: '/api/builds' + UPLOAD_EXTENSION).to_s
RestClient.log = $stdout
response = RestClient.post url, $testflight_data, accept: :json
puts 'Successfully distributed to TestFlight!' if response.net_http_res.is_a? Net::HTTPSuccess
puts response
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment