refinement 0.6.0 → 0.7.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.
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sq
4
+ module Refinement
5
+ # Represents a path that some target depends upon.
6
+ class UsedPath
7
+ # @return [Pathname] the absolute path to the file
8
+ attr_reader :path
9
+ private :path
10
+
11
+ # @return [String] the reason why this path is being used by a target
12
+ attr_reader :inclusion_reason
13
+ private :inclusion_reason
14
+
15
+ def initialize(path:, inclusion_reason:)
16
+ @path = path
17
+ @inclusion_reason = inclusion_reason
18
+ end
19
+
20
+ # @return [Nil, String] If the path has been modified, a string explaining the modification
21
+ # @param changeset [Changeset] the changeset to search for a modification to this path
22
+ def find_in_changeset(changeset)
23
+ add_reason changeset.find_modification_for_path(absolute_path: path), changeset:
24
+ end
25
+
26
+ # @return [Nil, String] If the path has been modified, a string explaining the modification
27
+ # @param changesets [Array<Changeset>] the changesets to search for a modification to this path
28
+ def find_in_changesets(changesets)
29
+ raise ArgumentError, 'Must provide at least one changeset' if changesets.empty?
30
+
31
+ changesets.reduce(true) do |explanation, changeset|
32
+ explanation && find_in_changeset(changeset)
33
+ end
34
+ end
35
+
36
+ # @return [String]
37
+ # @visibility private
38
+ def to_s
39
+ "#{path.to_s.inspect} (#{inclusion_reason})"
40
+ end
41
+
42
+ private
43
+
44
+ # @return [Nil, String] A string suitable for user display that explains
45
+ # why the given modification means a target is modified
46
+ # @param modification [Nil, FileModification]
47
+ # @param changeset [Changeset]
48
+ def add_reason(modification, changeset:)
49
+ return unless modification
50
+
51
+ add_changeset_description "#{modification.path} (#{inclusion_reason}) #{modification.type}", changeset:
52
+ end
53
+
54
+ # @return [String] A string suitable for user display that explains
55
+ # why the given modification means a target is modified, including the description
56
+ # of the changeset that contains the modification
57
+ # @param description [String]
58
+ # @param changeset [Nil, Changeset]
59
+ def add_changeset_description(description, changeset:)
60
+ return description unless changeset&.description
61
+
62
+ description + " (#{changeset.description})"
63
+ end
64
+
65
+ # Represents a path to a YAML file that some target depends upon,
66
+ # but where only a subset of the YAML is needed to determine a change.
67
+ class YAML < UsedPath
68
+ # @return [Array] the keypath to search for modifications in a YAML document
69
+ attr_reader :yaml_keypath
70
+ private :yaml_keypath
71
+
72
+ def initialize(yaml_keypath:, **kwargs)
73
+ super(**kwargs)
74
+ @yaml_keypath = yaml_keypath
75
+ end
76
+
77
+ # (see UsedPath#find_in_changeset)
78
+ def find_in_changeset(changeset)
79
+ modification, _yaml_diff = changeset.find_modification_for_yaml_keypath(absolute_path: path, keypath: yaml_keypath)
80
+ add_reason modification, changeset:
81
+ end
82
+
83
+ # (see UsedPath#to_s)
84
+ def to_s
85
+ "#{path.to_s.inspect} @ #{yaml_keypath.join('.')} (#{inclusion_reason})"
86
+ end
87
+
88
+ private
89
+
90
+ # (see UsedPath#add_reason)
91
+ def add_reason(modification, changeset:)
92
+ return unless modification
93
+
94
+ keypath_string =
95
+ if yaml_keypath.empty?
96
+ ''
97
+ else
98
+ " @ #{yaml_keypath.map { |path| path.to_s =~ /\A[a-zA-Z0-9_]+\z/ ? path : path.inspect }.join('.')}"
99
+ end
100
+ add_changeset_description "#{modification.path}#{keypath_string} (#{inclusion_reason}) #{modification.type}", changeset:
101
+ end
102
+ end
103
+ end
104
+
105
+ # Represents a glob that some target depends upon.
106
+ class UsedGlob
107
+ # @return [String] a relative path glob
108
+ attr_reader :glob
109
+ private :glob
110
+
111
+ # (see UsedPath#inclusion_reason)
112
+ attr_reader :inclusion_reason
113
+ private :inclusion_reason
114
+
115
+ def initialize(glob:, inclusion_reason:)
116
+ @glob = glob
117
+ @inclusion_reason = inclusion_reason
118
+ end
119
+
120
+ # (see UsedPath#find_in_changeset)
121
+ def find_in_changeset(changeset)
122
+ add_reason changeset.find_modification_for_glob(absolute_glob: glob), changeset:
123
+ end
124
+
125
+ # (see UsedPath#find_in_changesets)
126
+ def find_in_changesets(changesets)
127
+ raise ArgumentError, 'Must provide at least one changeset' if changesets.empty?
128
+
129
+ changesets.reduce(true) do |explanation, changeset|
130
+ explanation && find_in_changeset(changeset)
131
+ end
132
+ end
133
+
134
+ # (see UsedPath#to_s)
135
+ def to_s
136
+ "#{glob.to_s.inspect} (#{inclusion_reason})"
137
+ end
138
+
139
+ private
140
+
141
+ # (see UsedPath#add_reason)
142
+ def add_reason(modification, changeset:)
143
+ return unless modification
144
+
145
+ add_changeset_description "#{modification.path} (#{inclusion_reason}) #{modification.type}", changeset:
146
+ end
147
+
148
+ # (see UsedPath#add_changeset_description)
149
+ def add_changeset_description(description, changeset:)
150
+ return description unless changeset&.description
151
+
152
+ description + " (#{changeset.description})"
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sq
4
+ module Refinement
5
+ # @visibility private
6
+ VERSION = File.read(File.expand_path('../../../VERSION', __dir__)).strip.freeze
7
+ end
8
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: refinement
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Giddins
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-22 00:00:00.000000000 Z
11
+ date: 2023-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xcodeproj
@@ -30,20 +30,6 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2'
33
- - !ruby/object:Gem::Dependency
34
- name: rake
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '10.0'
40
- type: :development
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '10.0'
47
33
  description:
48
34
  email:
49
35
  - segiddins@squareup.com
@@ -59,17 +45,19 @@ files:
59
45
  - exe/refine
60
46
  - lib/cocoapods_plugin.rb
61
47
  - lib/refinement.rb
62
- - lib/refinement/analyzer.rb
63
- - lib/refinement/annotated_target.rb
64
- - lib/refinement/changeset.rb
65
- - lib/refinement/changeset/file_modification.rb
66
- - lib/refinement/cli.rb
67
- - lib/refinement/cocoapods_post_install_writer.rb
68
- - lib/refinement/used_path.rb
69
- - lib/refinement/version.rb
48
+ - lib/sq/refinement/analyzer.rb
49
+ - lib/sq/refinement/annotated_target.rb
50
+ - lib/sq/refinement/changeset.rb
51
+ - lib/sq/refinement/changeset/file_modification.rb
52
+ - lib/sq/refinement/cli.rb
53
+ - lib/sq/refinement/cocoapods_post_install_writer.rb
54
+ - lib/sq/refinement/setup.rb
55
+ - lib/sq/refinement/used_path.rb
56
+ - lib/sq/refinement/version.rb
70
57
  homepage: https://github.com/square/refinement
71
58
  licenses: []
72
- metadata: {}
59
+ metadata:
60
+ rubygems_mfa_required: 'true'
73
61
  post_install_message:
74
62
  rdoc_options: []
75
63
  require_paths:
@@ -78,14 +66,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
66
  requirements:
79
67
  - - ">="
80
68
  - !ruby/object:Gem::Version
81
- version: '2.3'
69
+ version: '3.1'
82
70
  required_rubygems_version: !ruby/object:Gem::Requirement
83
71
  requirements:
84
72
  - - ">="
85
73
  - !ruby/object:Gem::Version
86
74
  version: '0'
87
75
  requirements: []
88
- rubygems_version: 3.0.1
76
+ rubygems_version: 3.3.26
89
77
  signing_key:
90
78
  specification_version: 4
91
79
  summary: Generates a list of Xcode targets to build & test as a result of a git diff.
@@ -1,395 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Refinement
4
- # Analyzes changes in a repository
5
- # and determines how those changes impact the targets in Xcode projects in the workspace.
6
- class Analyzer
7
- attr_reader :changesets, :workspace_path, :augmenting_paths_yaml_files
8
- private :changesets, :workspace_path, :augmenting_paths_yaml_files
9
-
10
- # Initializes an analyzer with changesets, projects, and augmenting paths.
11
- # @param changesets [Array<Changeset>]
12
- # @param workspace_path [Pathname] path to a root workspace or project,
13
- # must be `nil` if `projects` are specified explicitly
14
- # @param projects [Array<Xcodeproj::Project>] projects to find targets in,
15
- # must not be specified if `workspace_path` is not `nil`
16
- # @param augmenting_paths_yaml_files [Array<Pathname>] paths to YAML files that provide augmenting paths by target,
17
- # must be `nil` if `augmenting_paths_by_target` are specified explicitly
18
- # @param augmenting_paths_by_target [Hash<String, Array>] arrays of hashes keyed by target name
19
- # (or '*' for all targets)
20
- # describing paths or globs that each target should be considered to be using,
21
- # must not be specified if `augmenting_paths_yaml_files` is not `nil`
22
- #
23
- # @raise [ArgumentError] when conflicting arguments are given
24
- #
25
- def initialize(changesets:, workspace_path:, projects: nil,
26
- augmenting_paths_yaml_files:, augmenting_paths_by_target: nil)
27
-
28
- @changesets = changesets
29
-
30
- raise ArgumentError, 'Can only specify one of workspace_path and projects' if workspace_path && projects
31
-
32
- @workspace_path = workspace_path
33
- @projects = projects
34
-
35
- raise ArgumentError, 'Can only specify one of augmenting_paths_yaml_files and augmenting_paths_by_target' if augmenting_paths_yaml_files && augmenting_paths_by_target
36
-
37
- @augmenting_paths_yaml_files = augmenting_paths_yaml_files
38
- @augmenting_paths_by_target = augmenting_paths_by_target
39
- end
40
-
41
- # @return [Array<AnnotatedTarget>] targets from the projects annotated with their changes, based upon
42
- # the changeset
43
- def annotate_targets!
44
- @annotate_targets ||= annotated_targets
45
- end
46
-
47
- # @param scheme_path [Pathname] the absolute path to the scheme to be filtered
48
- # @param change_level [Symbol] the change level at which a target must have changed in order
49
- # to remain in the scheme. defaults to `:full_transitive`
50
- # @param filter_when_scheme_has_changed [Boolean] whether the scheme should be filtered
51
- # even when the changeset includes the scheme's path as changed.
52
- # Defaults to `false`
53
- # @param log_changes [Boolean] whether modifications to the scheme are logged.
54
- # Defaults to `false`
55
- # @param filter_scheme_for_build_action [:building, :testing]
56
- # The xcodebuild action the scheme is being filtered for. The currently supported values are
57
- # `:building` and `:testing`, with the only difference being `BuildActionEntry` are not
58
- # filtered out when building for testing, since test action macro expansion could
59
- # depend on a build entry being present.
60
- # @param each_target [Proc] A proc called each time a target was determined to have changed or not.
61
- # @return [Xcodeproj::XCScheme] a scheme whose unchanged targets have been removed.
62
- def filtered_scheme(scheme_path:, change_level: :full_transitive, filter_when_scheme_has_changed: false, log_changes: false,
63
- filter_scheme_for_build_action:, each_target: nil)
64
- scheme = Xcodeproj::XCScheme.new(scheme_path)
65
-
66
- sections_to_filter =
67
- case filter_scheme_for_build_action
68
- when :building
69
- %w[BuildActionEntry TestableReference]
70
- when :testing
71
- # don't want to filter out build action entries running
72
- # xcodebuild build-for-testing / test, since the test action could have a macro expansion
73
- # that depends upon one of the build targets.
74
- %w[TestableReference]
75
- else
76
- raise ArgumentError,
77
- 'The supported values for the `filter_scheme_for_build_action` parameter are: [:building, :testing]. ' \
78
- "Given: #{filter_scheme_for_build_action.inspect}."
79
- end
80
-
81
- if !filter_when_scheme_has_changed &&
82
- UsedPath.new(path: Pathname(scheme_path), inclusion_reason: 'scheme').find_in_changesets(changesets)
83
- return scheme
84
- end
85
-
86
- changes_by_suite_name = Hash[annotate_targets!
87
- .map { |at| [at.xcode_target.name, at.change_reason(level: change_level)] }]
88
-
89
- doc = scheme.doc
90
-
91
- xpaths = sections_to_filter.map { |section| "//*/#{section}/BuildableReference" }
92
- xpaths.each do |xpath|
93
- doc.get_elements(xpath).to_a.each do |buildable_reference|
94
- suite_name = buildable_reference.attributes['BlueprintName']
95
- if (change_reason = changes_by_suite_name[suite_name])
96
- puts "#{suite_name} changed because #{change_reason}" if log_changes
97
- each_target&.call(type: :changed, target_name: suite_name, change_reason: change_reason)
98
- next
99
- end
100
- puts "#{suite_name} did not change, removing from scheme" if log_changes
101
- each_target&.call(type: :unchanged, target_name: suite_name, change_reason: nil)
102
- buildable_reference.parent.remove
103
- end
104
- end
105
-
106
- if filter_scheme_for_build_action == :testing
107
- doc.get_elements('//*/BuildActionEntry/BuildableReference').to_a.each do |buildable_reference|
108
- suite_name = buildable_reference.attributes['BlueprintName']
109
- if (change_reason = changes_by_suite_name[suite_name])
110
- puts "#{suite_name} changed because #{change_reason}" if log_changes
111
- each_target&.call(type: :changed, target_name: suite_name, change_reason: change_reason)
112
- next
113
- end
114
- puts "#{suite_name} did not change, setting to not build for testing" if log_changes
115
- each_target&.call(type: :unchanged, target_name: suite_name, change_reason: nil)
116
- buildable_reference.parent.attributes['buildForTesting'] = 'NO'
117
- end
118
- end
119
-
120
- scheme
121
- end
122
-
123
- # @return [String] a string suitable for user display that explains target changes
124
- # @param include_unchanged_targets [Boolean] whether targets that have not changed should also be displayed
125
- # @param change_level [Symbol] the change level used for computing whether a target has changed
126
- def format_changes(include_unchanged_targets: false, change_level: :full_transitive)
127
- annotate_targets!.group_by { |target| target.xcode_target.project.path.to_s }.sort_by(&:first)
128
- .map do |project, annotated_targets|
129
- changes = annotated_targets.sort_by { |annotated_target| annotated_target.xcode_target.name }
130
- .map do |annotated_target|
131
- change_reason = annotated_target.change_reason(level: change_level)
132
- next if !include_unchanged_targets && !change_reason
133
-
134
- change_reason ||= 'did not change'
135
- "\t#{annotated_target.xcode_target}: #{change_reason}"
136
- end.compact
137
- "#{project}:\n#{changes.join("\n")}" unless changes.empty?
138
- end.compact.join("\n")
139
- end
140
-
141
- private
142
-
143
- # @return [Array<Xcodeproj::Project>]
144
- def projects
145
- @projects ||= find_projects(workspace_path)
146
- end
147
-
148
- # @return [Hash<String,Array<Hash>>]
149
- def augmenting_paths_by_target
150
- @augmenting_paths_by_target ||= begin
151
- require 'yaml'
152
- augmenting_paths_yaml_files.reduce({}) do |augmenting_paths_by_target, yaml_file|
153
- yaml_file = Pathname(yaml_file).expand_path(changesets.first.repository)
154
- yaml = YAML.safe_load(yaml_file.read)
155
- augmenting_paths_by_target.merge(yaml) do |_target_name, prior_paths, new_paths|
156
- prior_paths + new_paths
157
- end
158
- end
159
- end
160
- end
161
-
162
- # @return [Array<AnnotatedTarget>] targets in the given list of Xcode projects,
163
- # annotated according to the given changeset
164
- def annotated_targets
165
- workspace_modification = find_workspace_modification_in_changesets
166
- project_changes = Hash[projects.map do |project|
167
- [project, find_project_modification_in_changesets(project: project) || workspace_modification]
168
- end]
169
-
170
- require 'tsort'
171
- targets = projects.flat_map(&:targets)
172
- targets_by_uuid = Hash[targets.map { |t| [t.uuid, t] }]
173
- targets_by_name = Hash[targets.map { |t| [t.name, t] }]
174
- targets_by_product_name = targets.each_with_object({}) do |t, h|
175
- next unless t.respond_to?(:product_reference)
176
- h[File.basename(t.product_reference.path)] = t
177
- h[File.basename(t.product_reference.name)] = t if t.product_reference.name
178
- end
179
-
180
- find_dep = ->(td) { targets_by_uuid[td.native_target_uuid] || targets_by_name[td.name] }
181
- target_deps = lambda do |target|
182
- target_dependencies = []
183
- target.dependencies.each do |td|
184
- target_dependencies << find_dep[td]
185
- end
186
-
187
- # TODO: also resolve OTHER_LDFLAGS?
188
- # yay auto-linking
189
- if (phase = target.frameworks_build_phases)
190
- phase.files_references.each do |fr|
191
- if (dt = fr&.path && targets_by_product_name[File.basename(fr.path)])
192
- target_dependencies << dt
193
- end
194
- end
195
- end
196
-
197
- target_dependencies
198
- end
199
-
200
- targets = TSort.tsort(
201
- ->(&b) { targets.each(&b) },
202
- ->(target, &b) { target_deps[target].each(&b) }
203
- )
204
-
205
- targets.each_with_object({}) do |target, h|
206
- change_reason = project_changes[target.project] || find_target_modification_in_changesets(target: target)
207
-
208
- h[target] = AnnotatedTarget.new(
209
- target: target,
210
- dependencies: target_deps[target].map { |td| h.fetch(td) },
211
- change_reason: change_reason
212
- )
213
- end.values
214
- end
215
-
216
- # @return [Array<Xcodeproj::Project>] the projects found by walking the
217
- # project/workspace at the given path
218
- # @param path [Pathname] path to a `.xcodeproj` or `.xcworkspace` on disk
219
- def find_projects(path)
220
- seen = {}
221
- find_projects_cached = lambda do |project_path|
222
- return if seen.key?(project_path)
223
-
224
- case File.extname(project_path)
225
- when '.xcodeproj'
226
- project = Xcodeproj::Project.open(project_path)
227
- seen[project_path] = project
228
- project.files.each do |file_reference|
229
- next unless File.extname(file_reference.path) == '.xcodeproj'
230
-
231
- find_projects_cached[file_reference.real_path]
232
- end
233
- when '.xcworkspace'
234
- workspace = Xcodeproj::Workspace.new_from_xcworkspace(project_path)
235
- workspace.file_references.each do |file_reference|
236
- next unless File.extname(file_reference.path) == '.xcodeproj'
237
-
238
- find_projects_cached[file_reference.absolute_path(File.dirname(project_path))]
239
- end
240
- else
241
- raise ArgumentError, "Unknown path #{project_path.inspect}"
242
- end
243
- end
244
- find_projects_cached[path]
245
-
246
- seen.values
247
- end
248
-
249
- # @yieldparam used_path [UsedPath] an absolute path that belongs to the given target
250
- # @return [Void]
251
- # @param target [Xcodeproj::Project::AbstractTarget]
252
- def target_each_file_path(target:)
253
- return enum_for(__method__, target: target) unless block_given?
254
-
255
- expand_build_settings = lambda do |s|
256
- return [s] unless s =~ /\$(?:\{([_a-zA-Z0-0]+?)\}|\(([_a-zA-Z0-0]+?)\))/
257
-
258
- match, key = Regexp.last_match.values_at(0, 1, 2).compact
259
- substitutions = target.resolved_build_setting(key, true).values.compact.uniq
260
- substitutions.flat_map do |sub|
261
- expand_build_settings[s.gsub(match, sub)]
262
- end
263
- end
264
-
265
- target.build_configuration_list.build_configurations.each do |build_configuration|
266
- ref = build_configuration.base_configuration_reference
267
- next unless ref
268
-
269
- yield UsedPath.new(path: ref.real_path,
270
- inclusion_reason: "base configuration reference for #{build_configuration}")
271
- end
272
-
273
- target.build_phases.each do |build_phase|
274
- build_phase.files_references.each do |fr|
275
- next unless fr
276
-
277
- yield UsedPath.new(path: fr.real_path,
278
- inclusion_reason: "#{build_phase.display_name.downcase.chomp('s')} file")
279
- end
280
- end
281
-
282
- target.shell_script_build_phases.each do |shell_script_build_phase|
283
- %w[input_file_list_paths output_file_list_paths input_paths output_paths].each do |method|
284
- next unless (paths = shell_script_build_phase.public_send(method))
285
-
286
- file_type = method.tr('_', ' ').chomp('s')
287
- paths.each do |config_path|
288
- next unless config_path
289
-
290
- expand_build_settings[config_path].each do |path|
291
- path = Pathname(path).expand_path(target.project.project_dir)
292
- yield UsedPath.new(path: path,
293
- inclusion_reason: "#{shell_script_build_phase.name} build phase #{file_type}")
294
- end
295
- end
296
- end
297
- end
298
-
299
- %w[INFOPLIST_FILE HEADER_SEARCH_PATHS FRAMEWORK_SEARCH_PATHS USER_HEADER_SEARCH_PATHS].each do |build_setting|
300
- target.resolved_build_setting(build_setting, true).each_value do |paths|
301
- Array(paths).each do |path|
302
- next unless path
303
-
304
- path = Pathname(path).expand_path(target.project.project_dir)
305
- yield UsedPath.new(path: path, inclusion_reason: "#{build_setting} value")
306
- end
307
- end
308
- end
309
- end
310
-
311
- # @return [FileModification,Nil] a modification to a file that is used by the given target, or `nil`
312
- # if none if found
313
- # @param target [Xcodeproj::Project::AbstractTarget]
314
- def find_target_modification_in_changesets(target:)
315
- augmenting_paths = used_paths_from_augmenting_paths_by_target[target.name]
316
- find_in_changesets = ->(path) { path.find_in_changesets(changesets) }
317
- Refinement.map_find(augmenting_paths, &find_in_changesets) ||
318
- Refinement.map_find(target_each_file_path(target: target), &find_in_changesets)
319
- end
320
-
321
- # @yieldparam used_path [UsedPath] an absolute path that belongs to the given project
322
- # @return [Void]
323
- # @param project [Xcodeproj::Project]
324
- def project_each_file_path(project:)
325
- return enum_for(__method__, project: project) unless block_given?
326
-
327
- yield UsedPath.new(path: project.path, inclusion_reason: 'project directory')
328
-
329
- project.root_object.build_configuration_list.build_configurations.each do |build_configuration|
330
- ref = build_configuration.base_configuration_reference
331
- next unless ref
332
-
333
- yield UsedPath.new(path: ref.real_path,
334
- inclusion_reason: "base configuration reference for #{build_configuration}")
335
- end
336
- end
337
-
338
- # # @return [FileModification,Nil] a modification to a file that is directly used by the given project, or `nil`
339
- # if none if found
340
- # @note This method does not take into account whatever file paths targets in the project may reference
341
- # @param project [Xcodeproj::Project]
342
- def find_project_modification_in_changesets(project:)
343
- Refinement.map_find(project_each_file_path(project: project)) do |path|
344
- path.find_in_changesets(changesets)
345
- end
346
- end
347
-
348
- # @return [FileModification,Nil] a modification to the workspace itself, or `nil`
349
- # if none if found
350
- # @note This method does not take into account whatever file paths projects or
351
- # targets in the workspace path may reference
352
- def find_workspace_modification_in_changesets
353
- return unless workspace_path
354
-
355
- UsedPath.new(path: workspace_path, inclusion_reason: 'workspace directory')
356
- .find_in_changesets(changesets)
357
- end
358
-
359
- # @return [Hash<String,UsedPath>]
360
- def used_paths_from_augmenting_paths_by_target
361
- @used_paths_from_augmenting_paths_by_target ||= begin
362
- repo = changesets.first.repository
363
- used_paths_from_augmenting_paths_by_target =
364
- augmenting_paths_by_target.each_with_object({}) do |(name, augmenting_paths), h|
365
- h[name] = augmenting_paths.map do |augmenting_path|
366
- case augmenting_path.keys.sort
367
- when %w[inclusion_reason path], %w[inclusion_reason path yaml_keypath]
368
- kwargs = {
369
- path: Pathname(augmenting_path['path']).expand_path(repo),
370
- inclusion_reason: augmenting_path['inclusion_reason']
371
- }
372
- if augmenting_path.key?('yaml_keypath')
373
- kwargs[:yaml_keypath] = augmenting_path['yaml_keypath']
374
- UsedPath::YAML.new(**kwargs)
375
- else
376
- UsedPath.new(**kwargs)
377
- end
378
- when %w[glob inclusion_reason]
379
- UsedGlob.new(glob: File.expand_path(augmenting_path['glob'], repo),
380
- inclusion_reason: augmenting_path['inclusion_reason'])
381
- else
382
- raise ArgumentError,
383
- "unhandled set of keys in augmenting paths dictionary entry: #{augmenting_path.keys.inspect}"
384
- end
385
- end
386
- end
387
- wildcard_paths = used_paths_from_augmenting_paths_by_target.fetch('*', [])
388
-
389
- Hash.new do |h, k|
390
- h[k] = wildcard_paths + used_paths_from_augmenting_paths_by_target.fetch(k, [])
391
- end
392
- end
393
- end
394
- end
395
- end