cocoapods 0.10.0 → 0.11.0

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