cocoapods-modularization 0.0.2

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 (27) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cocoapods-modularization/command/install.rb +98 -0
  3. data/lib/cocoapods-modularization/command/mod/add.rb +97 -0
  4. data/lib/cocoapods-modularization/command/mod/base.rb +35 -0
  5. data/lib/cocoapods-modularization/command/mod/binary.rb +82 -0
  6. data/lib/cocoapods-modularization/command/mod/config.rb +51 -0
  7. data/lib/cocoapods-modularization/command/mod/create.rb +34 -0
  8. data/lib/cocoapods-modularization/command/mod/inspect.rb +26 -0
  9. data/lib/cocoapods-modularization/command/mod/source.rb +81 -0
  10. data/lib/cocoapods-modularization/command/mod/sync.rb +29 -0
  11. data/lib/cocoapods-modularization/command/mod/update.rb +64 -0
  12. data/lib/cocoapods-modularization/command/mod.rb +28 -0
  13. data/lib/cocoapods-modularization/command.rb +2 -0
  14. data/lib/cocoapods-modularization/gem_version.rb +3 -0
  15. data/lib/cocoapods-modularization/generate/configuration.rb +364 -0
  16. data/lib/cocoapods-modularization/generate/installer.rb +388 -0
  17. data/lib/cocoapods-modularization/generate/podfile_generator.rb +398 -0
  18. data/lib/cocoapods-modularization/generate.rb +7 -0
  19. data/lib/cocoapods-modularization/meta/meta_accessor.rb +134 -0
  20. data/lib/cocoapods-modularization/meta/meta_constants.rb +135 -0
  21. data/lib/cocoapods-modularization/meta/meta_reference.rb +255 -0
  22. data/lib/cocoapods-modularization/meta.rb +7 -0
  23. data/lib/cocoapods-modularization/private/private_cache.rb +277 -0
  24. data/lib/cocoapods-modularization/private.rb +5 -0
  25. data/lib/cocoapods-modularization.rb +1 -0
  26. data/lib/cocoapods_plugin.rb +1 -0
  27. 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