cinder 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|