cocoapods-tt 0.0.1

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/lib/cocoapods-tt/command/native/install.rb +56 -0
  3. data/lib/cocoapods-tt/command/native/update.rb +157 -0
  4. data/lib/cocoapods-tt/command/tt/make.rb +92 -0
  5. data/lib/cocoapods-tt/command/tt.rb +115 -0
  6. data/lib/cocoapods-tt/command.rb +1 -0
  7. data/lib/cocoapods-tt/gem_version.rb +3 -0
  8. data/lib/cocoapods-tt/native/command.rb +185 -0
  9. data/lib/cocoapods-tt/native/config.rb +366 -0
  10. data/lib/cocoapods-tt/native/core_overrides.rb +1 -0
  11. data/lib/cocoapods-tt/native/downloader/cache.rb +322 -0
  12. data/lib/cocoapods-tt/native/downloader/request.rb +86 -0
  13. data/lib/cocoapods-tt/native/downloader/response.rb +16 -0
  14. data/lib/cocoapods-tt/native/downloader.rb +192 -0
  15. data/lib/cocoapods-tt/native/executable.rb +247 -0
  16. data/lib/cocoapods-tt/native/external_sources/abstract_external_source.rb +205 -0
  17. data/lib/cocoapods-tt/native/external_sources/downloader_source.rb +30 -0
  18. data/lib/cocoapods-tt/native/external_sources/path_source.rb +55 -0
  19. data/lib/cocoapods-tt/native/external_sources/podspec_source.rb +54 -0
  20. data/lib/cocoapods-tt/native/external_sources.rb +57 -0
  21. data/lib/cocoapods-tt/native/gem_version.rb +5 -0
  22. data/lib/cocoapods-tt/native/generator/acknowledgements/markdown.rb +44 -0
  23. data/lib/cocoapods-tt/native/generator/acknowledgements/plist.rb +94 -0
  24. data/lib/cocoapods-tt/native/generator/acknowledgements.rb +107 -0
  25. data/lib/cocoapods-tt/native/generator/app_target_helper.rb +363 -0
  26. data/lib/cocoapods-tt/native/generator/bridge_support.rb +22 -0
  27. data/lib/cocoapods-tt/native/generator/constant.rb +19 -0
  28. data/lib/cocoapods-tt/native/generator/copy_dsyms_script.rb +56 -0
  29. data/lib/cocoapods-tt/native/generator/copy_resources_script.rb +223 -0
  30. data/lib/cocoapods-tt/native/generator/copy_xcframework_script.rb +227 -0
  31. data/lib/cocoapods-tt/native/generator/dummy_source.rb +31 -0
  32. data/lib/cocoapods-tt/native/generator/embed_frameworks_script.rb +196 -0
  33. data/lib/cocoapods-tt/native/generator/file_list.rb +39 -0
  34. data/lib/cocoapods-tt/native/generator/header.rb +103 -0
  35. data/lib/cocoapods-tt/native/generator/info_plist_file.rb +128 -0
  36. data/lib/cocoapods-tt/native/generator/module_map.rb +99 -0
  37. data/lib/cocoapods-tt/native/generator/prefix_header.rb +60 -0
  38. data/lib/cocoapods-tt/native/generator/script_phase_constants.rb +100 -0
  39. data/lib/cocoapods-tt/native/generator/umbrella_header.rb +46 -0
  40. data/lib/cocoapods-tt/native/hooks_manager.rb +132 -0
  41. data/lib/cocoapods-tt/native/installer/analyzer/analysis_result.rb +87 -0
  42. data/lib/cocoapods-tt/native/installer/analyzer/locking_dependency_analyzer.rb +103 -0
  43. data/lib/cocoapods-tt/native/installer/analyzer/pod_variant.rb +87 -0
  44. data/lib/cocoapods-tt/native/installer/analyzer/pod_variant_set.rb +175 -0
  45. data/lib/cocoapods-tt/native/installer/analyzer/podfile_dependency_cache.rb +55 -0
  46. data/lib/cocoapods-tt/native/installer/analyzer/sandbox_analyzer.rb +268 -0
  47. data/lib/cocoapods-tt/native/installer/analyzer/specs_state.rb +108 -0
  48. data/lib/cocoapods-tt/native/installer/analyzer/target_inspection_result.rb +58 -0
  49. data/lib/cocoapods-tt/native/installer/analyzer/target_inspector.rb +258 -0
  50. data/lib/cocoapods-tt/native/installer/analyzer.rb +1204 -0
  51. data/lib/cocoapods-tt/native/installer/base_install_hooks_context.rb +135 -0
  52. data/lib/cocoapods-tt/native/installer/installation_options.rb +195 -0
  53. data/lib/cocoapods-tt/native/installer/pod_source_installer.rb +224 -0
  54. data/lib/cocoapods-tt/native/installer/pod_source_preparer.rb +77 -0
  55. data/lib/cocoapods-tt/native/installer/podfile_validator.rb +168 -0
  56. data/lib/cocoapods-tt/native/installer/post_install_hooks_context.rb +9 -0
  57. data/lib/cocoapods-tt/native/installer/post_integrate_hooks_context.rb +9 -0
  58. data/lib/cocoapods-tt/native/installer/pre_install_hooks_context.rb +51 -0
  59. data/lib/cocoapods-tt/native/installer/pre_integrate_hooks_context.rb +9 -0
  60. data/lib/cocoapods-tt/native/installer/project_cache/project_cache.rb +11 -0
  61. data/lib/cocoapods-tt/native/installer/project_cache/project_cache_analysis_result.rb +53 -0
  62. data/lib/cocoapods-tt/native/installer/project_cache/project_cache_analyzer.rb +200 -0
  63. data/lib/cocoapods-tt/native/installer/project_cache/project_cache_version.rb +43 -0
  64. data/lib/cocoapods-tt/native/installer/project_cache/project_installation_cache.rb +103 -0
  65. data/lib/cocoapods-tt/native/installer/project_cache/project_metadata_cache.rb +73 -0
  66. data/lib/cocoapods-tt/native/installer/project_cache/target_cache_key.rb +176 -0
  67. data/lib/cocoapods-tt/native/installer/project_cache/target_metadata.rb +74 -0
  68. data/lib/cocoapods-tt/native/installer/sandbox_dir_cleaner.rb +105 -0
  69. data/lib/cocoapods-tt/native/installer/sandbox_header_paths_installer.rb +45 -0
  70. data/lib/cocoapods-tt/native/installer/source_provider_hooks_context.rb +34 -0
  71. data/lib/cocoapods-tt/native/installer/target_uuid_generator.rb +34 -0
  72. data/lib/cocoapods-tt/native/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb +179 -0
  73. data/lib/cocoapods-tt/native/installer/user_project_integrator/target_integrator.rb +815 -0
  74. data/lib/cocoapods-tt/native/installer/user_project_integrator.rb +280 -0
  75. data/lib/cocoapods-tt/native/installer/xcode/multi_pods_project_generator.rb +82 -0
  76. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/aggregate_target_dependency_installer.rb +66 -0
  77. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/aggregate_target_installer.rb +192 -0
  78. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/app_host_installer.rb +154 -0
  79. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/file_references_installer.rb +329 -0
  80. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/pod_target_dependency_installer.rb +195 -0
  81. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/pod_target_installer.rb +1239 -0
  82. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/pod_target_integrator.rb +312 -0
  83. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/pods_project_writer.rb +90 -0
  84. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/project_generator.rb +120 -0
  85. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/target_installation_result.rb +140 -0
  86. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/target_installer.rb +257 -0
  87. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/target_installer_helper.rb +110 -0
  88. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator.rb +291 -0
  89. data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator_result.rb +54 -0
  90. data/lib/cocoapods-tt/native/installer/xcode/single_pods_project_generator.rb +38 -0
  91. data/lib/cocoapods-tt/native/installer/xcode/target_validator.rb +170 -0
  92. data/lib/cocoapods-tt/native/installer/xcode.rb +11 -0
  93. data/lib/cocoapods-tt/native/installer.rb +1044 -0
  94. data/lib/cocoapods-tt/native/native_target_extension.rb +60 -0
  95. data/lib/cocoapods-tt/native/open-uri.rb +33 -0
  96. data/lib/cocoapods-tt/native/podfile.rb +13 -0
  97. data/lib/cocoapods-tt/native/project.rb +544 -0
  98. data/lib/cocoapods-tt/native/resolver/lazy_specification.rb +88 -0
  99. data/lib/cocoapods-tt/native/resolver/resolver_specification.rb +41 -0
  100. data/lib/cocoapods-tt/native/resolver.rb +600 -0
  101. data/lib/cocoapods-tt/native/sandbox/file_accessor.rb +532 -0
  102. data/lib/cocoapods-tt/native/sandbox/headers_store.rb +163 -0
  103. data/lib/cocoapods-tt/native/sandbox/path_list.rb +242 -0
  104. data/lib/cocoapods-tt/native/sandbox/pod_dir_cleaner.rb +71 -0
  105. data/lib/cocoapods-tt/native/sandbox/podspec_finder.rb +23 -0
  106. data/lib/cocoapods-tt/native/sandbox.rb +470 -0
  107. data/lib/cocoapods-tt/native/sources_manager.rb +221 -0
  108. data/lib/cocoapods-tt/native/target/aggregate_target.rb +558 -0
  109. data/lib/cocoapods-tt/native/target/build_settings.rb +1385 -0
  110. data/lib/cocoapods-tt/native/target/pod_target.rb +1168 -0
  111. data/lib/cocoapods-tt/native/target.rb +378 -0
  112. data/lib/cocoapods-tt/native/user_interface/error_report.rb +204 -0
  113. data/lib/cocoapods-tt/native/user_interface/inspector_reporter.rb +102 -0
  114. data/lib/cocoapods-tt/native/user_interface.rb +463 -0
  115. data/lib/cocoapods-tt/native/validator.rb +1170 -0
  116. data/lib/cocoapods-tt/native/version_metadata.rb +26 -0
  117. data/lib/cocoapods-tt/native/xcode/framework_paths.rb +54 -0
  118. data/lib/cocoapods-tt/native/xcode/linkage_analyzer.rb +22 -0
  119. data/lib/cocoapods-tt/native/xcode/xcframework/xcframework_slice.rb +138 -0
  120. data/lib/cocoapods-tt/native/xcode/xcframework.rb +99 -0
  121. data/lib/cocoapods-tt/native/xcode.rb +7 -0
  122. data/lib/cocoapods-tt.rb +1 -0
  123. data/lib/cocoapods_plugin.rb +17 -0
  124. metadata +193 -0
@@ -0,0 +1,1170 @@
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 = '4.0'.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
+ @use_frameworks = true
43
+ @linter = Specification::Linter.new(spec_or_path)
44
+ @source_urls = if @linter.spec && @linter.spec.dependencies.empty? && @linter.spec.recursive_subspecs.all? { |s| s.dependencies.empty? }
45
+ []
46
+ else
47
+ source_urls.map { |url| config.sources_manager.source_with_name_or_url(url) }.map(&:url)
48
+ end
49
+
50
+ @platforms = platforms.map do |platform|
51
+ result = case platform.to_s.downcase
52
+ # Platform doesn't recognize 'macos' as being the same as 'osx' when initializing
53
+ when 'macos' then Platform.macos
54
+ else Platform.new(platform, nil)
55
+ end
56
+ unless valid_platform?(result)
57
+ raise Informative, "Unrecognized platform `#{platform}`. Valid platforms: #{VALID_PLATFORMS.join(', ')}"
58
+ end
59
+ result
60
+ end
61
+ @use_frameworks = true
62
+ end
63
+
64
+ #-------------------------------------------------------------------------#
65
+
66
+ # @return [Specification] the specification to lint.
67
+ #
68
+ def spec
69
+ @linter.spec
70
+ end
71
+
72
+ # @return [Pathname] the path of the `podspec` file where {#spec} is
73
+ # defined.
74
+ #
75
+ def file
76
+ @linter.file
77
+ end
78
+
79
+ # Returns a list of platforms to lint for a given Specification
80
+ #
81
+ # @param [Specification] spec
82
+ # The specification to lint
83
+ #
84
+ # @return [Array<Platform>] platforms to lint for the given specification
85
+ #
86
+ def platforms_to_lint(spec)
87
+ return spec.available_platforms if @platforms.empty?
88
+
89
+ # Validate that the platforms specified are actually supported by the spec
90
+ results = @platforms.map do |platform|
91
+ matching_platform = spec.available_platforms.find { |p| p.name == platform.name }
92
+ unless matching_platform
93
+ raise Informative, "Platform `#{platform}` is not supported by specification `#{spec}`."
94
+ end
95
+ matching_platform
96
+ end.uniq
97
+
98
+ results
99
+ end
100
+
101
+ # @return [Sandbox::FileAccessor] the file accessor for the spec.
102
+ #
103
+ attr_accessor :file_accessor
104
+
105
+ #-------------------------------------------------------------------------#
106
+
107
+ # Lints the specification adding a {Result} for any
108
+ # failed check to the {#results} list.
109
+ #
110
+ # @note This method shows immediately which pod is being processed and
111
+ # overrides the printed line once the result is known.
112
+ #
113
+ # @return [Bool] whether the specification passed validation.
114
+ #
115
+ def validate
116
+ @results = []
117
+
118
+ # Replace default spec with a subspec if asked for
119
+ a_spec = spec
120
+ if spec && @only_subspec
121
+ subspec_name = @only_subspec.start_with?("#{spec.root.name}/") ? @only_subspec : "#{spec.root.name}/#{@only_subspec}"
122
+ a_spec = spec.subspec_by_name(subspec_name, true, true)
123
+ @subspec_name = a_spec.name
124
+ end
125
+
126
+ UI.print " -> #{a_spec ? a_spec.name : file.basename}\r" unless config.silent?
127
+ $stdout.flush
128
+
129
+ perform_linting
130
+ perform_extensive_analysis(a_spec) if a_spec && !quick
131
+
132
+ UI.puts ' -> '.send(result_color) << (a_spec ? a_spec.to_s : file.basename.to_s)
133
+ print_results
134
+ validated?
135
+ end
136
+
137
+ # Prints the result of the validation to the user.
138
+ #
139
+ # @return [void]
140
+ #
141
+ def print_results
142
+ UI.puts results_message
143
+ end
144
+
145
+ def results_message
146
+ message = ''
147
+ results.each do |result|
148
+ if result.platforms == [:ios]
149
+ platform_message = '[iOS] '
150
+ elsif result.platforms == [:osx]
151
+ platform_message = '[OSX] '
152
+ elsif result.platforms == [:watchos]
153
+ platform_message = '[watchOS] '
154
+ elsif result.platforms == [:tvos]
155
+ platform_message = '[tvOS] '
156
+ end
157
+
158
+ subspecs_message = ''
159
+ if result.is_a?(Result)
160
+ subspecs = result.subspecs.uniq
161
+ if subspecs.count > 2
162
+ subspecs_message = '[' + subspecs[0..2].join(', ') + ', and more...] '
163
+ elsif subspecs.count > 0
164
+ subspecs_message = '[' + subspecs.join(',') + '] '
165
+ end
166
+ end
167
+
168
+ case result.type
169
+ when :error then type = 'ERROR'
170
+ when :warning then type = 'WARN'
171
+ when :note then type = 'NOTE'
172
+ else raise "#{result.type}" end
173
+ message << " - #{type.ljust(5)} | #{platform_message}#{subspecs_message}#{result.attribute_name}: #{result.message}\n"
174
+ end
175
+ message << "\n"
176
+ end
177
+
178
+ def failure_reason
179
+ results_by_type = results.group_by(&:type)
180
+ results_by_type.default = []
181
+ return nil if validated?
182
+ reasons = []
183
+ if (size = results_by_type[:error].size) && size > 0
184
+ reasons << "#{size} #{'error'.pluralize(size)}"
185
+ end
186
+ if !allow_warnings && (size = results_by_type[:warning].size) && size > 0
187
+ reason = "#{size} #{'warning'.pluralize(size)}"
188
+ pronoun = size == 1 ? 'it' : 'them'
189
+ reason << " (but you can use `--allow-warnings` to ignore #{pronoun})" if reasons.empty?
190
+ reasons << reason
191
+ end
192
+ if results.all?(&:public_only)
193
+ reasons << 'all results apply only to public specs, but you can use ' \
194
+ '`--private` to ignore them if linting the specification for a private pod'
195
+ end
196
+
197
+ reasons.to_sentence
198
+ end
199
+
200
+ #-------------------------------------------------------------------------#
201
+
202
+ # @!group Configuration
203
+
204
+ # @return [Bool] whether the validation should skip the checks that
205
+ # requires the download of the library.
206
+ #
207
+ attr_accessor :quick
208
+
209
+ # @return [Bool] whether the linter should not clean up temporary files
210
+ # for inspection.
211
+ #
212
+ attr_accessor :no_clean
213
+
214
+ # @return [Bool] whether the linter should fail as soon as the first build
215
+ # variant causes an error. Helpful for i.e. multi-platforms specs,
216
+ # specs with subspecs.
217
+ #
218
+ attr_accessor :fail_fast
219
+
220
+ # @return [Bool] whether the validation should be performed against the root of
221
+ # the podspec instead to its original source.
222
+ #
223
+ # @note Uses the `:path` option of the Podfile.
224
+ #
225
+ attr_accessor :local
226
+ alias_method :local?, :local
227
+
228
+ # @return [Bool] Whether the validator should fail on warnings, or only on errors.
229
+ #
230
+ attr_accessor :allow_warnings
231
+
232
+ # @return [String] name of the subspec to check, if nil all subspecs are checked.
233
+ #
234
+ attr_accessor :only_subspec
235
+
236
+ # @return [Bool] Whether the validator should validate all subspecs.
237
+ #
238
+ attr_accessor :no_subspecs
239
+
240
+ # @return [Bool] Whether the validator should skip building and running tests.
241
+ #
242
+ attr_accessor :skip_tests
243
+
244
+ # @return [Array<String>] List of test_specs to run. If nil, all tests are run (unless skip_tests is specified).
245
+ #
246
+ attr_accessor :test_specs
247
+
248
+ # @return [Bool] Whether the validator should run Xcode Static Analysis.
249
+ #
250
+ attr_accessor :analyze
251
+
252
+ # @return [Bool] Whether frameworks should be used for the installation.
253
+ #
254
+ attr_accessor :use_frameworks
255
+
256
+ # @return [Boolean] Whether modular headers should be used for the installation.
257
+ #
258
+ attr_accessor :use_modular_headers
259
+
260
+ # @return [Boolean] Whether static frameworks should be used for the installation.
261
+ #
262
+ attr_accessor :use_static_frameworks
263
+
264
+ # @return [Boolean] Whether attributes that affect only public sources
265
+ # Bool be skipped.
266
+ #
267
+ attr_accessor :ignore_public_only_results
268
+
269
+ # @return [String] A glob for podspecs to be used during building of
270
+ # the local Podfile via :path.
271
+ #
272
+ attr_accessor :include_podspecs
273
+
274
+ # @return [String] A glob for podspecs to be used during building of
275
+ # the local Podfile via :podspec.
276
+ #
277
+ attr_accessor :external_podspecs
278
+
279
+ attr_accessor :skip_import_validation
280
+ alias_method :skip_import_validation?, :skip_import_validation
281
+
282
+ attr_accessor :configuration
283
+
284
+ #-------------------------------------------------------------------------#
285
+
286
+ # !@group Lint results
287
+
288
+ #
289
+ #
290
+ attr_reader :results
291
+
292
+ # @return [Boolean]
293
+ #
294
+ def validated?
295
+ result_type != :error && (result_type != :warning || allow_warnings)
296
+ end
297
+
298
+ # @return [Symbol] The type, which should been used to display the result.
299
+ # One of: `:error`, `:warning`, `:note`.
300
+ #
301
+ def result_type
302
+ applicable_results = results
303
+ applicable_results = applicable_results.reject(&:public_only?) if ignore_public_only_results
304
+ types = applicable_results.map(&:type).uniq
305
+ if types.include?(:error) then :error
306
+ elsif types.include?(:warning) then :warning
307
+ else :note
308
+ end
309
+ end
310
+
311
+ # @return [Symbol] The color, which should been used to display the result.
312
+ # One of: `:green`, `:yellow`, `:red`.
313
+ #
314
+ def result_color
315
+ case result_type
316
+ when :error then :red
317
+ when :warning then :yellow
318
+ else :green end
319
+ end
320
+
321
+ # @return [Pathname] the temporary directory used by the linter.
322
+ #
323
+ def validation_dir
324
+ @validation_dir ||= Pathname(Dir.mktmpdir(['CocoaPods-Lint-', "-#{spec.name}"]))
325
+ end
326
+
327
+ # @return [String] The SWIFT_VERSION that should be used to validate the pod. This is set by passing the
328
+ # `--swift-version` parameter during validation.
329
+ #
330
+ attr_accessor :swift_version
331
+
332
+ # @return [String] the SWIFT_VERSION within the .swift-version file or nil.
333
+ #
334
+ def dot_swift_version
335
+ return unless file
336
+ swift_version_path = file.dirname + '.swift-version'
337
+ return unless swift_version_path.exist?
338
+ swift_version_path.read.strip
339
+ end
340
+
341
+ # @return [String] The derived Swift version to use for validation. The order of precedence is as follows:
342
+ # - The `--swift-version` parameter is always checked first and honored if passed.
343
+ # - The `swift_versions` DSL attribute within the podspec, in which case the latest version is always chosen.
344
+ # - The Swift version within the `.swift-version` file if present.
345
+ # - If none of the above are set then the `#DEFAULT_SWIFT_VERSION` is used.
346
+ #
347
+ def derived_swift_version
348
+ @derived_swift_version ||= begin
349
+ if !swift_version.nil?
350
+ swift_version
351
+ elsif version = spec.swift_versions.max || dot_swift_version
352
+ version.to_s
353
+ else
354
+ DEFAULT_SWIFT_VERSION
355
+ end
356
+ end
357
+ end
358
+
359
+ # @return [Boolean] Whether any of the pod targets part of this validator use Swift or not.
360
+ #
361
+ def uses_swift?
362
+ @installer.pod_targets.any?(&:uses_swift?)
363
+ end
364
+
365
+ #-------------------------------------------------------------------------#
366
+
367
+ private
368
+
369
+ # !@group Lint steps
370
+
371
+ #
372
+ #
373
+ def perform_linting
374
+ linter.lint
375
+ @results.concat(linter.results.to_a)
376
+ end
377
+
378
+ # Perform analysis for a given spec (or subspec)
379
+ #
380
+ def perform_extensive_analysis(spec)
381
+ if spec.non_library_specification?
382
+ error('spec', "Validating a non library spec (`#{spec.name}`) is not supported.")
383
+ return false
384
+ end
385
+ validate_homepage(spec)
386
+ validate_screenshots(spec)
387
+ validate_social_media_url(spec)
388
+ validate_documentation_url(spec)
389
+ validate_source_url(spec)
390
+
391
+ platforms = platforms_to_lint(spec)
392
+
393
+ valid = platforms.send(fail_fast ? :all? : :each) do |platform|
394
+ UI.message "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed
395
+ @consumer = spec.consumer(platform)
396
+ setup_validation_environment
397
+ begin
398
+ create_app_project
399
+ download_pod
400
+ check_file_patterns
401
+ install_pod
402
+ validate_swift_version
403
+ add_app_project_import
404
+ validate_vendored_dynamic_frameworks
405
+ build_pod
406
+ test_pod unless skip_tests
407
+ ensure
408
+ tear_down_validation_environment
409
+ end
410
+ validated?
411
+ end
412
+ return false if fail_fast && !valid
413
+ perform_extensive_subspec_analysis(spec) unless @no_subspecs
414
+ rescue => e
415
+ message = e.to_s
416
+ message << "\n" << e.backtrace.join("\n") << "\n" if config.verbose?
417
+ error('unknown', "Encountered an unknown error (#{message}) during validation.")
418
+ false
419
+ end
420
+
421
+ # Recursively perform the extensive analysis on all subspecs
422
+ #
423
+ def perform_extensive_subspec_analysis(spec)
424
+ spec.subspecs.reject(&:non_library_specification?).send(fail_fast ? :all? : :each) do |subspec|
425
+ @subspec_name = subspec.name
426
+ perform_extensive_analysis(subspec)
427
+ end
428
+ end
429
+
430
+ # @return [Consumer] the consumer for the current platform being validated
431
+ #
432
+ attr_accessor :consumer
433
+
434
+ # @return [String, Nil] the name of the current subspec being validated, or nil if none
435
+ #
436
+ attr_accessor :subspec_name
437
+
438
+ # Performs validation of a URL
439
+ #
440
+ def validate_url(url, user_agent = nil)
441
+ resp = Pod::HTTP.validate_url(url, user_agent)
442
+
443
+ if !resp
444
+ warning('url', "There was a problem validating the URL #{url}.", true)
445
+ elsif !resp.success?
446
+ note('url', "The URL (#{url}) is not reachable.", true)
447
+ end
448
+
449
+ resp
450
+ end
451
+
452
+ # Performs validations related to the `homepage` attribute.
453
+ #
454
+ def validate_homepage(spec)
455
+ if spec.homepage
456
+ validate_url(spec.homepage)
457
+ end
458
+ end
459
+
460
+ # Performs validation related to the `screenshots` attribute.
461
+ #
462
+ def validate_screenshots(spec)
463
+ spec.screenshots.compact.each do |screenshot|
464
+ response = validate_url(screenshot)
465
+ if response && !(response.headers['content-type'] && response.headers['content-type'].first =~ /image\/.*/i)
466
+ warning('screenshot', "The screenshot #{screenshot} is not a valid image.")
467
+ end
468
+ end
469
+ end
470
+
471
+ # Performs validations related to the `social_media_url` attribute.
472
+ #
473
+ def validate_social_media_url(spec)
474
+ validate_url(spec.social_media_url, 'CocoaPods') if spec.social_media_url
475
+ end
476
+
477
+ # Performs validations related to the `documentation_url` attribute.
478
+ #
479
+ def validate_documentation_url(spec)
480
+ validate_url(spec.documentation_url) if spec.documentation_url
481
+ end
482
+
483
+ # Performs validations related to the `source` -> `http` attribute (if exists)
484
+ #
485
+ def validate_source_url(spec)
486
+ return if spec.source.nil? || spec.source[:http].nil?
487
+ url = URI(spec.source[:http])
488
+ return if url.scheme == 'https' || url.scheme == 'file'
489
+ warning('http', "The URL (`#{url}`) doesn't use the encrypted HTTPS protocol. " \
490
+ 'It is crucial for Pods to be transferred over a secure protocol to protect your users from man-in-the-middle attacks. '\
491
+ 'This will be an error in future releases. Please update the URL to use https.')
492
+ end
493
+
494
+ # Performs validation for the version of Swift used during validation.
495
+ #
496
+ # An error will be displayed if the user has provided a `swift_versions` attribute within the podspec but is also
497
+ # using either `--swift-version` parameter or a `.swift-version` file with a Swift version that is not declared
498
+ # within the attribute.
499
+ #
500
+ # The user will be warned that the default version of Swift was used if the following things are true:
501
+ # - The project uses Swift at all
502
+ # - The user did not supply a Swift version via a parameter
503
+ # - There is no `swift_versions` attribute set within the specification
504
+ # - There is no `.swift-version` file present either.
505
+ #
506
+ def validate_swift_version
507
+ return unless uses_swift?
508
+ spec_swift_versions = spec.swift_versions.map(&:to_s)
509
+
510
+ unless spec_swift_versions.empty?
511
+ message = nil
512
+ if !dot_swift_version.nil? && !spec_swift_versions.include?(dot_swift_version)
513
+ message = "Specification `#{spec.name}` specifies inconsistent `swift_versions` (#{spec_swift_versions.map { |s| "`#{s}`" }.to_sentence}) compared to the one present in your `.swift-version` file (`#{dot_swift_version}`). " \
514
+ 'Please remove the `.swift-version` file which is now deprecated and only use the `swift_versions` attribute within your podspec.'
515
+ elsif !swift_version.nil? && !spec_swift_versions.include?(swift_version)
516
+ message = "Specification `#{spec.name}` specifies inconsistent `swift_versions` (#{spec_swift_versions.map { |s| "`#{s}`" }.to_sentence}) compared to the one passed during lint (`#{swift_version}`)."
517
+ end
518
+ unless message.nil?
519
+ error('swift', message)
520
+ return
521
+ end
522
+ end
523
+
524
+ if swift_version.nil? && spec.swift_versions.empty?
525
+ if !dot_swift_version.nil?
526
+ # The user will be warned to delete the `.swift-version` file in favor of the `swift_versions` DSL attribute.
527
+ # This is intentionally not a lint warning since we do not want to break existing setups and instead just soft
528
+ # deprecate this slowly.
529
+ #
530
+ UI.warn 'Usage of the `.swift_version` file has been deprecated! Please delete the file and use the ' \
531
+ "`swift_versions` attribute within your podspec instead.\n".yellow
532
+ else
533
+ warning('swift',
534
+ 'The validator used ' \
535
+ "Swift `#{DEFAULT_SWIFT_VERSION}` by default because no Swift version was specified. " \
536
+ 'To specify a Swift version during validation, add the `swift_versions` attribute in your podspec. ' \
537
+ 'Note that usage of a `.swift-version` file is now deprecated.')
538
+ end
539
+ end
540
+ end
541
+
542
+ def setup_validation_environment
543
+ validation_dir.rmtree if validation_dir.exist?
544
+ validation_dir.mkpath
545
+ @original_config = Config.instance.clone
546
+ config.installation_root = validation_dir
547
+ config.silent = !config.verbose
548
+ end
549
+
550
+ def tear_down_validation_environment
551
+ clean! unless no_clean
552
+ Config.instance = @original_config
553
+ end
554
+
555
+ def clean!
556
+ validation_dir.rmtree
557
+ end
558
+
559
+ # @return [String] The deployment targret of the library spec.
560
+ #
561
+ def deployment_target
562
+ deployment_target = spec.subspec_by_name(subspec_name).deployment_target(consumer.platform_name)
563
+ if consumer.platform_name == :ios && use_frameworks
564
+ minimum = Version.new('8.0')
565
+ deployment_target = [Version.new(deployment_target), minimum].max.to_s
566
+ end
567
+ deployment_target
568
+ end
569
+
570
+ def download_pod
571
+ test_spec_names = consumer.spec.test_specs.select { |ts| ts.supported_on_platform?(consumer.platform_name) }.map(&:name)
572
+ podfile = podfile_from_spec(consumer.platform_name, deployment_target, use_frameworks, test_spec_names, use_modular_headers, use_static_frameworks)
573
+ sandbox = Sandbox.new(config.sandbox_root)
574
+ @installer = Installer.new(sandbox, podfile)
575
+ @installer.use_default_plugins = false
576
+ @installer.has_dependencies = !spec.dependencies.empty?
577
+ %i(prepare resolve_dependencies download_dependencies write_lockfiles).each { |m| @installer.send(m) }
578
+ @file_accessor = @installer.pod_targets.flat_map(&:file_accessors).find { |fa| fa.spec.name == consumer.spec.name }
579
+ end
580
+
581
+ def create_app_project
582
+ app_project = Xcodeproj::Project.new(validation_dir + 'App.xcodeproj')
583
+ app_target = Pod::Generator::AppTargetHelper.add_app_target(app_project, consumer.platform_name, deployment_target)
584
+ sandbox = Sandbox.new(config.sandbox_root)
585
+ info_plist_path = app_project.path.dirname.+('App/App-Info.plist')
586
+ Pod::Installer::Xcode::PodsProjectGenerator::TargetInstallerHelper.create_info_plist_file_with_sandbox(sandbox,
587
+ info_plist_path,
588
+ app_target,
589
+ '1.0.0',
590
+ Platform.new(consumer.platform_name),
591
+ :appl,
592
+ :build_setting_value => '$(SRCROOT)/App/App-Info.plist')
593
+ Pod::Generator::AppTargetHelper.add_swift_version(app_target, derived_swift_version)
594
+ app_target.build_configurations.each do |config|
595
+ # Lint will fail if a AppIcon is set but no image is found with such name
596
+ # Happens only with Static Frameworks enabled but shouldn't be set anyway
597
+ config.build_settings.delete('ASSETCATALOG_COMPILER_APPICON_NAME')
598
+ # Ensure this is set generally but we have seen an issue with ODRs:
599
+ # see: https://github.com/CocoaPods/CocoaPods/issues/10933
600
+ config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = 'org.cocoapods.${PRODUCT_NAME:rfc1034identifier}'
601
+ end
602
+ app_project.save
603
+ app_project.recreate_user_schemes
604
+ end
605
+
606
+ def add_app_project_import
607
+ app_project = Xcodeproj::Project.open(validation_dir + 'App.xcodeproj')
608
+ app_target = app_project.targets.first
609
+ pod_target = validation_pod_target
610
+ Pod::Generator::AppTargetHelper.add_app_project_import(app_project, app_target, pod_target, consumer.platform_name)
611
+ Pod::Generator::AppTargetHelper.add_xctest_search_paths(app_target) if @installer.pod_targets.any? { |pt| pt.spec_consumers.any? { |c| c.frameworks.include?('XCTest') || c.weak_frameworks.include?('XCTest') } }
612
+ Pod::Generator::AppTargetHelper.add_empty_swift_file(app_project, app_target) if @installer.pod_targets.any?(&:uses_swift?)
613
+ app_project.save
614
+ Xcodeproj::XCScheme.share_scheme(app_project.path, 'App')
615
+ # Share the pods xcscheme only if it exists. For pre-built vendored pods there is no xcscheme generated.
616
+ Xcodeproj::XCScheme.share_scheme(@installer.pods_project.path, pod_target.label) if shares_pod_target_xcscheme?(pod_target)
617
+ end
618
+
619
+ # Returns the pod target for the pod being validated. Installation must have occurred before this can be invoked.
620
+ #
621
+ def validation_pod_target
622
+ @installer.pod_targets.find { |pt| pt.pod_name == spec.root.name }
623
+ end
624
+
625
+ # It creates a podfile in memory and builds a library containing the pod
626
+ # for all available platforms with xcodebuild.
627
+ #
628
+ def install_pod
629
+ %i(validate_targets generate_pods_project integrate_user_project
630
+ perform_post_install_actions).each { |m| @installer.send(m) }
631
+
632
+ deployment_target = spec.subspec_by_name(subspec_name).deployment_target(consumer.platform_name)
633
+ configure_pod_targets(@installer.target_installation_results)
634
+ validate_dynamic_framework_support(@installer.aggregate_targets, deployment_target)
635
+ @installer.pods_project.save
636
+ end
637
+
638
+ # @param [Array<Hash{String, TargetInstallationResult}>] target_installation_results
639
+ # The installation results to configure
640
+ #
641
+ def configure_pod_targets(target_installation_results)
642
+ target_installation_results.first.values.each do |pod_target_installation_result|
643
+ pod_target = pod_target_installation_result.target
644
+ native_target = pod_target_installation_result.native_target
645
+ native_target.build_configuration_list.build_configurations.each do |build_configuration|
646
+ (build_configuration.build_settings['OTHER_CFLAGS'] ||= '$(inherited)') << ' -Wincomplete-umbrella'
647
+ if pod_target.uses_swift?
648
+ # The Swift version for the target being validated can be overridden by `--swift-version` or the
649
+ # `.swift-version` file so we always use the derived Swift version.
650
+ #
651
+ # For dependencies, if the derived Swift version is supported then it is the one used. Otherwise, the Swift
652
+ # version for dependencies is inferred by the target that is integrating them.
653
+ swift_version = if pod_target == validation_pod_target
654
+ derived_swift_version
655
+ else
656
+ pod_target.spec_swift_versions.map(&:to_s).find do |v|
657
+ v == derived_swift_version
658
+ end || pod_target.swift_version
659
+ end
660
+ build_configuration.build_settings['SWIFT_VERSION'] = swift_version
661
+ end
662
+ end
663
+ pod_target_installation_result.test_specs_by_native_target.each do |test_native_target, test_spec|
664
+ if pod_target.uses_swift_for_spec?(test_spec)
665
+ test_native_target.build_configuration_list.build_configurations.each do |build_configuration|
666
+ swift_version = pod_target == validation_pod_target ? derived_swift_version : pod_target.swift_version
667
+ build_configuration.build_settings['SWIFT_VERSION'] = swift_version
668
+ end
669
+ end
670
+ end
671
+ end
672
+ end
673
+
674
+ # Produces an error of dynamic frameworks were requested but are not supported by the deployment target
675
+ #
676
+ # @param [Array<AggregateTarget>] aggregate_targets
677
+ # The aggregate targets installed by the installer
678
+ #
679
+ # @param [String,Version] deployment_target
680
+ # The deployment target of the installation
681
+ #
682
+ def validate_dynamic_framework_support(aggregate_targets, deployment_target)
683
+ return unless consumer.platform_name == :ios
684
+ return unless deployment_target.nil? || Version.new(deployment_target).major < 8
685
+ aggregate_targets.each do |target|
686
+ if target.pod_targets.any?(&:uses_swift?)
687
+ uses_xctest = target.spec_consumers.any? { |c| (c.frameworks + c.weak_frameworks).include? 'XCTest' }
688
+ error('swift', 'Swift support uses dynamic frameworks and is therefore only supported on iOS > 8.') unless uses_xctest
689
+ end
690
+ end
691
+ end
692
+
693
+ def validate_vendored_dynamic_frameworks
694
+ deployment_target = spec.subspec_by_name(subspec_name).deployment_target(consumer.platform_name)
695
+
696
+ unless file_accessor.nil?
697
+ dynamic_frameworks = file_accessor.vendored_dynamic_frameworks
698
+ dynamic_libraries = file_accessor.vendored_dynamic_libraries
699
+ if (dynamic_frameworks.count > 0 || dynamic_libraries.count > 0) && consumer.platform_name == :ios &&
700
+ (deployment_target.nil? || Version.new(deployment_target).major < 8)
701
+ error('dynamic', 'Dynamic frameworks and libraries are only supported on iOS 8.0 and onwards.')
702
+ end
703
+ end
704
+ end
705
+
706
+ # Performs platform specific analysis. It requires to download the source
707
+ # at each iteration
708
+ #
709
+ # @note Xcode warnings are treated as notes because the spec maintainer
710
+ # might not be the author of the library
711
+ #
712
+ # @return [void]
713
+ #
714
+ def build_pod
715
+ if !xcodebuild_available?
716
+ UI.warn "Skipping compilation with `xcodebuild` because it can't be found.\n".yellow
717
+ else
718
+ UI.message "\nBuilding with `xcodebuild`.\n".yellow do
719
+ scheme = if skip_import_validation?
720
+ validation_pod_target.label if validation_pod_target.should_build?
721
+ else
722
+ 'App'
723
+ end
724
+ if scheme.nil?
725
+ UI.warn "Skipping compilation with `xcodebuild` because target contains no sources.\n".yellow
726
+ else
727
+ requested_configuration = configuration ? configuration : 'Release'
728
+ if analyze
729
+ output = xcodebuild('analyze', scheme, requested_configuration, :deployment_target => deployment_target)
730
+ find_output = Executable.execute_command('find', [validation_dir, '-name', '*.html'], false)
731
+ if find_output != ''
732
+ message = 'Static Analysis failed.'
733
+ message += ' You can use `--verbose` for more information.' unless config.verbose?
734
+ message += ' You can use `--no-clean` to save a reproducible buid environment.' unless no_clean
735
+ error('build_pod', message)
736
+ end
737
+ else
738
+ output = xcodebuild('build', scheme, requested_configuration, :deployment_target => deployment_target)
739
+ end
740
+ parsed_output = parse_xcodebuild_output(output)
741
+ translate_output_to_linter_messages(parsed_output)
742
+ end
743
+ end
744
+ end
745
+ end
746
+
747
+ # Builds and runs all test sources associated with the current specification being validated.
748
+ #
749
+ # @note Xcode warnings are treated as notes because the spec maintainer
750
+ # might not be the author of the library
751
+ #
752
+ # @return [void]
753
+ #
754
+ def test_pod
755
+ if !xcodebuild_available?
756
+ UI.warn "Skipping test validation with `xcodebuild` because it can't be found.\n".yellow
757
+ else
758
+ UI.message "\nTesting with `xcodebuild`.\n".yellow do
759
+ pod_target = validation_pod_target
760
+ all_test_specs = consumer.spec.test_specs
761
+ unless test_specs.nil?
762
+ test_spec_names = all_test_specs.map(&:base_name)
763
+ all_test_specs.select! { |test_spec| test_specs.include? test_spec.base_name }
764
+ test_specs.each do |test_spec|
765
+ unless test_spec_names.include? test_spec
766
+ UI.warn "Requested test spec `#{test_spec}` does not exist in the podspec. Existing test specs are `#{test_spec_names}`"
767
+ end
768
+ end
769
+ end
770
+ all_test_specs.each do |test_spec|
771
+ if !test_spec.supported_on_platform?(consumer.platform_name)
772
+ UI.warn "Skipping test spec `#{test_spec.name}` on platform `#{consumer.platform_name}` since it is not supported.\n".yellow
773
+ else
774
+ scheme = @installer.target_installation_results.first[pod_target.name].native_target_for_spec(test_spec)
775
+ output = xcodebuild('test', scheme, 'Debug', :deployment_target => test_spec.deployment_target(consumer.platform_name))
776
+ parsed_output = parse_xcodebuild_output(output)
777
+ translate_output_to_linter_messages(parsed_output)
778
+ end
779
+ end
780
+ end
781
+ end
782
+ end
783
+
784
+ def xcodebuild_available?
785
+ !Executable.which('xcodebuild').nil? && ENV['COCOAPODS_VALIDATOR_SKIP_XCODEBUILD'].nil?
786
+ end
787
+
788
+ FILE_PATTERNS = %i(source_files resources preserve_paths vendored_libraries
789
+ vendored_frameworks public_header_files preserve_paths
790
+ project_header_files private_header_files resource_bundles).freeze
791
+
792
+ # It checks that every file pattern specified in a spec yields
793
+ # at least one file. It requires the pods to be already present
794
+ # in the current working directory under Pods/spec.name.
795
+ #
796
+ # @return [void]
797
+ #
798
+ def check_file_patterns
799
+ FILE_PATTERNS.each do |attr_name|
800
+ if respond_to?("_validate_#{attr_name}", true)
801
+ send("_validate_#{attr_name}")
802
+ else
803
+ validate_nonempty_patterns(attr_name, :error)
804
+ end
805
+ end
806
+
807
+ _validate_header_mappings_dir
808
+ if consumer.spec.root?
809
+ _validate_license
810
+ _validate_module_map
811
+ end
812
+ end
813
+
814
+ # Validates that the file patterns in `attr_name` match at least 1 file.
815
+ #
816
+ # @param [String,Symbol] attr_name the name of the attribute to check (ex. :public_header_files)
817
+ #
818
+ # @param [String,Symbol] message_type the type of message to send if the patterns are empty (ex. :error)
819
+ #
820
+ def validate_nonempty_patterns(attr_name, message_type)
821
+ return unless !file_accessor.spec_consumer.send(attr_name).empty? && file_accessor.send(attr_name).empty?
822
+
823
+ add_result(message_type, 'file patterns', "The `#{attr_name}` pattern did not match any file.")
824
+ end
825
+
826
+ def _validate_vendored_libraries
827
+ file_accessor.vendored_libraries.each do |lib|
828
+ basename = File.basename(lib)
829
+ lib_name = basename.downcase
830
+ unless lib_name.end_with?('.a') && lib_name.start_with?('lib')
831
+ warning('vendored_libraries', "`#{basename}` does not match the expected static library name format `lib[name].a`")
832
+ end
833
+ end
834
+ validate_nonempty_patterns(:vendored_libraries, :warning)
835
+ end
836
+
837
+ def _validate_project_header_files
838
+ _validate_header_files(:project_header_files)
839
+ validate_nonempty_patterns(:project_header_files, :warning)
840
+ end
841
+
842
+ def _validate_private_header_files
843
+ _validate_header_files(:private_header_files)
844
+ validate_nonempty_patterns(:private_header_files, :warning)
845
+ end
846
+
847
+ def _validate_public_header_files
848
+ _validate_header_files(:public_header_files)
849
+ validate_nonempty_patterns(:public_header_files, :warning)
850
+ end
851
+
852
+ def _validate_license
853
+ unless file_accessor.license || spec.license && (spec.license[:type] == 'Public Domain' || spec.license[:text])
854
+ warning('license', 'Unable to find a license file')
855
+ end
856
+ end
857
+
858
+ def _validate_module_map
859
+ if spec.module_map
860
+ unless file_accessor.module_map.exist?
861
+ error('module_map', 'Unable to find the specified module map file.')
862
+ end
863
+ unless file_accessor.module_map.extname == '.modulemap'
864
+ relative_path = file_accessor.module_map.relative_path_from file_accessor.root
865
+ error('module_map', "Unexpected file extension for modulemap file (#{relative_path}).")
866
+ end
867
+ end
868
+ end
869
+
870
+ def _validate_resource_bundles
871
+ file_accessor.resource_bundles.each do |bundle, resource_paths|
872
+ next unless resource_paths.empty?
873
+ error('file patterns', "The `resource_bundles` pattern for `#{bundle}` did not match any file.")
874
+ end
875
+ end
876
+
877
+ # Ensures that a list of header files only contains header files.
878
+ #
879
+ def _validate_header_files(attr_name)
880
+ header_files = file_accessor.send(attr_name)
881
+ non_header_files = header_files.
882
+ select { |f| !Sandbox::FileAccessor::HEADER_EXTENSIONS.include?(f.extname) }.
883
+ map { |f| f.relative_path_from(file_accessor.root) }
884
+ unless non_header_files.empty?
885
+ error(attr_name, "The pattern matches non-header files (#{non_header_files.join(', ')}).")
886
+ end
887
+ non_source_files = header_files - file_accessor.source_files
888
+ unless non_source_files.empty?
889
+ error(attr_name, 'The pattern includes header files that are not listed ' \
890
+ "in source_files (#{non_source_files.join(', ')}).")
891
+ end
892
+ end
893
+
894
+ def _validate_header_mappings_dir
895
+ return unless header_mappings_dir = file_accessor.spec_consumer.header_mappings_dir
896
+ absolute_mappings_dir = file_accessor.root + header_mappings_dir
897
+ unless absolute_mappings_dir.directory?
898
+ error('header_mappings_dir', "The header_mappings_dir (`#{header_mappings_dir}`) is not a directory.")
899
+ end
900
+ non_mapped_headers = file_accessor.headers.
901
+ reject { |h| h.to_path.start_with?(absolute_mappings_dir.to_path) }.
902
+ map { |f| f.relative_path_from(file_accessor.root) }
903
+ unless non_mapped_headers.empty?
904
+ error('header_mappings_dir', "There are header files outside of the header_mappings_dir (#{non_mapped_headers.join(', ')}).")
905
+ end
906
+ end
907
+
908
+ #-------------------------------------------------------------------------#
909
+
910
+ private
911
+
912
+ # !@group Result Helpers
913
+
914
+ def error(*args)
915
+ add_result(:error, *args)
916
+ end
917
+
918
+ def warning(*args)
919
+ add_result(:warning, *args)
920
+ end
921
+
922
+ def note(*args)
923
+ add_result(:note, *args)
924
+ end
925
+
926
+ def translate_output_to_linter_messages(parsed_output)
927
+ parsed_output.each do |message|
928
+ # Checking the error for `InputFile` is to work around an Xcode
929
+ # issue where linting would fail even though `xcodebuild` actually
930
+ # succeeds. Xcode.app also doesn't fail when this issue occurs, so
931
+ # it's safe for us to do the same.
932
+ #
933
+ # For more details see https://github.com/CocoaPods/CocoaPods/issues/2394#issuecomment-56658587
934
+ #
935
+ if message.include?("'InputFile' should have")
936
+ next
937
+ end
938
+
939
+ if message =~ /\S+:\d+:\d+: error:/
940
+ error('xcodebuild', message)
941
+ elsif message =~ /\S+:\d+:\d+: warning:/
942
+ warning('xcodebuild', message)
943
+ else
944
+ note('xcodebuild', message)
945
+ end
946
+ end
947
+ end
948
+
949
+ def shares_pod_target_xcscheme?(pod_target)
950
+ Pathname.new(@installer.pods_project.path + pod_target.label).exist?
951
+ end
952
+
953
+ def add_result(type, attribute_name, message, public_only = false)
954
+ result = results.find do |r|
955
+ r.type == type && r.attribute_name && r.message == message && r.public_only? == public_only
956
+ end
957
+ unless result
958
+ result = Result.new(type, attribute_name, message, public_only)
959
+ results << result
960
+ end
961
+ result.platforms << consumer.platform_name if consumer
962
+ result.subspecs << subspec_name if subspec_name && !result.subspecs.include?(subspec_name)
963
+ end
964
+
965
+ # Specialized Result to support subspecs aggregation
966
+ #
967
+ class Result < Specification::Linter::Results::Result
968
+ def initialize(type, attribute_name, message, public_only = false)
969
+ super(type, attribute_name, message, public_only)
970
+ @subspecs = []
971
+ end
972
+
973
+ attr_reader :subspecs
974
+ end
975
+
976
+ #-------------------------------------------------------------------------#
977
+
978
+ private
979
+
980
+ # !@group Helpers
981
+
982
+ # @return [Array<String>] an array of source URLs used to create the
983
+ # {Podfile} used in the linting process
984
+ #
985
+ attr_reader :source_urls
986
+
987
+ # @param [String] platform_name
988
+ # the name of the platform, which should be declared
989
+ # in the Podfile.
990
+ #
991
+ # @param [String] deployment_target
992
+ # the deployment target, which should be declared in
993
+ # the Podfile.
994
+ #
995
+ # @param [Bool] use_frameworks
996
+ # whether frameworks should be used for the installation
997
+ #
998
+ # @param [Array<String>] test_spec_names
999
+ # the test spec names to include in the podfile.
1000
+ #
1001
+ # @return [Podfile] a podfile that requires the specification on the
1002
+ # current platform.
1003
+ #
1004
+ # @note The generated podfile takes into account whether the linter is
1005
+ # in local mode.
1006
+ #
1007
+ def podfile_from_spec(platform_name, deployment_target, use_frameworks = true, test_spec_names = [], use_modular_headers = false, use_static_frameworks = false)
1008
+ name = subspec_name || spec.name
1009
+ podspec = file.realpath
1010
+ local = local?
1011
+ urls = source_urls
1012
+
1013
+ additional_podspec_pods = external_podspecs ? Dir.glob(external_podspecs) : []
1014
+ additional_path_pods = (include_podspecs ? Dir.glob(include_podspecs) : []) .select { |path| spec.name != Specification.from_file(path).name } - additional_podspec_pods
1015
+
1016
+ Pod::Podfile.new do
1017
+ install! 'cocoapods', :deterministic_uuids => false, :warn_for_unused_master_specs_repo => false
1018
+ # By default inhibit warnings for all pods, except the one being validated.
1019
+ inhibit_all_warnings!
1020
+ urls.each { |u| source(u) }
1021
+ target 'App' do
1022
+ if use_static_frameworks
1023
+ use_frameworks!(:linkage => :static)
1024
+ else
1025
+ use_frameworks!(use_frameworks)
1026
+ end
1027
+ use_modular_headers! if use_modular_headers
1028
+ platform(platform_name, deployment_target)
1029
+ if local
1030
+ pod name, :path => podspec.dirname.to_s, :inhibit_warnings => false
1031
+ else
1032
+ pod name, :podspec => podspec.to_s, :inhibit_warnings => false
1033
+ end
1034
+
1035
+ additional_path_pods.each do |podspec_path|
1036
+ podspec_name = File.basename(podspec_path, '.*')
1037
+ pod podspec_name, :path => File.dirname(podspec_path)
1038
+ end
1039
+
1040
+ additional_podspec_pods.each do |podspec_path|
1041
+ podspec_name = File.basename(podspec_path, '.*')
1042
+ pod podspec_name, :podspec => podspec_path
1043
+ end
1044
+
1045
+ test_spec_names.each do |test_spec_name|
1046
+ if local
1047
+ pod test_spec_name, :path => podspec.dirname.to_s, :inhibit_warnings => false
1048
+ else
1049
+ pod test_spec_name, :podspec => podspec.to_s, :inhibit_warnings => false
1050
+ end
1051
+ end
1052
+ end
1053
+ end
1054
+ end
1055
+
1056
+ # Parse the xcode build output to identify the lines which are relevant
1057
+ # to the linter.
1058
+ #
1059
+ # @param [String] output the output generated by the xcodebuild tool.
1060
+ #
1061
+ # @note The indentation and the temporary path is stripped form the
1062
+ # lines.
1063
+ #
1064
+ # @return [Array<String>] the lines that are relevant to the linter.
1065
+ #
1066
+ def parse_xcodebuild_output(output)
1067
+ lines = output.split("\n")
1068
+ selected_lines = lines.select do |l|
1069
+ l.include?('error: ') && (l !~ /errors? generated\./) && (l !~ /error: \(null\)/) ||
1070
+ l.include?('warning: ') && (l !~ /warnings? generated\./) && (l !~ /frameworks only run on iOS 8/) ||
1071
+ l.include?('note: ') && (l !~ /expanded from macro/)
1072
+ end
1073
+ selected_lines.map do |l|
1074
+ new = l.force_encoding('UTF-8').gsub(%r{#{validation_dir}/Pods/}, '')
1075
+ new.gsub!(/^ */, ' ')
1076
+ end
1077
+ end
1078
+
1079
+ # @return [String] Executes xcodebuild in the current working directory and
1080
+ # returns its output (both STDOUT and STDERR).
1081
+ #
1082
+ def xcodebuild(action, scheme, configuration, deployment_target:)
1083
+ require 'fourflusher'
1084
+ command = %W(clean #{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration})
1085
+ case consumer.platform_name
1086
+ when :osx, :macos
1087
+ command += %w(CODE_SIGN_IDENTITY=)
1088
+ when :ios
1089
+ command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator)
1090
+ command += Fourflusher::SimControl.new.destination(:oldest, 'iOS', deployment_target)
1091
+ xcconfig = consumer.pod_target_xcconfig
1092
+ if xcconfig
1093
+ archs = xcconfig['VALID_ARCHS']
1094
+ if archs && (archs.include? 'armv7') && !(archs.include? 'i386') && (archs.include? 'x86_64')
1095
+ # Prevent Xcodebuild from testing the non-existent i386 simulator if armv7 is specified without i386
1096
+ command += %w(ARCHS=x86_64)
1097
+ end
1098
+ end
1099
+ when :watchos
1100
+ command += %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator)
1101
+ command += Fourflusher::SimControl.new.destination(:oldest, 'watchOS', deployment_target)
1102
+ when :tvos
1103
+ command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator)
1104
+ command += Fourflusher::SimControl.new.destination(:oldest, 'tvOS', deployment_target)
1105
+ end
1106
+
1107
+ if analyze
1108
+ command += %w(CLANG_ANALYZER_OUTPUT=html CLANG_ANALYZER_OUTPUT_DIR=analyzer)
1109
+ end
1110
+
1111
+ begin
1112
+ _xcodebuild(command, true)
1113
+ rescue => e
1114
+ message = 'Returned an unsuccessful exit code.'
1115
+ message += ' You can use `--verbose` for more information.' unless config.verbose?
1116
+ error('xcodebuild', message)
1117
+ e.message
1118
+ end
1119
+ end
1120
+
1121
+ # Executes the given command in the current working directory.
1122
+ #
1123
+ # @return [String] The output of the given command
1124
+ #
1125
+ def _xcodebuild(command, raise_on_failure = false)
1126
+ Executable.execute_command('xcodebuild', command, raise_on_failure)
1127
+ end
1128
+
1129
+ # Whether the platform with the specified name is valid
1130
+ #
1131
+ # @param [Platform] platform
1132
+ # The platform to check
1133
+ #
1134
+ # @return [Bool] True if the platform is valid
1135
+ #
1136
+ def valid_platform?(platform)
1137
+ VALID_PLATFORMS.any? { |p| p.name == platform.name }
1138
+ end
1139
+
1140
+ # Whether the platform is supported by the specification
1141
+ #
1142
+ # @param [Platform] platform
1143
+ # The platform to check
1144
+ #
1145
+ # @param [Specification] spec
1146
+ # The specification which must support the provided platform
1147
+ #
1148
+ # @return [Bool] Whether the platform is supported by the specification
1149
+ #
1150
+ def supported_platform?(platform, spec)
1151
+ available_platforms = spec.available_platforms
1152
+
1153
+ available_platforms.any? { |p| p.name == platform.name }
1154
+ end
1155
+
1156
+ # Whether the provided name matches the platform
1157
+ #
1158
+ # @param [Platform] platform
1159
+ # The platform
1160
+ #
1161
+ # @param [String] name
1162
+ # The name to check against the provided platform
1163
+ #
1164
+ def platform_name_match?(platform, name)
1165
+ [platform.name, platform.string_name].any? { |n| n.casecmp(name) == 0 }
1166
+ end
1167
+
1168
+ #-------------------------------------------------------------------------#
1169
+ end
1170
+ end