crafter 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|