crafter 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.idea/.name +1 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/takeoff.iml +24 -0
- data/.idea/vcs.xml +8 -0
- data/.idea/workspace.xml +694 -0
- data/bin/crafter +13 -0
- data/craft.gemspec +20 -0
- data/lib/config/default.rb +108 -0
- data/lib/crafter.rb +103 -0
- data/lib/git_helper.rb +93 -0
- data/lib/project_helper.rb +102 -0
- data/lib/target_configuration.rb +59 -0
- metadata +87 -0
data/bin/crafter
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
$:.push File.expand_path("../../lib", __FILE__)
|
5
|
+
|
6
|
+
if ARGV[0] == 'reset'
|
7
|
+
puts 'copying defaults to ~/.crafter.rb'
|
8
|
+
root = File.expand_path('.', File.dirname(__FILE__))
|
9
|
+
FileUtils.cp("#{root}/../lib/config/default.rb", File.join(Dir.home, '.crafter.rb'))
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'crafter'
|
13
|
+
Crafter::setup_project()
|
data/craft.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = 'crafter'
|
6
|
+
gem.version = '0.1.2'
|
7
|
+
gem.authors = ['Krzysztof Zabłocki']
|
8
|
+
gem.email = ['merowing2@gmail.com']
|
9
|
+
gem.description = %q{CLI for setting up new Xcode projects. Inspired by thoughtbot liftoff.}
|
10
|
+
gem.summary = %q{Define your craft rules once, then apply it to all your Xcode projects.}
|
11
|
+
gem.homepage = 'https://github.com/krzysztofzablocki/crafter'
|
12
|
+
|
13
|
+
gem.add_dependency 'xcodeproj', '~> 0.5.5'
|
14
|
+
gem.add_dependency 'highline', '~> 1.6'
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ['lib']
|
20
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
icon_script = %q[
|
2
|
+
commit=`git rev-parse --short HEAD`
|
3
|
+
branch=`git rev-parse --abbrev-ref HEAD`
|
4
|
+
version=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${INFOPLIST_FILE}"`
|
5
|
+
|
6
|
+
function processIcon() {
|
7
|
+
export PATH=$PATH:/usr/local/bin
|
8
|
+
base_file=$1
|
9
|
+
if [ ! -f $base_file ]; then return; fi
|
10
|
+
|
11
|
+
target_file=`echo $base_file | sed "s/_base//"`
|
12
|
+
target_path="${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/${target_file}"
|
13
|
+
|
14
|
+
if [ $CONFIGURATION = "Release" ]; then
|
15
|
+
cp ${base_file} $target_path
|
16
|
+
return
|
17
|
+
fi
|
18
|
+
|
19
|
+
width=`identify -format %w ${base_file}`
|
20
|
+
|
21
|
+
convert -background '#0008' -fill white -gravity center -size ${width}x40\
|
22
|
+
caption:"${version} ${branch} ${commit}"\
|
23
|
+
${base_file} +swap -gravity south -composite ${target_path}
|
24
|
+
}
|
25
|
+
|
26
|
+
processIcon "Icon_base.png"
|
27
|
+
processIcon "Icon_base@2x.png"
|
28
|
+
processIcon "Icon_base~iPad.png"
|
29
|
+
processIcon "Icon_base@2x~iPad.png"
|
30
|
+
]
|
31
|
+
|
32
|
+
|
33
|
+
test_script = %q[
|
34
|
+
# Launch application using ios-sim and set up environment to inject test bundle into application
|
35
|
+
# Source: http://stackoverflow.com/a/12682617/504494
|
36
|
+
|
37
|
+
echo "ENTERING TEST SCRIPT"
|
38
|
+
|
39
|
+
if [ "$RUN_UNIT_TEST_WITH_IOS_SIM" = "YES" ]; then
|
40
|
+
echo "TESTING_"
|
41
|
+
test_bundle_path="$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.$WRAPPER_EXTENSION"
|
42
|
+
ios-sim launch "$(dirname "$TEST_HOST")" --setenv DYLD_INSERT_LIBRARIES=/../../Library/PrivateFrameworks/IDEBundleInjection.framework/IDEBundleInjection --setenv XCInjectBundle="$test_bundle_path" --setenv XCInjectBundleInto="$TEST_HOST" --args -SenTest All "$test_bundle_path"
|
43
|
+
echo "Finished running tests with ios-sim"
|
44
|
+
else
|
45
|
+
#"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests"
|
46
|
+
fi
|
47
|
+
]
|
48
|
+
|
49
|
+
Crafter.configure do
|
50
|
+
add_platform({:platform => :ios, :deployment => 6.0})
|
51
|
+
add_git_ignore
|
52
|
+
duplicate_configurations({:adhoc => :debug, :profiling => :debug})
|
53
|
+
|
54
|
+
set_options %w(
|
55
|
+
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED
|
56
|
+
GCC_WARN_MISSING_PARENTHESES
|
57
|
+
GCC_WARN_ABOUT_RETURN_TYPE
|
58
|
+
GCC_WARN_SIGN_COMPARE
|
59
|
+
GCC_WARN_CHECK_SWITCH_STATEMENTS
|
60
|
+
GCC_WARN_UNUSED_FUNCTION
|
61
|
+
GCC_WARN_UNUSED_LABEL
|
62
|
+
GCC_WARN_UNUSED_VALUE
|
63
|
+
GCC_WARN_UNUSED_VARIABLE
|
64
|
+
GCC_WARN_SHADOW
|
65
|
+
GCC_WARN_64_TO_32_BIT_CONVERSION
|
66
|
+
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS
|
67
|
+
GCC_WARN_UNDECLARED_SELECTOR
|
68
|
+
GCC_WARN_TYPECHECK_CALLS_TO_PRINTF
|
69
|
+
|
70
|
+
RUN_CLANG_STATIC_ANALYZER
|
71
|
+
GCC_TREAT_WARNINGS_AS_ERRORS
|
72
|
+
)
|
73
|
+
|
74
|
+
with :default do
|
75
|
+
pods << %w(NSLogger-CocoaLumberjack-connector TestFlightSDK)
|
76
|
+
|
77
|
+
add_option :networking do
|
78
|
+
pods << 'AFNetworking'
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
add_option :coredata do
|
83
|
+
pods << 'MagicalRecord'
|
84
|
+
end
|
85
|
+
|
86
|
+
# icon versioning
|
87
|
+
scripts << {:name => 'icon versioning', :script => icon_script}
|
88
|
+
icon_rename = proc do |file|
|
89
|
+
extension = File.extname(file)
|
90
|
+
file_name = File.basename(file, extension)
|
91
|
+
File.rename(file, "#{File.dirname(file)}/#{file_name}_base.#{extension}")
|
92
|
+
end
|
93
|
+
|
94
|
+
Dir['**/Icon.png'].each(&icon_rename)
|
95
|
+
Dir['**/Icon@2x.png'].each(&icon_rename)
|
96
|
+
Dir['**/Icon-72.png'].each(&icon_rename)
|
97
|
+
Dir['**/Icon-72@2x.png'].each(&icon_rename)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
with :tests do
|
102
|
+
add_option :testing do
|
103
|
+
pods << 'Kiwi'
|
104
|
+
scripts << {:name => 'command line unit tests', :script => test_script}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
data/lib/crafter.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require_relative 'project_helper'
|
2
|
+
require_relative 'git_helper'
|
3
|
+
require_relative 'target_configuration'
|
4
|
+
|
5
|
+
module Crafter
|
6
|
+
extend self
|
7
|
+
Crafter::ROOT = File.expand_path('.', File.dirname(__FILE__))
|
8
|
+
|
9
|
+
@targets = {}
|
10
|
+
@add_git_ignore = false
|
11
|
+
@platforms = []
|
12
|
+
|
13
|
+
def configure(&block)
|
14
|
+
instance_eval &block
|
15
|
+
end
|
16
|
+
|
17
|
+
def with(name, &block)
|
18
|
+
self.find_target_configuration(name).instance_eval(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def project
|
22
|
+
@project ||= ProjectHelper.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_target_configuration(name)
|
26
|
+
target = @targets[name]
|
27
|
+
target ||= self.project.select_target_for_name name
|
28
|
+
target_configuration = TargetConfiguration.new(name, target)
|
29
|
+
@targets[name] = target_configuration
|
30
|
+
target_configuration
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_platform(platform_hash)
|
34
|
+
@platforms << platform_hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_git_ignore
|
38
|
+
@add_git_ignore = true
|
39
|
+
end
|
40
|
+
|
41
|
+
def duplicate_configurations(duplication_hash)
|
42
|
+
@configuration = duplication_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def set_options(options)
|
46
|
+
@options = options
|
47
|
+
end
|
48
|
+
|
49
|
+
def setup_project
|
50
|
+
process_optional()
|
51
|
+
process_configurations() unless @configuration.empty?
|
52
|
+
process_options() unless @options.empty?
|
53
|
+
process_git() if @add_git_ignore
|
54
|
+
process_pods()
|
55
|
+
process_scripts()
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_configurations
|
59
|
+
self.project.duplicate_configurations(@configuration)
|
60
|
+
end
|
61
|
+
|
62
|
+
def process_optional
|
63
|
+
@targets.each { |_, v| v.process_optional }
|
64
|
+
end
|
65
|
+
|
66
|
+
def process_options
|
67
|
+
self.project.enable_options(@options)
|
68
|
+
end
|
69
|
+
|
70
|
+
def process_git
|
71
|
+
GitHelper.new.generate_files
|
72
|
+
end
|
73
|
+
|
74
|
+
def process_pods
|
75
|
+
File.open('Podfile', File::WRONLY|File::CREAT|File::EXCL) do |f|
|
76
|
+
|
77
|
+
@platforms.each do |hash|
|
78
|
+
name = hash[:platform]
|
79
|
+
deployment = hash[:deployment]
|
80
|
+
if deployment
|
81
|
+
f.puts "platform :#{name}, '#{deployment}'"
|
82
|
+
else
|
83
|
+
f.puts "platform #{name}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
@targets.each { |_, v| v.write_pods(f) }
|
88
|
+
end
|
89
|
+
|
90
|
+
rescue Exception => e
|
91
|
+
puts "Skipping pod generation - #{e}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def process_scripts
|
95
|
+
@targets.each { |_, v| v.process_scripts(self.project) }
|
96
|
+
end
|
97
|
+
|
98
|
+
if File.exists?(File.join(Dir.home, '.crafter.rb')) then
|
99
|
+
load '~/.crafter.rb'
|
100
|
+
else
|
101
|
+
load "#{Crafter::ROOT}/config/default.rb"
|
102
|
+
end
|
103
|
+
end
|
data/lib/git_helper.rb
ADDED
@@ -0,0 +1,93 @@
|
|
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
|
+
|
24
|
+
GITIGNORE_CONTENTS = <<GITIGNORE
|
25
|
+
# OS X Finder
|
26
|
+
.DS_Store
|
27
|
+
|
28
|
+
# Xcode per-user config
|
29
|
+
*.mode1
|
30
|
+
*.mode1v3
|
31
|
+
*.mode2v3
|
32
|
+
*.perspective
|
33
|
+
*.perspectivev3
|
34
|
+
*.pbxuser
|
35
|
+
xcuserdata
|
36
|
+
|
37
|
+
# Build products
|
38
|
+
build/
|
39
|
+
*.o
|
40
|
+
*.LinkFileList
|
41
|
+
*.hmap
|
42
|
+
|
43
|
+
# Automatic backup files
|
44
|
+
*~.nib/
|
45
|
+
*.swp
|
46
|
+
*~
|
47
|
+
*.dat
|
48
|
+
*.dep
|
49
|
+
|
50
|
+
# AppCode
|
51
|
+
.idea
|
52
|
+
|
53
|
+
GITIGNORE
|
54
|
+
|
55
|
+
GITATTRIBUTES_CONTENTS = '*.pbxproj binary merge=union'
|
56
|
+
|
57
|
+
class GitHelper
|
58
|
+
def initialize
|
59
|
+
if Dir['*.xcodeproj'].empty?
|
60
|
+
puts 'Could not find an Xcode project file. You need to run me from a valid project directory.'
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def generate_files
|
66
|
+
generate_gitignore
|
67
|
+
generate_gitattributes
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def generate_gitignore
|
73
|
+
write_unique_contents_to_file(GITIGNORE_CONTENTS, '.gitignore')
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_gitattributes
|
77
|
+
write_unique_contents_to_file(GITATTRIBUTES_CONTENTS, '.gitattributes')
|
78
|
+
end
|
79
|
+
|
80
|
+
def write_unique_contents_to_file(contents, filename)
|
81
|
+
if File.exists? filename
|
82
|
+
current_file_contents = File.read(filename).split("\n")
|
83
|
+
else
|
84
|
+
current_file_contents = []
|
85
|
+
end
|
86
|
+
|
87
|
+
new_contents = current_file_contents + contents.split("\n")
|
88
|
+
|
89
|
+
File.open(filename, 'w') do |file|
|
90
|
+
file.write(new_contents.uniq.join("\n"))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'xcodeproj'
|
2
|
+
require 'highline/import'
|
3
|
+
|
4
|
+
class ProjectHelper
|
5
|
+
PROJECTS = Dir.glob('*.xcodeproj')
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@project = Xcodeproj::Project.new(xcode_project_file)
|
9
|
+
end
|
10
|
+
|
11
|
+
def enable_options(options)
|
12
|
+
@project.build_configurations.each do |configuration|
|
13
|
+
options.each do |option|
|
14
|
+
configuration.build_settings[option] = 'YES'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
save_changes
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_shell_script(target, name, script)
|
21
|
+
if target.shell_script_build_phases.to_a.index { |phase| phase.name == name }
|
22
|
+
puts "Skipping adding shell script for target #{target} as #{name} already exists"
|
23
|
+
else
|
24
|
+
target.new_shell_script_build_phase(name).shell_script = script
|
25
|
+
save_changes
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def duplicate_configurations(configurations_hash)
|
30
|
+
configurations_hash.each do |name, base|
|
31
|
+
|
32
|
+
@project.targets.each do |target|
|
33
|
+
base_configuration, project_configuration = find_configurations(base, target)
|
34
|
+
|
35
|
+
if !base_configuration || !project_configuration
|
36
|
+
puts "unable to find configurations for #{base}"
|
37
|
+
next
|
38
|
+
end
|
39
|
+
|
40
|
+
target.build_configurations << clone_configuration(base_configuration, name)
|
41
|
+
@project.build_configurations << clone_configuration(project_configuration, name)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
save_changes
|
46
|
+
end
|
47
|
+
|
48
|
+
def select_target_for_name(name)
|
49
|
+
targets = @project.targets.to_a.select { |t| t.name.end_with? name.to_s }
|
50
|
+
targets = @project.targets.to_a if targets.empty?
|
51
|
+
choose_item("Which target should I use for #{name}?", targets)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def clone_configuration(base_configuration, name)
|
57
|
+
build_config = @project.new(Xcodeproj::Project::XCBuildConfiguration)
|
58
|
+
build_config.name = name.to_s
|
59
|
+
build_config.build_settings = base_configuration
|
60
|
+
build_config
|
61
|
+
end
|
62
|
+
|
63
|
+
def find_configurations(base, target)
|
64
|
+
base_configuration = target.build_configuration_list.build_configurations.find { |t| t.name.downcase == base.to_s.downcase }
|
65
|
+
base_configuration = base_configuration.build_settings if base_configuration
|
66
|
+
|
67
|
+
project_configuration = @project.build_configurations.find { |t| t.name.downcase == base.to_s.downcase }
|
68
|
+
project_configuration = project_configuration.build_settings if project_configuration
|
69
|
+
return base_configuration, project_configuration
|
70
|
+
end
|
71
|
+
|
72
|
+
def xcode_project_file
|
73
|
+
@xcode_project_file ||= choose_item('Project', PROJECTS)
|
74
|
+
|
75
|
+
if @xcode_project_file == 'Pods.xcodeproj'
|
76
|
+
raise 'Can not run in the Pods directory. $ cd .. maybe?'
|
77
|
+
end
|
78
|
+
|
79
|
+
@xcode_project_file
|
80
|
+
end
|
81
|
+
|
82
|
+
def choose_item(title, objects)
|
83
|
+
if objects.empty?
|
84
|
+
raise "Could not locate any #{title}s!"
|
85
|
+
elsif objects.size == 1
|
86
|
+
objects.first
|
87
|
+
else
|
88
|
+
choose do |menu|
|
89
|
+
menu.prompt = title
|
90
|
+
objects.map { |object| menu.choice(object) }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def available_targets
|
96
|
+
@project.targets.to_a.delete_if { |t| t.name.end_with?('Tests') }
|
97
|
+
end
|
98
|
+
|
99
|
+
def save_changes
|
100
|
+
@project.save_as xcode_project_file
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class TargetConfiguration
|
2
|
+
attr_accessor :pods
|
3
|
+
attr_accessor :scripts
|
4
|
+
|
5
|
+
def initialize(name, target)
|
6
|
+
@name = name
|
7
|
+
@target = target
|
8
|
+
@pods = []
|
9
|
+
@scripts = []
|
10
|
+
@options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_option(name, &block)
|
14
|
+
@options[name] = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_optional
|
18
|
+
@options.each do |key, obj|
|
19
|
+
key_string = key.to_s
|
20
|
+
if ask_question "do you want to add #{key_string}?"
|
21
|
+
raise unless obj.is_a? Proc
|
22
|
+
obj.call()
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def write_pods(f)
|
28
|
+
f.puts "target :#{@target.name}, :exclusive => true do"
|
29
|
+
pods.flatten.each do |pod|
|
30
|
+
f.puts "pod '#{pod}'"
|
31
|
+
end
|
32
|
+
f.puts 'end'
|
33
|
+
end
|
34
|
+
|
35
|
+
def process_scripts(project)
|
36
|
+
scripts.each do |hash|
|
37
|
+
project.add_shell_script(@target, hash[:name], hash[:script])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def ask_question(question)
|
42
|
+
puts question
|
43
|
+
get_input()
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_input
|
47
|
+
STDOUT.flush
|
48
|
+
input = STDIN.gets.chomp
|
49
|
+
case input.upcase
|
50
|
+
when 'Y'
|
51
|
+
true
|
52
|
+
when 'N'
|
53
|
+
false
|
54
|
+
else
|
55
|
+
puts 'Please enter Y or N'
|
56
|
+
get_input
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|