├── .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 | # Logo 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 | ![thoughtbot](https://thoughtbot.com/logo.png) 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 | --------------------------------------------------------------------------------