xcocoapods 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6303 -0
  3. data/LICENSE +28 -0
  4. data/README.md +80 -0
  5. data/bin/pod +56 -0
  6. data/bin/sandbox-pod +168 -0
  7. data/lib/cocoapods.rb +73 -0
  8. data/lib/cocoapods/command.rb +175 -0
  9. data/lib/cocoapods/command/cache.rb +28 -0
  10. data/lib/cocoapods/command/cache/clean.rb +90 -0
  11. data/lib/cocoapods/command/cache/list.rb +69 -0
  12. data/lib/cocoapods/command/env.rb +66 -0
  13. data/lib/cocoapods/command/init.rb +128 -0
  14. data/lib/cocoapods/command/install.rb +45 -0
  15. data/lib/cocoapods/command/ipc.rb +19 -0
  16. data/lib/cocoapods/command/ipc/list.rb +40 -0
  17. data/lib/cocoapods/command/ipc/podfile.rb +31 -0
  18. data/lib/cocoapods/command/ipc/podfile_json.rb +30 -0
  19. data/lib/cocoapods/command/ipc/repl.rb +51 -0
  20. data/lib/cocoapods/command/ipc/spec.rb +29 -0
  21. data/lib/cocoapods/command/ipc/update_search_index.rb +24 -0
  22. data/lib/cocoapods/command/lib.rb +11 -0
  23. data/lib/cocoapods/command/lib/create.rb +105 -0
  24. data/lib/cocoapods/command/lib/lint.rb +121 -0
  25. data/lib/cocoapods/command/list.rb +39 -0
  26. data/lib/cocoapods/command/options/project_directory.rb +36 -0
  27. data/lib/cocoapods/command/options/repo_update.rb +34 -0
  28. data/lib/cocoapods/command/outdated.rb +140 -0
  29. data/lib/cocoapods/command/repo.rb +29 -0
  30. data/lib/cocoapods/command/repo/add.rb +103 -0
  31. data/lib/cocoapods/command/repo/lint.rb +82 -0
  32. data/lib/cocoapods/command/repo/list.rb +93 -0
  33. data/lib/cocoapods/command/repo/push.rb +281 -0
  34. data/lib/cocoapods/command/repo/remove.rb +36 -0
  35. data/lib/cocoapods/command/repo/update.rb +28 -0
  36. data/lib/cocoapods/command/setup.rb +103 -0
  37. data/lib/cocoapods/command/spec.rb +112 -0
  38. data/lib/cocoapods/command/spec/cat.rb +51 -0
  39. data/lib/cocoapods/command/spec/create.rb +283 -0
  40. data/lib/cocoapods/command/spec/edit.rb +87 -0
  41. data/lib/cocoapods/command/spec/env_spec.rb +53 -0
  42. data/lib/cocoapods/command/spec/lint.rb +137 -0
  43. data/lib/cocoapods/command/spec/which.rb +43 -0
  44. data/lib/cocoapods/command/update.rb +101 -0
  45. data/lib/cocoapods/config.rb +347 -0
  46. data/lib/cocoapods/core_overrides.rb +1 -0
  47. data/lib/cocoapods/downloader.rb +190 -0
  48. data/lib/cocoapods/downloader/cache.rb +233 -0
  49. data/lib/cocoapods/downloader/request.rb +86 -0
  50. data/lib/cocoapods/downloader/response.rb +16 -0
  51. data/lib/cocoapods/executable.rb +222 -0
  52. data/lib/cocoapods/external_sources.rb +57 -0
  53. data/lib/cocoapods/external_sources/abstract_external_source.rb +205 -0
  54. data/lib/cocoapods/external_sources/downloader_source.rb +30 -0
  55. data/lib/cocoapods/external_sources/path_source.rb +55 -0
  56. data/lib/cocoapods/external_sources/podspec_source.rb +54 -0
  57. data/lib/cocoapods/gem_version.rb +5 -0
  58. data/lib/cocoapods/generator/acknowledgements.rb +107 -0
  59. data/lib/cocoapods/generator/acknowledgements/markdown.rb +44 -0
  60. data/lib/cocoapods/generator/acknowledgements/plist.rb +94 -0
  61. data/lib/cocoapods/generator/app_target_helper.rb +244 -0
  62. data/lib/cocoapods/generator/bridge_support.rb +22 -0
  63. data/lib/cocoapods/generator/constant.rb +19 -0
  64. data/lib/cocoapods/generator/copy_resources_script.rb +230 -0
  65. data/lib/cocoapods/generator/dummy_source.rb +31 -0
  66. data/lib/cocoapods/generator/embed_frameworks_script.rb +215 -0
  67. data/lib/cocoapods/generator/header.rb +103 -0
  68. data/lib/cocoapods/generator/info_plist_file.rb +116 -0
  69. data/lib/cocoapods/generator/module_map.rb +99 -0
  70. data/lib/cocoapods/generator/prefix_header.rb +60 -0
  71. data/lib/cocoapods/generator/umbrella_header.rb +46 -0
  72. data/lib/cocoapods/hooks_manager.rb +132 -0
  73. data/lib/cocoapods/installer.rb +703 -0
  74. data/lib/cocoapods/installer/analyzer.rb +972 -0
  75. data/lib/cocoapods/installer/analyzer/analysis_result.rb +87 -0
  76. data/lib/cocoapods/installer/analyzer/locking_dependency_analyzer.rb +98 -0
  77. data/lib/cocoapods/installer/analyzer/pod_variant.rb +67 -0
  78. data/lib/cocoapods/installer/analyzer/pod_variant_set.rb +157 -0
  79. data/lib/cocoapods/installer/analyzer/podfile_dependency_cache.rb +54 -0
  80. data/lib/cocoapods/installer/analyzer/sandbox_analyzer.rb +240 -0
  81. data/lib/cocoapods/installer/analyzer/specs_state.rb +84 -0
  82. data/lib/cocoapods/installer/analyzer/target_inspection_result.rb +53 -0
  83. data/lib/cocoapods/installer/analyzer/target_inspector.rb +260 -0
  84. data/lib/cocoapods/installer/installation_options.rb +158 -0
  85. data/lib/cocoapods/installer/pod_source_installer.rb +202 -0
  86. data/lib/cocoapods/installer/pod_source_preparer.rb +77 -0
  87. data/lib/cocoapods/installer/podfile_validator.rb +139 -0
  88. data/lib/cocoapods/installer/post_install_hooks_context.rb +132 -0
  89. data/lib/cocoapods/installer/pre_install_hooks_context.rb +51 -0
  90. data/lib/cocoapods/installer/source_provider_hooks_context.rb +34 -0
  91. data/lib/cocoapods/installer/user_project_integrator.rb +250 -0
  92. data/lib/cocoapods/installer/user_project_integrator/target_integrator.rb +463 -0
  93. data/lib/cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb +146 -0
  94. data/lib/cocoapods/installer/xcode.rb +8 -0
  95. data/lib/cocoapods/installer/xcode/pods_project_generator.rb +416 -0
  96. data/lib/cocoapods/installer/xcode/pods_project_generator/aggregate_target_installer.rb +181 -0
  97. data/lib/cocoapods/installer/xcode/pods_project_generator/app_host_installer.rb +84 -0
  98. data/lib/cocoapods/installer/xcode/pods_project_generator/file_references_installer.rb +334 -0
  99. data/lib/cocoapods/installer/xcode/pods_project_generator/pod_target_installer.rb +777 -0
  100. data/lib/cocoapods/installer/xcode/pods_project_generator/pod_target_integrator.rb +116 -0
  101. data/lib/cocoapods/installer/xcode/pods_project_generator/target_installation_result.rb +86 -0
  102. data/lib/cocoapods/installer/xcode/pods_project_generator/target_installer.rb +256 -0
  103. data/lib/cocoapods/installer/xcode/pods_project_generator/target_installer_helper.rb +68 -0
  104. data/lib/cocoapods/installer/xcode/target_validator.rb +147 -0
  105. data/lib/cocoapods/open-uri.rb +33 -0
  106. data/lib/cocoapods/project.rb +414 -0
  107. data/lib/cocoapods/resolver.rb +585 -0
  108. data/lib/cocoapods/resolver/lazy_specification.rb +79 -0
  109. data/lib/cocoapods/sandbox.rb +404 -0
  110. data/lib/cocoapods/sandbox/file_accessor.rb +444 -0
  111. data/lib/cocoapods/sandbox/headers_store.rb +146 -0
  112. data/lib/cocoapods/sandbox/path_list.rb +220 -0
  113. data/lib/cocoapods/sandbox/pod_dir_cleaner.rb +85 -0
  114. data/lib/cocoapods/sandbox/podspec_finder.rb +23 -0
  115. data/lib/cocoapods/sources_manager.rb +157 -0
  116. data/lib/cocoapods/target.rb +261 -0
  117. data/lib/cocoapods/target/aggregate_target.rb +338 -0
  118. data/lib/cocoapods/target/build_settings.rb +1075 -0
  119. data/lib/cocoapods/target/pod_target.rb +559 -0
  120. data/lib/cocoapods/user_interface.rb +459 -0
  121. data/lib/cocoapods/user_interface/error_report.rb +187 -0
  122. data/lib/cocoapods/user_interface/inspector_reporter.rb +109 -0
  123. data/lib/cocoapods/validator.rb +981 -0
  124. metadata +533 -0
@@ -0,0 +1,187 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rbconfig'
4
+ require 'cgi'
5
+ require 'gh_inspector'
6
+
7
+ module Pod
8
+ module UserInterface
9
+ module ErrorReport
10
+ class << self
11
+ def report(exception)
12
+ <<-EOS
13
+
14
+ #{'――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――'.reversed}
15
+
16
+ ### Command
17
+
18
+ ```
19
+ #{original_command}
20
+ ```
21
+
22
+ #{report_instructions}
23
+
24
+ #{stack}
25
+ ### Plugins
26
+
27
+ ```
28
+ #{plugins_string}
29
+ ```
30
+ #{markdown_podfile}
31
+ ### Error
32
+
33
+ ```
34
+ #{exception.class} - #{exception.message}
35
+ #{exception.backtrace.join("\n") if exception.backtrace}
36
+ ```
37
+
38
+ #{'――― TEMPLATE END ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――'.reversed}
39
+
40
+ #{'[!] Oh no, an error occurred.'.red}
41
+ #{error_from_podfile(exception)}
42
+ #{'Search for existing GitHub issues similar to yours:'.yellow}
43
+ #{issues_url(exception)}
44
+
45
+ #{'If none exists, create a ticket, with the template displayed above, on:'.yellow}
46
+ https://github.com/CocoaPods/CocoaPods/issues/new
47
+
48
+ #{'Be sure to first read the contributing guide for details on how to properly submit a ticket:'.yellow}
49
+ https://github.com/CocoaPods/CocoaPods/blob/master/CONTRIBUTING.md
50
+
51
+ Don't forget to anonymize any private data!
52
+
53
+ EOS
54
+ end
55
+
56
+ def report_instructions
57
+ <<-EOS
58
+ ### Report
59
+
60
+ * What did you do?
61
+
62
+ * What did you expect to happen?
63
+
64
+ * What happened instead?
65
+ EOS
66
+ end
67
+
68
+ def stack
69
+ parts = {
70
+ 'CocoaPods' => Pod::VERSION,
71
+ 'Ruby' => RUBY_DESCRIPTION,
72
+ 'RubyGems' => Gem::VERSION,
73
+ 'Host' => host_information,
74
+ 'Xcode' => xcode_information,
75
+ 'Git' => git_information,
76
+ 'Ruby lib dir' => RbConfig::CONFIG['libdir'],
77
+ 'Repositories' => repo_information,
78
+ }
79
+ justification = parts.keys.map(&:size).max
80
+
81
+ str = <<-EOS
82
+ ### Stack
83
+
84
+ ```
85
+ EOS
86
+ parts.each do |name, value|
87
+ str << name.rjust(justification)
88
+ str << ' : '
89
+ str << Array(value).join("\n" << (' ' * (justification + 3)))
90
+ str << "\n"
91
+ end
92
+
93
+ str << "```\n"
94
+ end
95
+
96
+ def plugins_string
97
+ plugins = installed_plugins
98
+ max_name_length = plugins.keys.map(&:length).max
99
+ plugins.map do |name, version|
100
+ "#{name.ljust(max_name_length)} : #{version}"
101
+ end.sort.join("\n")
102
+ end
103
+
104
+ def markdown_podfile
105
+ return '' unless Config.instance.podfile_path && Config.instance.podfile_path.exist?
106
+ <<-EOS
107
+
108
+ ### Podfile
109
+
110
+ ```ruby
111
+ #{Config.instance.podfile_path.read.strip}
112
+ ```
113
+ EOS
114
+ end
115
+
116
+ def search_for_exceptions(exception)
117
+ inspector = GhInspector::Inspector.new 'cocoapods', 'cocoapods'
118
+ message_delegate = UserInterface::InspectorReporter.new
119
+ inspector.search_exception exception, message_delegate
120
+ end
121
+
122
+ private
123
+
124
+ def `(other)
125
+ super
126
+ rescue Errno::ENOENT => e
127
+ "Unable to find an executable (#{e})"
128
+ end
129
+
130
+ def pathless_exception_message(message)
131
+ message.gsub(/- \(.*\):/, '-')
132
+ end
133
+
134
+ def error_from_podfile(error)
135
+ if error.message =~ /Podfile:(\d*)/
136
+ "\nIt appears to have originated from your Podfile at line #{Regexp.last_match[1]}.\n"
137
+ end
138
+ end
139
+
140
+ def remove_color(string)
141
+ string.gsub(/\e\[(\d+)m/, '')
142
+ end
143
+
144
+ def issues_url(exception)
145
+ message = remove_color(pathless_exception_message(exception.message))
146
+ 'https://github.com/CocoaPods/CocoaPods/search?q=' \
147
+ "#{CGI.escape(message)}&type=Issues"
148
+ end
149
+
150
+ def host_information
151
+ product, version, build = `sw_vers`.strip.split("\n").map { |line| line.split(':').last.strip }
152
+ "#{product} #{version} (#{build})"
153
+ end
154
+
155
+ def xcode_information
156
+ version, build = `xcodebuild -version`.strip.split("\n").map { |line| line.split(' ').last }
157
+ "#{version} (#{build})"
158
+ end
159
+
160
+ def git_information
161
+ `git --version`.strip.split("\n").first
162
+ end
163
+
164
+ def installed_plugins
165
+ CLAide::Command::PluginManager.specifications.
166
+ reduce({}) { |hash, s| hash.tap { |h| h[s.name] = s.version.to_s } }
167
+ end
168
+
169
+ def repo_information
170
+ Config.instance.sources_manager.all.map do |source|
171
+ next unless source.type == 'file system'
172
+ repo = source.repo
173
+ Dir.chdir(repo) do
174
+ url = `git config --get remote.origin.url 2>&1`.strip
175
+ sha = `git rev-parse HEAD 2>&1`.strip
176
+ "#{repo.basename} - #{url} @ #{sha}"
177
+ end
178
+ end
179
+ end
180
+
181
+ def original_command
182
+ "#{$PROGRAM_NAME} #{ARGV.join(' ')}"
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,109 @@
1
+ require 'uri'
2
+
3
+ module Pod
4
+ module UserInterface
5
+ # Redirects GH-issues delegate callbacks to CocoaPods UI methods.
6
+ #
7
+ class InspectorReporter
8
+ # Called just as the investigation has begun.
9
+ # Lets the user know that it's looking for an issue.
10
+ #
11
+ # @param [query] String unused
12
+ #
13
+ # @param [GhInspector::Inspector] inspector
14
+ # The current inspector
15
+ #
16
+ # @return [void]
17
+ #
18
+ def inspector_started_query(_, inspector)
19
+ UI.puts "Looking for related issues on #{inspector.repo_owner}/#{inspector.repo_name}..."
20
+ end
21
+
22
+ # Called once the inspector has received a report with more than one issue,
23
+ # showing the top 3 issues, and offering a link to see more.
24
+ #
25
+ # @param [GhInspector::InspectionReport] report
26
+ # Report a list of the issues
27
+ #
28
+ # @param [GhInspector::Inspector] inspector
29
+ # The current inspector
30
+ #
31
+ # @return [void]
32
+ #
33
+ def inspector_successfully_received_report(report, _)
34
+ report.issues[0..2].each { |issue| print_issue_full(issue) }
35
+
36
+ if report.issues.count > 3
37
+ UI.puts "and #{report.total_results - 3} more at:"
38
+ UI.puts report.url
39
+ end
40
+ end
41
+
42
+ # Called once the report has been received, but when there are no issues found.
43
+ #
44
+ # @param [GhInspector::InspectionReport] report
45
+ # An empty report
46
+ #
47
+ # @param [GhInspector::Inspector] inspector
48
+ # The current inspector
49
+ #
50
+ # @return [void]
51
+ #
52
+ def inspector_received_empty_report(_, inspector)
53
+ UI.puts 'Found no similar issues. To create a new issue, please visit:'
54
+ UI.puts "https://github.com/#{inspector.repo_owner}/#{inspector.repo_name}/issues/new"
55
+ end
56
+
57
+ # Called when there have been networking issues in creating the report.
58
+ #
59
+ # @param [Error] error
60
+ # The error returned during networking
61
+ #
62
+ # @param [String] query
63
+ # The original search query
64
+ #
65
+ # @param [GhInspector::Inspector] inspector
66
+ # The current inspector
67
+ #
68
+ # @return [void]
69
+ #
70
+ def inspector_could_not_create_report(error, query, inspector)
71
+ safe_query = URI.escape query
72
+ UI.puts 'Could not access the GitHub API, you may have better luck via the website.'
73
+ UI.puts "https://github.com/#{inspector.repo_owner}/#{inspector.repo_name}/search?q=#{safe_query}&type=Issues&utf8=✓"
74
+ UI.puts "Error: #{error.name}"
75
+ end
76
+
77
+ private
78
+
79
+ def print_issue_full(issue)
80
+ safe_url = URI.escape issue.html_url
81
+ UI.puts " - #{issue.title}"
82
+ UI.puts " #{safe_url} [#{issue.state}] [#{issue.comments} comment#{issue.comments == 1 ? '' : 's'}]"
83
+ UI.puts " #{pretty_date(issue.updated_at)}"
84
+ UI.puts ''
85
+ end
86
+
87
+ # Taken from http://stackoverflow.com/questions/195740/how-do-you-do-relative-time-in-rails
88
+ def pretty_date(date_string)
89
+ date = Time.parse(date_string)
90
+ a = (Time.now - date).to_i
91
+
92
+ case a
93
+ when 0 then 'just now'
94
+ when 1 then 'a second ago'
95
+ when 2..59 then a.to_s + ' seconds ago'
96
+ when 60..119 then 'a minute ago' # 120 = 2 minutes
97
+ when 120..3540 then (a / 60).to_i.to_s + ' minutes ago'
98
+ when 3541..7100 then 'an hour ago' # 3600 = 1 hour
99
+ when 7101..82_800 then ((a + 99) / 3600).to_i.to_s + ' hours ago'
100
+ when 82_801..172_000 then 'a day ago' # 86400 = 1 day
101
+ when 172_001..518_400 then ((a + 800) / (60 * 60 * 24)).to_i.to_s + ' days ago'
102
+ when 518_400..1_036_800 then 'a week ago'
103
+ when 1_036_801..4_147_204 then ((a + 180_000) / (60 * 60 * 24 * 7)).to_i.to_s + ' weeks ago'
104
+ else date.strftime('%d %b %Y')
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,981 @@
1
+ require 'active_support/core_ext/array'
2
+ require 'active_support/core_ext/string/inflections'
3
+
4
+ module Pod
5
+ # Validates a Specification.
6
+ #
7
+ # Extends the Linter from the Core to add additional which require the
8
+ # LocalPod and the Installer.
9
+ #
10
+ # In detail it checks that the file patterns defined by the user match
11
+ # actually do match at least a file and that the Pod builds, by installing
12
+ # it without integration and building the project with xcodebuild.
13
+ #
14
+ class Validator
15
+ include Config::Mixin
16
+
17
+ # The default version of Swift to use when linting pods
18
+ #
19
+ DEFAULT_SWIFT_VERSION = '3.2'.freeze
20
+
21
+ # The valid platforms for linting
22
+ #
23
+ VALID_PLATFORMS = Platform.all.freeze
24
+
25
+ # @return [Specification::Linter] the linter instance from CocoaPods
26
+ # Core.
27
+ #
28
+ attr_reader :linter
29
+
30
+ # Initialize a new instance
31
+ #
32
+ # @param [Specification, Pathname, String] spec_or_path
33
+ # the Specification or the path of the `podspec` file to lint.
34
+ #
35
+ # @param [Array<String>] source_urls
36
+ # the Source URLs to use in creating a {Podfile}.
37
+ #
38
+ # @param. [Array<String>] platforms
39
+ # the platforms to lint.
40
+ #
41
+ def initialize(spec_or_path, source_urls, platforms = [])
42
+ @linter = Specification::Linter.new(spec_or_path)
43
+ @source_urls = if @linter.spec && @linter.spec.dependencies.empty? && @linter.spec.recursive_subspecs.all? { |s| s.dependencies.empty? }
44
+ []
45
+ else
46
+ source_urls.map { |url| config.sources_manager.source_with_name_or_url(url) }.map(&:url)
47
+ end
48
+
49
+ @platforms = platforms.map do |platform|
50
+ result = case platform.to_s.downcase
51
+ # Platform doesn't recognize 'macos' as being the same as 'osx' when initializing
52
+ when 'macos' then Platform.macos
53
+ else Platform.new(platform, nil)
54
+ end
55
+ unless valid_platform?(result)
56
+ raise Informative, "Unrecognized platform `#{platform}`. Valid platforms: #{VALID_PLATFORMS.join(', ')}"
57
+ end
58
+ result
59
+ end
60
+ end
61
+
62
+ #-------------------------------------------------------------------------#
63
+
64
+ # @return [Specification] the specification to lint.
65
+ #
66
+ def spec
67
+ @linter.spec
68
+ end
69
+
70
+ # @return [Pathname] the path of the `podspec` file where {#spec} is
71
+ # defined.
72
+ #
73
+ def file
74
+ @linter.file
75
+ end
76
+
77
+ # Returns a list of platforms to lint for a given Specification
78
+ #
79
+ # @param [Specification] spec
80
+ # The specification to lint
81
+ #
82
+ # @return [Array<Platform>] platforms to lint for the given specification
83
+ #
84
+ def platforms_to_lint(spec)
85
+ return spec.available_platforms if @platforms.empty?
86
+
87
+ # Validate that the platforms specified are actually supported by the spec
88
+ results = @platforms.map do |platform|
89
+ matching_platform = spec.available_platforms.find { |p| p.name == platform.name }
90
+ unless matching_platform
91
+ raise Informative, "Platform `#{platform}` is not supported by specification `#{spec}`."
92
+ end
93
+ matching_platform
94
+ end.uniq
95
+
96
+ results
97
+ end
98
+
99
+ # @return [Sandbox::FileAccessor] the file accessor for the spec.
100
+ #
101
+ attr_accessor :file_accessor
102
+
103
+ #-------------------------------------------------------------------------#
104
+
105
+ # Lints the specification adding a {Result} for any
106
+ # failed check to the {#results} list.
107
+ #
108
+ # @note This method shows immediately which pod is being processed and
109
+ # overrides the printed line once the result is known.
110
+ #
111
+ # @return [Bool] whether the specification passed validation.
112
+ #
113
+ def validate
114
+ @results = []
115
+
116
+ # Replace default spec with a subspec if asked for
117
+ a_spec = spec
118
+ if spec && @only_subspec
119
+ subspec_name = @only_subspec.start_with?(spec.root.name) ? @only_subspec : "#{spec.root.name}/#{@only_subspec}"
120
+ a_spec = spec.subspec_by_name(subspec_name, true, true)
121
+ @subspec_name = a_spec.name
122
+ end
123
+
124
+ UI.print " -> #{a_spec ? a_spec.name : file.basename}\r" unless config.silent?
125
+ $stdout.flush
126
+
127
+ perform_linting
128
+ perform_extensive_analysis(a_spec) if a_spec && !quick
129
+
130
+ UI.puts ' -> '.send(result_color) << (a_spec ? a_spec.to_s : file.basename.to_s)
131
+ print_results
132
+ validated?
133
+ end
134
+
135
+ # Prints the result of the validation to the user.
136
+ #
137
+ # @return [void]
138
+ #
139
+ def print_results
140
+ UI.puts results_message
141
+ end
142
+
143
+ def results_message
144
+ message = ''
145
+ results.each do |result|
146
+ if result.platforms == [:ios]
147
+ platform_message = '[iOS] '
148
+ elsif result.platforms == [:osx]
149
+ platform_message = '[OSX] '
150
+ elsif result.platforms == [:watchos]
151
+ platform_message = '[watchOS] '
152
+ elsif result.platforms == [:tvos]
153
+ platform_message = '[tvOS] '
154
+ end
155
+
156
+ subspecs_message = ''
157
+ if result.is_a?(Result)
158
+ subspecs = result.subspecs.uniq
159
+ if subspecs.count > 2
160
+ subspecs_message = '[' + subspecs[0..2].join(', ') + ', and more...] '
161
+ elsif subspecs.count > 0
162
+ subspecs_message = '[' + subspecs.join(',') + '] '
163
+ end
164
+ end
165
+
166
+ case result.type
167
+ when :error then type = 'ERROR'
168
+ when :warning then type = 'WARN'
169
+ when :note then type = 'NOTE'
170
+ else raise "#{result.type}" end
171
+ message << " - #{type.ljust(5)} | #{platform_message}#{subspecs_message}#{result.attribute_name}: #{result.message}\n"
172
+ end
173
+ message << "\n"
174
+ end
175
+
176
+ def failure_reason
177
+ results_by_type = results.group_by(&:type)
178
+ results_by_type.default = []
179
+ return nil if validated?
180
+ reasons = []
181
+ if (size = results_by_type[:error].size) && size > 0
182
+ reasons << "#{size} #{'error'.pluralize(size)}"
183
+ end
184
+ if !allow_warnings && (size = results_by_type[:warning].size) && size > 0
185
+ reason = "#{size} #{'warning'.pluralize(size)}"
186
+ pronoun = size == 1 ? 'it' : 'them'
187
+ reason << " (but you can use `--allow-warnings` to ignore #{pronoun})" if reasons.empty?
188
+ reasons << reason
189
+ end
190
+ if results.all?(&:public_only)
191
+ reasons << 'all results apply only to public specs, but you can use ' \
192
+ '`--private` to ignore them if linting the specification for a private pod'
193
+ end
194
+
195
+ reasons.to_sentence
196
+ end
197
+
198
+ #-------------------------------------------------------------------------#
199
+
200
+ #  @!group Configuration
201
+
202
+ # @return [Bool] whether the validation should skip the checks that
203
+ # requires the download of the library.
204
+ #
205
+ attr_accessor :quick
206
+
207
+ # @return [Bool] whether the linter should not clean up temporary files
208
+ # for inspection.
209
+ #
210
+ attr_accessor :no_clean
211
+
212
+ # @return [Bool] whether the linter should fail as soon as the first build
213
+ # variant causes an error. Helpful for i.e. multi-platforms specs,
214
+ # specs with subspecs.
215
+ #
216
+ attr_accessor :fail_fast
217
+
218
+ # @return [Bool] whether the validation should be performed against the root of
219
+ # the podspec instead to its original source.
220
+ #
221
+ # @note Uses the `:path` option of the Podfile.
222
+ #
223
+ attr_accessor :local
224
+ alias_method :local?, :local
225
+
226
+ # @return [Bool] Whether the validator should fail on warnings, or only on errors.
227
+ #
228
+ attr_accessor :allow_warnings
229
+
230
+ # @return [String] name of the subspec to check, if nil all subspecs are checked.
231
+ #
232
+ attr_accessor :only_subspec
233
+
234
+ # @return [Bool] Whether the validator should validate all subspecs.
235
+ #
236
+ attr_accessor :no_subspecs
237
+
238
+ # @return [Bool] Whether the validator should skip building and running tests.
239
+ #
240
+ attr_accessor :skip_tests
241
+
242
+ # @return [Bool] Whether frameworks should be used for the installation.
243
+ #
244
+ attr_accessor :use_frameworks
245
+
246
+ # @return [Boolean] Whether attributes that affect only public sources
247
+ # Bool be skipped.
248
+ #
249
+ attr_accessor :ignore_public_only_results
250
+
251
+ attr_accessor :skip_import_validation
252
+ alias_method :skip_import_validation?, :skip_import_validation
253
+
254
+ #-------------------------------------------------------------------------#
255
+
256
+ # !@group Lint results
257
+
258
+ #
259
+ #
260
+ attr_reader :results
261
+
262
+ # @return [Boolean]
263
+ #
264
+ def validated?
265
+ result_type != :error && (result_type != :warning || allow_warnings)
266
+ end
267
+
268
+ # @return [Symbol] The type, which should been used to display the result.
269
+ # One of: `:error`, `:warning`, `:note`.
270
+ #
271
+ def result_type
272
+ applicable_results = results
273
+ applicable_results = applicable_results.reject(&:public_only?) if ignore_public_only_results
274
+ types = applicable_results.map(&:type).uniq
275
+ if types.include?(:error) then :error
276
+ elsif types.include?(:warning) then :warning
277
+ else :note
278
+ end
279
+ end
280
+
281
+ # @return [Symbol] The color, which should been used to display the result.
282
+ # One of: `:green`, `:yellow`, `:red`.
283
+ #
284
+ def result_color
285
+ case result_type
286
+ when :error then :red
287
+ when :warning then :yellow
288
+ else :green end
289
+ end
290
+
291
+ # @return [Pathname] the temporary directory used by the linter.
292
+ #
293
+ def validation_dir
294
+ @validation_dir ||= Pathname(Dir.mktmpdir(['CocoaPods-Lint-', "-#{spec.name}"]))
295
+ end
296
+
297
+ # @return [String] the SWIFT_VERSION to use for validation.
298
+ #
299
+ def swift_version
300
+ return @swift_version unless @swift_version.nil?
301
+ if (version = spec.swift_version) || (version = dot_swift_version)
302
+ @swift_version = version.to_s
303
+ else
304
+ DEFAULT_SWIFT_VERSION
305
+ end
306
+ end
307
+
308
+ # Set the SWIFT_VERSION that should be used to validate the pod.
309
+ #
310
+ attr_writer :swift_version
311
+
312
+ # @return [String] the SWIFT_VERSION in the .swift-version file or nil.
313
+ #
314
+ def dot_swift_version
315
+ return unless file
316
+ swift_version_path = file.dirname + '.swift-version'
317
+ return unless swift_version_path.exist?
318
+ swift_version_path.read.strip
319
+ end
320
+
321
+ # @return [Boolean] Whether any of the pod targets part of this validator use Swift or not.
322
+ #
323
+ def uses_swift?
324
+ @installer.pod_targets.any?(&:uses_swift?)
325
+ end
326
+
327
+ #-------------------------------------------------------------------------#
328
+
329
+ private
330
+
331
+ # !@group Lint steps
332
+
333
+ #
334
+ #
335
+ def perform_linting
336
+ linter.lint
337
+ @results.concat(linter.results.to_a)
338
+ end
339
+
340
+ # Perform analysis for a given spec (or subspec)
341
+ #
342
+ def perform_extensive_analysis(spec)
343
+ if spec.test_specification?
344
+ error('spec', "Validating a test spec (`#{spec.name}`) is not supported.")
345
+ return false
346
+ end
347
+ validate_homepage(spec)
348
+ validate_screenshots(spec)
349
+ validate_social_media_url(spec)
350
+ validate_documentation_url(spec)
351
+ validate_source_url(spec)
352
+
353
+ platforms = platforms_to_lint(spec)
354
+
355
+ valid = platforms.send(fail_fast ? :all? : :each) do |platform|
356
+ UI.message "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed
357
+ @consumer = spec.consumer(platform)
358
+ setup_validation_environment
359
+ begin
360
+ create_app_project
361
+ download_pod
362
+ check_file_patterns
363
+ install_pod
364
+ validate_swift_version
365
+ add_app_project_import
366
+ validate_vendored_dynamic_frameworks
367
+ build_pod
368
+ test_pod unless skip_tests
369
+ ensure
370
+ tear_down_validation_environment
371
+ end
372
+ validated?
373
+ end
374
+ return false if fail_fast && !valid
375
+ perform_extensive_subspec_analysis(spec) unless @no_subspecs
376
+ rescue => e
377
+ message = e.to_s
378
+ message << "\n" << e.backtrace.join("\n") << "\n" if config.verbose?
379
+ error('unknown', "Encountered an unknown error (#{message}) during validation.")
380
+ false
381
+ end
382
+
383
+ # Recursively perform the extensive analysis on all subspecs
384
+ #
385
+ def perform_extensive_subspec_analysis(spec)
386
+ spec.subspecs.reject(&:test_specification?).send(fail_fast ? :all? : :each) do |subspec|
387
+ @subspec_name = subspec.name
388
+ perform_extensive_analysis(subspec)
389
+ end
390
+ end
391
+
392
+ attr_accessor :consumer
393
+ attr_accessor :subspec_name
394
+
395
+ # Performs validation of a URL
396
+ #
397
+ def validate_url(url)
398
+ resp = Pod::HTTP.validate_url(url)
399
+
400
+ if !resp
401
+ warning('url', "There was a problem validating the URL #{url}.", true)
402
+ elsif !resp.success?
403
+ warning('url', "The URL (#{url}) is not reachable.", true)
404
+ end
405
+
406
+ resp
407
+ end
408
+
409
+ # Performs validations related to the `homepage` attribute.
410
+ #
411
+ def validate_homepage(spec)
412
+ if spec.homepage
413
+ validate_url(spec.homepage)
414
+ end
415
+ end
416
+
417
+ # Performs validation related to the `screenshots` attribute.
418
+ #
419
+ def validate_screenshots(spec)
420
+ spec.screenshots.compact.each do |screenshot|
421
+ response = validate_url(screenshot)
422
+ if response && !(response.headers['content-type'] && response.headers['content-type'].first =~ /image\/.*/i)
423
+ warning('screenshot', "The screenshot #{screenshot} is not a valid image.")
424
+ end
425
+ end
426
+ end
427
+
428
+ # Performs validations related to the `social_media_url` attribute.
429
+ #
430
+ def validate_social_media_url(spec)
431
+ validate_url(spec.social_media_url) if spec.social_media_url
432
+ end
433
+
434
+ # Performs validations related to the `documentation_url` attribute.
435
+ #
436
+ def validate_documentation_url(spec)
437
+ validate_url(spec.documentation_url) if spec.documentation_url
438
+ end
439
+
440
+ # Performs validations related to the `source` -> `http` attribute (if exists)
441
+ #
442
+ def validate_source_url(spec)
443
+ return if spec.source.nil? || spec.source[:http].nil?
444
+ url = URI(spec.source[:http])
445
+ return if url.scheme == 'https' || url.scheme == 'file'
446
+ warning('http', "The URL (`#{url}`) doesn't use the encrypted HTTPs protocol. " \
447
+ 'It is crucial for Pods to be transferred over a secure protocol to protect your users from man-in-the-middle attacks. '\
448
+ 'This will be an error in future releases. Please update the URL to use https.')
449
+ end
450
+
451
+ # Performs validation for which version of Swift is used during validation.
452
+ #
453
+ # An error will be displayed if the user has provided a `swift_version` attribute within the podspec but is also
454
+ # using either `--swift-version` parameter or a `.swift-version with a different Swift version.
455
+ #
456
+ # The user will be warned that the default version of Swift was used if the following things are true:
457
+ # - The project uses Swift at all
458
+ # - The user did not supply a Swift version via a parameter
459
+ # - There is no `swift_version` attribute set within the specification
460
+ # - There is no `.swift-version` file present either.
461
+ #
462
+ def validate_swift_version
463
+ return unless uses_swift?
464
+ spec_swift_version = spec.swift_version
465
+ unless spec_swift_version.nil?
466
+ message = nil
467
+ if !dot_swift_version.nil? && dot_swift_version != spec_swift_version.to_s
468
+ message = "Specification `#{spec.name}` specifies an inconsistent `swift_version` (`#{spec_swift_version}`) compared to the one present in your `.swift-version` file (`#{dot_swift_version}`). " \
469
+ 'Please remove the `.swift-version` file which is now deprecated and only use the `swift_version` attribute within your podspec.'
470
+ elsif !@swift_version.nil? && @swift_version != spec_swift_version.to_s
471
+ message = "Specification `#{spec.name}` specifies an inconsistent `swift_version` (`#{spec_swift_version}`) compared to the one passed during lint (`#{@swift_version}`)."
472
+ end
473
+ unless message.nil?
474
+ error('swift', message)
475
+ return
476
+ end
477
+ end
478
+ if @swift_version.nil? && spec_swift_version.nil? && dot_swift_version.nil?
479
+ warning('swift',
480
+ 'The validator used ' \
481
+ "Swift #{DEFAULT_SWIFT_VERSION} by default because no Swift version was specified. " \
482
+ 'To specify a Swift version during validation, add the `swift_version` attribute in your podspec. ' \
483
+ 'Note that usage of the `--swift-version` parameter or a `.swift-version` file is now deprecated.')
484
+ end
485
+ end
486
+
487
+ def setup_validation_environment
488
+ validation_dir.rmtree if validation_dir.exist?
489
+ validation_dir.mkpath
490
+ @original_config = Config.instance.clone
491
+ config.installation_root = validation_dir
492
+ config.silent = !config.verbose
493
+ end
494
+
495
+ def tear_down_validation_environment
496
+ validation_dir.rmtree unless no_clean
497
+ Config.instance = @original_config
498
+ end
499
+
500
+ def deployment_target
501
+ deployment_target = spec.subspec_by_name(subspec_name).deployment_target(consumer.platform_name)
502
+ if consumer.platform_name == :ios && use_frameworks
503
+ minimum = Version.new('8.0')
504
+ deployment_target = [Version.new(deployment_target), minimum].max.to_s
505
+ end
506
+ deployment_target
507
+ end
508
+
509
+ def download_pod
510
+ podfile = podfile_from_spec(consumer.platform_name, deployment_target, use_frameworks, consumer.spec.test_specs.map(&:name))
511
+ sandbox = Sandbox.new(config.sandbox_root)
512
+ @installer = Installer.new(sandbox, podfile)
513
+ @installer.use_default_plugins = false
514
+ @installer.has_dependencies = !spec.dependencies.empty?
515
+ %i(prepare resolve_dependencies download_dependencies).each { |m| @installer.send(m) }
516
+ @file_accessor = @installer.pod_targets.flat_map(&:file_accessors).find { |fa| fa.spec.name == consumer.spec.name }
517
+ end
518
+
519
+ def create_app_project
520
+ app_project = Xcodeproj::Project.new(validation_dir + 'App.xcodeproj')
521
+ app_target = Pod::Generator::AppTargetHelper.add_app_target(app_project, consumer.platform_name, deployment_target)
522
+ Pod::Generator::AppTargetHelper.add_swift_version(app_target, swift_version)
523
+ app_project.save
524
+ app_project.recreate_user_schemes
525
+ end
526
+
527
+ def add_app_project_import
528
+ app_project = Xcodeproj::Project.open(validation_dir + 'App.xcodeproj')
529
+ app_target = app_project.targets.first
530
+ pod_target = @installer.pod_targets.find { |pt| pt.pod_name == spec.root.name }
531
+ Pod::Generator::AppTargetHelper.add_app_project_import(app_project, app_target, pod_target, consumer.platform_name)
532
+ Pod::Generator::AppTargetHelper.add_xctest_search_paths(app_target) if @installer.pod_targets.any? { |pt| pt.spec_consumers.any? { |c| c.frameworks.include?('XCTest') } }
533
+ Pod::Generator::AppTargetHelper.add_empty_swift_file(app_project, app_target) if @installer.pod_targets.any?(&:uses_swift?)
534
+ app_project.save
535
+ Xcodeproj::XCScheme.share_scheme(app_project.path, 'App')
536
+ # Share the pods xcscheme only if it exists. For pre-built vendored pods there is no xcscheme generated.
537
+ Xcodeproj::XCScheme.share_scheme(@installer.pods_project.path, pod_target.label) if shares_pod_target_xcscheme?(pod_target)
538
+ end
539
+
540
+ # It creates a podfile in memory and builds a library containing the pod
541
+ # for all available platforms with xcodebuild.
542
+ #
543
+ def install_pod
544
+ %i(validate_targets generate_pods_project integrate_user_project
545
+ perform_post_install_actions).each { |m| @installer.send(m) }
546
+
547
+ deployment_target = spec.subspec_by_name(subspec_name).deployment_target(consumer.platform_name)
548
+ configure_pod_targets(@installer.aggregate_targets, @installer.target_installation_results, deployment_target)
549
+ @installer.pods_project.save
550
+ end
551
+
552
+ def configure_pod_targets(targets, target_installation_results, deployment_target)
553
+ target_installation_results.first.values.each do |pod_target_installation_result|
554
+ pod_target = pod_target_installation_result.target
555
+ native_target = pod_target_installation_result.native_target
556
+ native_target.build_configuration_list.build_configurations.each do |build_configuration|
557
+ (build_configuration.build_settings['OTHER_CFLAGS'] ||= '$(inherited)') << ' -Wincomplete-umbrella'
558
+ build_configuration.build_settings['SWIFT_VERSION'] = (pod_target.swift_version || swift_version) if pod_target.uses_swift?
559
+ end
560
+ pod_target_installation_result.test_specs_by_native_target.each do |test_native_target, test_specs|
561
+ if pod_target.uses_swift_for_test_type?(test_specs.first.test_type)
562
+ test_native_target.build_configuration_list.build_configurations.each do |build_configuration|
563
+ build_configuration.build_settings['SWIFT_VERSION'] = swift_version
564
+ end
565
+ end
566
+ end
567
+ end
568
+ targets.each do |target|
569
+ if target.pod_targets.any?(&:uses_swift?) && consumer.platform_name == :ios &&
570
+ (deployment_target.nil? || Version.new(deployment_target).major < 8)
571
+ uses_xctest = target.spec_consumers.any? { |c| (c.frameworks + c.weak_frameworks).include? 'XCTest' }
572
+ error('swift', 'Swift support uses dynamic frameworks and is therefore only supported on iOS > 8.') unless uses_xctest
573
+ end
574
+ end
575
+ end
576
+
577
+ def validate_vendored_dynamic_frameworks
578
+ deployment_target = spec.subspec_by_name(subspec_name).deployment_target(consumer.platform_name)
579
+
580
+ unless file_accessor.nil?
581
+ dynamic_frameworks = file_accessor.vendored_dynamic_frameworks
582
+ dynamic_libraries = file_accessor.vendored_dynamic_libraries
583
+ if (dynamic_frameworks.count > 0 || dynamic_libraries.count > 0) && consumer.platform_name == :ios &&
584
+ (deployment_target.nil? || Version.new(deployment_target).major < 8)
585
+ error('dynamic', 'Dynamic frameworks and libraries are only supported on iOS 8.0 and onwards.')
586
+ end
587
+ end
588
+ end
589
+
590
+ # Performs platform specific analysis. It requires to download the source
591
+ # at each iteration
592
+ #
593
+ # @note Xcode warnings are treated as notes because the spec maintainer
594
+ # might not be the author of the library
595
+ #
596
+ # @return [void]
597
+ #
598
+ def build_pod
599
+ if !xcodebuild_available?
600
+ UI.warn "Skipping compilation with `xcodebuild` because it can't be found.\n".yellow
601
+ else
602
+ UI.message "\nBuilding with `xcodebuild`.\n".yellow do
603
+ scheme = if skip_import_validation?
604
+ pod_target = @installer.pod_targets.find { |pt| pt.pod_name == spec.root.name }
605
+ pod_target.label if pod_target.should_build?
606
+ else
607
+ 'App'
608
+ end
609
+ if scheme.nil?
610
+ UI.warn "Skipping compilation with `xcodebuild` because target contains no sources.\n".yellow
611
+ else
612
+ output = xcodebuild('build', scheme, 'Release')
613
+ parsed_output = parse_xcodebuild_output(output)
614
+ translate_output_to_linter_messages(parsed_output)
615
+ end
616
+ end
617
+ end
618
+ end
619
+
620
+ # Builds and runs all test sources associated with the current specification being validated.
621
+ #
622
+ # @note Xcode warnings are treated as notes because the spec maintainer
623
+ # might not be the author of the library
624
+ #
625
+ # @return [void]
626
+ #
627
+ def test_pod
628
+ if !xcodebuild_available?
629
+ UI.warn "Skipping test validation with `xcodebuild` because it can't be found.\n".yellow
630
+ else
631
+ UI.message "\nTesting with `xcodebuild`.\n".yellow do
632
+ pod_target = @installer.pod_targets.find { |pt| pt.pod_name == spec.root.name }
633
+ consumer.spec.test_specs.each do |test_spec|
634
+ scheme = @installer.target_installation_results.first[pod_target.name].native_target_for_spec(test_spec)
635
+ output = xcodebuild('test', scheme, 'Debug')
636
+ parsed_output = parse_xcodebuild_output(output)
637
+ translate_output_to_linter_messages(parsed_output)
638
+ end
639
+ end
640
+ end
641
+ end
642
+
643
+ def xcodebuild_available?
644
+ !Executable.which('xcodebuild').nil? && ENV['COCOAPODS_VALIDATOR_SKIP_XCODEBUILD'].nil?
645
+ end
646
+
647
+ FILE_PATTERNS = %i(source_files resources preserve_paths vendored_libraries
648
+ vendored_frameworks public_header_files preserve_paths
649
+ private_header_files resource_bundles).freeze
650
+
651
+ # It checks that every file pattern specified in a spec yields
652
+ # at least one file. It requires the pods to be already present
653
+ # in the current working directory under Pods/spec.name.
654
+ #
655
+ # @return [void]
656
+ #
657
+ def check_file_patterns
658
+ FILE_PATTERNS.each do |attr_name|
659
+ if respond_to?("_validate_#{attr_name}", true)
660
+ send("_validate_#{attr_name}")
661
+ else
662
+ validate_nonempty_patterns(attr_name, :error)
663
+ end
664
+ end
665
+
666
+ _validate_header_mappings_dir
667
+ if consumer.spec.root?
668
+ _validate_license
669
+ _validate_module_map
670
+ end
671
+ end
672
+
673
+ # Validates that the file patterns in `attr_name` match at least 1 file.
674
+ #
675
+ # @param [String,Symbol] attr_name the name of the attribute to check (ex. :public_header_files)
676
+ #
677
+ # @param [String,Symbol] message_type the type of message to send if the patterns are empty (ex. :error)
678
+ #
679
+ def validate_nonempty_patterns(attr_name, message_type)
680
+ return unless !file_accessor.spec_consumer.send(attr_name).empty? && file_accessor.send(attr_name).empty?
681
+
682
+ add_result(message_type, 'file patterns', "The `#{attr_name}` pattern did not match any file.")
683
+ end
684
+
685
+ def _validate_private_header_files
686
+ _validate_header_files(:private_header_files)
687
+ validate_nonempty_patterns(:private_header_files, :warning)
688
+ end
689
+
690
+ def _validate_public_header_files
691
+ _validate_header_files(:public_header_files)
692
+ validate_nonempty_patterns(:public_header_files, :warning)
693
+ end
694
+
695
+ def _validate_license
696
+ unless file_accessor.license || spec.license && (spec.license[:type] == 'Public Domain' || spec.license[:text])
697
+ warning('license', 'Unable to find a license file')
698
+ end
699
+ end
700
+
701
+ def _validate_module_map
702
+ if spec.module_map
703
+ unless file_accessor.module_map.exist?
704
+ error('module_map', 'Unable to find the specified module map file.')
705
+ end
706
+ unless file_accessor.module_map.extname == '.modulemap'
707
+ relative_path = file_accessor.module_map.relative_path_from file_accessor.root
708
+ error('module_map', "Unexpected file extension for modulemap file (#{relative_path}).")
709
+ end
710
+ end
711
+ end
712
+
713
+ def _validate_resource_bundles
714
+ file_accessor.resource_bundles.each do |bundle, resource_paths|
715
+ next unless resource_paths.empty?
716
+ error('file patterns', "The `resource_bundles` pattern for `#{bundle}` did not match any file.")
717
+ end
718
+ end
719
+
720
+ # Ensures that a list of header files only contains header files.
721
+ #
722
+ def _validate_header_files(attr_name)
723
+ header_files = file_accessor.send(attr_name)
724
+ non_header_files = header_files.
725
+ select { |f| !Sandbox::FileAccessor::HEADER_EXTENSIONS.include?(f.extname) }.
726
+ map { |f| f.relative_path_from(file_accessor.root) }
727
+ unless non_header_files.empty?
728
+ error(attr_name, "The pattern matches non-header files (#{non_header_files.join(', ')}).")
729
+ end
730
+ non_source_files = header_files - file_accessor.source_files
731
+ unless non_source_files.empty?
732
+ error(attr_name, 'The pattern includes header files that are not listed ' \
733
+ "in source_files (#{non_source_files.join(', ')}).")
734
+ end
735
+ end
736
+
737
+ def _validate_header_mappings_dir
738
+ return unless header_mappings_dir = file_accessor.spec_consumer.header_mappings_dir
739
+ absolute_mappings_dir = file_accessor.root + header_mappings_dir
740
+ unless absolute_mappings_dir.directory?
741
+ error('header_mappings_dir', "The header_mappings_dir (`#{header_mappings_dir}`) is not a directory.")
742
+ end
743
+ non_mapped_headers = file_accessor.headers.
744
+ reject { |h| h.to_path.start_with?(absolute_mappings_dir.to_path) }.
745
+ map { |f| f.relative_path_from(file_accessor.root) }
746
+ unless non_mapped_headers.empty?
747
+ error('header_mappings_dir', "There are header files outside of the header_mappings_dir (#{non_mapped_headers.join(', ')}).")
748
+ end
749
+ end
750
+
751
+ #-------------------------------------------------------------------------#
752
+
753
+ private
754
+
755
+ # !@group Result Helpers
756
+
757
+ def error(*args)
758
+ add_result(:error, *args)
759
+ end
760
+
761
+ def warning(*args)
762
+ add_result(:warning, *args)
763
+ end
764
+
765
+ def note(*args)
766
+ add_result(:note, *args)
767
+ end
768
+
769
+ def translate_output_to_linter_messages(parsed_output)
770
+ parsed_output.each do |message|
771
+ # Checking the error for `InputFile` is to work around an Xcode
772
+ # issue where linting would fail even though `xcodebuild` actually
773
+ # succeeds. Xcode.app also doesn't fail when this issue occurs, so
774
+ # it's safe for us to do the same.
775
+ #
776
+ # For more details see https://github.com/CocoaPods/CocoaPods/issues/2394#issuecomment-56658587
777
+ #
778
+ if message.include?("'InputFile' should have")
779
+ next
780
+ end
781
+
782
+ if message =~ /\S+:\d+:\d+: error:/
783
+ error('xcodebuild', message)
784
+ elsif message =~ /\S+:\d+:\d+: warning:/
785
+ warning('xcodebuild', message)
786
+ else
787
+ note('xcodebuild', message)
788
+ end
789
+ end
790
+ end
791
+
792
+ def shares_pod_target_xcscheme?(pod_target)
793
+ Pathname.new(@installer.pods_project.path + pod_target.label).exist?
794
+ end
795
+
796
+ def add_result(type, attribute_name, message, public_only = false)
797
+ result = results.find do |r|
798
+ r.type == type && r.attribute_name && r.message == message && r.public_only? == public_only
799
+ end
800
+ unless result
801
+ result = Result.new(type, attribute_name, message, public_only)
802
+ results << result
803
+ end
804
+ result.platforms << consumer.platform_name if consumer
805
+ result.subspecs << subspec_name if subspec_name && !result.subspecs.include?(subspec_name)
806
+ end
807
+
808
+ # Specialized Result to support subspecs aggregation
809
+ #
810
+ class Result < Specification::Linter::Results::Result
811
+ def initialize(type, attribute_name, message, public_only = false)
812
+ super(type, attribute_name, message, public_only)
813
+ @subspecs = []
814
+ end
815
+
816
+ attr_reader :subspecs
817
+ end
818
+
819
+ #-------------------------------------------------------------------------#
820
+
821
+ private
822
+
823
+ # !@group Helpers
824
+
825
+ # @return [Array<String>] an array of source URLs used to create the
826
+ # {Podfile} used in the linting process
827
+ #
828
+ attr_reader :source_urls
829
+
830
+ # @param [String] platform_name
831
+ # the name of the platform, which should be declared
832
+ # in the Podfile.
833
+ #
834
+ # @param [String] deployment_target
835
+ # the deployment target, which should be declared in
836
+ # the Podfile.
837
+ #
838
+ # @param [Bool] use_frameworks
839
+ # whether frameworks should be used for the installation
840
+ #
841
+ # @param [Array<String>] test_spec_names
842
+ # the test spec names to include in the podfile.
843
+ #
844
+ # @return [Podfile] a podfile that requires the specification on the
845
+ # current platform.
846
+ #
847
+ # @note The generated podfile takes into account whether the linter is
848
+ # in local mode.
849
+ #
850
+ def podfile_from_spec(platform_name, deployment_target, use_frameworks = true, test_spec_names = [])
851
+ name = subspec_name || spec.name
852
+ podspec = file.realpath
853
+ local = local?
854
+ urls = source_urls
855
+ Pod::Podfile.new do
856
+ install! 'cocoapods', :deterministic_uuids => false
857
+ # By default inhibit warnings for all pods, except the one being validated.
858
+ inhibit_all_warnings!
859
+ urls.each { |u| source(u) }
860
+ target 'App' do
861
+ use_frameworks!(use_frameworks)
862
+ platform(platform_name, deployment_target)
863
+ if local
864
+ pod name, :path => podspec.dirname.to_s, :inhibit_warnings => false
865
+ else
866
+ pod name, :podspec => podspec.to_s, :inhibit_warnings => false
867
+ end
868
+ test_spec_names.each do |test_spec_name|
869
+ if local
870
+ pod test_spec_name, :path => podspec.dirname.to_s, :inhibit_warnings => false
871
+ else
872
+ pod test_spec_name, :podspec => podspec.to_s, :inhibit_warnings => false
873
+ end
874
+ end
875
+ end
876
+ end
877
+ end
878
+
879
+ # Parse the xcode build output to identify the lines which are relevant
880
+ # to the linter.
881
+ #
882
+ # @param [String] output the output generated by the xcodebuild tool.
883
+ #
884
+ # @note The indentation and the temporary path is stripped form the
885
+ # lines.
886
+ #
887
+ # @return [Array<String>] the lines that are relevant to the linter.
888
+ #
889
+ def parse_xcodebuild_output(output)
890
+ lines = output.split("\n")
891
+ selected_lines = lines.select do |l|
892
+ l.include?('error: ') && (l !~ /errors? generated\./) && (l !~ /error: \(null\)/) ||
893
+ l.include?('warning: ') && (l !~ /warnings? generated\./) && (l !~ /frameworks only run on iOS 8/) ||
894
+ l.include?('note: ') && (l !~ /expanded from macro/)
895
+ end
896
+ selected_lines.map do |l|
897
+ new = l.gsub(%r{#{validation_dir}/Pods/}, '')
898
+ new.gsub!(/^ */, ' ')
899
+ end
900
+ end
901
+
902
+ # @return [String] Executes xcodebuild in the current working directory and
903
+ # returns its output (both STDOUT and STDERR).
904
+ #
905
+ def xcodebuild(action, scheme, configuration)
906
+ require 'fourflusher'
907
+ command = %W(clean #{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration})
908
+ case consumer.platform_name
909
+ when :osx, :macos
910
+ command += %w(CODE_SIGN_IDENTITY=)
911
+ when :ios
912
+ command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator)
913
+ command += Fourflusher::SimControl.new.destination(:oldest, 'iOS', deployment_target)
914
+ when :watchos
915
+ command += %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator)
916
+ command += Fourflusher::SimControl.new.destination(:oldest, 'watchOS', deployment_target)
917
+ when :tvos
918
+ command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator)
919
+ command += Fourflusher::SimControl.new.destination(:oldest, 'tvOS', deployment_target)
920
+ end
921
+
922
+ begin
923
+ _xcodebuild(command, true)
924
+ rescue => e
925
+ message = 'Returned an unsuccessful exit code.'
926
+ message += ' You can use `--verbose` for more information.' unless config.verbose?
927
+ error('xcodebuild', message)
928
+ e.message
929
+ end
930
+ end
931
+
932
+ # Executes the given command in the current working directory.
933
+ #
934
+ # @return [String] The output of the given command
935
+ #
936
+ def _xcodebuild(command, raise_on_failure = false)
937
+ Executable.execute_command('xcodebuild', command, raise_on_failure)
938
+ end
939
+
940
+ # Whether the platform with the specified name is valid
941
+ #
942
+ # @param [Platform] platform
943
+ # The platform to check
944
+ #
945
+ # @return [Bool] True if the platform is valid
946
+ #
947
+ def valid_platform?(platform)
948
+ VALID_PLATFORMS.any? { |p| p.name == platform.name }
949
+ end
950
+
951
+ # Whether the platform is supported by the specification
952
+ #
953
+ # @param [Platform] platform
954
+ # The platform to check
955
+ #
956
+ # @param [Specification] specification
957
+ # The specification which must support the provided platform
958
+ #
959
+ # @return [Bool] Whether the platform is supported by the specification
960
+ #
961
+ def supported_platform?(platform, spec)
962
+ available_platforms = spec.available_platforms
963
+
964
+ available_platforms.any? { |p| p.name == platform.name }
965
+ end
966
+
967
+ # Whether the provided name matches the platform
968
+ #
969
+ # @param [Platform] platform
970
+ # The platform
971
+ #
972
+ # @param [String] name
973
+ # The name to check against the provided platform
974
+ #
975
+ def platform_name_match?(platform, name)
976
+ [platform.name, platform.string_name].any? { |n| n.casecmp(name) == 0 }
977
+ end
978
+
979
+ #-------------------------------------------------------------------------#
980
+ end
981
+ end