cocoapods 0.16.4 → 0.17.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +108 -0
  3. data/README.md +3 -3
  4. data/bin/pod +1 -1
  5. data/lib/cocoapods.rb +31 -31
  6. data/lib/cocoapods/command.rb +62 -107
  7. data/lib/cocoapods/command/inter_process_communication.rb +103 -0
  8. data/lib/cocoapods/command/list.rb +45 -44
  9. data/lib/cocoapods/command/outdated.rb +28 -25
  10. data/lib/cocoapods/command/project.rb +90 -0
  11. data/lib/cocoapods/command/push.rb +50 -32
  12. data/lib/cocoapods/command/repo.rb +125 -155
  13. data/lib/cocoapods/command/search.rb +23 -12
  14. data/lib/cocoapods/command/setup.rb +103 -64
  15. data/lib/cocoapods/command/spec.rb +329 -90
  16. data/lib/cocoapods/config.rb +197 -44
  17. data/lib/cocoapods/downloader.rb +47 -34
  18. data/lib/cocoapods/executable.rb +98 -41
  19. data/lib/cocoapods/external_sources.rb +325 -0
  20. data/lib/cocoapods/file_list.rb +8 -1
  21. data/lib/cocoapods/gem_version.rb +7 -0
  22. data/lib/cocoapods/generator/acknowledgements.rb +71 -7
  23. data/lib/cocoapods/generator/acknowledgements/markdown.rb +10 -9
  24. data/lib/cocoapods/generator/acknowledgements/plist.rb +9 -8
  25. data/lib/cocoapods/generator/copy_resources_script.rb +2 -2
  26. data/lib/cocoapods/generator/documentation.rb +153 -37
  27. data/lib/cocoapods/generator/prefix_header.rb +82 -0
  28. data/lib/cocoapods/generator/target_header.rb +58 -0
  29. data/lib/cocoapods/generator/xcconfig.rb +130 -0
  30. data/lib/cocoapods/hooks/installer_representation.rb +123 -0
  31. data/lib/cocoapods/hooks/library_representation.rb +79 -0
  32. data/lib/cocoapods/hooks/pod_representation.rb +74 -0
  33. data/lib/cocoapods/installer.rb +398 -147
  34. data/lib/cocoapods/installer/analyzer.rb +556 -0
  35. data/lib/cocoapods/installer/analyzer/sandbox_analyzer.rb +253 -0
  36. data/lib/cocoapods/installer/file_references_installer.rb +179 -0
  37. data/lib/cocoapods/installer/pod_source_installer.rb +289 -0
  38. data/lib/cocoapods/installer/target_installer.rb +307 -112
  39. data/lib/cocoapods/installer/user_project_integrator.rb +140 -176
  40. data/lib/cocoapods/installer/user_project_integrator/target_integrator.rb +193 -0
  41. data/lib/cocoapods/library.rb +195 -0
  42. data/lib/cocoapods/open_uri.rb +16 -14
  43. data/lib/cocoapods/project.rb +175 -52
  44. data/lib/cocoapods/resolver.rb +151 -164
  45. data/lib/cocoapods/sandbox.rb +276 -54
  46. data/lib/cocoapods/sandbox/file_accessor.rb +210 -0
  47. data/lib/cocoapods/sandbox/headers_store.rb +96 -0
  48. data/lib/cocoapods/sandbox/path_list.rb +178 -0
  49. data/lib/cocoapods/sources_manager.rb +218 -0
  50. data/lib/cocoapods/user_interface.rb +82 -18
  51. data/lib/cocoapods/{command → user_interface}/error_report.rb +5 -5
  52. data/lib/cocoapods/validator.rb +379 -0
  53. metadata +74 -55
  54. data/lib/cocoapods/command/install.rb +0 -55
  55. data/lib/cocoapods/command/linter.rb +0 -317
  56. data/lib/cocoapods/command/update.rb +0 -25
  57. data/lib/cocoapods/dependency.rb +0 -285
  58. data/lib/cocoapods/downloader/git.rb +0 -276
  59. data/lib/cocoapods/downloader/http.rb +0 -99
  60. data/lib/cocoapods/downloader/mercurial.rb +0 -26
  61. data/lib/cocoapods/downloader/subversion.rb +0 -42
  62. data/lib/cocoapods/local_pod.rb +0 -620
  63. data/lib/cocoapods/lockfile.rb +0 -274
  64. data/lib/cocoapods/platform.rb +0 -127
  65. data/lib/cocoapods/podfile.rb +0 -551
  66. data/lib/cocoapods/source.rb +0 -223
  67. data/lib/cocoapods/specification.rb +0 -579
  68. data/lib/cocoapods/specification/set.rb +0 -175
  69. data/lib/cocoapods/specification/statistics.rb +0 -112
  70. data/lib/cocoapods/user_interface/ui_pod.rb +0 -130
  71. data/lib/cocoapods/version.rb +0 -26
@@ -0,0 +1,218 @@
1
+ module Pod
2
+
3
+ # Manages all the sources known to the running CocoaPods Instance.
4
+ #
5
+ class SourcesManager
6
+
7
+ class << self
8
+
9
+ include Config::Mixin
10
+
11
+ # @return [Source::Aggregate] the aggregate of all the sources known to
12
+ # this installation of CocoaPods.
13
+ #
14
+ def aggregate
15
+ Source::Aggregate.new(config.repos_dir)
16
+ end
17
+
18
+ # @return [Array<Source>] the list of all the sources known to this
19
+ # installation of CocoaPods.
20
+ #
21
+ def all
22
+ aggregate.all
23
+ end
24
+
25
+ # @return [Array<Specification::Set>] the list of all the specification
26
+ # sets know to this installation of CocoaPods.
27
+ #
28
+ def all_sets
29
+ aggregate.all_sets
30
+ end
31
+
32
+ # Search all the sources to match the set for the given dependency.
33
+ #
34
+ # @return [Set, nil] a set for a given dependency including all the
35
+ # {Source} that contain the Pod. If no sources containing the
36
+ # Pod where found it returns nil.
37
+ #
38
+ # @raise If no source including the set can be found.
39
+ #
40
+ def search(dependency)
41
+ set = aggregate.search(dependency)
42
+ unless set
43
+ raise Informative, "Unable to find a pod named `#{dependency.name}`"
44
+ end
45
+ set
46
+ end
47
+
48
+ # Search all the sources with the given search term.
49
+ #
50
+ # @param [String] query
51
+ # The search term.
52
+ #
53
+ # @param [Bool] full_text_search
54
+ # Whether the search should be limited to the name of the Pod or
55
+ # should include also the author, the summary, and the
56
+ # description.
57
+ #
58
+ # @raise If no source including the set can be found.
59
+ #
60
+ # @note Full text search requires to load the specification for each
61
+ # pod, hence is considerably slower.
62
+ #
63
+ # @return [Array<Set>] The sets that contain the search term.
64
+ #
65
+ def search_by_name(query, full_text_search = false)
66
+ result = aggregate.search_by_name(query, full_text_search)
67
+ if result.empty?
68
+ extra = ", author, summary, or description" if full_text_search
69
+ raise Informative "Unable to find a pod with name#{extra} matching `#{query}`"
70
+ end
71
+ result
72
+ end
73
+
74
+ #-----------------------------------------------------------------------#
75
+
76
+ # @!group Updating Sources
77
+
78
+ extend Executable
79
+ executable :git
80
+
81
+ # Updates the local clone of the spec-repo with the given name or of all
82
+ # the git repos if the name is omitted.
83
+ #
84
+ # @param [String] name
85
+ #
86
+ # @return [void]
87
+ #
88
+ def update(source_name = nil, show_output = false)
89
+ if source_name
90
+ source = aggregate.all.find { |s| s.name == source_name }
91
+ raise Informative, "Unable to find the `#{source_name}` repo." unless source
92
+ raise Informative, "The `#{source_name}` repo is not a git repo." unless git_repo?(source.repo)
93
+ sources = [source]
94
+ else
95
+ sources = aggregate.all.select { |source| git_repo?(source.repo) }
96
+ end
97
+
98
+ sources.each do |source|
99
+ UI.section "Updating spec repo `#{source.name}`" do
100
+ Dir.chdir(source.repo) do
101
+ output = git!("pull")
102
+ UI.puts output if show_output && !config.verbose?
103
+ end
104
+ check_version_information(source.repo)
105
+ end
106
+ end
107
+ end
108
+
109
+ # Returns whether a source is a GIT repo.
110
+ #
111
+ # @param [Pathname] dir
112
+ # The directory where the source is stored.
113
+ #
114
+ # @return [Bool] Wether the given source is a GIT repo.
115
+ #
116
+ def git_repo?(dir)
117
+ Dir.chdir(dir) { `git rev-parse >/dev/null 2>&1` }
118
+ $?.exitstatus.zero?
119
+ end
120
+
121
+ # Checks the version information of the source with the given directory.
122
+ # It raises if the source is not compatible and if there is CocoaPods
123
+ # update it informs the user.
124
+ #
125
+ # @param [Pathname] dir
126
+ # The directory where the source is stored.
127
+ #
128
+ # @raise If the source is not compatible.
129
+ #
130
+ # @return [void]
131
+ #
132
+ def check_version_information(dir)
133
+ versions = version_information(dir)
134
+ unless repo_compatible?(dir)
135
+ min, max = versions['min'], versions['max']
136
+ version_msg = ( min == max ) ? min : "#{min} - #{max}"
137
+ raise Informative, "The `#{dir.basename}` repo requires " \
138
+ "CocoaPods #{version_msg}\n".red +
139
+ "Update Cocoapods, or checkout the appropriate tag in the repo."
140
+ end
141
+
142
+ if config.new_version_message? && cocoapods_update?(versions)
143
+ UI.puts "\nCocoapods #{versions['last']} is available.\n".green
144
+ end
145
+ end
146
+
147
+ # Returns whether a source is compatible with the current version of
148
+ # CocoaPods.
149
+ #
150
+ # @param [Pathname] dir
151
+ # The directory where the source is stored.
152
+ #
153
+ # @return [Bool] whether the source is compatible.
154
+ #
155
+ def repo_compatible?(dir)
156
+ versions = version_information(dir)
157
+
158
+ min, max = versions['min'], versions['max']
159
+ bin_version = Gem::Version.new(Pod::VERSION)
160
+ supports_min = !min || bin_version >= Gem::Version.new(min)
161
+ supports_max = !max || bin_version <= Gem::Version.new(max)
162
+ supports_min && supports_max
163
+ end
164
+
165
+ # Checks whether there is a CocoaPods given the version information of a
166
+ # repo.
167
+ #
168
+ # @param [Hash] version_information
169
+ # The version information of a repository.
170
+ #
171
+ # @return [Bool] whether there is an update.
172
+ #
173
+ def cocoapods_update?(version_information)
174
+ version = version_information['last']
175
+ version && Gem::Version.new(version) > Gem::Version.new(Pod::VERSION)
176
+ end
177
+
178
+ # Returns the contents of the `CocoaPods-version.yml` file, which stores
179
+ # information about CocoaPods versions.
180
+ #
181
+ # This file is a hash with the following keys:
182
+ #
183
+ # - last: the last version of CocoaPods known to the source.
184
+ # - min: the minimum version of CocoaPods supported by the source.
185
+ # - max: the maximum version of CocoaPods supported by the source.
186
+ #
187
+ # @param [Pathname] dir
188
+ # The directory where the source is stored.
189
+ #
190
+ # @return [Hash] the versions information from the repo.
191
+ #
192
+ def version_information(dir)
193
+ require 'yaml'
194
+ yaml_file = dir + 'CocoaPods-version.yml'
195
+ yaml_file.exist? ? YAML.load_file(yaml_file) : {}
196
+ end
197
+
198
+ #-----------------------------------------------------------------------#
199
+
200
+ # @!group Master repo
201
+
202
+ # @return [Pathname] The path of the master repo.
203
+ #
204
+ def master_repo_dir
205
+ config.repos_dir + 'master'
206
+ end
207
+
208
+ # @return [Bool] Checks if the master repo is usable.
209
+ #
210
+ # @note Note this is used to automatically setup the master repo if
211
+ # needed.
212
+ #
213
+ def master_repo_functional?
214
+ master_repo_dir.exist? && repo_compatible?(master_repo_dir)
215
+ end
216
+ end
217
+ end
218
+ end
@@ -1,18 +1,27 @@
1
+ require 'cocoapods/user_interface/error_report'
2
+
1
3
  module Pod
2
- require 'colored'
4
+
5
+ # Provides support for UI output. It provides support for nested sections of
6
+ # information and for a verbose mode.
7
+ #
3
8
  module UserInterface
4
9
 
5
- autoload :UIPod, 'cocoapods/user_interface/ui_pod'
10
+ require 'colored'
6
11
 
7
12
  @title_colors = %w|yellow green|
8
13
  @title_level = 0
9
14
  @indentation_level = 2
10
15
  @treat_titles_as_messages = false
16
+ @warnings = []
11
17
 
12
18
  class << self
19
+
13
20
  include Config::Mixin
14
21
 
15
- attr_accessor :indentation_level, :title_level
22
+ attr_accessor :indentation_level
23
+ attr_accessor :title_level
24
+ attr_accessor :warnings
16
25
 
17
26
  # Prints a title taking an optional verbose prefix and
18
27
  # a relative indentation valid for the UI action in the passed
@@ -22,13 +31,34 @@ module Pod
22
31
  # to their level. In normal mode titles are printed only if
23
32
  # they have nesting level smaller than 2.
24
33
  #
25
- # TODO: refactor to title (for always visible titles like search)
26
- # and sections (titles that reppresent collapsible sections).
34
+ # @todo Refactor to title (for always visible titles like search)
35
+ # and sections (titles that represent collapsible sections).
27
36
  #
28
37
  def section(title, verbose_prefix = '', relative_indentation = 0)
29
38
  if config.verbose?
30
39
  title(title, verbose_prefix, relative_indentation)
31
- elsif title_level < 2
40
+ elsif title_level < 1
41
+ puts title
42
+ end
43
+
44
+ self.indentation_level += relative_indentation
45
+ self.title_level += 1
46
+ yield if block_given?
47
+ self.indentation_level -= relative_indentation
48
+ self.title_level -= 1
49
+ end
50
+
51
+ # In verbose mode it shows the sections and the contents.
52
+ # In normal mode it just prints the title.
53
+ #
54
+ # @return [void]
55
+ #
56
+ def titled_section(title, options = {})
57
+ relative_indentation = options[:relative_indentation] || 0
58
+ verbose_prefix = options[:verbose_prefix] || ''
59
+ if config.verbose?
60
+ title(title, verbose_prefix, relative_indentation)
61
+ else
32
62
  puts title
33
63
  end
34
64
 
@@ -39,7 +69,7 @@ module Pod
39
69
  self.title_level -= 1
40
70
  end
41
71
 
42
- # A title oposed to a section is always visible
72
+ # A title opposed to a section is always visible
43
73
  #
44
74
  def title(title, verbose_prefix = '', relative_indentation = 2)
45
75
  if(@treat_titles_as_messages)
@@ -67,7 +97,7 @@ module Pod
67
97
  # a relative indentation valid for the UI action in the passed
68
98
  # block.
69
99
  #
70
- # TODO: clean interface.
100
+ # @todo Clean interface.
71
101
  #
72
102
  def message(message, verbose_prefix = '', relative_indentation = 2)
73
103
  message = verbose_prefix + message if config.verbose?
@@ -106,19 +136,32 @@ module Pod
106
136
  puts("\n[!] #{message}".green)
107
137
  end
108
138
 
109
- # Prints an important warning to the user optionally followed by actions
110
- # that the user should take.
139
+ # Stores important warning to the user optionally followed by actions
140
+ # that the user should take. To print them use #{print_warnings}
111
141
  #
112
142
  # @param [String] message The message to print.
113
143
  # @param [Array] actions The actions that the user should take.
114
144
  #
115
145
  # return [void]
116
146
  #
117
- def warn(message, actions)
118
- puts("\n[!] #{message}".yellow)
119
- actions.each do |action|
120
- indented = wrap_string(action, " - ")
121
- puts(indented)
147
+ def warn(message, actions = [], verbose_only = false)
148
+ warnings << { :message => message, :actions => actions }
149
+ end
150
+
151
+ # Prints the stored warnings. This method is intended to be called at the
152
+ # end of the execution of the binary.
153
+ #
154
+ # @return [void]
155
+ #
156
+ def print_warnings
157
+ return if config.silent? && verbose_only
158
+ STDOUT.flush
159
+ warnings.each do |warning|
160
+ STDERR.puts("\n[!] #{warning[:message]}".yellow)
161
+ warning[:actions].each do |action|
162
+ indented = wrap_string(action, " - ")
163
+ puts(indented)
164
+ end
122
165
  end
123
166
  end
124
167
 
@@ -128,19 +171,20 @@ module Pod
128
171
  #
129
172
  def path(pathname)
130
173
  if pathname
131
- "`./#{pathname.relative_path_from(config.project_podfile.dirname || Pathname.pwd)}'"
174
+ path = pathname.relative_path_from(config.podfile_path.dirname || Pathname.pwd)
175
+ "`#{path}`"
132
176
  else
133
177
  ''
134
178
  end
135
179
  end
136
180
 
137
- # Prints the textual repprensentation of a given set.
181
+ # Prints the textual representation of a given set.
138
182
  #
139
183
  def pod(set, mode = :normal)
140
184
  if mode == :name
141
185
  puts_indented set.name
142
186
  else
143
- pod = UIPod.new(set)
187
+ pod = Specification::Set::Presenter.new(set)
144
188
  title("\n-> #{pod.name} (#{pod.version})".green, '', 1) do
145
189
  puts_indented pod.summary
146
190
  labeled('Homepage', pod.homepage)
@@ -217,4 +261,24 @@ module Pod
217
261
  end
218
262
  end
219
263
  UI = UserInterface
264
+
265
+ # Redirects cocoapods-core UI.
266
+ #
267
+ module CoreUI
268
+
269
+ class << self
270
+
271
+ # @todo enable in CocoaPods 0.17.0 release
272
+ #
273
+ def puts(message)
274
+ # UI.puts message
275
+ end
276
+
277
+ # @todo enable in CocoaPods 0.17.0 release
278
+ #
279
+ def warn(message)
280
+ # UI.warn message
281
+ end
282
+ end
283
+ end
220
284
  end
@@ -4,13 +4,13 @@ require 'rbconfig'
4
4
  require 'cgi'
5
5
 
6
6
  module Pod
7
- class Command
7
+ module UserInterface
8
8
  module ErrorReport
9
9
  class << self
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
 
@@ -58,13 +58,13 @@ EOS
58
58
  private
59
59
 
60
60
  def markdown_podfile
61
- return '' unless Config.instance.project_podfile && Config.instance.project_podfile.exist?
61
+ return '' unless Config.instance.podfile_path && Config.instance.podfile_path.exist?
62
62
  <<-EOS
63
63
 
64
64
  ### Podfile
65
65
 
66
66
  ```ruby
67
- #{Config.instance.project_podfile.read.strip}
67
+ #{Config.instance.podfile_path.read.strip}
68
68
  ```
69
69
  EOS
70
70
  end
@@ -86,7 +86,7 @@ EOS
86
86
  end
87
87
 
88
88
  def repo_information
89
- Pod::Source.all.map do |source|
89
+ SourcesManager.all.map do |source|
90
90
  repo = source.repo
91
91
  Dir.chdir(repo) do
92
92
  url = `git config --get remote.origin.url 2>&1`.strip
@@ -0,0 +1,379 @@
1
+ module Pod
2
+
3
+ # Validates a Specification.
4
+ #
5
+ # Extends the Linter from the Core to add additional which require the
6
+ # LocalPod and the Installer.
7
+ #
8
+ # In detail it checks that the file patterns defined by the user match
9
+ # actually do match at least a file and that the Pod builds, by installing
10
+ # it without integration and building the project with xcodebuild.
11
+ #
12
+ class Validator
13
+
14
+ include Config::Mixin
15
+
16
+ # @return [Specification::Linter] the linter instance from CocoaPods
17
+ # Core.
18
+ #
19
+ attr_reader :linter
20
+
21
+ # @param [Specification, Pathname, String] spec_or_path
22
+ # the Specification or the path of the `podspec` file to lint.
23
+ #
24
+ def initialize(spec_or_path)
25
+ @linter = Specification::Linter.new(spec_or_path)
26
+ end
27
+
28
+ #-------------------------------------------------------------------------#
29
+
30
+ # @return [Specification] the specification to lint.
31
+ #
32
+ def spec
33
+ @linter.spec
34
+ end
35
+
36
+ # @return [Pathname] the path of the `podspec` file where {#spec} is
37
+ # defined.
38
+ #
39
+ def file
40
+ @linter.file
41
+ end
42
+
43
+ # @return [Sandbox::FileAccessor] the file accessor for the spec.
44
+ #
45
+ attr_accessor :file_accessor
46
+
47
+ #-------------------------------------------------------------------------#
48
+
49
+ # Lints the specification adding a {Specification::Linter::Result} for any
50
+ # failed check to the {#results} list.
51
+ #
52
+ # @note This method shows immediately which pod is being processed and
53
+ # overrides the printed line once the result is known.
54
+ #
55
+ # @return [Bool] whether the specification passed validation.
56
+ #
57
+ def validate
58
+ @results = []
59
+ unless disable_ui_output
60
+ print " -> #{spec ? spec.name : file.basename}\r" unless config.silent?
61
+ $stdout.flush
62
+ end
63
+
64
+ perform_linting
65
+
66
+ # begin
67
+ if spec
68
+ check_repo_path if repo_path
69
+ perform_extensive_analysis unless quick
70
+ end
71
+ # rescue Exception => e
72
+ # error "The specification is malformed and crashed the linter."
73
+ # end
74
+
75
+ unless disable_ui_output
76
+ UI.puts " -> ".send(result_color) << (spec ? spec.name : file.basename.to_s)
77
+ print_results
78
+ end
79
+ validated?
80
+ end
81
+
82
+ def print_results
83
+ results.each do |result|
84
+ if result.platforms == [:ios]
85
+ platform_message = "[iOS] "
86
+ elsif result.platforms == [:osx]
87
+ platform_message = "[OSX] "
88
+ end
89
+
90
+ case result.type
91
+ when :error then type = "ERROR"
92
+ when :warning then type = "WARN"
93
+ when :note then type = "NOTE"
94
+ else raise "#{result.type}" end
95
+ UI.puts " - #{type.ljust(5)} | #{platform_message}#{result.message}"
96
+ end
97
+ UI.puts
98
+ end
99
+
100
+ #-------------------------------------------------------------------------#
101
+
102
+ # @!group Configuration
103
+
104
+ # @return [Bool] Whether the validator should print the results of the
105
+ # validation. This is useful for clients which want to customize
106
+ # output.
107
+ #
108
+ attr_accessor :disable_ui_output
109
+
110
+ # @return [Pathname] whether the validation should be performed against a repo.
111
+ #
112
+ attr_accessor :repo_path
113
+
114
+ # @return [Bool] whether the validation should skip the checks that
115
+ # requires the download of the library.
116
+ #
117
+ attr_accessor :quick
118
+
119
+ # @return [Bool] whether the linter should not clean up temporary files
120
+ # for inspection.
121
+ #
122
+ attr_accessor :no_clean
123
+
124
+ # @return [Bool] whether the validation should be performed against the root of
125
+ # the podspec instead to its original source.
126
+ #
127
+ # @note Uses the `:local` option of the Podfile.
128
+ #
129
+ attr_writer :local
130
+ def local?; @local; end
131
+
132
+ # @return [Bool] Whether the validator should fail only on errors or also
133
+ # on warnings.
134
+ #
135
+ attr_accessor :only_errors
136
+
137
+ #-------------------------------------------------------------------------#
138
+
139
+ # !@group Lint results
140
+
141
+ #
142
+ #
143
+ attr_reader :results
144
+
145
+ # @return [Boolean]
146
+ #
147
+ def validated?
148
+ result_type != :error && (result_type != :warning || only_errors)
149
+ end
150
+
151
+ # @return [Symbol]
152
+ #
153
+ def result_type
154
+ types = results.map(&:type).uniq
155
+ if types.include?(:error) then :error
156
+ elsif types.include?(:warning) then :warning
157
+ else :note end
158
+ end
159
+
160
+ # @return [Symbol]
161
+ #
162
+ def result_color
163
+ case result_type
164
+ when :error then :red
165
+ when :warning then :yellow
166
+ else :green end
167
+ end
168
+
169
+ # @return [Pathname] the temporary directory used by the linter.
170
+ #
171
+ def validation_dir
172
+ Pathname.new('/tmp/CocoaPods/Lint')
173
+ end
174
+
175
+ #-------------------------------------------------------------------------#
176
+
177
+ private
178
+
179
+ # !@group Lint steps
180
+
181
+ #
182
+ #
183
+ def perform_linting
184
+ linter.lint
185
+ @results.concat(linter.results)
186
+ end
187
+
188
+ #
189
+ #
190
+ def check_repo_path
191
+ expected_path = "#{spec.name}/#{spec.version}/#{spec.name}.podspec"
192
+ path = file.relative_path_from(repo_path).to_s
193
+ unless path == expected_path
194
+ error "Incorrect path, the path is `#{file}` and should be `#{expected_path}`"
195
+ end
196
+ end
197
+
198
+ #
199
+ #
200
+ def perform_extensive_analysis
201
+ spec.available_platforms.each do |platform|
202
+ UI.message "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed
203
+ @consumer = spec.consumer(platform)
204
+ setup_validation_environment
205
+ install_pod
206
+ build_pod
207
+ check_file_patterns
208
+ tear_down_validation_environment
209
+ end
210
+ end
211
+
212
+ attr_accessor :consumer
213
+
214
+ def setup_validation_environment
215
+ validation_dir.rmtree if validation_dir.exist?
216
+ validation_dir.mkpath
217
+ @original_config = Config.instance.clone
218
+ config.installation_root = validation_dir
219
+ config.sandbox_root = validation_dir + 'Pods'
220
+ config.silent = !config.verbose
221
+ config.integrate_targets = false
222
+ config.generate_docs = false
223
+ config.skip_repo_update = true
224
+ end
225
+
226
+ def tear_down_validation_environment
227
+ validation_dir.rmtree unless no_clean
228
+ Config.instance = @original_config
229
+ end
230
+
231
+ # It creates a podfile in memory and builds a library containing the pod
232
+ # for all available platforms with xcodebuild.
233
+ #
234
+ def install_pod
235
+ podfile = podfile_from_spec(consumer.platform_name, spec.deployment_target(consumer.platform_name))
236
+ sandbox = Sandbox.new(config.sandbox_root)
237
+ installer = Installer.new(sandbox, podfile)
238
+ installer.install!
239
+
240
+ file_accessors = installer.libraries.first.file_accessors
241
+ @file_accessor = file_accessors.find { |accessor| accessor.spec == spec }
242
+ config.silent
243
+ end
244
+
245
+ # Performs platform specific analysis. It requires to download the source
246
+ # at each iteration
247
+ #
248
+ # @note Xcode warnings are treaded as notes because the spec maintainer
249
+ # might not be the author of the library
250
+ #
251
+ # @return [void]
252
+ #
253
+ def build_pod
254
+ if `which xcodebuild`.strip.empty?
255
+ UI.warn "Skipping compilation with `xcodebuild' because it can't be found.\n".yellow
256
+ else
257
+ UI.message "\nBuilding with xcodebuild.\n".yellow do
258
+ output = Dir.chdir(config.sandbox_root) { xcodebuild }
259
+ UI.puts output
260
+ parsed_output = parse_xcodebuild_output(output)
261
+ parsed_output.each do |message|
262
+ if message.include?('error: ')
263
+ error "[xcodebuild] #{message}"
264
+ else
265
+ note "[xcodebuild] #{message}"
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ # It checks that every file pattern specified in a spec yields
273
+ # at least one file. It requires the pods to be already present
274
+ # in the current working directory under Pods/spec.name.
275
+ #
276
+ # @return [void]
277
+ #
278
+ def check_file_patterns
279
+ [:source_files, :resources, :preserve_paths].each do |attr_name|
280
+ # file_attr = Specification::DSL.attributes.values.find{|attr| attr.name == attr_name }
281
+ if !file_accessor.spec_consumer.send(attr_name).empty? && file_accessor.send(attr_name).empty?
282
+ error "The `#{attr_name}` pattern did not match any file."
283
+ end
284
+ end
285
+
286
+ unless file_accessor.license || spec.license && ( spec.license[:type] == 'Public Domain' || spec.license[:text] )
287
+ warning "Unable to find a license file"
288
+ end
289
+ end
290
+
291
+ #-------------------------------------------------------------------------#
292
+
293
+ private
294
+
295
+ # !@group Result Helpers
296
+
297
+ def error(message)
298
+ add_result(:error, message)
299
+ end
300
+
301
+ def warning(message)
302
+ add_result(:warning, message)
303
+ end
304
+
305
+ def note(message)
306
+ add_result(:note, message)
307
+ end
308
+
309
+ def add_result(type, message)
310
+ result = results.find { |r| r.type == type && r.message == message }
311
+ unless result
312
+ result = Specification::Linter::Result.new(type, message)
313
+ results << result
314
+ end
315
+ result.platforms << consumer.platform_name if consumer
316
+ end
317
+
318
+ #-------------------------------------------------------------------------#
319
+
320
+ private
321
+
322
+ # !@group Helpers
323
+
324
+ # @return [Podfile] a podfile that requires the specification on the
325
+ # current platform.
326
+ #
327
+ # @note The generated podfile takes into account whether the linter is
328
+ # in local mode.
329
+ #
330
+ def podfile_from_spec(platform_name, deployment_target)
331
+ name = spec.name
332
+ podspec = file.realpath
333
+ local = local?
334
+ podfile = Pod::Podfile.new do
335
+ platform(platform_name, deployment_target)
336
+ if (local)
337
+ pod name, :local => podspec.dirname.to_s
338
+ else
339
+ pod name, :podspec => podspec.to_s
340
+ end
341
+ end
342
+ podfile
343
+ end
344
+
345
+ # Parse the xcode build output to identify the lines which are relevant
346
+ # to the linter.
347
+ #
348
+ # @param [String] output the output generated by the xcodebuild tool.
349
+ #
350
+ # @note The indentation and the temporary path is stripped form the
351
+ # lines.
352
+ #
353
+ # @return [Array<String>] the lines that are relevant to the linter.
354
+ #
355
+ def parse_xcodebuild_output(output)
356
+ lines = output.split("\n")
357
+ selected_lines = lines.select do |l|
358
+ l.include?('error: ') &&
359
+ (l !~ /errors? generated\./) && (l !~ /error: \(null\)/) ||
360
+ l.include?('warning: ') && (l !~ /warnings? generated\./) ||
361
+ l.include?('note: ') && (l !~ /expanded from macro/)
362
+ end
363
+ selected_lines.map do |l|
364
+ new = l.gsub(/\/tmp\/CocoaPods\/Lint\/Pods\//,'')
365
+ new.gsub!(/^ */,' ')
366
+ end
367
+ end
368
+
369
+ # @return [String] Executes xcodebuild in the current working directory and
370
+ # returns its output (bot STDOUT and STDERR).
371
+ #
372
+ def xcodebuild
373
+ `xcodebuild clean build 2>&1`
374
+ end
375
+
376
+ #-------------------------------------------------------------------------#
377
+
378
+ end
379
+ end