LayerKit

Build Status

This project contains the source code for LayerKit for iOS. The project is buildable as a framework for iOS and Mac OS X and contains a test suite to exercise the code base.

Getting Oriented

LayerKit is designed to be distributed to external developers with a simple, lightweight external API. Behind the scenes, the framework is dealing with considerable complexity as it transparently handles synchronization and persistence concerns.

Please keep in mind the following as you work with the project:

  1. Execution of the unit tests requires the installation of dependencies via CocoaPods
  2. The externally distributed header files represent a minority of the project interfaces.
  3. SQLite is used internally as a persistence technology, but this is an implementation detail that is not exposed to end developers.
  4. The project has extensive automation tasks available via Rake. Run rake -T to see what is available. Xcode is only used for code editing and debugging. All project automation is exposed via Rake.
  5. The use of Storyboards and XIBs is forbidden in the project. You must layout any views with code.
  6. The project is under Continuous Integration provided by Jenkins. If you commit changes on a feature branch and open a Pull Request on Github, Jenkins will automatically build the branch and annotate the pull request. Jenkins is also configured to notify the #github slack channel of build status.
  7. The use of Storyboards and XIBs is forbidden in the project. You must layout any views with code.

The project is organized as detailed in the table below:

Path Type Contains
Code Directory Source code organized by type
Documentation Directory Composed project documentation
Documentation/API Directory Appledoc generated documentation
Gemfile Ruby code Ruby Gem dependency declarations for Bundler
Gemfile.lock ASCII text Exact Gem dependency manifest (generated by Bundler)
LICENSE ASCII text Licensing details for the project
Vendor Directory Submodules and external project dependencies
Podfile Ruby code CocoaPods library dependencies for the project
Podfile.lock ASCII text Exact Pod dependency manifest (generated by CocoaPods)
Pods Directory CocoaPods generated artifacts. Ignored by Git.
README.md Markdown text This comprehensive README file
Rakefile Ruby source Rake automation tasks
Resources Directory Assets such images
LayerKit.podspec CocoaPods Spec (Ruby) The CocoaPods spec for installing LayerKit into an external host app
LayerKit.xcodeproj Xcode Project The Xcode project for LayerKit. Use the workspace instead.
LayerKit.xcworkspace Xcode Workspace The Xcode workspace for LayerKit. Used for day to day development.
Tests Directory Unit and Functional Tests as well as Fixtures and the testing server code
VERSION ASCII text The current version of the project in text
.ruby-version rbenv configuration Specifies the version of Ruby that rbenv will bind to
xcodebuild.log Log file Log out xcodebuild output generated by test Rake tasks. Under .gitignore

Developer Setup

In order to get a development environment up and running a number of pre-requisites must be installed and some basic configuration tasks performed.

Install Xcode

  1. Visit the Mac App Store and Install Xcode.
  2. Download and install the Xcode app to your Applications folder.
  3. Launch Xcode and proceed through first launch configuration.

Installing Dependencies via Rake

LayerKit requires the installation of a number of dependencies before performing day to day development work. Once Homebrew has been installed, it can be used to bootstrap a development environment with all required dependencies. For convenience, all required dependencies can be installed via a Rake task:

$ rake init

This task will take care of installing all required software for working on LayerKit. Specifically, the task does the following:

  1. Checks if Homebrew has been installed and if it has not, installs it.
  2. Updates the Homebrew package catalog and upgrades any outdated dependencies.
  3. Installs various packages via Homebrew. See the Brewfile for details.
  4. Checks for the version of Ruby specified in the .ruby-version file and installs it if necessary.
  5. Checks for and installs Bundler if it not already installed.
  6. Bundles all RubyGems dependencies specified in the Gemfile manifest.
  7. Installs all CocoaPods dependencies specified in the Podfile manifest.

This task is designed to be safe to run repeatedly and is used to keep the CI environment up to date as well. A quick run through of the dependencies discussed is outlined below for references.

Homebrew

Homebrew is a package manager for OS X useful for maintaining third-party UNIX software packages independent of the underlying OS X operating system.

Git & git-flow

While OS X does ship with Git pre-installed it can lag behind the most current stable releases. It is recommended that developers maintain up to date packages distributed via Homebrew. git-flow provides a branching strategy for feature, release, and maintenance branches wherein the master branch always represents the current stable production release.

Layer uses a customized git-flow configuration that is detailed on the wiki.

rbenv, ruby-build, & Ruby

As with Git, OS X ships with Ruby pre-installed but tends to lag greatly behind the current release versions and patch levels. OS X bundled Ruby distributions have also historically been subject to various technical short-comings when compared with installations built directly from source code due to underlying details of Apple’s build process. As such, it is recommended that developers work with a Ruby interpretter built from source with a known version and patch level.

To handle the installation of Ruby interpretters from source code and manage multiple Ruby interpretters on the host system we recommend installing rbenv and ruby-build.

rbenv is a simple, lightweight utility that handles the automatic selection of Ruby interpretters on a per project basis. ruby-build is a plugin for rbenv that handles the compilation and installation of Ruby interpretters from source code. Warning: If you already have another version manager (e.g. RVM), it is recommended you uninstall it and rely solely on rbenv to avoid conflicts.

Bundler

Bundler is a dependency manager for Ruby applications. It ensures that an exact runtime environment is configured for an application when executed. Bundler manages the Ruby gems that provide much of the testing infrastructure for this project and ensures that all developers are working with the same version of CocoaPods.

CocoaPods

CocoaPods is a dependency manager for Objective-C applications. Much like Bundler, it resolves and configures dependencies for libraries that are used by an application. It is used to install all dependencies necessary to build this project and its test suite.

Updating File and Process Limits

The LayerKit test suite commonly exceeds the maximum number of open file descriptors available in stock installs of Mac OS X. It is strongly recommended that you update the maximum process and file ulimit’s by following the instructions from the Riak Tuning Docs.

Maintaining Project Dependencies

Keeping a project up to date is a straightforward task. You only need to ensure that Homebrew, Bundler, and CocoaPods are tracking the latest revisions. Keep an eye out for changes to the Gemfile and Podfile when changing branches or pulling updates – they indicate that Bundler and CocoaPods dependencies, respectively, are out of date and need to be updated.

You can refresh all dependencies using the Rake task: $ rbenv install -s && rake init

Alternately, you can refresh the RubyGems via $ bundle install and the CocoaPods via bundle exec pod install.

Testing

LayerKit contains an extensive test suite covering all parts of the software. Testing is crucial for driving robust designs and maintaining confidence in a rapidly changing codebase. Please take the time to learn the testing tools available and integrate them into your workflow.

LayerKit testing is implemented on top of the XCTest, which is distributed with Xcode, and several third-party Open Source libraries that provide additional functionality such as mock objects and expressive test assertions:

  • OCMock - OCMock is an Objective-C implementation of mock objects.
  • Expecta - Expecta is library of matchers and test assertions that do not require you to specify data types, resulting in much more succinct and expressive test code.

Running Tests

LayerKit test files are organized according to the specific type of functiality that each file tests. Each test group cooresponds with an XCode target and scheme, which allows test groups to be run independently. Tests can be run from within XCode or via the command line (LayerKit uses the XCTasks command line tool). The test buckets, descriptions and associated rake commands can be found in the table below.

Type Description Rake Command
Unit Tests should exercise discrete units of functionality and should not involve network calls. rake test:unit
Functional Tests should exercise individual operations or APIs that may invovled network calls or other classes. rake test:functional
Integration Tests should exercise significant buckets of functionality that may span multiple areas of the SDK. rake test:integration
Public API Tests should exercise Public API, public Model objects and their associated behaviors. rake test:public
Feature Tests should exercise significant LayerKit features which invovle coordination across the SDK. rake test:feature
Application Tests should exercise Layerkit functionality through an actual user interface rake test:application

Additionally, the entire suite can be run from the commandline in aggregate by executing rake test.

The tests tasks are configured to log the full details of the underlying xcodebuild activities to xcodebuild.log.

Configuring Test Environment

The LayerKit test suite is set up to allow configuration of key testing setting via environment variables. This enables the easy, automated testing of the client against different backend configurations. The table below enumerates the settings available:

Environment Variable Default Value Purpose
LAYER_TEST_HOST @"localhost" Configures the remote TMC host to test against
LAYER_LOG_LEVEL @"ERROR" Configures the logging level (OFF, ERROR, WARN, INFO, DEBUG, VERBOSE)

These variables can be configured directly within Xcode or provided via the Rake command line automation:

$ rake test LAYER_TEST_HOST=smoke1.layer.com

Continuous Integration

The project is under continuous integration via Jenkins. You can visit the LayerKit Jenkins job: here.

Logging

LayerKit includes a robust logging framework built on top of CocoaLumberjack.

Log Levels

There are six log level defined:

  1. LOG_LEVEL_OFF - Disable all LayerKit logging.
  2. LOG_LEVEL_ERROR - Log only unexpected, non-recoverable runtime errors.
  3. LOG_LEVEL_WARN - Log all errors including transient, potentially recoverable runtime and warnings.
  4. LOG_LEVEL_INFO - Log informational runtime information.
  5. LOG_LEVEL_DEBUG - Log additional debugging information.
  6. LOG_LEVEL_VERBOSE - Log detailed runtime information.

Log Configuration

Logging can be configured at build time or at run time. The default log level is configured at build time based on the value of the LYR_LOG_LEVEL preprocessor definition. When undefined, it defaults to a value of LOG_LEVEL_ERROR.

Logging can also be configured via an environment variable if an invocation is made to the LYRSetLogLevelFromEnvironment function (see Code/Private/Support/Logging/LYRLog.h). This function configures the log level based on the value of the LAYER_LOG_LEVEL environment variable. Acceptable values are: OFF, ERROR, WARN, INFO, DEBUG, and VERBOSE.

Logging Utilities

The LYRLog.h file includes some additional log related utilities that can be valuable during debugging. Examples include:

  • LYRLogWithLevelWhileExecutingBlock - Temporarily sets the log level during the execution of a block, restoring its previous value upon exit.
  • LYRLogSilenceWhileExecutingBlock - Silences all logging during the execution of a block.

The CocoaSPDY library has also been integrated with the logging environment.

Generating Documentation

Documentation can be generated from appledoc via Rake: rake docs.

Creating Releases

LayerKit releases can be built and deployed using Rake tasks. These tasks automate the creation and publication of an iOS Framework and Podspec for CocoaPods.

The source of truth for all releases is the LayerKit.podspec file that resides in the root of the project directory. This podspec compiles the LayerKit source code and is used to build binaries of the framework for public distribution. There are three tasks for managing releases:

  1. rake version:set - Creates a new version of the framework. Requires the VERSION environment variable be inputted.
  2. rake release:build - Builds a release candidate for internal testing and QA.
  3. rake release:push - Publishes a release

Setting Release Version

The rake version:set VERSION=[VERSION] task will set LayerKit to a new version. It will update LayerKit.podspec and Code/LayerKit.m to the new version to ensure that all build artifacts track the same version.

Building a Release

The rake release:build task takes the source podspec and uses the CocoaPods packager plugin to compile a fat, statically linked binary version of LayerKit. The build process makes use of symbol aliasing to ensure that all LayerKit dependencies are isolated and will not conflict with overlapping libraries in the target application.

Release packages are populated into the Releases directory when built.

If your Layer Dropbox directory is located elsewhere (not in ~/Dropbox (Layer)), use this environmental variable to specify it: rake release:build LAYER_DROPBOX_PATH=~/Dropbox/Layer/Builds/iOS

Pushing a Release

Once a release package has been built and validated locally, it must be pushed into the release repositories for distribution. This is accomplished by invoking the rake release:push task.

This task will push the built framework and podspec to the releases-ios and releases-cocoapods repositories for consumption by external developers.

Note that that release:push will invoke the publish_github_release task after completing the release. This task will publish a Github release to http://github.com/layerhq/releases-ios that includes the release notes. In order for this task to succeed you must create a personal Github Access Token and export it via the GITHUB_TOKEN environment variable.

Please take care to ensure that all accompanying documentation and sample code is also made available when you push a release.

Manual Release Checklist

Building a Pre-release

  • [ ] Make sure all tests are green.
  • [ ] Verify that the release doesn’t break any APIs compared to the previous release.
  • [ ] Add the new (pre-release) version section into CHANGELOG.md.
  • [ ] And make sure all changes made are documented in the CHANGELOG.md.
  • [ ] Set the version accordingly (for example: rake version:set VERSION=0.15.1)
    • [ ] If there are any breaking API changes, bump the 0.x.0 version,
    • [ ] If the release is a patch, performance update any other minor update, bump the 0.0.x version.
  • [ ] If you’re building it off a branch, make sure your LayerKit.podspec grabs the sources from that branch, eg.: s.source = { :git => 'git@github.com:layerhq/LayerKit.git', branch: 'feature/APPS-2233-PartiallyPartialSync' }
  • [ ] Build and make it available for our internal usage (rake release:build LAYER_DROPBOX_PATH=/Users/chipxsd/Dropbox/Layer)

Pushing a release

  • [ ] Make sure all tests are green.
  • [ ] Verify that the release doesn’t break any APIs compared to the previous release.
  • [ ] Make sure all changes made are documented in the CHANGELOG.md.
    • [ ] Most importantly, make sure to update the version
      • [ ] It should be higher than the previous version.
      • [ ] It should not include any pre-release or other suffixes.
    • [ ] Breaking API changes should be documented with: deprecatedMethod: deprecated, use newMethod: instead.
  • [ ] Set the version accordingly (for example: rake version:set VERSION=0.15.1)
    • [ ] If there were any breaking API changes, bump the 0.x.0 version,
    • [ ] If the release is a patch, performance update any other minor update, bump the 0.0.x version.
  • [ ] Make sure LayerKit.podspec doesn’t pull sources from any other branch but master s.source = ....
  • [ ] Build and make it available on public CocoaPods repo (rake release:push LAYER_DROPBOX_PATH=/Users/chipxsd/Dropbox/Layer)

Contact

LayerKit was developed in San Francisco by the Layer team. If you have any technical questions or concerns about this project feel free to reach out to engineers responsible for the development:

License

This project contains source code that is the exclusive intellectual property of Layer, Inc. It is intended for distribution in binary form as a proprietary product of Layer. This source code is to be accessed only by employees or authorized contractors of Layer.