cocoapods-core 0.17.0.rc5 → 0.17.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+