xcocoapods 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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