refinement 0.3.1 → 0.6.1

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: 42b43f8f3081cf4cca09c0a24956f5f12acdf6a9d4d6118c50e633be98314eed
4
- data.tar.gz: 0c3146bb128a1c914a0d8ada40bb822f6e2fb6ea808c9f375d5cfc2bd28d971b
3
+ metadata.gz: 56b3b7a2e07f1e8fa23b66b8315d00251b6e06bb95be13d8aa003c03da61a31a
4
+ data.tar.gz: ccee2542ae61bb6de2593ac355cf19caf7875d42ecfb19adc15905857472be39
5
5
  SHA512:
6
- metadata.gz: b317658245f639d73554c56bc25db74305f046376b5b966b2cc9a443c379390ace4fe5e8ccbbe7318f18722d707271439211d2b0635ce7b418191a4f471f4b6f
7
- data.tar.gz: 40fe0d269f5dbf305ac152eb3bbcd9961f596e4dde117602154dbf2f4b0266c44a856770d709e437010efec9d8bbd4f772e278c4c1d63fa743b618b67a84f446
6
+ metadata.gz: 2865c1e30ca1b080dba0c186dbcd1b3656b9aae9a5c793727738f34c3c6b110c53c266d0be46a28aa8f005794b8e48aca551cd37d92d63f08ac479bb6439c37b
7
+ data.tar.gz: 7b793a31d222ba4ac8621812c5c963289aed3b4aee10dc31818266dd25b92db85d3f1ea25bcb29af94472010d5fbbf356f60e049c1ba797186ef818159ce9ac1
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.6.1
@@ -4,11 +4,11 @@ module Refinement
4
4
  # Analyzes changes in a repository
5
5
  # and determines how those changes impact the targets in Xcode projects in the workspace.
6
6
  class Analyzer
7
- attr_reader :changeset, :workspace_path, :augmenting_paths_yaml_files
8
- private :changeset, :workspace_path, :augmenting_paths_yaml_files
7
+ attr_reader :changesets, :workspace_path, :augmenting_paths_yaml_files
8
+ private :changesets, :workspace_path, :augmenting_paths_yaml_files
9
9
 
10
- # Initializes an analyzer with a changeset, projects, and augmenting paths.
11
- # @param changeset [Changeset]
10
+ # Initializes an analyzer with changesets, projects, and augmenting paths.
11
+ # @param changesets [Array<Changeset>]
12
12
  # @param workspace_path [Pathname] path to a root workspace or project,
13
13
  # must be `nil` if `projects` are specified explicitly
14
14
  # @param projects [Array<Xcodeproj::Project>] projects to find targets in,
@@ -22,10 +22,10 @@ module Refinement
22
22
  #
23
23
  # @raise [ArgumentError] when conflicting arguments are given
24
24
  #
25
- def initialize(changeset:, workspace_path:, projects: nil,
25
+ def initialize(changesets:, workspace_path:, projects: nil,
26
26
  augmenting_paths_yaml_files:, augmenting_paths_by_target: nil)
27
27
 
28
- @changeset = changeset
28
+ @changesets = changesets
29
29
 
30
30
  raise ArgumentError, 'Can only specify one of workspace_path and projects' if workspace_path && projects
31
31
 
@@ -57,9 +57,10 @@ module Refinement
57
57
  # `:building` and `:testing`, with the only difference being `BuildActionEntry` are not
58
58
  # filtered out when building for testing, since test action macro expansion could
59
59
  # depend on a build entry being present.
60
- # @return [Xcodeproj::XCScheme] a scheme whose unchanged targets have been removed
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.
61
62
  def filtered_scheme(scheme_path:, change_level: :full_transitive, filter_when_scheme_has_changed: false, log_changes: false,
62
- filter_scheme_for_build_action:)
63
+ filter_scheme_for_build_action:, each_target: nil)
63
64
  scheme = Xcodeproj::XCScheme.new(scheme_path)
64
65
 
65
66
  sections_to_filter =
@@ -77,25 +78,42 @@ module Refinement
77
78
  "Given: #{filter_scheme_for_build_action.inspect}."
78
79
  end
79
80
 
80
- if filter_when_scheme_has_changed ||
81
- !UsedPath.new(path: Pathname(scheme_path), inclusion_reason: 'scheme').find_in_changeset(changeset)
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
82
85
 
83
- changes_by_suite_name = Hash[annotate_targets!
84
- .map { |at| [at.xcode_target.name, at.change_reason(level: change_level)] }]
86
+ changes_by_suite_name = Hash[annotate_targets!
87
+ .map { |at| [at.xcode_target.name, at.change_reason(level: change_level)] }]
85
88
 
86
- doc = scheme.doc
89
+ doc = scheme.doc
87
90
 
88
- xpaths = sections_to_filter.map { |section| "//*/#{section}/BuildableReference" }
89
- xpaths.each do |xpath|
90
- doc.get_elements(xpath).to_a.each do |buildable_reference|
91
- suite_name = buildable_reference.attributes['BlueprintName']
92
- if (change_reason = changes_by_suite_name[suite_name])
93
- puts "#{suite_name} changed because #{change_reason}" if log_changes
94
- next
95
- end
96
- puts "#{suite_name} did not change, removing from scheme" if log_changes
97
- buildable_reference.parent.remove
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
98
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'
99
117
  end
100
118
  end
101
119
 
@@ -132,7 +150,7 @@ module Refinement
132
150
  @augmenting_paths_by_target ||= begin
133
151
  require 'yaml'
134
152
  augmenting_paths_yaml_files.reduce({}) do |augmenting_paths_by_target, yaml_file|
135
- yaml_file = Pathname(yaml_file).expand_path(changeset.repository)
153
+ yaml_file = Pathname(yaml_file).expand_path(changesets.first.repository)
136
154
  yaml = YAML.safe_load(yaml_file.read)
137
155
  augmenting_paths_by_target.merge(yaml) do |_target_name, prior_paths, new_paths|
138
156
  prior_paths + new_paths
@@ -144,9 +162,9 @@ module Refinement
144
162
  # @return [Array<AnnotatedTarget>] targets in the given list of Xcode projects,
145
163
  # annotated according to the given changeset
146
164
  def annotated_targets
147
- workspace_modification = find_workspace_modification_in_changeset
165
+ workspace_modification = find_workspace_modification_in_changesets
148
166
  project_changes = Hash[projects.map do |project|
149
- [project, find_project_modification_in_changeset(project: project) || workspace_modification]
167
+ [project, find_project_modification_in_changesets(project: project) || workspace_modification]
150
168
  end]
151
169
 
152
170
  require 'tsort'
@@ -185,7 +203,7 @@ module Refinement
185
203
  )
186
204
 
187
205
  targets.each_with_object({}) do |target, h|
188
- change_reason = project_changes[target.project] || find_target_modification_in_changeset(target: target)
206
+ change_reason = project_changes[target.project] || find_target_modification_in_changesets(target: target)
189
207
 
190
208
  h[target] = AnnotatedTarget.new(
191
209
  target: target,
@@ -293,11 +311,11 @@ module Refinement
293
311
  # @return [FileModification,Nil] a modification to a file that is used by the given target, or `nil`
294
312
  # if none if found
295
313
  # @param target [Xcodeproj::Project::AbstractTarget]
296
- def find_target_modification_in_changeset(target:)
314
+ def find_target_modification_in_changesets(target:)
297
315
  augmenting_paths = used_paths_from_augmenting_paths_by_target[target.name]
298
- find_in_changeset = ->(path) { path.find_in_changeset(changeset) }
299
- Refinement.map_find(augmenting_paths, &find_in_changeset) ||
300
- Refinement.map_find(target_each_file_path(target: target), &find_in_changeset)
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)
301
319
  end
302
320
 
303
321
  # @yieldparam used_path [UsedPath] an absolute path that belongs to the given project
@@ -321,9 +339,9 @@ module Refinement
321
339
  # if none if found
322
340
  # @note This method does not take into account whatever file paths targets in the project may reference
323
341
  # @param project [Xcodeproj::Project]
324
- def find_project_modification_in_changeset(project:)
342
+ def find_project_modification_in_changesets(project:)
325
343
  Refinement.map_find(project_each_file_path(project: project)) do |path|
326
- path.find_in_changeset(changeset)
344
+ path.find_in_changesets(changesets)
327
345
  end
328
346
  end
329
347
 
@@ -331,17 +349,17 @@ module Refinement
331
349
  # if none if found
332
350
  # @note This method does not take into account whatever file paths projects or
333
351
  # targets in the workspace path may reference
334
- def find_workspace_modification_in_changeset
352
+ def find_workspace_modification_in_changesets
335
353
  return unless workspace_path
336
354
 
337
355
  UsedPath.new(path: workspace_path, inclusion_reason: 'workspace directory')
338
- .find_in_changeset(changeset)
356
+ .find_in_changesets(changesets)
339
357
  end
340
358
 
341
359
  # @return [Hash<String,UsedPath>]
342
360
  def used_paths_from_augmenting_paths_by_target
343
361
  @used_paths_from_augmenting_paths_by_target ||= begin
344
- repo = changeset.repository
362
+ repo = changesets.first.repository
345
363
  used_paths_from_augmenting_paths_by_target =
346
364
  augmenting_paths_by_target.each_with_object({}) do |(name, augmenting_paths), h|
347
365
  h[name] = augmenting_paths.map do |augmenting_path|
@@ -57,7 +57,9 @@ module Refinement
57
57
  change_reason = direct_change_reason
58
58
  if distance_from_target.positive?
59
59
  change_reason ||= Refinement.map_find(dependencies) do |dependency|
60
- next unless (dependency_change_reason = dependency.change_reason(level: [:at_most_n_away, level.last.pred]))
60
+ unless (dependency_change_reason = dependency.change_reason(level: [:at_most_n_away, level.last.pred]))
61
+ next
62
+ end
61
63
 
62
64
  "dependency #{dependency} changed because #{dependency_change_reason}"
63
65
  end
@@ -18,12 +18,15 @@ module Refinement
18
18
  attr_reader :modified_paths
19
19
  # @return [Hash<Pathname,FileModification>] modifications keyed by relative path
20
20
  attr_reader :modified_absolute_paths
21
+ # @return [String] a desciption of the changeset
22
+ attr_reader :description
21
23
 
22
24
  private :modifications, :modified_paths, :modified_absolute_paths
23
25
 
24
- def initialize(repository:, modifications:)
26
+ def initialize(repository:, modifications:, description: nil)
25
27
  @repository = repository
26
28
  @modifications = self.class.add_directories(modifications).uniq.freeze
29
+ @description = description
27
30
 
28
31
  @modified_paths = {}
29
32
  @modifications
@@ -86,7 +89,7 @@ module Refinement
86
89
  pattern = pattern.gsub('/**/', '{/**/,/}')
87
90
  values_by_set = {}
88
91
  pattern.scan(/\{[^}]*\}/) do |set|
89
- values = set.gsub(/[{}]/, '').split(',')
92
+ values = set.gsub(/[{}]/, '').split(',', -1)
90
93
  values_by_set[set] = values
91
94
  end
92
95
 
@@ -145,7 +148,7 @@ module Refinement
145
148
  diff = git!('diff', '--raw', '-z', merge_base, chdir: repository)
146
149
  modifications = parse_raw_diff(diff, repository: repository, base_revision: merge_base).freeze
147
150
 
148
- new(repository: repository, modifications: modifications)
151
+ new(repository: repository, modifications: modifications, description: "since #{base_revision}")
149
152
  end
150
153
 
151
154
  CHANGE_TYPES = {
@@ -77,7 +77,7 @@ module Refinement
77
77
  dig_yaml = lambda do |yaml, path|
78
78
  return yaml if DOES_NOT_EXIST == yaml
79
79
 
80
- object = @cached_yaml[path] ||= YAML.safe_load(yaml, [Symbol])
80
+ object = @cached_yaml[path] ||= YAML.safe_load(yaml, [Symbol], aliases: true)
81
81
  if keypath.empty?
82
82
  object
83
83
  elsif object.respond_to?(:dig)
@@ -103,6 +103,10 @@ module Refinement
103
103
  inclusion_reason: 'CocoaPods lockfile',
104
104
  yaml_keypath: ['SPEC CHECKSUMS', pod_target.pod_name] }
105
105
  ]
106
+ if pod_target.sandbox.predownloaded?(pod_target.pod_name)
107
+ paths << { path: 'Podfile.lock', inclusion_reason: 'Dependency external source', yaml_keypath: ['EXTERNAL SOURCES', pod_target.pod_name] }
108
+ paths << { path: 'Podfile.lock', inclusion_reason: 'Pod checkout options', yaml_keypath: ['CHECKOUT OPTIONS', pod_target.pod_name] }
109
+ end
106
110
  spec_paths.each { |path| paths << { path: path, inclusion_reason: 'podspec' } }
107
111
 
108
112
  Pod::Validator::FILE_PATTERNS.each do |pattern|
@@ -19,7 +19,17 @@ module Refinement
19
19
  # @return [Nil, String] If the path has been modified, a string explaining the modification
20
20
  # @param changeset [Changeset] the changeset to search for a modification to this path
21
21
  def find_in_changeset(changeset)
22
- add_reason changeset.find_modification_for_path(absolute_path: path)
22
+ add_reason changeset.find_modification_for_path(absolute_path: path), changeset: changeset
23
+ end
24
+
25
+ # @return [Nil, String] If the path has been modified, a string explaining the modification
26
+ # @param changesets [Array<Changeset>] the changesets to search for a modification to this path
27
+ def find_in_changesets(changesets)
28
+ raise ArgumentError, 'Must provide at least one changeset' if changesets.empty?
29
+
30
+ changesets.reduce(true) do |explanation, changeset|
31
+ explanation && find_in_changeset(changeset)
32
+ end
23
33
  end
24
34
 
25
35
  # @return [String]
@@ -33,10 +43,22 @@ module Refinement
33
43
  # @return [Nil, String] A string suitable for user display that explains
34
44
  # why the given modification means a target is modified
35
45
  # @param modification [Nil, FileModification]
36
- def add_reason(modification)
46
+ # @param changeset [Changeset]
47
+ def add_reason(modification, changeset:)
37
48
  return unless modification
38
49
 
39
- "#{modification.path} (#{inclusion_reason}) #{modification.type}"
50
+ add_changeset_description "#{modification.path} (#{inclusion_reason}) #{modification.type}", changeset: changeset
51
+ end
52
+
53
+ # @return [String] A string suitable for user display that explains
54
+ # why the given modification means a target is modified, including the description
55
+ # of the changeset that contains the modification
56
+ # @param description [String]
57
+ # @param changeset [Nil, Changeset]
58
+ def add_changeset_description(description, changeset:)
59
+ return description unless changeset&.description
60
+
61
+ description + " (#{changeset.description})"
40
62
  end
41
63
 
42
64
  # Represents a path to a YAML file that some target depends upon,
@@ -54,7 +76,7 @@ module Refinement
54
76
  # (see UsedPath#find_in_changeset)
55
77
  def find_in_changeset(changeset)
56
78
  modification, _yaml_diff = changeset.find_modification_for_yaml_keypath(absolute_path: path, keypath: yaml_keypath)
57
- add_reason modification
79
+ add_reason modification, changeset: changeset
58
80
  end
59
81
 
60
82
  # (see UsedPath#to_s)
@@ -65,7 +87,7 @@ module Refinement
65
87
  private
66
88
 
67
89
  # (see UsedPath#add_reason)
68
- def add_reason(modification)
90
+ def add_reason(modification, changeset:)
69
91
  return unless modification
70
92
 
71
93
  keypath_string =
@@ -74,7 +96,7 @@ module Refinement
74
96
  else
75
97
  ' @ ' + yaml_keypath.map { |path| path.to_s =~ /\A[a-zA-Z0-9_]+\z/ ? path : path.inspect }.join('.')
76
98
  end
77
- "#{modification.path}#{keypath_string} (#{inclusion_reason}) #{modification.type}"
99
+ add_changeset_description "#{modification.path}#{keypath_string} (#{inclusion_reason}) #{modification.type}", changeset: changeset
78
100
  end
79
101
  end
80
102
  end
@@ -96,7 +118,16 @@ module Refinement
96
118
 
97
119
  # (see UsedPath#find_in_changeset)
98
120
  def find_in_changeset(changeset)
99
- add_reason changeset.find_modification_for_glob(absolute_glob: glob)
121
+ add_reason changeset.find_modification_for_glob(absolute_glob: glob), changeset: changeset
122
+ end
123
+
124
+ # (see UsedPath#find_in_changesets)
125
+ def find_in_changesets(changesets)
126
+ raise ArgumentError, 'Must provide at least one changeset' if changesets.empty?
127
+
128
+ changesets.reduce(true) do |explanation, changeset|
129
+ explanation && find_in_changeset(changeset)
130
+ end
100
131
  end
101
132
 
102
133
  # (see UsedPath#to_s)
@@ -107,10 +138,17 @@ module Refinement
107
138
  private
108
139
 
109
140
  # (see UsedPath#add_reason)
110
- def add_reason(modification)
141
+ def add_reason(modification, changeset:)
111
142
  return unless modification
112
143
 
113
- "#{modification.path} (#{inclusion_reason}) #{modification.type}"
144
+ add_changeset_description "#{modification.path} (#{inclusion_reason}) #{modification.type}", changeset: changeset
145
+ end
146
+
147
+ # (see UsedPath#add_changeset_description)
148
+ def add_changeset_description(description, changeset:)
149
+ return description unless changeset&.description
150
+
151
+ description + " (#{changeset.description})"
114
152
  end
115
153
  end
116
154
  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.3.1
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Giddins
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-09-20 00:00:00.000000000 Z
11
+ date: 2021-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xcodeproj
@@ -44,7 +44,7 @@ dependencies:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '10.0'
47
- description:
47
+ description:
48
48
  email:
49
49
  - segiddins@squareup.com
50
50
  executables:
@@ -70,7 +70,7 @@ files:
70
70
  homepage: https://github.com/square/refinement
71
71
  licenses: []
72
72
  metadata: {}
73
- post_install_message:
73
+ post_install_message:
74
74
  rdoc_options: []
75
75
  require_paths:
76
76
  - lib
@@ -86,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
86
  version: '0'
87
87
  requirements: []
88
88
  rubygems_version: 3.0.1
89
- signing_key:
89
+ signing_key:
90
90
  specification_version: 4
91
91
  summary: Generates a list of Xcode targets to build & test as a result of a git diff.
92
92
  test_files: []