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.
- 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
|
#
|