cinder 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +79 -0
- data/Rakefile +10 -0
- data/bin/cinder +19 -0
- data/cinder.gemspec +34 -0
- data/lib/cinder.rb +5 -0
- data/lib/cinder/commands.rb +13 -0
- data/lib/cinder/commands/build.rb +69 -0
- data/lib/cinder/commands/lint.rb +284 -0
- data/lib/cinder/commands/setup.rb +32 -0
- data/lib/cinder/external_commands.rb +43 -0
- data/lib/cinder/version.rb +3 -0
- data/spec/cinder/commands/lint_spec.rb +121 -0
- data/spec/cinder_spec.rb +9 -0
- data/spec/fake_workspace.rb +23 -0
- data/spec/fixtures/both-configs-sans-ad_hoc-provisioning/Foo.xcodeproj/project.pbxproj +329 -0
- data/spec/fixtures/both-configs-sans-ad_hoc-provisioning/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/both-configs-sans-ad_hoc-provisioning/Podfile +3 -0
- data/spec/fixtures/both-configs-sans-ad_hoc-provisioning/enterprise.mobileprovision +0 -0
- data/spec/fixtures/both-configs-sans-enterprise-provisioning/Foo.xcodeproj/project.pbxproj +329 -0
- data/spec/fixtures/both-configs-sans-enterprise-provisioning/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/both-configs-sans-enterprise-provisioning/Podfile +3 -0
- data/spec/fixtures/both-configs-sans-enterprise-provisioning/ad_hoc.mobileprovision +0 -0
- data/spec/fixtures/both-configs/Foo.xcodeproj/project.pbxproj +329 -0
- data/spec/fixtures/both-configs/Foo.xcodeproj/xcshareddata/xcschemes/Foo.xcscheme +0 -0
- data/spec/fixtures/both-configs/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/both-configs/Podfile +3 -0
- data/spec/fixtures/both-configs/ad_hoc.mobileprovision +0 -0
- data/spec/fixtures/both-configs/enterprise.mobileprovision +0 -0
- data/spec/fixtures/empty-podfile/Foo.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/empty-podfile/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/empty-podfile/Podfile +0 -0
- data/spec/fixtures/lowercase/Podfile +3 -0
- data/spec/fixtures/lowercase/ad_hoc.mobileprovision +0 -0
- data/spec/fixtures/lowercase/foo.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/lowercase/foo.xcodeproj/xcshareddata/xcschemes/foo.xcscheme +0 -0
- data/spec/fixtures/lowercase/foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/multiple-workspaces/Bar.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/multiple-workspaces/Foo.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/multiple-workspaces/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/multiple-workspaces/Podfile +3 -0
- data/spec/fixtures/proper/Foo.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/proper/Foo.xcodeproj/xcshareddata/xcschemes/Foo.xcscheme +0 -0
- data/spec/fixtures/proper/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/proper/Podfile +3 -0
- data/spec/fixtures/proper/ad_hoc.mobileprovision +0 -0
- data/spec/fixtures/sans-ad_hoc-or-enterprise-config/Foo.xcodeproj/project.pbxproj +309 -0
- data/spec/fixtures/sans-ad_hoc-or-enterprise-config/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/sans-ad_hoc-or-enterprise-config/Podfile +3 -0
- data/spec/fixtures/sans-podfile/Foo.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/sans-podfile/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/sans-project/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/sans-project/Podfile +3 -0
- data/spec/fixtures/sans-target/Foo.xcodeproj/project.pbxproj +113 -0
- data/spec/fixtures/sans-target/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/sans-target/Podfile +3 -0
- data/spec/fixtures/sans-workspace/Foo.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/sans-workspace/Podfile +3 -0
- data/spec/fixtures/whitespace-workspace/Foo App.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/whitespace-workspace/Foo App.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/whitespace-workspace/Podfile +3 -0
- data/spec/fixtures/workspace-project-mismatch/Bar.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/workspace-project-mismatch/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/workspace-project-mismatch/Podfile +3 -0
- data/spec/fixtures/workspace-projects-mismatch/Bar.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/workspace-projects-mismatch/Baz.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/workspace-projects-mismatch/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/workspace-projects-mismatch/Podfile +3 -0
- data/spec/fixtures/workspace-target-mismatch/Foo.xcodeproj/project.pbxproj +319 -0
- data/spec/fixtures/workspace-target-mismatch/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/workspace-target-mismatch/Podfile +3 -0
- data/spec/fixtures/workspace-targets-mismatch/Foo.xcodeproj/project.pbxproj +504 -0
- data/spec/fixtures/workspace-targets-mismatch/Foo.xcworkspace/contents.xcworkspacedata +1 -0
- data/spec/fixtures/workspace-targets-mismatch/Podfile +3 -0
- data/spec/spec_helper.rb +20 -0
- metadata +363 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Detroit Labs
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Cinder
|
|
2
|
+
========
|
|
3
|
+
|
|
4
|
+
Continuous Delivery for iOS Apps.
|
|
5
|
+
|
|
6
|
+
Cinder favors convention over configuration. Of the myriad ways to organize an iOS project, Cinder supports a subset that still gets the job done.
|
|
7
|
+
|
|
8
|
+
<a href="http://www.flickr.com/photos/cindy_cinder/6063851300/" title="Flaming Butterfly and Flowers by Cinder's, on Flickr"><img src="http://farm7.staticflickr.com/6204/6063851300_6c81f2360d.jpg" width="150" height="200" alt="Flaming Butterfly and Flowers"></a>
|
|
9
|
+
|
|
10
|
+
iOS projects using Cinder:
|
|
11
|
+
|
|
12
|
+
* have developers up and running after running a single command following `git clone`
|
|
13
|
+
* build your app's ipa whenever code is pushed to your GitHub repo
|
|
14
|
+
* publish successful builds of `master` as versions in [TestFlight](https://testflightapp.com/). <span style="color:#999">The "What's New" shows changes since the last build and since the last release.</span>
|
|
15
|
+
* <span style="color:#999">publish successful builds of *topic branches* as comments in the pull request with links to install a version of the app with that feature</span>
|
|
16
|
+
* <span style="color:#999">comment and close pull requests of failed builds</span>
|
|
17
|
+
|
|
18
|
+
Items in <span style="color:#999">de-emphasized text</span> are still being developed.
|
|
19
|
+
|
|
20
|
+
Usage
|
|
21
|
+
-----
|
|
22
|
+
|
|
23
|
+
* `script/setup` - run this after cloning a project and you're ready to develop in Xcode. Re-run it whenever project dependencies have changed.
|
|
24
|
+
|
|
25
|
+
* `script/cibuild` - is the two-liner shell script which tells the [Janky continuous integration server](https://github.com/github/janky) how to build Ad Hoc or Enterprise versions of your app:
|
|
26
|
+
|
|
27
|
+
#!/bin/bash
|
|
28
|
+
exec "$(dirname $0)/build" --configuration Enterprise
|
|
29
|
+
|
|
30
|
+
* `bin/cinder lint` - will guide you through setting up a new project or modifying an existing project to work with Cinder.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
New Project Quickstart
|
|
34
|
+
----------------------
|
|
35
|
+
|
|
36
|
+
In the top-level of your project run:
|
|
37
|
+
|
|
38
|
+
ruby -e "$(curl -fsSL https://raw.github.com/detroit-labs/cinder/hot)"
|
|
39
|
+
|
|
40
|
+
This is a one-time initialization that only one developer must complete. Afterwards, anyone cloning the project can simply run:
|
|
41
|
+
|
|
42
|
+
script/setup
|
|
43
|
+
|
|
44
|
+
Installation for Existing Projects
|
|
45
|
+
----------------------------------
|
|
46
|
+
|
|
47
|
+
If your project doesn't use Bundler (e.g. doesn't have a `Gemfile`) then follow **New Project Quickstart** above.
|
|
48
|
+
|
|
49
|
+
Add this line to the top of `Gemfile`:
|
|
50
|
+
|
|
51
|
+
source 'https://raw.github.com/detroit-labs/erebor/gems/'
|
|
52
|
+
|
|
53
|
+
Add this line to the gems portion of `Gemfile`:
|
|
54
|
+
|
|
55
|
+
gem 'cinder'
|
|
56
|
+
|
|
57
|
+
Cinder expects that you use binstubs with Bundler and vendor gems under `.bundle`. Run:
|
|
58
|
+
|
|
59
|
+
bundle install --binstubs --path .bundle
|
|
60
|
+
git add .bundle/config
|
|
61
|
+
echo '/.bundle/ruby/' >>.gitignore
|
|
62
|
+
|
|
63
|
+
Henceforth `bundle install` without flags is sufficient.
|
|
64
|
+
|
|
65
|
+
The `cinder` CLI should now be available under `bin/`. It will guide you the rest of the way:
|
|
66
|
+
|
|
67
|
+
bin/cinder lint
|
|
68
|
+
# 10 fix errors
|
|
69
|
+
# 20 bin/cinder lint
|
|
70
|
+
# 30 if errors GOTO 10
|
|
71
|
+
|
|
72
|
+
Cinder is Opinionated
|
|
73
|
+
-----------------------
|
|
74
|
+
|
|
75
|
+
* Ruby is an integral part of the development, build, and
|
|
76
|
+
release toolchain for native iOS apps
|
|
77
|
+
* Ruby versions are managed by [rbenv](https://github.com/sstephenson/rbenv)
|
|
78
|
+
* Automation always wins
|
|
79
|
+
* Convention over configuration; when there's more than one way to do it, pick one
|
data/Rakefile
ADDED
data/bin/cinder
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'commander/import'
|
|
4
|
+
|
|
5
|
+
$:.push File.expand_path("../../lib", __FILE__)
|
|
6
|
+
require 'cinder'
|
|
7
|
+
|
|
8
|
+
HighLine.track_eof = false # Fix for built-in Ruby
|
|
9
|
+
|
|
10
|
+
program :version, Cinder::VERSION
|
|
11
|
+
program :description, 'Continuous Delivery for iOS Apps'
|
|
12
|
+
|
|
13
|
+
program :help, 'Author', 'Tim Taylor <tim@detroitlabs.com>'
|
|
14
|
+
program :help, 'Website', 'https://github.com/detroit-labs/cinder'
|
|
15
|
+
program :help_formatter, :compact
|
|
16
|
+
|
|
17
|
+
default_command :help
|
|
18
|
+
|
|
19
|
+
require 'cinder/commands'
|
data/cinder.gemspec
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$:.unshift(lib) unless $:.include?(lib)
|
|
4
|
+
|
|
5
|
+
require 'cinder/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |s|
|
|
8
|
+
s.name = "cinder"
|
|
9
|
+
s.version = Cinder::VERSION
|
|
10
|
+
s.authors = ["Tim Taylor"]
|
|
11
|
+
s.email = ["tim@detroitlabs.com"]
|
|
12
|
+
s.description = "Continuous Delivery for iOS Apps"
|
|
13
|
+
s.summary = "Cinder"
|
|
14
|
+
s.homepage = "https://github.com/detroit-labs/cinder"
|
|
15
|
+
s.license = "MIT"
|
|
16
|
+
|
|
17
|
+
s.add_dependency "commander", "~> 4.1.3"
|
|
18
|
+
s.add_dependency "extlib", "~> 0.9.16"
|
|
19
|
+
s.add_dependency "plist", "~> 3.1.0"
|
|
20
|
+
s.add_dependency "dotenv", "~> 0.5.0"
|
|
21
|
+
s.add_dependency "cocoapods", "~> 0.19.0"
|
|
22
|
+
s.add_dependency "shenzhen", "~> 0.3.0"
|
|
23
|
+
s.add_dependency "rugged", "~> 0.17.0.b8"
|
|
24
|
+
|
|
25
|
+
s.add_development_dependency "bundler", "~> 1.3"
|
|
26
|
+
s.add_development_dependency "rake"
|
|
27
|
+
s.add_development_dependency 'rspec'
|
|
28
|
+
s.add_development_dependency 'fakefs', '~> 0.3.2'
|
|
29
|
+
|
|
30
|
+
s.files = `git ls-files`.split($/)
|
|
31
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
32
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
|
33
|
+
s.require_paths = ["lib"]
|
|
34
|
+
end
|
data/lib/cinder.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
$:.push File.expand_path('../', __FILE__)
|
|
2
|
+
|
|
3
|
+
require 'dotenv'
|
|
4
|
+
Dotenv.load File.join(ENV['JENKINS_HOME'] || '.', '.env.ios')
|
|
5
|
+
|
|
6
|
+
require 'extlib/string'
|
|
7
|
+
require 'openssl'
|
|
8
|
+
require 'plist'
|
|
9
|
+
require 'external_commands'
|
|
10
|
+
|
|
11
|
+
require 'commands/build'
|
|
12
|
+
require 'commands/setup'
|
|
13
|
+
require 'commands/lint'
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Cinder
|
|
2
|
+
command :build do |c|
|
|
3
|
+
c.syntax = 'cinder build [OPTIONS]'
|
|
4
|
+
c.summary = 'Build & publish a release.'
|
|
5
|
+
c.description = 'Build a release. For AdHoc or Enterprise builds against `master`, publish to TestFlight.'
|
|
6
|
+
|
|
7
|
+
c.option '-c', '--configuration CONFIGURATION', 'Configuration used to build. One of AdHoc, AppStore, or Enterprise. Defaults to AdHoc.'
|
|
8
|
+
|
|
9
|
+
c.action do |args, options|
|
|
10
|
+
options.default :configuration => 'AdHoc'
|
|
11
|
+
|
|
12
|
+
name = determine_workspace
|
|
13
|
+
use_keychain options.configuration.snake_case
|
|
14
|
+
set_bundle_version name
|
|
15
|
+
build_ipa options.configuration, name
|
|
16
|
+
publish_ipa name
|
|
17
|
+
say_ok "Did all the things for #{name}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def self.determine_workspace
|
|
23
|
+
workspaces = Dir["*.xcworkspace"].grep(/^(.*)\.xcworkspace$/){$1}
|
|
24
|
+
say_error "No Xcode workspace found" and abort if workspaces.empty?
|
|
25
|
+
say_error "There can be only one workspace" and abort if workspaces.length > 1
|
|
26
|
+
workspaces.first
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.use_keychain keychain_name
|
|
30
|
+
keychain_file = "#{ENV['HOME']}/Library/Keychains/#{keychain_name}.keychain"
|
|
31
|
+
if File.exists?(keychain_file)
|
|
32
|
+
args = [
|
|
33
|
+
keychain_file,
|
|
34
|
+
"/Library/Keychains/System.keychain"
|
|
35
|
+
]
|
|
36
|
+
log 'security', "Using #{keychain_name}.keychain"
|
|
37
|
+
# TODO: restore original keychains
|
|
38
|
+
security! "list-keychains -s #{args.join(" ")}"
|
|
39
|
+
security! %{default-keychain -s "#{keychain_file}"}
|
|
40
|
+
security! %{unlock-keychain -p "#{ENV['CI_KEYCHAIN_PASSWORD']}" "#{keychain_file}"}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.set_bundle_version name
|
|
45
|
+
short_sha1 = git!("rev-parse --short #{ENV['JANKY_SHA1'] || 'HEAD'}").strip
|
|
46
|
+
log 'plist', "Setting CFBundleVersion to `#{short_sha1}'"
|
|
47
|
+
PlistBuddy! %{-c "Set :CFBundleVersion #{short_sha1}" "#{name}/#{name}-Info.plist"}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.build_ipa configuration, name
|
|
51
|
+
log 'ipa', "Building an ipa with configuration `#{configuration}' and scheme `#{name}'"
|
|
52
|
+
ipa! "build --trace -c #{configuration} -s #{name}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.publish_ipa name
|
|
56
|
+
master_sha1 = git!('rev-parse origin/master').strip
|
|
57
|
+
if master_sha1 == ENV['JANKY_SHA1']
|
|
58
|
+
team_token = ENV['CI_TESTFLIGHT_TEAM_TOKEN']
|
|
59
|
+
api_token = ENV['CI_TESTFLIGHT_API_TOKEN']
|
|
60
|
+
build_number = ENV['JANKY_ID']
|
|
61
|
+
build_output_url = "#{ENV['CI_JANKY_BASE_URL']}#{build_number}/output"
|
|
62
|
+
notes = "Automated Build ##{build_number}\n#{build_output_url}"
|
|
63
|
+
|
|
64
|
+
log 'ipa', 'Distribute ipa to TestFlight'
|
|
65
|
+
ipa! %{distribute:testflight --team_token #{team_token} --api_token #{api_token} --replace --lists "#{name}" --notify --notes "#{notes}"}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
require 'xcodeproj'
|
|
3
|
+
require 'extlib'
|
|
4
|
+
require 'cocoapods-core'
|
|
5
|
+
require 'rugged'
|
|
6
|
+
|
|
7
|
+
module Cinder
|
|
8
|
+
|
|
9
|
+
class ProvisioningProfile
|
|
10
|
+
attr_reader :distribution
|
|
11
|
+
attr_reader :devices
|
|
12
|
+
|
|
13
|
+
def self.from_file f
|
|
14
|
+
begin
|
|
15
|
+
p7 = OpenSSL::PKCS7.new(File.read(f))
|
|
16
|
+
p7.verify([], OpenSSL::X509::Store.new)
|
|
17
|
+
new Plist::parse_xml(p7.data)
|
|
18
|
+
rescue
|
|
19
|
+
new
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize profile = nil
|
|
24
|
+
if profile
|
|
25
|
+
@valid = true
|
|
26
|
+
@devices = []
|
|
27
|
+
if profile['ProvisionsAllDevices']
|
|
28
|
+
@distribution = :enterprise
|
|
29
|
+
elsif profile['ProvisionedDevices']
|
|
30
|
+
@distribution = :ad_hoc
|
|
31
|
+
@devices = profile['ProvisionedDevices']
|
|
32
|
+
else
|
|
33
|
+
# FIXME: could be a developer provisioning profile; need a better check
|
|
34
|
+
@distribution = :app_store
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
@valid = false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def valid?
|
|
42
|
+
@valid == true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ad_hoc?
|
|
46
|
+
@distribution == :ad_hoc
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class Linter
|
|
51
|
+
DISTRIBUTION_METHODS = {
|
|
52
|
+
:ad_hoc => 'AdHoc',
|
|
53
|
+
:app_store => 'AppStore',
|
|
54
|
+
:enterprise => 'Enterprise'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def lint options, args
|
|
58
|
+
abort unless acc = _lint(detect_repository({}))
|
|
59
|
+
say <<-EOS.margin if `git ls-files .cinder`.split($/).empty?
|
|
60
|
+
|
|
61
|
+
Additional Steps
|
|
62
|
+
================
|
|
63
|
+
|
|
64
|
+
1. Add ‘deckard’ group to #{acc[:repo_company]}/#{acc[:repo_name]} in GitHub
|
|
65
|
+
|
|
66
|
+
2. Tell Hubot to set it up in Janky
|
|
67
|
+
|
|
68
|
+
@bot ci setup #{acc[:repo_company]}/#{acc[:repo_name]}
|
|
69
|
+
@bot ci set room #{acc[:repo_name]} #{acc[:name]}
|
|
70
|
+
|
|
71
|
+
3. Create a distribution list named ‘#{acc[:name]}’ in TestFlight
|
|
72
|
+
|
|
73
|
+
4. Create `setup` and `build` scripts
|
|
74
|
+
|
|
75
|
+
mkdir -p script
|
|
76
|
+
curl -L http://git.io/dqT6SA >script/.cinder-exec
|
|
77
|
+
( cd script ; \\
|
|
78
|
+
chmod +x .cinder-exec ; \\
|
|
79
|
+
ln -s .cinder-exec setup ; \\
|
|
80
|
+
ln -s .cinder-exec build )
|
|
81
|
+
|
|
82
|
+
5. Create `script/cibuild`
|
|
83
|
+
|
|
84
|
+
#!/bin/bash
|
|
85
|
+
exec "$(dirname $0)/build" --configuration #{acc[:build_configs].keys.grep(/ad_hoc|enterprise/).sort.first.to_s.camel_case}
|
|
86
|
+
|
|
87
|
+
6. Add the following to `.gitignore`
|
|
88
|
+
|
|
89
|
+
/.bundle/ruby/
|
|
90
|
+
/Pods/
|
|
91
|
+
/bin/
|
|
92
|
+
|
|
93
|
+
7. Add and commit a `.cinder` file to repository to turn off this message
|
|
94
|
+
|
|
95
|
+
EOS
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def _lint acc = {}
|
|
99
|
+
acc &&= detect_projects acc
|
|
100
|
+
acc &&= detect_workspaces acc
|
|
101
|
+
acc &&= determine_name acc
|
|
102
|
+
acc &&= check_podfile acc
|
|
103
|
+
acc &&= check_workspace acc
|
|
104
|
+
acc &&= check_single_project acc
|
|
105
|
+
acc &&= check_multiple_projects acc
|
|
106
|
+
acc &&= load_project acc
|
|
107
|
+
acc &&= detect_targets acc
|
|
108
|
+
acc &&= check_single_target acc
|
|
109
|
+
acc &&= check_multiple_targets acc
|
|
110
|
+
acc &&= detect_build_configs acc
|
|
111
|
+
acc &&= check_app_store_config acc
|
|
112
|
+
acc &&= check_testing_configs acc
|
|
113
|
+
acc &&= detect_provisioning_profiles acc
|
|
114
|
+
acc &&= check_provisioning_profiles acc
|
|
115
|
+
acc &&= check_scheme acc
|
|
116
|
+
|
|
117
|
+
say_ok 'OK to go' if acc
|
|
118
|
+
acc
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def detect_repository acc
|
|
124
|
+
begin
|
|
125
|
+
path = Rugged::Repository.discover
|
|
126
|
+
rescue
|
|
127
|
+
say_error "Must be in a git repository" and return nil
|
|
128
|
+
end
|
|
129
|
+
acc[:repo] = repo = Rugged::Repository.new path
|
|
130
|
+
acc[:root] = root = File.expand_path '..', repo.path
|
|
131
|
+
|
|
132
|
+
say_error "Must be at the root of the project at `#{root}'" and return nil unless root == Dir.getwd
|
|
133
|
+
upstream = repo.remotes.grep(/upstream/).first
|
|
134
|
+
upstream ||= repo.remotes.grep(/origin/).first
|
|
135
|
+
upstream &&= Rugged::Remote.lookup repo, upstream
|
|
136
|
+
if upstream.url =~ %r{github.com[:/]([\w-]+)/([\w-]+).git}
|
|
137
|
+
acc[:repo_company], acc[:repo_name] = $1, $2
|
|
138
|
+
end
|
|
139
|
+
say_error "Must have `upstream' or `origin' remote on GitHub" and return nil unless acc[:repo_name]
|
|
140
|
+
acc
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def detect_projects acc
|
|
144
|
+
acc[:projects] = projects = Dir["*.xcodeproj"].map {|f| File.basename(f)}.grep(/^(.*)\.xcodeproj$/){$1}
|
|
145
|
+
say_error "No Xcode project found" and return nil if projects.empty?
|
|
146
|
+
acc
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def detect_workspaces acc
|
|
150
|
+
acc[:workspaces] = Dir["*.xcworkspace"].map {|f| File.basename(f)}.grep(/^(.*)\.xcworkspace$/){$1}
|
|
151
|
+
acc
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def determine_name acc
|
|
155
|
+
if acc[:workspaces].length == 1
|
|
156
|
+
acc[:name] = acc[:workspaces].first
|
|
157
|
+
elsif acc[:projects].length == 1
|
|
158
|
+
acc[:name] = acc[:projects].first
|
|
159
|
+
end
|
|
160
|
+
acc
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def check_podfile acc
|
|
164
|
+
result = acc
|
|
165
|
+
file = Dir['Podfile'].first
|
|
166
|
+
say_error "No CocoaPods Podfile found" and return nil unless file
|
|
167
|
+
podfile = Pod::Podfile.from_file file
|
|
168
|
+
target = podfile.root_target_definitions.first
|
|
169
|
+
platform = target.platform.name if target.platform
|
|
170
|
+
say_error 'CocoaPods platform must be iOS' and result = nil unless platform == :ios
|
|
171
|
+
say_error 'Must have at least one CocoaPods dependency' and result = nil if target.dependencies.empty?
|
|
172
|
+
result
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def check_workspace acc
|
|
176
|
+
workspaces = Dir["*.xcworkspace"].map {|f| File.basename(f)}.grep(/^(.*)\.xcworkspace$/){$1}
|
|
177
|
+
say_error "No Xcode workspace found" and return nil if workspaces.empty?
|
|
178
|
+
say_error "There can be only one Xcode workspace" and return nil if workspaces.length > 1
|
|
179
|
+
acc[:workspace] = workspace = workspaces.first
|
|
180
|
+
say_error "Workspace name `#{workspace}' must not contain whitespace" and return nil if workspace =~ /\s/
|
|
181
|
+
say_warning "Workspace name `#{workspace}' should be CamelCase" unless workspace =~ /^[[:upper:]]\S*(?:[[:upper:]]\s*)*$/
|
|
182
|
+
acc
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def check_single_project acc
|
|
186
|
+
return acc if acc[:projects].length > 1
|
|
187
|
+
|
|
188
|
+
acc[:project] = acc[:projects].find { |p| p == acc[:workspace] }
|
|
189
|
+
say_error "Xcode project name `#{acc[:projects].first}' must match workspace name `#{acc[:workspace]}'" and return nil unless acc[:project]
|
|
190
|
+
acc
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def check_multiple_projects acc
|
|
194
|
+
return acc if acc[:projects].length == 1
|
|
195
|
+
|
|
196
|
+
acc[:project] = acc[:projects].find { |p| p == acc[:workspace] }
|
|
197
|
+
say_error "One Xcode project name must match workspace name `#{acc[:workspace]}'" and return nil unless acc[:project]
|
|
198
|
+
acc
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def load_project acc
|
|
202
|
+
acc[:xcodeproj] = Xcodeproj::Project.new "#{acc[:project]}.xcodeproj"
|
|
203
|
+
acc
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def detect_targets acc
|
|
207
|
+
acc[:targets] = acc[:xcodeproj].targets
|
|
208
|
+
say_error "Xcode project must have at least one target" and return nil if acc[:targets].empty?
|
|
209
|
+
acc
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def check_single_target acc
|
|
213
|
+
return acc if acc[:targets].length > 1
|
|
214
|
+
|
|
215
|
+
acc[:target] = target = acc[:targets].first
|
|
216
|
+
say_error "Target name `#{target.name}' must match workspace name `#{acc[:workspace]}'" and return nil unless target.name == acc[:workspace]
|
|
217
|
+
acc
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def check_multiple_targets acc
|
|
221
|
+
return acc if acc[:targets].length == 1
|
|
222
|
+
|
|
223
|
+
acc[:target] = acc[:targets].find { |t| t.name == acc[:workspace] }
|
|
224
|
+
say_error "One Xcode target name must match workspace name `#{acc[:workspace]}'" and return nil unless acc[:target]
|
|
225
|
+
acc
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def detect_build_configs acc
|
|
229
|
+
a = acc[:xcodeproj].build_configurations
|
|
230
|
+
acc[:build_configs] = a.find_all { |c| c.name =~ /^(?:AdHoc|AppStore|Enterprise)$/ }.each_with_object({}) { |c,h| h[c.name.snake_case.to_sym] = c }
|
|
231
|
+
acc
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def check_testing_configs acc
|
|
235
|
+
configs = acc[:build_configs].find_all { |k,v| k == :ad_hoc || k == :enterprise }
|
|
236
|
+
say_error "Must have `AdHoc', `Enterprise', or both build configurations" and return nil if configs.empty?
|
|
237
|
+
acc
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def check_app_store_config acc
|
|
241
|
+
say_warning "Should have `AppStore' build configuration" unless acc[:build_configs][:app_store]
|
|
242
|
+
acc
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def detect_provisioning_profiles acc
|
|
246
|
+
a = Dir['*.mobileprovision'].grep(/^(ad_hoc|app_store|enterprise)\./){$1.to_sym}
|
|
247
|
+
acc[:provisioning_profiles] = a.each_with_object({}) { |p,h| h[p] = "#{p}.mobileprovision" }
|
|
248
|
+
acc
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def check_provisioning_profiles acc
|
|
252
|
+
result = acc
|
|
253
|
+
|
|
254
|
+
acc[:build_configs].each do |name, config|
|
|
255
|
+
file = acc[:provisioning_profiles][name]
|
|
256
|
+
if file
|
|
257
|
+
profile = ProvisioningProfile.from_file file
|
|
258
|
+
if profile.valid?
|
|
259
|
+
say_error "`#{file}' appears to be an #{DISTRIBUTION_METHODS[profile.distribution]} provisioning profile" and result = nil unless name == profile.distribution
|
|
260
|
+
say_error "AdHoc provisioning must have at least 1 device provisioned" and result = nil if profile.ad_hoc? && profile.devices.empty?
|
|
261
|
+
else
|
|
262
|
+
say_error "Invalid provisioning profile `#{file}'" and result = nil
|
|
263
|
+
end
|
|
264
|
+
else
|
|
265
|
+
say_error "`#{config.name}' build configuration must have an `#{name}.mobileprovision' in project root" and result = nil
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
result
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def check_scheme acc
|
|
272
|
+
schemes = Dir["#{acc[:name]}.xcodeproj/xcshareddata/xcschemes/#{acc[:name]}.xcscheme"].grep(/^(.*)\.xcscheme$/){$1}
|
|
273
|
+
say_error "Must have a shared Xcode scheme named `#{acc[:name]}'" and return nil if schemes.empty?
|
|
274
|
+
acc
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
command :lint do |c|
|
|
279
|
+
c.syntax = 'cinder lint'
|
|
280
|
+
c.summary = 'Check for CI problems'
|
|
281
|
+
c.description = 'Report if your project is missing or deviating from Cinder conventions.'
|
|
282
|
+
c.action Linter, :lint
|
|
283
|
+
end
|
|
284
|
+
end
|