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.
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.
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.
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.
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.
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.
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.
- 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 subdirectoryrakelib
- All files with
.yml
extension are assumed to be stored in subdirectoryconfig
- 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 upHashugar
andHash
objects in the code.