refinement 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Refinement
4
- # A target, annotated with any changes
5
- class AnnotatedTarget
6
- # @return [Xcodeproj::Project::AbstactTarget] the target in an Xcode project
7
- attr_reader :xcode_target
8
-
9
- # @return [String,Nil] the reason why the target has changed, or `nil` if it has not changed
10
- attr_reader :direct_change_reason
11
- private :direct_change_reason
12
-
13
- def initialize(target:, change_reason:, dependencies: [])
14
- @xcode_target = target
15
- @direct_change_reason = change_reason
16
- @dependencies = dependencies
17
- dependencies.each do |dependency|
18
- dependency.depended_upon_by << self
19
- end
20
- @depended_upon_by = []
21
- end
22
-
23
- # @visibility private
24
- def to_s
25
- xcode_target.to_s
26
- end
27
-
28
- CHANGE_LEVELS = %i[
29
- itself
30
- at_most_n_away
31
- full_transitive
32
- ].freeze
33
- private_constant :CHANGE_LEVELS
34
-
35
- # @return [Boolean] whether the target has changed, at the given change level
36
- # @param level [Symbol,(:at_most_n_away,Integer)] change level, e.g. :itself, :at_most_n_away, :full_transitive
37
- def change_reason(level:)
38
- @change_reason ||= {}
39
- # need to use this form for memoization, as opposed to ||=,
40
- # since this will (often) be nil and it makes a significant performance difference
41
- return @change_reason[level] if @change_reason.key?(level)
42
-
43
- @change_reason[level] =
44
- case level
45
- when :itself
46
- direct_change_reason
47
- when :full_transitive
48
- direct_change_reason || Refinement.map_find(dependencies) do |dependency|
49
- next unless (dependency_change_reason = dependency.change_reason(level: level))
50
-
51
- "dependency #{dependency} changed because #{dependency_change_reason}"
52
- end
53
- when proc { |symbol, int| (symbol == :at_most_n_away) && int.is_a?(Integer) }
54
- distance_from_target = level.last
55
- raise ArgumentError, "level must be positive, not #{distance_from_target}" if distance_from_target.negative?
56
-
57
- change_reason = direct_change_reason
58
- if distance_from_target.positive?
59
- change_reason ||= Refinement.map_find(dependencies) do |dependency|
60
- unless (dependency_change_reason = dependency.change_reason(level: [:at_most_n_away, level.last.pred]))
61
- next
62
- end
63
-
64
- "dependency #{dependency} changed because #{dependency_change_reason}"
65
- end
66
- end
67
- change_reason
68
- else
69
- raise Error, "No known change level #{level.inspect}, only #{CHANGE_LEVELS.inspect} are known"
70
- end
71
- end
72
-
73
- # @return [Array<AnnotatedTarget>] the list of annotated targets this target depends upon
74
- attr_reader :dependencies
75
- # @return [Array<AnnotatedTarget>] the list of annotated targets that depend upon this target
76
- attr_reader :depended_upon_by
77
- end
78
- end
@@ -1,138 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Refinement
4
- class Changeset
5
- # Represents a modification to a single file or directory on disk
6
- class FileModification
7
- # @return [Symbol] the change type for directories
8
- DIRECTORY_CHANGE_TYPE = :'had contents change'
9
-
10
- # @return [Pathname] the path to the modified file
11
- attr_reader :path
12
-
13
- # @return [Pathname, Nil] the prior path to the modified file, or `nil` if it was not renamed or copied
14
- attr_reader :prior_path
15
-
16
- # @return [#to_s] the type of change that happened to this file
17
- attr_reader :type
18
-
19
- def initialize(path:, type:,
20
- prior_path: nil,
21
- contents_reader: -> { nil },
22
- prior_contents_reader: -> { nil })
23
- @path = path
24
- @type = type
25
- @prior_path = prior_path
26
- @contents_reader = contents_reader
27
- @prior_contents_reader = prior_contents_reader
28
- end
29
-
30
- # @visibility private
31
- def to_s
32
- case type
33
- when DIRECTORY_CHANGE_TYPE
34
- "contents of dir `#{path}` changed"
35
- else
36
- message = "file `#{path}` #{type}"
37
- message += " (from #{prior_path})" if prior_path
38
- message
39
- end
40
- end
41
-
42
- # @visibility private
43
- def inspect
44
- "#<#{self.class} path=#{path.inspect} type=#{type.inspect} prior_path=#{prior_path.inspect}" \
45
- " contents=#{contents.inspect} prior_contents=#{prior_contents.inspect}>"
46
- end
47
-
48
- # @visibility private
49
- def hash
50
- path.hash ^ type.hash
51
- end
52
-
53
- # @visibility private
54
- def ==(other)
55
- return unless other.is_a?(FileModification)
56
-
57
- (path == other.path) && (type == other.type) && prior_path == other.prior_path
58
- end
59
-
60
- # @visibility private
61
- def eql?(other)
62
- return unless other.is_a?(FileModification)
63
-
64
- path.eql?(other.path) && type.eql?(other.type) && prior_path.eql?(other.prior_path)
65
- end
66
-
67
- # @return [String,Nil] a YAML string representing the diff of the file
68
- # from the prior revision to the current revision at the given keypath
69
- # in the YAML, or `nil` if there is no diff
70
- # @param keypath [Array] a list of indices passed to `dig`.
71
- # An empty array is equivalent to the entire YAML document
72
- def yaml_diff(keypath)
73
- require 'yaml'
74
-
75
- @cached_yaml ||= {}
76
-
77
- dig_yaml = lambda do |yaml, path|
78
- return yaml if DOES_NOT_EXIST == yaml
79
-
80
- object = @cached_yaml[path] ||= YAML.safe_load(yaml, [Symbol])
81
- if keypath.empty?
82
- object
83
- elsif object.respond_to?(:dig)
84
- object.dig(*keypath)
85
- else # backwards compatibility
86
- keypath.reduce(object) do |acc, elem|
87
- acc[elem]
88
- end
89
- end
90
- end
91
-
92
- prior = dig_yaml[prior_contents, :prior]
93
- current = dig_yaml[contents, :current]
94
-
95
- require 'xcodeproj/differ'
96
-
97
- return unless (diff = Xcodeproj::Differ.diff(
98
- prior,
99
- current,
100
- key_1: 'prior_revision',
101
- key_2: 'current_revision'
102
- ))
103
-
104
- diff.to_yaml.prepend("#{path} changed at keypath #{keypath.inspect}\n")
105
- end
106
-
107
- DOES_NOT_EXIST = Object.new.tap do |o|
108
- class << o
109
- def to_s
110
- 'DOES NOT EXISTS'
111
- end
112
- alias_method :inspect, :to_s
113
- end
114
- end.freeze
115
- private_constant :DOES_NOT_EXIST
116
-
117
- # @return [String] the current contents of the file
118
- def contents
119
- @contents ||=
120
- begin
121
- @contents_reader[].tap { @contents_reader = nil } || DOES_NOT_EXIST
122
- rescue StandardError
123
- DOES_NOT_EXIST
124
- end
125
- end
126
-
127
- # @return [String] the prior contents of the file
128
- def prior_contents
129
- @prior_contents ||=
130
- begin
131
- @prior_contents_reader[].tap { @prior_contents_reader = nil } || DOES_NOT_EXIST
132
- rescue StandardError
133
- DOES_NOT_EXIST
134
- end
135
- end
136
- end
137
- end
138
- end
@@ -1,223 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cocoapods/executable'
4
- require 'set'
5
-
6
- module Refinement
7
- # Represents a set of changes in a repository between a prior revision and the current state
8
- class Changeset
9
- # An error that happens when computing a git diff
10
- class GitError < Error; end
11
- require 'refinement/changeset/file_modification'
12
-
13
- # @return [Pathname] the path to the repository
14
- attr_reader :repository
15
- # @return [Array<FileModification>] the modifications in the changeset
16
- attr_reader :modifications
17
- # @return [Hash<Pathname,FileModification>] modifications keyed by relative path
18
- attr_reader :modified_paths
19
- # @return [Hash<Pathname,FileModification>] modifications keyed by relative path
20
- attr_reader :modified_absolute_paths
21
- # @return [String] a desciption of the changeset
22
- attr_reader :description
23
-
24
- private :modifications, :modified_paths, :modified_absolute_paths
25
-
26
- def initialize(repository:, modifications:, description: nil)
27
- @repository = repository
28
- @modifications = self.class.add_directories(modifications).uniq.freeze
29
- @description = description
30
-
31
- @modified_paths = {}
32
- @modifications
33
- .each { |mod| @modified_paths[mod.path] = mod }
34
- .each { |mod| @modified_paths[mod.prior_path] ||= mod if mod.prior_path }
35
- @modified_paths.freeze
36
-
37
- @modified_absolute_paths = {}
38
- @modified_paths
39
- .each { |path, mod| @modified_absolute_paths[path.expand_path(repository).freeze] = mod }
40
- @modified_absolute_paths.freeze
41
- end
42
-
43
- # @visibility private
44
- # @return [Array<FileModification>] file modifications that include modifications for each
45
- # directory that has had a child modified
46
- # @param modifications [Array<FileModification>] The modifications to add directory modifications to
47
- def self.add_directories(modifications)
48
- dirs = Set.new
49
- add = lambda { |path|
50
- break unless dirs.add?(path)
51
-
52
- add[path.dirname]
53
- }
54
- modifications.each do |mod|
55
- add[mod.path.dirname]
56
- add[mod.prior_path.dirname] if mod.prior_path
57
- end
58
- modifications +
59
- dirs.map { |d| FileModification.new(path: Pathname("#{d}/").freeze, type: FileModification::DIRECTORY_CHANGE_TYPE) }
60
- end
61
-
62
- # @return [FileModification,Nil] the changeset for the given absolute path,
63
- # or `nil` if the given path is un-modified
64
- # @param absolute_path [Pathname]
65
- def find_modification_for_path(absolute_path:)
66
- modified_absolute_paths[absolute_path]
67
- end
68
-
69
- # @return [Array<String>] An array of patterns converted from a
70
- # {Dir.glob} pattern to patterns that {File.fnmatch} can handle.
71
- # This is used by the {#relative_glob} method to emulate
72
- # {Dir.glob}.
73
- #
74
- # The expansion provides support for:
75
- #
76
- # - Literals
77
- #
78
- # dir_glob_equivalent_patterns('{file1,file2}.{h,m}')
79
- # => ["file1.h", "file1.m", "file2.h", "file2.m"]
80
- #
81
- # - Matching the direct children of a directory with `**`
82
- #
83
- # dir_glob_equivalent_patterns('Classes/**/file.m')
84
- # => ["Classes/**/file.m", "Classes/file.m"]
85
- #
86
- # @param [String] pattern A {Dir#glob} like pattern.
87
- #
88
- def dir_glob_equivalent_patterns(pattern)
89
- pattern = pattern.gsub('/**/', '{/**/,/}')
90
- values_by_set = {}
91
- pattern.scan(/\{[^}]*\}/) do |set|
92
- values = set.gsub(/[{}]/, '').split(',', -1)
93
- values_by_set[set] = values
94
- end
95
-
96
- if values_by_set.empty?
97
- [pattern]
98
- else
99
- patterns = [pattern]
100
- values_by_set.each do |set, values|
101
- patterns = patterns.flat_map do |old_pattern|
102
- values.map do |value|
103
- old_pattern.gsub(set, value)
104
- end
105
- end
106
- end
107
- patterns
108
- end
109
- end
110
- private :dir_glob_equivalent_patterns
111
-
112
- # @return [FileModification,Nil] the modification for the given absolute glob,
113
- # or `nil` if no files matching the glob were modified
114
- # @note Will only return a single (arbitrary) matching modification, even if there are
115
- # multiple modifications that match the glob
116
- # @param absolute_glob [String] a glob pattern for absolute paths, suitable for an invocation of `Dir.glob`
117
- def find_modification_for_glob(absolute_glob:)
118
- absolute_globs = dir_glob_equivalent_patterns(absolute_glob)
119
- _path, modification = modified_absolute_paths.find do |absolute_path, _modification|
120
- absolute_globs.any? do |glob|
121
- File.fnmatch?(glob, absolute_path, File::FNM_CASEFOLD | File::FNM_PATHNAME)
122
- end
123
- end
124
- modification
125
- end
126
-
127
- # @return [FileModification,Nil] a modification and yaml diff for the keypath at the given absolute path,
128
- # or `nil` if the value at the given keypath is un-modified
129
- # @param absolute_path [Pathname]
130
- # @param keypath [Array]
131
- def find_modification_for_yaml_keypath(absolute_path:, keypath:)
132
- return unless (file_modification = find_modification_for_path(absolute_path: absolute_path))
133
-
134
- diff = file_modification.yaml_diff(keypath)
135
- return unless diff
136
-
137
- [file_modification, diff]
138
- end
139
-
140
- # @return [Changeset] the changes in the given git repository between the given revision and HEAD
141
- # @param repository [Pathname]
142
- # @param base_revision [String]
143
- def self.from_git(repository:, base_revision:)
144
- raise ArgumentError, "must be given a Pathname for repository, got #{repository.inspect}" unless repository.is_a?(Pathname)
145
- raise ArgumentError, "must be given a String for base_revision, got #{base_revision.inspect}" unless base_revision.is_a?(String)
146
-
147
- merge_base = git!('merge-base', base_revision, 'HEAD', chdir: repository).strip
148
- diff = git!('diff', '--raw', '-z', merge_base, chdir: repository)
149
- modifications = parse_raw_diff(diff, repository: repository, base_revision: merge_base).freeze
150
-
151
- new(repository: repository, modifications: modifications, description: "since #{base_revision}")
152
- end
153
-
154
- CHANGE_TYPES = {
155
- 'was added': 'A',
156
- 'was copied': 'C',
157
- 'was deleted': 'D',
158
- 'was modified': 'M',
159
- 'was renamed': 'R',
160
- 'changed type': 'T',
161
- 'is unmerged': 'U',
162
- 'changed in an unknown way': 'X'
163
- }.freeze
164
- private_constant :CHANGE_TYPES
165
-
166
- CHANGE_CHARACTERS = CHANGE_TYPES.invert.freeze
167
- private_constant :CHANGE_CHARACTERS
168
-
169
- # Parses the raw diff into FileModification objects
170
- # @return [Array<FileModification>]
171
- # @param diff [String] a diff generated by `git diff --raw -z`
172
- # @param repository [Pathname] the path to the repository
173
- # @param base_revision [String] the base revision the diff was constructed agains
174
- def self.parse_raw_diff(diff, repository:, base_revision:)
175
- # since we're null separating the chunks (to avoid dealing with path escaping) we have to reconstruct
176
- # the chunks into individual diff entries. entries always start with a colon so we can use that to signal if
177
- # we're on a new entry
178
- parsed_lines = diff.split("\0").each_with_object([]) do |chunk, lines|
179
- lines << [] if chunk.start_with?(':')
180
- lines.last << chunk
181
- end
182
-
183
- parsed_lines.map do |split_line|
184
- # change chunk (letter + optional similarity percentage) will always be the last part of first line chunk
185
- change_chunk = split_line[0].split(/\s/).last
186
-
187
- new_path = Pathname(split_line[2]).freeze if split_line[2]
188
- old_path = Pathname(split_line[1]).freeze
189
- prior_path = old_path if new_path
190
- # new path if one exists, else existing path. new path only exists for rename and copy
191
- changed_path = new_path || old_path
192
-
193
- change_character = change_chunk[0]
194
- # returns 0 when a similarity percentage isn't specified by git.
195
- _similarity = change_chunk[1..3].to_i
196
-
197
- FileModification.new(
198
- path: changed_path,
199
- type: CHANGE_CHARACTERS[change_character],
200
- prior_path: prior_path,
201
- contents_reader: -> { repository.join(changed_path).read },
202
- prior_contents_reader: lambda {
203
- git!('show', "#{base_revision}:#{prior_path || changed_path}", chdir: repository)
204
- }
205
- )
206
- end
207
- end
208
-
209
- # @return [String] the STDOUT of the git command
210
- # @raise [GitError] when running the git command fails
211
- # @param command [String] the base git command to run
212
- # @param args [String...] arguments to the git command
213
- # @param chdir [String,Pathname] the directory to run the git command in
214
- def self.git!(command, *args, chdir:)
215
- require 'open3'
216
- out, err, status = Open3.capture3('git', command, *args, chdir: chdir.to_s)
217
- raise GitError, "Running git #{command} failed (#{status.to_s.gsub(/pid \d+\s*/, '')}):\n\n#{err}" unless status.success?
218
-
219
- out
220
- end
221
- private_class_method :git!
222
- end
223
- end
@@ -1,119 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'claide'
4
-
5
- module Refinement
6
- # @visibility private
7
- class CLI < CLAide::Command
8
- self.abstract_command = true
9
- self.command = 'refine'
10
- self.version = VERSION
11
-
12
- self.summary = 'Generates a list of Xcode targets to build & test as a result of a diff'
13
-
14
- def self.options
15
- super + [
16
- ['--repository=REPOSITORY', 'Path to repository'],
17
- ['--workspace=WORKSPACE_PATH', 'Path to project or workspace'],
18
- ['--scheme=SCHEME_PATH', 'Path to scheme to be filtered'],
19
- ['--augmenting-paths-yaml-files=PATH1,PATH2...', 'Paths to augmenting yaml files, relative to the repository path'],
20
- ['--[no-]print-changes', 'Print the change reason for changed targets'],
21
- ['--[no-]print-scheme-changes', 'Print the change reason for targets in the given scheme'],
22
- ['--change-level=LEVEL', 'Change level at which a target must have changed in order to be considered changed. ' \
23
- 'One of `full-transitive`, `itself`, or an integer'],
24
- ['--filter-scheme-for-build-action=BUILD_ACTION', 'The xcodebuild action the scheme (if given) is filtered for. ' \
25
- 'One of `building` or `testing`.']
26
- ]
27
- end
28
-
29
- def initialize(argv)
30
- @repository = argv.option('repository', '.')
31
- @workspace = argv.option('workspace')
32
- @scheme = argv.option('scheme')
33
- @augmenting_paths_yaml_files = argv.option('augmenting-paths-yaml-files', '')
34
- @print_changes = argv.flag?('print-changes', false)
35
- @print_scheme_changes = argv.flag?('print-scheme-changes', false)
36
- @change_level = argv.option('change-level', 'full-transitive')
37
- @filter_scheme_for_build_action = argv.option('filter-scheme-for-build-action', 'testing').to_sym
38
-
39
- super
40
- end
41
-
42
- def run
43
- changeset = compute_changeset
44
-
45
- analyzer = Refinement::Analyzer.new(changeset: changeset,
46
- workspace_path: @workspace,
47
- augmenting_paths_yaml_files: @augmenting_paths_yaml_files)
48
- analyzer.annotate_targets!
49
-
50
- puts analyzer.format_changes if @print_changes
51
-
52
- return unless @scheme
53
-
54
- analyzer.filtered_scheme(scheme_path: @scheme, log_changes: @print_scheme_changes, filter_scheme_for_build_action: @filter_scheme_for_build_action)
55
- .save_as(@scheme.gsub(%r{\.(xcodeproj|xcworkspace)/.+}, '.\1'), File.basename(@scheme, '.xcscheme'), true)
56
- end
57
-
58
- def validate!
59
- super
60
-
61
- File.directory?(@repository) || help!("Unable to find a repository at #{@repository.inspect}")
62
-
63
- @workspace || help!('Must specify a project or workspace path')
64
- File.directory?(@workspace) || help!("Unable to find a project or workspace at #{@workspace.inspect}")
65
-
66
- @augmenting_paths_yaml_files = @augmenting_paths_yaml_files.split(',')
67
- @augmenting_paths_yaml_files.each do |yaml_path|
68
- yaml_path = File.join(@repository, yaml_path)
69
- File.file?(yaml_path) || help!("Unable to find a YAML file at #{yaml_path.inspect}")
70
-
71
- require 'yaml'
72
- begin
73
- YAML.safe_load(File.read(yaml_path))
74
- rescue StandardError => e
75
- help! "Failed to load YAML file at #{yaml_path.inspect} (#{e})"
76
- end
77
- end
78
-
79
- File.file?(@scheme) || help!("Unabled to find a scheme at #{@scheme.inspect}") if @scheme
80
-
81
- @change_level =
82
- case @change_level
83
- when 'full-transitive' then :full_transitive
84
- when 'itself' then :itself
85
- when /\A\d+\z/ then [:at_most_n_away, @change_level.to_i]
86
- else help! "Unknown change level #{@change_level.inspect}"
87
- end
88
- end
89
-
90
- # @visibility private
91
- class Git < CLI
92
- self.summary = 'Generates a list of Xcode targets to build & test as a result of a git diff'
93
-
94
- def self.options
95
- super + [
96
- ['--base-revision=SHA', 'Base revision to compute the git diff against']
97
- ]
98
- end
99
-
100
- def initialize(argv)
101
- @base_revision = argv.option('base-revision')
102
-
103
- super
104
- end
105
-
106
- def validate!
107
- super
108
-
109
- @base_revision || help!('Must specify a base revision')
110
- end
111
-
112
- private
113
-
114
- def compute_changeset
115
- Refinement::Changeset.from_git(repository: Pathname(@repository), base_revision: @base_revision)
116
- end
117
- end
118
- end
119
- end
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Refinement
4
- # Called after CocoaPods installation to write an augmenting file that
5
- # takes into account changes to Pod configuration,
6
- # as well as the globs used by podspecs to search for files
7
- class CocoaPodsPostInstallWriter
8
- attr_reader :aggregate_targets, :pod_targets, :config, :repo, :options
9
- private :aggregate_targets, :pod_targets, :config, :repo, :options
10
-
11
- # Initializes a post-install writer with CocoaPods target objects.
12
- # @return [CocoaPodsPostInstallWriter] a new instance of CocoaPodsPostInstallWriter
13
- # @param aggregate_targets [Array<Pod::AggregateTarget>]
14
- # @param pod_targets [Array<Pod::PodTarget>]
15
- # @param config [Pod::Config]
16
- # @param options [Hash]
17
- def initialize(aggregate_targets, pod_targets, config, options)
18
- @aggregate_targets = aggregate_targets
19
- @pod_targets = pod_targets
20
- @config = config
21
- @repo = config.installation_root
22
- @options = options || {}
23
- end
24
-
25
- # Writes the refinement augmenting file to the configured path
26
- # @return [Void]
27
- def write!
28
- write_file options.fetch('output_path', config.sandbox.root.join('pods_refinement.json')), paths_by_target_name
29
- end
30
-
31
- private
32
-
33
- def write_file(path, hash)
34
- require 'json'
35
- File.open(path, 'w') do |f|
36
- f << JSON.generate(hash) << "\n"
37
- end
38
- end
39
-
40
- def paths_by_target_name
41
- targets = {}
42
- aggregate_targets.each do |aggregate_target|
43
- targets[aggregate_target.label] = paths_for_aggregate_target(aggregate_target)
44
- end
45
- pod_targets.each do |pod_target|
46
- targets.merge! paths_for_pod_targets(pod_target)
47
- end
48
- targets
49
- end
50
-
51
- def paths_for_aggregate_target(aggregate_target)
52
- paths = []
53
- if (podfile_path = aggregate_target.podfile.defined_in_file)
54
- paths << { path: podfile_path.relative_path_from(repo), inclusion_reason: 'Podfile' }
55
- end
56
- if (user_project_path = aggregate_target.user_project_path)
57
- paths << { path: user_project_path.relative_path_from(repo), inclusion_reason: 'user project' }
58
- end
59
- paths
60
- end
61
-
62
- def library_specification?(spec)
63
- # Backwards compatibility
64
- if spec.respond_to?(:library_specification?, false)
65
- spec.library_specification?
66
- else
67
- !spec.test_specification?
68
- end
69
- end
70
-
71
- def specification_paths_from_pod_target(pod_target)
72
- pod_target
73
- .target_definitions
74
- .map(&:podfile)
75
- .uniq
76
- .flat_map do |podfile|
77
- podfile
78
- .dependencies
79
- .select { |d| d.root_name == pod_target.pod_name }
80
- .map { |d| (d.external_source || {})[:path] }
81
- .compact
82
- end.uniq
83
- end
84
-
85
- def paths_for_pod_targets(pod_target)
86
- file_accessors_by_target_name = pod_target.file_accessors.group_by do |fa|
87
- if library_specification?(fa.spec)
88
- pod_target.label
89
- elsif pod_target.respond_to?(:non_library_spec_label)
90
- pod_target.non_library_spec_label(fa.spec)
91
- else
92
- pod_target.test_target_label(fa.spec)
93
- end
94
- end
95
-
96
- pod_dir = pod_target.sandbox.pod_dir(pod_target.pod_name).relative_path_from(repo)
97
-
98
- spec_paths = specification_paths_from_pod_target(pod_target)
99
-
100
- file_accessors_by_target_name.each_with_object({}) do |(label, file_accessors), h|
101
- paths = [
102
- { path: 'Podfile.lock',
103
- inclusion_reason: 'CocoaPods lockfile',
104
- yaml_keypath: ['SPEC CHECKSUMS', pod_target.pod_name] }
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
110
- spec_paths.each { |path| paths << { path: path, inclusion_reason: 'podspec' } }
111
-
112
- Pod::Validator::FILE_PATTERNS.each do |pattern|
113
- file_accessors.each do |fa|
114
- globs = fa.spec_consumer.send(pattern)
115
- globs = globs.values.flatten if globs.is_a?(Hash)
116
- globs.each do |glob|
117
- paths << { glob: pod_dir.join(glob), inclusion_reason: pattern.to_s.tr('_', ' ').chomp('s') }
118
- end
119
- end
120
- end
121
-
122
- h[label] = paths.uniq
123
- end
124
- end
125
- end
126
- end