cocoapods-modularization 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/cocoapods-modularization/command/install.rb +98 -0
- data/lib/cocoapods-modularization/command/mod/add.rb +97 -0
- data/lib/cocoapods-modularization/command/mod/base.rb +35 -0
- data/lib/cocoapods-modularization/command/mod/binary.rb +82 -0
- data/lib/cocoapods-modularization/command/mod/config.rb +51 -0
- data/lib/cocoapods-modularization/command/mod/create.rb +34 -0
- data/lib/cocoapods-modularization/command/mod/inspect.rb +26 -0
- data/lib/cocoapods-modularization/command/mod/source.rb +81 -0
- data/lib/cocoapods-modularization/command/mod/sync.rb +29 -0
- data/lib/cocoapods-modularization/command/mod/update.rb +64 -0
- data/lib/cocoapods-modularization/command/mod.rb +28 -0
- data/lib/cocoapods-modularization/command.rb +2 -0
- data/lib/cocoapods-modularization/gem_version.rb +3 -0
- data/lib/cocoapods-modularization/generate/configuration.rb +364 -0
- data/lib/cocoapods-modularization/generate/installer.rb +388 -0
- data/lib/cocoapods-modularization/generate/podfile_generator.rb +398 -0
- data/lib/cocoapods-modularization/generate.rb +7 -0
- data/lib/cocoapods-modularization/meta/meta_accessor.rb +134 -0
- data/lib/cocoapods-modularization/meta/meta_constants.rb +135 -0
- data/lib/cocoapods-modularization/meta/meta_reference.rb +255 -0
- data/lib/cocoapods-modularization/meta.rb +7 -0
- data/lib/cocoapods-modularization/private/private_cache.rb +277 -0
- data/lib/cocoapods-modularization/private.rb +5 -0
- data/lib/cocoapods-modularization.rb +1 -0
- data/lib/cocoapods_plugin.rb +1 -0
- metadata +96 -0
@@ -0,0 +1,364 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pod
|
4
|
+
module Generate
|
5
|
+
class Configuration
|
6
|
+
@options = []
|
7
|
+
class << self
|
8
|
+
# @return [Array<Option>]
|
9
|
+
# all of the options available in the configuration
|
10
|
+
#
|
11
|
+
attr_reader :options
|
12
|
+
end
|
13
|
+
|
14
|
+
Option = Struct.new(:name, :type, :default, :message, :arg_name, :validator, :coercer) do
|
15
|
+
def validate(value)
|
16
|
+
return if value.nil?
|
17
|
+
errors = []
|
18
|
+
if (exceptions = Array(value).grep(Exception)) && exceptions.any?
|
19
|
+
errors << "Error computing #{name}"
|
20
|
+
errors.concat exceptions.map(&:message)
|
21
|
+
else
|
22
|
+
errors << "got type #{value.class}, expected object of type #{Array(type).join('|')}" unless Array(type).any? { |t| t === value }
|
23
|
+
validator_errors = begin
|
24
|
+
validator && validator[value]
|
25
|
+
rescue StandardError
|
26
|
+
"failed to run validator (#{$ERROR_INFO})"
|
27
|
+
end
|
28
|
+
errors.concat Array(validator_errors) if validator_errors
|
29
|
+
errors.unshift "#{value.inspect} invalid for #{name}" if errors.any?
|
30
|
+
end
|
31
|
+
errors.join(', ') unless errors.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
def cli_name
|
35
|
+
return unless message
|
36
|
+
@cli_name ||= name.to_s.tr '_', '-'
|
37
|
+
end
|
38
|
+
|
39
|
+
def flag?
|
40
|
+
arg_name.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def coerce(value)
|
44
|
+
coercer ? coercer[value] : value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
private_constant :Option
|
48
|
+
|
49
|
+
# Declares a new option
|
50
|
+
#
|
51
|
+
# @!macro [attach] $0
|
52
|
+
# @attribute [r] $1
|
53
|
+
# @return [$2] $4
|
54
|
+
# defaults to `$3`
|
55
|
+
#
|
56
|
+
def self.option(*args)
|
57
|
+
options << Option.new(*args)
|
58
|
+
end
|
59
|
+
private_class_method :option
|
60
|
+
|
61
|
+
# @visibility private
|
62
|
+
#
|
63
|
+
# Implements `===` to do type checking against an array.
|
64
|
+
#
|
65
|
+
class ArrayOf
|
66
|
+
attr_reader :types
|
67
|
+
|
68
|
+
def initialize(*types)
|
69
|
+
@types = types
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
"Array<#{types.join('|')}>"
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [Boolean] whether the given object is an array with elements all of the given types
|
77
|
+
#
|
78
|
+
def ===(other)
|
79
|
+
other.is_a?(Array) && other.all? { |o| types.any? { |t| t === o } }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
private_constant :ArrayOf
|
83
|
+
|
84
|
+
# @visibility private
|
85
|
+
#
|
86
|
+
# Implements `===` to do type checking against a hash.
|
87
|
+
#
|
88
|
+
class HashOf
|
89
|
+
attr_reader :key_types, :value_types
|
90
|
+
|
91
|
+
def initialize(keys:, values:)
|
92
|
+
@key_types = keys
|
93
|
+
@value_types = values
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
"Hash<#{key_types.join('|')} => #{value_types.join('|')}}>"
|
98
|
+
end
|
99
|
+
|
100
|
+
# @return [Boolean] whether the given object is a hash with elements all of the given types
|
101
|
+
#
|
102
|
+
def ===(other)
|
103
|
+
other.is_a?(Hash) && other.all? do |key, value|
|
104
|
+
key_types.any? { |t| t === key } &&
|
105
|
+
value_types.any? { |t| t === value }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
private_constant :HashOf
|
110
|
+
|
111
|
+
coerce_to_bool = lambda do |value|
|
112
|
+
if value.is_a?(String)
|
113
|
+
value =
|
114
|
+
case value.downcase
|
115
|
+
when ''
|
116
|
+
nil
|
117
|
+
when 'true'
|
118
|
+
true
|
119
|
+
when 'false'
|
120
|
+
false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
value
|
124
|
+
end
|
125
|
+
|
126
|
+
coerce_to_pathname = lambda do |path|
|
127
|
+
path && Pathname(path).expand_path
|
128
|
+
end
|
129
|
+
|
130
|
+
BOOLEAN = [TrueClass, FalseClass].freeze
|
131
|
+
private_constant :BOOLEAN
|
132
|
+
|
133
|
+
option :pod_config, Config, 'Pod::Config.instance', nil
|
134
|
+
|
135
|
+
option :podfile_path, [String, Pathname], 'pod_config.podfile_path', 'Path to podfile to use', 'PATH', ->(path) { 'file does not exist' unless path.file? }, coerce_to_pathname
|
136
|
+
option :podfile, [Podfile], 'Podfile.from_file(podfile_path) if (podfile_path && File.file?(File.expand_path(podfile_path)))'
|
137
|
+
option :use_podfile, BOOLEAN, '!!podfile', 'Whether restrictions should be copied from the podfile', nil, nil, coerce_to_bool
|
138
|
+
option :use_podfile_plugins, BOOLEAN, 'use_podfile', 'Whether plugins should be copied from the podfile', nil, nil, coerce_to_bool
|
139
|
+
option :podfile_plugins, HashOf.new(keys: [String], values: [NilClass, HashOf.new(keys: [String], values: [TrueClass, FalseClass, NilClass, String, Hash, Array])]),
|
140
|
+
'(use_podfile && podfile) ? podfile.plugins : {}',
|
141
|
+
nil,
|
142
|
+
nil,
|
143
|
+
nil,
|
144
|
+
->(hash) { Hash[hash] }
|
145
|
+
|
146
|
+
option :lockfile, [Pod::Lockfile], 'pod_config.lockfile', nil
|
147
|
+
option :use_lockfile, BOOLEAN, '!!lockfile', 'Whether the lockfile should be used to discover transitive dependencies', nil, nil, coerce_to_bool
|
148
|
+
option :use_lockfile_versions, BOOLEAN, 'use_lockfile', 'Whether versions from the lockfile should be used', nil, nil, coerce_to_bool
|
149
|
+
|
150
|
+
option :use_libraries, BOOLEAN, 'false', 'Whether to use libraries instead of frameworks', nil, nil, coerce_to_bool
|
151
|
+
|
152
|
+
option :generate_multiple_pod_projects, BOOLEAN, 'false', 'Whether to generate multiple Xcode projects', nil, nil, coerce_to_bool
|
153
|
+
option :incremental_installation, BOOLEAN, 'false', 'Whether to use incremental installation', nil, nil, coerce_to_bool
|
154
|
+
|
155
|
+
option :gen_directory, [String, Pathname], 'Pathname("gen").expand_path', 'Path to generate workspaces in', 'PATH', ->(path) { 'path is file' if path.file? }, coerce_to_pathname
|
156
|
+
option :auto_open, BOOLEAN, 'false', 'Whether to automatically open the generated workspaces', nil, nil, coerce_to_bool
|
157
|
+
option :clean, BOOLEAN, 'false', 'Whether to clean the generated directories before generating', nil, nil, coerce_to_bool
|
158
|
+
|
159
|
+
option :app_host_source_dir, [String, Pathname], 'nil',
|
160
|
+
'A directory containing sources to use for the app host',
|
161
|
+
'DIR',
|
162
|
+
->(dir) { 'not a directory' unless dir.directory? },
|
163
|
+
coerce_to_pathname
|
164
|
+
|
165
|
+
option :podspec_paths, ArrayOf.new(String, Pathname, URI),
|
166
|
+
'[Pathname(?.)]',
|
167
|
+
nil,
|
168
|
+
nil,
|
169
|
+
->(paths) { ('paths do not exist' unless paths.all? { |p| p.is_a?(URI) || p.exist? }) },
|
170
|
+
->(paths) { paths && paths.map { |path| path.to_s =~ %r{https?://} ? URI(path) : Pathname(path).expand_path } }
|
171
|
+
option :podspecs, ArrayOf.new(Pod::Specification),
|
172
|
+
'self.class.podspecs_from_paths(podspec_paths)',
|
173
|
+
nil,
|
174
|
+
nil,
|
175
|
+
->(specs) { 'no podspecs found' if specs.empty? },
|
176
|
+
->(paths) { paths && paths.map { |path| Pathname(path).expand_path } }
|
177
|
+
|
178
|
+
# installer options
|
179
|
+
option :sources, ArrayOf.new(String),
|
180
|
+
'if use_podfile && podfile then ::Pod::Installer::Analyzer.new(:sandbox, podfile).sources.map(&:url) else pod_config.sources_manager.all.map(&:url) end',
|
181
|
+
'The sources from which to pull dependent pods (defaults to all repos in the podfile if using the podfile, else all available repos). Can be a repo name or URL. Multiple sources must be comma-delimited.',
|
182
|
+
'SOURCE1,SOURCE2',
|
183
|
+
->(_) { nil },
|
184
|
+
->(sources) { Array(sources).flat_map { |s| s.split(',') } }
|
185
|
+
option :local_sources, ArrayOf.new(String),
|
186
|
+
[],
|
187
|
+
'Paths from which to find local podspecs for transitive dependencies. Multiple local-sources must be comma-delimited.',
|
188
|
+
'SOURCE1,SOURCE2',
|
189
|
+
->(_) { nil },
|
190
|
+
->(local_sources) { Array(local_sources).flat_map { |s| s.split(',') } }
|
191
|
+
option :platforms, ArrayOf.new(String),
|
192
|
+
nil,
|
193
|
+
'Limit to specific platforms. Default is all platforms supported by the podspec. Multiple platforms must be comma-delimited.',
|
194
|
+
'ios,macos',
|
195
|
+
lambda { |platforms|
|
196
|
+
valid_platforms = Platform.all.map { |p| p.string_name.downcase }
|
197
|
+
valid_platforms unless (platforms - valid_platforms).empty?
|
198
|
+
}, # validates platforms is a subset of Platform.all
|
199
|
+
->(platforms) { Array(platforms).flat_map { |s| s.split(',') } }
|
200
|
+
option :repo_update, BOOLEAN, 'false', 'Force running `pod repo update` before install', nil, nil, coerce_to_bool
|
201
|
+
option :use_default_plugins, BOOLEAN, 'false', 'Whether installation should activate default plugins', nil, nil, coerce_to_bool
|
202
|
+
option :deterministic_uuids, BOOLEAN, 'false', 'Whether installation should use deterministic UUIDs for pods projects', nil, nil, coerce_to_bool
|
203
|
+
option :share_schemes_for_development_pods, BOOLEAN, 'true', 'Whether installation should share schemes for development pods', nil, nil, coerce_to_bool
|
204
|
+
option :warn_for_multiple_pod_sources, BOOLEAN, 'false', 'Whether installation should warn when a pod is found in multiple sources', nil, nil, coerce_to_bool
|
205
|
+
option :use_modular_headers, BOOLEAN, 'false', 'Whether the target should be generated as a clang module, treating dependencies as modules, as if `use_modular_headers!` were specified. Will error if both this option and a podfile are specified', nil, nil, coerce_to_bool
|
206
|
+
|
207
|
+
options.freeze
|
208
|
+
options.each do |o|
|
209
|
+
attr_reader o.name
|
210
|
+
alias_method :"#{o.name}?", o.name if o.type == BOOLEAN
|
211
|
+
end
|
212
|
+
|
213
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
214
|
+
# @!visibility private
|
215
|
+
def initialize(
|
216
|
+
#{options.map { |o| "#{o.name}: (begin (#{o.default}); rescue => e; e; end)" }.join(', ')}
|
217
|
+
)
|
218
|
+
#{options.map { |o| "@#{o.name} = #{o.name}" }.join('; ')}
|
219
|
+
end
|
220
|
+
RUBY
|
221
|
+
|
222
|
+
# @return [Hash<Symbol,Object>] the configuration hash parsed from the given file
|
223
|
+
#
|
224
|
+
# @param [Pathname] path
|
225
|
+
#
|
226
|
+
# @raises [Informative] if the file does not exist or is not a YAML hash
|
227
|
+
#
|
228
|
+
def self.from_file(path)
|
229
|
+
raise Informative, "No cocoapods-generate configuration found at #{UI.path path}" unless path.file?
|
230
|
+
require 'yaml'
|
231
|
+
yaml = YAML.load_file(path)
|
232
|
+
unless yaml.is_a?(Hash)
|
233
|
+
unless path.read.strip.empty?
|
234
|
+
raise Informative, "Hash not found in configuration at #{UI.path path} -- got #{yaml.inspect}"
|
235
|
+
end
|
236
|
+
yaml = {}
|
237
|
+
end
|
238
|
+
yaml = yaml.with_indifferent_access
|
239
|
+
|
240
|
+
Dir.chdir(path.dirname) do
|
241
|
+
options.each_with_object({}) do |option, config|
|
242
|
+
next unless yaml.key?(option.name)
|
243
|
+
config[option.name] = option.coerce yaml[option.name]
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# @return [Hash<Symbol,Object>] the configuration hash parsed from the env
|
249
|
+
#
|
250
|
+
# @param [ENV,Hash<String,String>] env
|
251
|
+
#
|
252
|
+
def self.from_env(env = ENV)
|
253
|
+
options.each_with_object({}) do |option, config|
|
254
|
+
next unless (value = env["COCOAPODS_GENERATE_#{option.name.upcase}"])
|
255
|
+
config[option.name] = option.coerce(value)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# @return [Array<String>] errors in the configuration
|
260
|
+
#
|
261
|
+
def validate
|
262
|
+
hash = to_h
|
263
|
+
self.class.options.map do |option|
|
264
|
+
option.validate(hash[option.name])
|
265
|
+
end.compact
|
266
|
+
end
|
267
|
+
|
268
|
+
# @return [Configuration] a new configuration object with the given changes applies
|
269
|
+
#
|
270
|
+
# @param [Hash<Symbol,Object>] changes
|
271
|
+
#
|
272
|
+
def with_changes(changes)
|
273
|
+
self.class.new(**to_h.merge(changes))
|
274
|
+
end
|
275
|
+
|
276
|
+
# @return [Hash<Symbol,Object>]
|
277
|
+
# a hash where the keys are option names and values are the non-nil set values
|
278
|
+
#
|
279
|
+
def to_h
|
280
|
+
self.class.options.each_with_object({}) do |option, hash|
|
281
|
+
value = send(option.name)
|
282
|
+
next if value.nil?
|
283
|
+
hash[option.name] = value
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# @return [Boolean] whether this configuration is equivalent to other
|
288
|
+
#
|
289
|
+
def ==(other)
|
290
|
+
self.class == other.class &&
|
291
|
+
to_h == other.to_h
|
292
|
+
end
|
293
|
+
|
294
|
+
# @return [String] a string describing the configuration, suitable for UI presentation
|
295
|
+
#
|
296
|
+
def to_s
|
297
|
+
hash = to_h
|
298
|
+
hash.delete(:pod_config)
|
299
|
+
hash.each_with_index.each_with_object('`pod gen` configuration {'.dup) do |((k, v), i), s|
|
300
|
+
s << ',' unless i.zero?
|
301
|
+
s << "\n" << ' ' << k.to_s << ': ' << v.to_s.gsub(/:0x\h+/, '')
|
302
|
+
end << ' }'
|
303
|
+
end
|
304
|
+
|
305
|
+
# @return [Pathname] the directory for installation of the generated workspace
|
306
|
+
#
|
307
|
+
# @param [String] name the name of the pod
|
308
|
+
#
|
309
|
+
def gen_dir_for_pod(name)
|
310
|
+
gen_directory.join(name)
|
311
|
+
end
|
312
|
+
|
313
|
+
# @return [Boolean] whether gen should install with dynamic frameworks
|
314
|
+
#
|
315
|
+
def use_frameworks?
|
316
|
+
!use_libraries?
|
317
|
+
end
|
318
|
+
|
319
|
+
# @return [String] The project name to use for generating this workspace.
|
320
|
+
#
|
321
|
+
# @param [Specification] spec
|
322
|
+
# the specification to generate project name for.
|
323
|
+
#
|
324
|
+
def project_name_for_spec(spec)
|
325
|
+
project_name = spec.name.dup
|
326
|
+
# When using multiple Xcode project the project name will collide with the actual .xcodeproj meant for the pod
|
327
|
+
# that we are generating the workspace for.
|
328
|
+
project_name << 'Sample' if generate_multiple_pod_projects?
|
329
|
+
project_name
|
330
|
+
end
|
331
|
+
|
332
|
+
# @return [Array<Specification>] the podspecs found at the given paths.
|
333
|
+
# This method will download specs from URLs and traverse a directory's children.
|
334
|
+
#
|
335
|
+
# @param [Array<Pathname,URI>] paths
|
336
|
+
# the paths to search for podspecs
|
337
|
+
#
|
338
|
+
def self.podspecs_from_paths(paths)
|
339
|
+
paths = [Pathname('.')] if paths.empty?
|
340
|
+
paths.flat_map do |path|
|
341
|
+
if path.is_a?(URI)
|
342
|
+
require 'cocoapods/open-uri'
|
343
|
+
begin
|
344
|
+
contents = open(path.to_s).read
|
345
|
+
rescue StandardError => e
|
346
|
+
next e
|
347
|
+
end
|
348
|
+
begin
|
349
|
+
Pod::Specification.from_string contents, path.to_s
|
350
|
+
rescue StandardError
|
351
|
+
$ERROR_INFO
|
352
|
+
end
|
353
|
+
elsif path.directory?
|
354
|
+
glob = Pathname.glob(path + '*.podspec{.json,}')
|
355
|
+
next StandardError.new "no specs found in #{UI.path path}" if glob.empty?
|
356
|
+
glob.map { |f| Pod::Specification.from_file(f) }.sort_by(&:name)
|
357
|
+
else
|
358
|
+
Pod::Specification.from_file(path)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|