spinjector 0.0.5 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13754e7fa2165ee93579dd1d2064c9eb5e692ef67a2e2b7cd59f8a755b14a21b
4
- data.tar.gz: 21e6d78086b4ed644eaf628c1548fc6570f198d2ad3aaf224d51da41e4d065ba
3
+ metadata.gz: 770afb3d2da618abf0c2f12be01a8638d6190c5b21085d8ee6cbc89a05159e50
4
+ data.tar.gz: 6d371c30b8a6fa185a7775bf5b63e34f76269b25beb63cd738d0a0627ca48237
5
5
  SHA512:
6
- metadata.gz: f44c4b944a3a1859d5e2d9396bcc5a4ca4820d4d1b1f9633f1657b5b4f4430a60ab26bdedfa725bccba05c695a109d9e2d8e254c247a45b4d651639a88733463
7
- data.tar.gz: 45a06ffa00166fa35326be22ac1156f1930d42e5737906e6c0f3d9a34c904ff3d5490bf3ab3e5f3403d18e9ac6bfd2f9e9f589b6a62e1b85dae45e6bdb709952
6
+ metadata.gz: f627ae1db194c15105e591e3d7f1c43000df84b72b47701e3c587b6962f6ba25978ad6b52763be3f11d1550eebc454bbd5b858adfa112557f1e1f4bbc8583d6e
7
+ data.tar.gz: 884f0b622a6f4bc9c3d421200e0651970f961831fc4cada4dc87c3e1814ed0cc2d8f85a7ccf4b3adc293f6a57b48a2428241452d4edce84202301aee0a54fc11
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby: ["2.6.6", "2.7.3", "3.0.1"]
11
+
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: ${{ matrix.ruby }}
17
+ - name: Build and test with Rake
18
+ run: |
19
+ gem install bundler
20
+ bundle install --jobs 4 --retry 3
21
+ bundle exec rake test
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+ `spinjector` adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ ## [NEXT] - Date
6
+
7
+ ### Created
8
+
9
+ ### Fixed
10
+
11
+ ### Removed
12
+
13
+ ## [1.0.0] - 2022-12-19
14
+
15
+ ### Created
16
+
17
+ - Make the script idempotent. Successive calls to configure don’t modify the pbxproj file anymore
18
+ - Added tests
19
+
20
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ad_localize.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,53 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ spinjector (0.0.6)
5
+ optparse (~> 0.1)
6
+ xcodeproj (~> 1.21)
7
+ yaml (~> 0.2)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ CFPropertyList (3.0.5)
13
+ rexml
14
+ ansi (1.5.0)
15
+ atomos (0.1.3)
16
+ builder (3.2.4)
17
+ claide (1.1.0)
18
+ colored2 (3.1.2)
19
+ minitest (5.15.0)
20
+ minitest-hooks (1.5.0)
21
+ minitest (> 5.3)
22
+ minitest-reporters (1.5.0)
23
+ ansi
24
+ builder
25
+ minitest (>= 5.0)
26
+ ruby-progressbar
27
+ nanaimo (0.3.0)
28
+ optparse (0.2.0)
29
+ rake (12.3.3)
30
+ rexml (3.2.5)
31
+ ruby-progressbar (1.11.0)
32
+ xcodeproj (1.21.0)
33
+ CFPropertyList (>= 2.3.3, < 4.0)
34
+ atomos (~> 0.1.3)
35
+ claide (>= 1.0.2, < 2.0)
36
+ colored2 (~> 3.1)
37
+ nanaimo (~> 0.3.0)
38
+ rexml (~> 3.2.4)
39
+ yaml (0.2.0)
40
+
41
+ PLATFORMS
42
+ x86_64-darwin-18
43
+
44
+ DEPENDENCIES
45
+ bundler (>= 1.12.0, < 3.0.0)
46
+ minitest (~> 5.11)
47
+ minitest-hooks (~> 1.5)
48
+ minitest-reporters (~> 1.3)
49
+ rake (~> 12.3)
50
+ spinjector!
51
+
52
+ BUNDLED WITH
53
+ 2.2.26
data/Makefile ADDED
@@ -0,0 +1,11 @@
1
+ build:
2
+ gem build spinjector.gemspec
3
+
4
+ install: clean build
5
+ gem install spinjector-*.gem
6
+
7
+ publish: clean build
8
+ gem push spinjector-*.gem
9
+
10
+ clean:
11
+ rm -f spinjector-*.gem
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # spinjector
2
+
2
3
  Inject Script phase in your Xcode project easily.
3
4
 
4
5
  # How to install
@@ -8,7 +9,9 @@ gem install spinjector
8
9
  ```
9
10
 
10
11
  # How to use
12
+
11
13
  ## Global configuration file
14
+
12
15
  First, create a YAML configuration file under `./Configuration/spinjector_configuration.yaml` (default path where spinjector looks for a configuration file).
13
16
 
14
17
  ```
@@ -31,7 +34,9 @@ targets:
31
34
  ```
32
35
 
33
36
  ## Script configuration file
37
+
34
38
  Then, for each script you want to inject in your Xcode project:
39
+
35
40
  - You can use `scripts` section in the global configuration file to define your script directly (eg. `foo`)...
36
41
 
37
42
  - ...Or create a script configuration file (eg. `helloworld.yaml`)
@@ -60,16 +65,19 @@ output_file_list_paths: # optional.
60
65
 
61
66
  dependency_file: # optional.
62
67
 
63
- execution_position: # optional. [:before-compile | :after-compile | :before-headers | :after-headers].
68
+ execution_position: # optional. [:before_compile | :after_compile | :before_headers | :after_headers | :after_all].
64
69
  ```
65
70
 
66
- - If you use the `script_path option, create the script file
71
+ - If you use the `script_path option`, create the script file
72
+
67
73
  ```
68
74
  echo Hello World
69
75
  ```
70
76
 
71
77
  ## Execution
78
+
72
79
  Finally, inject script phases
80
+
73
81
  ```
74
82
  spinjector [-c] <path-to-your-global-configuration-file>
75
83
  ```
@@ -77,3 +85,20 @@ spinjector [-c] <path-to-your-global-configuration-file>
77
85
  Enjoy your build phases
78
86
  ![Image of your build phases](/Examples/Images/build_phases.png)
79
87
  ![Image of hello world 2 build phase](/Examples/Images/hello_world_explicit.png)
88
+
89
+ ## How to contribute
90
+
91
+ 1. After all your changes are reviewed and merged
92
+ 2. Create a `release` branch
93
+ 3. Update the version in field `s.version` from file `spinjector.gemspec`
94
+ 4. Execute `make publish`
95
+
96
+ You may need to configure your account at step `4.` if you've never pushed any gem. You can find all the informations you need on [the official documentation](https://guides.rubygems.org/make-your-own-gem/#your-first-gem).
97
+
98
+ ## Tests
99
+
100
+ To run the tests, simply run:
101
+
102
+ ```sh
103
+ rake test
104
+ ```
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ t.warning = false
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,9 @@
1
+
2
+ class Configuration
3
+
4
+ attr_reader :targets
5
+
6
+ def initialize(targets)
7
+ @targets = targets || []
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+
2
+ class Script
3
+
4
+ attr_reader :name, :source_code, :shell_path, :input_paths, :output_paths, :input_file_list_paths, :output_file_list_paths, :dependency_file, :execution_position, :show_env_vars_in_log
5
+
6
+ def initialize(
7
+ name,
8
+ source_code,
9
+ shell_path,
10
+ input_paths,
11
+ output_paths,
12
+ input_file_list_paths,
13
+ output_file_list_paths,
14
+ dependency_file,
15
+ execution_position,
16
+ show_env_vars_in_log
17
+ )
18
+ @name = name
19
+ @source_code = source_code
20
+ @shell_path = shell_path
21
+ @input_paths = input_paths
22
+ @output_paths = output_paths
23
+ @input_file_list_paths = input_file_list_paths
24
+ @output_file_list_paths = output_file_list_paths
25
+ @dependency_file = dependency_file
26
+ @execution_position = execution_position
27
+ @show_env_vars_in_log = show_env_vars_in_log
28
+ verify()
29
+ end
30
+
31
+ def verify
32
+ verify_execution_position()
33
+ end
34
+
35
+ def verify_execution_position
36
+ case execution_position
37
+ when :before_compile, :before_headers
38
+ true
39
+ when :after_compile, :after_headers
40
+ false
41
+ when :after_all,
42
+ false
43
+ else
44
+ raise ArgumentError, "Unknown execution position `#{execution_position}`"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,14 @@
1
+
2
+ class Target
3
+
4
+ attr_reader :name, :scripts
5
+
6
+ def initialize(name, scripts)
7
+ @name = name
8
+ @scripts = scripts || []
9
+ end
10
+
11
+ def scripts_names
12
+ @scripts.map { |script| script.name }
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ class Logger
2
+ def log(content)
3
+ raise "Abstract class, should be overriden"
4
+ end
5
+ end
6
+
7
+ class EmptyLogger < Logger
8
+ def log(content)
9
+ end
10
+ end
11
+
12
+ class VerboseLogger < Logger
13
+ def log(content)
14
+ puts content
15
+ end
16
+ end
@@ -0,0 +1,191 @@
1
+ require 'xcodeproj'
2
+ require_relative 'entity/configuration'
3
+ require_relative 'entity/script'
4
+ require_relative 'entity/target'
5
+
6
+ # @return [String] prefix used for all the build phase injected by this script
7
+ # [SPI] stands for Script Phase Injector
8
+ #
9
+ BUILD_PHASE_PREFIX = '[SPI] '.freeze
10
+
11
+ class ProjectService
12
+
13
+ # @param [Xcodeproj::Project] project
14
+ #
15
+ def initialize(project, logger)
16
+ @project = project
17
+ @logger = logger
18
+ end
19
+
20
+ # @param [Configuration] configuration containing all scripts to add in each target
21
+ #
22
+ def update_scripts_in_targets(configuration)
23
+ @project.targets.each do |target|
24
+ @insertion_offset_after_compile = 0
25
+ @insertion_offset_after_headers = 0
26
+ target_configuration = configuration.targets.find { |conf_target| conf_target.name == target.name }
27
+ if target_configuration == nil
28
+ @logger.log "No Spinjector managed build phases in target #{target}"
29
+ remove_all_scripts(target)
30
+ next
31
+ end
32
+ @logger.log "Configurating target #{target}"
33
+ scripts_to_apply = target_configuration.scripts_names.map { |name| BUILD_PHASE_PREFIX + name }.to_set
34
+ native_target_script_phases = target.shell_script_build_phases.select do |bp|
35
+ !bp.name.nil? && bp.name.start_with?(BUILD_PHASE_PREFIX)
36
+ end
37
+ native_target_script_phases.each do |script_phase|
38
+ if scripts_to_apply.include?(script_phase.name)
39
+ # Update existing script phase with new values
40
+ script_configuration = target_configuration.scripts.find { |script|
41
+ BUILD_PHASE_PREFIX + script.name == script_phase.name
42
+ }
43
+ update_script_in_target(script_phase, script_configuration, target)
44
+ scripts_to_apply.delete(script_phase.name)
45
+ elsif
46
+ target.build_phases.delete(script_phase)
47
+ # Remove now defunct script phase
48
+ end
49
+ end
50
+ # We may miss scripts that are yet to be added to the pbxproj target, this is fixed in the following method
51
+ reorder_and_add_missing_script_phases_of(target, target_configuration)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target to remove the script phases
58
+ # @return [<Xcodeproj::Project::Object::PBXShellScriptBuildPhase>] the newly created build phase
59
+ #
60
+ def spinjector_managed_phases(target)
61
+ spinjector_managed_phases = target.shell_script_build_phases.select do |bp|
62
+ !bp.name.nil? && bp.name.start_with?(BUILD_PHASE_PREFIX)
63
+ end
64
+ return spinjector_managed_phases
65
+ end
66
+
67
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target to remove the script phases
68
+ #
69
+ def remove_all_scripts(target)
70
+ # Delete all the spinjector managed scripts from the selected target
71
+ spinjector_managed_phases(target).each do |script_phase|
72
+ target.build_phases.delete(script_phase)
73
+ end
74
+ end
75
+
76
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target to add the script phases
77
+ # @param [Target] the target configuration describing the scripts to be added
78
+ #
79
+ def reorder_and_add_missing_script_phases_of(target, target_configuration)
80
+ target_configuration.scripts.each do |script|
81
+ current_phase = spinjector_managed_phases(target).find { |phase| phase.name == BUILD_PHASE_PREFIX + script.name }
82
+ if current_phase == nil && should_add_script_in_target(script, target)
83
+ current_phase = add_script_in_target(script, target)
84
+ end
85
+ execution_position = script.execution_position
86
+ reorder_script_phase(target, current_phase, execution_position)
87
+ end
88
+ end
89
+
90
+ # Checks If the target contains a build phase associated to the script execution position
91
+ # For instance, it checks if the Headers build phase is present when the script has a :after_headers position
92
+ # @param [Script] script the script phase defined in configuration files to add to the target
93
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target to add the script phases
94
+ # @return [Boolean] If the script should be added to the target
95
+ #
96
+ def should_add_script_in_target(script, target)
97
+ return true if script.execution_position === :after_all
98
+
99
+ target_phase_type = case script.execution_position
100
+ when :before_compile, :after_compile
101
+ Xcodeproj::Project::Object::PBXSourcesBuildPhase
102
+ when :before_headers, :after_headers
103
+ Xcodeproj::Project::Object::PBXHeadersBuildPhase
104
+ else
105
+ nil
106
+ end
107
+
108
+ found_phase_in_target = target.build_phases.find { |bp| bp.is_a?(target_phase_type) }
109
+ @logger.log("Can't find build phase associated to execution position #{script.execution_position} in target #{target.name}") if found_phase_in_target.nil?
110
+ return !found_phase_in_target.nil?
111
+ end
112
+
113
+ # @param [Script] script the script phase defined in configuration files to add to the target
114
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target to add the script phases
115
+ # @return [Xcodeproj::Project::Object::PBXShellScriptBuildPhase] the newly created build phase
116
+ #
117
+ def add_script_in_target(script, target)
118
+ name_with_prefix = BUILD_PHASE_PREFIX + script.name
119
+ phase = target.new_shell_script_build_phase(name_with_prefix)
120
+ update_script_in_target(phase, script, target)
121
+ return phase
122
+ end
123
+
124
+ # @param [Xcodeproj::Project::Object::PBXShellScriptBuildPhase] phase to update with the values from the script
125
+ # @param [Script] script the script phase defined in configuration files to add to the target
126
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target to add the script phase
127
+ #
128
+ def update_script_in_target(existing_phase, script_configuration, target)
129
+ existing_phase.shell_script = script_configuration.source_code
130
+ existing_phase.shell_path = script_configuration.shell_path
131
+ existing_phase.input_paths = script_configuration.input_paths
132
+ existing_phase.output_paths = script_configuration.output_paths
133
+ existing_phase.input_file_list_paths = script_configuration.input_file_list_paths
134
+ existing_phase.output_file_list_paths = script_configuration.output_file_list_paths
135
+ existing_phase.dependency_file = script_configuration.dependency_file
136
+ # At least with Xcode 10 `showEnvVarsInLog` is *NOT* set to any value even if it's checked and it only
137
+ # gets set to '0' if the user has explicitly disabled this.
138
+ if script_configuration.show_env_vars_in_log == '0'
139
+ existing_phase.show_env_vars_in_log = script_configuration.show_env_vars_in_log
140
+ end
141
+ end
142
+
143
+ # @param [Xcodeproj::Project::Object::PBXNativeTarget] target where build phases should be reordered
144
+ # @param [Hash] script_phase to reorder
145
+ # @param [Symbol] execution_position could be :before_compile, :after_compile, :before_headers, :after_headers
146
+ #
147
+ def reorder_script_phase(target, script_phase, execution_position)
148
+ return if execution_position == :any || execution_position.to_s.empty? || script_phase.nil?
149
+ if execution_position == :after_all
150
+ target.build_phases.move(script_phase, target.build_phases.count - 1)
151
+ return
152
+ end
153
+
154
+ offset = -1
155
+ # Find the point P where to add the script phase
156
+ target_phase_type = case execution_position
157
+ when :before_compile
158
+ Xcodeproj::Project::Object::PBXSourcesBuildPhase
159
+ when :after_compile
160
+ offset = @insertion_offset_after_compile
161
+ @insertion_offset_after_compile += 1
162
+ Xcodeproj::Project::Object::PBXSourcesBuildPhase
163
+ when :before_headers
164
+ Xcodeproj::Project::Object::PBXHeadersBuildPhase
165
+ when :after_headers
166
+ offset = @insertion_offset_after_headers
167
+ @insertion_offset_after_headers += 1
168
+ Xcodeproj::Project::Object::PBXHeadersBuildPhase
169
+ else
170
+ raise ArgumentError, "Unknown execution position `#{execution_position}`"
171
+ end
172
+
173
+ # Get the first build phase index of P
174
+ target_phase_index = target.build_phases.index do |bp|
175
+ bp.is_a?(target_phase_type)
176
+ end
177
+ return if target_phase_index.nil?
178
+
179
+ # Get the script phase we want to reorder index
180
+ script_phase_index = target.build_phases.index do |bp|
181
+ bp.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && !bp.name.nil? && bp.name == script_phase.name
182
+ end
183
+ if target_phase_index < script_phase_index
184
+ offset += 1
185
+ end
186
+
187
+ target.build_phases.move_from(script_phase_index, target_phase_index + offset)
188
+ end
189
+
190
+ end
191
+
@@ -0,0 +1,32 @@
1
+ # Inject script phases into your Xcode project
2
+ #
3
+ # @author Guillaume Berthier
4
+ #
5
+
6
+ require 'xcodeproj'
7
+ require_relative './project_service'
8
+ require_relative './yaml_parser'
9
+
10
+ class Runner
11
+
12
+ attr_reader :project
13
+
14
+ def initialize(project_path, configuration_file_path, logger)
15
+ raise "[Error] No xcodeproj found at #{project_path}" unless File.exist?(project_path)
16
+ @project = Xcodeproj::Project.open(project_path)
17
+ @configuration_file_path = configuration_file_path
18
+ @logger = logger
19
+ end
20
+
21
+ def run
22
+ configuration = YAMLParser.new(@configuration_file_path, @logger).configuration
23
+
24
+ project_service = ProjectService.new(project, @logger)
25
+ project_service.update_scripts_in_targets(configuration)
26
+
27
+ @project.save()
28
+ @logger.log "Success."
29
+ end
30
+
31
+ end
32
+
@@ -0,0 +1,36 @@
1
+ require_relative 'entity/script'
2
+
3
+ class ScriptMapper
4
+
5
+ def initialize(script_hash)
6
+ @script_hash = script_hash
7
+ verify_syntax
8
+ end
9
+
10
+ def map
11
+ script_code = @script_hash["script"] || load_script(@script_hash["script_path"])
12
+ Script.new(
13
+ @script_hash["name"],
14
+ script_code,
15
+ @script_hash["shell_path"] || '/bin/sh',
16
+ @script_hash["input_paths"] || [],
17
+ @script_hash["output_paths"] || [],
18
+ @script_hash["input_file_list_paths"] || [],
19
+ @script_hash["output_file_list_paths"] || [],
20
+ @script_hash["dependency_file"],
21
+ @script_hash["execution_position"] || :before_compile,
22
+ @script_hash["show_env_vars_in_log"]
23
+ )
24
+ end
25
+
26
+ def verify_syntax
27
+ raise "[Error] Invalid script description #{@script_hash}" unless @script_hash.is_a?(Hash)
28
+ raise "[Error] Script must have a name and an associated script" unless @script_hash.has_key?("name") && @script_hash.has_key?("script") || @script_hash.has_key?("script_path")
29
+ raise "[Error] Invalid name in script #{@script_hash}" unless !@script_hash["name"].nil?
30
+ end
31
+
32
+ def load_script(path)
33
+ raise "[Error] File #{path} does not exist" unless !path.nil? && File.exist?(path)
34
+ File.read(path)
35
+ end
36
+ end
@@ -0,0 +1,83 @@
1
+ require 'yaml'
2
+ require 'set'
3
+ require_relative 'entity/configuration'
4
+ require_relative 'entity/script'
5
+ require_relative 'entity/target'
6
+ require_relative 'script_mapper'
7
+
8
+ class YAMLParser
9
+
10
+ # The configuration to use in order to add scripts in your project
11
+ #
12
+ attr_reader :configuration
13
+
14
+ def initialize(yaml_file_path, logger)
15
+ @logger = logger
16
+ @inlined_scripts_ids = Set[]
17
+ @inlined_scripts_names = Set[]
18
+ @other_scripts_paths = Set[]
19
+ @other_scripts_names = Set[]
20
+ @configuration_description = load_yml_content(yaml_file_path)
21
+ @configuration = Configuration.new(targets)
22
+ end
23
+
24
+ private
25
+
26
+ def targets
27
+ if @configuration_description["targets"].nil?
28
+ @logger.log "[Warning] There is no target in your configuration file."
29
+ return
30
+ end
31
+ @configuration_description["targets"].map do |target_name, script_entries|
32
+ if script_entries.nil?
33
+ @logger.log "[Warning] There is no scripts in your configuration file under target #{target_name}"
34
+ return
35
+ end
36
+ scripts = script_entries.map do |entry|
37
+ get_script(entry)
38
+ end
39
+ Target.new(target_name, scripts)
40
+ end
41
+ end
42
+
43
+ def get_script(entry)
44
+ script =
45
+ if !@configuration_description["scripts"].nil? && !@configuration_description["scripts"][entry].nil?
46
+ get_script_by_name(entry)
47
+ elsif File.exist?(entry)
48
+ get_script_by_path(entry)
49
+ else
50
+ raise "[Error] Script #{entry} does not exist" unless !script.nil?
51
+ end
52
+ end
53
+
54
+ def get_script_by_name(name)
55
+ @inlined_scripts_ids.add(name)
56
+ script_description = @configuration_description["scripts"][name]
57
+ script = ScriptMapper.new(script_description).map()
58
+ @inlined_scripts_names.add(script.name)
59
+ error = "[Error] Multiple scripts with same name \'#{script.name}\'"
60
+ raise error unless @inlined_scripts_ids.count == @inlined_scripts_names.count
61
+ raise error unless !@inlined_scripts_names.intersect?(@other_scripts_names)
62
+ return script
63
+ end
64
+
65
+ def get_script_by_path(path)
66
+ @other_scripts_paths.add(path)
67
+ script_description = load_yml_content(path)
68
+ script = ScriptMapper.new(script_description).map()
69
+ @other_scripts_names.add(script.name)
70
+ error = "[Error] Multiple scripts with same name \'#{script.name}\'"
71
+ raise error unless @other_scripts_paths.count == @other_scripts_names.count
72
+ raise error unless !@inlined_scripts_names.intersect?(@other_scripts_names)
73
+ return script
74
+ end
75
+
76
+ # @param [String] configuration_path
77
+ # @return [Hash] the hash in the configuration file
78
+ #
79
+ def load_yml_content(configuration_path)
80
+ raise "[Error] YAML file #{configuration_path} not found." unless File.exist?(configuration_path)
81
+ YAML.load(File.read(configuration_path)) || {}
82
+ end
83
+ end
data/lib/spinjector.rb CHANGED
@@ -5,12 +5,8 @@
5
5
 
6
6
  require 'optparse'
7
7
  require 'xcodeproj'
8
- require 'yaml'
9
-
10
- # @return [String] prefix used for all the build phase injected by this script
11
- # [SPI] stands for Script Phase Injector
12
- #
13
- BUILD_PHASE_PREFIX = '[SPI] '.freeze
8
+ require_relative 'spinjector/runner'
9
+ require_relative 'spinjector/logger'
14
10
 
15
11
  CONFIGURATION_FILE_PATH = 'Configuration/spinjector_configuration.yaml'.freeze
16
12
 
@@ -18,165 +14,17 @@ options = {}
18
14
  OptionParser.new do |opts|
19
15
  opts.banner = "Usage: spinjector [options]"
20
16
 
21
-
22
17
  opts.on("-cName", "--configuration-path=Name", "Inject scripts using configuration file at Name location. Default is ./#{CONFIGURATION_FILE_PATH}") do |config_path|
23
18
  options[:configuration_path] = config_path
24
19
  end
25
20
  end.parse!
26
21
 
27
- # @param [Xcodeproj::Project] project
28
- #
29
- def remove_all_spi_script_phases(project)
30
- project.targets.each do |target|
31
- # Delete script phases no longer present in the target.
32
- native_target_script_phases = target.shell_script_build_phases.select do |bp|
33
- !bp.name.nil? && bp.name.start_with?(BUILD_PHASE_PREFIX)
34
- end
35
- native_target_script_phases.each do |script_phase|
36
- target.build_phases.delete(script_phase)
37
- end
38
- end
39
- end
40
-
41
- # @param [Xcodeproj::Project] project
42
- #
43
- def inject_script_phases(project, configuration_file_path)
44
- configuration_file = load_yml_content(configuration_file_path)
45
- # Check if there are scripts in the global configuration file
46
- implicit_scripts = configuration_file["scripts"] || []
47
- configuration_file["targets"].each do |target_name, script_paths|
48
- script_phases = (script_paths || []).flat_map do |script_path|
49
- if !implicit_scripts.empty? and !implicit_scripts[script_path].nil?
50
- # Target uses script from the global configuration file
51
- script = implicit_scripts[script_path]
52
- else
53
- # Target uses script from a dedicated configuration file
54
- script = load_yml_content(script_path)
55
- end
56
- raise "[Error] Could not find script #{script_path}" unless !script.nil?
57
- script
58
- end
59
- warn "[Warning] No script phases found for #{target_name} target. You can add them in your configuration file at #{configuration_file_path}" unless !script_phases.empty?
60
- target = app_target(project, target_name)
61
- create_script_phases(script_phases, target)
62
- end
63
- end
64
-
65
- # @param [String] configuration_path
66
- # @return [Hash] the hash in the configuration file
67
- #
68
- def load_yml_content(configuration_path)
69
- raise "[Error] YAML file #{configuration_path} not found." unless File.exist?(configuration_path)
70
- YAML.load(File.read(configuration_path)) || {}
71
- end
72
-
73
- # @param [Xcodeproj::Project] project
74
- # @param [String] target_name
75
- # @return [Xcodeproj::Project::Object::PBXNativeTarget] the target named by target_name
76
- #
77
- def app_target(project, target_name)
78
- target = project.targets.find { |t| t.name == target_name }
79
- raise "[Error] Invalid #{target_name} target." unless !target.nil?
80
- return target
81
- end
82
-
83
- # @param [Array<Hash>] script_phases the script phases defined in configuration files
84
- # @param [Xcodeproj::Project::Object::PBXNativeTarget] target to add the script phases
85
- #
86
- def create_script_phases(script_phases, target)
87
- script_phases.each do |script_phase|
88
- name_with_prefix = BUILD_PHASE_PREFIX + script_phase["name"]
89
- phase = target.new_shell_script_build_phase(name_with_prefix)
90
- shell_script = get_script(script_phase)
91
- phase.shell_script = shell_script
92
- phase.shell_path = script_phase["shell_path"] || '/bin/sh'
93
- phase.input_paths = script_phase["input_paths"]
94
- phase.output_paths = script_phase["output_paths"]
95
- phase.input_file_list_paths = script_phase["input_file_list_paths"]
96
- phase.output_file_list_paths = script_phase["output_file_list_paths"]
97
- phase.dependency_file = script_phase["dependency_file"]
98
- # At least with Xcode 10 `showEnvVarsInLog` is *NOT* set to any value even if it's checked and it only
99
- # gets set to '0' if the user has explicitly disabled this.
100
- if (show_env_vars_in_log = script_phase.fetch("show_env_vars_in_log", '1')) == '0'
101
- phase.show_env_vars_in_log = show_env_vars_in_log
102
- end
103
-
104
- execution_position = script_phase["execution_position"] || :before_compile
105
- reorder_script_phase(target, phase, execution_position)
106
- end
107
- end
108
-
109
- # @param [Hash] script_phase the script phase defined in configuration files
110
- # @return [String] script to add in script phase
111
- #
112
- # If script is in a dedicated file, find the URL under <script_path>.
113
- # Otherwise, it may be written directly in the configuration file, find it under <script>.
114
- #
115
- def get_script(script_phase)
116
- if !script_phase["script"].nil? and !script_phase["script_path"].nil?
117
- raise "[Error] Script phase #{script_phase["name"]} contains 2 script sources. Please use one of :script_path or :script option"
118
- end
119
- if !script_phase["script_path"].nil?
120
- raise "[Error] File #{script_phase["script_path"]} does not exist" unless File.exist?(script_phase["script_path"])
121
- File.read(script_phase["script_path"])
122
- elsif !script_phase["script"].nil?
123
- script_phase["script"]
124
- else
125
- warn "[Warning] Script phase #{script_phase["name"]} contains no script"
126
- return ""
127
- end
128
- end
129
-
130
- # @param [Xcodeproj::Project::Object::PBXNativeTarget] target where build phases should be reordered
131
- # @param [Hash] script_phase to reorder
132
- # @param [Symbol] execution_position could be :before_compile, :after_compile, :before_headers, :after_headers
133
- #
134
- def reorder_script_phase(target, script_phase, execution_position)
135
- return if execution_position == :any || execution_position.to_s.empty?
136
-
137
- # Find the point P where to add the script phase
138
- target_phase_type = case execution_position
139
- when :before_compile, :after_compile
140
- Xcodeproj::Project::Object::PBXSourcesBuildPhase
141
- when :before_headers, :after_headers
142
- Xcodeproj::Project::Object::PBXHeadersBuildPhase
143
- else
144
- raise ArgumentError, "Unknown execution position `#{execution_position}`"
145
- end
146
-
147
- # Decide whether to add script_phase before or after point P
148
- order_before = case execution_position
149
- when :before_compile, :before_headers
150
- true
151
- when :after_compile, :after_headers
152
- false
153
- else
154
- raise ArgumentError, "Unknown execution position `#{execution_position}`"
155
- end
156
-
157
- # Get the first build phase index of P
158
- target_phase_index = target.build_phases.index do |bp|
159
- bp.is_a?(target_phase_type)
160
- end
161
- return if target_phase_index.nil?
162
-
163
- # Get the script phase we want to reorder index
164
- script_phase_index = target.build_phases.index do |bp|
165
- bp.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && !bp.name.nil? && bp.name == script_phase.name
166
- end
167
-
168
- # Move script phase to P if needed
169
- if (order_before && script_phase_index > target_phase_index) ||
170
- (!order_before && script_phase_index < target_phase_index)
171
- target.build_phases.move_from(script_phase_index, target_phase_index)
172
- end
173
- end
174
-
175
22
  project_path = Dir.glob("*.xcodeproj").first
176
- project = Xcodeproj::Project.open(project_path)
177
- raise "[Error] No xcodeproj found" unless !project.nil?
178
- remove_all_spi_script_phases(project)
179
- configuration_file_path = options[:configuration_path] || CONFIGURATION_FILE_PATH
180
- inject_script_phases(project, configuration_file_path)
181
- project.save()
182
- puts "Success."
23
+ raise "[Error] No xcodeproj found in #{Dir.pwd}" if project_path.nil?
24
+
25
+ runner = Runner.new(
26
+ project_path,
27
+ options[:configuration_path] || CONFIGURATION_FILE_PATH,
28
+ VerboseLogger.new
29
+ )
30
+ runner.run
data/spinjector.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'spinjector'
3
- s.version = '0.0.5'
3
+ s.version = '1.0.0'
4
4
  s.executables << 'spinjector'
5
5
  s.summary = "Inject script phases into your Xcode project"
6
6
  s.description = ""
@@ -8,10 +8,16 @@ Gem::Specification.new do |s|
8
8
  s.email = 'guillaume.berthier@fabernovel.com'
9
9
  # use `git ls-files -coz -x *.gem` for development
10
10
  s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
11
- s.homepage = 'https://github.com/guillaumeberthier/spinjector'
11
+ s.homepage = 'https://github.com/faberNovel/spinjector'
12
12
  s.license = 'MIT'
13
13
 
14
14
  s.add_dependency 'optparse', '~> 0.1'
15
15
  s.add_dependency 'xcodeproj', '~> 1.21'
16
16
  s.add_dependency 'yaml', '~> 0.2'
17
- end
17
+
18
+ s.add_development_dependency 'bundler', '>= 1.12.0', '< 3.0.0'
19
+ s.add_development_dependency 'rake', '~> 12.3'
20
+ s.add_development_dependency 'minitest', '~> 5.11'
21
+ s.add_development_dependency 'minitest-reporters', '~> 1.3'
22
+ s.add_development_dependency 'minitest-hooks', '~> 1.5'
23
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spinjector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guillaume Berthier, Fabernovel
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-21 00:00:00.000000000 Z
11
+ date: 2022-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: optparse
@@ -52,6 +52,82 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.12.0
62
+ - - "<"
63
+ - !ruby/object:Gem::Version
64
+ version: 3.0.0
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 1.12.0
72
+ - - "<"
73
+ - !ruby/object:Gem::Version
74
+ version: 3.0.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: rake
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '12.3'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '12.3'
89
+ - !ruby/object:Gem::Dependency
90
+ name: minitest
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '5.11'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '5.11'
103
+ - !ruby/object:Gem::Dependency
104
+ name: minitest-reporters
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.3'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '1.3'
117
+ - !ruby/object:Gem::Dependency
118
+ name: minitest-hooks
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1.5'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '1.5'
55
131
  description: ''
56
132
  email: guillaume.berthier@fabernovel.com
57
133
  executables:
@@ -59,9 +135,9 @@ executables:
59
135
  extensions: []
60
136
  extra_rdoc_files: []
61
137
  files:
138
+ - ".github/workflows/ci.yml"
62
139
  - ".gitignore"
63
- - Examples/Configuration/hello_world_input
64
- - Examples/Configuration/hello_world_output
140
+ - CHANGELOG.md
65
141
  - Examples/Configuration/helloworld.yaml
66
142
  - Examples/Configuration/helloworld_explicit_script.yaml
67
143
  - Examples/Configuration/helloworld_short.yaml
@@ -69,15 +145,27 @@ files:
69
145
  - Examples/Images/build_phases.png
70
146
  - Examples/Images/hello_world_explicit.png
71
147
  - Examples/Scripts/helloworld.sh
148
+ - Gemfile
149
+ - Gemfile.lock
150
+ - Makefile
72
151
  - README.md
152
+ - Rakefile
73
153
  - bin/spinjector
74
154
  - lib/spinjector.rb
155
+ - lib/spinjector/entity/configuration.rb
156
+ - lib/spinjector/entity/script.rb
157
+ - lib/spinjector/entity/target.rb
158
+ - lib/spinjector/logger.rb
159
+ - lib/spinjector/project_service.rb
160
+ - lib/spinjector/runner.rb
161
+ - lib/spinjector/script_mapper.rb
162
+ - lib/spinjector/yaml_parser.rb
75
163
  - spinjector.gemspec
76
- homepage: https://github.com/guillaumeberthier/spinjector
164
+ homepage: https://github.com/faberNovel/spinjector
77
165
  licenses:
78
166
  - MIT
79
167
  metadata: {}
80
- post_install_message:
168
+ post_install_message:
81
169
  rdoc_options: []
82
170
  require_paths:
83
171
  - lib
@@ -92,8 +180,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
180
  - !ruby/object:Gem::Version
93
181
  version: '0'
94
182
  requirements: []
95
- rubygems_version: 3.0.3
96
- signing_key:
183
+ rubygems_version: 3.3.3
184
+ signing_key:
97
185
  specification_version: 4
98
186
  summary: Inject script phases into your Xcode project
99
187
  test_files: []
File without changes
File without changes