cocoapods-generate 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6b13e0f00ce8c1f769d2b12efad7689bfd8a46aa377abe04cbcd78e712b2f4e3
4
+ data.tar.gz: 7f236e2188cad4f0fac8f7e3d44819acb6a152cda2bd59bd6e8d031aa3a5465d
5
+ SHA512:
6
+ metadata.gz: be0ecfd373ec36a35e41fab37ec61b30bf98fcaf69c501f8cf2798c2e45e159fa102bd7688f370749b92bd12b5fd61b3e579f44bf88591f8b80599d22d4e1417
7
+ data.tar.gz: e43f56eaf2d99dec52e65015c0d5d79f8523fdb5533180a81c19b8a341f917b09ca67221a3255a35cb300dfe32699468bea6c63e3692c02db8f2bdbdd0cd13ce
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at segiddins@squareup.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # `cocoapods-generate`
2
+
3
+ A [CocoaPods](https://cocoapods.org/) plugin that allows you to easily generate a workspace from a podspec.
4
+
5
+ Whether you want to completely remove all Xcode projects from your library's repository or you want to be able to focus on a small piece of a monorepo, a single `pod gen` command will build up a workspace suitable for writing, running, testing, and debugging in Xcode.
6
+
7
+ When you're done working, you don't have to do anything -- and that means no merge conflicts, no managing Xcode projects, and no tearing down a sample app setup. `pod gen` manages all that for you.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'cocoapods-generate'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install cocoapods-generate
24
+
25
+ ## CLI
26
+
27
+ The main point of interaction with the plugin is via the `pod gen` command.
28
+ It takes in a list of podspecs (or directories) as arguments,
29
+ as well as many options that modify how `gen` will create your workspace.
30
+
31
+ <!-- begin cli usage -->
32
+ ```
33
+ Usage:
34
+
35
+ $ pod gen [PODSPEC|DIR ...]
36
+
37
+ Generates Xcode workspaces from a podspec.
38
+
39
+ pod gen allows you to keep your Podfile and podspecs as the single source of
40
+ truth for pods under development. By generating throw-away workspaces capable of
41
+ building, running, and testing a pod, you can focus on library development
42
+ without worrying about other code or managing an Xcode project.
43
+
44
+ pod gen works particularly well for monorepo projects, since it is capable of
45
+ using your existing settings when generating the workspace, making each gen'ed
46
+ project truly a small slice of the larger application.
47
+
48
+ Options:
49
+
50
+ --silent Show nothing
51
+ --verbose Show more debugging information
52
+ --no-ansi Show output without ANSI codes
53
+ --help Show help banner of specified command
54
+ --podfile-path=PATH Path to podfile to use
55
+ --use-podfile Whether restrictions should be copied from
56
+ the podfile
57
+ --use-lockfile Whether the lockfile should be used
58
+ --use-lockfile-versions Whether versions from the lockfile should
59
+ be used
60
+ --use-libraries Whether to use libraries instead of
61
+ frameworks
62
+ --gen-directory=PATH Path to generate workspaces in
63
+ --auto-open Whether to automatically open the generated
64
+ workspaces
65
+ --clean Whether to clean the generated directories
66
+ before generating
67
+ --app-host-source-dir=DIR A directory containing sources to use for
68
+ the app host
69
+ --sources=SOURCE1,SOURCE2 The sources from which to pull dependant
70
+ pods (defaults to all repos in the podfile
71
+ if using the podfile, else all available
72
+ repos). Can be a repo name or URL. Multiple
73
+ sources must be comma-delimited.
74
+ --repo-update Force running `pod repo update` before
75
+ install
76
+ --use-default-plugins Whether installation should activate
77
+ default plugins
78
+ --deterministic-uuids Whether installation should use
79
+ deterministic UUIDs for pods projects
80
+ --share-schemes-for-development-pods Whether installation should share schemes
81
+ for development pods
82
+ --warn-for-multiple-pod-sources Whether installation should warn when a pod
83
+ is found in multiple sources
84
+ ```
85
+ <!-- end cli usage -->
86
+
87
+ ## `.gen_config.yml`
88
+
89
+ All of the command line options above can also be specified in a `.gen_config.yml` file.
90
+
91
+ For example, the equivalent of running `pod gen --no-deterministic-uuids --sources=a,b --gen-directory=/tmp/gen --use-libraries` would be
92
+
93
+ ```yaml
94
+ deterministic_uuids: false
95
+ sources:
96
+ - a
97
+ - b
98
+ gen_directory: /tmp/gen
99
+ use_libraries: true
100
+ ```
101
+
102
+ ## Development
103
+
104
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rake spec` to run the tests.
105
+
106
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
107
+
108
+ To install this gem onto your local machine, run `bundle exec rake install`.
109
+
110
+ To release a new version, update the version number in `VERSION`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
111
+
112
+ ## Contributing
113
+
114
+ Bug reports and pull requests are welcome on GitHub at https://github.com/square/cocoapods-generate. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
115
+
116
+ ## Code of Conduct
117
+
118
+ Everyone interacting in the cocoapods-generate project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/square/cocoapods-generate/blob/master/CODE_OF_CONDUCT.md).
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cocoapods/generate'
4
+
5
+ module Pod
6
+ class Command
7
+ class Gen < Command
8
+ self.summary = 'Generates Xcode workspaces from a podspec.'
9
+
10
+ self.description = <<-DESC.strip_heredoc
11
+ #{summary}
12
+
13
+ pod gen allows you to keep your Podfile and podspecs as the single source of
14
+ truth for pods under development. By generating throw-away workspaces capable of
15
+ building, running, and testing a pod, you can focus on library development
16
+ without worrying about other code or managing an Xcode project.
17
+
18
+ pod gen works particularly well for monorepo projects, since it is capable of
19
+ using your existing settings when generating the workspace, making each gen'ed
20
+ project truly a small slice of the larger application.
21
+ DESC
22
+
23
+ def self.options
24
+ super.concat(Generate::Configuration.options.map do |option|
25
+ next unless option.cli_name
26
+
27
+ flag = "--#{option.cli_name}"
28
+ flag += "=#{option.arg_name}" if option.arg_name
29
+ [flag, option.message]
30
+ end.compact)
31
+ end
32
+
33
+ self.arguments = [
34
+ CLAide::Argument.new(%w[PODSPEC DIR], false, true)
35
+ ]
36
+
37
+ # @return [Configuration]
38
+ # the configuration used when generating workspaces
39
+ #
40
+ attr_reader :configuration
41
+
42
+ def initialize(argv)
43
+ options_hash = Generate::Configuration.options.each_with_object({}) do |option, options|
44
+ value =
45
+ if option.name == :podspec_paths
46
+ argv.arguments!
47
+ elsif option.flag?
48
+ argv.flag?(option.cli_name)
49
+ else
50
+ argv.option(option.cli_name)
51
+ end
52
+
53
+ next if value.nil?
54
+ options[option.name] = option.coerce(value)
55
+ end
56
+ @configuration = merge_configuration(options_hash)
57
+ super
58
+ end
59
+
60
+ def run
61
+ UI.puts "[pod gen] Running with #{configuration.to_s.gsub("\n", " \n")}" if configuration.pod_config.verbose?
62
+
63
+ # this is done here rather than in the installer so we only update sources once,
64
+ # even if there are multiple podspecs
65
+ update_sources if configuration.repo_update?
66
+
67
+ Generate::PodfileGenerator.new(configuration).podfiles_by_spec.each do |spec, podfile|
68
+ Generate::Installer.new(configuration, spec, podfile).install!
69
+ end
70
+
71
+ remove_warnings(UI.warnings)
72
+ end
73
+
74
+ def validate!
75
+ super
76
+
77
+ config_errors = configuration.validate
78
+ help! config_errors.join("\n ") if config_errors.any?
79
+ end
80
+
81
+ private
82
+
83
+ def merge_configuration(options)
84
+ # must use #to_enum explicitly since descend doesn't return an enumerator on 2.1
85
+ config_hashes = Pathname.pwd.to_enum(:descend).map do |dir|
86
+ path = dir + '.gen_config.yml'
87
+ next unless path.file?
88
+ Pod::Generate::Configuration.from_file(path)
89
+ end
90
+
91
+ env = Generate::Configuration.from_env(ENV)
92
+ config_hashes.insert(-2, env)
93
+ config_hashes << options
94
+
95
+ configuration = config_hashes.compact.each_with_object({}) { |e, h| h.merge!(e) }
96
+ Pod::Generate::Configuration.new(pod_config: config, **configuration)
97
+ end
98
+
99
+ def remove_warnings(warnings)
100
+ warnings.reject! do |warning|
101
+ warning[:message].include? 'Automatically assigning platform'
102
+ end
103
+ end
104
+
105
+ def update_sources
106
+ UI.title 'Updating specs repos' do
107
+ configuration.sources.each do |source|
108
+ source = config.sources_manager.source_with_name_or_url(source)
109
+ UI.titled_section "Updating spec repo `#{source.name}`" do
110
+ source.update(config.verbose?)
111
+ source.verify_compatibility!
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,10 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ module Pod
5
+ module Generate
6
+ autoload :Configuration, 'cocoapods/generate/configuration'
7
+ autoload :Installer, 'cocoapods/generate/installer'
8
+ autoload :PodfileGenerator, 'cocoapods/generate/podfile_generator'
9
+ end
10
+ end
@@ -0,0 +1,298 @@
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
+ coerce_to_bool = lambda do |value|
85
+ if value.is_a?(String)
86
+ value =
87
+ case value.downcase
88
+ when ''
89
+ nil
90
+ when 'true'
91
+ true
92
+ when 'false'
93
+ false
94
+ end
95
+ end
96
+ value
97
+ end
98
+
99
+ coerce_to_pathname = lambda do |path|
100
+ path && Pathname(path).expand_path
101
+ end
102
+
103
+ BOOLEAN = [TrueClass, FalseClass].freeze
104
+ private_constant :BOOLEAN
105
+
106
+ option :pod_config, Config, 'Pod::Config.instance', nil
107
+
108
+ 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
109
+ option :podfile, [Podfile], 'Podfile.from_file(podfile_path) if (podfile_path && File.file?(File.expand_path(podfile_path)))'
110
+ option :use_podfile, BOOLEAN, '!!podfile', 'Whether restrictions should be copied from the podfile', nil, nil, coerce_to_bool
111
+
112
+ option :lockfile, [Pod::Lockfile], 'pod_config.lockfile', nil
113
+ option :use_lockfile, BOOLEAN, '!!lockfile', 'Whether the lockfile should be used to discover transitive dependencies', nil, nil, coerce_to_bool
114
+ option :use_lockfile_versions, BOOLEAN, 'use_lockfile', 'Whether versions from the lockfile should be used', nil, nil, coerce_to_bool
115
+
116
+ option :use_libraries, BOOLEAN, 'false', 'Whether to use libraries instead of frameworks', nil, nil, coerce_to_bool
117
+
118
+ 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
119
+ option :auto_open, BOOLEAN, 'false', 'Whether to automatically open the generated workspaces', nil, nil, coerce_to_bool
120
+ option :clean, BOOLEAN, 'false', 'Whether to clean the generated directories before generating', nil, nil, coerce_to_bool
121
+
122
+ option :app_host_source_dir, [String, Pathname], 'nil',
123
+ 'A directory containing sources to use for the app host',
124
+ 'DIR',
125
+ ->(dir) { 'not a directory' unless dir.directory? },
126
+ coerce_to_pathname
127
+
128
+ option :podspec_paths, ArrayOf.new(String, Pathname, URI),
129
+ '[Pathname(?.)]',
130
+ nil,
131
+ nil,
132
+ ->(paths) { ('paths do not exist' unless paths.all? { |p| p.is_a?(URI) || p.exist? }) },
133
+ ->(paths) { paths && paths.map { |path| path.to_s =~ %r{https?://} ? URI(path) : Pathname(path).expand_path } }
134
+ option :podspecs, ArrayOf.new(Pod::Specification),
135
+ 'self.class.podspecs_from_paths(podspec_paths)',
136
+ nil,
137
+ nil,
138
+ ->(specs) { 'no podspecs found' if specs.empty? },
139
+ ->(paths) { paths && paths.map { |path| Pathname(path).expand_path } }
140
+
141
+ # installer options
142
+ option :sources, ArrayOf.new(String),
143
+ 'if use_podfile && podfile then ::Pod::Installer::Analyzer.new(:sandbox, podfile).sources.map(&:url) else pod_config.sources_manager.all.map(&:url) end',
144
+ 'The sources from which to pull dependant 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.',
145
+ 'SOURCE1,SOURCE2',
146
+ ->(_) { nil },
147
+ ->(sources) { Array(sources).flat_map { |s| s.split(',') } }
148
+ option :repo_update, BOOLEAN, 'false', 'Force running `pod repo update` before install', nil, nil, coerce_to_bool
149
+ option :use_default_plugins, BOOLEAN, 'false', 'Whether installation should activate default plugins', nil, nil, coerce_to_bool
150
+ option :deterministic_uuids, BOOLEAN, 'false', 'Whether installation should use deterministic UUIDs for pods projects', nil, nil, coerce_to_bool
151
+ option :share_schemes_for_development_pods, BOOLEAN, 'true', 'Whether installation should share schemes for development pods', nil, nil, coerce_to_bool
152
+ 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
153
+
154
+ options.freeze
155
+ options.each do |o|
156
+ attr_reader o.name
157
+ alias_method :"#{o.name}?", o.name if o.type == BOOLEAN
158
+ end
159
+
160
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
161
+ # @!visibility private
162
+ def initialize(
163
+ #{options.map { |o| "#{o.name}: (begin (#{o.default}); rescue => e; e; end)" }.join(', ')}
164
+ )
165
+ #{options.map { |o| "@#{o.name} = #{o.name}" }.join('; ')}
166
+ end
167
+ RUBY
168
+
169
+ # @return [Hash<Symbol,Object>] the configuration hash parsed from the given file
170
+ #
171
+ # @param [Pathname] path
172
+ #
173
+ # @raises [Informative] if the file does not exist or is not a YAML hash
174
+ #
175
+ def self.from_file(path)
176
+ raise Informative, "No cocoapods-generate configuration found at #{UI.path path}" unless path.file?
177
+ require 'yaml'
178
+ yaml = YAML.load_file(path)
179
+ unless yaml.is_a?(Hash)
180
+ unless path.read.strip.empty?
181
+ raise Informative, "Hash not found in configuration at #{UI.path path} -- got #{yaml.inspect}"
182
+ end
183
+ yaml = {}
184
+ end
185
+ yaml = yaml.with_indifferent_access
186
+
187
+ Dir.chdir(path.dirname) do
188
+ options.each_with_object({}) do |option, config|
189
+ next unless yaml.key?(option.name)
190
+ config[option.name] = option.coerce yaml[option.name]
191
+ end
192
+ end
193
+ end
194
+
195
+ # @return [Hash<Symbol,Object>] the configuration hash parsed from the env
196
+ #
197
+ # @param [ENV,Hash<String,String>] env
198
+ #
199
+ def self.from_env(env = ENV)
200
+ options.each_with_object({}) do |option, config|
201
+ next unless (value = env["COCOAPODS_GENERATE_#{option.name.upcase}"])
202
+ config[option.name] = option.coerce(value)
203
+ end
204
+ end
205
+
206
+ # @return [Array<String>] errors in the configuration
207
+ #
208
+ def validate
209
+ hash = to_h
210
+ self.class.options.map do |option|
211
+ option.validate(hash[option.name])
212
+ end.compact
213
+ end
214
+
215
+ # @return [Configuration] a new configuration object with the given changes applies
216
+ #
217
+ # @param [Hash<Symbol,Object>] changes
218
+ #
219
+ def with_changes(changes)
220
+ self.class.new(**to_h.merge(changes))
221
+ end
222
+
223
+ # @return [Hash<Symbol,Object>]
224
+ # a hash where the keys are option names and values are the non-nil set values
225
+ #
226
+ def to_h
227
+ self.class.options.each_with_object({}) do |option, hash|
228
+ value = send(option.name)
229
+ next if value.nil?
230
+ hash[option.name] = value
231
+ end
232
+ end
233
+
234
+ # @return [Boolean] whether this configuration is equivalent to other
235
+ #
236
+ def ==(other)
237
+ self.class == other.class &&
238
+ to_h == other.to_h
239
+ end
240
+
241
+ # @return [String] a string describing the configuration, suitable for UI presentation
242
+ #
243
+ def to_s
244
+ hash = to_h
245
+ hash.delete(:pod_config)
246
+ hash.each_with_index.each_with_object('`pod gen` configuration {'.dup) do |((k, v), i), s|
247
+ s << ',' unless i.zero?
248
+ s << "\n" << ' ' << k.to_s << ': ' << v.to_s.gsub(/:0x\h+/, '')
249
+ end << ' }'
250
+ end
251
+
252
+ # @return [Pathname] the directory for installation of the generated workspace
253
+ #
254
+ # @param [String] name the name of the pod
255
+ #
256
+ def gen_dir_for_pod(name)
257
+ gen_directory.join(name)
258
+ end
259
+
260
+ # @return [Boolean] whether gen should install with dynamic frameworks
261
+ #
262
+ def use_frameworks?
263
+ !use_libraries?
264
+ end
265
+
266
+ # @return [Array<Specification>] the podspecs found at the given paths.
267
+ # This method will download specs from URLs and traverse a directory's children.
268
+ #
269
+ # @param [Array<Pathname,URI>] paths
270
+ # the paths to search for podspecs
271
+ #
272
+ def self.podspecs_from_paths(paths)
273
+ paths = [Pathname('.')] if paths.empty?
274
+ paths.flat_map do |path|
275
+ if path.is_a?(URI)
276
+ require 'cocoapods/open-uri'
277
+ begin
278
+ contents = open(path.to_s).read
279
+ rescue StandardError => e
280
+ next e
281
+ end
282
+ begin
283
+ Pod::Specification.from_string contents, path.to_s
284
+ rescue StandardError
285
+ $ERROR_INFO
286
+ end
287
+ elsif path.directory?
288
+ glob = Pathname.glob(path + '*.podspec{.json,}')
289
+ next StandardError.new "no specs found in #{UI.path path}" if glob.empty?
290
+ glob.map { |f| Pod::Specification.from_file(f) }
291
+ else
292
+ Pod::Specification.from_file(path)
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,315 @@
1
+ module Pod
2
+ module Generate
3
+ # Responsible for creating a workspace for a single specification,
4
+ # given a configuration and a generated podfile.
5
+ #
6
+ class Installer
7
+ # A subclass of the CocoaPods installer that vends analyzer classes
8
+ # that skip validating podfiles
9
+ #
10
+ class InstallerNoValidatePodfile < ::Pod::Installer
11
+ def create_analyzer(*)
12
+ super.tap do |analyzer|
13
+ def analyzer.validate_podfile!; end
14
+ end
15
+ end
16
+ end
17
+ private_constant :InstallerNoValidatePodfile
18
+
19
+ # @return [Configuration]
20
+ # the configuration to use when installing
21
+ #
22
+ attr_reader :configuration
23
+
24
+ # @return [Specification]
25
+ # the spec whose workspace is being created
26
+ #
27
+ attr_reader :spec
28
+
29
+ # @return [Podfile]
30
+ # the podfile to install
31
+ #
32
+ attr_reader :podfile
33
+
34
+ def initialize(configuration, spec, podfile)
35
+ @configuration = configuration
36
+ @spec = spec
37
+ @podfile = podfile
38
+ end
39
+
40
+ # @return [Pathname]
41
+ # The directory that pods will be installed into
42
+ #
43
+ def install_directory
44
+ @install_directory ||= podfile.defined_in_file.dirname
45
+ end
46
+
47
+ # Installs the {podfile} into the {install_directory}
48
+ #
49
+ # @return [void]
50
+ #
51
+ def install!
52
+ UI.title "Generating #{spec.name} in #{UI.path install_directory}" do
53
+ clean! if configuration.clean?
54
+ install_directory.mkpath
55
+
56
+ UI.message 'Creating stub application' do
57
+ create_app_project
58
+ end
59
+
60
+ UI.message 'Writing Podfile' do
61
+ podfile.defined_in_file.open('w') { |f| f << podfile.to_yaml }
62
+ end
63
+
64
+ installer = nil
65
+ UI.section 'Installing...' do
66
+ configuration.pod_config.with_changes(installation_root: install_directory, podfile: podfile, lockfile: configuration.lockfile, sandbox: nil, sandbox_root: install_directory, podfile_path: podfile.defined_in_file, silent: !configuration.pod_config.verbose?, verbose: false, lockfile_path: nil) do
67
+ installer = InstallerNoValidatePodfile.new(configuration.pod_config.sandbox, podfile, configuration.lockfile)
68
+ installer.use_default_plugins = configuration.use_default_plugins
69
+ installer.install!
70
+ end
71
+ end
72
+
73
+ UI.section 'Performing post-installation steps' do
74
+ perform_post_install_steps(open_app_project, installer)
75
+ end
76
+
77
+ print_post_install_message
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ # Removes the {install_directory}
84
+ #
85
+ # @return [void]
86
+ #
87
+ def clean!
88
+ UI.message 'Cleaning gen install directory' do
89
+ FileUtils.rm_rf install_directory
90
+ end
91
+ end
92
+
93
+ def open_app_project(recreate: false)
94
+ app_project_path = install_directory.join("#{spec.name}.xcodeproj")
95
+ if !recreate && app_project_path.exist?
96
+ Xcodeproj::Project.open(app_project_path)
97
+ else
98
+ Xcodeproj::Project.new(app_project_path)
99
+ end
100
+ end
101
+
102
+ # Creates an app project that CocoaPods will integrate into
103
+ #
104
+ # @return [Xcodeproj::Project]
105
+ #
106
+ def create_app_project
107
+ app_project = open_app_project(recreate: true)
108
+
109
+ spec.available_platforms.map do |platform|
110
+ consumer = spec.consumer(platform)
111
+ target_name = "App-#{Platform.string_name(consumer.platform_name)}"
112
+ Pod::Generator::AppTargetHelper.add_app_target(app_project, consumer.platform_name, deployment_target(consumer), target_name)
113
+ end
114
+ .tap do
115
+ app_project.recreate_user_schemes do |scheme, target|
116
+ scheme.set_launch_target(target)
117
+ end
118
+ end
119
+ .each do |target|
120
+ Xcodeproj::XCScheme.share_scheme(app_project.path, target.name)
121
+ end
122
+
123
+ app_project.save
124
+ end
125
+
126
+ def deployment_target(consumer)
127
+ deployment_target = consumer.spec.deployment_target(consumer.platform_name)
128
+ if consumer.platform_name == :ios && configuration.use_frameworks?
129
+ minimum = Version.new('8.0')
130
+ deployment_target = [Version.new(deployment_target), minimum].max.to_s
131
+ end
132
+ deployment_target
133
+ end
134
+
135
+ def perform_post_install_steps(app_project, installer)
136
+ app_project.native_targets.each do |native_app_target|
137
+ remove_script_phase_from_target(native_app_target, 'Check Pods Manifest.lock')
138
+
139
+ pod_target = installer.pod_targets.find { |pt| pt.platform.name == native_app_target.platform_name && pt.pod_name == spec.name }
140
+ raise "unable to find a pod target for #{native_app_target} / #{spec}" unless pod_target
141
+
142
+ if (app_host_source_dir = configuration.app_host_source_dir)
143
+ files = Pathname.glob(app_host_source_dir + '**/*')
144
+ .map { |f| f.relative_path_from(app_host_source_dir) }
145
+ app_host_source_dir = app_host_source_dir.relative_path_from(installer.sandbox.root)
146
+ group = app_project.new_group(native_app_target.name, app_host_source_dir)
147
+ source_file_refs = files.map { |source_file| group.new_file(source_file) }
148
+ native_app_target.add_file_references(source_file_refs)
149
+ elsif Pod::Generator::AppTargetHelper.method(:add_app_project_import).arity == -5 # CocoaPods >= 1.6
150
+ Pod::Generator::AppTargetHelper.add_app_project_import(app_project, native_app_target, pod_target, pod_target.platform.name, native_app_target.name)
151
+ else
152
+ Pod::Generator::AppTargetHelper.add_app_project_import(app_project, native_app_target, pod_target, pod_target.platform.name, pod_target.requires_frameworks?, native_app_target.name)
153
+ end
154
+
155
+ # Set `PRODUCT_BUNDLE_IDENTIFIER`
156
+ native_app_target.build_configurations.each do |bc|
157
+ bc.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = 'org.cocoapods-generate.${PRODUCT_NAME:rfc1034identifier}'
158
+ end
159
+
160
+ case native_app_target.platform_name
161
+ when :ios
162
+ make_ios_app_launchable(installer, app_project, native_app_target)
163
+ end
164
+
165
+ Pod::Generator::AppTargetHelper.add_swift_version(native_app_target, pod_target.swift_version) unless pod_target.swift_version.blank?
166
+ if installer.pod_targets.any? { |pt| pt.spec_consumers.any? { |c| c.frameworks.include?('XCTest') } }
167
+ Pod::Generator::AppTargetHelper.add_xctest_search_paths(native_app_target)
168
+ end
169
+
170
+ # Share the pods xcscheme only if it exists. For pre-built vendored pods there is no xcscheme generated.
171
+ Xcodeproj::XCScheme.share_scheme(installer.pods_project.path, pod_target.label) if File.exist?(installer.pods_project.path + pod_target.label)
172
+
173
+ add_test_spec_schemes_to_app_scheme(installer, app_project)
174
+ end
175
+
176
+ app_project.save
177
+ end
178
+
179
+ def remove_script_phase_from_target(native_target, script_phase_name)
180
+ script_phase = native_target.shell_script_build_phases.find { |bp| bp.name && bp.name.end_with?(script_phase_name) }
181
+ return unless script_phase.present?
182
+ native_target.build_phases.delete(script_phase)
183
+ end
184
+
185
+ def add_test_spec_schemes_to_app_scheme(installer, app_project)
186
+ test_native_targets =
187
+ if installer.respond_to?(:target_installation_results) # CocoaPods >= 1.6
188
+ installer
189
+ .target_installation_results
190
+ .pod_target_installation_results
191
+ .values
192
+ .flatten(1)
193
+ .select { |installation_result| installation_result.target.pod_name == spec.root.name }
194
+ else
195
+ installer
196
+ .pod_targets
197
+ .select { |pod_target| pod_target.pod_name == spec.root.name }
198
+ end
199
+ .flat_map(&:test_native_targets)
200
+ .group_by(&:platform_name)
201
+
202
+ Xcodeproj::Plist.write_to_path(
203
+ { 'IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded' => false },
204
+ app_project.path.sub_ext('.xcworkspace').join('xcshareddata').tap(&:mkpath).join('WorkspaceSettings.xcsettings')
205
+ )
206
+
207
+ test_native_targets.each do |platform_name, test_targets|
208
+ app_scheme_path = Xcodeproj::XCScheme.shared_data_dir(app_project.path).join("App-#{Platform.string_name(platform_name)}.xcscheme")
209
+ raise "Missing app scheme for #{platform_name}: #{app_scheme_path.inspect}" unless app_scheme_path.file?
210
+
211
+ app_scheme = Xcodeproj::XCScheme.new(app_scheme_path)
212
+ test_action = app_scheme.test_action
213
+ existing_test_targets = test_action.testables.flat_map(&:buildable_references).map(&:target_name)
214
+
215
+ test_targets.sort_by(&:name).each do |target|
216
+ next if existing_test_targets.include?(target.name)
217
+
218
+ testable = Xcodeproj::XCScheme::TestAction::TestableReference.new(target)
219
+ testable.buildable_references.each do |buildable|
220
+ buildable.xml_element.attributes['ReferencedContainer'] = 'container:Pods.xcodeproj'
221
+ end
222
+ test_action.add_testable(testable)
223
+ end
224
+
225
+ app_scheme.save!
226
+ end
227
+ end
228
+
229
+ def make_ios_app_launchable(installer, app_project, native_app_target)
230
+ platform_name = Platform.string_name(native_app_target.platform_name)
231
+ generated_source_dir = installer.sandbox.root.join('App', platform_name).tap(&:mkpath)
232
+
233
+ # Add `LaunchScreen.storyboard`
234
+ launch_storyboard = generated_source_dir.join('LaunchScreen.storyboard')
235
+ launch_storyboard.write <<-XML.strip_heredoc
236
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
237
+ <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
238
+ <dependencies>
239
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
240
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
241
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
242
+ </dependencies>
243
+ <scenes>
244
+ <!--View Controller-->
245
+ <scene sceneID="EHf-IW-A2E">
246
+ <objects>
247
+ <viewController id="01J-lp-oVM" sceneMemberID="viewController">
248
+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
249
+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
250
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
251
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
252
+ <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
253
+ </view>
254
+ </viewController>
255
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
256
+ </objects>
257
+ <point key="canvasLocation" x="53" y="375"/>
258
+ </scene>
259
+ </scenes>
260
+ </document>
261
+ XML
262
+
263
+ # Add & wire `Info.plist`
264
+ info_plist_contents = {
265
+ 'CFBundleDevelopmentRegion' => '$(DEVELOPMENT_LANGUAGE)',
266
+ 'CFBundleExecutable' => '$(EXECUTABLE_NAME)',
267
+ 'CFBundleIdentifier' => '$(PRODUCT_BUNDLE_IDENTIFIER)',
268
+ 'CFBundleInfoDictionaryVersion' => '6.0',
269
+ 'CFBundleName' => '$(PRODUCT_NAME)',
270
+ 'CFBundlePackageType' => 'APPL',
271
+ 'CFBundleShortVersionString' => '1.0',
272
+ 'CFBundleVersion' => '1',
273
+ 'LSRequiresIPhoneOS' => true,
274
+ 'UILaunchStoryboardName' => 'LaunchScreen',
275
+ 'UIRequiredDeviceCapabilities' => [
276
+ 'armv7'
277
+ ],
278
+ 'UISupportedInterfaceOrientations' => %w[
279
+ UIInterfaceOrientationPortrait
280
+ UIInterfaceOrientationLandscapeLeft
281
+ UIInterfaceOrientationLandscapeRight
282
+ ],
283
+ 'UISupportedInterfaceOrientations~ipad' => %w[
284
+ UIInterfaceOrientationPortrait
285
+ UIInterfaceOrientationPortraitUpsideDown
286
+ UIInterfaceOrientationLandscapeLeft
287
+ UIInterfaceOrientationLandscapeRight
288
+ ]
289
+ }
290
+ info_plist_path = generated_source_dir.join('Info.plist')
291
+ Xcodeproj::Plist.write_to_path(info_plist_contents, info_plist_path)
292
+
293
+ native_app_target.build_configurations.each do |bc|
294
+ bc.build_settings['INFOPLIST_FILE'] = "${SRCROOT}/App/#{platform_name}/Info.plist"
295
+ end
296
+
297
+ group = app_project.main_group.find_subpath("App-#{platform_name}", true)
298
+ group.new_file(info_plist_path)
299
+ native_app_target.resources_build_phase.add_file_reference group.new_file(launch_storyboard)
300
+ end
301
+
302
+ def print_post_install_message
303
+ workspace_path = install_directory.join(podfile.workspace_path)
304
+
305
+ if configuration.auto_open?
306
+ configuration.pod_config.with_changes(verbose: true) do
307
+ Executable.execute_command 'open', [workspace_path]
308
+ end
309
+ else
310
+ UI.info "Open #{UI.path workspace_path} to work on #{spec.name}"
311
+ end
312
+ end
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pod
4
+ module Generate
5
+ # Generates podfiles for pod specifications given a configuration.
6
+ #
7
+ class PodfileGenerator
8
+ # @return [Configuration]
9
+ # the configuration used when generating podfiles
10
+ #
11
+ attr_reader :configuration
12
+
13
+ def initialize(configuration)
14
+ @configuration = configuration
15
+ end
16
+
17
+ # @return [Hash<Specification, Podfile>]
18
+ # a hash of specifications to generated podfiles
19
+ #
20
+ def podfiles_by_spec
21
+ Hash[configuration.podspecs.map do |spec|
22
+ [spec, podfile_for_spec(spec)]
23
+ end]
24
+ end
25
+
26
+ # @return [Podfile] a podfile suitable for installing the given spec
27
+ #
28
+ # @param [Specification] spec
29
+ #
30
+ def podfile_for_spec(spec)
31
+ generator = self
32
+ dir = configuration.gen_dir_for_pod(spec.name)
33
+
34
+ Pod::Podfile.new do
35
+ project "#{spec.name}.xcodeproj"
36
+ workspace "#{spec.name}.xcworkspace"
37
+
38
+ plugin 'cocoapods-generate'
39
+
40
+ install! 'cocoapods',
41
+ deterministic_uuids: generator.configuration.deterministic_uuids?,
42
+ share_schemes_for_development_pods: generator.configuration.share_schemes_for_development_pods?,
43
+ warn_for_multiple_pod_sources: generator.configuration.warn_for_multiple_pod_sources?
44
+
45
+ use_frameworks!(generator.configuration.use_frameworks?)
46
+
47
+ # Explicitly set sources
48
+ generator.configuration.sources.each do |source_url|
49
+ source(source_url)
50
+ end
51
+
52
+ self.defined_in_file = dir.join('Podfile.yaml')
53
+
54
+ test_specs = spec.recursive_subspecs.select(&:test_specification)
55
+
56
+ # Stick all of the transitive dependencies in an abstract target.
57
+ # This allows us to force CocoaPods to use the versions / sources / external sources
58
+ # that we want.
59
+ # By using an abstract target,
60
+ abstract_target 'Transitive Dependencies' do
61
+ pods_for_transitive_dependencies = [spec.name]
62
+ .concat(test_specs.map(&:name))
63
+ .concat(test_specs.flat_map { |ts| ts.dependencies.flat_map(&:name) })
64
+
65
+ dependencies = generator
66
+ .transitive_dependencies_by_pod
67
+ .values_at(*pods_for_transitive_dependencies)
68
+ .compact
69
+ .flatten(1)
70
+ .uniq
71
+ .sort_by(&:name)
72
+ .reject { |d| d.root_name == spec.root.name }
73
+
74
+ dependencies.each do |dependency|
75
+ pod_args = generator.pod_args_for_dependency(self, dependency)
76
+ pod(*pod_args)
77
+ end
78
+ end
79
+
80
+ # Add platform-specific concrete targets that inherit the
81
+ # `pod` declaration for the local pod
82
+ spec.available_platforms.map(&:string_name).sort.each do |platform_name|
83
+ target "App-#{platform_name}" do
84
+ current_target_definition.swift_version = generator.swift_version if generator.swift_version
85
+ end
86
+ end
87
+
88
+ # this block hash to come _before_ inhibit_all_warnings! / use_modular_headers!,
89
+ # and the local `pod` declaration
90
+ current_target_definition.instance_exec do
91
+ transitive_dependencies = children.find { |c| c.name == 'Transitive Dependencies' }
92
+
93
+ %w[use_modular_headers inhibit_warnings].each do |key|
94
+ value = transitive_dependencies.send(:internal_hash).delete(key)
95
+ next if value.blank?
96
+ set_hash_value(key, value)
97
+ end
98
+ end
99
+
100
+ inhibit_all_warnings! if generator.inhibit_all_warnings?
101
+ use_modular_headers! if generator.use_modular_headers?
102
+
103
+ # This is the pod declaration for the local pod,
104
+ # it will be inherited by the concrete target definitions below
105
+ pod spec.name,
106
+ path: spec.defined_in_file.relative_path_from(dir).to_s,
107
+ testspecs: test_specs.map { |s| s.name.sub(%r{^#{Regexp.escape spec.root.name}/}, '') }.sort,
108
+ **generator.dependency_compilation_kwargs(spec.name)
109
+ end
110
+ end
111
+
112
+ # @return [Boolean]
113
+ # whether all warnings should be inhibited
114
+ #
115
+ def inhibit_all_warnings?
116
+ return false unless configuration.use_podfile?
117
+ target_definition_list.all? do |target_definition|
118
+ target_definition.send(:inhibit_warnings_hash)['all']
119
+ end
120
+ end
121
+
122
+ # @return [Boolean]
123
+ # whether all pods should use modular headers
124
+ #
125
+ def use_modular_headers?
126
+ return false unless configuration.use_podfile?
127
+ target_definition_list.all? do |target_definition|
128
+ target_definition.use_modular_headers_hash['all']
129
+ end
130
+ end
131
+
132
+ # @return [Hash]
133
+ # a hash with "compilation"-related dependency options for the `pod` DSL method
134
+ #
135
+ # @param [String] pod_name
136
+ #
137
+ def dependency_compilation_kwargs(pod_name)
138
+ options = {}
139
+ options[:inhibit_warnings] = inhibit_warnings?(pod_name) if inhibit_warnings?(pod_name) != inhibit_all_warnings?
140
+ options[:modular_headers] = modular_headers?(pod_name) if modular_headers?(pod_name) != use_modular_headers?
141
+ options
142
+ end
143
+
144
+ # @return [Hash<String,Array<Dependency>>]
145
+ # the transitive dependency objects dependency upon by each pod
146
+ #
147
+ def transitive_dependencies_by_pod
148
+ return {} unless configuration.use_lockfile?
149
+ @transitive_dependencies_by_pod ||= begin
150
+ lda = ::Pod::Installer::Analyzer::LockingDependencyAnalyzer
151
+ dependency_graph = Molinillo::DependencyGraph.new
152
+ configuration.lockfile.dependencies.each do |dependency|
153
+ dependency_graph.add_vertex(dependency.name, dependency, true)
154
+ end
155
+ add_to_dependency_graph = if lda.method(:add_to_dependency_graph).parameters.size == 4 # CocoaPods < 1.6.0
156
+ ->(pod) { lda.add_to_dependency_graph(pod, [], dependency_graph, []) }
157
+ else
158
+ ->(pod) { lda.add_to_dependency_graph(pod, [], dependency_graph, [], Set.new) }
159
+ end
160
+ configuration.lockfile.internal_data['PODS'].each(&add_to_dependency_graph)
161
+
162
+ transitive_dependencies_by_pod = Hash.new { |hash, key| hash[key] = [] }
163
+ dependency_graph.each do |v|
164
+ transitive_dependencies_by_pod[v.name].concat v.recursive_successors.map(&:payload) << v.payload
165
+ end
166
+
167
+ transitive_dependencies_by_pod.each_value(&:uniq!)
168
+ transitive_dependencies_by_pod
169
+ end
170
+ end
171
+
172
+ # @return [Hash<String,Array<Dependency>>]
173
+ # dependencies in the podfile grouped by root name
174
+ #
175
+ def podfile_dependencies
176
+ return {} unless configuration.use_podfile?
177
+ @podfile_dependencies ||= configuration.podfile.dependencies.group_by(&:root_name).tap { |h| h.default = [] }
178
+ end
179
+
180
+ # @return [Hash<String,String>]
181
+ # versions in the lockfile keyed by pod name
182
+ #
183
+ def lockfile_versions
184
+ return {} unless configuration.use_lockfile_versions?
185
+ @lockfile_versions ||= Hash[configuration.lockfile.pod_names.map { |name| [name, "= #{configuration.lockfile.version(name)}"] }]
186
+ end
187
+
188
+ # @return [Hash<String,Array<Dependency>>]
189
+ # returns the arguments that should be passed to the Podfile DSL's
190
+ # `pod` method for the given podfile and dependency
191
+ #
192
+ # @param [Podfile] podfile
193
+ #
194
+ # @param [Dependency] dependency
195
+ #
196
+ def pod_args_for_dependency(podfile, dependency)
197
+ dependency = podfile_dependencies[dependency.root_name]
198
+ .map { |dep| dep.dup.tap { |d| d.name = dependency.name } }
199
+ .push(dependency)
200
+ .reduce(&:merge)
201
+
202
+ options = dependency_compilation_kwargs(dependency.name)
203
+ options[:source] = dependency.podspec_repo if dependency.podspec_repo
204
+ options.update(dependency.external_source) if dependency.external_source
205
+ %i[path podspec].each do |key|
206
+ next unless (path = options[key])
207
+ options[key] = Pathname(path)
208
+ .expand_path(configuration.podfile.defined_in_file.dirname)
209
+ .relative_path_from(podfile.defined_in_file.dirname)
210
+ .to_s
211
+ end
212
+ args = [dependency.name]
213
+ if dependency.external_source.nil?
214
+ requirements = dependency.requirement.as_list
215
+ if (version = lockfile_versions[dependency.name])
216
+ requirements << version
217
+ end
218
+ args.concat requirements.uniq
219
+ end
220
+ args << options unless options.empty?
221
+ args
222
+ end
223
+
224
+ def swift_version
225
+ @swift_version ||= target_definition_list.map(&:swift_version).compact.max
226
+ end
227
+
228
+ private
229
+
230
+ # @return [Array<Podfile::TargetDefinition>]
231
+ # a list of all target definitions to consider from the podfile
232
+ #
233
+ def target_definition_list
234
+ return [] unless configuration.use_podfile?
235
+ @target_definition_list ||= begin
236
+ list = configuration.podfile.target_definition_list
237
+ list.reject!(&:abstract?) unless list.all?(&:abstract?)
238
+ list
239
+ end
240
+ end
241
+
242
+ # @return [Boolean]
243
+ # whether warnings should be inhibited for the given pod
244
+ #
245
+ # @param [String] pod_name
246
+ #
247
+ def inhibit_warnings?(pod_name)
248
+ return false unless configuration.use_podfile?
249
+ target_definitions_for_pod(pod_name).all? do |target_definition|
250
+ target_definition.inhibits_warnings_for_pod?(pod_name)
251
+ end
252
+ end
253
+
254
+ # @return [Boolean]
255
+ # whether modular headers should be enabled for the given pod
256
+ #
257
+ # @param [String] pod_name
258
+ #
259
+ def modular_headers?(pod_name)
260
+ return false unless configuration.use_podfile?
261
+ target_definitions_for_pod(pod_name).all? do |target_definition|
262
+ target_definition.build_pod_as_module?(pod_name)
263
+ end
264
+ end
265
+
266
+ # @return [Podfile::TargetDefinition]
267
+ #
268
+ # @param [String] pod_name
269
+ #
270
+ def target_definitions_for_pod(pod_name)
271
+ target_definitions = target_definition_list.reject { |td| td.dependencies.none? { |d| d.name == pod_name } }
272
+ target_definitions.empty? ? target_definition_list : target_definitions
273
+ end
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ if Pod::VERSION >= '1.5.0'
4
+ require 'cocoapods/command/gen'
5
+ else
6
+ Pod::UI.warn 'cocoapods-generate requires CocoaPods >= 1.5.0'
7
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cocoapods-generate
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Giddins
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '1.16'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '10.0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '10.0'
47
+ description: |
48
+ pod gen allows you to keep your Podfile and podspecs as the single source of
49
+ truth for pods under development. By generating throw-away workspaces capable of
50
+ building, running, and testing a pod, you can focus on library development
51
+ without worrying about other code or managing an Xcode project.
52
+ pod gen works particularly well for monorepo projects, since it is capable of
53
+ using your existing settings when generating the workspace, making each gen'ed
54
+ project truly a small slice of the larger application.
55
+ email:
56
+ - segiddins@squareup.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - CODE_OF_CONDUCT.md
62
+ - README.md
63
+ - VERSION
64
+ - lib/cocoapods/command/gen.rb
65
+ - lib/cocoapods/generate.rb
66
+ - lib/cocoapods/generate/configuration.rb
67
+ - lib/cocoapods/generate/installer.rb
68
+ - lib/cocoapods/generate/podfile_generator.rb
69
+ - lib/cocoapods_plugin.rb
70
+ homepage: https://github.com/square/cocoapods-generate
71
+ licenses: []
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '2.1'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 2.7.3
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Generates Xcode workspaces from a podspec.
93
+ test_files: []