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 CHANGED
@@ -1,4 +1,21 @@
1
- ## 0.10.0 (Unreleased)
1
+ ## 0.11.0
2
+
3
+ [CocoaPods](https://github.com/CocoaPods/CocoaPods/compare/0.10.0...master)
4
+
5
+ ###### Enhancements
6
+
7
+ - Added support for public headers. [#440]
8
+ - Added `pod repo lint`. [#423]
9
+ - Improved support for `:head` option and svn repositories.
10
+ - When integrating Pods with a project without "Frameworks" group in root of the project, raise an informative message. [#431](https://github.com/CocoaPods/CocoaPods/pull/431)
11
+ - Dropped support for legacy `config.ios?` and `config.osx?`
12
+
13
+ ###### Bug fixes
14
+
15
+ - Version message now correctly terminates with a 0 exit status.
16
+ - Resolved an issue that lead to git error messages in the error report.
17
+
18
+ ## 0.10.0
2
19
 
3
20
  [CocoaPods](http://git.io/4i75YA)
4
21
 
data/lib/cocoapods.rb CHANGED
@@ -13,7 +13,7 @@ unless Gem::Version::Requirement.new('>= 1.4.0').satisfied_by?(Gem::Version.new(
13
13
  end
14
14
 
15
15
  module Pod
16
- VERSION = '0.10.0'
16
+ VERSION = '0.11.0'
17
17
 
18
18
  class PlainInformative < StandardError
19
19
  end
@@ -5,6 +5,7 @@ module Pod
5
5
  autoload :ErrorReport, 'cocoapods/command/error_report'
6
6
  autoload :Install, 'cocoapods/command/install'
7
7
  autoload :List, 'cocoapods/command/list'
8
+ autoload :Linter, 'cocoapods/command/linter'
8
9
  autoload :Presenter, 'cocoapods/command/presenter'
9
10
  autoload :Push, 'cocoapods/command/push'
10
11
  autoload :Repo, 'cocoapods/command/repo'
@@ -86,7 +87,10 @@ module Pod
86
87
 
87
88
  def self.parse(*argv)
88
89
  argv = ARGV.new(argv)
89
- raise PlainInformative, VERSION if argv.option('--version')
90
+ if argv.option('--version')
91
+ puts VERSION
92
+ exit!(0)
93
+ end
90
94
 
91
95
  show_help = argv.option('--help')
92
96
  Config.instance.silent = argv.option('--silent')
@@ -10,7 +10,7 @@ module Pod
10
10
  def report(error)
11
11
  return <<-EOS
12
12
 
13
- #{'――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――'.reversed}
13
+ #{'――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――'.reversed}
14
14
 
15
15
  ### Report
16
16
 
@@ -59,14 +59,14 @@ EOS
59
59
 
60
60
  def markdown_podfile
61
61
  return '' unless Config.instance.project_podfile && Config.instance.project_podfile.exist?
62
- <<-EOS
62
+ <<-EOS
63
63
 
64
64
  ### Podfile
65
65
 
66
66
  ```ruby
67
- #{Config.instance.project_podfile.read.strip}
67
+ #{Config.instance.project_podfile.read.strip}
68
68
  ```
69
- EOS
69
+ EOS
70
70
  end
71
71
 
72
72
  def error_from_podfile(error)
@@ -89,8 +89,8 @@ EOS
89
89
  Pod::Source.all.map do |source|
90
90
  repo = source.repo
91
91
  Dir.chdir(repo) do
92
- url = `git config --get remote.origin.url`.strip
93
- sha = `git rev-parse HEAD`.strip
92
+ url = `git config --get remote.origin.url 2>&1`.strip
93
+ sha = `git rev-parse HEAD 2>&1`.strip
94
94
  "#{repo.basename} - #{url} @ #{sha}"
95
95
  end
96
96
  end
@@ -0,0 +1,292 @@
1
+ module Pod
2
+ class Command
3
+ class Linter
4
+ include Config::Mixin
5
+
6
+ # TODO: Add check to ensure that attributes inherited by subspecs are not duplicated ?
7
+
8
+ attr_accessor :quick, :no_clean, :repo_path
9
+ attr_reader :spec, :file
10
+ attr_reader :errors, :warnings, :notes
11
+
12
+ def initialize(podspec)
13
+ @file = podspec
14
+ end
15
+
16
+ def spec_name
17
+ name = file.basename('.*').to_s
18
+ if @spec
19
+ name << " (#{spec.version})"
20
+ elsif @repo_path
21
+ name << " (#{file.dirname.basename})"
22
+ end
23
+ name
24
+ end
25
+
26
+ # Takes an array of podspec files and lints them all
27
+ #
28
+ # It returns true if the spec passed validation
29
+ #
30
+ def lint
31
+ @errors, @warnings, @notes = [], [], []
32
+ @platform_errors, @platform_warnings, @platform_notes = {}, {}, {}
33
+
34
+ if !deprecation_errors.empty?
35
+ @errors = deprecation_errors
36
+ @errors << "#{spec_name} [!] Fatal errors found skipping the rest of the validation"
37
+ else
38
+ @spec = Specification.from_file(file)
39
+ platforms = spec.available_platforms
40
+
41
+ if @repo_path
42
+ expected_path = "#{@spec.name}/#{@spec.version}/#{@spec.name}.podspec"
43
+ path = file.relative_path_from(@repo_path).to_s
44
+ @errors << "Incorrect path, the path is `#{file}` and should be `#{expected_path}`" unless path.end_with?(expected_path)
45
+ end
46
+
47
+ platforms.each do |platform|
48
+ @platform_errors[platform], @platform_warnings[platform], @platform_notes[platform] = [], [], []
49
+
50
+ spec.activate_platform(platform)
51
+ @platform = platform
52
+ puts "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed if config.verbose? && !@quick
53
+
54
+ # Skip validation if there are errors in the podspec as it would result in a crash
55
+ if !podspec_errors.empty?
56
+ @platform_errors[platform] += podspec_errors
57
+ @platform_notes[platform] << "#{platform.name} [!] Fatal errors found skipping the rest of the validation"
58
+ else
59
+ @platform_warnings[platform] += podspec_warnings
60
+ peform_extensive_analysis unless quick
61
+ end
62
+ end
63
+
64
+ # Get common messages
65
+ @errors += @platform_errors.values.reduce(:&)
66
+ @warnings += @platform_warnings.values.reduce(:&)
67
+ @notes += @platform_notes.values.reduce(:&)
68
+
69
+ platforms.each do |platform|
70
+ # Mark platform specific messages
71
+ @errors += (@platform_errors[platform] - @errors).map {|m| "[#{platform}] #{m}"}
72
+ @warnings += (@platform_warnings[platform] - @warnings).map {|m| "[#{platform}] #{m}"}
73
+ @notes += (@platform_notes[platform] - @notes).map {|m| "[#{platform}] #{m}"}
74
+ end
75
+ end
76
+ end
77
+
78
+ def result_type
79
+ return :error unless errors.empty?
80
+ return :warning unless warnings.empty?
81
+ return :note unless notes.empty?
82
+ :success
83
+ end
84
+
85
+ # Performs platform specific analysis.
86
+ # It requires to download the source at each iteration
87
+ #
88
+ def peform_extensive_analysis
89
+ set_up_lint_environment
90
+ install_pod
91
+ if `which xcodebuild`.strip.empty?
92
+ puts "Skipping compilation with `xcodebuild' because it can't be found.\n".yellow if config.verbose?
93
+ else
94
+ puts "Building with xcodebuild.\n".yellow if config.verbose?
95
+ # treat xcodebuild warnings as notes because the spec maintainer might not be the author of the library
96
+ xcodebuild_output.each { |msg| ( msg.include?('error: ') ? @platform_errors[@platform] : @platform_notes[@platform] ) << msg }
97
+ end
98
+ @platform_errors[@platform] += file_patterns_errors
99
+ @platform_warnings[@platform] += file_patterns_warnings
100
+ tear_down_lint_environment
101
+ end
102
+
103
+ def install_pod
104
+ podfile = podfile_from_spec
105
+ config.verbose
106
+ installer = Installer.new(podfile)
107
+ installer.install!
108
+ @pod = installer.pods.find { |pod| pod.top_specification == spec }
109
+ config.silent
110
+ end
111
+
112
+ def podfile_from_spec
113
+ name = spec.name
114
+ podspec = file.realpath.to_s
115
+ platform = @platform
116
+ podfile = Pod::Podfile.new do
117
+ platform(platform.to_sym, platform.deployment_target)
118
+ pod name, :podspec => podspec
119
+ end
120
+ podfile
121
+ end
122
+
123
+ def set_up_lint_environment
124
+ tmp_dir.rmtree if tmp_dir.exist?
125
+ tmp_dir.mkpath
126
+ @original_config = Config.instance.clone
127
+ config.project_root = tmp_dir
128
+ config.project_pods_root = tmp_dir + 'Pods'
129
+ config.silent = !config.verbose
130
+ config.integrate_targets = false
131
+ config.generate_docs = false
132
+ end
133
+
134
+ def tear_down_lint_environment
135
+ tmp_dir.rmtree unless no_clean
136
+ Config.instance = @original_config
137
+ end
138
+
139
+ def tmp_dir
140
+ Pathname.new('/tmp/CocoaPods/Lint')
141
+ end
142
+
143
+ def pod_dir
144
+ tmp_dir + 'Pods' + spec.name
145
+ end
146
+
147
+ # It reads a podspec file and checks for strings corresponding
148
+ # to features that are or will be deprecated
149
+ #
150
+ # @return [Array<String>]
151
+ #
152
+ def deprecation_errors
153
+ text = @file.read
154
+ deprecations = []
155
+ deprecations << "`config.ios?' and `config.osx?' are deprecated" if text. =~ /config\..?os.?/
156
+ deprecations << "clean_paths are deprecated and ignored (use preserve_paths)" if text. =~ /clean_paths/
157
+ deprecations
158
+ end
159
+
160
+ # @return [Array<String>] List of the fatal defects detected in a podspec
161
+ def podspec_errors
162
+ messages = []
163
+ messages << "The name of the spec should match the name of the file" unless names_match?
164
+ messages << "Unrecognized platfrom" unless platform_valid?
165
+ messages << "Missing name" unless spec.name
166
+ messages << "Missing version" unless spec.version
167
+ messages << "Missing summary" unless spec.summary
168
+ messages << "Missing homepage" unless spec.homepage
169
+ messages << "Missing author(s)" unless spec.authors
170
+ messages << "Missing or invalid source: #{spec.source}" unless source_valid?
171
+
172
+ # attributes with multiplatform values
173
+ return messages unless platform_valid?
174
+ 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?
175
+ messages += paths_starting_with_a_slash_errors
176
+ messages += deprecation_errors
177
+ messages
178
+ end
179
+
180
+ def names_match?
181
+ return true unless spec.name
182
+ root_name = spec.name.match(/[^\/]*/)[0]
183
+ file.basename.to_s == root_name + '.podspec'
184
+ end
185
+
186
+ def platform_valid?
187
+ !spec.platform || [:ios, :osx].include?(spec.platform.name)
188
+ end
189
+
190
+ def source_valid?
191
+ spec.source && !(spec.source =~ /http:\/\/EXAMPLE/)
192
+ end
193
+
194
+ def paths_starting_with_a_slash_errors
195
+ messages = []
196
+ %w[source_files public_header_files resources clean_paths].each do |accessor|
197
+ patterns = spec.send(accessor.to_sym)
198
+ # Some values are multiplaform
199
+ patterns = patterns.is_a?(Hash) ? patterns.values.flatten(1) : patterns
200
+ patterns = patterns.compact # some patterns may be nil (public_header_files, for instance)
201
+ patterns.each do |pattern|
202
+ # Skip FileList that would otherwise be resolved from the working directory resulting
203
+ # in a potentially very expensi operation
204
+ next if pattern.is_a?(FileList)
205
+ invalid = pattern.is_a?(Array) ? pattern.any? { |path| path.start_with?('/') } : pattern.start_with?('/')
206
+ if invalid
207
+ messages << "Paths cannot start with a slash (#{accessor})"
208
+ break
209
+ end
210
+ end
211
+ end
212
+ messages
213
+ end
214
+
215
+ # @return [Array<String>] List of the **non** fatal defects detected in a podspec
216
+ def podspec_warnings
217
+ license = spec.license || {}
218
+ source = spec.source || {}
219
+ text = @file.read
220
+ messages = []
221
+ messages << "Missing license type" unless license[:type]
222
+ messages << "Sample license type" if license[:type] && license[:type] =~ /\(example\)/
223
+ messages << "Invalid license type" if license[:type] && license[:type] =~ /\n/
224
+ messages << "The summary is not meaningful" if spec.summary =~ /A short description of/
225
+ messages << "The description is not meaningful" if spec.description && spec.description =~ /An optional longer description of/
226
+ messages << "The summary should end with a dot" if spec.summary !~ /.*\./
227
+ messages << "The description should end with a dot" if spec.description !~ /.*\./ && spec.description != spec.summary
228
+ messages << "Git sources should specify either a tag or a commit" if source[:git] && !source[:commit] && !source[:tag]
229
+ messages << "Github repositories should end in `.git'" if github_source? && source[:git] !~ /.*\.git/
230
+ messages << "Github repositories should use `https' link" if github_source? && source[:git] !~ /https:\/\/github.com/
231
+ messages << "Comments must be deleted" if text.scan(/^\s*#/).length > 24
232
+ messages
233
+ end
234
+
235
+ def github_source?
236
+ spec.source && spec.source[:git] =~ /github.com/
237
+ end
238
+
239
+ # It creates a podfile in memory and builds a library containing
240
+ # the pod for all available platfroms with xcodebuild.
241
+ #
242
+ # @return [Array<String>]
243
+ #
244
+ def xcodebuild_output
245
+ return [] if `which xcodebuild`.strip.empty?
246
+ messages = []
247
+ output = Dir.chdir(config.project_pods_root) { `xcodebuild clean build 2>&1` }
248
+ clean_output = process_xcode_build_output(output)
249
+ messages += clean_output
250
+ puts(output) if config.verbose?
251
+ messages
252
+ end
253
+
254
+ def process_xcode_build_output(output)
255
+ output_by_line = output.split("\n")
256
+ selected_lines = output_by_line.select do |l|
257
+ l.include?('error: ') && (l !~ /errors? generated\./) && (l !~ /error: \(null\)/)\
258
+ || l.include?('warning: ') && (l !~ /warnings? generated\./)\
259
+ || l.include?('note: ') && (l !~ /expanded from macro/)
260
+ end
261
+ selected_lines.map do |l|
262
+ new = l.gsub(/\/tmp\/CocoaPods\/Lint\/Pods\//,'') # Remove the unnecessary tmp path
263
+ new.gsub!(/^ */,' ') # Remove indentation
264
+ "XCODEBUILD > " << new # Mark
265
+ end
266
+ end
267
+
268
+ # It checks that every file pattern specified in a spec yields
269
+ # at least one file. It requires the pods to be alredy present
270
+ # in the current working directory under Pods/spec.name.
271
+ #
272
+ # @return [Array<String>]
273
+ #
274
+ def file_patterns_errors
275
+ messages = []
276
+ messages << "The sources did not match any file" if !spec.source_files.empty? && @pod.source_files.empty?
277
+ messages << "The resources did not match any file" if !spec.resources.empty? && @pod.resource_files.empty?
278
+ messages << "The preserve_paths did not match any file" if !spec.preserve_paths.empty? && @pod.preserve_files.empty?
279
+ 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?
280
+ messages
281
+ end
282
+
283
+ def file_patterns_warnings
284
+ messages = []
285
+ unless @pod.license_file || spec.license && ( spec.license[:type] == 'Public Domain' || spec.license[:text] )
286
+ messages << "Unable to find a license file"
287
+ end
288
+ messages
289
+ end
290
+ end
291
+ end
292
+ end
@@ -8,9 +8,10 @@ module Pod
8
8
 
9
9
  $ pod push REPO [NAME.podspec]
10
10
 
11
- Validates NAME.podspec or `*.podspec' in the current working dir, updates
12
- the local copy of the repository named REPO, adds the specifications
13
- to the REPO, and finally it pushes REPO to its remote.}
11
+ Validates NAME.podspec or `*.podspec' in the current working dir, creates
12
+ a directory and version folder for the pod in the local copy of
13
+ REPO (~/.cocoapods/[REPO]), copies the podspec file into the version directory,
14
+ and finally it pushes REPO to its remote.}
14
15
  end
15
16
 
16
17
  def self.options
@@ -11,10 +11,20 @@ module Pod
11
11
  Clones `URL' in the local spec-repos directory at `~/.cocoapods'. The
12
12
  remote can later be referred to by `NAME'.
13
13
 
14
- $ pod repo update NAME
14
+ $ pod repo update [NAME]
15
15
 
16
16
  Updates the local clone of the spec-repo `NAME'. If `NAME' is omitted
17
- this will update all spec-repos in `~/.cocoapods'.}
17
+ this will update all spec-repos in `~/.cocoapods'.
18
+
19
+ $ pod repo update [NAME | DIRECTORY]
20
+
21
+ Lints the spec-repo `NAME'. If a directory is provided it is assumed
22
+ to be the root of a repo. Finally, if NAME is not provided this will
23
+ lint all the spec-repos known to CocoaPods.}
24
+ end
25
+
26
+ def self.options
27
+ [["--only-errors", "Lint presents only the errors"]].concat(super)
18
28
  end
19
29
 
20
30
  extend Executable
@@ -29,6 +39,9 @@ module Pod
29
39
  @branch = argv.arguments[3]
30
40
  when 'update'
31
41
  @name = argv.arguments[1]
42
+ when 'lint'
43
+ @name = argv.arguments[1]
44
+ @only_errors = argv.option('--only-errors')
32
45
  else
33
46
  super
34
47
  end
@@ -66,6 +79,60 @@ module Pod
66
79
  end
67
80
  end
68
81
 
82
+ def lint
83
+ if @name
84
+ dirs = File.exists?(@name) ? [ Pathname.new(@name) ] : [ dir ]
85
+ else
86
+ dirs = config.repos_dir.children.select {|c| c.directory?}
87
+ end
88
+ dirs.each do |dir|
89
+ check_versions(dir)
90
+ puts "\nLinting spec repo `#{dir.realpath.basename}'\n".yellow
91
+ podspecs = dir.glob('**/*.podspec')
92
+ invalid_count = 0
93
+
94
+ podspecs.each do |podspec|
95
+ linter = Linter.new(podspec)
96
+ linter.quick = true
97
+ linter.repo_path = dir
98
+
99
+ linter.lint
100
+
101
+ case linter.result_type
102
+ when :error
103
+ invalid_count += 1
104
+ color = :red
105
+ should_display = true
106
+ when :warning
107
+ color = :yellow
108
+ should_display = !@only_errors
109
+ end
110
+
111
+ if should_display
112
+ puts " -> ".send(color) << linter.spec_name
113
+ print_messages('ERROR', linter.errors)
114
+ unless @only_errors
115
+ print_messages('WARN', linter.warnings)
116
+ print_messages('NOTE', linter.notes)
117
+ end
118
+ puts unless config.silent?
119
+ end
120
+ end
121
+ puts "Analyzed #{podspecs.count} podspecs files.\n\n" unless config.silent?
122
+
123
+ if invalid_count == 0
124
+ puts "All the specs passed validation.".green << "\n\n" unless config.silent?
125
+ else
126
+ raise Informative, "#{invalid_count} podspecs failed validation."
127
+ end
128
+ end
129
+ end
130
+
131
+ def print_messages(type, messages)
132
+ return if config.silent?
133
+ messages.each {|msg| puts " - #{type.ljust(5)} | #{msg}"}
134
+ end
135
+
69
136
  def check_versions(dir)
70
137
  versions = versions(dir)
71
138
  unless is_compatilbe(versions)