refinement 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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