├── .gitignore
├── .rubocop.yml
├── CONTRIBUTING.md
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── NEWS.md
├── README.md
├── Rakefile
├── bin
└── liftoff
├── defaults
└── liftoffrc
├── homebrew
└── liftoff.rb
├── lib
├── liftoff.rb
└── liftoff
│ ├── build_configuration_builder.rb
│ ├── cli.rb
│ ├── configuration_parser.rb
│ ├── dependency_manager.rb
│ ├── dependency_manager_coordinator.rb
│ ├── dependency_managers
│ ├── bundler.rb
│ ├── carthage.rb
│ ├── cocoapods.rb
│ └── null_dependency_manager.rb
│ ├── deprecation_manager.rb
│ ├── file_manager.rb
│ ├── git_setup.rb
│ ├── launchpad.rb
│ ├── object_picker.rb
│ ├── option_fetcher.rb
│ ├── project.rb
│ ├── project_builder.rb
│ ├── project_configuration.rb
│ ├── scheme_builder.rb
│ ├── settings_generator.rb
│ ├── string_renderer.rb
│ ├── template_finder.rb
│ ├── template_generator.rb
│ ├── version.rb
│ ├── xcodeproj_helper.rb
│ └── xcodeproj_monkeypatch.rb
├── man
├── liftoff.1
└── liftoffrc.5
├── release.sh
├── spec
├── build_configuration_builder_specs.rb
├── configuration_parser_spec.rb
├── dependency_manager_coordinator_spec.rb
├── dependency_manager_spec.rb
├── dependency_managers
│ └── bundler_spec.rb
├── project_configuration_spec.rb
├── scheme_builder_spec.rb
├── spec_helper.rb
├── template_finder_spec.rb
└── template_generator_spec.rb
├── src
└── liftoff
├── templates
├── <%= prefix %>AppDelegate.h
├── <%= prefix %>AppDelegate.m
├── <%= project_name %>-Prefix.pch
├── <%= test_target_name %>-Info.plist
├── <%= test_target_name %>-Prefix.pch
├── AppDelegate.swift
├── Cartfile
├── Gemfile.rb
├── Images.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── LaunchScreen.xib
├── Main.storyboard
├── Podfile
├── README.md
├── Settings.bundle
│ ├── Root.plist
│ └── en.lproj
│ │ └── Root.strings
├── bundle_version.sh
├── copy_frameworks.sh
├── gitattributes
├── gitignore
├── main.m
├── setup.sh
├── test.sh
├── todo.sh
└── travis.yml
└── vendor
└── vendorize
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/gems
2 | pkg/
3 |
4 | # Sub-repositories for release
5 | gh-pages
6 | homebrew-formulae
7 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | AllCops:
2 | Exclude:
3 | - vendor/**/*
4 | - db/schema.rb
5 | UseCache: false
6 | Style/CollectionMethods:
7 | Description: Preferred collection methods.
8 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#map-find-select-reduce-size
9 | Enabled: true
10 | PreferredMethods:
11 | collect: map
12 | collect!: map!
13 | find: detect
14 | find_all: select
15 | reduce: inject
16 | Style/DotPosition:
17 | Description: Checks the position of the dot in multi-line method calls.
18 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains
19 | Enabled: true
20 | EnforcedStyle: trailing
21 | SupportedStyles:
22 | - leading
23 | - trailing
24 | Style/FileName:
25 | Description: Use snake_case for source file names.
26 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-files
27 | Enabled: false
28 | Exclude: []
29 | Style/GuardClause:
30 | Description: Check for conditionals that can be replaced with guard clauses
31 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals
32 | Enabled: false
33 | MinBodyLength: 1
34 | Style/IfUnlessModifier:
35 | Description: Favor modifier if/unless usage when you have a single-line body.
36 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier
37 | Enabled: false
38 | MaxLineLength: 80
39 | Style/OptionHash:
40 | Description: Don't use option hashes when you can use keyword arguments.
41 | Enabled: false
42 | Style/PercentLiteralDelimiters:
43 | Description: Use `%`-literal delimiters consistently
44 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-literal-braces
45 | Enabled: false
46 | PreferredDelimiters:
47 | "%": "()"
48 | "%i": "()"
49 | "%q": "()"
50 | "%Q": "()"
51 | "%r": "{}"
52 | "%s": "()"
53 | "%w": "()"
54 | "%W": "()"
55 | "%x": "()"
56 | Style/PredicateName:
57 | Description: Check the names of predicate methods.
58 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark
59 | Enabled: true
60 | NamePrefix:
61 | - is_
62 | - has_
63 | - have_
64 | NamePrefixBlacklist:
65 | - is_
66 | Exclude:
67 | - spec/**/*
68 | Style/RaiseArgs:
69 | Description: Checks the arguments passed to raise/fail.
70 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#exception-class-messages
71 | Enabled: false
72 | EnforcedStyle: exploded
73 | SupportedStyles:
74 | - compact
75 | - exploded
76 | Style/SignalException:
77 | Description: Checks for proper usage of fail and raise.
78 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#fail-method
79 | Enabled: false
80 | EnforcedStyle: semantic
81 | SupportedStyles:
82 | - only_raise
83 | - only_fail
84 | - semantic
85 | Style/SingleLineBlockParams:
86 | Description: Enforces the names of some block params.
87 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#reduce-blocks
88 | Enabled: false
89 | Methods:
90 | - reduce:
91 | - a
92 | - e
93 | - inject:
94 | - a
95 | - e
96 | Style/SingleLineMethods:
97 | Description: Avoid single-line methods.
98 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-single-line-methods
99 | Enabled: false
100 | AllowIfMethodIsEmpty: true
101 | Style/StringLiterals:
102 | Description: Checks if uses of quotes match the configured preference.
103 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-string-literals
104 | Enabled: true
105 | EnforcedStyle: double_quotes
106 | SupportedStyles:
107 | - single_quotes
108 | - double_quotes
109 | Style/StringLiteralsInInterpolation:
110 | Description: Checks if uses of quotes inside expressions in interpolated strings
111 | match the configured preference.
112 | Enabled: true
113 | EnforcedStyle: single_quotes
114 | SupportedStyles:
115 | - single_quotes
116 | - double_quotes
117 | Style/TrailingComma:
118 | Description: Checks for trailing comma in parameter lists and literals.
119 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas
120 | Enabled: false
121 | EnforcedStyleForMultiline: no_comma
122 | SupportedStyles:
123 | - comma
124 | - no_comma
125 | Metrics/AbcSize:
126 | Description: A calculated magnitude based on number of assignments, branches, and
127 | conditions.
128 | Enabled: false
129 | Max: 15
130 | Metrics/ClassLength:
131 | Description: Avoid classes longer than 100 lines of code.
132 | Enabled: false
133 | CountComments: false
134 | Max: 100
135 | Metrics/ModuleLength:
136 | CountComments: false
137 | Max: 100
138 | Description: Avoid modules longer than 100 lines of code.
139 | Enabled: false
140 | Metrics/CyclomaticComplexity:
141 | Description: A complexity metric that is strongly correlated to the number of test
142 | cases needed to validate a method.
143 | Enabled: false
144 | Max: 6
145 | Metrics/MethodLength:
146 | Description: Avoid methods longer than 10 lines of code.
147 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#short-methods
148 | Enabled: false
149 | CountComments: false
150 | Max: 10
151 | Metrics/ParameterLists:
152 | Description: Avoid parameter lists longer than three or four parameters.
153 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#too-many-params
154 | Enabled: false
155 | Max: 5
156 | CountKeywordArgs: true
157 | Metrics/PerceivedComplexity:
158 | Description: A complexity metric geared towards measuring complexity for a human
159 | reader.
160 | Enabled: false
161 | Max: 7
162 | Lint/AssignmentInCondition:
163 | Description: Don't use assignment in conditions.
164 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition
165 | Enabled: false
166 | AllowSafeAssignment: true
167 | Style/InlineComment:
168 | Description: Avoid inline comments.
169 | Enabled: false
170 | Style/AccessorMethodName:
171 | Description: Check the naming of accessor methods for get_/set_.
172 | Enabled: false
173 | Style/Alias:
174 | Description: Use alias_method instead of alias.
175 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#alias-method
176 | Enabled: false
177 | Style/Documentation:
178 | Description: Document classes and non-namespace modules.
179 | Enabled: false
180 | Style/DoubleNegation:
181 | Description: Checks for uses of double negation (!!).
182 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-bang-bang
183 | Enabled: false
184 | Style/EachWithObject:
185 | Description: Prefer `each_with_object` over `inject` or `reduce`.
186 | Enabled: false
187 | Style/EmptyLiteral:
188 | Description: Prefer literals to Array.new/Hash.new/String.new.
189 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#literal-array-hash
190 | Enabled: false
191 | Style/ModuleFunction:
192 | Description: Checks for usage of `extend self` in modules.
193 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#module-function
194 | Enabled: false
195 | Style/OneLineConditional:
196 | Description: Favor the ternary operator(?:) over if/then/else/end constructs.
197 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#ternary-operator
198 | Enabled: false
199 | Style/PerlBackrefs:
200 | Description: Avoid Perl-style regex back references.
201 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers
202 | Enabled: false
203 | Style/Send:
204 | Description: Prefer `Object#__send__` or `Object#public_send` to `send`, as `send`
205 | may overlap with existing methods.
206 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#prefer-public-send
207 | Enabled: false
208 | Style/SpecialGlobalVars:
209 | Description: Avoid Perl-style global variables.
210 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms
211 | Enabled: false
212 | Style/VariableInterpolation:
213 | Description: Don't interpolate global, instance and class variables directly in
214 | strings.
215 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#curlies-interpolate
216 | Enabled: false
217 | Style/WhenThen:
218 | Description: Use when x then ... for one-line cases.
219 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#one-line-cases
220 | Enabled: false
221 | Lint/EachWithObjectArgument:
222 | Description: Check for immutable argument given to each_with_object.
223 | Enabled: true
224 | Lint/HandleExceptions:
225 | Description: Don't suppress exception.
226 | StyleGuide: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions
227 | Enabled: false
228 | Lint/LiteralInCondition:
229 | Description: Checks of literals used in conditions.
230 | Enabled: false
231 | Lint/LiteralInInterpolation:
232 | Description: Checks for literals used in interpolation.
233 | Enabled: false
234 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to liftoff
2 |
3 | We love pull requests from everyone. Follow the thoughtbot [code of conduct]
4 | while contributing.
5 |
6 | [code of conduct]: https://thoughtbot.com/open-source-code-of-conduct
7 |
8 | ## Contributing a feature
9 |
10 | We love pull requests. Here's a quick guide:
11 |
12 | 1. Clone the repo:
13 |
14 | git clone https://github.com/liftoffcli/liftoff.git
15 |
16 | 2. Run the tests. We only take pull requests with passing tests, and it's great
17 | to know that you have a clean slate:
18 |
19 | bundle
20 | rspec
21 |
22 | 3. Add a test for your change. Only refactoring and documentation changes
23 | require no new tests. If you are adding functionality or fixing a bug, we
24 | need a test!
25 |
26 | 4. Make the test pass.
27 |
28 | 5. Fork the repo, push to your fork, and submit a pull request.
29 |
30 | At this point you're waiting on us. We like to at least comment on, if not
31 | accept, pull requests within three business days. We may suggest some changes or
32 | improvements or alternatives.
33 |
34 | Some things that will increase the chance that your pull request is accepted:
35 |
36 | * Include tests that fail without your code, and pass with it.
37 | * Update the documentation, especially the man page, whatever is affected by
38 | your contribution.
39 | * Follow the [thoughtbot style guide][style-guide].
40 |
41 | And in case we didn't emphasize it enough: we love tests!
42 |
43 | ## Releasing a new version
44 |
45 | liftoff's packaging is handled through a series of Rake tasks
46 |
47 | 1. Update the version number in `lib/liftoff/version.rb`.
48 |
49 | 2. Vendorize the gem dependencies:
50 |
51 | rake gems:vendorize
52 |
53 | 3. Build and publish the release:
54 |
55 | rake release:build
56 | rake release:push
57 | rake release:clean
58 |
59 | Alternatively, you can use a single command that will run steps 2 and 3 for
60 | you. If anything goes wrong, this will be harder to debug:
61 |
62 | ./release.sh
63 |
64 | [style-guide]: https://github.com/thoughtbot/guides/tree/master/style#ruby
65 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | group :dist do
4 | gem 'xcodeproj', '~> 1.5.7'
5 | gem 'highline', '~> 1.7'
6 | end
7 |
8 | group :test do
9 | gem 'rspec'
10 | end
11 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.0)
5 | atomos (0.1.3)
6 | claide (1.0.2)
7 | colored2 (3.1.2)
8 | diff-lcs (1.3)
9 | highline (1.7.10)
10 | nanaimo (0.2.6)
11 | rspec (3.8.0)
12 | rspec-core (~> 3.8.0)
13 | rspec-expectations (~> 3.8.0)
14 | rspec-mocks (~> 3.8.0)
15 | rspec-core (3.8.0)
16 | rspec-support (~> 3.8.0)
17 | rspec-expectations (3.8.1)
18 | diff-lcs (>= 1.2.0, < 2.0)
19 | rspec-support (~> 3.8.0)
20 | rspec-mocks (3.8.0)
21 | diff-lcs (>= 1.2.0, < 2.0)
22 | rspec-support (~> 3.8.0)
23 | rspec-support (3.8.0)
24 | xcodeproj (1.5.9)
25 | CFPropertyList (>= 2.3.3, < 4.0)
26 | atomos (~> 0.1.2)
27 | claide (>= 1.0.2, < 2.0)
28 | colored2 (~> 3.1)
29 | nanaimo (~> 0.2.5)
30 |
31 | PLATFORMS
32 | ruby
33 |
34 | DEPENDENCIES
35 | highline (~> 1.7)
36 | rspec
37 | xcodeproj (~> 1.5.7)
38 |
39 | BUNDLED WITH
40 | 1.16.1
41 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2013 thoughtbot, inc.
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.
23 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # Liftoff Changelog #
2 |
3 | ## Liftoff 1.8.2 - 20 November 2017 ##
4 |
5 | ### Changes ###
6 |
7 | * Dependencies have been updated to their most recent versions to avoid
8 | running into `SIGABORT` when trying to create projects with Xcode 9. -
9 | [Oliver Halligon][liftoff#290]
10 |
11 | [liftoff#290]: https://github.com/liftoffcli/liftoff/pull/290
12 |
13 | ## Liftoff 1.8.1 - 27 June 2016 ##
14 |
15 | ### Changes ###
16 |
17 | - Bumped dependencies to their most recent versions to reduce
18 | warnings/collisions - [Gordon Fontenot][liftoff#276]
19 |
20 | [liftoff#276]: https://github.com/liftoffcli/liftoff/pull/276
21 |
22 | ## Liftoff 1.8.0 - 20 May 2016 ##
23 |
24 | ### New Features ###
25 |
26 | * Bundler is now listed as a dependency manager like CocoaPods and Carthage.
27 | This means you can enable or disable it the same way you would other
28 | dependency managers. It's still enabled by default. - [Edward
29 | Loveall][liftoff#260]
30 |
31 | [liftoff#260]: https://github.com/liftoffcli/liftoff/pull/260
32 |
33 | ### Changes ###
34 |
35 | * Bumped dependency versions to reduce warnings/collisions - [Sabatino Masala][liftoff#263]
36 |
37 | [liftoff#263]: https://github.com/liftoffcli/liftoff/pull/263
38 |
39 | ## Liftoff 1.7.0 - 20 May 2016 ##
40 |
41 | ### New Features ###
42 |
43 | * Now compatible with CocoaPods 1.0 - [Mason Phillips][liftoff#268]
44 |
45 | [liftoff#268]: https://github.com/liftoffcli/liftoff/pull/268
46 |
47 | ## Liftoff 1.6.0 - 20 Nov 2015 ##
48 |
49 | ### New Features ###
50 |
51 | * Add Carthage support. You can now set the `dependency_managers` to one or
52 | more dependency management systems. Right now, this supports `cocoapods` and
53 | `carthage`. This also adds a `--dependency-managers` flag to the cli
54 | options. The `--[no-]cocoapods` option will be removed in a future version
55 | of Liftoff, and so should not be used going forward. The `use_cocoapods`
56 | `liftoffrc` key has also been removed. You will now see a deprecation
57 | warning if you try to set this key. - [Jake Craige][liftoff#226]
58 | * Define custom build configurations. You can now set a `build_configurations`
59 | key in your `liftoffrc` to define custom build configurations that can be
60 | used elsewhere in your `liftoffrc`. These configurations need to be copies
61 | of either the `release` or `debug` schemes. See the `liftoffrc` man page for
62 | more info. - [Marcelo Fabri][liftoff#224] (Thanks to [Mark
63 | Adams][liftoff#71])
64 | * Define custom schemes in your `liftoffrc.` By default, this isn't set, but
65 | if you set `schemes` in your `liftoffrc`, Liftoff will generate a scheme
66 | with the specified actions. See the `liftoffrc` man page for more info. -
67 | [Marcelo Fabri][liftoff#225]
68 |
69 | [liftoff#71]: https://github.com/liftoffcli/liftoff/pull/71
70 | [liftoff#226]: https://github.com/liftoffcli/liftoff/pull/226
71 | [liftoff#224]: https://github.com/liftoffcli/liftoff/pull/224
72 | [liftoff#225]: https://github.com/liftoffcli/liftoff/pull/225
73 |
74 | ### Changes ###
75 |
76 | * `bundle_version` now uses `git rev-list --count` instead of `git rev-list |
77 | wc -l | tr -d ' '` - [Giovanni Lodi][liftoff#221]
78 | * The default `TODO` and `FIXME` script now ignores Carthage source files in
79 | addition to CocoaPods source files. - [Josh Steiner][liftoff#223]
80 | * Improve test target detection. We were previously just matching against the
81 | word "Tests" instead of checking against the user-defined test target
82 | name. - [Gordon Fontenot][liftoff#251] (Thanks to [Oliver Halligon][liftoff#218])
83 |
84 | [liftoff#221]: https://github.com/liftoffcli/liftoff/pull/221
85 | [liftoff#223]: https://github.com/liftoffcli/liftoff/pull/223
86 | [liftoff#251]: https://github.com/liftoffcli/liftoff/pull/251
87 | [liftoff#218]: https://github.com/liftoffcli/liftoff/pull/218
88 |
89 | ### Bug Fixes ###
90 |
91 | * Unset `INSTALL_PATH` for projects. We previously had this set to nothing,
92 | which caused problems where archiving an app would try to create a generic
93 | archive instead of an iOS app archive - [Marcelo Fabri][liftoff#245] (Thanks
94 | to [Jake Craige][liftoff#235])
95 | * Storyboards and XIBs are now properly treated as resources. Previously, they
96 | were added to Compile Sources, which could lead to crashes in some versions
97 | of Xcode. - [Marcelo Fabri][liftoff#238]
98 |
99 | [liftoff#245]: https://github.com/liftoffcli/liftoff/pull/245
100 | [liftoff#235]: https://github.com/liftoffcli/liftoff/pull/235
101 | [liftoff#238]: https://github.com/liftoffcli/liftoff/pull/238
102 |
103 | ## Liftoff 1.5.0 - 13 Feb 2015 ##
104 |
105 | ### New Features ###
106 |
107 | * Let users add arbitrary configuration to test targets. You can now add a
108 | `extra_test_config` key to your `liftoffrc` and have Liftoff perform custom
109 | configuration for the test target. - [Gordon Fontenot][liftoff#213] (Thanks
110 | to [Olivier Halligon][liftoff#193])
111 | * Allow custom ordering of script phases. This adds an optional index to
112 | script phases that Liftoff will use to determine where it should insert the
113 | phase. This index defaults to -1. Note that this changes the format of the
114 | run script build phases key and so is a breaking change from 1.4. - [Juan
115 | Pablo Civile][liftoff#210] (Thanks to [Lode Vanhove][liftoff#204])
116 | * Add the ability to provide a custom path from the command line. Liftoff will
117 | now use this path as the root project folder if provided, defaulting to the
118 | project name if it isn't provided. - [Juan Pablo Civile][liftoff#209]
119 | (Thanks to [Tony DiPasquale][liftoff#77])
120 | * Add new run script build phase to automatically update version and bundle
121 | number using Git - [Reda Lemeden][liftoff#208]
122 | * Users can now customize the deployment target in their `liftoffrc` - [Lode
123 | Vanhove][liftoff#199]
124 | * Liftoff will now automatically generate a settings bundle by default. If you
125 | are using CocoaPods, it will also automatically add the acknowledgements
126 | from your included pods. This can be disabled in your liftoffrc with the
127 | `enable_settings` key, or on the command line with the `--[no]-settings`
128 | flag - [Lode Vanhove][liftoff#191]
129 | * Customize the test target name with the new `test_target_name` key in your
130 | `liftoffrc`. This can also be set on the command line with the
131 | `--test-target-name` flag - [Matt Oakes][liftoff#187] (Thanks to [Dal
132 | Rupnik][liftoff#169])
133 |
134 | [liftoff#213]: https://github.com/liftoffcli/liftoff/issues/213
135 | [liftoff#193]: https://github.com/liftoffcli/liftoff/issues/193
136 | [liftoff#210]: https://github.com/liftoffcli/liftoff/issues/210
137 | [liftoff#204]: https://github.com/liftoffcli/liftoff/issues/204
138 | [liftoff#209]: https://github.com/liftoffcli/liftoff/issues/209
139 | [liftoff#77]: https://github.com/liftoffcli/liftoff/issues/77
140 | [liftoff#208]: https://github.com/liftoffcli/liftoff/issues/208
141 | [liftoff#199]: https://github.com/liftoffcli/liftoff/issues/199
142 | [liftoff#191]: https://github.com/liftoffcli/liftoff/issues/191
143 | [liftoff#187]: https://github.com/liftoffcli/liftoff/issues/187
144 | [liftoff#169]: https://github.com/liftoffcli/liftoff/issues/169
145 |
146 | ### Changes ###
147 |
148 | * The format of the `run_script_phases` key has changed. If you have
149 | overridden this key, you'll need to update to the new format. - [Juan Pablo
150 | Civile][liftoff#210]
151 | * The default `AppDelegate` template for Swift now has a standard header
152 | comment. This keeps Liftoff's behavior in line with Xcode's. - [Gordon
153 | Fontenot][liftoff#211]
154 | * The default TODO and FIXME script will now find these comments in Swift
155 | files - [Kevin Xu][liftoff#206]
156 | * The `Resources` folder for the default `objc` template is now properly
157 | nested in the main target directory - [Gordon Fontenot][liftoff#195]
158 | * You can now use Liftoff's templating features with Podfiles - [Lode
159 | Vanhove][liftoff#189]
160 | * The default Podfile has been updated for CocoaPods 0.34.x - [Keith
161 | Smiley][liftoff#184]
162 | * The default `gitattributes` file has been updated so that `*.strings` files
163 | are now treated as text instead of as binary data - [Lode
164 | Vanhove][liftoff#184]
165 |
166 | [liftoff#211]: https://github.com/liftoffcli/liftoff/issues/211
167 | [liftoff#206]: https://github.com/liftoffcli/liftoff/issues/206
168 | [liftoff#195]: https://github.com/liftoffcli/liftoff/issues/195
169 | [liftoff#189]: https://github.com/liftoffcli/liftoff/issues/189
170 | [liftoff#184]: https://github.com/liftoffcli/liftoff/issues/184
171 |
172 | ### Bug Fixes ###
173 |
174 | * Force UTF-8 encoding when normalizing company name. This fixes a possible
175 | crash when using other locales. - [Gordon Fontenot][liftoff#181] (Thanks to
176 | [Dal Rupnik][liftoff#171])
177 | * Liftoff no longer prints the path to CocoaPods when checking to see if it's
178 | installed - [Lode Vanhove][liftoff#189]
179 |
180 | [liftoff#181]: https://github.com/liftoffcli/liftoff/issues/181
181 | [liftoff#171]: https://github.com/liftoffcli/liftoff/issues/171
182 |
183 | ## Liftoff 1.4.1 - 7 Oct 2014 ##
184 |
185 | ### Bug Fixes ###
186 |
187 | * Now using an updated version of [Xcodeproj][] to fix some segfaults reported
188 | by CocoaPods users.
189 |
190 | [Xcodeproj]: https://github.com/CocoaPods/Xcodeproj
191 |
192 | ## Liftoff 1.4 - 6 Oct 2014 ##
193 |
194 | ### New Features ###
195 |
196 | * Add the ability to define multiple project templates. This feature allows
197 | you to created named project templates inside your liftoffrc and then use
198 | them by defining them as the default inside your liftoffrc, or by passing
199 | their name on the command line. Be sure to check out `liftoff(1)` and
200 | `liftoffrc(5)` for more info. By default, Liftoff comes with templates for
201 | Objective-C (`objc`) and Swift (`swift`) projects. - [Gordon
202 | Fontenot][liftoff#175]
203 | * Add arbitrary configuration settings to liftoffrc. This lets you define an
204 | arbitrary dictionary structure inside your liftoffrc to create default
205 | configuration settings for projects. - [Gordon Fontenot][liftoff#174]
206 | ([Thanks to Marshall Huss][liftoff#142], [Juan Pablo Civile][liftoff#170],
207 | and [Keith Smiley][liftoff#160])
208 | * Add default storyboard file. Liftoff will now generate an empty storyboard
209 | file for use in the project. - [Gordon Fontenot][liftoff#177]
210 | * Add support for the new Launch Screen xib files. Liftoff will now generate
211 | an empty `LaunchScreen.xib` file and use it as the default launch screen
212 | option. - [Gordon Fontenot][liftoff#178] (lol recruiters)
213 | * Let users customize Xcode open command. This lets you override the default
214 | command used to launch Xcode inside your liftoffrc. This means you can
215 | default to opening Vim, AppCode, beta versions of Xcode, or even disable the
216 | feature completely. - [Gordon Fontenot][liftoff#172] ([Thanks to
217 | @asmod3us][liftoff#166])
218 |
219 | [liftoff#175]: https://github.com/liftoffcli/liftoff/issues/175
220 | [liftoff#174]: https://github.com/liftoffcli/liftoff/issues/174
221 | [liftoff#142]: https://github.com/liftoffcli/liftoff/issues/142
222 | [liftoff#170]: https://github.com/liftoffcli/liftoff/issues/170
223 | [liftoff#160]: https://github.com/liftoffcli/liftoff/issues/160
224 | [liftoff#177]: https://github.com/liftoffcli/liftoff/issues/177
225 | [liftoff#178]: https://github.com/liftoffcli/liftoff/issues/178
226 | [liftoff#172]: https://github.com/liftoffcli/liftoff/issues/172
227 | [liftoff#166]: https://github.com/liftoffcli/liftoff/issues/166
228 |
229 | ### Changes ###
230 |
231 | * Update objc default project template - [Gordon Fontenot][liftoff#180]
232 | * Simplify the status output. Liftoff no longer prints every file/directory it
233 | touches. Instead, it confirms that it's using the designated template. -
234 | [Gordon Fontenot][liftoff#179]
235 | * Rename Info.plist template. This is to keep Liftoff's defaults in line with
236 | Xcode's. This template is now simply named `Info.plist` - [Gordon
237 | Fontenot][liftoff#175]
238 | * Bump deployment target to 8.0. Welcome to the future. - [Gordon
239 | Fontenot][liftoff#176]
240 |
241 | [liftoff#180]: https://github.com/liftoffcli/liftoff/issues/180
242 | [liftoff#179]: https://github.com/liftoffcli/liftoff/issues/179
243 | [liftoff#175]: https://github.com/liftoffcli/liftoff/issues/175
244 | [liftoff#176]: https://github.com/liftoffcli/liftoff/issues/176
245 |
246 | ### Bug Fixes ###
247 |
248 | - OS 10.10 support. - [Gordon Fontenot][liftoff#173]
249 |
250 | [liftoff#173]: https://github.com/liftoffcli/liftoff/issues/173
251 |
252 | ## Liftoff 1.3 - 16 May 2014 ##
253 |
254 | ### New Features ###
255 |
256 | * Install arbitrary template files in the project directory. You can use the
257 | new `templates` key in `.liftoffrc` to define arbitrary templates that
258 | should be installed in the project directory. Liftoff will install these
259 | templates relative to the project's root. - [Gordon Fontenot][liftoff#147]
260 | ([Thanks to James Frost][liftoff#146])
261 | * Add Travis configuration by default. Liftoff will now generate the template
262 | files required for Travis to work out of the box. This can be disabled by
263 | overriding the templates we install by default - [Gordon
264 | Fontenot][liftoff#152]
265 | * Add `setup`, `test`, and `README` templates to the project. - [Gordon
266 | Fontenot][liftoff#154]
267 |
268 | [liftoff#147]: https://github.com/liftoffcli/liftoff/issues/147
269 | [liftoff#146]: https://github.com/liftoffcli/liftoff/issues/146
270 | [liftoff#152]: https://github.com/liftoffcli/liftoff/issues/152
271 | [liftoff#154]: https://github.com/liftoffcli/liftoff/issues/154
272 |
273 | ### Changes ###
274 |
275 | * Generated scheme is now shared. Previously, Xcode was creating a private
276 | scheme after the project was opened for the first time. We are now creating
277 | this scheme ourselves, and making it shared. - [Gordon
278 | Fontenot][liftoff#153] ([Thanks to Mark Adams][liftoff#70])
279 | * Add OHHTTPStubs as a default testing dependency - [Gordon
280 | Fontenot][liftoff#143]
281 | * Add documentation hint to default Podfile. This is intended to solve some
282 | confusion about where to add new pods after initial installation - [Gordon
283 | Fontenot][liftoff#145] ([Thanks to Mark Flowers][liftoff#144])
284 |
285 | [liftoff#153]: https://github.com/liftoffcli/liftoff/issues/153
286 | [liftoff#70]: https://github.com/liftoffcli/liftoff/issues/70
287 | [liftoff#143]: https://github.com/liftoffcli/liftoff/issues/143
288 | [liftoff#145]: https://github.com/liftoffcli/liftoff/issues/145
289 | [liftoff#144]: https://github.com/liftoffcli/liftoff/issues/144
290 |
291 | ### Bug Fixes ###
292 |
293 | * Don't skip installation for app targets. Previously, we were setting
294 | `SKIP_INSTALLATION` to `YES`, which caused the Archive action to fail
295 | silently. This change brings us back in line with Xcode's default behavior.
296 | - [Gordon Fontenot][liftoff#151] ([Thanks to James Frost][liftoff#149])
297 |
298 | [liftoff#151]: https://github.com/liftoffcli/liftoff/issues/151
299 | [liftoff#149]: https://github.com/liftoffcli/liftoff/issues/149
300 |
301 | ## Liftoff 1.2 - March 28, 2014 ##
302 |
303 | ### New Features ###
304 |
305 | * Add command line flags. You can now pass a set of flags to the `liftoff`
306 | executable to override specific configurations at run time - [JP
307 | Simard][liftoff#126]
308 | * Add `strict_prompts` option. This configuration option and the corresponding
309 | `--[no-]strict-prompts` command line flag tell `liftoff` to only prompt you
310 | for options that don't have default values set. This allows you to set values
311 | at run time and skip the prompt altogether. - [JP Simard][liftoff#127]
312 | * Add configuration for setting up Run Script phases. This replaces the
313 | `install_todo_script` configuration key with a much more flexible
314 | `run_script_phases` key. By overriding this key, you can install any
315 | arbitrary script phases as long as you're providing a template for them.
316 | - [Mikael Bartlett][liftoff#120] ([Thanks to Magnus Ottosson][liftoff#118])
317 | * Reluctantly allow the use of tabs for indentation. Even though my conscience
318 | protested, we've added a `use_tabs` key to the configuration. Enabling this
319 | will configure the project to use tabs instead of spaces. Note that this
320 | doesn't change the spacing in the default templates, so if you override this
321 | you will probably want to override those as well. - [Gordon
322 | Fontenot][liftoff#125] ([Thanks to Magnus Ottosson][liftoff#119])
323 |
324 | [liftoff#126]: https://github.com/liftoffcli/liftoff/issues/126
325 | [liftoff#127]: https://github.com/liftoffcli/liftoff/issues/127
326 | [liftoff#120]: https://github.com/liftoffcli/liftoff/issues/120
327 | [liftoff#125]: https://github.com/liftoffcli/liftoff/issues/125
328 | [liftoff#118]: https://github.com/liftoffcli/liftoff/issues/118
329 | [liftoff#119]: https://github.com/liftoffcli/liftoff/issues/119
330 |
331 | ### Changes ###
332 |
333 | * Enable some more warnings by default. - [Gordon Fontenot][liftoff#134]
334 | * Handle key deprecations a bit more gracefully. - [Gordon
335 | Fontenot][liftoff#133]
336 | * Stop treating all plists as though they are the Info.plist. Previously, any
337 | plist that was added as a template was being treated as though it was the
338 | Info.plist for that target. We're now being more explicit about matching that
339 | file, and linking all other plists properly. - [Gordon Fontenot][liftoff#122]
340 | ([Thanks to @mattyohe][liftoff#121])
341 |
342 | [liftoff#134]: https://github.com/liftoffcli/liftoff/issues/134
343 | [liftoff#133]: https://github.com/liftoffcli/liftoff/issues/133
344 | [liftoff#122]: https://github.com/liftoffcli/liftoff/issues/122
345 | [liftoff#121]: https://github.com/liftoffcli/liftoff/issues/121
346 |
347 | ### Bug Fixes ###
348 |
349 | * Set the deployment target at the project level. This mimics the behavior
350 | when creating a new project with Xcode. - [Gordon Fontenot][liftoff#123]
351 |
352 | [liftoff#123]: https://github.com/liftoffcli/liftoff/issues/123
353 |
354 | ## Liftoff 1.1.1 - March 18, 2014 ##
355 |
356 | ### Bug Fixes ###
357 |
358 | * Remove `OTHER_LDFLAGS` setting from app target. This was being set to a
359 | blank string, which caused it to override any xcconfig files added to the
360 | target. - Gordon Fontenot ([Thanks to @frosty][liftoff#111])
361 | * Prevent RubyGems from loading. In some installs, users were seeing crashes
362 | due to the wrong Xcodeproj native C extensions being loaded. Current theory
363 | is that RVM is doing some loadpath stuff for gems that is overriding our
364 | loadpaths. The simple fix that seems to solve the issue is to completely
365 | disable RubyGems while running Liftoff. - Gordon Fontenot ([Thanks to
366 | @iOSDevil, Jim Rutherford, and @endoze][liftoff#113])
367 |
368 | [liftoff#111]: https://github.com/liftoffcli/liftoff/issues/111
369 | [liftoff#113]: https://github.com/liftoffcli/liftoff/issues/113
370 |
371 | ## Liftoff 1.1 - March 14, 2014 ##
372 |
373 | ### New Features ###
374 |
375 | * Allow users to override templates. Now users can create templates inside
376 | `.liftoff/templates` at the local or user level in order to add custom
377 | templates, or override the default templates. - Gordon Fontenot
378 | * Add support for CocoaPods. By default, Liftoff will generate a default
379 | podfile, and run `pod install` inside the project directory. This can be
380 | disabled inside the `.liftoffrc`. - JP Simard
381 | * Add default pods for test target. By default, Liftoff will install `Specta`,
382 | `Expecta`, and `OCMock` for the test target. This can be overridden by using
383 | a custom template for the `Podfile` - Gordon Fontenot
384 | * Add `company_identifier` property to configuration. We will now prompt for
385 | the company identifier on new project creation. The default for this prompt
386 | is generated by normalizing the provided company name, unless there is a
387 | default set by the `.liftoffrc` - Gordon Fontenot ([Thanks to Tony
388 | DiPasquale][liftoff#94])
389 | * Open new projects after creation - Mark Adams
390 |
391 | [liftoff#94]: https://github.com/liftoffcli/liftoff/issues/94
392 |
393 | ### Changes ###
394 |
395 | * Application target defaults to portrait orientation only. - Mark Adams
396 | * Automatically default to the current user's name. We no longer prompt for
397 | the user's name, instead defaulting to the name provided by the system. This
398 | can still be overridden by a `.liftoffrc` - Gordon Fontenot (Thanks to [Mike
399 | Burns][liftoff#80])
400 | * Remove explicit framework linking. Since modules are enabled, we don't need
401 | to manually link frameworks anymore. - Gordon Fontenot
402 | * Prefer new @import over #import - Mark Adams
403 |
404 | [liftoff#80]: https://github.com/liftoffcli/liftoff/issues/80
405 |
406 | ### Bug Fixes ###
407 |
408 | * Install ruby files into rubylib instead of lib. Previously, Homebrew was
409 | linking these into /usr/local/lib, which caused warnings when running `brew
410 | audit` - Gordon Fontenot ([Thanks to Ashton Williams][liftoff#107])
411 | * Use `https` for GitHub URL in homebrew formula. This was causing warnings
412 | when running `brew audit` - Gordon Fontenot ([Thanks to Ashton Williams][liftoff#107])
413 | * Print a nicer error if the directory exists. Previously, this was throwing
414 | an ugly ruby stack trace. - Gordon Fontenot ([Thanks to Mark
415 | Adams][liftoff#87])
416 | * Trap interrupt during option input. Previously, this was throwing an ugly
417 | ruby stack trace. - Gordon Fontenot ([Thanks to George
418 | Brocklehurst][liftoff#81])
419 | * Set SDKROOT at the project level - Gordon Fontenot ([Thanks to Tony
420 | DiPasquale][liftoff#97])
421 |
422 | [liftoff#107]: https://github.com/liftoffcli/liftoff/issues/107
423 | [liftoff#87]: https://github.com/liftoffcli/liftoff/issues/87
424 | [liftoff#81]: https://github.com/liftoffcli/liftoff/issues/81
425 | [liftoff#97]: https://github.com/liftoffcli/liftoff/issues/97
426 |
427 | ## Liftoff 1.0 - March 7, 2014 ##
428 |
429 | Initial 1.0 release. See [blog post][liftoff-release] for details.
430 |
431 | [liftoff-release]: http://robots.thoughtbot.com/liftoff-10
432 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 | **Liftoff is a CLI for creating and configuring iOS Xcode projects.**
4 |
5 | > 👋 We need your help!
6 | > We're looking for contributors and people willing to take over Liftoff maintenance.
7 | > If you're interested, we'd love to hear from you! 👍
8 |
9 | ## Installation
10 |
11 | brew tap liftoffcli/formulae
12 | brew install liftoff
13 |
14 | Liftoff was previously distributed via RubyGems. This method of installation has
15 | been deprecated, and all new releases will be done through Homebrew. If you are
16 | migrating from RubyGems, you should uninstall the gem version to avoid confusion
17 | in the future.
18 |
19 | ## Usage
20 |
21 | Run this command in a directory
22 |
23 | liftoff
24 |
25 | View the documentation:
26 |
27 | man liftoff
28 | man liftoffrc
29 |
30 | When Liftoff finds an existing project in the current directory, it will
31 | perform the following configurations:
32 |
33 | * Set the indentation level (In spaces, 4 by default).
34 | * Treat warnings as errors for release schemes.
35 | * Enable warnings at the project level, check `liftoffrc(5)` for a list of the warnings.
36 | * Turn on Static Analysis for the project.
37 | * Add a build phase shell script that [turns "TODO:" and "FIXME:" into
38 | warnings][deallocated-todo].
39 | * Add a build phase shell script that sets the version to the latest Git tag,
40 | and the build number to the number of commits on master.
41 | * Perform arbitrary configuration as defined in the user's `liftoffrc`. See
42 | `liftoffrc(5)` for more information.
43 | * Add default [.gitignore] and [.gitattributes] files.
44 | * Initialize a new `git` repo and create an initial commit (if needed).
45 |
46 | [.gitignore]: https://github.com/liftoffcli/liftoff/blob/master/templates/gitignore
47 | [.gitattributes]: https://github.com/liftoffcli/liftoff/blob/master/templates/gitattributes
48 | [deallocated-todo]: http://deallocatedobjects.com/posts/show-todos-and-fixmes-as-warnings-in-xcode-4
49 |
50 | When you run Liftoff in a directory without a project file, it will create a
51 | new directory structure for a project, and generate a well-configured Xcode
52 | project in that subdirectory:
53 |
54 | ```
55 | $ cd ~/dev/
56 | $ liftoff
57 | Project name? MyCoolApp
58 | Company name? thoughtbot
59 | Author name? Gordon Fontenot
60 | Prefix? MCA
61 | Creating MyCoolApp
62 | Creating MyCoolApp/Categories
63 | Creating MyCoolApp/Classes
64 | [snip]
65 | ```
66 |
67 | Liftoff will generate a brand new project for you based on the provided
68 | values. Generating projects via Liftoff has these advantages:
69 |
70 | * Minimized time reorganizing the repository
71 | * Sets up `git` repository automatically
72 | * Defined group structure
73 | * Matching directory structure on disk (linked to the proper group)
74 | * Easily customizable
75 | * Configurations can be shared easily
76 |
77 | ### Configuration
78 |
79 | You can use a `liftoffrc` file to speed up your workflow by defining your
80 | preferred configuration for Liftoff.
81 |
82 | Liftoff will look for config files in the local directory and then the home
83 | directory. If it can't find a key in `./.liftoffrc` or `~/.liftoffrc`, it will
84 | use the default values. Check `liftoffrc(5)` for more information:
85 |
86 | man liftoffrc
87 |
88 | You can see the [current liftoffrc on master][liftoffrc], but be aware that
89 | the keys might not match up completely with the current released version.
90 |
91 | [liftoffrc]: https://github.com/liftoffcli/liftoff/blob/master/defaults/liftoffrc
92 |
93 | ### Directory Structure and Templates
94 |
95 | One of the most powerful things that Liftoff can do for you is let you quickly
96 | and easily customize your project's group and directory structure. By defining
97 | a YAML dictionary inside your local or user `.liftoffrc`, you can completely
98 | dictate the structure that will be created. This includes group structure,
99 | order, placement of template files, etc. And remember that these groups will
100 | be mimicked on disk as well.
101 |
102 | You can also create your own templates, or override the defaults by adding
103 | them to `~/.liftoff/templates` or `./.liftoff/templates`. Liftoff will use the
104 | same fallback order when looking for templates as it does for the
105 | `.liftoffrc`.
106 |
107 | These files (and filenames) will be parsed with `ERB`, using the values
108 | provided at run time (or the default values from a `liftoffrc`).
109 |
110 | ### Note about Xcode 7.0
111 |
112 | In Xcode 7.0, there is a bug that when combined with a bug in Liftoff 1.5
113 | results in a crash when trying to launch a project generated by liftoff. This
114 | bug is fixed in Liftoff 1.6, but if you have a project created with a version
115 | of liftoff <= 1.5, you might experience this crash.
116 |
117 | To fix it:
118 |
119 | 1. Open the project in Xcode 6 or Xcode 7.1+
120 | 2. Remove `Main.storyboard` from the project (only need to remove the
121 | reference, you don't need to trash the file itself)
122 | 3. Re-add `Main.storyboard` to the project
123 |
124 | Your project should now open cleanly in Xcode 7.0.
125 |
126 | Contributing
127 | ------------
128 |
129 | See the [CONTRIBUTING] document. Thank you, [contributors]!
130 |
131 | [CONTRIBUTING]: CONTRIBUTING.md
132 | [contributors]: https://github.com/liftoffcli/Liftoff/graphs/contributors
133 |
134 | We'd also love some help for maintaining Liftoff up-to-date on a more regular basis. If you're interested, we'd love to hear from you!
135 |
136 | License
137 | -------
138 |
139 | Liftoff is Copyright (c) 2015 thoughtbot, inc. It is free software, and may be
140 | redistributed under the terms specified in the [LICENSE] file.
141 |
142 | [LICENSE]: /LICENSE.txt
143 |
144 | About
145 | -----
146 |
147 | 
148 |
149 | Liftoff was originally maintained and funded by thoughtbot, inc. The names and
150 | logos for thoughtbot are trademarks of thoughtbot, inc.
151 |
152 | Similar projects
153 | ----------------
154 |
155 | * [XcodeGen by @yonaskolb](https://github.com/yonaskolb/XcodeGen)
156 | * [xcake by @jcampbell05](https://github.com/jcampbell05/xcake)
157 | * [struct by @lyptt](https://github.com/lyptt/struct)
158 | * [Cookie-Cutter](http://cookiecutter.readthedocs.io/en/latest/)
159 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rspec/core/rake_task'
2 | require 'rake/packagetask'
3 |
4 | $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
5 | require 'liftoff/version'
6 |
7 | GEMS_DIR = 'vendor/gems'
8 | GH_PAGES_DIR = 'gh-pages'
9 | HOMEBREW_FORMULAE_DIR = 'homebrew-formulae'
10 |
11 | namespace :gems do
12 | desc 'Vendorize dependencies'
13 | task :vendorize do
14 | system('vendor/vendorize', GEMS_DIR)
15 | end
16 |
17 | desc 'Remove vendorized dependencies'
18 | task :clean do
19 | if Dir.exists?(GEMS_DIR)
20 | FileUtils.rm_r(GEMS_DIR)
21 | end
22 | end
23 | end
24 |
25 | desc 'Push new release'
26 | task :release => ['release:build', 'release:push', 'release:clean']
27 |
28 | namespace :release do
29 | desc 'Build a new release'
30 | task :build => ['tarball:build', 'homebrew:build']
31 |
32 | desc 'Push sub-repositories'
33 | task :push => ['tarball:push', 'homebrew:push']
34 |
35 | desc 'Clean all build artifacts'
36 | task :clean => ['gems:clean', 'tarball:clean', 'homebrew:clean']
37 | end
38 |
39 | namespace :homebrew do
40 | desc 'Generate homebrew formula and add it to the repo'
41 | task :build => ['checkout', 'formula:build', 'commit']
42 |
43 | desc 'Checkout homebrew repo locally'
44 | task :checkout do
45 | `git clone git@github.com:liftoffcli/homebrew-formulae.git #{HOMEBREW_FORMULAE_DIR}`
46 | end
47 |
48 | desc 'Check in the new Homebrew formula'
49 | task :commit do
50 | Dir.chdir(HOMEBREW_FORMULAE_DIR) do
51 | `git add Formula/liftoff.rb`
52 | `git commit -m "liftoff: Release version #{Liftoff::VERSION}"`
53 | end
54 | end
55 |
56 | desc 'Push homebrew repo'
57 | task :push do
58 | Dir.chdir(HOMEBREW_FORMULAE_DIR) do
59 | `git push`
60 | end
61 | end
62 |
63 | desc 'Remove Homebrew repo'
64 | task :clean do
65 | if Dir.exists?(HOMEBREW_FORMULAE_DIR)
66 | FileUtils.rm_rf(HOMEBREW_FORMULAE_DIR)
67 | end
68 | end
69 |
70 | namespace :formula do
71 | desc 'Build homebrew formula'
72 | task :build do
73 | formula = File.read('homebrew/liftoff.rb')
74 | formula.gsub!('__VERSION__', Liftoff::VERSION)
75 | formula.gsub!('__SHA__', `shasum -a 256 #{GH_PAGES_DIR}/Liftoff-#{Liftoff::VERSION}.tar.gz`.split.first)
76 | File.write("#{HOMEBREW_FORMULAE_DIR}/Formula/liftoff.rb", formula)
77 | end
78 | end
79 | end
80 |
81 | namespace :tarball do
82 | desc 'Build the tarball'
83 | task :build => ['checkout', 'package', 'move', 'commit']
84 |
85 | desc 'Checkout gh-pages'
86 | task :checkout do
87 | `git clone --branch gh-pages git@github.com:liftoffcli/liftoff.git #{GH_PAGES_DIR}`
88 | end
89 |
90 | desc 'Move tarball into gh-pages'
91 | task :move do
92 | FileUtils.mv("pkg/Liftoff-#{Liftoff::VERSION}.tar.gz", GH_PAGES_DIR)
93 | end
94 |
95 | desc 'Check in the new tarball'
96 | task :commit do
97 | Dir.chdir(GH_PAGES_DIR) do
98 | `git add Liftoff-#{Liftoff::VERSION}.tar.gz`
99 | `git commit -m "Release version #{Liftoff::VERSION}"`
100 | end
101 | end
102 |
103 | desc 'Push the gh-pages branch'
104 | task :push do
105 | Dir.chdir(GH_PAGES_DIR) do
106 | `git push`
107 | end
108 | end
109 |
110 | desc 'Remove gh-pages and pkg directories'
111 | task :clean do
112 | if Dir.exists?(GH_PAGES_DIR)
113 | FileUtils.rm_rf(GH_PAGES_DIR)
114 | end
115 |
116 | if Dir.exists?('pkg')
117 | FileUtils.rm_rf('pkg')
118 | end
119 | end
120 |
121 | Rake::PackageTask.new('Liftoff', Liftoff::VERSION) do |p|
122 | p.need_tar_gz = true
123 | p.package_files.include('src/**/*')
124 | p.package_files.include('defaults/**/*')
125 | p.package_files.include('lib/**/*')
126 | p.package_files.include('templates/**/*')
127 | p.package_files.include('vendor/**/*')
128 | p.package_files.include('man/**/*')
129 | p.package_files.include('LICENSE.txt')
130 | end
131 | end
132 |
133 | desc 'Run tests'
134 | RSpec::Core::RakeTask.new(:spec)
135 |
136 | task :default => :spec
137 |
--------------------------------------------------------------------------------
/bin/liftoff:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # This is a stub of the liftoff executable for development purposes.
4 | # The actual executable is located at src/liftoff
5 |
6 |
7 | $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
8 | require 'liftoff'
9 |
10 | Liftoff::CLI.new(ARGV).run
11 |
--------------------------------------------------------------------------------
/defaults/liftoffrc:
--------------------------------------------------------------------------------
1 | ############################################################################
2 | # The following keys can be used to configure defaults for project creation
3 | # project_name:
4 | # company:
5 | # author:
6 | # prefix:
7 | # company_identifier:
8 | ############################################################################
9 |
10 | test_target_name: UnitTests
11 | configure_git: true
12 | warnings_as_errors: true
13 | enable_static_analyzer: true
14 | indentation_level: 4
15 | use_tabs: false
16 | dependency_managers: cocoapods
17 | enable_settings: true
18 | strict_prompts: false
19 | deployment_target: 8.0
20 | swift_version: 4.0
21 |
22 | run_script_phases:
23 | - file: todo.sh
24 | name: Warn for TODO and FIXME comments
25 | - file: bundle_version.sh
26 | name: Set version number
27 |
28 | templates:
29 | - travis.yml: .travis.yml
30 | - test.sh: bin/test
31 | - setup.sh: bin/setup
32 | - README.md: README.md
33 |
34 | warnings:
35 | - GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED
36 | - GCC_WARN_MISSING_PARENTHESES
37 | - GCC_WARN_ABOUT_RETURN_TYPE
38 | - GCC_WARN_SIGN_COMPARE
39 | - GCC_WARN_CHECK_SWITCH_STATEMENTS
40 | - GCC_WARN_UNUSED_FUNCTION
41 | - GCC_WARN_UNUSED_LABEL
42 | - GCC_WARN_UNUSED_VALUE
43 | - GCC_WARN_UNUSED_VARIABLE
44 | - GCC_WARN_SHADOW
45 | - GCC_WARN_64_TO_32_BIT_CONVERSION
46 | - GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS
47 | - GCC_WARN_ABOUT_MISSING_NEWLINE
48 | - GCC_WARN_UNDECLARED_SELECTOR
49 | - GCC_WARN_TYPECHECK_CALLS_TO_PRINTF
50 | - GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS
51 | - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS
52 | - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF
53 | - CLANG_WARN_IMPLICIT_SIGN_CONVERSION
54 | - CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION
55 | - CLANG_WARN_EMPTY_BODY
56 | - CLANG_WARN_ENUM_CONVERSION
57 | - CLANG_WARN_INT_CONVERSION
58 | - CLANG_WARN_CONSTANT_CONVERSION
59 |
60 | xcode_command: open -a 'Xcode' .
61 |
62 | project_template: swift
63 |
64 | app_target_templates:
65 | objc:
66 | - <%= project_name %>:
67 | - Categories:
68 | - Protocols:
69 | - Headers:
70 | - Models:
71 | - ViewModels:
72 | - Controllers:
73 | - <%= prefix %>AppDelegate.h
74 | - <%= prefix %>AppDelegate.m
75 | - Views:
76 | - Resources:
77 | - Images.xcassets
78 | - Storyboards:
79 | - Main.storyboard
80 | - Nibs:
81 | - LaunchScreen.xib
82 | - Other-Sources:
83 | - Info.plist
84 | - <%= project_name %>-Prefix.pch
85 | - main.m
86 | swift:
87 | - <%= project_name %>:
88 | - Extensions:
89 | - Protocols:
90 | - Models:
91 | - ViewModels:
92 | - Controllers:
93 | - AppDelegate.swift
94 | - Views:
95 | - Resources:
96 | - Images.xcassets
97 | - Storyboards:
98 | - Main.storyboard
99 | - Nibs:
100 | - LaunchScreen.xib
101 | - Other-Sources:
102 | - Info.plist
103 |
104 | test_target_templates:
105 | objc:
106 | - <%= test_target_name %>:
107 | - Resources:
108 | - <%= test_target_name %>-Info.plist
109 | - <%= test_target_name %>-Prefix.pch
110 | - Helpers:
111 | - Tests:
112 | swift:
113 | - <%= test_target_name %>:
114 | - Resources:
115 | - <%= test_target_name %>-Info.plist
116 | - Helpers:
117 | - Tests:
118 |
--------------------------------------------------------------------------------
/homebrew/liftoff.rb:
--------------------------------------------------------------------------------
1 | require 'formula'
2 |
3 | class Liftoff < Formula
4 | homepage 'https://github.com/liftoffcli/liftoff'
5 | url 'http://liftoffcli.github.io/liftoff/Liftoff-__VERSION__.tar.gz'
6 | sha256 '__SHA__'
7 |
8 | depends_on 'xcproj' => :recommended
9 |
10 | def install
11 | prefix.install 'defaults', 'templates', 'vendor'
12 | prefix.install 'lib' => 'rubylib'
13 |
14 | man1.install ['man/liftoff.1']
15 | man5.install ['man/liftoffrc.5']
16 |
17 | bin.install 'src/liftoff'
18 | end
19 |
20 | test do
21 | system "#{bin}/liftoff", '--version'
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/liftoff.rb:
--------------------------------------------------------------------------------
1 | require 'optparse'
2 | require 'fileutils'
3 | require 'yaml'
4 | require 'erb'
5 | require 'etc'
6 | require 'find'
7 |
8 | require 'highline/import'
9 | require 'xcodeproj'
10 |
11 | require 'liftoff/build_configuration_builder'
12 | require 'liftoff/cli'
13 | require "liftoff/dependency_manager_coordinator"
14 | require "liftoff/dependency_manager"
15 | require "liftoff/dependency_managers/bundler"
16 | require "liftoff/dependency_managers/carthage"
17 | require "liftoff/dependency_managers/cocoapods"
18 | require "liftoff/dependency_managers/null_dependency_manager"
19 | require 'liftoff/settings_generator'
20 | require 'liftoff/configuration_parser'
21 | require 'liftoff/deprecation_manager'
22 | require 'liftoff/file_manager'
23 | require 'liftoff/git_setup'
24 | require 'liftoff/launchpad'
25 | require 'liftoff/object_picker'
26 | require 'liftoff/option_fetcher'
27 | require 'liftoff/project'
28 | require 'liftoff/project_builder'
29 | require 'liftoff/project_configuration'
30 | require 'liftoff/scheme_builder'
31 | require 'liftoff/string_renderer'
32 | require 'liftoff/template_finder'
33 | require 'liftoff/template_generator'
34 | require 'liftoff/version'
35 | require 'liftoff/xcodeproj_helper'
36 | require 'liftoff/xcodeproj_monkeypatch'
37 |
--------------------------------------------------------------------------------
/lib/liftoff/build_configuration_builder.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class BuildConfigurationBuilder
3 | def initialize(xcode_project)
4 | @xcode_project = xcode_project
5 | end
6 |
7 | def generate_build_configuration(name, type)
8 | @xcode_project.add_build_configuration(name, type.to_sym)
9 | end
10 |
11 | def generate_build_configurations(build_configurations)
12 | build_configurations ||= []
13 | build_configurations.each do |configuration|
14 | name = configuration["name"]
15 | type = configuration["type"]
16 | generate_build_configuration(name, type)
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/liftoff/cli.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class CLI
3 | def initialize(argv)
4 | @argv = argv
5 | @options = {}
6 | end
7 |
8 | def run
9 | parse_command_line_options
10 | LaunchPad.new.liftoff @options
11 | end
12 |
13 | private
14 |
15 | def parse_command_line_options
16 | global_options.parse!(@argv)
17 | @options[:path] = @argv.first
18 | end
19 |
20 | def global_options
21 | OptionParser.new do |opts|
22 | opts.banner = 'usage: liftoff [-v | --version] [-h | --help] [config options] [path]'
23 |
24 | opts.on('-v', '--version', 'Display the version and exit') do
25 | puts "Version: #{Liftoff::VERSION}"
26 | exit
27 | end
28 |
29 | opts.on('-h', '--help', 'Display this help message and exit') do
30 | puts opts
31 | exit
32 | end
33 |
34 | opts.on('--[no-]strict-prompts', 'Enable/Disable strict prompts') do |strict_prompts|
35 | @options[:strict_prompts] = strict_prompts
36 | end
37 |
38 | opts.on('--dependency-managers [NAME(s)]',
39 | "Comma separated list of dependency managers to enable.\
40 | Available options: cocoapods,carthage,bundler") do |list|
41 | @options[:dependency_managers] = (list || "").split(",")
42 | end
43 |
44 | opts.on('--[no-]cocoapods', 'Enable/Disable Cocoapods') do |use_cocoapods|
45 | @options[:dependency_managers] ||= []
46 |
47 | if use_cocoapods
48 | @options[:dependency_managers] += ["cocoapods"]
49 | else
50 | @options[:dependency_managers] -= ["cocoapods"]
51 | end
52 | end
53 |
54 | opts.on('--[no-]git', 'Enable/Disable git') do |configure_git|
55 | @options[:configure_git] = configure_git
56 | end
57 |
58 | opts.on('--no-open', "Don't open Xcode after generation") do
59 | @options[:xcode_command] = false
60 | end
61 |
62 | opts.on('--[no-]settings', 'Enable/Disable Settings.bundle') do |enable_settings|
63 | @options[:enable_settings] = enable_settings
64 | end
65 |
66 | opts.on('--template [TEMPLATE NAME]', 'Use the specified project template') do |template_name|
67 | @options[:project_template] = template_name
68 | end
69 |
70 | opts.on('-t', '--indentation N', 'Set indentation level') do |indentation_level|
71 | @options[:indentation_level] = indentation_level
72 | end
73 |
74 | opts.on('-n', '--name [PROJECT_NAME]', 'Set project name') do |name|
75 | @options[:project_name] = name
76 | end
77 |
78 | opts.on('-c', '--company [COMPANY]', 'Set project company') do |company|
79 | @options[:company] = company
80 | end
81 |
82 | opts.on('-a', '--author [AUTHOR]', 'Set project author') do |author|
83 | @options[:author] = author
84 | end
85 |
86 | opts.on('-p', '--prefix [PREFIX]', 'Set project prefix') do |prefix|
87 | @options[:prefix] = prefix
88 | end
89 |
90 | opts.on('-i', '--identifier [IDENTIFIER]', 'Set project company ID (com.example)') do |identifier|
91 | @options[:company_identifier] = identifier
92 | end
93 |
94 | opts.on('--test-target-name [TEST_TARGET_NAME]', 'Set the name of the unit test target') do |test_target_name|
95 | @options[:test_target_name] = test_target_name
96 | end
97 | end
98 | end
99 | end
100 | end
101 |
--------------------------------------------------------------------------------
/lib/liftoff/configuration_parser.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class ConfigurationParser
3 |
4 | TEMPLATE_GROUPS = [:app_target_templates, :test_target_templates]
5 |
6 | def initialize(options)
7 | @options = options
8 | end
9 |
10 | def project_configuration
11 | @configuration ||= evaluated_configuration
12 | end
13 |
14 | private
15 |
16 | def evaluated_configuration
17 | initial_configuration.merge(template_groups_configuration)
18 | end
19 |
20 | def initial_configuration
21 | default_configuration
22 | .merge(user_configuration)
23 | .merge(local_configuration)
24 | .merge(@options)
25 | end
26 |
27 | def template_groups_configuration
28 | TEMPLATE_GROUPS.inject({}) do |acc, key|
29 | acc.merge(key => evaluated_templates(key))
30 | end
31 | end
32 |
33 | def evaluated_templates(key)
34 | default_configuration[key]
35 | .merge(user_configuration[key])
36 | .merge(local_configuration[key])
37 | end
38 |
39 | def default_configuration
40 | @default ||= configuration_from_file(File.expand_path('../../../defaults/liftoffrc', __FILE__))
41 | end
42 |
43 | def user_configuration
44 | @user ||= configuration_from_file(ENV['HOME'] + '/.liftoffrc')
45 | end
46 |
47 | def local_configuration
48 | @local ||= configuration_from_file(Dir.pwd + '/.liftoffrc')
49 | end
50 |
51 | def configuration_from_file(path)
52 | hash = if File.exists? path
53 | configuration = YAML.load_file(path)
54 | with_symbolized_keys(configuration)
55 | else
56 | Hash.new
57 | end
58 |
59 | with_default_values(hash)
60 | end
61 |
62 | def with_symbolized_keys(hash)
63 | Hash[hash.map { |key, value| [key.to_sym, value] }]
64 | end
65 |
66 | def with_default_values(hash)
67 | hash.default = {}
68 | hash
69 | end
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/lib/liftoff/dependency_manager.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class DependencyManager
3 | attr_reader :config
4 |
5 | def initialize(config)
6 | @config = config
7 | end
8 |
9 | def setup
10 | raise NotImplementedError.new("#{self.class}#setup must be implemented.")
11 | end
12 |
13 | def install
14 | raise NotImplementedError.new("#{self.class}#install must be implemented.")
15 | end
16 |
17 | def run_script_phases
18 | []
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/liftoff/dependency_manager_coordinator.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class DependencyManagerCoordinator
3 | def initialize(dependencies)
4 | @dependencies = dependencies
5 | end
6 |
7 | def setup_dependencies
8 | dependencies.map(&:setup)
9 | end
10 |
11 | def install_dependencies
12 | dependencies.map(&:install)
13 | end
14 |
15 | def run_script_phases_for_dependencies
16 | dependencies.map(&:run_script_phases).flatten
17 | end
18 |
19 | private
20 |
21 | attr_reader :dependencies
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/liftoff/dependency_managers/bundler.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class Bundler < DependencyManager
3 | def setup
4 | if bundler_installed?
5 | move_gemfile
6 | else
7 | puts "Please install Bundler or disable bundler from liftoff"
8 | end
9 | end
10 |
11 | def install
12 | if bundler_installed?
13 | run_bundle_install
14 | end
15 | end
16 |
17 | private
18 |
19 | def bundler_installed?
20 | system("which bundle > /dev/null")
21 | end
22 |
23 | def move_gemfile
24 | FileManager.new.generate("Gemfile.rb", "Gemfile", config)
25 | end
26 |
27 | def run_bundle_install
28 | puts "Running bundle install"
29 | system("bundle install")
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/liftoff/dependency_managers/carthage.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class Carthage < DependencyManager
3 | def setup
4 | if carthage_installed?
5 | move_cartfile
6 | else
7 | puts 'Please install Carthage or disable carthage from liftoff'
8 | end
9 | end
10 |
11 | def install
12 | if carthage_installed?
13 | run_carthage_update
14 | end
15 | end
16 |
17 | def run_script_phases
18 | [
19 | {
20 | "file" => "copy_frameworks.sh",
21 | "name" => "Copy frameworks (Carthage)",
22 | }
23 | ]
24 | end
25 |
26 | private
27 |
28 | def carthage_installed?
29 | system('which carthage > /dev/null')
30 | end
31 |
32 | def move_cartfile
33 | FileManager.new.generate('Cartfile', 'Cartfile', @config)
34 | end
35 |
36 | def run_carthage_update
37 | puts 'Running carthage update'
38 | system('carthage update')
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/liftoff/dependency_managers/cocoapods.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class Cocoapods < DependencyManager
3 | def setup
4 | if pod_installed?
5 | move_podfile
6 | else
7 | puts 'Please install CocoaPods or disable pods from liftoff'
8 | end
9 | end
10 |
11 | def install
12 | if pod_installed?
13 | run_pod_install
14 | end
15 | end
16 |
17 | private
18 |
19 | def pod_installed?
20 | system('which pod > /dev/null')
21 | end
22 |
23 | def move_podfile
24 | FileManager.new.generate('Podfile', 'Podfile', config)
25 | end
26 |
27 | def run_pod_install
28 | puts 'Running pod install'
29 | system('pod install')
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/liftoff/dependency_managers/null_dependency_manager.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class NullDependencyManager < DependencyManager
3 | def setup; end
4 | def install; end
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/lib/liftoff/deprecation_manager.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class DeprecationManager
3 | DEPRECATIONS = {
4 | :install_todo_script => 'run_script_phases',
5 | :use_cocoapods => 'dependency_managers',
6 | }
7 |
8 | def initialize
9 | @errors = false
10 | end
11 |
12 | def handle_key(key)
13 | replacement = DEPRECATIONS[key]
14 |
15 | if replacement
16 | STDERR.puts "Deprecated key '#{key}' found in liftoffrc!"
17 | STDERR.puts "Please use the new key: #{replacement}"
18 | else
19 | STDERR.puts "Unknown key '#{key}' found in liftoffrc!"
20 | end
21 |
22 | @errors = true
23 | end
24 |
25 | def finish
26 | if @errors
27 | exit 1
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/liftoff/file_manager.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class FileManager
3 | def create_project_dir(name, &block)
4 | FileUtils.mkdir(name)
5 | Dir.chdir(name, &block)
6 | rescue Errno::EEXIST
7 | STDERR.puts "Directory '#{name}' already exists"
8 | exit 1
9 | end
10 |
11 | def generate(template, destination = template, config = ProjectConfiguration.new({}))
12 | create_destination_path(destination)
13 | template_path = TemplateFinder.new.template_path(template)
14 | if template_is_directory?(template_path)
15 | copy_template_directory(template_path, destination, config)
16 | else
17 | create_template_file(destination, template_path, config)
18 | end
19 | end
20 |
21 | def mkdir_gitkeep(path)
22 | dir_path = File.join(*path)
23 | FileUtils.mkdir_p(dir_path)
24 | FileUtils.touch(File.join(dir_path, '.gitkeep'))
25 | end
26 |
27 | def template_contents(filename)
28 | file_path = TemplateFinder.new.template_path(filename)
29 | File.read(file_path)
30 | end
31 |
32 | private
33 |
34 | def create_template_file(destination, template_path, config)
35 | existing_content = existing_file_contents(destination)
36 | move_template(template_path, destination, config)
37 | append_original_file_contents(destination, existing_content)
38 | if File.executable?(template_path)
39 | File.chmod(0755, destination)
40 | end
41 | end
42 |
43 | def copy_template_directory(template, path, config)
44 | destination = File.join(*path)
45 | FileUtils.cp_r(template, destination)
46 | Find.find(destination) do |file|
47 | unless (File.directory?(file))
48 | move_template(file, file, config)
49 | end
50 | end
51 | end
52 |
53 | def existing_file_contents(filename)
54 | if File.exists? filename
55 | puts "#{filename} already exists!"
56 | puts 'We will append the contents of the existing file to the end of the template'
57 | File.read(filename)
58 | end
59 | end
60 |
61 | def create_destination_path(destination)
62 | FileUtils.mkdir_p(File.dirname(destination))
63 | end
64 |
65 | def move_template(template, destination, config)
66 | rendered_template = render_template(template, config)
67 |
68 | File.open(destination, 'w') do |file|
69 | file.write(rendered_template)
70 | end
71 | end
72 |
73 | def render_template(template, config)
74 | StringRenderer.new(config).render(File.read(template))
75 | end
76 |
77 | def append_original_file_contents(filename, original_contents)
78 | if original_contents
79 | File.open(filename, 'a') do |file|
80 | file.write("\n# Original #{filename} contents\n")
81 | file.write(original_contents)
82 | end
83 | end
84 | end
85 |
86 | def template_is_directory?(template_path)
87 | File.directory?(template_path)
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/lib/liftoff/git_setup.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class GitSetup
3 | def initialize(config)
4 | @config = config
5 | end
6 |
7 | def setup
8 | if @config.configure_git
9 | generate_files
10 |
11 | if needs_git_init?
12 | initialize_repo
13 | create_initial_commit
14 | end
15 | end
16 | end
17 |
18 | private
19 |
20 | def generate_files
21 | file_manager.generate('gitignore', '.gitignore', @config)
22 | file_manager.generate('gitattributes', '.gitattributes', @config)
23 | end
24 |
25 | def initialize_repo
26 | `git init`
27 | end
28 |
29 | def create_initial_commit
30 | `git add -A`
31 | `git commit --message='Initial Commit'`
32 | end
33 |
34 | def needs_git_init?
35 | `git rev-parse --git-dir 2>/dev/null`.strip.empty?
36 | end
37 |
38 | def file_manager
39 | @file_manager ||= FileManager.new
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/liftoff/launchpad.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class LaunchPad
3 | EX_NOINPUT = 66
4 |
5 | def liftoff(options)
6 | liftoffrc = ConfigurationParser.new(options).project_configuration
7 | @config = ProjectConfiguration.new(liftoffrc)
8 | if project_exists?
9 | perform_project_actions
10 | else
11 | validate_template
12 | fetch_options
13 |
14 | file_manager.create_project_dir(@config.path) do
15 | generate_project
16 | setup_dependency_managers
17 | generate_templates
18 | generate_settings
19 | install_dependencies
20 | perform_project_actions
21 | open_project
22 | end
23 | end
24 | end
25 |
26 | private
27 |
28 | def validate_template
29 | unless @config.app_target_groups
30 | STDERR.puts "Invalid template name: '#{@config.project_template}'"
31 | exit EX_NOINPUT
32 | end
33 | end
34 |
35 | def fetch_options
36 | OptionFetcher.new(@config).fetch_options
37 | end
38 |
39 | def perform_project_actions
40 | set_indentation_level
41 | enable_warnings
42 | treat_warnings_as_errors
43 | add_script_phases
44 | enable_static_analyzer
45 | perform_extra_config
46 | save_project
47 | generate_git
48 | end
49 |
50 | def setup_dependency_managers
51 | dependency_manager_coordinator.setup_dependencies
52 | end
53 |
54 | def install_dependencies
55 | dependency_manager_coordinator.install_dependencies
56 | end
57 |
58 | def generate_templates
59 | TemplateGenerator.new(@config).generate_templates(file_manager)
60 | end
61 |
62 | def generate_project
63 | ProjectBuilder.new(@config).create_project
64 | end
65 |
66 | def generate_settings
67 | SettingsGenerator.new(@config).generate
68 | end
69 |
70 | def generate_git
71 | GitSetup.new(@config).setup
72 | end
73 |
74 | def set_indentation_level
75 | xcode_helper.set_indentation_level(@config.indentation_level, @config.use_tabs)
76 | end
77 |
78 | def treat_warnings_as_errors
79 | xcode_helper.treat_warnings_as_errors(@config.warnings_as_errors)
80 | end
81 |
82 | def add_script_phases
83 | phases = @config.run_script_phases + dependency_manager_coordinator.run_script_phases_for_dependencies
84 | xcode_helper.add_script_phases(phases)
85 | end
86 |
87 | def enable_warnings
88 | xcode_helper.enable_warnings(@config.warnings)
89 | end
90 |
91 | def perform_extra_config
92 | xcode_helper.perform_extra_config(@config.extra_config, @config.extra_test_config)
93 | end
94 |
95 | def enable_static_analyzer
96 | xcode_helper.enable_static_analyzer(@config.enable_static_analyzer)
97 | end
98 |
99 | def open_project
100 | if @config.xcode_command
101 | `#{@config.xcode_command}`
102 | end
103 | end
104 |
105 | def save_project
106 | xcode_helper.save
107 | end
108 |
109 | def project_exists?
110 | Dir.glob('*.xcodeproj').count > 0
111 | end
112 |
113 | def xcode_helper
114 | @xcode_helper ||= XcodeprojHelper.new(@config)
115 | end
116 |
117 | def file_manager
118 | @file_manager ||= FileManager.new
119 | end
120 |
121 | def dependency_manager_coordinator
122 | @dependency_manager_coordinator ||=
123 | DependencyManagerCoordinator.new(dependency_managers)
124 | end
125 |
126 | def dependency_managers
127 | @dependency_managers ||= [cocoapods, carthage, bundler]
128 | end
129 |
130 | def cocoapods
131 | if @config.dependency_manager_enabled?("cocoapods")
132 | Cocoapods.new(@config)
133 | else
134 | NullDependencyManager.new(@config)
135 | end
136 | end
137 |
138 | def carthage
139 | if @config.dependency_manager_enabled?("carthage")
140 | Carthage.new(@config)
141 | else
142 | NullDependencyManager.new(@config)
143 | end
144 | end
145 |
146 | def bundler
147 | if @config.dependency_manager_enabled?("bundler")
148 | Bundler.new(@config)
149 | else
150 | NullDependencyManager.new(@config)
151 | end
152 | end
153 | end
154 | end
155 |
--------------------------------------------------------------------------------
/lib/liftoff/object_picker.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class ObjectPicker
3 | def self.choose_item(title, objects)
4 | if objects.empty?
5 | STDERR.puts "Could not locate any #{title}s!"
6 | exit 1
7 | elsif objects.size == 1
8 | objects.first
9 | else
10 | choose("Which #{title} would you like to modify?") do |menu|
11 | menu.index = :number
12 | objects.map { |object| menu.choice(object) }
13 | end
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/liftoff/option_fetcher.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class OptionFetcher
3 | def initialize(configuration)
4 | @configuration = configuration
5 | end
6 |
7 | def fetch_options
8 | fetch_option_for(:project_name, 'Project name')
9 | fetch_option_for(:company, 'Company name')
10 | fetch_option_for(:company_identifier, 'Company identifier')
11 | fetch_option_for(:prefix, 'Prefix')
12 | fetch_option_for(:test_target_name, 'Test target name')
13 | end
14 |
15 | private
16 |
17 | def fetch_option_for(attribute, prompt)
18 | default = @configuration.public_send(attribute)
19 | unless skip_prompt?(default)
20 | value = ask("#{prompt}? ") { |q| q.default = default }
21 | @configuration.public_send("#{attribute}=", value)
22 | end
23 | rescue EOFError
24 | puts
25 | fetch_option_for(attribute, prompt)
26 | rescue Interrupt
27 | exit 1
28 | end
29 |
30 | def skip_prompt?(default)
31 | default && @configuration.strict_prompts
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/liftoff/project.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class Project
3 | def initialize(configuration)
4 | @name = configuration.project_name
5 | @deployment_target = configuration.deployment_target
6 | @swift_version = configuration.swift_version
7 | @test_target_name = configuration.test_target_name
8 | set_company_name(configuration.company)
9 | set_prefix(configuration.prefix)
10 | create_build_configurations(configuration.build_configurations)
11 | configure_base_project_settings
12 | end
13 |
14 | def app_target
15 | @app_target ||= new_app_target
16 | end
17 |
18 | def unit_test_target
19 | @unit_test_target ||= new_test_target(@test_target_name)
20 | end
21 |
22 | def save
23 | reorder_groups
24 | xcode_project.save
25 | end
26 |
27 | def new_group(name, path)
28 | xcode_project.new_group(name, path)
29 | end
30 |
31 | def generate_default_scheme
32 | generate_scheme(@name)
33 | end
34 |
35 | def generate_scheme(name)
36 | scheme = Xcodeproj::XCScheme.new
37 | scheme.add_build_target(app_target)
38 | scheme.add_test_target(unit_test_target)
39 | scheme.set_launch_target(app_target)
40 | if block_given?
41 | yield scheme
42 | end
43 | scheme.save_as(xcode_project.path, name)
44 | end
45 |
46 | private
47 |
48 | def reorder_groups
49 | children = xcode_project.main_group.children
50 | frameworks = xcode_project.frameworks_group
51 | products = xcode_project.products_group
52 | children.move(frameworks, -1)
53 | children.move(products, -1)
54 | end
55 |
56 | def new_app_target
57 | target = xcode_project.new_target(:application, @name, :ios)
58 | target.build_configurations.each do |configuration|
59 | configuration.build_settings.delete('OTHER_LDFLAGS')
60 | configuration.build_settings.delete('IPHONEOS_DEPLOYMENT_TARGET')
61 | configuration.build_settings.delete('SKIP_INSTALL')
62 | configuration.build_settings.delete('INSTALL_PATH')
63 | configuration.build_settings['LD_RUNPATH_SEARCH_PATHS'] = ['$(inherited)', '@executable_path/Frameworks']
64 | end
65 | target
66 | end
67 |
68 | def set_prefix(prefix)
69 | xcode_project.root_object.attributes['CLASSPREFIX'] = prefix
70 | end
71 |
72 | def set_company_name(company)
73 | xcode_project.root_object.attributes['ORGANIZATIONNAME'] = company
74 | end
75 |
76 | def create_build_configurations(build_configurations)
77 | builder = BuildConfigurationBuilder.new(xcode_project)
78 | builder.generate_build_configurations(build_configurations)
79 | end
80 |
81 | def new_test_target(name)
82 | target = xcode_project.new_resources_bundle(name, :ios)
83 | target.product_type = 'com.apple.product-type.bundle.unit-test'
84 | target.product_reference.name = "#{name}.xctest"
85 | target.add_dependency(app_target)
86 | configure_search_paths(target)
87 | target.build_configurations.each do |configuration|
88 | configuration.build_settings['BUNDLE_LOADER'] = "$(BUILT_PRODUCTS_DIR)/#{@name}.app/#{@name}"
89 | configuration.build_settings['WRAPPER_EXTENSION'] = 'xctest'
90 | configuration.build_settings['TEST_HOST'] = '$(BUNDLE_LOADER)'
91 | end
92 | target
93 | end
94 |
95 | def configure_base_project_settings
96 | xcode_project.build_configurations.each do |configuration|
97 | configuration.build_settings['CODE_SIGN_IDENTITY[sdk=iphoneos*]'] = 'iPhone Developer'
98 | configuration.build_settings['ASSETCATALOG_COMPILER_APPICON_NAME'] = 'AppIcon'
99 | configuration.build_settings['SDKROOT'] = 'iphoneos'
100 | configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = @deployment_target.to_s
101 | configuration.build_settings['SWIFT_VERSION'] = @swift_version.to_s
102 | end
103 | end
104 |
105 | def configure_search_paths(target)
106 | target.build_configurations.each do |configuration|
107 | configuration.build_settings['FRAMEWORK_SEARCH_PATHS'] = ['$(SDKROOT)/Developer/Library/Frameworks', '$(inherited)', '$(DEVELOPER_FRAMEWORKS_DIR)']
108 | end
109 | end
110 |
111 | def xcode_project
112 | path = Pathname.new("#{@name}.xcodeproj").expand_path
113 | @project ||= Xcodeproj::Project.new(path)
114 | end
115 | end
116 | end
117 |
--------------------------------------------------------------------------------
/lib/liftoff/project_builder.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class ProjectBuilder
3 |
4 | def initialize(config)
5 | @config = config
6 | end
7 |
8 | def create_project
9 | puts "Generating the '#{@config.project_template}' project template"
10 | groups_and_targets.each do |groups, target|
11 | groups.each do |group|
12 | create_tree(group, target)
13 | end
14 | end
15 |
16 | xcode_project.save
17 | scheme_builder.create_schemes
18 | end
19 |
20 | private
21 |
22 | def create_tree(current_element, target, path = [], parent_group = xcode_project)
23 | if template_file?(current_element)
24 | add_file(current_element, target, path, parent_group)
25 | else
26 | create_groups_for_tree(current_element, target, path, parent_group)
27 | end
28 | end
29 |
30 | def add_file(file, target, path, parent_group)
31 | file_manager.mkdir_gitkeep(path)
32 | move_template(path, file)
33 | link_file(file, parent_group, path, target)
34 | end
35 |
36 | def create_groups_for_tree(tree, target, path, parent_group)
37 | tree.each_pair do |raw_directory, children|
38 | name = string_renderer.render(raw_directory)
39 | path += [name]
40 | created_group = create_group(name, path, parent_group)
41 |
42 | if children
43 | children.each do |child|
44 | create_tree(child, target, path, created_group)
45 | end
46 | end
47 | end
48 | end
49 |
50 | def create_group(name, path, parent_group)
51 | file_manager.mkdir_gitkeep(path)
52 | parent_group.new_group(name, name)
53 | end
54 |
55 | def move_template(path, raw_template_name)
56 | rendered_template_name = string_renderer.render(raw_template_name)
57 | destination_template_path = File.join(*path, rendered_template_name)
58 | FileManager.new.generate(raw_template_name, destination_template_path, @config)
59 | end
60 |
61 | def link_file(raw_template_name, parent_group, path, target)
62 | rendered_template_name = string_renderer.render(raw_template_name)
63 | file = parent_group.new_file(rendered_template_name)
64 |
65 | if resource_file?(rendered_template_name)
66 | target.add_resources([file])
67 | elsif linkable_file?(rendered_template_name)
68 | target.add_file_references([file])
69 | else
70 | add_file_to_build_settings(rendered_template_name, path, target)
71 | end
72 | end
73 |
74 | def add_file_to_build_settings(name, path, target)
75 | file_path = File.join(*path, name)
76 |
77 | target.build_configurations.each do |configuration|
78 | if name.end_with?('plist')
79 | configuration.build_settings['INFOPLIST_FILE'] = file_path
80 | elsif name.end_with?('pch')
81 | configuration.build_settings['GCC_PREFIX_HEADER'] = file_path
82 | end
83 | end
84 | end
85 |
86 | def linkable_file?(name)
87 | !name.end_with?('h', 'Info.plist')
88 | end
89 |
90 | def resource_file?(name)
91 | name.end_with?('xcassets', 'bundle', 'xib', 'storyboard')
92 | end
93 |
94 | def template_file?(object)
95 | object.class == String
96 | end
97 |
98 | def groups_and_targets
99 | group_map = {
100 | @config.app_target_groups => xcode_project.app_target,
101 | }
102 |
103 | if @config.test_target_groups
104 | group_map[@config.test_target_groups] = xcode_project.unit_test_target
105 | end
106 |
107 | group_map
108 | end
109 |
110 | def xcode_project
111 | @xcode_project ||= Project.new(@config)
112 | end
113 |
114 | def file_manager
115 | @file_manager ||= FileManager.new
116 | end
117 |
118 | def string_renderer
119 | @renderer ||= StringRenderer.new(@config)
120 | end
121 |
122 | def scheme_builder
123 | @scheme_builder ||= SchemeBuilder.new(xcode_project, @config)
124 | end
125 | end
126 | end
127 |
--------------------------------------------------------------------------------
/lib/liftoff/project_configuration.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class ProjectConfiguration
3 | attr_accessor :project_name,
4 | :company,
5 | :prefix,
6 | :test_target_name,
7 | :configure_git,
8 | :enable_settings,
9 | :warnings_as_errors,
10 | :enable_static_analyzer,
11 | :indentation_level,
12 | :warnings,
13 | :templates,
14 | :project_template,
15 | :app_target_templates,
16 | :test_target_templates,
17 | :dependency_managers,
18 | :run_script_phases,
19 | :strict_prompts,
20 | :xcode_command,
21 | :extra_config,
22 | :extra_test_config,
23 | :deployment_target,
24 | :schemes,
25 | :build_configurations,
26 | :swift_version
27 |
28 | attr_writer :author,
29 | :company_identifier,
30 | :use_tabs,
31 | :path
32 |
33 | def initialize(liftoffrc)
34 | deprecations = DeprecationManager.new
35 | liftoffrc.each_pair do |attribute, value|
36 | if respond_to?("#{attribute}=")
37 | send("#{attribute}=", value)
38 | else
39 | deprecations.handle_key(attribute)
40 | end
41 | end
42 |
43 | deprecations.finish
44 | end
45 |
46 | def author
47 | @author || Etc.getpwuid.gecos.split(',').first
48 | end
49 |
50 | def company_identifier
51 | @company_identifier || "com.#{normalized_company_name}"
52 | end
53 |
54 | def use_tabs
55 | if @use_tabs
56 | '1'
57 | else
58 | '0'
59 | end
60 | end
61 |
62 | def each_template(&block)
63 | return enum_for(__method__) unless block_given?
64 |
65 | templates.each do |template|
66 | template.each_pair(&block)
67 | end
68 | end
69 |
70 | def get_binding
71 | binding
72 | end
73 |
74 | def app_target_groups
75 | @app_target_templates[@project_template]
76 | end
77 |
78 | def test_target_groups
79 | @test_target_templates[@project_template]
80 | end
81 |
82 | def path
83 | @path || project_name
84 | end
85 |
86 | def dependency_manager_enabled?(name)
87 | dependency_managers.include?(name)
88 | end
89 |
90 | private
91 |
92 | def normalized_company_name
93 | company.force_encoding('UTF-8').gsub(/[^0-9a-z]/i, '').downcase
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/lib/liftoff/scheme_builder.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class SchemeBuilder
3 | def initialize(xcode_project, config)
4 | @xcode_project = xcode_project
5 | @config = config
6 | end
7 |
8 | def create_schemes
9 | xcode_project.generate_default_scheme
10 |
11 | config_schemes.each do |scheme_config|
12 | name = name_from_scheme_config(scheme_config)
13 | generate_scheme(name, scheme_config["actions"])
14 | end
15 | end
16 |
17 | private
18 |
19 | attr_reader :xcode_project, :config
20 |
21 | def config_schemes
22 | config.schemes || []
23 | end
24 |
25 | def name_from_scheme_config(scheme_config)
26 | string_renderer.render(scheme_config["name"])
27 | end
28 |
29 | def generate_scheme(name, actions)
30 | actions ||= []
31 | xcode_project.generate_scheme(name) do |scheme|
32 | actions.each do |action, action_config|
33 | add_action_to_scheme(action, action_config, scheme)
34 | end
35 | end
36 | end
37 |
38 | def add_action_to_scheme(action, action_config, scheme)
39 | action_elem = action_from_scheme(action, scheme)
40 |
41 | build_configuration = action_config["build_configuration"]
42 | if build_configuration
43 | action_elem.build_configuration = build_configuration
44 | end
45 | end
46 |
47 | def action_from_scheme(action_name, scheme)
48 | scheme.send("#{action_name.downcase}_action")
49 | end
50 |
51 | def string_renderer
52 | @renderer ||= StringRenderer.new(config)
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/lib/liftoff/settings_generator.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class SettingsGenerator
3 | def initialize(config)
4 | @config = config
5 | end
6 |
7 | def generate
8 | if @config.enable_settings
9 | move_settings_bundle
10 | end
11 | end
12 |
13 | def move_settings_bundle
14 | parent_group = xcode_project[@config.project_name]['Resources']
15 | if (parent_group)
16 | settings_bundle_path = "#{@config.project_name}/Resources/Settings.bundle"
17 | else
18 | parent_group = xcode_project[@config.project_name]
19 |
20 | if (parent_group)
21 | settings_bundle_path = "#{@config.project_name}/Settings.bundle"
22 | else
23 | parent_group = xcode_project.main_group
24 |
25 | if (parent_group)
26 | settings_bundle_path = 'Settings.bundle'
27 | end
28 | end
29 | end
30 |
31 | if (parent_group)
32 | FileManager.new.generate('Settings.bundle', settings_bundle_path, @config)
33 | file_reference = parent_group.new_file('Settings.bundle')
34 | target.add_resources([file_reference])
35 | xcode_project.save
36 | end
37 | end
38 |
39 | private
40 |
41 | def available_targets
42 | xcode_project.targets.to_a.reject { |t| t.name.end_with?('Tests') }
43 | end
44 |
45 | def target
46 | @target ||= ObjectPicker.choose_item('target', available_targets)
47 | end
48 |
49 | def xcode_project
50 | @xcode_project ||= Xcodeproj::Project.open(project_file)
51 | end
52 |
53 | def project_file
54 | @project_file ||= ObjectPicker.choose_item('project', Dir.glob('*.xcodeproj'))
55 | end
56 |
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/lib/liftoff/string_renderer.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class StringRenderer
3 | def initialize(config)
4 | @config = config
5 | end
6 |
7 | def render(string)
8 | ERB.new(string).result(@config.get_binding)
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/liftoff/template_finder.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class TemplateFinder
3 | def template_path(name)
4 | local_template(name) || user_template(name) || default_template(name)
5 | end
6 |
7 | private
8 |
9 | def local_template(name)
10 | path_for_file(File.expand_path('../'), name)
11 | end
12 |
13 | def user_template(name)
14 | path_for_file(ENV['HOME'], name)
15 | end
16 |
17 | def default_template(name)
18 | File.join(templates_dir, name)
19 | end
20 |
21 | def path_for_file(location, name)
22 | path = File.join(location, '.liftoff', 'templates', name)
23 | if File.exists?(path)
24 | path
25 | end
26 | end
27 |
28 | def templates_dir
29 | File.expand_path('../../../templates', __FILE__)
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/liftoff/template_generator.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class TemplateGenerator
3 | def initialize(config)
4 | @config = config
5 | end
6 |
7 | def generate_templates(file_manager)
8 | if @config.templates
9 | @config.each_template do |source, destination|
10 | file_manager.generate(source, destination, @config)
11 | end
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/liftoff/version.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | VERSION = '1.8.3'
3 | end
4 |
--------------------------------------------------------------------------------
/lib/liftoff/xcodeproj_helper.rb:
--------------------------------------------------------------------------------
1 | module Liftoff
2 | class XcodeprojHelper
3 | LAST_INDEX = -1
4 |
5 | def initialize(config)
6 | @test_target_name = config.test_target_name
7 | end
8 |
9 | def treat_warnings_as_errors(enable_errors)
10 | if enable_errors
11 | puts 'Setting GCC_TREAT_WARNINGS_AS_ERRORS for Release builds'
12 | application_target.build_settings('Release')['GCC_TREAT_WARNINGS_AS_ERRORS'] = 'YES'
13 | end
14 | end
15 |
16 | def enable_warnings(warnings)
17 | if warnings
18 | puts 'Setting warnings at the project level'
19 | xcode_project.build_configurations.each do |configuration|
20 | warnings.each do |warning|
21 | configuration.build_settings[warning] = 'YES'
22 | end
23 | end
24 | end
25 | end
26 |
27 | def enable_static_analyzer(enable_static_analyzer)
28 | if enable_static_analyzer
29 | puts 'Turning on Static Analyzer at the project level'
30 | xcode_project.build_configurations.each do |configuration|
31 | configuration.build_settings['RUN_CLANG_STATIC_ANALYZER'] = 'YES'
32 | end
33 | end
34 | end
35 |
36 | def set_indentation_level(level, use_tabs)
37 | if level
38 | puts "Setting the project indentation level to #{level}"
39 | main_group = xcode_project.main_group
40 | main_group.indent_width = level.to_s
41 | main_group.tab_width = level.to_s
42 | main_group.uses_tabs = use_tabs
43 | end
44 | end
45 |
46 | def add_script_phases(scripts)
47 | if scripts
48 | scripts.each do |script|
49 |
50 | file = script['file']
51 | name = script['name']
52 | index = script.fetch('index', LAST_INDEX)
53 |
54 | puts "Adding shell script build phase '#{name}'"
55 | add_shell_script_build_phase(file_manager.template_contents(file), name, index)
56 | end
57 | end
58 | end
59 |
60 | def perform_extra_config(app_config, test_config)
61 | {app_config => application_target, test_config => test_target}.each do |config, target|
62 | if config
63 | config.each do |name, settings|
64 | if name.downcase == "all"
65 | object = target
66 | else
67 | object = target.build_settings(name)
68 | end
69 |
70 | if object
71 | object.merge!(settings)
72 | end
73 | end
74 | end
75 | end
76 | end
77 |
78 | def save
79 | xcode_project.save
80 | end
81 |
82 | private
83 |
84 | def application_target
85 | @target ||= ObjectPicker.choose_item('target', application_targets)
86 | end
87 |
88 | def test_target
89 | @test_target ||= ObjectPicker.choose_item('test target', test_targets)
90 | end
91 |
92 | def application_targets
93 | all_targets.reject { |t| t.name == @test_target_name }
94 | end
95 |
96 | def test_targets
97 | all_targets.select { |t| t.name == @test_target_name }
98 | end
99 |
100 | def all_targets
101 | xcode_project.targets.to_a
102 | end
103 |
104 | def add_shell_script_build_phase(script, name, index)
105 | if build_phase_does_not_exist_with_name?(name)
106 | build_phase = application_target.new_shell_script_build_phase(name)
107 | build_phase.shell_script = script
108 |
109 | application_target.build_phases.delete(build_phase)
110 | application_target.build_phases.insert(index, build_phase)
111 |
112 | xcode_project.save
113 | end
114 | end
115 |
116 | def build_phase_does_not_exist_with_name?(name)
117 | application_target.build_phases.to_a.none? { |phase| phase.display_name == name }
118 | end
119 |
120 | def file_manager
121 | @file_manager ||= FileManager.new
122 | end
123 |
124 | def xcode_project
125 | @xcode_project ||= Xcodeproj::Project.open(project_file)
126 | end
127 |
128 | def project_file
129 | @project_file ||= ObjectPicker.choose_item('project', Dir.glob('*.xcodeproj'))
130 | end
131 | end
132 | end
133 |
--------------------------------------------------------------------------------
/lib/liftoff/xcodeproj_monkeypatch.rb:
--------------------------------------------------------------------------------
1 | module Xcodeproj
2 | class Project
3 | module Object
4 | class PBXNativeTarget
5 | def []=(key, value)
6 | self.build_configurations.each do |configuration|
7 | configuration.build_settings[key] = value
8 | end
9 | end
10 |
11 | def merge!(hash)
12 | hash.each do |key, value|
13 | self[key] = value
14 | end
15 | end
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/man/liftoff.1:
--------------------------------------------------------------------------------
1 | .Dd March 3, 2014
2 | .Dt LIFTOFF 1
3 | .Os
4 | .Sh NAME
5 | .Nm liftoff
6 | .Nd create and configure Xcode projects
7 | .
8 | .Sh SYNOPSIS
9 | .Nm
10 | .Op Fl -version
11 | .Op Fl -help
12 | .Op Fl -[no-]strict-prompts
13 | .Op Fl -dependency-managers Ar NAMES
14 | .Op Fl -[no-]cocoapods
15 | .Op Fl -[no-]git
16 | .Op Fl -no-open
17 | .Op Fl -[no-]settings
18 | .Op Fl -template Ar TEMPLATE_NAME
19 | .Op Fl -indentation Ar N
20 | .Op Fl -name Ar PROJECT_NAME
21 | .Op Fl -company Ar COMPANY_NAME
22 | .Op Fl -author Ar AUTHOR_NAME
23 | .Op Fl -prefix Ar PREFIX
24 | .Op Fl -identifier Ar IDENTIFIER
25 | .Op Fl -test-target-name Ar TEST_TARGET_NAME
26 | .Op Ar PATH
27 | .
28 | .Sh DESCRIPTION
29 | .Nm
30 | is designed to help iOS developers quickly get projects up and running without
31 | spending valuable time performing tedious configurations.
32 | .Pp
33 | Run the
34 | .Nm
35 | command inside a directory.
36 | .Pp
37 | It supports the following options:
38 | .Bl -tag -width "-i, --identifier IDENTIFIER"
39 | .It Fl v , Fl -version
40 | display the version number and exit
41 | .It Fl h , Fl -help
42 | display a help message and exit
43 | .It Fl -[no-]strict-prompts
44 | enable or disable strict prompts. If this is enabled,
45 | .Nm
46 | will only prompt for values that don't have a default set.
47 | .It Fl -dependency-managers Ar NAMES
48 | specify the dependency managers to use for the project
49 | .It Fl -[no-]cocoapods
50 | enable or disable CocoaPods support
51 | .It Fl -[no-]git
52 | enable or disable
53 | .Xr git 1
54 | configuration
55 | .It Fl -no-open
56 | Don't open Xcode after project generation
57 | .It Fl -[no-]settings
58 | enable or disable Settings.bundle
59 | .It Fl -template Ar TEMPLATE_NAME
60 | Use the specified template structure
61 | .It Fl t , Fl -indentation Ar N
62 | set the indentation width in spaces
63 | .It Fl n , Fl -name Ar PROJECT_NAME
64 | set the project name
65 | .It Fl c , Fl -company Ar COMPANY_NAME
66 | set the company name
67 | .It Fl a , Fl -author Ar AUTHOR_NAME
68 | set the author name. If you don't provide a default here or in the
69 | .Xr liftoffrc 5 ,
70 | .Nm
71 | will generate a default value by looking at the
72 | .Ic gecos
73 | field from
74 | .Xr passwd 5 .
75 | .It Fl p , Fl -prefix Ar PREFIX
76 | set the project's prefix
77 | .It Fl i , Fl -identifier Ar IDENTIFIER
78 | set the company identifier (com.mycompany). If you don't provide a default here
79 | or in the
80 | .Xr liftoffrc 5 ,
81 | .Nm
82 | will generate a default from a normalized version of the company name.
83 | .It Fl -test-target-name Ar TEST_TARGET_NAME
84 | set the name for the test target
85 | .El
86 | .Pp
87 | If an Xcode project is found inside the current directory,
88 | .Nm
89 | will configure the existing project.
90 | .Pp
91 | If no existing project is found,
92 | .Nm
93 | will generate a new project structure, create a new Xcode project, and perform
94 | the same configurations.
95 | .Pp
96 | If you pass a custom path on the command line,
97 | .Nm
98 | will use that path as the root folder for the project generation. Otherwise, it
99 | will use the project's name by default.
100 | .Pp
101 | The following configurations will be performed:
102 | .Pp
103 | .Bl -dash -compact -width 2 -offset indent
104 | .It
105 | Set the indentation level on the project (in spaces, 4 by default)
106 | .It
107 | Enable additional warnings at the project level
108 | .It
109 | Treat warnings as errors for release schemes
110 | .It
111 | Add a build phase shell script to turn
112 | .Ic TODO
113 | and
114 | .Ic FIXME
115 | comments into warnings
116 | .It
117 | Add a build phase shell script to automatically set the app's version
118 | information based on git
119 | .It
120 | Turn on static analysis for the project
121 | .It
122 | Perform arbitrary configuration as specified by the user's
123 | .Xr liftoffrc 5
124 | .It
125 | Add default
126 | .Pa .gitignore
127 | and
128 | .Pa .gitattributes
129 | files
130 | .It
131 | Initialize a new
132 | .Ic git
133 | repository and create an initial commit (if needed)
134 | .El
135 | .Pp
136 | .Sh CONFIGURATION
137 | You can use a
138 | .Pa .liftoffrc
139 | file to speed up and customize your configuration by pre-defining your
140 | preferred configurations. This is a YAML file that defines a set of keys and
141 | values for configuring
142 | .Nm .
143 | .Pp
144 | See
145 | .Xr liftoffrc 5
146 | for more detailed information on configuration.
147 | .
148 | .Sh Templates
149 | You can add custom templates or override the default templates by adding them
150 | to
151 | .Pa ~/.liftoff/templates/
152 | or
153 | .Pa ./.liftoff/templates/ .
154 | .Pp
155 | .Nm
156 | will use the same fallback order for the templates that it does for the
157 | .Pa .liftoffrc :
158 | .Bl -enum -offset indent -compact -width 10
159 | .It
160 | Local directory:
161 | .Pa ./.liftoff/templates/
162 | .It
163 | User directory:
164 | .Pa ~/.liftoff/templates/
165 | .It
166 | Default templates:
167 | .Pa /templates/
168 | .El
169 | .Pp
170 | These templates and their filenames will be rendered with
171 | .Ic ERB .
172 | You can use any value available in the
173 | .Ic liftoffrc
174 | configuration inside the template.
175 | .
176 | .Sh FILES
177 | .Pa ~/.liftoffrc
178 | .
179 | .Sh SEE ALSO
180 | .Xr liftoffrc 5
181 | .
182 | .Sh AUTHORS
183 | .Nm
184 | is maintained by
185 | .An "Gordon Fontenot" Aq gordon@thoughtbot.com
186 | and
187 | .Lk http://thoughtbot.com thoughtbot
188 | .Pp
189 | Originally created and conceptualized by
190 | .An "Mark Adams" Aq mark@thoughtbot.com
191 |
--------------------------------------------------------------------------------
/man/liftoffrc.5:
--------------------------------------------------------------------------------
1 | .Dd March 4, 2014
2 | .Dt LIFTOFFRC 5
3 | .Os
4 | .
5 | .Sh NAME
6 | .Nm liftoffrc
7 | .Nd configuration for liftoff
8 | .
9 | .Sh DESCRIPTION
10 | The
11 | .Xr liftoff 1
12 | Xcode configuration tool can be configured using a
13 | .Pa .liftoffrc
14 | file in your home directory, or the directory from which you are running
15 | .Ic liftoff .
16 | This is a YAML file that defines a set of keys and values for configuring
17 | .Ic liftoff .
18 | .Pp
19 | .Ic liftoff
20 | will use the value for a given key from
21 | .Pa ./.liftoffrc ,
22 | then
23 | .Pa ~/.liftoffrc .
24 | If it can't find a value in those files, it will use the default configuration.
25 | .
26 | .Sh USAGE
27 | You can use the lookup order to improve the experience when creating new
28 | projects. For example, you can set the
29 | .Ic author
30 | key inside
31 | .Pa ~/.liftoffrc :
32 | .Pp
33 | .Dl author: Gordon Fontenot
34 | .Pp
35 | Now, any projects you create will have your name set as the author by default.
36 | .Pp
37 | You can also use local
38 | .Nm
39 | files to customize information at a higher level. For example, you can set the
40 | .Ic company
41 | key inside
42 | .Pa ~/dev/work-projects/.liftoffrc :
43 | .Pp
44 | .Dl company: thoughtbot
45 | .Pp
46 | Now, whenever you create projects for work, the company name will default to
47 | .Ic thoughtbot ,
48 | and the author name will default to
49 | .Ic Gordon Fontenot .
50 | .Pp
51 | You can continue to create these for any directory in order to tweak the
52 | settings for projects created inside them. For example, I also have
53 | .Ic company
54 | set inside
55 | .Pa ~/dev/personal/.liftoffrc :
56 | .Pp
57 | .Dl company: Gordon Fontenot
58 | .Pp
59 | I could also set the
60 | .Ic warnings
61 | key to enable different warnings based on client requirements, or personal
62 | preference.
63 | .
64 | .Sh CONFIGURATION
65 | .Bl -tag -width 10
66 | .It Ic project_template
67 | type: string
68 | .br
69 | default:
70 | .Ic swift
71 | .Pp
72 | Set the default template to use for project generation. This key corresponds to
73 | a top level key under
74 | .Ic app_target_templates .
75 | If the key also exists in
76 | .Ic test_target templates ,
77 | then that template will be used for the test target.
78 | .Pp
79 | This setting can be overridden on the command line by using the
80 | .Ic Fl -template Ar TEMPLATE_NAME
81 | option.
82 | .It Ic deployment_target
83 | type: float
84 | .br
85 | default:
86 | .Ic 8.0
87 | .Pp
88 | Set the desired deployment target for this project.
89 | .It Ic swift_version
90 | type: float
91 | .br
92 | default:
93 | .Ic 4.0
94 | .Pp
95 | Set the desired swift version for this project.
96 | .It Ic configure_git
97 | type: boolean
98 | .br
99 | default:
100 | .Ic true
101 | .Pp
102 | Create
103 | .Ic .gitignore ,
104 | .Ic .gitattributes ,
105 | and initialize git repo if needed.
106 | .It Ic warnings_as_errors
107 | type: boolean
108 | .br
109 | default:
110 | .Ic true
111 | .Pp
112 | Turn warnings into errors on release builds.
113 | .It Ic run_script_phases
114 | type: array
115 | .br
116 | default: See
117 | .Sx SCRIPT PHASES
118 | .Pp
119 | Install the specified scripts as individual Run Script Build Phases. The
120 | scripts that you want to add should be located inside either the local
121 | .Pa .liftoff/templates/
122 | directory, or the user's
123 | .Pa ~/.liftoff/templates/
124 | directory.
125 | .Pp
126 | Each item in the array should be a dictionary containing a single key/value
127 | pair. The key for the dictionary will be used as the template script's name.
128 | The value will be used as the name of the script phase inside Xcode.
129 | .It Ic enable_static_analyzer
130 | type: boolean
131 | .br
132 | default:
133 | .Ic true
134 | .Pp
135 | Turn on the static analyzer for the project.
136 | .It Ic indentation_level
137 | type: integer
138 | .br
139 | default:
140 | .Ic 4
141 | .Pp
142 | Set the indentation width on the project (in spaces).
143 | .It Ic use_tabs
144 | type: boolean
145 | .br
146 | default:
147 | .Ic false
148 | .Pp
149 | Configure the project to use spaces or tabs for indentation. As everyone knows,
150 | you should really be using spaces for indentation. If you prefer to be wrong,
151 | however, you can change this configuration to use tabs in your project.
152 | .Pp
153 | Note that this does
154 | .Em not
155 | affect the default templates. If you choose to be incorrect with your
156 | indentation settings, we recommend that you also override the default templates
157 | in order to be consistently wrong.
158 | .It Ic dependency_managers
159 | type: array
160 | .br
161 | default:
162 | .Ic cocoapods
163 | .Pp
164 | Specify the dependency managers to use.
165 | .Pp
166 | Currently, this accepts
167 | .Ic cocoapods ,
168 | .Ic carthage ,
169 | and/or
170 | .Ic bundler
171 | as options. For each dependency manager listed,
172 | .Ic liftoff
173 | will install the default dependency file, as well as perform any necessary
174 | actions.
175 | .Pp
176 | For example, if you specify
177 | .Ic carthage
178 | as your dependency manager,
179 | .Ic liftoff
180 | will link the default
181 | .Pa Cartfile
182 | into place, run
183 | .Ic carthage update ,
184 | and add a run script build phase with Carthage's
185 | .Ic copy-frameworks
186 | command.
187 | .Pp
188 | This replaces the old `use_cocoapods` key.
189 | .It Ic strict_prompts
190 | type: boolean
191 | .br
192 | default:
193 | .Ic false
194 | .Pp
195 | Only prompt for values that don't have a default value.
196 | .Pp
197 | This is useful when combined with command line options to speed up the project
198 | creation process, or when being used in environments where the interactive
199 | prompt can't be used.
200 | .It Ic warnings
201 | type: array
202 | .br
203 | default: See
204 | .Sx WARNINGS
205 | .Pp
206 | Enable the provided warnings for the Application target. Set to
207 | .Ic false
208 | to prevent warnings from being set.
209 | .It Ic templates
210 | type: array
211 | .br
212 | default: See
213 | .Sx TEMPLATES
214 | .Pp
215 | Generate the specified templates for the project. User-defined templates should
216 | be located either inside the local
217 | .Pa .liftoff/templates/
218 | directory, or in the user's
219 | .Pa ~/.liftoff/templates/
220 | directory.
221 | .Pp
222 | Each item in the array should be a single key/value pair. The key for the
223 | dictionary will be used as the template's name, and the value will be used as
224 | the destination relative to the project's root.
225 | .Pp
226 | See
227 | .Sx TEMPLATES
228 | for an example configuration.
229 | .It Ic app_target_templates
230 | type: dictionary
231 | .br
232 | default: See
233 | .Sx TEMPLATE DIRECTORY STRUCTURES
234 | .Pp
235 | Specify template directory structures for the main application target. By
236 | default, this comes with 2 templates:
237 | .Ic swift
238 | and
239 | .Ic objc .
240 | .It Ic test_target_groups
241 | type: dictionary
242 | .br
243 | default: See
244 | .Sx TEMPLATE DIRECTORY STRUCTURES
245 | .Pp
246 | Specify template directory structures for the unit test target. By default,
247 | this comes with 2 templates:
248 | .Ic swift
249 | and
250 | .Ic objc .
251 | .It Ic test_target_name
252 | type: string
253 | .br
254 | default:
255 | .Ic UnitTests
256 | .Pp
257 | Set the name of the unit test target.
258 | .It Ic project_name
259 | type: string
260 | .br
261 | default: none
262 | .Pp
263 | Set the default value for the project name when generating new projects. Not
264 | defined by default.
265 | .It Ic company
266 | type: string
267 | .br
268 | default: none
269 | .Pp
270 | Set the default value for the company name when generating new projects. Not
271 | defined by default.
272 | .It Ic company_identifier
273 | type: string
274 | .br
275 | default: based on company name
276 | .Pp
277 | Set the default value for the company identifier when generating new projects.
278 | Default value is the provided company name, downcased and stripped of special
279 | characters. For example:
280 | .Ic My Company Name!
281 | becomes
282 | .Ic com.mycompanyname .
283 | .It Ic author
284 | type: string
285 | .br
286 | default: Pulled from the
287 | .Ic gecos
288 | field in
289 | .Xr passwd 5
290 | .Pp
291 | Set the default value for the author name when generating new projects. The
292 | current user's name will be automatically set as the default.
293 | .It Ic prefix
294 | type: string
295 | .br
296 | default: none
297 | .Pp
298 | Set the default value for the project prefix when generating new projects. Not
299 | enabled by default.
300 | .It Ic xcode_command
301 | type: string
302 | .br
303 | default:
304 | .Ic open -a 'Xcode' .
305 | .Pp
306 | Set the command used to open the project after generation. By default we open
307 | the current folder with Xcode, which will search for a
308 | .Ic *.xcworkspace
309 | file to open, falling back to a
310 | .Ic *.pbxproj
311 | file if it can't find one.
312 | .Pp
313 | Set this key to
314 | .Ic false
315 | to disable the automatic-open functionality
316 | .It Ic build_configurations
317 | type: dictionary
318 | .br
319 | default: none
320 | .Pp
321 | Add additional build configurations to the project.
322 | By default this key isn't set. See
323 | .Sx BUILD CONFIGURATIONS
324 | for more information on the format of this key.
325 | .It Ic extra_config
326 | type: dictionary
327 | .br
328 | default: none
329 | .Pp
330 | Add additional per-configuration settings to the main application target. By
331 | default this key isn't set. See
332 | .Sx EXTRA CONFIGURATION
333 | for more information on the format of this key.
334 | .It Ic extra_test_config
335 | type: dictionary
336 | .br
337 | default: none
338 | .Pp
339 | Add additional per-configuration settings to the test target. By default this
340 | key isn't set. See
341 | .Sx EXTRA CONFIGURATION
342 | for more information on the format of this key.
343 | .It Ic enable_settings
344 | type: boolean
345 | .br
346 | default:
347 | .Ic true
348 | .Pp
349 | Create a settings bundle. If you also have
350 | .Ic use_cocoapods
351 | enabled, this settings bundle will automatically contain the acknowledgements
352 | for any installed pods.
353 | .It Ic schemes
354 | type: dictionary
355 | .br
356 | default: none
357 | .Pp
358 | Create additional custom schemes. By
359 | default this key isn't set. See
360 | .Sx CUSTOM SCHEMES
361 | for more information on the format of this key.
362 | .El
363 | .
364 | .Sh SCRIPT PHASES
365 | .Ic liftoff
366 | installs two Run Script Build Phases by default:
367 | .Bd -literal
368 | - file: todo.sh
369 | name: Warn for TODO and FIXME comments
370 | .Ed
371 | .Pp
372 | This script turns any
373 | .Ic TODO
374 | or
375 | .Ic FIXME
376 | comments into warnings at compilation time.
377 | .Bd -literal
378 | - file: bundle_version.sh
379 | name: Set the version number
380 | .Ed
381 | .Pp
382 | This script sets the build number based on the number of
383 | .Ic git
384 | commits on
385 | .Ic master ,
386 | and sets the marketing version based on the most recent
387 | .Ic git
388 | tag.
389 | .Pp
390 | You can also add an optional
391 | .Ic index
392 | key to the build phase. The value of this key (an
393 | .Ic integer )
394 | will be used to determine where to insert the build phase when adding it to the
395 | target. This list is zero-indexed. You can use
396 | .Ic -1
397 | to indicate that the script should be added to the end, which is the default
398 | behavior.
399 | .Bd -literal
400 | - file: my_custon_script.sh
401 | name: Run my custom script before anything else
402 | index: 0
403 | .Ed
404 | .
405 | .Sh WARNINGS
406 | .Ic liftoff
407 | enables a set of warnings by default:
408 | .Bl -tag -width 10
409 | .It Ic GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED
410 | Warn if an aggregate or union initializer is not fully bracketed.
411 | .It Ic GCC_WARN_MISSING_PARENTHESES
412 | Warn if parentheses are omitted in certain contexts, such as when there is an
413 | assignment in a context where a truth value is expected, or when operators are
414 | nested whose precedence people often get confused about.
415 | .It Ic GCC_WARN_ABOUT_RETURN_TYPE
416 | Causes warnings to be emitted when a function with a defined return type (not
417 | void) contains a return statement without a return-value. Also emits a warning
418 | when a function is defined without specifying a return type.
419 | .It Ic GCC_WARN_SIGN_COMPARE
420 | Warn when a comparison between signed and unsigned values could produce an
421 | incorrect result when the signed value is converted to unsigned.
422 | .It Ic GCC_WARN_CHECK_SWITCH_STATEMENTS
423 | Warn whenever a switch statement has an index of enumeral type and lacks a case
424 | for one or more of the named codes of that enumeration.
425 | .It Ic GCC_WARN_UNUSED_FUNCTION
426 | Warn whenever a static function is declared but not defined or a non-inline
427 | static function is unused.
428 | .It Ic GCC_WARN_UNUSED_LABEL
429 | Warn whenever a label is declared but not used.
430 | .It Ic GCC_WARN_UNUSED_VALUE
431 | Warn whenever a statement computes a result that is explicitly not used.
432 | .It Ic GCC_WARN_UNUSED_VARIABLE
433 | Warn whenever a local variable or non-constant static variable is unused aside
434 | from its declaration.
435 | .It Ic GCC_WARN_SHADOW
436 | Warn whenever a local variable shadows another local variable, parameter or
437 | global variable or whenever a built-in function is shadowed.
438 | .It Ic GCC_WARN_64_TO_32_BIT_CONVERSION
439 | Warn if a value is implicitly converted from a 64 bit type to a 32 bit type.
440 | .It Ic GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS
441 | Warn if a structure's initializer has some fields missing.
442 | .It Ic GCC_WARN_ABOUT_MISSING_NEWLINE
443 | Warn when a source file does not end with a newline.
444 | .It Ic GCC_WARN_UNDECLARED_SELECTOR
445 | Warn if a
446 | .Ic @selector(...)
447 | expression referring to an undeclared selector is found.
448 | .It Ic GCC_WARN_TYPECHECK_CALLS_TO_PRINTF
449 | Check calls to
450 | .Xr printf 3
451 | and
452 | .Xr scanf 3 ,
453 | etc., to make sure that the arguments supplied have types appropriate to the
454 | format string specified, and that the conversions specified in the format
455 | string make sense.
456 | .It Ic GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS
457 | Warn about the use of deprecated functions, variables, and types (as indicated
458 | by the
459 | .Ic deprecated
460 | attribute).
461 | .It Ic CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATION
462 | Warn if an Objective-C class either subclasses a deprecated class or overrides
463 | a method that has been marked deprecated.
464 | .It Ic CLANG_WARN_OBJC_IMPLICIT_RETAIN_SEL
465 | Warn about implicit retains of 'self' within blocks, which can create a
466 | retain-cycle.
467 | .It Ic CLANG_WARN_IMPLICIT_SIGN_CONVERSION
468 | Warn about implicit integer conversions that change the signedness of an
469 | integer value.
470 | .It Ic CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION
471 | Warn about various implicit conversions that can lose information or are
472 | otherwise suspicious.
473 | .It Ic CLANG_WARN_EMPTY_BODY
474 | Warn about loop bodies that are suspiciously empty.
475 | .It Ic CLANG_WARN_ENUM_CONVERSION
476 | Warn about implicit conversions between different kinds of enum values. For
477 | example, this can catch issues when using the wrong enum flag as an argument to
478 | a function or method.
479 | .It Ic CLANG_WARN_INT_CONVERSION
480 | Warn about implicit conversions between pointers and integers. For example,
481 | this can catch issues when one incorrectly intermixes using
482 | .Ic NSNumber*
483 | and raw integers.
484 | .It Ic CLANG_WARN_CONSTANT_CONVERSION
485 | Warn about implicit conversions of constant values that cause the constant
486 | value to change, either through a loss of precision, or entirely in its
487 | meaning.
488 | .El
489 | .
490 | .Sh TEMPLATES
491 | .Ic liftoff
492 | installs a number of templates by default:
493 | .Bl -tag -width 10
494 | .It Pa travis.yml
495 | This template is installed to
496 | .Pa .travis.yml ,
497 | and contains a default setup for Travis integration.
498 | .It Pa Gemfile.rb
499 | This template is installed to
500 | .Pa Gemfile ,
501 | and contains a set of default gems for use with the project. Right now, it
502 | contains
503 | .Ic XCPretty
504 | and
505 | .Ic CocoaPods
506 | if the cocoapods dependency manager is enabled.
507 | .It Pa test.sh
508 | This template is installed to
509 | .Pa bin/test
510 | and enables the running of tests from the command line. This is used by the
511 | default
512 | .Pa travis.yml
513 | template to determine build status.
514 | .El
515 | .Pp
516 | .Ic liftoff
517 | expects templates in the following format:
518 | .Pp
519 | .Bd -literal
520 | - travis.yml: .travis.yml
521 | .Ed
522 | .Pp
523 | This will install the template named
524 | .Pa travis.yml
525 | found inside the templates directory to
526 | .Pa .travis.yml
527 | inside the project directory.
528 | .Pp
529 | This file will be parsed with ERB with the project configuration, giving you
530 | access to any values that you can set via the
531 | .Nm
532 | .
533 | .Sh TEMPLATE DIRECTORY STRUCTURES
534 | .Ic liftoff
535 | creates default directory and group structures for application and unit test
536 | targets. These structures are specified with language specific keys inside the
537 | .Ic app_target_templates
538 | and
539 | .Ic test_target_templates
540 | configuration keys.
541 | .Ic liftoff
542 | will select the proper structure to build based on the
543 | .Ic project_template
544 | setting either in the
545 | .Nm
546 | or as provided on the command line with the
547 | .Ic Fl -template Ar [TEMPLATE_NAME]
548 | option.
549 | .Pp
550 | An example project template for Objective-C projects might look like the
551 | following:
552 | .Pp
553 | .Bd -literal
554 | app_target_templates:
555 | objc:
556 | - <%= project_name %>:
557 | - Categories:
558 | - Classes:
559 | - Controllers:
560 | - DataSources:
561 | - Delegates:
562 | - <%= prefix %>AppDelegate.h
563 | - <%= prefix %>AppDelegate.m
564 | - Models:
565 | - ViewControllers:
566 | - Views:
567 | - Constants:
568 | - Resources:
569 | - Images.xcassets
570 | - Storyboards:
571 | - Nibs:
572 | - Other-Sources:
573 | - <%= project_name %>-Info.plist
574 | - <%= project_name %>-Prefix.pch
575 | - main.m
576 | .Ed
577 | .Pp
578 | This would override the default
579 | .Ic objc
580 | template for the main application target. This structure would also cause
581 | .Ic liftoff
582 | to generate templates for the
583 | .Ic AppDelegate
584 | class (prepending the proper prefix), as well as
585 | .Ic Info.plist,
586 | .Ic Prefix.pch,
587 | and
588 | .Ic main.m
589 | files. The
590 | .Ic Info.plist
591 | and
592 | .Ic Prefix.pch
593 | will be prepended with the project name.
594 | .
595 | .Pp
596 | See
597 | .Sx TEMPLATES
598 | for more information on
599 | .Ic liftoff 's
600 | templating ability.
601 | .
602 | .Pp
603 | These keys are special, in that you can add template specific keys inside your
604 | user or local
605 | .Nm
606 | files without overriding the defaults. This means that if I want to define a
607 | new
608 | .Ic empty
609 | template, I can do so with the following inside my local or user
610 | .Nm :
611 | .Pp
612 | .Bd -literal
613 | app_target_templates:
614 | empty:
615 | - Files:
616 | .Ed
617 | .Pp
618 | This would create a single
619 | .Pa Files
620 | directory inside the project root, and would not create a test target, since I
621 | haven't defined a template directory structure for that target. You can now
622 | specify this template by name by using
623 | .Ic Fl -template Ar empty
624 | on the command line, or even make it your default by setting
625 | .Ic project_template
626 | in your
627 | .Nm .
628 | .
629 | .Sh BUILD CONFIGURATIONS
630 | .Ic liftoff
631 | can add additional build configurations to the project. In order to do
632 | it, you should add the
633 | .Ic build_configurations
634 | key in your
635 | .Nm ,
636 | and add dictionaries that correspond to the build configurations you'd like to
637 | add.
638 | .Pp
639 | A new build configuration can either be of
640 | .Ic debug
641 | type or
642 | .Ic release .
643 | .Pp
644 | For example, you can create build configurations that will be used when
645 | uploading to the App Store:
646 | .Pp
647 | .Bd -literal
648 | build_configurations:
649 | - name: Debug-AppStore
650 | type: debug
651 | - name: Release-AppStore
652 | type: release
653 | .Ed
654 | .Pp
655 | Note that the value of
656 | .Ic type
657 | key must be either
658 | .Ic debug
659 | or
660 | .Ic release.
661 | .
662 | .Pp
663 | You can use the created build configurations to create a scheme with the
664 | .Pa schemes
665 | key. You can also customize the created configurations with the
666 | .Pa extra_config
667 | key.
668 | .
669 | .Sh EXTRA CONFIGURATION
670 | .Ic liftoff
671 | can perform additional arbitrary configuration to the main application target
672 | or the test target on a per-build configuration basis. In order to add
673 | arbitrary settings, you should add the
674 | .Ic extra_config
675 | or the
676 | .Ic extra_test_config
677 | key in your
678 | .Nm ,
679 | and add dictionaries that correspond to the build configuration you'd like to
680 | modify. For example, to set all warnings on your
681 | .Ic Debug
682 | build configuration, you can set the following:
683 | .Pp
684 | .Bd -literal
685 | extra_config:
686 | Debug:
687 | WARNING_CFLAGS:
688 | - -Weverything
689 | .Ed
690 | .Pp
691 | Note that the key for the build configuration must match the name of the build
692 | configuration you'd like to modify exactly.
693 | .Pp
694 | If you would like to add a setting for all available build configurations, you
695 | can use the special
696 | .Ic all
697 | key in the configuration:
698 | .Pp
699 | .Bd -literal
700 | extra_config:
701 | all:
702 | WARNING_CFLAGS:
703 | - -Weverything
704 | .Ed
705 |
706 | .
707 | .Sh CUSTOM SCHEMES
708 | .Ic liftoff
709 | can create additional custom schemes for your application. To do so, you need
710 | to specify a name and which build configuration will be used for each
711 | .Ic action
712 | (test, launch, profile, archive or analyze). In order to create additional
713 | schemes, you should add the
714 | .Ic schemes
715 | key in your
716 | .Nm ,
717 | and add dictionaries that correspond to the schemes you'd like to create.
718 | For example, to create a scheme that will be used when distributing your
719 | app to the App Store, you can set the following:
720 | .Pp
721 | .Bd -literal
722 | schemes:
723 | - name: <%= project_name %>-AppStore
724 | actions:
725 | test:
726 | build_configuration: Debug
727 | launch:
728 | build_configuration: Debug
729 | profile:
730 | build_configuration: Release
731 | archive:
732 | build_configuration: Release
733 | analyze:
734 | build_configuration: Debug
735 | .Ed
736 | .Pp
737 | Note that the keys inside actions must match the predefined keys and the key
738 | for the build configuration must match the name of the build
739 | configuration you'd like to use exactly.
740 | .Ed
741 | .
742 | .Sh FILES
743 | .Pa ~/.liftoffrc
744 | .
745 | .Sh SEE ALSO
746 | .Xr liftoff 1
747 | .
748 | .Sh AUTHORS
749 | .An "Gordon Fontenot" Aq gordon@thoughtbot.com
750 | and
751 | .Lk http://thoughtbot.com thoughtbot
752 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | rake gems:vendorize && rake release
4 |
--------------------------------------------------------------------------------
/spec/build_configuration_builder_specs.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Liftoff::BuildConfigurationBuilder do
4 | describe "#generate_build_configuration" do
5 | it "creates a build configuration" do
6 | project = double("xcodeproj")
7 | builder = build_configuration_builder(project)
8 |
9 | allow(project).to receive(:add_build_configuration)
10 |
11 | builder.generate_build_configuration("Release-CI", :release)
12 |
13 | expect(project).to have_received(:add_build_configuration).with("Release-CI", :release)
14 | end
15 | end
16 |
17 | describe "#generate_build_configurations" do
18 | it "creates multiple build configurations" do
19 | project = double("xcodeproj")
20 | builder = build_configuration_builder(project)
21 |
22 | allow(project).to receive(:add_build_configuration)
23 |
24 | builder.generate_build_configurations(build_configurations)
25 |
26 | expect(project).to have_received(:add_build_configuration).with("Release-CI", :release)
27 | expect(project).to have_received(:add_build_configuration).with("Debug-CI", :debug)
28 | end
29 | end
30 |
31 | def build_configurations
32 | [
33 | {
34 | "name" => "Release-CI",
35 | "type" => "release",
36 | },
37 | {
38 | "name" => "Debug-CI",
39 | "type" => "debug",
40 | },
41 | ]
42 | end
43 |
44 | def build_configuration_builder(project)
45 | Liftoff::BuildConfigurationBuilder.new(project)
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/spec/configuration_parser_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Liftoff::ConfigurationParser do
4 | describe '#project_configuration' do
5 | it 'returns a hash evaluated from the local, user, default, and command line configurations' do
6 | stub_and_override(:user, :local)
7 |
8 | parser = Liftoff::ConfigurationParser.new(cli_options)
9 |
10 | expected = { :pasta => 0, :pizza => 2, :beer => 0, :cheese_cake => 2, :whiskey => 1 , :app_target_templates => { :foo => {:bar => nil}, :bar => 'baz' }, :test_target_templates => { :foo => nil }}
11 | expect(parser.project_configuration).to eq expected
12 | end
13 |
14 | context 'when no command line options are passed' do
15 | it 'falls back to the user configuration' do
16 | stub_and_override(:user, :local)
17 |
18 | parser = Liftoff::ConfigurationParser.new({})
19 |
20 | expected = { :pasta => 0, :beer => 2, :pizza => 2, :cheese_cake => 2, :app_target_templates => { :foo => {:bar => nil}, :bar => 'baz' }, :test_target_templates => { :foo => nil }}
21 | expect(parser.project_configuration).to eq expected
22 | end
23 | end
24 |
25 | context 'when the local configuration is missing' do
26 | it 'falls back to the user configuration' do
27 | stub_and_override(:user)
28 |
29 | parser = Liftoff::ConfigurationParser.new(cli_options)
30 |
31 | expected = { :pasta => 0, :pizza => 2, :beer => 0, :cheese_cake => 1 , :whiskey => 1, :app_target_templates => { :foo => nil, :bar => 'baz' }, :test_target_templates => { :foo => nil }}
32 | expect(parser.project_configuration).to eq expected
33 | end
34 | end
35 |
36 | context 'when the user configuration is missing' do
37 | it 'falls back to the default' do
38 | stub_and_override(:local)
39 |
40 | parser = Liftoff::ConfigurationParser.new(cli_options)
41 |
42 | expected_project_configuration = { :pasta => 1, :beer => 0, :cheese_cake => 2 , :whiskey => 1, :app_target_templates=>{ :foo => { :bar => nil }}, :test_target_templates => { :foo => nil }}
43 | expect(parser.project_configuration).to eq(expected_project_configuration)
44 | end
45 | end
46 | end
47 |
48 | def stub_and_override(*overrides)
49 | stub_defaults
50 | stub_user(overrides.include?(:user))
51 | stub_local(overrides.include?(:local))
52 | end
53 |
54 | def stub_defaults
55 | liftoffrc_path = File.expand_path('../../defaults/liftoffrc', __FILE__)
56 | default_configuration = { :pasta => 1, :beer => 1, :cheese_cake => 1, :app_target_templates => { :foo => nil }, :test_target_templates => { :foo => nil }}
57 | stub_path(liftoffrc_path, default_configuration, true)
58 | end
59 |
60 | def stub_user(exists)
61 | liftoffrc_path = File.join(ENV['HOME'], '.liftoffrc')
62 | user_configuration = { :pasta => 0, :pizza => 2, :app_target_templates => { :bar => 'baz' }}
63 | stub_path(liftoffrc_path, user_configuration, exists)
64 | end
65 |
66 | def stub_local(exists)
67 | liftoffrc_path = File.join(Dir.pwd, '.liftoffrc')
68 | local_configuration = { :beer => 2, :cheese_cake => 2, :app_target_templates => { :foo => {:bar => nil }}}
69 | stub_path(liftoffrc_path, local_configuration, exists)
70 | end
71 |
72 | def stub_path(path, result, exists)
73 | allow(File).to receive(:exists?).with(path) { exists }
74 | allow(YAML).to receive(:load_file).with(path) { result }
75 | end
76 |
77 | def cli_options
78 | { :beer => 0, :whiskey => 1 }
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/spec/dependency_manager_coordinator_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Liftoff::DependencyManagerCoordinator do
4 | describe "#setup_dependencies" do
5 | it "sets up each depenency" do
6 | deps = mocked_dependencies
7 | manager = Liftoff::DependencyManagerCoordinator.new(deps)
8 | deps.each { |dep| allow(dep).to receive(:setup) }
9 |
10 | manager.setup_dependencies
11 |
12 | deps.each { |dep| expect(dep).to have_received(:setup) }
13 | end
14 | end
15 |
16 | describe "#install_dependencies" do
17 | it "installs each dependency" do
18 | deps = mocked_dependencies
19 | manager = Liftoff::DependencyManagerCoordinator.new(deps)
20 | deps.each { |dep| allow(dep).to receive(:install) }
21 |
22 | manager.install_dependencies
23 |
24 | deps.each { |dep| expect(dep).to have_received(:install) }
25 | end
26 | end
27 |
28 | describe "#run_script_phases_for_dependencies" do
29 | it "returns flattened run_script_phases for dependencies" do
30 | dependency_manager = double_dependency(run_script_phases: [1])
31 | dependency_manager2 = double_dependency(run_script_phases: [2])
32 |
33 | manager = Liftoff::DependencyManagerCoordinator.new(
34 | [dependency_manager, dependency_manager2]
35 | )
36 |
37 | phases = manager.run_script_phases_for_dependencies
38 |
39 | expect(phases).to eq([1,2])
40 | end
41 | end
42 |
43 | def mocked_dependencies
44 | [double_dependency, double_dependency]
45 | end
46 |
47 | def double_dependency(options = {})
48 | double("DependencyManager", options)
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/spec/dependency_manager_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Liftoff::DependencyManager do
4 | describe "#setup" do
5 | it "raises an error when not implemented" do
6 | manager = InvalidDependencyManager.new(:config)
7 |
8 | expect { manager.setup }.to raise_error(NotImplementedError)
9 | end
10 |
11 | it "doesn't raise an error when implemented" do
12 | manager = ValidDependencyManager.new(:config)
13 |
14 | expect { manager.setup }.not_to raise_error
15 | end
16 | end
17 |
18 | describe "#install" do
19 | it "raises an error when not implemented" do
20 | manager = InvalidDependencyManager.new(:config)
21 |
22 | expect { manager.install }.to raise_error(NotImplementedError)
23 | end
24 |
25 | it "doesn't raise an error when implemented" do
26 | manager = ValidDependencyManager.new(:config)
27 |
28 | expect { manager.install }.not_to raise_error
29 | end
30 | end
31 |
32 | describe "#run_script_phases" do
33 | it "provides an empty array as a default" do
34 | manager = Liftoff::DependencyManager.new(:config)
35 |
36 | expect(manager.run_script_phases).to eq([])
37 | end
38 | end
39 |
40 |
41 | class InvalidDependencyManager < Liftoff::DependencyManager; end
42 | class ValidDependencyManager < Liftoff::DependencyManager;
43 | def install; end
44 | def setup; end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/spec/dependency_managers/bundler_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | RSpec.describe Liftoff::Bundler do
4 | describe "#setup" do
5 | it "checks to see if bundler is installed" do
6 | system_call = "which bundle > /dev/null"
7 | bundler = Liftoff::Bundler.new(:config)
8 | allow(bundler).to receive(:system).with(system_call)
9 |
10 | bundler.setup
11 |
12 | expect(bundler).to have_received(:system).with(system_call)
13 | end
14 |
15 | context "when bundler is installed" do
16 | it "asks FileManager to move the Gemfile" do
17 | bundler = Liftoff::Bundler.new(:config)
18 | allow(bundler).to receive(:bundler_installed?).and_return(true)
19 | arguments = ["Gemfile.rb", "Gemfile", :config]
20 | file_manager = double("FileManager")
21 | allow(Liftoff::FileManager).to receive(:new).and_return(file_manager)
22 | allow(file_manager).to receive(:generate).with(*arguments)
23 |
24 | bundler.setup
25 |
26 | expect(file_manager).to have_received(:generate).with(*arguments)
27 | end
28 | end
29 |
30 | context "when bundler is not installed" do
31 | it "puts a message out to the system" do
32 | bundler = Liftoff::Bundler.new(:config)
33 | output_string = "Please install Bundler or disable bundler from liftoff"
34 | allow(bundler).to receive(:bundler_installed?).and_return(false)
35 | allow(bundler).to receive(:puts).with(output_string)
36 |
37 | bundler.setup
38 |
39 | expect(bundler).to have_received(:puts).with(output_string)
40 | end
41 | end
42 | end
43 |
44 | describe "#install" do
45 | context "if bundler is installed" do
46 | it "runs bundle install" do
47 | bundler = Liftoff::Bundler.new(:config)
48 | allow(bundler).to receive(:bundler_installed?).and_return(true)
49 | system_call = "bundle install"
50 | allow(bundler).to receive(:system).with(system_call)
51 |
52 | bundler.install
53 |
54 | expect(bundler).to have_received(:system).with(system_call)
55 | end
56 | end
57 |
58 | context "if bundler is not installed" do
59 | it "does not run bundle install" do
60 | bundler = Liftoff::Bundler.new(:config)
61 | allow(bundler).to receive(:bundler_installed?).and_return(false)
62 | allow(bundler).to receive(:system)
63 |
64 | bundler.install
65 |
66 | expect(bundler).not_to have_received(:system)
67 | end
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/spec/project_configuration_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Liftoff::ProjectConfiguration do
4 | describe '#company_identifier' do
5 | context 'when the identifier is set directly' do
6 | it 'returns the given identifier' do
7 | config = Liftoff::ProjectConfiguration.new({})
8 | config.company_identifier = 'foo'
9 |
10 | expect(config.company_identifier).to eq 'foo'
11 | end
12 | end
13 |
14 | context 'when the identifier is not set directly' do
15 | it 'returns a reverse domain string from the normalized company name' do
16 | config = Liftoff::ProjectConfiguration.new({})
17 | config.company = 'My Cool Company!'
18 |
19 | expect(config.company_identifier).to eq 'com.mycoolcompany'
20 | end
21 |
22 | it 'forces the encoding to utf-8' do
23 | config = Liftoff::ProjectConfiguration.new({})
24 | config.company = "My Cool\xC2\xB7Company!".force_encoding('US-ASCII')
25 |
26 | expect(config.company_identifier).to eq 'com.mycoolcompany'
27 | end
28 | end
29 | end
30 |
31 | describe '#author' do
32 | context 'when the author name is set directly' do
33 | it 'returns the given author name' do
34 | config = Liftoff::ProjectConfiguration.new({})
35 | config.author = 'Bunk Moreland'
36 |
37 | expect(config.author).to eq 'Bunk Moreland'
38 | end
39 | end
40 |
41 | context 'when the author name is not set directly' do
42 | it 'returns the name of the current user from passwd(5)' do
43 | config = Liftoff::ProjectConfiguration.new({})
44 | getpwuid = double('getpwuid', gecos: 'Jimmy McNulty')
45 | expect(Etc).to receive(:getpwuid) { getpwuid }
46 |
47 | expect(config.author).to eq 'Jimmy McNulty'
48 | end
49 | end
50 | end
51 |
52 | describe '#test_target' do
53 | context 'when the test target name is set directly' do
54 | it 'returns the given test target name' do
55 | config = Liftoff::ProjectConfiguration.new({})
56 | config.test_target_name = 'FunkyTests'
57 |
58 | expect(config.test_target_name).to eq 'FunkyTests'
59 | end
60 | end
61 | end
62 |
63 | describe '#each_template' do
64 | it 'returns an array of templates and destinations' do
65 | templates = [
66 | {'foo' => 'bar'},
67 | {'baz' => 'bat'}
68 | ]
69 |
70 | config = Liftoff::ProjectConfiguration.new({:templates => templates})
71 |
72 | expect(config.each_template.to_a).to eq([['foo', 'bar'], ['baz', 'bat']])
73 | end
74 | end
75 |
76 | describe '#app_target_groups' do
77 | context 'when the project_template is set to swift' do
78 | it 'returns the swift app target groups' do
79 | config = build_config('swift')
80 |
81 | expect(config.app_target_groups).to eq({'swift' => 'app'})
82 | end
83 | end
84 |
85 | context 'when the project_template is set to objc' do
86 | it 'returns the objc app target groups' do
87 | config = build_config('objc')
88 |
89 | expect(config.app_target_groups).to eq({'objc' => 'app'})
90 | end
91 | end
92 | end
93 |
94 | describe '#test_target_groups' do
95 | context 'when the project_template is set to swift' do
96 | it 'returns the swift test target groups' do
97 | config = build_config('swift')
98 |
99 | expect(config.test_target_groups).to eq({'swift' => 'test'})
100 | end
101 | end
102 |
103 | context 'when the project_template is set to objc' do
104 | it 'returns the objc test target groups' do
105 | config = build_config('objc')
106 |
107 | expect(config.test_target_groups).to eq({'objc' => 'test'})
108 | end
109 | end
110 | end
111 |
112 | describe '#path' do
113 | context 'when the configuration does not have a path set' do
114 | it 'defaults to the project name' do
115 | config = Liftoff::ProjectConfiguration.new({})
116 | config.project_name = 'My Cool Project'
117 |
118 | expect(config.path).to eq 'My Cool Project'
119 | end
120 | end
121 |
122 | context 'when the configuration has a custom path set' do
123 | it 'uses the provided path' do
124 | config = Liftoff::ProjectConfiguration.new({})
125 | config.project_name = 'My Cool Project'
126 | config.path = 'Another Path'
127 |
128 | expect(config.path).to eq 'Another Path'
129 | end
130 | end
131 |
132 | describe '#schemes' do
133 | it 'returns an array of schemes' do
134 | schemes = [
135 | {
136 | "name" => "<%= project_name %>-CI",
137 | "actions" => {
138 | "test" => {
139 | "build_configuration" => "Debug"
140 | },
141 | "profile" => {
142 | "build_configuration" => "Release"
143 | },
144 | "analyze" => {
145 | "build_configuration" => "Debug"
146 | },
147 | "archive" => {
148 | "build_configuration" => "Release"
149 | },
150 | "launch" => {
151 | "build_configuration" => "Debug"
152 | }
153 | }
154 | }
155 | ]
156 |
157 | config = Liftoff::ProjectConfiguration.new({})
158 | config.schemes = schemes
159 |
160 | expect(config.schemes.to_a).to eq(schemes)
161 | end
162 | end
163 | end
164 |
165 | describe "#dependency_manager_enabled?" do
166 | it "returns true when name is included in list" do
167 | config = Liftoff::ProjectConfiguration.new({})
168 | config.dependency_managers = ["cocoapods"]
169 |
170 | expect(config.dependency_manager_enabled?("cocoapods")).to eq(true)
171 | end
172 |
173 | it "returns false when name is missing from list" do
174 | config = Liftoff::ProjectConfiguration.new({})
175 | config.dependency_managers = ["carthage"]
176 |
177 | expect(config.dependency_manager_enabled?("cocoapods")).to eq(false)
178 | end
179 | end
180 |
181 | describe "#build_configurations" do
182 | it "returns an array of build configurations" do
183 | build_configurations = [
184 | {
185 | "name" => "Debug-CI",
186 | "type" => "debug",
187 | },
188 | {
189 | "name" => "Release-CI",
190 | "type" => "release",
191 | },
192 | ]
193 |
194 | config = Liftoff::ProjectConfiguration.new({})
195 | config.build_configurations = build_configurations
196 |
197 | expect(config.build_configurations).to eq(build_configurations)
198 | end
199 | end
200 |
201 | def build_config(name)
202 | app_templates = build_templates('app')
203 | test_templates = build_templates('test')
204 | Liftoff::ProjectConfiguration.new({
205 | :project_template => name,
206 | :app_target_templates => app_templates,
207 | :test_target_templates => test_templates
208 | })
209 | end
210 |
211 | def build_templates(type)
212 | swift_groups = {'swift' => type}
213 | objc_groups = {'objc' => type}
214 | {'swift' => swift_groups, 'objc' => objc_groups}
215 | end
216 | end
217 |
--------------------------------------------------------------------------------
/spec/scheme_builder_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Liftoff::SchemeBuilder do
4 | describe "#create_schemes" do
5 | it "creates schemes with the right names" do
6 | project = double("xcodeproj")
7 | config = build_config("FakeApp", build_schemes)
8 | scheme_builder = scheme_builder(project, config)
9 |
10 | allow(project).to receive(:generate_default_scheme)
11 | allow(project).to receive(:generate_scheme)
12 |
13 | scheme_builder.create_schemes
14 |
15 | expect(project).to have_received(:generate_default_scheme).with(no_args)
16 | expect(project).to have_received(:generate_scheme).with("FakeApp-CI")
17 | expect(project).to have_received(:generate_scheme).with("FakeApp-AppStore")
18 | end
19 |
20 | it "add build configurations to actions" do
21 | project = double("xcodeproj")
22 | schemes = [build_scheme("<%= project_name %>-CI")]
23 | config = build_config("FakeApp", schemes)
24 | scheme_builder = scheme_builder(project, config)
25 |
26 | scheme = Xcodeproj::XCScheme.new
27 |
28 | allow(project).to receive(:generate_default_scheme)
29 | allow(project).to receive(:generate_scheme).and_yield(scheme)
30 |
31 | scheme_builder.create_schemes
32 |
33 | expect(project).to have_received(:generate_default_scheme).with(no_args)
34 | expect(project).to have_received(:generate_scheme).with("FakeApp-CI")
35 |
36 | expect(scheme.test_action.build_configuration).to eq "Debug-CI"
37 | expect(scheme.profile_action.build_configuration).to eq "Release-CI"
38 | expect(scheme.analyze_action.build_configuration).to eq "Debug-CI"
39 | expect(scheme.archive_action.build_configuration).to eq "Release-CI"
40 | expect(scheme.launch_action.build_configuration).to eq "Debug-CI"
41 | end
42 |
43 | end
44 |
45 | def scheme_builder(project, config)
46 | Liftoff::SchemeBuilder.new(project, config)
47 | end
48 |
49 | def build_config(name, schemes)
50 | Liftoff::ProjectConfiguration.new({
51 | :project_name => name,
52 | :schemes => schemes,
53 | })
54 | end
55 |
56 | def build_scheme(name)
57 | {
58 | "name" => name,
59 | "actions" => {
60 | "test" => {
61 | "build_configuration" => "Debug-CI"
62 | },
63 | "profile" => {
64 | "build_configuration" => "Release-CI"
65 | },
66 | "analyze" => {
67 | "build_configuration" => "Debug-CI"
68 | },
69 | "archive" => {
70 | "build_configuration" => "Release-CI"
71 | },
72 | "launch" => {
73 | "build_configuration" => "Debug-CI"
74 | }
75 | }
76 | }
77 | end
78 |
79 | def build_schemes
80 | [
81 | build_scheme("<%= project_name %>-CI"),
82 | build_scheme("<%= project_name %>-AppStore"),
83 | ]
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rspec'
2 | require 'liftoff'
3 |
--------------------------------------------------------------------------------
/spec/template_finder_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Liftoff::TemplateFinder do
4 | describe '#template_path' do
5 | let (:finder) { described_class.new }
6 |
7 | it 'returns the local file if it exists' do
8 | expect(File).to receive(:exists?).with(local_file('local')) { true }
9 |
10 | expect(finder.template_path('local')).to eq local_file('local')
11 | end
12 |
13 | it 'returns the user file if it exists' do
14 | expect(File).to receive(:exists?).with(user_file('user')) { true }
15 | expect(File).to receive(:exists?).with(local_file('user')) { false }
16 |
17 | expect(finder.template_path('user')).to eq user_file('user')
18 | end
19 |
20 | it 'returns the default template if there is no local or user template' do
21 | expect(File).to receive(:exists?).with(user_file('default')) { false }
22 | expect(File).to receive(:exists?).with(local_file('default')) { false }
23 |
24 | expect(finder.template_path('default')).to eq default_file('default')
25 | end
26 | end
27 |
28 | def local_file(name)
29 | File.join(File.expand_path('..'), '.liftoff', 'templates', name)
30 | end
31 |
32 | def user_file(name)
33 | File.join(ENV['HOME'], '.liftoff', 'templates', name)
34 | end
35 |
36 | def default_file(name)
37 | File.join(File.expand_path('../../templates', __FILE__), name)
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/template_generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Liftoff::TemplateGenerator do
4 | describe '#generate_templates' do
5 | context 'when there are no templates provided' do
6 | it 'does nothing' do
7 | manager = stubbed_file_manager
8 |
9 | generator = Liftoff::TemplateGenerator.new(configuration_with_templates(nil))
10 | generator.generate_templates(manager)
11 |
12 | expect(manager).to_not have_received(:generate)
13 | end
14 | end
15 |
16 | context 'when it is given templates' do
17 | it 'tells the FileManager to generate the templates' do
18 | manager = stubbed_file_manager
19 | templates = [
20 | { 'foo' => 'bar'} ,
21 | { 'baz' => 'quz' }
22 | ]
23 | configuration = configuration_with_templates(templates)
24 |
25 | generator = Liftoff::TemplateGenerator.new(configuration)
26 | generator.generate_templates(manager)
27 |
28 | expect(manager).to have_received(:generate).twice
29 | expect(manager).to have_received(:generate).with('foo', 'bar', configuration)
30 | expect(manager).to have_received(:generate).with('baz', 'quz', configuration)
31 | end
32 | end
33 | end
34 | end
35 |
36 | def configuration_with_templates(templates)
37 | Liftoff::ProjectConfiguration.new({:templates => templates})
38 | end
39 |
40 | def stubbed_file_manager
41 | double('FileManager', :generate => nil)
42 | end
43 |
--------------------------------------------------------------------------------
/src/liftoff:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby --disable-gems
2 |
3 | require 'pathname'
4 |
5 | file_path = Pathname.new(__FILE__).realpath
6 | gems_setup = File.expand_path('../../vendor/gems/setup.rb', file_path)
7 |
8 | require gems_setup
9 |
10 | $LOAD_PATH.unshift(File.expand_path('../../rubylib', file_path))
11 |
12 | require 'liftoff'
13 |
14 | Liftoff::CLI.new(ARGV).run
15 |
--------------------------------------------------------------------------------
/templates/<%= prefix %>AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // <%= prefix %>AppDelegate.h
3 | // <%= project_name %>
4 | //
5 | // Created by <%= author %> on <%= Time.now.strftime("%-m/%-d/%y") %>
6 | // Copyright (c) <%= Time.now.strftime('%Y') %> <%= company %>. All rights reserved.
7 | //
8 |
9 | @interface <%= prefix %>AppDelegate : UIResponder
10 |
11 | @property (strong, nonatomic) UIWindow *window;
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/templates/<%= prefix %>AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // <%= prefix %>AppDelegate.m
3 | // <%= project_name %>
4 | //
5 | // Created by <%= author %> on <%= Time.now.strftime("%-m/%-d/%y") %>
6 | // Copyright (c) <%= Time.now.strftime('%Y') %> <%= company %>. All rights reserved.
7 | //
8 |
9 | #import "<%= prefix %>AppDelegate.h"
10 |
11 | @implementation <%= prefix %>AppDelegate
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/templates/<%= project_name %>-Prefix.pch:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #ifndef __IPHONE_<%= deployment_target.to_s.gsub('.', '_') %>
4 | #warning "This project uses features only available in iOS SDK <%= deployment_target %> and later."
5 | #endif
6 |
7 | #ifdef __OBJC__
8 | @import UIKit;
9 | @import Foundation;
10 | #endif
11 |
--------------------------------------------------------------------------------
/templates/<%= test_target_name %>-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | <%= company_identifier %>.${PRODUCT_NAME:rfc1034identifier}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/templates/<%= test_target_name %>-Prefix.pch:
--------------------------------------------------------------------------------
1 | #import "<%= project_name %>-Prefix.pch"
2 |
3 | #ifdef __OBJC__
4 | <% if dependency_manager_enabled?("cocoapods") %>
5 | #define EXP_SHORTHAND
6 | #import
7 | #import
8 | #import
9 | <% else %>
10 | @import XCTest;
11 | <% end %>
12 | #endif
13 |
--------------------------------------------------------------------------------
/templates/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // <%= project_name %>
4 | //
5 | // Created by <%= author %> on <%= Time.now.strftime("%-m/%-d/%y") %>
6 | // Copyright (c) <%= Time.now.strftime('%Y') %> <%= company %>. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 | }
15 |
--------------------------------------------------------------------------------
/templates/Cartfile:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liftoffcli/liftoff/76566a6f9802e6cffdb4aa7cb4ae51b3bb6e7e6f/templates/Cartfile
--------------------------------------------------------------------------------
/templates/Gemfile.rb:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | <% if enable_settings && dependency_manager_enabled?("cocoapods") %>
4 | gem 'cocoapods'
5 | <% end %>
6 | gem 'xcpretty'
7 |
--------------------------------------------------------------------------------
/templates/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "60x60"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "2x",
11 | "size" : "40x40"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/templates/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | <%= company_identifier %>.${PRODUCT_NAME:rfc1034identifier}
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.0
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UIRequiredDeviceCapabilities
32 |
33 | armv7
34 |
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/templates/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/templates/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/templates/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://github.com/CocoaPods/Specs'
2 |
3 | platform :ios, '<%= deployment_target %>'
4 |
5 | # Uncomment this line if you use Swift
6 | # use_frameworks!
7 |
8 | target '<%= project_name %>' do
9 | # Application Pods
10 | end
11 |
12 | abstract_target :unit_tests do
13 | target '<%= test_target_name %>'
14 | pod 'Specta'
15 | pod 'Expecta'
16 | pod 'OCMock'
17 | pod 'OHHTTPStubs'
18 | end
19 |
20 | <% if enable_settings && dependency_manager_enabled?("cocoapods") %>
21 | # Copy acknowledgements to the Settings.bundle
22 |
23 | post_install do | installer |
24 | require 'fileutils'
25 |
26 | pods_acknowledgements_path = 'Pods/Target Support Files/Pods/Pods-Acknowledgements.plist'
27 | settings_bundle_path = Dir.glob("**/*Settings.bundle*").first
28 |
29 | if File.file?(pods_acknowledgements_path)
30 | puts 'Copying acknowledgements to Settings.bundle'
31 | FileUtils.cp_r(pods_acknowledgements_path, "#{settings_bundle_path}/Acknowledgements.plist", :remove_destination => true)
32 | end
33 | end
34 | <% end %>
35 |
--------------------------------------------------------------------------------
/templates/README.md:
--------------------------------------------------------------------------------
1 | # <%= project_name %> #
2 |
3 | ## Setup ##
4 |
5 | Run `bin/setup`
6 |
7 | This will:
8 |
9 | - Install the gem dependencies
10 | - Install the pod dependencies
11 |
12 | ## Testing ##
13 |
14 | Run `bin/test`
15 |
16 | This will run the tests from the command line, and pipe the result through
17 | [XCPretty][].
18 |
19 | [XCPretty]: https://github.com/supermarin/xcpretty
20 |
--------------------------------------------------------------------------------
/templates/Settings.bundle/Root.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 | <% if dependency_manager_enabled?("cocoapods") %>
8 |
9 | Type
10 | PSGroupSpecifier
11 |
12 |
13 | File
14 | Acknowledgements
15 | Title
16 | Acknowledgements
17 | Type
18 | PSChildPaneSpecifier
19 |
20 | <% end %>
21 |
22 | StringsTable
23 | Root
24 |
25 |
--------------------------------------------------------------------------------
/templates/Settings.bundle/en.lproj/Root.strings:
--------------------------------------------------------------------------------
1 | /* A single strings file, whose title is specified in your preferences schema. The strings files provide the localized content to display to the user for each of your preferences. */
2 |
3 | "Acknowledgements" = "Acknowledgements";
4 |
--------------------------------------------------------------------------------
/templates/bundle_version.sh:
--------------------------------------------------------------------------------
1 | git=$(sh /etc/profile; which git)
2 | git_release_version=$("$git" describe --tags --always --abbrev=0)
3 | number_of_commits=$("$git" rev-list master --count)
4 | target_plist="$TARGET_BUILD_DIR/$INFOPLIST_PATH"
5 | dsym_plist="$DWARF_DSYM_FOLDER_PATH/$DWARF_DSYM_FILE_NAME/Contents/Info.plist"
6 |
7 | for plist in "$target_plist" "$dsym_plist"; do
8 | if [ -f "$plist" ]; then
9 | /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $number_of_commits" "$plist"
10 | /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${git_release_version#*v}" "$plist"
11 | fi
12 | done
13 |
--------------------------------------------------------------------------------
/templates/copy_frameworks.sh:
--------------------------------------------------------------------------------
1 | /usr/local/bin/carthage copy-frameworks
2 |
--------------------------------------------------------------------------------
/templates/gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj merge=union
2 | *.strings text diff
3 |
--------------------------------------------------------------------------------
/templates/gitignore:
--------------------------------------------------------------------------------
1 | # OS X Finder
2 | .DS_Store
3 |
4 | # Xcode per-user config
5 | *.mode1
6 | *.mode1v3
7 | *.mode2v3
8 | *.perspective
9 | *.perspectivev3
10 | *.pbxuser
11 | xcuserdata
12 | *.xccheckout
13 |
14 | # Build products
15 | build/
16 | *.o
17 | *.LinkFileList
18 | *.hmap
19 |
20 | # Automatic backup files
21 | *~.nib/
22 | *.swp
23 | *~
24 | *.dat
25 | *.dep
26 |
27 | # Cocoapods
28 | Pods
29 | Carthage
30 |
31 | # AppCode specific files
32 | .idea/
33 | *.iml
34 |
--------------------------------------------------------------------------------
/templates/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // <%= project_name %>
4 | //
5 | // Created by <%= author %> on <%= Time.now.strftime("%-m/%-d/%y") %>
6 | // Copyright (c) <%= Time.now.strftime('%Y') %> <%= company %>. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 |
11 | #import "<%= prefix %>AppDelegate.h"
12 |
13 | int main(int argc, char *argv[])
14 | {
15 | @autoreleasepool {
16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([<%= prefix %>AppDelegate class]));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/templates/setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | <% if dependency_manager_enabled?("bundler") %>
4 | bundle install
5 | <% end %>
6 | <% if enable_settings && dependency_manager_enabled?("cocoapods") %>
7 | pod install
8 | <% end %>
9 | <% if dependency_manager_enabled?("carthage") %>
10 | carthage update
11 | <% end %>
12 |
--------------------------------------------------------------------------------
/templates/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -o pipefail
4 |
5 | xcodebuild test -workspace <%= project_name %>.xcworkspace -scheme <%= project_name %> -sdk iphonesimulator BUILD_ACTIVE_ARCH=NO | xcpretty -t -c
6 |
--------------------------------------------------------------------------------
/templates/todo.sh:
--------------------------------------------------------------------------------
1 | KEYWORDS="TODO:|FIXME:|\?\?\?:|\!\!\!:"
2 | FILE_EXTENSIONS="swift|h|m|mm|c|cpp"
3 | find -E "${SRCROOT}" -ipath "${SRCROOT}/Carthage" -prune -o -ipath "${SRCROOT}/pods" -prune -o \( -regex ".*\.($FILE_EXTENSIONS)$" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($KEYWORDS).*\$" | perl -p -e "s/($KEYWORDS)/ warning: \$1/"
4 |
--------------------------------------------------------------------------------
/templates/travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 |
3 | branches:
4 | only:
5 | master
6 |
7 | env: export LANG=en_US.UTF-8
8 |
9 | script: bin/test
10 |
--------------------------------------------------------------------------------
/vendor/vendorize:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'bundler'
4 |
5 | EX_USAGE = 64
6 |
7 | unless ARGV.length == 1
8 | $stderr.puts "usage: vendorize path-to-gems"
9 | exit EX_USAGE
10 | end
11 |
12 | TARGET_PATH = ARGV.first
13 | SETUP_PATH = File.join(TARGET_PATH, 'setup.rb')
14 |
15 | FileUtils.mkdir_p(TARGET_PATH)
16 |
17 | File.open(SETUP_PATH, 'w') do |setup_file|
18 | Bundler.definition.specs_for([:dist]).each do |gem|
19 | if gem.name != 'bundler'
20 | system(
21 | '/usr/bin/env', 'gem', 'unpack',
22 | '--target', TARGET_PATH,
23 | '--version', gem.version.to_s,
24 | gem.name
25 | )
26 |
27 | gem.require_paths.each do |path|
28 | setup_file << "$LOAD_PATH.unshift(File.expand_path(%s, __FILE__))\n" % [
29 | "../#{gem.name}-#{gem.version}/#{path}".inspect
30 | ]
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------