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 +4 -4
- data/VERSION +1 -1
- data/lib/refinement/analyzer.rb +54 -36
- data/lib/refinement/annotated_target.rb +3 -1
- data/lib/refinement/changeset.rb +6 -3
- data/lib/refinement/changeset/file_modification.rb +1 -1
- data/lib/refinement/cocoapods_post_install_writer.rb +4 -0
- data/lib/refinement/used_path.rb +47 -9
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56b3b7a2e07f1e8fa23b66b8315d00251b6e06bb95be13d8aa003c03da61a31a
|
4
|
+
data.tar.gz: ccee2542ae61bb6de2593ac355cf19caf7875d42ecfb19adc15905857472be39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2865c1e30ca1b080dba0c186dbcd1b3656b9aae9a5c793727738f34c3c6b110c53c266d0be46a28aa8f005794b8e48aca551cd37d92d63f08ac479bb6439c37b
|
7
|
+
data.tar.gz: 7b793a31d222ba4ac8621812c5c963289aed3b4aee10dc31818266dd25b92db85d3f1ea25bcb29af94472010d5fbbf356f60e049c1ba797186ef818159ce9ac1
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.1
|
data/lib/refinement/analyzer.rb
CHANGED
@@ -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 :
|
8
|
-
private :
|
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
|
11
|
-
# @param
|
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(
|
25
|
+
def initialize(changesets:, workspace_path:, projects: nil,
|
26
26
|
augmenting_paths_yaml_files:, augmenting_paths_by_target: nil)
|
27
27
|
|
28
|
-
@
|
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
|
-
# @
|
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
|
-
|
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
|
-
|
84
|
-
|
86
|
+
changes_by_suite_name = Hash[annotate_targets!
|
87
|
+
.map { |at| [at.xcode_target.name, at.change_reason(level: change_level)] }]
|
85
88
|
|
86
|
-
|
89
|
+
doc = scheme.doc
|
87
90
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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(
|
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 =
|
165
|
+
workspace_modification = find_workspace_modification_in_changesets
|
148
166
|
project_changes = Hash[projects.map do |project|
|
149
|
-
[project,
|
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] ||
|
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
|
314
|
+
def find_target_modification_in_changesets(target:)
|
297
315
|
augmenting_paths = used_paths_from_augmenting_paths_by_target[target.name]
|
298
|
-
|
299
|
-
Refinement.map_find(augmenting_paths, &
|
300
|
-
Refinement.map_find(target_each_file_path(target: target), &
|
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
|
342
|
+
def find_project_modification_in_changesets(project:)
|
325
343
|
Refinement.map_find(project_each_file_path(project: project)) do |path|
|
326
|
-
path.
|
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
|
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
|
-
.
|
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 =
|
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
|
-
|
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
|
data/lib/refinement/changeset.rb
CHANGED
@@ -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|
|
data/lib/refinement/used_path.rb
CHANGED
@@ -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
|
-
|
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.
|
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:
|
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: []
|