cocoapods-core 0.17.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +36 -0
  4. data/lib/cocoapods-core/core_ui.rb +19 -0
  5. data/lib/cocoapods-core/dependency.rb +295 -0
  6. data/lib/cocoapods-core/gem_version.rb +6 -0
  7. data/lib/cocoapods-core/lockfile.rb +440 -0
  8. data/lib/cocoapods-core/platform.rb +171 -0
  9. data/lib/cocoapods-core/podfile/dsl.rb +459 -0
  10. data/lib/cocoapods-core/podfile/target_definition.rb +503 -0
  11. data/lib/cocoapods-core/podfile.rb +345 -0
  12. data/lib/cocoapods-core/requirement.rb +15 -0
  13. data/lib/cocoapods-core/source/validator.rb +183 -0
  14. data/lib/cocoapods-core/source.rb +284 -0
  15. data/lib/cocoapods-core/specification/consumer.rb +356 -0
  16. data/lib/cocoapods-core/specification/dsl/attribute.rb +245 -0
  17. data/lib/cocoapods-core/specification/dsl/attribute_support.rb +76 -0
  18. data/lib/cocoapods-core/specification/dsl/deprecations.rb +47 -0
  19. data/lib/cocoapods-core/specification/dsl/platform_proxy.rb +67 -0
  20. data/lib/cocoapods-core/specification/dsl.rb +1110 -0
  21. data/lib/cocoapods-core/specification/linter.rb +436 -0
  22. data/lib/cocoapods-core/specification/root_attribute_accessors.rb +152 -0
  23. data/lib/cocoapods-core/specification/set/presenter.rb +229 -0
  24. data/lib/cocoapods-core/specification/set/statistics.rb +277 -0
  25. data/lib/cocoapods-core/specification/set.rb +171 -0
  26. data/lib/cocoapods-core/specification/yaml.rb +60 -0
  27. data/lib/cocoapods-core/specification.rb +592 -0
  28. data/lib/cocoapods-core/standard_error.rb +84 -0
  29. data/lib/cocoapods-core/vendor/dependency.rb +264 -0
  30. data/lib/cocoapods-core/vendor/requirement.rb +208 -0
  31. data/lib/cocoapods-core/vendor/version.rb +333 -0
  32. data/lib/cocoapods-core/vendor.rb +56 -0
  33. data/lib/cocoapods-core/version.rb +99 -0
  34. data/lib/cocoapods-core/yaml_converter.rb +202 -0
  35. data/lib/cocoapods-core.rb +23 -0
  36. metadata +154 -0
@@ -0,0 +1,436 @@
1
+ module Pod
2
+ class Specification
3
+
4
+ # The Linter check specifications for errors and warnings.
5
+ #
6
+ # It is designed not only to guarantee the formal functionality of a
7
+ # specification, but also to support the maintenance of sources.
8
+ #
9
+ class Linter
10
+
11
+ # @return [Specification] the specification to lint.
12
+ #
13
+ attr_reader :spec
14
+
15
+ # @return [Pathname] the path of the `podspec` file where {#spec} is
16
+ # defined.
17
+ #
18
+ attr_reader :file
19
+
20
+ # @param [Specification, Pathname, String] spec_or_path
21
+ # the Specification or the path of the `podspec` file to lint.
22
+ #
23
+ def initialize(spec_or_path)
24
+ if spec_or_path.is_a?(Specification)
25
+ @spec = spec_or_path
26
+ @file = @spec.defined_in_file
27
+ else
28
+ @file = Pathname.new(spec_or_path)
29
+ begin
30
+ @spec = Specification.from_file(@file)
31
+ rescue Exception => e
32
+ @spec = nil
33
+ @raise_message = e.message
34
+ end
35
+ end
36
+ end
37
+
38
+ # Lints the specification adding a {Result} for any failed check to the
39
+ # {#results} list.
40
+ #
41
+ # @return [Bool] whether the specification passed validation.
42
+ #
43
+ def lint
44
+ @results = []
45
+ if spec
46
+ perform_textual_analysis
47
+ check_required_root_attributes
48
+ run_root_validation_hooks
49
+ perform_all_specs_ananlysis
50
+ else
51
+ error "The specification defined in `#{file}` could not be loaded." \
52
+ "\n\n#{@raise_message}"
53
+ end
54
+ results.empty?
55
+ end
56
+
57
+ #-----------------------------------------------------------------------#
58
+
59
+ # !@group Lint results
60
+
61
+ public
62
+
63
+ # @return [Array<Result>] all the results generated by the Linter.
64
+ #
65
+ attr_reader :results
66
+
67
+ # @return [Array<Result>] all the errors generated by the Linter.
68
+ #
69
+ def errors
70
+ @errors ||= results.select { |r| r.type == :error }
71
+ end
72
+
73
+ # @return [Array<Result>] all the warnings generated by the Linter.
74
+ #
75
+ def warnings
76
+ @warnings ||= results.select { |r| r.type == :warning }
77
+ end
78
+
79
+ #-----------------------------------------------------------------------#
80
+
81
+ private
82
+
83
+ # !@group Lint steps
84
+
85
+ # It reads a podspec file and checks for strings corresponding
86
+ # to features that are or will be deprecated
87
+ #
88
+ # @return [void]
89
+ #
90
+ def perform_textual_analysis
91
+ return unless @file
92
+ text = @file.read
93
+ error "`config.ios?' and `config.osx?' are deprecated." if text =~ /config\..?os.?/
94
+ error "clean_paths are deprecated (use preserve_paths)." if text =~ /clean_paths/
95
+ warning "Comments must be deleted." if text.scan(/^\s*#/).length > 24
96
+ end
97
+
98
+ # Checks that every root only attribute which is required has a value.
99
+ #
100
+ # @return [void]
101
+ #
102
+ def check_required_root_attributes
103
+ attributes = DSL.attributes.values.select(&:root_only?)
104
+ attributes.each do |attr|
105
+ # if spec.respond_to?(attr.name)
106
+ value = spec.send(attr.name)
107
+ next unless attr.required?
108
+ unless value && (!value.respond_to?(:empty?) || !value.empty?)
109
+ if attr.name == :license
110
+ warning("Missing required attribute `#{attr.name}`.")
111
+ else
112
+ error("Missing required attribute `#{attr.name}`.")
113
+ end
114
+ end
115
+ end
116
+ # end
117
+ end
118
+
119
+ # Runs the validation hook for root only attributes.
120
+ #
121
+ # @return [void]
122
+ #
123
+ def run_root_validation_hooks
124
+ attributes = DSL.attributes.values.select(&:root_only?)
125
+ attributes.each do |attr|
126
+ validation_hook = "_validate_#{attr.name}"
127
+ next unless respond_to?(validation_hook, true)
128
+ value = spec.send(attr.name)
129
+ next unless value
130
+ send(validation_hook, value)
131
+ end
132
+ end
133
+
134
+ # Run validations for multi-platform attributes activating .
135
+ #
136
+ # @return [void]
137
+ #
138
+ def perform_all_specs_ananlysis
139
+ all_specs = [ spec, *spec.recursive_subspecs ]
140
+ all_specs.each do |current_spec|
141
+ current_spec.available_platforms.each do |platform|
142
+ @consumer = Specification::Consumer.new(current_spec, platform)
143
+ run_all_specs_valudation_hooks
144
+ validate_file_patterns
145
+ check_tmp_arc_not_nil
146
+ check_if_spec_is_empty
147
+ @consumer = nil
148
+ end
149
+ end
150
+ end
151
+
152
+ # @return [Specification::Consumer] the current consumer.
153
+ #
154
+ attr_accessor :consumer
155
+
156
+ # Runs the validation hook for the attributes that are not root only.
157
+ #
158
+ # @return [void]
159
+ #
160
+ def run_all_specs_valudation_hooks
161
+ attributes = DSL.attributes.values.reject(&:root_only?)
162
+ attributes.each do |attr|
163
+ validation_hook = "_validate_#{attr.name}"
164
+ next unless respond_to?(validation_hook, true)
165
+ value = consumer.send(attr.name)
166
+ next unless value
167
+ send(validation_hook, value)
168
+ end
169
+ end
170
+
171
+ # Runs the validation hook for each attribute.
172
+ #
173
+ # @note Hooks are called only if there is a value for the attribute as
174
+ # required attributes are already checked by the
175
+ # {#check_required_root_attributes} step.
176
+ #
177
+ # @return [void]
178
+ #
179
+ def run_validation_hooks(attributes)
180
+
181
+ end
182
+
183
+ #-----------------------------------------------------------------------#
184
+
185
+ private
186
+
187
+ # @!group Root spec validation helpers
188
+
189
+ # Performs validations related to the `name` attribute.
190
+ #
191
+ def _validate_name(n)
192
+ if spec.name && file
193
+ names_match = (file.basename.to_s == spec.root.name + '.podspec')
194
+ unless names_match
195
+ error "The name of the spec should match the name of the file."
196
+ end
197
+ end
198
+ end
199
+
200
+ # Performs validations related to the `summary` attribute.
201
+ #
202
+ def _validate_summary(s)
203
+ warning "The summary should be short use `description` (max 140 characters)." if s.length > 140
204
+ warning "The summary is not meaningful." if s =~ /A short description of/
205
+ warning "The summary should end with proper punctuation." if s !~ /(\.|\?|!)$/
206
+ end
207
+
208
+ # Performs validations related to the `description` attribute.
209
+ #
210
+ def _validate_description(d)
211
+ warning "The description is not meaningful." if d =~ /An optional longer description of/
212
+ warning "The description should end with proper punctuation." if d !~ /(\.|\?|!)$/
213
+ warning "The description is equal to the summary." if d == spec.summary
214
+ warning "The description is shorter than the summary." if d.length < spec.summary.length
215
+ end
216
+
217
+ # Performs validations related to the `license` attribute.
218
+ #
219
+ def _validate_license(l)
220
+ type = l[:type]
221
+ warning "Missing license type." if type.nil?
222
+ warning "Sample license type." if type && type =~ /\(example\)/
223
+ warning "Invalid license type." if type && type.gsub(' ', '').gsub("\n", '').empty?
224
+ end
225
+
226
+ # Performs validations related to the `source` attribute.
227
+ #
228
+ def _validate_source(s)
229
+ if git = s[:git]
230
+ tag, commit = s.values_at(:tag, :commit)
231
+ github = git.include?('github.com')
232
+ version = spec.version.to_s
233
+
234
+ error "Example source." if git =~ /http:\/\/EXAMPLE/
235
+ error 'The commit of a Git source cannot be `HEAD`.' if commit && commit.downcase =~ /head/
236
+ warning 'The version should be included in the Git tag.' if tag && !tag.include?(version)
237
+ warning "Github repositories should end in `.git`." if github && !git.end_with?('.git')
238
+ warning "Github repositories should use `https` link." if github && !git.start_with?('https://github.com') && !git.start_with?('git://gist.github.com')
239
+
240
+ if version == '0.0.1'
241
+ error 'Git sources should specify either a commit or a tag.' if commit.nil? && tag.nil?
242
+ else
243
+ warning 'Git sources should specify a tag.' if tag.nil?
244
+ end
245
+ end
246
+ end
247
+
248
+ #-----------------------------------------------------------------------#
249
+
250
+ # @!group All specs validation helpers
251
+
252
+ private
253
+
254
+ # Performs validations related to the `compiler_flags` attribute.
255
+ #
256
+ def _validate_compiler_flags(flags)
257
+ if flags.join(' ').split(' ').any? { |flag| flag.start_with?('-Wno') }
258
+ warning "Warnings must not be disabled (`-Wno' compiler flags)."
259
+ end
260
+ end
261
+
262
+ # Checks the attributes that represent file patterns.
263
+ #
264
+ # @todo Check the attributes hash directly.
265
+ #
266
+ def validate_file_patterns
267
+ attributes = DSL.attributes.values.select(&:file_patterns?)
268
+ attributes.each do |attrb|
269
+ patterns = consumer.send(attrb.name)
270
+ if patterns.is_a?(Hash)
271
+ patterns = patterns.values.flatten(1)
272
+ end
273
+ patterns.each do |pattern|
274
+ if pattern.is_a?(Rake::FileList)
275
+ error "Rake::FileList is deprecated, use `exclude_files` (#{attrb.name})."
276
+ else
277
+ if pattern.start_with?('/')
278
+ error "File patterns must be relative and cannot start with a slash (#{attrb.name})."
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ # @todo remove in 0.18 and switch the default to true.
286
+ #
287
+ def check_tmp_arc_not_nil
288
+ if consumer.requires_arc.nil?
289
+ warning "A value for `requires_arc` should be specified until the migration to a `true` default."
290
+ end
291
+ end
292
+
293
+ # Check empty subspec attributes
294
+ #
295
+ def check_if_spec_is_empty
296
+ methods = %w[ source_files resources preserve_paths subspecs ]
297
+ empty = methods.all? { |m| consumer.send(m).empty? }
298
+ if empty
299
+ error "The spec appears to be empty (no source files, resources, or preserve paths)."
300
+ end
301
+ end
302
+
303
+ #-----------------------------------------------------------------------#
304
+
305
+ # !@group Result Helpers
306
+
307
+ private
308
+
309
+ # Adds an error result with the given message.
310
+ #
311
+ # @param [String] message
312
+ # The message of the result.
313
+ #
314
+ # @return [void]
315
+ #
316
+ def error(message)
317
+ add_result(:error, message)
318
+ end
319
+
320
+ # Adds an warning result with the given message.
321
+ #
322
+ # @param [String] message
323
+ # The message of the result.
324
+ #
325
+ # @return [void]
326
+ #
327
+ def warning(message)
328
+ add_result(:warning, message)
329
+ end
330
+
331
+ # Adds a result of the given type with the given message. If there is a
332
+ # current platform it is added to the result. If a result with the same
333
+ # type and the same message is already available the current platform is
334
+ # added to the existing result.
335
+ #
336
+ # @param [Symbol] type
337
+ # The type of the result (`:error`, `:warning`).
338
+ #
339
+ # @param [String] message
340
+ # The message of the result.
341
+ #
342
+ # @return [void]
343
+ #
344
+ def add_result(type, message)
345
+ result = results.find { |r| r.type == type && r.message == message }
346
+ unless result
347
+ result = Result.new(type, message)
348
+ results << result
349
+ end
350
+ result.platforms << consumer.platform_name if consumer
351
+ end
352
+
353
+ #-----------------------------------------------------------------------#
354
+
355
+ class Result
356
+
357
+ # @return [Symbol] the type of result.
358
+ #
359
+ attr_reader :type
360
+
361
+ # @return [String] the message associated with result.
362
+ #
363
+ attr_reader :message
364
+
365
+ # @param [Symbol] type @see type
366
+ # @param [String] message @see message
367
+ #
368
+ def initialize(type, message)
369
+ @type = type
370
+ @message = message
371
+ @platforms = []
372
+ end
373
+
374
+ # @return [Array<Platform>] the platforms where this result was
375
+ # generated.
376
+ #
377
+ attr_reader :platforms
378
+
379
+ # @return [String] a string representation suitable for UI output.
380
+ #
381
+ def to_s
382
+ r = "[#{type.to_s.upcase}] #{message}"
383
+ if platforms != Specification::PLATFORMS
384
+ platforms_names = platforms.uniq.map { |p| Platform.string_name(p) }
385
+ r << " [#{platforms_names * ' - '}]" unless platforms.empty?
386
+ end
387
+ r
388
+ end
389
+ end
390
+
391
+ #-----------------------------------------------------------------------#
392
+
393
+ end
394
+ end
395
+ end
396
+
397
+ # # TODO
398
+ # # Converts the resources file patterns to a hash defaulting to the
399
+ # # resource key if they are defined as an Array or a String.
400
+ # #
401
+ # # @param [String, Array, Hash] value.
402
+ # # The value of the attribute as specified by the user.
403
+ # #
404
+ # # @return [Hash] the resources.
405
+ # #
406
+ # def _prepare_deployment_target(deployment_target)
407
+ # unless @define_for_platforms.count == 1
408
+ # raise StandardError, "The deployment target must be defined per platform like `s.ios.deployment_target = '5.0'`."
409
+ # end
410
+ # Version.new(deployment_target)
411
+ # end
412
+
413
+ # # TODO
414
+ # # Converts the resources file patterns to a hash defaulting to the
415
+ # # resource key if they are defined as an Array or a String.
416
+ # #
417
+ # # @param [String, Array, Hash] value.
418
+ # # The value of the attribute as specified by the user.
419
+ # #
420
+ # # @return [Hash] the resources.
421
+ # #
422
+ # def _prepare_platform(name_and_deployment_target)
423
+ # return nil if name_and_deployment_target.nil?
424
+ # if name_and_deployment_target.is_a?(Array)
425
+ # name = name_and_deployment_target.first
426
+ # deployment_target = name_and_deployment_target.last
427
+ # else
428
+ # name = name_and_deployment_target
429
+ # deployment_target = nil
430
+ # end
431
+ # unless PLATFORMS.include?(name)
432
+ # raise StandardError, "Unsupported platform `#{name}`. The available " \
433
+ # "names are `#{PLATFORMS.inspect}`"
434
+ # end
435
+ # Platform.new(name, deployment_target)
436
+ # end
@@ -0,0 +1,152 @@
1
+ module Pod
2
+ class Specification
3
+ module DSL
4
+
5
+ # Provides the accessors methods for the root attributes. Root attributes
6
+ # do not support multiplatform values and inheritance.
7
+ #
8
+ module RootAttributesAccessors
9
+
10
+ # @return [String] The name of the specification *not* including the
11
+ # names of the parents, in case of ‘sub-specifications’.
12
+ #
13
+ def base_name
14
+ attributes_hash["name"]
15
+ end
16
+
17
+ # @return [String] The name of the specification including the names of
18
+ # the parents, in case of ‘sub-specifications’.
19
+ #
20
+ def name
21
+ parent ? "#{parent.name}/#{base_name}" : base_name
22
+ end
23
+
24
+ # @return [Version] The version of the Pod.
25
+ #
26
+ # @todo The version is memoized because the Resolvers sets the head
27
+ # state on it. This information should be stored in the
28
+ # specification instance and the lockfile should have a more
29
+ # robust handling of head versions (like a dedicated section).
30
+ #
31
+ def version
32
+ @version ||= root? ? Version.new(attributes_hash["version"]) : root.version
33
+ end
34
+
35
+ # @return [Hash] a hash containing the authors as the keys and their
36
+ # email address as the values.
37
+ #
38
+ # @note The value is coherced to a hash with a nil email if needed.
39
+ #
40
+ # @example Possible values
41
+ #
42
+ # { 'Author' => 'email@host.com' }
43
+ # [ 'Author', { 'Author_2' => 'email@host.com' } ]
44
+ # [ 'Author', 'Author_2' ]
45
+ # 'Author'
46
+ #
47
+ def authors
48
+ authors = attributes_hash["authors"]
49
+ if authors.is_a?(Hash)
50
+ authors
51
+ elsif authors.is_a?(Array)
52
+ result = {}
53
+ authors.each do |name_or_hash|
54
+ if name_or_hash.is_a?(String)
55
+ result[name_or_hash] = nil
56
+ else
57
+ result.merge!(name_or_hash)
58
+ end
59
+ end
60
+ result
61
+ elsif authors.is_a?(String)
62
+ { authors => nil }
63
+ end
64
+ end
65
+
66
+ # @return [Hash] A hash containing the license information of the Pod.
67
+ #
68
+ # @note The indentation is stripped from the license text.
69
+ #
70
+ def license
71
+ license = attributes_hash["license"]
72
+ if license.is_a?(String)
73
+ { :type => license }
74
+ elsif license.is_a?(Hash)
75
+ license = convert_keys_to_symbol(license)
76
+ license[:text] = license[:text].strip_heredoc if license[:text]
77
+ license
78
+ else
79
+ {}
80
+ end
81
+ end
82
+
83
+ # @return [String] The URL of the homepage of the Pod.
84
+ #
85
+ def homepage
86
+ attributes_hash["homepage"]
87
+ end
88
+
89
+ # @return [Hash{Symbol=>String}] The location from where the library
90
+ # should be retrieved.
91
+ #
92
+ def source
93
+ convert_keys_to_symbol(attributes_hash["source"])
94
+ end
95
+
96
+ # @return [String] A short description of the Pod.
97
+ #
98
+ def summary
99
+ attributes_hash["summary"]
100
+ end
101
+
102
+ # @return [String] A longer description of the Pod.
103
+ #
104
+ # @note The indentation is stripped from the description.
105
+ #
106
+ def description
107
+ description = attributes_hash["description"]
108
+ description.strip_heredoc if description
109
+ end
110
+
111
+ # @return [Array<String>] The list of the URL for the screenshots of the
112
+ # Pod.
113
+ #
114
+ # @note The value is coherced to an array.
115
+ #
116
+ def screenshots
117
+ value = attributes_hash["screenshots"]
118
+ [*value]
119
+ end
120
+
121
+ # @return [Hash{Symbol=>Array<String>}] The options to pass to the
122
+ # appledoc tool.
123
+ #
124
+ def documentation
125
+ convert_keys_to_symbol(attributes_hash["documentation"])
126
+ end
127
+
128
+ #-----------------------------------------------------------------------#
129
+
130
+ private
131
+
132
+ # Converts the keys of the given hash to a string.
133
+ #
134
+ # @param [Object] value
135
+ # the value that needs to be stripped from the Symbols.
136
+ #
137
+ # @return [Hash] the hash with the strings instead of the keys.
138
+ #
139
+ def convert_keys_to_symbol(value)
140
+ return unless value
141
+ result = {}
142
+ value.each do |key, subvalue|
143
+ subvalue = convert_keys_to_symbol(subvalue) if subvalue.is_a?(Hash)
144
+ result[key.to_sym] = subvalue
145
+ end
146
+ result
147
+ end
148
+
149
+ end
150
+ end
151
+ end
152
+ end