cocoapods 0.10.0 → 0.11.0

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.
@@ -11,12 +11,11 @@ module Pod
11
11
  Creates a PodSpec, in the current working dir, called `NAME.podspec'.
12
12
  If a GitHub url is passed the spec is prepopulated.
13
13
 
14
- $ pod spec lint [ NAME.podspec | DIRECTORY | http://PATH/NAME.podspec ]
14
+ $ pod spec lint [ NAME.podspec | DIRECTORY | http://PATH/NAME.podspec, ... ]
15
15
 
16
- Validates `NAME.podspec'. If a directory is provided it performs a quick
17
- validation on all the podspec files found, including subfolders. In case
18
- the argument is omitted, it defaults to the current working dir.
19
- }
16
+ Validates `NAME.podspec'. If a directory is provided it validates
17
+ the podspec files found, including subfolders. In case
18
+ the argument is omitted, it defaults to the current working dir.}
20
19
  end
21
20
 
22
21
  def self.options
@@ -31,16 +30,15 @@ module Pod
31
30
  @name_or_url = argv.shift_argument
32
31
  @url = argv.shift_argument
33
32
  super if @name_or_url.nil?
33
+ super unless argv.empty?
34
34
  elsif @action == 'lint'
35
- @quick = argv.option('--quick')
36
- @only_errors = argv.option('--only-errors')
37
- @no_clean = argv.option('--no-clean')
38
- @repo_or_podspec = argv.shift_argument unless argv.empty?
39
- super unless argv.size <= 1
35
+ @quick = argv.option('--quick')
36
+ @only_errors = argv.option('--only-errors')
37
+ @no_clean = argv.option('--no-clean')
38
+ @podspecs_paths = argv
40
39
  else
41
40
  super
42
41
  end
43
- super unless argv.empty?
44
42
  end
45
43
 
46
44
  def run
@@ -72,84 +70,77 @@ module Pod
72
70
 
73
71
  def lint
74
72
  puts
75
- invalid_count = lint_podspecs
76
- count = specs_to_lint.count
77
- if invalid_count == 0
78
- lint_passed_message = count == 1 ? "#{podspecs_to_lint.first.basename} passed validation." : "All the specs passed validation."
79
- puts lint_passed_message.green << "\n\n" unless config.silent?
80
- else
81
- raise Informative, count == 1 ? "The spec did not pass validation." : "#{invalid_count} out of #{count} specs failed validation."
82
- end
83
- podspecs_tmp_dir.rmtree if podspecs_tmp_dir.exist?
84
- end
85
-
86
- private
87
-
88
- def lint_podspecs
89
73
  invalid_count = 0
90
- specs_to_lint.each do |spec|
74
+ podspecs_to_lint.each do |podspec|
75
+ linter = Linter.new(podspec)
76
+ linter.quick = @quick
77
+ linter.no_clean = @no_clean
78
+
91
79
  # Show immediatly which pod is being processed.
92
- print " -> #{spec}\r" unless config.silent? || @multiple_files
80
+ print " -> #{linter.spec_name}\r" unless config.silent?
93
81
  $stdout.flush
94
-
95
- linter = Linter.new(spec)
96
- linter.lenient = @only_errors
97
- linter.quick = @quick || @multiple_files
98
- linter.no_clean = @no_clean
99
- invalid_count += 1 unless linter.lint
82
+ linter.lint
83
+
84
+ case linter.result_type
85
+ when :error
86
+ invalid_count += 1
87
+ color = :red
88
+ when :warning
89
+ invalid_count += 1 unless @only_errors
90
+ color = :yellow
91
+ else
92
+ color = :green
93
+ end
100
94
 
101
95
  # This overwrites the previously printed text
102
- puts " -> ".send(lint_result_color(linter)) << spec.to_s unless config.silent? || should_skip?(linter)
103
- print_messages(spec, 'ERROR', linter.errors)
104
- print_messages(spec, 'WARN', linter.warnings)
105
- print_messages(spec, 'NOTE', linter.notes)
96
+ puts " -> ".send(color) << linter.spec_name unless config.silent?
97
+ print_messages('ERROR', linter.errors)
98
+ print_messages('WARN', linter.warnings)
99
+ print_messages('NOTE', linter.notes)
106
100
 
107
- puts unless config.silent? || should_skip?(linter)
101
+ puts unless config.silent?
108
102
  end
109
- puts "Analyzed #{specs_to_lint.count} specs in #{podspecs_to_lint.count} podspecs files.\n\n" if @multiple_files && !config.silent?
110
- invalid_count
111
- end
112
103
 
113
- def lint_result_color(linter)
114
- if linter.errors.empty? && linter.warnings.empty?
115
- :green
116
- elsif linter.errors.empty?
117
- :yellow
104
+ puts "Analyzed #{podspecs_to_lint.count} podspecs files.\n\n" unless config.silent?
105
+ count = podspecs_to_lint.count
106
+ if invalid_count == 0
107
+ lint_passed_message = count == 1 ? "#{podspecs_to_lint.first.basename} passed validation." : "All the specs passed validation."
108
+ puts lint_passed_message.green << "\n\n" unless config.silent?
118
109
  else
119
- :red
110
+ raise Informative, count == 1 ? "The spec did not pass validation." : "#{invalid_count} out of #{count} specs failed validation."
120
111
  end
112
+ podspecs_tmp_dir.rmtree if podspecs_tmp_dir.exist?
121
113
  end
122
114
 
123
- def should_skip?(linter)
124
- @multiple_files && linter.errors.empty? && linter.warnings.empty? && linter.notes.empty?
125
- end
115
+ private
126
116
 
127
- def print_messages(spec, type, messages)
117
+ def print_messages(type, messages)
128
118
  return if config.silent?
129
119
  messages.each {|msg| puts " - #{type.ljust(5)} | #{msg}"}
130
120
  end
131
121
 
132
122
  def podspecs_to_lint
133
123
  @podspecs_to_lint ||= begin
134
- if @repo_or_podspec =~ /https?:\/\//
135
- require 'open-uri'
136
- output_path = podspecs_tmp_dir + File.basename(@repo_or_podspec)
137
- output_path.dirname.mkpath
138
- open(@repo_or_podspec) do |io|
139
- output_path.open('w') { |f| f << io.read }
124
+ files = []
125
+ @podspecs_paths << '.' if @podspecs_paths.empty?
126
+ @podspecs_paths.each do |path|
127
+ if path =~ /https?:\/\//
128
+ require 'open-uri'
129
+ output_path = podspecs_tmp_dir + File.basename(path)
130
+ output_path.dirname.mkpath
131
+ open(path) do |io|
132
+ output_path.open('w') { |f| f << io.read }
133
+ end
134
+ files << output_path
135
+ else if (pathname = Pathname.new(path)).directory?
136
+ files += pathname.glob('**/*.podspec')
137
+ raise Informative, "No specs found in the current directory." if files.empty?
138
+ else
139
+ files << (pathname = Pathname.new(path))
140
+ raise Informative, "Unable to find a spec named `#{path}'." unless pathname.exist? && path.include?('.podspec')
140
141
  end
141
- return [output_path]
142
- end
143
-
144
- path = Pathname.new(@repo_or_podspec || '.')
145
- if path.directory?
146
- files = path.glob('**/*.podspec')
147
- raise Informative, "No specs found in the current directory." if files.empty?
148
- @multiple_files = true
149
- else
150
- files = [path]
151
- raise Informative, "Unable to find a spec named `#{@repo_or_podspec}'." unless files[0].exist? && @repo_or_podspec.include?('.podspec')
152
142
  end
143
+ end
153
144
  files
154
145
  end
155
146
  end
@@ -158,273 +149,6 @@ module Pod
158
149
  Pathname.new('/tmp/CocoaPods/Lint_podspec')
159
150
  end
160
151
 
161
- def specs_to_lint
162
- @specs_to_lint ||= begin
163
- podspecs_to_lint.map do |podspec|
164
- root_spec = Specification.from_file(podspec)
165
- # TODO find a way to lint subspecs
166
- # root_spec.preferred_dependency ? root_spec.subspec_dependencies : root_spec
167
- end.flatten
168
- end
169
- end
170
-
171
- # Linter class
172
- #
173
- class Linter
174
- include Config::Mixin
175
-
176
- # TODO: Add check to ensure that attributes inherited by subspecs are not duplicated ?
177
-
178
- attr_accessor :quick, :lenient, :no_clean
179
- attr_reader :spec, :file
180
- attr_reader :errors, :warnings, :notes
181
-
182
- def initialize(spec)
183
- @spec = spec
184
- @file = spec.defined_in_file.realpath
185
- end
186
-
187
- # Takes an array of podspec files and lints them all
188
- #
189
- # It returns true if the spec passed validation
190
- #
191
- def lint
192
- @platform_errors, @platform_warnings, @platform_notes = {}, {}, {}
193
-
194
- platforms = @spec.available_platforms
195
- platforms.each do |platform|
196
- @platform_errors[platform], @platform_warnings[platform], @platform_notes[platform] = [], [], []
197
-
198
- @spec.activate_platform(platform)
199
- @platform = platform
200
- puts "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed if config.verbose? && !@quick
201
-
202
- # Skip validation if there are errors in the podspec as it would result in a crash
203
- if !podspec_errors.empty?
204
- @platform_errors[platform] += podspec_errors
205
- @platform_notes[platform] << "#{platform.name} [!] Fatal errors found skipping the rest of the validation"
206
- else
207
- @platform_warnings[platform] += podspec_warnings + deprecation_warnings
208
- peform_extensive_analysis unless quick
209
- end
210
- end
211
-
212
- # Get common messages
213
- @errors = @platform_errors.values.reduce(:&) || []
214
- @warnings = @platform_warnings.values.reduce(:&) || []
215
- @notes = @platform_notes.values.reduce(:&) || []
216
-
217
- platforms.each do |platform|
218
- # Mark platform specific messages
219
- @errors += (@platform_errors[platform] - @errors).map {|m| "[#{platform}] #{m}"}
220
- @warnings += (@platform_warnings[platform] - @warnings).map {|m| "[#{platform}] #{m}"}
221
- @notes += (@platform_notes[platform] - @notes).map {|m| "[#{platform}] #{m}"}
222
- end
223
-
224
- valid?
225
- end
226
-
227
- def valid?
228
- lenient ? errors.empty? : ( errors.empty? && warnings.empty? )
229
- end
230
-
231
- # Performs platform specific analysis.
232
- # It requires to download the source at each iteration
233
- #
234
- def peform_extensive_analysis
235
- set_up_lint_environment
236
- install_pod
237
- puts "Building with xcodebuild.\n".yellow if config.verbose?
238
- # treat xcodebuild warnings as notes because the spec maintainer might not be the author of the library
239
- xcodebuild_output.each { |msg| ( msg.include?('error: ') ? @platform_errors[@platform] : @platform_notes[@platform] ) << msg }
240
- @platform_errors[@platform] += file_patterns_errors
241
- @platform_warnings[@platform] += file_patterns_warnings
242
- tear_down_lint_environment
243
- end
244
-
245
- def install_pod
246
- podfile = podfile_from_spec
247
- config.verbose
248
- installer = Installer.new(podfile)
249
- installer.install!
250
- @pod = installer.pods.find { |pod| pod.top_specification == @spec }
251
- config.silent
252
- end
253
-
254
- def podfile_from_spec
255
- name = spec.name
256
- podspec = file.realpath.to_s
257
- platform = @platform
258
- podfile = Pod::Podfile.new do
259
- platform(platform.to_sym, platform.deployment_target)
260
- pod name, :podspec => podspec
261
- end
262
- podfile
263
- end
264
-
265
- def set_up_lint_environment
266
- tmp_dir.rmtree if tmp_dir.exist?
267
- tmp_dir.mkpath
268
- @original_config = Config.instance.clone
269
- config.project_root = tmp_dir
270
- config.project_pods_root = tmp_dir + 'Pods'
271
- config.silent = !config.verbose
272
- config.integrate_targets = false
273
- config.generate_docs = false
274
- end
275
-
276
- def tear_down_lint_environment
277
- tmp_dir.rmtree unless no_clean
278
- Config.instance = @original_config
279
- end
280
-
281
- def tmp_dir
282
- Pathname.new('/tmp/CocoaPods/Lint')
283
- end
284
-
285
- def pod_dir
286
- tmp_dir + 'Pods' + spec.name
287
- end
288
-
289
- # @return [Array<String>] List of the fatal defects detected in a podspec
290
- def podspec_errors
291
- messages = []
292
- messages << "The name of the spec should match the name of the file" unless names_match?
293
- messages << "Unrecognized platfrom" unless platform_valid?
294
- messages << "Missing name" unless spec.name
295
- messages << "Missing version" unless spec.version
296
- messages << "Missing summary" unless spec.summary
297
- messages << "Missing homepage" unless spec.homepage
298
- messages << "Missing author(s)" unless spec.authors
299
- messages << "Missing source" unless spec.source
300
-
301
- # attributes with multiplatform values
302
- return messages unless platform_valid?
303
- messages << "The spec appears to be empty (no source files, resources, or preserve paths)" if spec.source_files.empty? && spec.subspecs.empty? && spec.resources.empty? && spec.preserve_paths.empty?
304
- messages += paths_starting_with_a_slash_errors
305
- messages
306
- end
307
-
308
- def names_match?
309
- return true unless spec.name
310
- root_name = spec.name.match(/[^\/]*/)[0]
311
- file.basename.to_s == root_name + '.podspec'
312
- end
313
-
314
- def platform_valid?
315
- !spec.platform || [:ios, :osx].include?(spec.platform.name)
316
- end
317
-
318
- def paths_starting_with_a_slash_errors
319
- messages = []
320
- %w[source_files resources clean_paths].each do |accessor|
321
- patterns = spec.send(accessor.to_sym)
322
- # Some values are multiplaform
323
- patterns = patterns.is_a?(Hash) ? patterns.values.flatten(1) : patterns
324
- patterns.each do |pattern|
325
- # Skip FileList that would otherwise be resolved from the working directory resulting
326
- # in a potentially very expensi operation
327
- next if pattern.is_a?(FileList)
328
- invalid = pattern.is_a?(Array) ? pattern.any? { |path| path.start_with?('/') } : pattern.start_with?('/')
329
- if invalid
330
- messages << "Paths cannot start with a slash (#{accessor})"
331
- break
332
- end
333
- end
334
- end
335
- messages
336
- end
337
-
338
- # @return [Array<String>] List of the **non** fatal defects detected in a podspec
339
- def podspec_warnings
340
- license = @spec.license || {}
341
- source = @spec.source || {}
342
- text = @file.read
343
- messages = []
344
- messages << "Missing license type" unless license[:type]
345
- messages << "Sample license type" if license[:type] && license[:type] =~ /\(example\)/
346
- messages << "Invalid license type" if license[:type] && license[:type] =~ /\n/
347
- messages << "The summary is not meaningful" if spec.summary =~ /A short description of/
348
- messages << "The description is not meaningful" if spec.description && spec.description =~ /An optional longer description of/
349
- messages << "The summary should end with a dot" if @spec.summary !~ /.*\./
350
- messages << "The description should end with a dot" if @spec.description !~ /.*\./ && @spec.description != @spec.summary
351
- messages << "Git sources should specify either a tag or a commit" if source[:git] && !source[:commit] && !source[:tag]
352
- messages << "Github repositories should end in `.git'" if github_source? && source[:git] !~ /.*\.git/
353
- messages << "Github repositories should use `https' link" if github_source? && source[:git] !~ /https:\/\/github.com/
354
- messages << "Comments must be deleted" if text.scan(/^\s*#/).length > 24
355
- messages
356
- end
357
-
358
- def github_source?
359
- @spec.source && @spec.source[:git] =~ /github.com/
360
- end
361
-
362
- # It reads a podspec file and checks for strings corresponding
363
- # to features that are or will be deprecated
364
- #
365
- # @return [Array<String>]
366
- #
367
- def deprecation_warnings
368
- text = @file.read
369
- deprecations = []
370
- deprecations << "`config.ios?' and `config.osx?' are deprecated" if text. =~ /config\..?os.?/
371
- deprecations << "clean_paths are deprecated and ignored (use preserve_paths)" if text. =~ /clean_paths/
372
- deprecations
373
- end
374
-
375
- # It creates a podfile in memory and builds a library containing
376
- # the pod for all available platfroms with xcodebuild.
377
- #
378
- # @return [Array<String>]
379
- #
380
- def xcodebuild_output
381
- return [] if `which xcodebuild`.strip.empty?
382
- messages = []
383
- output = Dir.chdir(config.project_pods_root) { `xcodebuild clean build 2>&1` }
384
- clean_output = process_xcode_build_output(output)
385
- messages += clean_output
386
- puts(output) if config.verbose?
387
- messages
388
- end
389
-
390
- def process_xcode_build_output(output)
391
- output_by_line = output.split("\n")
392
- selected_lines = output_by_line.select do |l|
393
- l.include?('error: ') && (l !~ /errors? generated\./) && (l !~ /error: \(null\)/)\
394
- || l.include?('warning: ') && (l !~ /warnings? generated\./)\
395
- || l.include?('note: ') && (l !~ /expanded from macro/)
396
- end
397
- selected_lines.map do |l|
398
- new = l.gsub(/\/tmp\/CocoaPods\/Lint\/Pods\//,'') # Remove the unnecessary tmp path
399
- new.gsub!(/^ */,' ') # Remove indentation
400
- "XCODEBUILD > " << new # Mark
401
- end
402
- end
403
-
404
- # It checks that every file pattern specified in a spec yields
405
- # at least one file. It requires the pods to be alredy present
406
- # in the current working directory under Pods/spec.name.
407
- #
408
- # @return [Array<String>]
409
- #
410
- def file_patterns_errors
411
- messages = []
412
- messages << "The sources did not match any file" if !@spec.source_files.empty? && @pod.source_files.empty?
413
- messages << "The resources did not match any file" if !@spec.resources.empty? && @pod.resource_files.empty?
414
- messages << "The preserve_paths did not match any file" if !@spec.preserve_paths.empty? && @pod.preserve_files.empty?
415
- messages << "The exclude_header_search_paths did not match any file" if !@spec.exclude_header_search_paths.empty? && @pod.headers_excluded_from_search_paths.empty?
416
- messages
417
- end
418
-
419
- def file_patterns_warnings
420
- messages = []
421
- unless @pod.license_file || @spec.license && ( @spec.license[:type] == 'Public Domain' || @spec.license[:text] )
422
- messages << "Unable to find a license file"
423
- end
424
- messages
425
- end
426
- end
427
-
428
152
  # Templates and github information retrival for spec create
429
153
 
430
154
  def default_data_for_template(name)
@@ -559,6 +283,17 @@ Pod::Spec.new do |s|
559
283
  #
560
284
  s.source_files = 'Classes', 'Classes/**/*.{h,m}'
561
285
 
286
+ # A list of file patterns which select the header files that should be
287
+ # made available to the application. If the pattern is a directory then the
288
+ # path will automatically have '*.h' appended.
289
+ #
290
+ # Also allows the use of the FileList class like `source_files does.
291
+ #
292
+ # If you do not explicitely set the list of public header files,
293
+ # all headers of source_files will be made public.
294
+ #
295
+ # s.public_header_files = 'Classes/**/*.h'
296
+
562
297
  # A list of resources included with the Pod. These are copied into the
563
298
  # target bundle with a build phase script.
564
299
  #