cocoapods-core 0.17.0.rc5 → 0.17.0.rc6

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.
@@ -0,0 +1,159 @@
1
+ module Pod
2
+ class Source
3
+
4
+ # Checks whether a podspec can be accepted by a source. The check takes
5
+ # into account the introduction of 0.0.1 version if there are already
6
+ # tagged ones or whether there is change in the source.
7
+ #
8
+ class Acceptor
9
+
10
+ # @return [Source] the source where the podspec should be added.
11
+ #
12
+ attr_reader :source
13
+
14
+ # @param [Pathname] repo @see Source#repo.
15
+ #
16
+ def initialize(repo)
17
+ @source = Source.new(repo)
18
+ end
19
+
20
+ public
21
+
22
+ # @!group Actions
23
+ #-----------------------------------------------------------------------#
24
+
25
+ # Checks whether the given specification can be accepted.
26
+ #
27
+ # @return [Array<String>] A list of errors. If the list is empty the
28
+ # specification should be accepted.
29
+ #
30
+ def analyze(spec, previous_spec = nil)
31
+ errors = []
32
+ check_spec_source_change(spec, errors)
33
+ check_if_untagged_version_is_acceptable(spec, errors)
34
+ check_commit_change_for_untagged_version(spec, previous_spec, errors)
35
+ check_dependencies(spec, errors)
36
+ errors
37
+ end
38
+
39
+ # Checks whether the specification at the given path can be accepted.
40
+ #
41
+ # @return [Array<String>] A list of errors. If the list is empty the
42
+ # specification should be accepted.
43
+ #
44
+ def analyze_path(spec_path)
45
+ spec = Specification.from_file(spec_path)
46
+ analyze(spec)
47
+ rescue
48
+ ["Unable to load the specification."]
49
+ end
50
+
51
+ private
52
+
53
+ # @!group Private helpers
54
+ #-----------------------------------------------------------------------#
55
+
56
+ # Checks whether the source of the proposed specification is different
57
+ # from the one of the reference specification.
58
+ #
59
+ # @return [void]
60
+ #
61
+ def check_spec_source_change(spec, errors)
62
+ return unless spec
63
+ return unless reference_spec(spec)
64
+ keys = Spec::DSL::SOURCE_KEYS.keys
65
+ source = spec.source.values_at(*keys).compact.first
66
+ old_source = reference_spec(spec).source.values_at(*keys).compact.first
67
+ unless source == old_source
68
+ errors << "The source of the spec doesn't match with the recorded ones." \
69
+ "Source: `#{source}`. Previous: `#{old_source}`.\n " \
70
+ "Please contact the specs repo maintainers if the library changed " \
71
+ "location."
72
+ end
73
+ end
74
+
75
+ # Checks there are already tagged specifications if the specification has
76
+ # a git source and doesn't specify a tag (i.e. rejects 0.0.1 specs if
77
+ # they are not admissible anymore).
78
+ #
79
+ # @return [void]
80
+ #
81
+ def check_if_untagged_version_is_acceptable(spec, errors)
82
+ return if !spec.source[:git] || spec.source[:tag]
83
+ return unless related_specifications(spec)
84
+ has_tagged_spec = related_specifications(spec).any? { |s| s.version != '0.0.1' }
85
+ if has_tagged_spec
86
+ errors << "There is already at least one versioned specification so " \
87
+ "untagged versions cannot be accepted."
88
+ end
89
+ end
90
+
91
+ # If the previous specification for the given file is passed it is
92
+ # checked for any attempt to update the commit of a 0.0.1 version.
93
+ #
94
+ # @return [void]
95
+ #
96
+ def check_commit_change_for_untagged_version(spec, previous_spec, errors)
97
+ return unless previous_spec
98
+ return unless spec.version == Version.new('0.0.1')
99
+ unless spec.source[:commit] == previous_spec.source[:commit]
100
+ errors << "Attempt to rewrite the commit of a 0.0.1 version."
101
+ end
102
+ end
103
+
104
+ # Checks that there is a specification available for the dependencies of
105
+ # the given specification.
106
+ #
107
+ # @return [void]
108
+ #
109
+ def check_dependencies(spec, errors)
110
+ spec.dependencies.each do |dep|
111
+ set = source.search(dep)
112
+ unless set && set.specification
113
+ errors << "Unable to find a specification for the `#{dep}` dependency."
114
+ end
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ # @!group Source helpers
121
+ #-----------------------------------------------------------------------#
122
+
123
+ # Returns the specifications related to the given spec.
124
+ #
125
+ # @param [Specification] spec
126
+ # The specification for which the siblings specs are needed.
127
+ #
128
+ # @return [Array<Specification>] The other specifications of the Pod.
129
+ #
130
+ # @return [Nil] If there are no other specifications stored.
131
+ #
132
+ def related_specifications(spec)
133
+ versions = source.versions(spec.name)
134
+ return unless versions
135
+ specs = versions.sort.map { |v| source.specification(spec.name, v) }
136
+ specs.delete(spec)
137
+ specs
138
+ end
139
+
140
+ # Returns the most representative specification for the Pod of the given
141
+ # spec.
142
+ #
143
+ # @param [Specification] spec
144
+ # The specification for which the representative spec is needed.
145
+ #
146
+ # @return [Specification] The specification with the highest version.
147
+ #
148
+ # @return [Nil] If there are no other specifications stored.
149
+ #
150
+ def reference_spec(spec)
151
+ specs = related_specifications(spec)
152
+ specs.last if specs
153
+ end
154
+
155
+ #-----------------------------------------------------------------------#
156
+
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,229 @@
1
+ module Pod
2
+ class Source
3
+
4
+ # The Aggregate manages a directory of sources repositories.
5
+ #
6
+ class Aggregate
7
+
8
+ # @return [Pathname] the directory were the repositories are stored.
9
+ #
10
+ attr_reader :repos_dir
11
+
12
+ # @param [Pathname] repos_dir @see repos_dir.
13
+ #
14
+ def initialize(repos_dir)
15
+ @repos_dir = repos_dir
16
+ end
17
+
18
+ # @return [Array<Source>] all the sources.
19
+ #
20
+ def all
21
+ @sources ||= dirs.map { |repo| Source.new(repo) }.sort_by(&:name)
22
+ end
23
+
24
+ # @return [Array<String>] the names of all the pods available.
25
+ #
26
+ def all_pods
27
+ all.map(&:pods).flatten.uniq
28
+ end
29
+
30
+ # @return [Array<Set>] the sets for all the pods available.
31
+ #
32
+ # @note Implementation detail: The sources don't cache their values
33
+ # because they might change in response to an update. Therefore
34
+ # this method to prevent slowness caches the values before
35
+ # processing them.
36
+ #
37
+ def all_sets
38
+ pods_by_source = {}
39
+ all.each do |source|
40
+ pods_by_source[source] = source.pods
41
+ end
42
+ sources = pods_by_source.keys
43
+ pods = pods_by_source.values.flatten.uniq
44
+
45
+ pods.map do |pod|
46
+ pod_sources = sources.select{ |s| pods_by_source[s].include?(pod) }.compact
47
+ Specification::Set.new(pod, pod_sources)
48
+ end
49
+ end
50
+
51
+ # @return [Array<Pathname>] the directories where the sources are stored.
52
+ #
53
+ # @note If the repos dir doesn't exits this will return an empty array.
54
+ #
55
+ # @raise If the repos dir doesn't exits.
56
+ #
57
+ def dirs
58
+ if repos_dir.exist?
59
+ repos_dir.children.select(&:directory?)
60
+ else
61
+ []
62
+ end
63
+ end
64
+
65
+ # Returns a set configured with the source which contains the highest
66
+ # version in the aggregate.
67
+ #
68
+ # @param [String] name
69
+ # The name of the Pod.
70
+ #
71
+ # @return [Set] The most representative set for the Pod with the given
72
+ # name.
73
+ #
74
+ def represenative_set(name)
75
+ representative_source = nil
76
+ highest_version = nil
77
+ all.each do |source|
78
+ source_versions = source.versions(name)
79
+ if source_versions
80
+ source_version = source_versions.first
81
+ if highest_version.nil? || (highest_version < source_version)
82
+ highest_version = source_version
83
+ representative_source = source
84
+ end
85
+ end
86
+ end
87
+ Specification::Set.new(name, representative_source)
88
+ end
89
+
90
+ public
91
+
92
+ # @!group Search
93
+ #-----------------------------------------------------------------------#
94
+
95
+ # @return [Set, nil] a set for a given dependency including all the
96
+ # {Source} that contain the Pod. If no sources containing the
97
+ # Pod where found it returns nil.
98
+ #
99
+ # @raise If no source including the set can be found.
100
+ #
101
+ # @see Source#search
102
+ #
103
+ def search(dependency)
104
+ sources = all.select { |s| !s.search(dependency).nil? }
105
+ Specification::Set.new(dependency.root_name, sources) unless sources.empty?
106
+ end
107
+
108
+ # @return [Array<Set>] the sets that contain the search term.
109
+ #
110
+ # @raise If no source including the set can be found.
111
+ #
112
+ # @todo Clients should raise not this method.
113
+ #
114
+ # @see Source#search_by_name
115
+ #
116
+ def search_by_name(query, full_text_search = false)
117
+ pods_by_source = {}
118
+ result = []
119
+ all.each { |s| pods_by_source[s] = s.search_by_name(query, full_text_search).map(&:name) }
120
+ root_spec_names = pods_by_source.values.flatten.uniq
121
+ root_spec_names.each do |pod|
122
+ sources = []
123
+ pods_by_source.each{ |source, pods| sources << source if pods.include?(pod) }
124
+ result << Specification::Set.new(pod, sources)
125
+ end
126
+ if result.empty?
127
+ extra = ", author, summary, or description" if full_text_search
128
+ raise(Informative, "Unable to find a pod with name" \
129
+ "#{extra} matching `#{query}'")
130
+ end
131
+ result
132
+ end
133
+
134
+ public
135
+
136
+ # @!group Search Index
137
+ #-----------------------------------------------------------------------#
138
+
139
+ # Generates from scratch the search data for all the sources of the
140
+ # aggregate. This operation can take a considerable amount of time
141
+ # (seconds) as it needs to evaluate the most representative podspec
142
+ # for each Pod.
143
+ #
144
+ # @return [Hash{String=>Hash}] The search data of every set grouped by
145
+ # name.
146
+ #
147
+ def generate_search_index
148
+ result = {}
149
+ all_sets.each do |set|
150
+ result[set.name] = search_data_from_set(set)
151
+ end
152
+ result
153
+ end
154
+
155
+ # Updates inline the given search data with the information stored in all
156
+ # the sources. The update skips the Pods for which the version of the
157
+ # search data is the same of the highest version known to the aggregate.
158
+ # This can lead to updates in podspecs being skipped until a new version
159
+ # is released.
160
+ #
161
+ # @note This procedure is considerably faster as it only needs to
162
+ # load the most representative spec of the new or updated Pods.
163
+ #
164
+ # @return [Hash{String=>Hash}] The search data of every set grouped by
165
+ # name.
166
+ #
167
+ def update_search_index(search_data)
168
+ enumerated_names = []
169
+ all_sets.each do |set|
170
+ enumerated_names << set.name
171
+ set_data = search_data[set.name]
172
+ has_data = set_data && set_data['version']
173
+ needs_update = !has_data || Version.new(set_data['version']) < set.required_version
174
+ if needs_update
175
+ search_data[set.name] = search_data_from_set(set)
176
+ end
177
+ end
178
+
179
+ stored_names = search_data.keys
180
+ delted_names = stored_names - enumerated_names
181
+ delted_names.each do |name|
182
+ search_data.delete(name)
183
+ end
184
+
185
+ search_data
186
+ end
187
+
188
+ private
189
+
190
+ # @!group Private helpers
191
+ #-----------------------------------------------------------------------#
192
+
193
+ # Returns the search related information from the most representative
194
+ # specification of the set following keys:
195
+ #
196
+ # - version
197
+ # - summary
198
+ # - description
199
+ # - authors
200
+ #
201
+ # @param [Set] set
202
+ # The set for which the information is needed.
203
+ #
204
+ # @note If the specification can't load an empty hash is returned and
205
+ # a warning is printed.
206
+ #
207
+ # @note For compatibility with non Ruby clients a strings are used
208
+ # instead of symbols for the keys.
209
+ #
210
+ # @return [Hash{String=>String}] A hash with the search information.
211
+ #
212
+ def search_data_from_set(set)
213
+ result = {}
214
+ spec = set.specification
215
+ result['version'] = spec.version.to_s
216
+ result['summary'] = spec.summary
217
+ result['description'] = spec.description
218
+ result['authors'] = spec.authors.keys.sort * ', '
219
+ result
220
+ rescue
221
+ CoreUI.warn "Skipping `#{set.name}` because the podspec contains errors."
222
+ result
223
+ end
224
+
225
+ #-----------------------------------------------------------------------#
226
+
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,208 @@
1
+ module Pod
2
+ class Source
3
+
4
+ # Checks a source for errors and warnings.
5
+ #
6
+ class HealthReporter
7
+
8
+ # @return [Source] the source to check.
9
+ #
10
+ attr_reader :source
11
+
12
+ # @param [Pathname] repo @see Source#repo.
13
+ #
14
+ def initialize(repo)
15
+ @source = Source.new(repo)
16
+ @errors = {}
17
+ @linter_results = {}
18
+ end
19
+
20
+ # @return [Bool] Whether the more strict validation of the master repo
21
+ # should be used. Specifically The master repo treats certain
22
+ # warnings as errors.
23
+ #
24
+ attr_accessor :master_repo_mode
25
+
26
+ public
27
+
28
+ # @!group Configuration
29
+ #-----------------------------------------------------------------------#
30
+
31
+ # Allows to specify an optional callback which is called before
32
+ # analysing every spec. Suitable for UI.
33
+ #
34
+ # @param [Proc] A callback which is called before checking any
35
+ # specification. It receives the name and the version of the
36
+ # spec.
37
+ #
38
+ # @return [void]
39
+ #
40
+ def pre_check(&block)
41
+ @pre_check_callback = block
42
+ end
43
+
44
+ public
45
+
46
+ # @!group Actions
47
+ #-----------------------------------------------------------------------#
48
+
49
+ # Analyzes all the specification files in the source.
50
+ #
51
+ # @return [HealthReport] A report which contains the information about the
52
+ # state of the source.
53
+ #
54
+ def analyze
55
+ @report = HealthReport.new(source)
56
+
57
+ source.pods.each do |name|
58
+ source.versions(name).each do |version|
59
+ @pre_check_callback.call(name, version) if @pre_check_callback
60
+ spec_path = source.specification_path(name, version)
61
+ spec = lint_spec(name, version, spec_path)
62
+ check_spec_path(name, version, spec) if spec
63
+ report.analyzed_paths << spec_path
64
+ end
65
+ end
66
+
67
+ check_stray_specs
68
+ report
69
+ end
70
+
71
+ # @return [HealtReport] The report produced by the analysis.
72
+ #
73
+ attr_reader :report
74
+
75
+ private
76
+
77
+ # @!group Private helpers
78
+ #-----------------------------------------------------------------------#
79
+
80
+ # Checks the validity of the specification with the linter.
81
+ #
82
+ # @param [String] name
83
+ # The name of the Pod.
84
+ #
85
+ # @param [Version] version
86
+ # The version of the specification.
87
+ #
88
+ # @param [Pathname] spec_path
89
+ # The path of the specification to check.
90
+ #
91
+ # @return [Specification] The specification loaded by the linter.
92
+ # @return [Nil] If the specifications raised during evaluation.
93
+ #
94
+ def lint_spec(name, version, spec_path)
95
+ linter = Specification::Linter.new(spec_path)
96
+ linter.master_repo_mode = master_repo_mode
97
+ linter.lint
98
+ linter.results.each do |result|
99
+ type = result.type == :error ? :error : :warning
100
+ report.add_message(type, result.message, name, version)
101
+ end
102
+ linter.spec
103
+ end
104
+
105
+ # Ensures that the name and the version of the specification correspond
106
+ # to the ones expected by the repo given its path.
107
+ #
108
+ # @param [String] name
109
+ # The name of the Pod.
110
+ #
111
+ # @param [Version] version
112
+ # The version of the specification.
113
+ #
114
+ # @param [Specification] spec
115
+ # The specification to check.
116
+ #
117
+ # @return [void]
118
+ #
119
+ def check_spec_path(name, version, spec)
120
+ unless spec.name == name && spec.version == version
121
+ report.add_message(:error, "Incorrect path", name)
122
+ end
123
+ end
124
+
125
+ # Checks for any stray specification in the repo.
126
+ #
127
+ # @param [Array<Pathname>] analyzed_paths
128
+ # The specification to check.
129
+ #
130
+ # @return [void]
131
+ #
132
+ def check_stray_specs
133
+ all_paths = Pathname.glob(source.repo + '**/*.podspec{,.yaml}')
134
+ stray_specs = all_paths - report.analyzed_paths
135
+ stray_specs.each do |path|
136
+ report.add_message(:error, "Stray spec", path)
137
+ end
138
+ end
139
+
140
+ #-----------------------------------------------------------------------#
141
+
142
+ # Encapsulates the information about the state of a repo.
143
+ #
144
+ class HealthReport
145
+
146
+ # @return [Source] the source analyzed.
147
+ #
148
+ attr_reader :source
149
+
150
+ # @param [Source] @see source.
151
+ #
152
+ def initialize(source)
153
+ @source = source
154
+ @analyzed_paths = []
155
+ @pods_by_error = {}
156
+ @pods_by_warning = {}
157
+ end
158
+
159
+ # @return [Array<Pathname>] The list of the analyzed paths.
160
+ #
161
+ attr_accessor :analyzed_paths
162
+
163
+ # @return [Hash{ String => Hash }] The pods (the version grouped by
164
+ # name) grouped by an error message.
165
+ #
166
+ attr_accessor :pods_by_error
167
+
168
+ # @return [Hash{ String => Hash }] The pods (the version grouped by
169
+ # name) grouped by a warning message.
170
+ #
171
+ attr_accessor :pods_by_warning
172
+
173
+ # Adds a message with the given type for the specification with the
174
+ # given name and version.
175
+ #
176
+ # @param [Symbol] type
177
+ # The type of message. Either `:error` or `:warning`.
178
+ #
179
+ # @param [String] message
180
+ # The contents of the message.
181
+ #
182
+ # @param [String] spec_name
183
+ # The name of the Pod.
184
+ #
185
+ # @param [String] spec_version
186
+ # The version of the specification.
187
+ #
188
+ # @return [void]
189
+ #
190
+ def add_message(type, message, spec_name, spec_version = nil)
191
+ if type == :error
192
+ pods_by_error[message] ||= {}
193
+ pods_by_error[message][spec_name] ||= []
194
+ pods_by_error[message][spec_name] << spec_version
195
+ else
196
+ pods_by_warning[message] ||= {}
197
+ pods_by_warning[message][spec_name] ||= []
198
+ pods_by_warning[message][spec_name] << spec_version
199
+ end
200
+ end
201
+ end
202
+
203
+ #-----------------------------------------------------------------------#
204
+
205
+ end
206
+ end
207
+ end
208
+