xcode-utils 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '083f71f7700a515fd77cd99b5ea4637c30108c95f86ac796166049644cd525c0'
4
+ data.tar.gz: faeace269689e68f1ed160b8728c57f7845ec834d1b87360c120fde34e26bb7a
5
+ SHA512:
6
+ metadata.gz: 4d51d1e3a1a5a21070ac8a899f8f85baf86fa380e67e2cb48b044c90f674c84b61b289b3e139dde65a3e180caaa66e58ebacc7b0baa0a9d1c0ad0dd372b6427c
7
+ data.tar.gz: 71f167297435f66b9b3d77915b6c3522960b3a497e65fa00bafe97ce80aaa4e9e485f1f47110738fa1150f6fbcaddb84e3a60f0e265a76b8e9ccf0839f152a30
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2020 linecorp
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.
@@ -0,0 +1,117 @@
1
+ # Xcode::Utils
2
+
3
+ [![Gem Version](http://img.shields.io/gem/v/xcode-utils.svg?style=flat)](http://badge.fury.io/rb/xcode-utils)
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ $ gem install xcode-utils
9
+ ```
10
+
11
+ ## UITestRunner
12
+
13
+ ### Install UITestRunner in your xcode project
14
+ ```
15
+ $ cd #{your project root}
16
+
17
+ $ xcutils uitest install --project-name="VVID" --target-config="Debug" --target-scheme="VVID Dev"
18
+ ```
19
+
20
+ ### Xcode configuration has changed automacally
21
+
22
+ New Build Configuration and Build Settings
23
+
24
+ <img src="img/screenshot_01.png"/>
25
+
26
+ <br/>
27
+
28
+ New Build Scheme
29
+
30
+ <img src="img/screenshot_02.png"/>
31
+
32
+ <br/>
33
+
34
+ UITestRunner Template
35
+
36
+ <img src="img/screenshot_03.png"/>
37
+
38
+ ### Implement your authorization provider generated
39
+ ``` swift
40
+ struct UITestAuthorizationProviderImpl: UITestAuthorizationProvider {
41
+
42
+ // MARK: - Implementations of Protocol UITestAuthorizationProvider
43
+
44
+ var hasAccessToken: Bool {
45
+ return false
46
+ }
47
+ var hasRefreshToken: Bool {
48
+ return false
49
+ }
50
+
51
+ func refreshToken(completion: @escaping () -> Void) {
52
+ }
53
+
54
+ func signIn(_ parent: UIViewController, completion: @escaping () -> Void) {
55
+ }
56
+
57
+ func signOut() {
58
+ }
59
+ }
60
+ ```
61
+ ### Implement your test suite generated
62
+ ``` swift
63
+ final class MainTestSuite: UITestSuite {
64
+
65
+ // MARK: - Implementations of abstraction
66
+
67
+ override func setUp() {
68
+ // Put setup code here. This method is called before the invocation of test suite.
69
+ }
70
+
71
+ override func runTests() {
72
+ // Put run tests code here. This method is called before the invocation of test suite.
73
+ testExample()
74
+ }
75
+
76
+ // MARK: - Tests
77
+
78
+ private func testExample() {
79
+ // This is an example of a functional test case.
80
+ }
81
+ }
82
+ ```
83
+
84
+ ## Carthage Wrapper
85
+ ### Execution Flow
86
+ 1. install carthage if it does not exists
87
+ 2. execute carthage command with arguments
88
+ 3. set the xcode config for carthage frameworks
89
+ ```
90
+ $ xcutils carthage #{command} #{arguments...}
91
+
92
+ ex) xcutils carthage update --platform ios
93
+ ```
94
+
95
+ ### Xcode configuration has changed automacally
96
+
97
+ New Build Phases
98
+
99
+ <img src="img/screenshot_04.png"/>
100
+
101
+ <br/>
102
+
103
+ Framework Search Paths
104
+
105
+ <img src="img/screenshot_05.png"/>
106
+
107
+
108
+ ### Clean
109
+ ```
110
+ $ xcutils carthage clean
111
+ ```
112
+
113
+ ## License
114
+
115
+ This project is licensed under the terms of the MIT license. See the [LICENSE](LICENSE) file.
116
+
117
+ > This project and all fastlane tools are in no way affiliated with Apple Inc or Google. This project is open source under the MIT license, which means you have full access to the source code and can modify it to fit your own needs. All fastlane tools run on your own computer or server, so your credentials or other sensitive information will never leave your own computer. You are responsible for how you use fastlane tools.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if $PROGRAM_NAME == __FILE__
4
+ ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
5
+ require 'rubygems'
6
+ require 'bundler/setup'
7
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
8
+ end
9
+
10
+ require 'xcutils'
11
+
12
+ XcodeUtils::Command.run(ARGV)
@@ -0,0 +1,6 @@
1
+ require 'xcutils/command'
2
+ require 'xcutils/carthage'
3
+ require 'xcutils/version'
4
+
5
+ module XcodeUtils
6
+ end
@@ -0,0 +1,55 @@
1
+ require 'xcutils/carthage/xcconfig'
2
+
3
+ module XcodeUtils
4
+ class Command
5
+ class Carthage < Command
6
+ self.summary = 'Carthage command wrapper.'
7
+ self.command = 'carthage'
8
+
9
+ def self.install_bash_file_path
10
+ File.expand_path('../../../sh/install_carthage.sh', __FILE__)
11
+ end
12
+
13
+ def self.options
14
+ []
15
+ end
16
+
17
+ def initialize(argv)
18
+ super
19
+ @additional_args = argv.remainder!
20
+ end
21
+
22
+ def validate!
23
+ end
24
+
25
+ def run
26
+ path = File.expand_path('../../../sh/install_carthage.sh', __FILE__)
27
+ system("bash #{Carthage::install_bash_file_path}")
28
+ system("carthage #{@additional_args.join(" ")}")
29
+ XcodeUtils::Carthage::XcodeConfig.new.run
30
+ end
31
+
32
+ class Clean < Carthage
33
+ self.summary = 'Cleanup Carthage.'
34
+ self.arguments = []
35
+
36
+ def self.options
37
+ []
38
+ end
39
+
40
+ def initialize(argv)
41
+ super
42
+ @additional_args = argv.remainder!
43
+ end
44
+
45
+ def validate!
46
+ end
47
+
48
+ def run
49
+ XcodeUtils::Carthage::XcodeConfig.new.clean
50
+ system("rm -rf ./Carthage")
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,26 @@
1
+
2
+ class String
3
+ def black; "\e[30m#{self}\e[0m" end
4
+ def red; "\e[31m#{self}\e[0m" end
5
+ def green; "\e[32m#{self}\e[0m" end
6
+ def brown; "\e[33m#{self}\e[0m" end
7
+ def blue; "\e[34m#{self}\e[0m" end
8
+ def magenta; "\e[35m#{self}\e[0m" end
9
+ def cyan; "\e[36m#{self}\e[0m" end
10
+ def gray; "\e[37m#{self}\e[0m" end
11
+
12
+ def bg_black; "\e[40m#{self}\e[0m" end
13
+ def bg_red; "\e[41m#{self}\e[0m" end
14
+ def bg_green; "\e[42m#{self}\e[0m" end
15
+ def bg_brown; "\e[43m#{self}\e[0m" end
16
+ def bg_blue; "\e[44m#{self}\e[0m" end
17
+ def bg_magenta; "\e[45m#{self}\e[0m" end
18
+ def bg_cyan; "\e[46m#{self}\e[0m" end
19
+ def bg_gray; "\e[47m#{self}\e[0m" end
20
+
21
+ def bold; "\e[1m#{self}\e[22m" end
22
+ def italic; "\e[3m#{self}\e[23m" end
23
+ def underline; "\e[4m#{self}\e[24m" end
24
+ def blink; "\e[5m#{self}\e[25m" end
25
+ def reverse_color; "\e[7m#{self}\e[27m" end
26
+ end
@@ -0,0 +1,198 @@
1
+ require 'xcodeproj'
2
+ require 'xcutils/carthage/colors'
3
+
4
+ module XcodeUtils
5
+ module Carthage
6
+ class XcodeConfig
7
+ def initialize
8
+ @build_path = './Carthage/Build/iOS'
9
+ @embed_framework_name = 'Embed Carthage Frameworks'
10
+ @framework_search_paths = '$(PROJECT_DIR)/Carthage/Build/iOS'
11
+ @remove_unwanted_framework_architectures = 'Remove Unwanted Framework Architectures'
12
+ @remove_unwanted_framework_architectures_script = get_file_as_string(File.expand_path('../../../../sh/remove_unwanted_architectures.sh', __FILE__))
13
+ end
14
+
15
+ def run
16
+ open_project
17
+ begin_xcconfig
18
+ end
19
+
20
+ def clean
21
+ open_project
22
+ begin_cleanup
23
+ end
24
+
25
+ def open_project
26
+ if !Dir.exist?(@build_path)
27
+ abort("😭 The Carthage build has not yet been successful. Please try again after successful arthage build.")
28
+ end
29
+
30
+ project_names = Dir["./*.xcodeproj"]
31
+
32
+ if project_names.first.nil?
33
+ abort("😭 Does not exist project to configure.")
34
+ end
35
+
36
+ puts "💎💎 Project Found -> #{project_names.first} 💎💎"
37
+
38
+ @project = Xcodeproj::Project.open(project_names.first)
39
+ end
40
+
41
+ def begin_cleanup
42
+ puts "🙏 Start cleanup..".green
43
+
44
+ @project.targets.each do |target|
45
+ puts "👻 Clean target -> #{target.name}"
46
+
47
+ exist_build_phase = target.build_phases.find { |build_phase| build_phase.class == Xcodeproj::Project::Object::PBXCopyFilesBuildPhase && build_phase.name == @embed_framework_name }
48
+ exist_arch_build_phase = target.build_phases.find { |build_phase| build_phase.class == Xcodeproj::Project::Object::PBXShellScriptBuildPhase && build_phase.name == @remove_unwanted_framework_architectures }
49
+
50
+ if !exist_build_phase.nil?
51
+ puts "Delete exist embed carthage framework in build phases".gray
52
+ exist_build_phase.files_references.each do |reference|
53
+ reference.remove_from_project
54
+ end
55
+ exist_build_phase.clear
56
+ target.build_phases.delete(exist_build_phase)
57
+ end
58
+
59
+ if !exist_arch_build_phase.nil?
60
+ puts "Delete exist remove unwanted framework architectures in build phases".gray
61
+ exist_arch_build_phase.clear
62
+ target.build_phases.delete(exist_arch_build_phase)
63
+ end
64
+
65
+ target.build_configurations.each do |config|
66
+ paths = config.build_settings['FRAMEWORK_SEARCH_PATHS']
67
+
68
+ if paths.include?(@framework_search_paths)
69
+ paths.delete(@framework_search_paths)
70
+ config.build_settings['FRAMEWORK_SEARCH_PATHS'] = paths
71
+ puts "#{config}: Delete #{@framework_search_paths} in FRAMEWORK_SEARCH_PATHS".gray
72
+ end
73
+ end
74
+ puts "\n"
75
+ end
76
+
77
+ @project.save()
78
+
79
+ puts "👌 Finish cleanup".green
80
+ end
81
+
82
+ def begin_xcconfig
83
+ puts "🙏 Start xcconfig..".green
84
+
85
+ new_build_phase = nil
86
+ new_arch_build_phase = nil
87
+
88
+ @project.targets.each do |target|
89
+ puts "👻 Build target -> #{target.name}"
90
+
91
+ exist_build_phase = target.build_phases.find { |build_phase| build_phase.class == Xcodeproj::Project::Object::PBXCopyFilesBuildPhase && build_phase.name == @embed_framework_name }
92
+ exist_arch_build_phase = target.build_phases.find { |build_phase| build_phase.class == Xcodeproj::Project::Object::PBXShellScriptBuildPhase && build_phase.name == @remove_unwanted_framework_architectures }
93
+
94
+ if !exist_build_phase.nil?
95
+ puts "Delete exist embed carthage framework in build phases".gray
96
+ exist_build_phase.files_references.each do |reference|
97
+ reference.remove_from_project
98
+ end
99
+ exist_build_phase.clear
100
+ target.build_phases.delete(exist_build_phase)
101
+ end
102
+
103
+ if new_build_phase.nil?
104
+ new_build_phase = create_embed_frameworks_build_phase
105
+ add_embedded_binaries(new_build_phase)
106
+ puts "Insert new embed carthage framework #{new_build_phase} into build phases".gray
107
+ end
108
+
109
+ if !exist_arch_build_phase.nil?
110
+ puts "Delete exist remove unwanted framework architectures in build phases".gray
111
+ exist_arch_build_phase.clear
112
+ target.build_phases.delete(exist_arch_build_phase)
113
+ end
114
+
115
+ if new_arch_build_phase.nil?
116
+ new_arch_build_phase = create_shell_script_build_phase(@remove_unwanted_framework_architectures)
117
+ new_arch_build_phase.shell_script = @remove_unwanted_framework_architectures_script
118
+ add_input_files(new_arch_build_phase)
119
+ puts "Insert new remove unwanted framework architectures #{new_arch_build_phase} into build phases".gray
120
+ end
121
+
122
+ target.build_phases << new_build_phase
123
+ target.build_phases << new_arch_build_phase
124
+
125
+ puts "#{target.build_configurations}"
126
+
127
+ target.build_configurations.each do |config|
128
+ paths = config.build_settings['FRAMEWORK_SEARCH_PATHS']
129
+
130
+ if !paths.include?(@framework_search_paths)
131
+ paths << @framework_search_paths
132
+ config.build_settings['FRAMEWORK_SEARCH_PATHS'] = paths
133
+ puts "#{config}: Insert #{@framework_search_paths} into FRAMEWORK_SEARCH_PATHS".gray
134
+ end
135
+ end
136
+ puts "\n"
137
+ end
138
+
139
+ @project.save()
140
+
141
+ puts "👌 Finish xcconfig".green
142
+ end
143
+
144
+ def get_file_as_string(filename)
145
+ data = ''
146
+ f = File.open(filename, "r")
147
+ f.each_line do |line|
148
+ data += line
149
+ end
150
+ return data
151
+ end
152
+
153
+ def create_embed_frameworks_build_phase
154
+ build_phase = @project.new(Xcodeproj::Project::Object::PBXCopyFilesBuildPhase)
155
+ build_phase.name = @embed_framework_name
156
+ build_phase.symbol_dst_subfolder_spec = :frameworks
157
+ return build_phase
158
+ end
159
+
160
+ def create_shell_script_build_phase(name)
161
+ build_phase = @project.new(Xcodeproj::Project::Object::PBXShellScriptBuildPhase)
162
+ build_phase.name = name
163
+ return build_phase
164
+ end
165
+
166
+ def add_embedded_binaries(build_phase)
167
+ input_paths = []
168
+ Dir.entries(@build_path).each do |entry|
169
+ matched = /^(.*)\.framework$/.match(entry)
170
+
171
+ if !matched.nil?
172
+ frameworks_group = @project.groups.find { |group| group.display_name == 'Frameworks' }
173
+ framework_ref = frameworks_group.new_file("Carthage/Build/iOS/#{matched.string}")
174
+ build_file = build_phase.add_file_reference(framework_ref)
175
+ build_file.settings = { 'ATTRIBUTES' => ['CodeSignOnCopy', 'RemoveHeadersOnCopy'] }
176
+ input_paths.push("${SRCROOT}/Carthage/Build/iOS/#{matched.string}")
177
+ puts "framework_ref -> #{framework_ref}".gray
178
+ end
179
+ end
180
+ end
181
+
182
+ def add_input_files(build_phase)
183
+ input_paths = []
184
+
185
+ Dir.entries(@build_path).each do |entry|
186
+ matched = /^(.*)\.framework$/.match(entry)
187
+ if !matched.nil?
188
+ input_paths.push("${SRCROOT}/Carthage/Build/iOS/#{matched.string}")
189
+ end
190
+ end
191
+
192
+ build_phase.input_paths = input_paths
193
+
194
+ puts "Add input files to run script -> #{input_paths}".gray
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,14 @@
1
+ require 'claide'
2
+ require 'xcutils/version'
3
+
4
+ module XcodeUtils
5
+ class Command < CLAide::Command
6
+ require 'xcutils/uitest'
7
+ require 'xcutils/carthage'
8
+
9
+ self.abstract_command = true
10
+ self.command = 'xcutils'
11
+ self.version = VERSION
12
+ self.description = 'Xcode utils.'
13
+ end
14
+ end
@@ -0,0 +1,184 @@
1
+ require 'xcodeproj'
2
+ require 'plist'
3
+ require 'nokogiri'
4
+
5
+ module XcodeUtils
6
+ class Command
7
+ class UITest < Command
8
+ self.abstract_command = true
9
+ self.command = 'uitest'
10
+
11
+ class Install < UITest
12
+ self.summary = 'Install uitest.'
13
+
14
+ self.arguments = [
15
+ CLAide::Argument.new('PROJECT_NAME', :true),
16
+ CLAide::Argument.new('BASE_CONFIG_NAME', :true),
17
+ CLAide::Argument.new('BASE_SCHEME_NAME', :true)
18
+ ]
19
+
20
+ def self.options
21
+ [
22
+ ['--project-name=""', 'The name of your xcode project'],
23
+ ['--target-config=""', 'The name of build configuration to be copied to uitestrunner build configuration'],
24
+ ['--target-scheme=""', 'The name of build scheme to be copied to uitestrunner build scheme'],
25
+ ]
26
+ end
27
+
28
+ def initialize(argv)
29
+ @project_name = argv.option('project-name', nil)
30
+ @taget_config = argv.option('target-config', nil)
31
+ @target_scheme = argv.option('target-scheme', nil)
32
+ @uitest_name = 'UITestRunner'
33
+ @project_path = "./#{@project_name}.xcodeproj"
34
+ super
35
+ @additional_args = argv.remainder!
36
+ end
37
+
38
+ def validate!
39
+ super
40
+ help! 'Argument --project-name is required.' unless @project_name
41
+ help! 'Argument --target-config is required.' unless @taget_config
42
+ help! 'Argument --target-scheme is required.' unless @target_scheme
43
+ end
44
+
45
+ def run
46
+ generate_build_configuration
47
+ generate_scheme
48
+ copy_template_files
49
+ end
50
+
51
+ def generate_build_configuration
52
+ puts "Input: project_name: #{@project_name}, target_config: #{@taget_config}, target_scheme: #{@target_scheme}"
53
+
54
+ project = Xcodeproj::Project.open(@project_path)
55
+ configuration_list = project.root_object.build_configuration_list
56
+
57
+ if configuration_list.build_settings(@uitest_name) != nil
58
+ puts "uitest already install in the project #{@project_name}"
59
+ return
60
+ end
61
+
62
+ puts "Initializing #{@uitest_name}"
63
+ puts "Generating project configuration"
64
+
65
+ configuration = project.new(Xcodeproj::Project::Object::XCBuildConfiguration)
66
+ configuration.name = @uitest_name
67
+ configuration.build_settings = configuration_list.build_settings(@taget_config)
68
+ configuration_list.build_configurations << configuration
69
+
70
+ project.targets.each do |target|
71
+ break if target.build_configurations.select { |e| e.name == @uitest_name }.count > 0
72
+
73
+ puts "Generating build configuration #{target.name}:#{@uitest_name}"
74
+
75
+ base_config = target.build_configurations.select { |e| e.name == @taget_config }.first
76
+ org_main_storyboard_file = nil
77
+
78
+ target.build_configurations.each { |e|
79
+ path = e.build_settings['INFOPLIST_FILE']
80
+ obj = Plist.parse_xml(path)
81
+ var_name = '$(MAIN_STORYBOARD_FILE)'
82
+ e.build_settings['MAIN_STORYBOARD_FILE'] = obj['UIMainStoryboardFile'] != var_name ? obj['UIMainStoryboardFile'] : org_main_storyboard_file
83
+ obj['UIMainStoryboardFile'] = '$(MAIN_STORYBOARD_FILE)'
84
+
85
+ if org_main_storyboard_file == nil
86
+ org_main_storyboard_file = e.build_settings['MAIN_STORYBOARD_FILE']
87
+ end
88
+
89
+ File.write(path, obj.to_plist)
90
+ }
91
+
92
+ new_config = project.new(Xcodeproj::Project::Object::XCBuildConfiguration)
93
+ new_config.name = @uitest_name
94
+ new_config.base_configuration_reference = base_config.base_configuration_reference
95
+ new_config.build_settings.update(base_config.build_settings)
96
+ new_config.build_settings['MAIN_STORYBOARD_FILE'] = @uitest_name
97
+
98
+ target.build_configurations << new_config
99
+
100
+ puts "Generating scheme #{target.name} #{@uitest_name}"
101
+
102
+ project.save()
103
+ end
104
+ end
105
+
106
+ def generate_scheme
107
+ scheme_name = "#{@project_name} #{@uitest_name}"
108
+
109
+ FileUtils.copy_file(scheme_path(@target_scheme), scheme_path(scheme_name))
110
+
111
+ scheme = Xcodeproj::XCScheme.new(scheme_path(scheme_name))
112
+ scheme.launch_action.build_configuration = @uitest_name
113
+ scheme.save_as(@project_path, scheme_name)
114
+ end
115
+
116
+ def copy_template_files
117
+ template_path = "./#{@project_name}/#{@uitest_name}"
118
+
119
+ if Dir.exist?(template_path)
120
+ puts "Template files already exist in the project #{@project_name}"
121
+ return
122
+ end
123
+
124
+ puts "Copying template files into project #{@project_name}"
125
+
126
+ path = File.expand_path('../../../templates', __FILE__)
127
+ FileUtils.copy_entry path, "./#{@project_name}"
128
+
129
+ puts "Adding references for files and resources into project #{@project_name}"
130
+
131
+ project = Xcodeproj::Project.open(@project_path)
132
+
133
+ project.targets.each { |target|
134
+ target_group = project.groups.select { |e| e.path == target.name }.first
135
+ template_group = target_group.new_group(nil, @uitest_name)
136
+ add_files("#{template_path}/*", template_group, target)
137
+ }
138
+
139
+ project.save()
140
+ end
141
+
142
+ def add_files(direc, current_group, main_target)
143
+ Dir.glob(direc) do |item|
144
+ next if item == '.' or item == '.DS_Store'
145
+ if File.directory?(item)
146
+ group_name = File.basename(item)
147
+ created_group = current_group.new_group(nil, group_name)
148
+ add_files("#{item}/*", created_group, main_target)
149
+ else
150
+ i = current_group.new_file(File.basename(item))
151
+
152
+ if item.include? ".swift" or item.include? ".m"
153
+ main_target.add_file_references([i])
154
+ end
155
+
156
+ if item.include? ".storyboard" or item.include? ".xib"
157
+ set_custom_module(item)
158
+ main_target.add_resources([i])
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def set_custom_module(item)
165
+ doc = Nokogiri::XML(File.open(item))
166
+ nodes = doc.xpath("//viewController")
167
+
168
+ nodes.each { |e|
169
+ e['customModule'] = @project_name
170
+ }
171
+
172
+ if nodes.count > 0
173
+ File.write(item, doc.to_xml)
174
+ end
175
+ end
176
+
177
+ def scheme_path(name)
178
+ return "#{@project_path}/xcshareddata/xcschemes/#{name}.xcscheme"
179
+ end
180
+
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,3 @@
1
+ module XcodeUtils
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,3 @@
1
+ if ! brew ls --versions carthage > /dev/null 2>&1; then
2
+ brew install carthage
3
+ fi
@@ -0,0 +1,30 @@
1
+ if [ "${CONFIGURATION}" = "Release" ]; then
2
+ APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
3
+
4
+ # This script loops through the frameworks embedded in the application and
5
+ # removes unused architectures.
6
+ find "$APP_PATH" -name \'*.framework\' -type d | while read -r FRAMEWORK
7
+ do
8
+ FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
9
+ FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
10
+ echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
11
+
12
+ EXTRACTED_ARCHS=()
13
+
14
+ for ARCH in $ARCHS
15
+ do
16
+ echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
17
+ lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
18
+ EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
19
+ done
20
+
21
+ echo "Merging extracted architectures: ${ARCHS}"
22
+ lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
23
+ rm "${EXTRACTED_ARCHS[@]}"
24
+
25
+ echo "Replacing original executable with thinned version"
26
+ rm "$FRAMEWORK_EXECUTABLE_PATH"
27
+ mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
28
+
29
+ done
30
+ fi
@@ -0,0 +1,14 @@
1
+ //
2
+ // UITestAuthorizationProvider.swift
3
+ // Copyright © 2020 abc-studio. All rights reserved.
4
+ //
5
+
6
+ #if DEBUG
7
+ protocol UITestAuthorizationProvider {
8
+ var hasAccessToken: Bool {get}
9
+ var hasRefreshToken: Bool {get}
10
+ func refreshToken(completion: @escaping () -> Void)
11
+ func signIn(_ parent: UIViewController, completion: @escaping () -> Void)
12
+ func signOut()
13
+ }
14
+ #endif
@@ -0,0 +1,97 @@
1
+ //
2
+ // UITestSuite.swift
3
+ // Copyright © 2020 abc-studio. All rights reserved.
4
+ //
5
+
6
+ #if DEBUG
7
+ import Foundation
8
+
9
+ protocol UITestSuiteInitializable {
10
+ init()
11
+ }
12
+
13
+ class UITestSuite: UITestSuiteInitializable {
14
+
15
+ // MARK: - Implementations of Protocol UITestSuiteInitializable
16
+
17
+ required init() {}
18
+
19
+ // MARK: - Internal Properties
20
+
21
+ static var authorizationProvider: UITestAuthorizationProvider?
22
+
23
+ var isNewSession = false
24
+ var useLogin = true
25
+ var testRunnerViewController: UITestRunnerViewController! {
26
+ didSet {
27
+ setUp()
28
+ }
29
+ }
30
+ var view: UIView {
31
+ return testRunnerViewController.view
32
+ }
33
+
34
+ // MARK: - Private Properties
35
+
36
+ private lazy var strongReferences = [Any]()
37
+
38
+ // MARK: - Abstract Methods
39
+
40
+ func setUp() {
41
+ fatalError("setUp has not been implemented")
42
+ }
43
+
44
+ func runTests() {
45
+ fatalError("runTests has not been implemented")
46
+ }
47
+
48
+ // MARK: - Internal Methods
49
+
50
+ func run() {
51
+ guard useLogin else {
52
+ logout()
53
+ runTests()
54
+ return
55
+ }
56
+ guard Self.authorizationProvider?.hasAccessToken == true else {
57
+ signIn()
58
+ return
59
+ }
60
+
61
+ if isNewSession || Self.authorizationProvider?.hasRefreshToken == false {
62
+ logout()
63
+ signIn()
64
+ } else {
65
+ Self.authorizationProvider?.refreshAccessToken {
66
+ self.runTests()
67
+ }
68
+ }
69
+ }
70
+
71
+ func setAsStrongReference(_ any: Any) {
72
+ strongReferences.append(any)
73
+ }
74
+
75
+ // MARK: - Private Methods
76
+
77
+ private func signIn() {
78
+ Self.authorizationProvider?.signIn(testRunnerViewController) {
79
+ self.runTests()
80
+ }
81
+ }
82
+
83
+ private func logout() {
84
+ Self.authorizationProvider?.signOut()
85
+ }
86
+ }
87
+
88
+ extension UITestSuite {
89
+ @discardableResult
90
+ func present(_ viewController: UIViewController, completion: (() -> Void)? = nil) -> UINavigationController {
91
+ let navigationController = UINavigationController(rootViewController: viewController)
92
+ navigationController.modalPresentationStyle = .fullScreen
93
+ testRunnerViewController.present(navigationController, animated: false, completion: completion)
94
+ return navigationController
95
+ }
96
+ }
97
+ #endif
@@ -0,0 +1,28 @@
1
+ //
2
+ // MainTestSuite.swift
3
+ // Copyright © 2020 abc-studio. All rights reserved.
4
+ //
5
+
6
+ #if DEBUG
7
+ import Foundation
8
+
9
+ final class MainTestSuite: UITestSuite {
10
+
11
+ // MARK: - Implementations of abstraction
12
+
13
+ override func setUp() {
14
+ // Put setup code here. This method is called before the invocation of test suite.
15
+ }
16
+
17
+ override func runTests() {
18
+ // Put run tests code here. This method is called before the invocation of test suite.
19
+ testExample()
20
+ }
21
+
22
+ // MARK: - Tests
23
+
24
+ private func testExample() {
25
+ // This is an example of a functional test case.
26
+ }
27
+ }
28
+ #endif
@@ -0,0 +1,27 @@
1
+ //
2
+ // UITestAuthorizationProviderImpl.swift
3
+ // Copyright © 2020 abc-studio. All rights reserved.
4
+ //
5
+
6
+ #if DEBUG
7
+ struct UITestAuthorizationProviderImpl: UITestAuthorizationProvider {
8
+
9
+ // MARK: - Implementations of Protocol UITestAuthorizationProvider
10
+
11
+ var hasAccessToken: Bool {
12
+ return false
13
+ }
14
+ var hasRefreshToken: Bool {
15
+ return false
16
+ }
17
+
18
+ func refreshToken(completion: @escaping () -> Void) {
19
+ }
20
+
21
+ func signIn(_ parent: UIViewController, completion: @escaping () -> Void) {
22
+ }
23
+
24
+ func signOut() {
25
+ }
26
+ }
27
+ #endif
@@ -0,0 +1,26 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="SCr-6D-ZxU">
3
+ <device id="retina6_1" orientation="portrait" appearance="light"/>
4
+ <dependencies>
5
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
6
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
7
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
8
+ </dependencies>
9
+ <scenes>
10
+ <!--Test Runner View Controller-->
11
+ <scene sceneID="Qdh-9A-DFL">
12
+ <objects>
13
+ <viewController id="SCr-6D-ZxU" customClass="UITestRunnerViewController" customModule="" customModuleProvider="target" sceneMemberID="viewController">
14
+ <view key="view" contentMode="scaleToFill" id="TAc-ms-UKq">
15
+ <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
16
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
17
+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
18
+ <viewLayoutGuide key="safeArea" id="TVl-0y-oBG"/>
19
+ </view>
20
+ </viewController>
21
+ <placeholder placeholderIdentifier="IBFirstResponder" id="Kxe-uh-kQb" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
22
+ </objects>
23
+ <point key="canvasLocation" x="-129" y="20"/>
24
+ </scene>
25
+ </scenes>
26
+ </document>
@@ -0,0 +1,38 @@
1
+ //
2
+ // UITestRunnerViewController.swift
3
+ // Copyright © 2020 abc-studio. All rights reserved.
4
+ //
5
+
6
+ #if DEBUG
7
+ import UIKit
8
+
9
+ final class UITestRunnerViewController: UIViewController {
10
+
11
+ // MARK: - Overridden: UIViewController
12
+
13
+ override func viewDidLoad() {
14
+ super.viewDidLoad()
15
+
16
+ title = "UITestRunner"
17
+
18
+ UITestSuite.authorizationProvider = UITestAuthorizationProviderImpl()
19
+
20
+ runTestSuites()
21
+ }
22
+
23
+ // MARK: - Private Properties
24
+
25
+ private var suite: UITestSuite!
26
+
27
+ // MARK: - Private Methods
28
+
29
+ private func runTestSuites() {
30
+ runTestSuite(MainTestSuite.self)
31
+ }
32
+ private func runTestSuite<T: UITestSuite>(_ suiteType: T.Type) {
33
+ suite = suiteType.init()
34
+ suite.testRunnerViewController = self
35
+ suite.run()
36
+ }
37
+ }
38
+ #endif
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xcode-utils
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Steve Kim
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: claide
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.1
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 1.1.0
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 0.9.1
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: xcodeproj
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.18.0
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '2.0'
43
+ type: :development
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 1.18.0
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '2.0'
53
+ - !ruby/object:Gem::Dependency
54
+ name: plist
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 3.5.0
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '4.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 3.5.0
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '4.0'
73
+ - !ruby/object:Gem::Dependency
74
+ name: nokogiri
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 1.10.10
80
+ - - "<"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 1.10.10
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: '2.0'
93
+ - !ruby/object:Gem::Dependency
94
+ name: bundler
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '1.7'
100
+ type: :development
101
+ prerelease: false
102
+ version_requirements: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - "~>"
105
+ - !ruby/object:Gem::Version
106
+ version: '1.7'
107
+ description: Xcode utils provides uitest and carthage proxy
108
+ email:
109
+ - pisces@linecorp.com
110
+ executables:
111
+ - xcutils
112
+ extensions: []
113
+ extra_rdoc_files: []
114
+ files:
115
+ - LICENSE
116
+ - README.md
117
+ - bin/xcutils
118
+ - lib/xcutils.rb
119
+ - lib/xcutils/carthage.rb
120
+ - lib/xcutils/carthage/colors.rb
121
+ - lib/xcutils/carthage/xcconfig.rb
122
+ - lib/xcutils/command.rb
123
+ - lib/xcutils/uitest.rb
124
+ - lib/xcutils/version.rb
125
+ - sh/install_carthage.sh
126
+ - sh/remove_unwanted_architectures.sh
127
+ - templates/UITestRunner/Base/UITestAuthorizationProvider.swift
128
+ - templates/UITestRunner/Base/UITestSuite.swift
129
+ - templates/UITestRunner/Suites/MainTestSuite.swift
130
+ - templates/UITestRunner/UITestAuthorizationProviderImpl.swift
131
+ - templates/UITestRunner/UITestRunner.storyboard
132
+ - templates/UITestRunner/UITestRunnerViewController.swift
133
+ homepage: https://git.linecorp.com/abc/xcode-utils.git
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 2.0.0
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.0.3
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Xcode utils
156
+ test_files: []