cocoapods-generate 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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: []