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.
Files changed (78) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +5 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +79 -0
  5. data/Rakefile +10 -0
  6. data/bin/cinder +19 -0
  7. data/cinder.gemspec +34 -0
  8. data/lib/cinder.rb +5 -0
  9. data/lib/cinder/commands.rb +13 -0
  10. data/lib/cinder/commands/build.rb +69 -0
  11. data/lib/cinder/commands/lint.rb +284 -0
  12. data/lib/cinder/commands/setup.rb +32 -0
  13. data/lib/cinder/external_commands.rb +43 -0
  14. data/lib/cinder/version.rb +3 -0
  15. data/spec/cinder/commands/lint_spec.rb +121 -0
  16. data/spec/cinder_spec.rb +9 -0
  17. data/spec/fake_workspace.rb +23 -0
  18. data/spec/fixtures/both-configs-sans-ad_hoc-provisioning/Foo.xcodeproj/project.pbxproj +329 -0
  19. data/spec/fixtures/both-configs-sans-ad_hoc-provisioning/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  20. data/spec/fixtures/both-configs-sans-ad_hoc-provisioning/Podfile +3 -0
  21. data/spec/fixtures/both-configs-sans-ad_hoc-provisioning/enterprise.mobileprovision +0 -0
  22. data/spec/fixtures/both-configs-sans-enterprise-provisioning/Foo.xcodeproj/project.pbxproj +329 -0
  23. data/spec/fixtures/both-configs-sans-enterprise-provisioning/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  24. data/spec/fixtures/both-configs-sans-enterprise-provisioning/Podfile +3 -0
  25. data/spec/fixtures/both-configs-sans-enterprise-provisioning/ad_hoc.mobileprovision +0 -0
  26. data/spec/fixtures/both-configs/Foo.xcodeproj/project.pbxproj +329 -0
  27. data/spec/fixtures/both-configs/Foo.xcodeproj/xcshareddata/xcschemes/Foo.xcscheme +0 -0
  28. data/spec/fixtures/both-configs/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  29. data/spec/fixtures/both-configs/Podfile +3 -0
  30. data/spec/fixtures/both-configs/ad_hoc.mobileprovision +0 -0
  31. data/spec/fixtures/both-configs/enterprise.mobileprovision +0 -0
  32. data/spec/fixtures/empty-podfile/Foo.xcodeproj/project.pbxproj +319 -0
  33. data/spec/fixtures/empty-podfile/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  34. data/spec/fixtures/empty-podfile/Podfile +0 -0
  35. data/spec/fixtures/lowercase/Podfile +3 -0
  36. data/spec/fixtures/lowercase/ad_hoc.mobileprovision +0 -0
  37. data/spec/fixtures/lowercase/foo.xcodeproj/project.pbxproj +319 -0
  38. data/spec/fixtures/lowercase/foo.xcodeproj/xcshareddata/xcschemes/foo.xcscheme +0 -0
  39. data/spec/fixtures/lowercase/foo.xcworkspace/contents.xcworkspacedata +1 -0
  40. data/spec/fixtures/multiple-workspaces/Bar.xcworkspace/contents.xcworkspacedata +1 -0
  41. data/spec/fixtures/multiple-workspaces/Foo.xcodeproj/project.pbxproj +319 -0
  42. data/spec/fixtures/multiple-workspaces/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  43. data/spec/fixtures/multiple-workspaces/Podfile +3 -0
  44. data/spec/fixtures/proper/Foo.xcodeproj/project.pbxproj +319 -0
  45. data/spec/fixtures/proper/Foo.xcodeproj/xcshareddata/xcschemes/Foo.xcscheme +0 -0
  46. data/spec/fixtures/proper/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  47. data/spec/fixtures/proper/Podfile +3 -0
  48. data/spec/fixtures/proper/ad_hoc.mobileprovision +0 -0
  49. data/spec/fixtures/sans-ad_hoc-or-enterprise-config/Foo.xcodeproj/project.pbxproj +309 -0
  50. data/spec/fixtures/sans-ad_hoc-or-enterprise-config/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  51. data/spec/fixtures/sans-ad_hoc-or-enterprise-config/Podfile +3 -0
  52. data/spec/fixtures/sans-podfile/Foo.xcodeproj/project.pbxproj +319 -0
  53. data/spec/fixtures/sans-podfile/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  54. data/spec/fixtures/sans-project/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  55. data/spec/fixtures/sans-project/Podfile +3 -0
  56. data/spec/fixtures/sans-target/Foo.xcodeproj/project.pbxproj +113 -0
  57. data/spec/fixtures/sans-target/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  58. data/spec/fixtures/sans-target/Podfile +3 -0
  59. data/spec/fixtures/sans-workspace/Foo.xcodeproj/project.pbxproj +319 -0
  60. data/spec/fixtures/sans-workspace/Podfile +3 -0
  61. data/spec/fixtures/whitespace-workspace/Foo App.xcodeproj/project.pbxproj +319 -0
  62. data/spec/fixtures/whitespace-workspace/Foo App.xcworkspace/contents.xcworkspacedata +1 -0
  63. data/spec/fixtures/whitespace-workspace/Podfile +3 -0
  64. data/spec/fixtures/workspace-project-mismatch/Bar.xcodeproj/project.pbxproj +319 -0
  65. data/spec/fixtures/workspace-project-mismatch/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  66. data/spec/fixtures/workspace-project-mismatch/Podfile +3 -0
  67. data/spec/fixtures/workspace-projects-mismatch/Bar.xcodeproj/project.pbxproj +319 -0
  68. data/spec/fixtures/workspace-projects-mismatch/Baz.xcodeproj/project.pbxproj +319 -0
  69. data/spec/fixtures/workspace-projects-mismatch/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  70. data/spec/fixtures/workspace-projects-mismatch/Podfile +3 -0
  71. data/spec/fixtures/workspace-target-mismatch/Foo.xcodeproj/project.pbxproj +319 -0
  72. data/spec/fixtures/workspace-target-mismatch/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  73. data/spec/fixtures/workspace-target-mismatch/Podfile +3 -0
  74. data/spec/fixtures/workspace-targets-mismatch/Foo.xcodeproj/project.pbxproj +504 -0
  75. data/spec/fixtures/workspace-targets-mismatch/Foo.xcworkspace/contents.xcworkspacedata +1 -0
  76. data/spec/fixtures/workspace-targets-mismatch/Podfile +3 -0
  77. data/spec/spec_helper.rb +20 -0
  78. metadata +363 -0
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ /vendor/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ source 'https://raw.github.com/detroit-labs/erebor/gems/'
3
+
4
+ # Specify your gem's dependencies in cinder.gemspec
5
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc "Run all specs"
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.rspec_opts = %w[--color]
7
+ t.verbose = false
8
+ end
9
+
10
+ task :default => :spec
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,5 @@
1
+ require "cinder/version"
2
+
3
+ module Cinder
4
+ # Your code goes here...
5
+ end
@@ -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