cocoapods-modularization 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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