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.
- checksums.yaml +7 -0
- data/lib/cocoapods-tt/command/native/install.rb +56 -0
- data/lib/cocoapods-tt/command/native/update.rb +157 -0
- data/lib/cocoapods-tt/command/tt/make.rb +92 -0
- data/lib/cocoapods-tt/command/tt.rb +115 -0
- data/lib/cocoapods-tt/command.rb +1 -0
- data/lib/cocoapods-tt/gem_version.rb +3 -0
- data/lib/cocoapods-tt/native/command.rb +185 -0
- data/lib/cocoapods-tt/native/config.rb +366 -0
- data/lib/cocoapods-tt/native/core_overrides.rb +1 -0
- data/lib/cocoapods-tt/native/downloader/cache.rb +322 -0
- data/lib/cocoapods-tt/native/downloader/request.rb +86 -0
- data/lib/cocoapods-tt/native/downloader/response.rb +16 -0
- data/lib/cocoapods-tt/native/downloader.rb +192 -0
- data/lib/cocoapods-tt/native/executable.rb +247 -0
- data/lib/cocoapods-tt/native/external_sources/abstract_external_source.rb +205 -0
- data/lib/cocoapods-tt/native/external_sources/downloader_source.rb +30 -0
- data/lib/cocoapods-tt/native/external_sources/path_source.rb +55 -0
- data/lib/cocoapods-tt/native/external_sources/podspec_source.rb +54 -0
- data/lib/cocoapods-tt/native/external_sources.rb +57 -0
- data/lib/cocoapods-tt/native/gem_version.rb +5 -0
- data/lib/cocoapods-tt/native/generator/acknowledgements/markdown.rb +44 -0
- data/lib/cocoapods-tt/native/generator/acknowledgements/plist.rb +94 -0
- data/lib/cocoapods-tt/native/generator/acknowledgements.rb +107 -0
- data/lib/cocoapods-tt/native/generator/app_target_helper.rb +363 -0
- data/lib/cocoapods-tt/native/generator/bridge_support.rb +22 -0
- data/lib/cocoapods-tt/native/generator/constant.rb +19 -0
- data/lib/cocoapods-tt/native/generator/copy_dsyms_script.rb +56 -0
- data/lib/cocoapods-tt/native/generator/copy_resources_script.rb +223 -0
- data/lib/cocoapods-tt/native/generator/copy_xcframework_script.rb +227 -0
- data/lib/cocoapods-tt/native/generator/dummy_source.rb +31 -0
- data/lib/cocoapods-tt/native/generator/embed_frameworks_script.rb +196 -0
- data/lib/cocoapods-tt/native/generator/file_list.rb +39 -0
- data/lib/cocoapods-tt/native/generator/header.rb +103 -0
- data/lib/cocoapods-tt/native/generator/info_plist_file.rb +128 -0
- data/lib/cocoapods-tt/native/generator/module_map.rb +99 -0
- data/lib/cocoapods-tt/native/generator/prefix_header.rb +60 -0
- data/lib/cocoapods-tt/native/generator/script_phase_constants.rb +100 -0
- data/lib/cocoapods-tt/native/generator/umbrella_header.rb +46 -0
- data/lib/cocoapods-tt/native/hooks_manager.rb +132 -0
- data/lib/cocoapods-tt/native/installer/analyzer/analysis_result.rb +87 -0
- data/lib/cocoapods-tt/native/installer/analyzer/locking_dependency_analyzer.rb +103 -0
- data/lib/cocoapods-tt/native/installer/analyzer/pod_variant.rb +87 -0
- data/lib/cocoapods-tt/native/installer/analyzer/pod_variant_set.rb +175 -0
- data/lib/cocoapods-tt/native/installer/analyzer/podfile_dependency_cache.rb +55 -0
- data/lib/cocoapods-tt/native/installer/analyzer/sandbox_analyzer.rb +268 -0
- data/lib/cocoapods-tt/native/installer/analyzer/specs_state.rb +108 -0
- data/lib/cocoapods-tt/native/installer/analyzer/target_inspection_result.rb +58 -0
- data/lib/cocoapods-tt/native/installer/analyzer/target_inspector.rb +258 -0
- data/lib/cocoapods-tt/native/installer/analyzer.rb +1204 -0
- data/lib/cocoapods-tt/native/installer/base_install_hooks_context.rb +135 -0
- data/lib/cocoapods-tt/native/installer/installation_options.rb +195 -0
- data/lib/cocoapods-tt/native/installer/pod_source_installer.rb +224 -0
- data/lib/cocoapods-tt/native/installer/pod_source_preparer.rb +77 -0
- data/lib/cocoapods-tt/native/installer/podfile_validator.rb +168 -0
- data/lib/cocoapods-tt/native/installer/post_install_hooks_context.rb +9 -0
- data/lib/cocoapods-tt/native/installer/post_integrate_hooks_context.rb +9 -0
- data/lib/cocoapods-tt/native/installer/pre_install_hooks_context.rb +51 -0
- data/lib/cocoapods-tt/native/installer/pre_integrate_hooks_context.rb +9 -0
- data/lib/cocoapods-tt/native/installer/project_cache/project_cache.rb +11 -0
- data/lib/cocoapods-tt/native/installer/project_cache/project_cache_analysis_result.rb +53 -0
- data/lib/cocoapods-tt/native/installer/project_cache/project_cache_analyzer.rb +200 -0
- data/lib/cocoapods-tt/native/installer/project_cache/project_cache_version.rb +43 -0
- data/lib/cocoapods-tt/native/installer/project_cache/project_installation_cache.rb +103 -0
- data/lib/cocoapods-tt/native/installer/project_cache/project_metadata_cache.rb +73 -0
- data/lib/cocoapods-tt/native/installer/project_cache/target_cache_key.rb +176 -0
- data/lib/cocoapods-tt/native/installer/project_cache/target_metadata.rb +74 -0
- data/lib/cocoapods-tt/native/installer/sandbox_dir_cleaner.rb +105 -0
- data/lib/cocoapods-tt/native/installer/sandbox_header_paths_installer.rb +45 -0
- data/lib/cocoapods-tt/native/installer/source_provider_hooks_context.rb +34 -0
- data/lib/cocoapods-tt/native/installer/target_uuid_generator.rb +34 -0
- data/lib/cocoapods-tt/native/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb +179 -0
- data/lib/cocoapods-tt/native/installer/user_project_integrator/target_integrator.rb +815 -0
- data/lib/cocoapods-tt/native/installer/user_project_integrator.rb +280 -0
- data/lib/cocoapods-tt/native/installer/xcode/multi_pods_project_generator.rb +82 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/aggregate_target_dependency_installer.rb +66 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/aggregate_target_installer.rb +192 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/app_host_installer.rb +154 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/file_references_installer.rb +329 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/pod_target_dependency_installer.rb +195 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/pod_target_installer.rb +1239 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/pod_target_integrator.rb +312 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/pods_project_writer.rb +90 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/project_generator.rb +120 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/target_installation_result.rb +140 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/target_installer.rb +257 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator/target_installer_helper.rb +110 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator.rb +291 -0
- data/lib/cocoapods-tt/native/installer/xcode/pods_project_generator_result.rb +54 -0
- data/lib/cocoapods-tt/native/installer/xcode/single_pods_project_generator.rb +38 -0
- data/lib/cocoapods-tt/native/installer/xcode/target_validator.rb +170 -0
- data/lib/cocoapods-tt/native/installer/xcode.rb +11 -0
- data/lib/cocoapods-tt/native/installer.rb +1044 -0
- data/lib/cocoapods-tt/native/native_target_extension.rb +60 -0
- data/lib/cocoapods-tt/native/open-uri.rb +33 -0
- data/lib/cocoapods-tt/native/podfile.rb +13 -0
- data/lib/cocoapods-tt/native/project.rb +544 -0
- data/lib/cocoapods-tt/native/resolver/lazy_specification.rb +88 -0
- data/lib/cocoapods-tt/native/resolver/resolver_specification.rb +41 -0
- data/lib/cocoapods-tt/native/resolver.rb +600 -0
- data/lib/cocoapods-tt/native/sandbox/file_accessor.rb +532 -0
- data/lib/cocoapods-tt/native/sandbox/headers_store.rb +163 -0
- data/lib/cocoapods-tt/native/sandbox/path_list.rb +242 -0
- data/lib/cocoapods-tt/native/sandbox/pod_dir_cleaner.rb +71 -0
- data/lib/cocoapods-tt/native/sandbox/podspec_finder.rb +23 -0
- data/lib/cocoapods-tt/native/sandbox.rb +470 -0
- data/lib/cocoapods-tt/native/sources_manager.rb +221 -0
- data/lib/cocoapods-tt/native/target/aggregate_target.rb +558 -0
- data/lib/cocoapods-tt/native/target/build_settings.rb +1385 -0
- data/lib/cocoapods-tt/native/target/pod_target.rb +1168 -0
- data/lib/cocoapods-tt/native/target.rb +378 -0
- data/lib/cocoapods-tt/native/user_interface/error_report.rb +204 -0
- data/lib/cocoapods-tt/native/user_interface/inspector_reporter.rb +102 -0
- data/lib/cocoapods-tt/native/user_interface.rb +463 -0
- data/lib/cocoapods-tt/native/validator.rb +1170 -0
- data/lib/cocoapods-tt/native/version_metadata.rb +26 -0
- data/lib/cocoapods-tt/native/xcode/framework_paths.rb +54 -0
- data/lib/cocoapods-tt/native/xcode/linkage_analyzer.rb +22 -0
- data/lib/cocoapods-tt/native/xcode/xcframework/xcframework_slice.rb +138 -0
- data/lib/cocoapods-tt/native/xcode/xcframework.rb +99 -0
- data/lib/cocoapods-tt/native/xcode.rb +7 -0
- data/lib/cocoapods-tt.rb +1 -0
- data/lib/cocoapods_plugin.rb +17 -0
- 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
|