refinement 0.3.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23ec4c7c515278641dbd8dd7f8927a42126801d00d0963979a5deca88937382d
4
- data.tar.gz: 10b9168d22d7853fbcbdc0d5a47127643a86f41a55bf6260b9f21c3a88457778
3
+ metadata.gz: bc6b7da6738a2abb9d51bbb8b07333f370ef7f819307217e01cee42cde2b4ea4
4
+ data.tar.gz: bf21a6db5de7f51904296972c4e45f27b8853b5751ec31705d7d3e45d2d7f80f
5
5
  SHA512:
6
- metadata.gz: d88a448b3f573e5f7441dd81b2fc818b928f61f4aeb7fcbbf4b31a9cbb33fd64cc0f91b9d9199a572017b1bde699595c694831368ac528d961d54f3e65f951d3
7
- data.tar.gz: 7d9eddf6218a4f70d433a55e8fa1aef2b055dbe5d8f599c94e1496a407de45704e260808562dd2c8358991ca40054cb0eeeae52740c011d5d5384428e9124038
6
+ metadata.gz: 7333b23a303b68863872011b638d1bab86fb29ea56dd244a33e194873e0c90c4969a1340636c2a80bcee4d0f141b5f12003d038e21ecd10f566e3590d14e63a1
7
+ data.tar.gz: 024b7afb50f77256b0b399c3c349a3cd2abb92eeda59e51a4dfd1c3a80a757e1901937417e64471be04282470c692489ba10ec23482c72618f8e6e3fa9ec9c8d
@@ -1,5 +1,12 @@
1
1
  # Refinement Changes
2
2
 
3
+ ## 0.3.1 (2019-08-09)
4
+
5
+ ##### Bug Fixes
6
+
7
+ * Take into consideration product reference names when refinining.
8
+
9
+
3
10
  ## 0.3.0 (2019-08-09)
4
11
 
5
12
  ##### Bug Fixes
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.6.0
@@ -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,20 +162,20 @@ 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'
153
171
  targets = projects.flat_map(&:targets)
154
172
  targets_by_uuid = Hash[targets.map { |t| [t.uuid, t] }]
155
173
  targets_by_name = Hash[targets.map { |t| [t.name, t] }]
156
- targets_by_product_name = Hash[targets.map do |t|
174
+ targets_by_product_name = targets.each_with_object({}) do |t, h|
157
175
  next unless t.respond_to?(:product_reference)
158
-
159
- [File.basename(t.product_reference.path), t]
160
- end.compact]
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
161
179
 
162
180
  find_dep = ->(td) { targets_by_uuid[td.native_target_uuid] || targets_by_name[td.name] }
163
181
  target_deps = lambda do |target|
@@ -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 = {
@@ -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.0
4
+ version: 0.6.0
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-08-12 00:00:00.000000000 Z
11
+ date: 2021-01-22 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
@@ -85,8 +85,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
85
  - !ruby/object:Gem::Version
86
86
  version: '0'
87
87
  requirements: []
88
- rubygems_version: 3.0.4
89
- signing_key:
88
+ rubygems_version: 3.0.1
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: []