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 +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/README.md +118 -0
- data/VERSION +1 -0
- data/lib/cocoapods/command/gen.rb +118 -0
- data/lib/cocoapods/generate.rb +10 -0
- data/lib/cocoapods/generate/configuration.rb +298 -0
- data/lib/cocoapods/generate/installer.rb +315 -0
- data/lib/cocoapods/generate/podfile_generator.rb +276 -0
- data/lib/cocoapods_plugin.rb +7 -0
- metadata +93 -0
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
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -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
|
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: []
|