cocoapods 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +18 -1
- data/lib/cocoapods.rb +1 -1
- data/lib/cocoapods/command.rb +5 -1
- data/lib/cocoapods/command/error_report.rb +6 -6
- data/lib/cocoapods/command/linter.rb +292 -0
- data/lib/cocoapods/command/push.rb +4 -3
- data/lib/cocoapods/command/repo.rb +69 -2
- data/lib/cocoapods/command/spec.rb +70 -335
- data/lib/cocoapods/config.rb +0 -10
- data/lib/cocoapods/dependency.rb +3 -2
- data/lib/cocoapods/downloader/subversion.rb +8 -2
- data/lib/cocoapods/file_list.rb +1 -1
- data/lib/cocoapods/installer/target_installer.rb +10 -6
- data/lib/cocoapods/installer/user_project_integrator.rb +4 -1
- data/lib/cocoapods/local_pod.rb +42 -2
- data/lib/cocoapods/sandbox.rb +51 -35
- data/lib/cocoapods/specification.rb +3 -0
- metadata +7 -3
@@ -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
|
17
|
-
|
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
|
36
|
-
@only_errors
|
37
|
-
@no_clean
|
38
|
-
@
|
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
|
-
|
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 " -> #{
|
80
|
+
print " -> #{linter.spec_name}\r" unless config.silent?
|
93
81
|
$stdout.flush
|
94
|
-
|
95
|
-
|
96
|
-
linter.
|
97
|
-
|
98
|
-
|
99
|
-
|
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(
|
103
|
-
print_messages(
|
104
|
-
print_messages(
|
105
|
-
print_messages(
|
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?
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
:
|
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
|
-
|
124
|
-
@multiple_files && linter.errors.empty? && linter.warnings.empty? && linter.notes.empty?
|
125
|
-
end
|
115
|
+
private
|
126
116
|
|
127
|
-
def print_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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
output_path
|
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
|
#
|