cocoapods-bazel 0.1.1

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.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Cocoapods::Bazel
2
+ ![](https://github.com/ob/cocoapods-bazel/workflows/master/badge.svg)
3
+
4
+
5
+ Cocoapods::Bazel is a Cocoapods plugin that makes it easy to use [Bazel](https://bazel.build) instead of Xcode to build your iOS project. It automatically generates Bazel's `BUILD.bazel` files. It uses [`rules_ios`](https://github.com/ob/rules_ios) so you need to set up the `WORKSPACE` file following the instructions in the [`README`](https://github.com/ob/rules_ios/blob/master/README.md).
6
+
7
+ > :warning: **This is alpha software.** We are developing this plugin in the open so you should only use it if you know what you are doing and are willing to help develop it.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'cocoapods-bazel'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install cocoapods-bazel
24
+
25
+ ## Usage
26
+
27
+ This plugin will run extra steps after post_install to generate BUILD.bazel files for Bazel.
28
+
29
+ To enable the plugin, simply add the following section to your `Podfile`
30
+
31
+ ```
32
+ plugin 'cocoapods-bazel', {
33
+ rules: {
34
+ 'apple_framework' => { load: '@build_bazel_rules_ios//rules:framework.bzl', rule: 'apple_framework' }.freeze,
35
+ 'ios_application' => { load: '@build_bazel_rules_ios//rules:app.bzl', rule: 'ios_application' }.freeze,
36
+ 'ios_unit_test' => { load: '@build_bazel_rules_ios//rules:test.bzl', rule: 'ios_unit_test' }.freeze
37
+ }.freeze,
38
+ }
39
+ ```
40
+
41
+ ## Development
42
+
43
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
44
+
45
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, 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).
46
+
47
+ ## Contributing
48
+
49
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ob/cocoapods-bazel.
50
+
51
+ ## License
52
+
53
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ namespace :spec do
8
+ RSpec::Core::RakeTask.new(:unit) do |t|
9
+ t.rspec_opts = %w[--format progress]
10
+ end
11
+
12
+ desc 'Run integration specs'
13
+ task :integration do
14
+ sh 'bundle', 'exec', 'bacon', 'spec/integration.rb', '-q'
15
+ end
16
+
17
+ namespace :integration do
18
+ desc 'Update integration spec fixtures'
19
+ task :update do
20
+ rm_rf 'spec/integration/tmp'
21
+ sh('bin/rake', 'spec:integration') {}
22
+ # Copy the files to the files produced by the specs to the after folders
23
+ FileList['spec/integration/tmp/*/transformed'].each do |source|
24
+ walk_dir = lambda do |d|
25
+ Dir.each_child(d) do |c|
26
+ child = File.join(d, c)
27
+ walk_dir[child] if File.directory?(child)
28
+ end
29
+ Dir.delete(d) if Dir.empty?(d)
30
+ end
31
+ walk_dir[source]
32
+
33
+ name = source.match(%r{tmp/([^/]+)/transformed$})[1]
34
+ destination = "spec/integration/#{name}/after"
35
+ rm_rf destination
36
+ mv source, destination
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ desc 'Run all specs'
43
+ task spec: %w[spec:unit spec:integration]
44
+
45
+ RuboCop::RakeTask.new(:rubocop)
46
+
47
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'cocoapods/bazel'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/pod ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'pod' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path('bundle', __dir__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require 'rubygems'
27
+ require 'bundler/setup'
28
+
29
+ load Gem.bin_path('cocoapods', 'pod')
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'pathname'
5
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
6
+ Pathname.new(__FILE__).realpath)
7
+
8
+ bundle_binstub = File.expand_path('bundle', __dir__)
9
+
10
+ if File.file?(bundle_binstub)
11
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
12
+ load(bundle_binstub)
13
+ else
14
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
15
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
16
+ end
17
+ end
18
+
19
+ require 'rubygems'
20
+ require 'bundler/setup'
21
+
22
+ ARGV.replace %w[install]
23
+
24
+ load Gem.bin_path('cocoapods', 'pod')
25
+
26
+ exec 'bazelisk', '--ignore_all_rc_files', 'build', '--verbose_failures', '...'
data/bin/rake ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path('bundle', __dir__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require 'rubygems'
27
+ require 'bundler/setup'
28
+
29
+ load Gem.bin_path('rake', 'rake')
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/cocoapods/bazel/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'cocoapods-bazel'
7
+ spec.version = Pod::Bazel::VERSION
8
+ spec.authors = ['Shawn Chen', 'Samuel Giddins']
9
+ spec.email = ['swchen@linkedin.com', 'segiddins@squareup.com']
10
+
11
+ spec.summary = 'A plugin for CocoaPods that generates Bazel build files for pods'
12
+ spec.homepage = 'https://github.com/ob/cocoapods-bazel'
13
+ spec.license = 'apache2'
14
+
15
+ spec.metadata['allowed_push_host'] = "https://rubygems.org/"
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = spec.homepage
19
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/master/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.add_development_dependency 'bundler', '>= 2.1'
31
+
32
+ spec.add_runtime_dependency 'starlark_compiler', '~> 0.3'
33
+
34
+ spec.required_ruby_version = '>= 2.6'
35
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'starlark_compiler/build_file'
4
+ require 'cocoapods/bazel/config'
5
+ require 'cocoapods/bazel/target'
6
+ require 'cocoapods/bazel/xcconfig_resolver'
7
+ require 'cocoapods/bazel/util'
8
+
9
+ module Pod
10
+ module Bazel
11
+ def self.post_install(installer:)
12
+ return unless (config = Config.from_podfile(installer.podfile))
13
+
14
+ default_xcconfigs = config.default_xcconfigs.transform_values do |xcconfig|
15
+ _name, xcconfig = XCConfigResolver.resolve_xcconfig(xcconfig)
16
+ xcconfig
17
+ end
18
+
19
+ UI.titled_section 'Generating Bazel files' do
20
+ workspace = installer.config.installation_root
21
+ sandbox = installer.sandbox
22
+ build_files = Hash.new { |h, k| h[k] = StarlarkCompiler::BuildFile.new(workspace: workspace, package: k) }
23
+ installer.pod_targets.each do |pod_target|
24
+ package = sandbox.pod_dir(pod_target.pod_name).relative_path_from(workspace).to_s
25
+ if package.start_with?('..')
26
+ raise Informative, <<~MSG
27
+ Bazel does not support Pod located outside of current workspace: \"#{package}\".
28
+ To fix this, you can move the Pod into workspace,
29
+ or you can symlink the Pod inside the workspace by running `ln -s <path_to_pod> .` at workspace root
30
+ Then change path declared in Podfile to `./<pod_name>`
31
+ Current workspace: #{workspace}
32
+ MSG
33
+ end
34
+
35
+ build_file = build_files[package]
36
+
37
+ bazel_targets = [Target.new(installer, pod_target, nil, default_xcconfigs)] +
38
+ pod_target.file_accessors.reject { |fa| fa.spec.library_specification? }.map { |fa| Target.new(installer, pod_target, fa.spec, default_xcconfigs) }
39
+
40
+ bazel_targets.each do |t|
41
+ load = config.load_for(macro: t.type)
42
+ build_file.add_load(of: load[:rule], from: load[:load])
43
+ build_file.add_target StarlarkCompiler::AST::FunctionCall.new(load[:rule], **t.to_rule_kwargs)
44
+ end
45
+ end
46
+
47
+ build_files.each_value(&:save!)
48
+ format_files(build_files: build_files, buildifier: config.buildifier, workspace: workspace)
49
+
50
+ cocoapods_bazel_path = File.join(sandbox.root, 'cocoapods-bazel')
51
+ FileUtils.mkdir_p cocoapods_bazel_path
52
+
53
+ write_cocoapods_bazel_build_file(cocoapods_bazel_path, workspace, config)
54
+ write_non_empty_default_xcconfigs(cocoapods_bazel_path, default_xcconfigs)
55
+ end
56
+ end
57
+
58
+ def self.write_cocoapods_bazel_build_file(path, workspace, config)
59
+ FileUtils.touch(File.join(path, 'BUILD.bazel'))
60
+
61
+ cocoapods_bazel_pkg = Pathname.new(path).relative_path_from Pathname.new(workspace)
62
+ configs_build_file = StarlarkCompiler::BuildFile.new(workspace: workspace, package: cocoapods_bazel_pkg)
63
+
64
+ configs_build_file.add_load(of: 'string_flag', from: '@bazel_skylib//rules:common_settings.bzl')
65
+ configs_build_file.add_target StarlarkCompiler::AST::FunctionCall.new('string_flag', name: 'config', build_setting_default: 'debug', visibility: ['//visibility:public'])
66
+ configs_build_file.add_target StarlarkCompiler::AST::FunctionCall.new('config_setting', name: 'debug', flag_values: { ':config' => 'debug' })
67
+ configs_build_file.add_target StarlarkCompiler::AST::FunctionCall.new('config_setting', name: 'release', flag_values: { ':config' => 'release' })
68
+
69
+ configs_build_file.save!
70
+ format_files(build_files: { cocoapods_bazel_pkg => configs_build_file }, buildifier: config.buildifier, workspace: workspace)
71
+ end
72
+
73
+ def self.write_non_empty_default_xcconfigs(path, default_xcconfigs)
74
+ return if default_xcconfigs.empty?
75
+
76
+ hash = StarlarkCompiler::AST.new(toplevel: [StarlarkCompiler::AST::Dictionary.new(default_xcconfigs)])
77
+
78
+ File.open(File.join(path, 'default_xcconfigs.bzl'), 'w') do |f|
79
+ f << <<~STARLARK
80
+ """
81
+ Default xcconfigs given as options to cocoapods-bazel.
82
+ """
83
+
84
+ STARLARK
85
+ f << 'DEFAULT_XCCONFIGS = '
86
+ StarlarkCompiler::Writer.write(ast: hash, io: f)
87
+ end
88
+ end
89
+
90
+ def self.format_files(build_files:, buildifier:, workspace:)
91
+ return if build_files.empty?
92
+
93
+ args = []
94
+ case buildifier
95
+ when true
96
+ return unless Pod::Executable.which('buildifier')
97
+
98
+ args = ['buildifier']
99
+ when String, Array
100
+ args = Array(buildifier)
101
+ else
102
+ return
103
+ end
104
+ args += %w[-type build]
105
+
106
+ executable, *args = args
107
+ Pod::Executable.execute_command executable,
108
+ args + build_files.each_key.map { |d| File.join workspace, d, 'BUILD.bazel' },
109
+ true
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pod
4
+ module Bazel
5
+ class Config
6
+ PLUGIN_KEY = 'cocoapods-bazel'
7
+ private_constant :PLUGIN_KEY
8
+ DEFAULTS = {
9
+ rules: {
10
+ 'apple_framework' => { load: '@build_bazel_rules_ios//rules:framework.bzl', rule: 'apple_framework' }.freeze,
11
+ 'ios_application' => { load: '@build_bazel_rules_ios//rules:app.bzl', rule: 'ios_application' }.freeze,
12
+ 'ios_unit_test' => { load: '@build_bazel_rules_ios//rules:test.bzl', rule: 'ios_unit_test' }.freeze
13
+ }.freeze,
14
+ overrides: {}.freeze,
15
+ buildifier: true,
16
+ default_xcconfigs: {}.freeze
17
+ }.with_indifferent_access.freeze
18
+ private_constant :DEFAULTS
19
+
20
+ attr_reader :to_h
21
+
22
+ def self.enabled_in_podfile?(podfile)
23
+ podfile.plugins.key?(PLUGIN_KEY)
24
+ end
25
+
26
+ def self.from_podfile(podfile)
27
+ return unless enabled_in_podfile?(podfile)
28
+
29
+ from_podfile_options(podfile.plugins[PLUGIN_KEY])
30
+ end
31
+
32
+ def self.from_podfile_options(options)
33
+ new(DEFAULTS.merge(options) do |_key, old_val, new_val|
34
+ case old_val
35
+ when Hash
36
+ old_val.merge(new_val) # intentionally only 1 level deep of merging
37
+ else
38
+ new_val
39
+ end
40
+ end)
41
+ end
42
+
43
+ def initialize(to_h)
44
+ @to_h = to_h
45
+ end
46
+
47
+ def buildifier
48
+ to_h[:buildifier]
49
+ end
50
+
51
+ def load_for(macro:)
52
+ to_h.dig('rules', macro) || raise("no rule configured for #{macro}")
53
+ end
54
+
55
+ def default_xcconfigs
56
+ to_h[:default_xcconfigs]
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,630 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'xcconfig_resolver'
4
+
5
+ module Pod
6
+ module Bazel
7
+ class Target
8
+ class RuleArgs
9
+ attr_reader :kwargs
10
+
11
+ def initialize
12
+ @kwargs = {}
13
+
14
+ yield self if block_given?
15
+ end
16
+
17
+ def add(name, value, defaults: nil)
18
+ return self if defaults&.include?(value)
19
+
20
+ raise 'Duplicate' if kwargs.key?(name)
21
+
22
+ kwargs[name] = value
23
+
24
+ self
25
+ end
26
+ end
27
+
28
+ include XCConfigResolver
29
+
30
+ attr_reader :installer, :pod_target, :file_accessors, :non_library_spec, :label, :package, :default_xcconfigs, :resolved_xconfig_by_config
31
+ private :installer, :pod_target, :file_accessors, :non_library_spec, :label, :package, :default_xcconfigs, :resolved_xconfig_by_config
32
+
33
+ def initialize(installer, pod_target, non_library_spec = nil, default_xcconfigs = {})
34
+ @installer = installer
35
+ @pod_target = pod_target
36
+ @file_accessors = non_library_spec ? pod_target.file_accessors.select { |fa| fa.spec == non_library_spec } : pod_target.file_accessors.select { |fa| fa.spec.library_specification? }
37
+ @non_library_spec = non_library_spec
38
+ @label = (non_library_spec ? pod_target.non_library_spec_label(non_library_spec) : pod_target.label)
39
+ @package_dir = installer.sandbox.pod_dir(pod_target.pod_name)
40
+ @package = installer.sandbox.pod_dir(pod_target.pod_name).relative_path_from(installer.config.installation_root).to_s
41
+ @default_xcconfigs = default_xcconfigs
42
+ @resolved_xconfig_by_config = {}
43
+ end
44
+
45
+ def bazel_label(relative_to: nil)
46
+ package_basename = File.basename(package)
47
+ if package == relative_to
48
+ ":#{label}"
49
+ elsif package_basename == label
50
+ "//#{package}"
51
+ else
52
+ "//#{package}:#{label}"
53
+ end
54
+ end
55
+
56
+ def build_settings_label(config)
57
+ relative_sandbox_root = @installer.sandbox.root.relative_path_from(@installer.config.installation_root).to_s
58
+ cocoapods_bazel_path = File.join(relative_sandbox_root, 'cocoapods-bazel')
59
+
60
+ "//#{cocoapods_bazel_path}:#{config}"
61
+ end
62
+
63
+ def test_host
64
+ unless (app_host_info = pod_target.test_app_hosts_by_spec_name[non_library_spec.name])
65
+ return
66
+ end
67
+
68
+ app_spec, app_target = *app_host_info
69
+ Target.new(installer, app_target, app_spec)
70
+ end
71
+
72
+ def type
73
+ platform = pod_target.platform.name
74
+ case non_library_spec&.spec_type
75
+ when nil
76
+ 'apple_framework'
77
+ when :app
78
+ "#{platform}_application"
79
+ when :test
80
+ "#{platform}_#{non_library_spec.test_type}_test"
81
+ else
82
+ raise "Unhandled: #{non_library_spec.spec_type}"
83
+ end
84
+ end
85
+
86
+ def dependent_targets_by_config
87
+ targets =
88
+ case non_library_spec&.spec_type
89
+ when nil
90
+ pod_target.dependent_targets_by_config
91
+ when :app
92
+ pod_target.app_dependent_targets_by_spec_name_by_config[non_library_spec.name].transform_values { |v| v + [pod_target] }
93
+ when :test
94
+ pod_target.test_dependent_targets_by_spec_name_by_config[non_library_spec.name].transform_values { |v| v + [pod_target] }
95
+ else
96
+ raise "Unhandled: #{non_library_spec.spec_type}"
97
+ end
98
+
99
+ targets.transform_values { |v| v.uniq.map { |target| self.class.new(installer, target) } }
100
+ end
101
+
102
+ def product_module_name
103
+ name = resolved_value_by_build_setting('PRODUCT_MODULE_NAME') || resolved_value_by_build_setting('PRODUCT_NAME') ||
104
+ if non_library_spec
105
+ label.tr('-', '_')
106
+ else
107
+ pod_target.product_module_name
108
+ end
109
+
110
+ raise 'The product module name must be the same for both debug and release.' unless name.is_a? String
111
+
112
+ name.gsub(/^([0-9])/, '_\1').gsub(/[^a-zA-Z0-9_]/, '_')
113
+ end
114
+
115
+ def swift_objc_bridging_header
116
+ resolved_value_by_build_setting('SWIFT_OBJC_BRIDGING_HEADER')
117
+ end
118
+
119
+ def uses_swift?
120
+ file_accessors.any? { |fa| fa.source_files.any? { |s| s.extname == '.swift' } }
121
+ end
122
+
123
+ def pod_target_xcconfig_by_build_setting
124
+ debug_xcconfig = resolved_xcconfig(configuration: :debug)
125
+ release_xcconfig = resolved_xcconfig(configuration: :release)
126
+ debug_only_xcconfig = debug_xcconfig.reject { |k, v| release_xcconfig[k] == v }
127
+ release_only_xcconfig = release_xcconfig.reject { |k, v| debug_xcconfig[k] == v }
128
+
129
+ xconfig_by_build_setting = {}
130
+ xconfig_by_build_setting[build_settings_label(:debug)] = debug_only_xcconfig unless debug_only_xcconfig.empty?
131
+ xconfig_by_build_setting[build_settings_label(:release)] = release_only_xcconfig unless release_only_xcconfig.empty?
132
+ xconfig_by_build_setting
133
+ end
134
+
135
+ def common_pod_target_xcconfig
136
+ debug_xcconfig = resolved_xcconfig(configuration: :debug)
137
+ release_xcconfig = resolved_xcconfig(configuration: :release)
138
+ common_xcconfig = debug_xcconfig.select { |k, v| release_xcconfig[k] == v }
139
+ # If the value is an array, merge it into a string.
140
+ common_xcconfig.map do |k, v|
141
+ [k, v.is_a?(Array) ? v.shelljoin : v]
142
+ end.to_h
143
+ end
144
+
145
+ def resolved_xcconfig(configuration:)
146
+ unless resolved_xconfig_by_config[configuration]
147
+ xcconfig = pod_target_xcconfig(configuration: configuration)
148
+ resolved_xconfig_by_config[configuration] = resolve_xcconfig(xcconfig)[1]
149
+ end
150
+ resolved_xconfig_by_config[configuration].clone
151
+ end
152
+
153
+ def pod_target_xcconfig(configuration:)
154
+ pod_target
155
+ .build_settings_for_spec(non_library_spec || pod_target.root_spec, configuration: configuration)
156
+ .merged_pod_target_xcconfigs
157
+ .to_h
158
+ .merge(
159
+ 'CONFIGURATION' => configuration.to_s.capitalize,
160
+ 'PODS_TARGET_SRCROOT' => ':',
161
+ 'SRCROOT' => ':',
162
+ 'SDKROOT' => '__BAZEL_XCODE_SDKROOT__'
163
+ )
164
+ end
165
+
166
+ def resolved_value_by_build_setting(setting, additional_settings: {}, is_label_argument: false)
167
+ debug_settings = pod_target_xcconfig(configuration: :debug).merge(additional_settings)
168
+ debug_value = resolved_build_setting_value(setting, settings: debug_settings)
169
+ release_settings = pod_target_xcconfig(configuration: :release).merge(additional_settings)
170
+ release_value = resolved_build_setting_value(setting, settings: release_settings)
171
+ if debug_value == release_value
172
+ debug_value&.empty? && is_label_argument ? nil : debug_value
173
+ else
174
+ value_by_build_setting = {
175
+ build_settings_label(:debug) => debug_value.empty? && is_label_argument ? nil : debug_value,
176
+ build_settings_label(:release) => release_value.empty? && is_label_argument ? nil : release_value
177
+ }
178
+ StarlarkCompiler::AST::FunctionCall.new('select', value_by_build_setting)
179
+ end
180
+ end
181
+
182
+ def pod_target_xcconfig_header_search_paths(configuration)
183
+ settings = pod_target_xcconfig(configuration: configuration).merge('PODS_TARGET_SRCROOT' => @package)
184
+ resolved_build_setting_value('HEADER_SEARCH_PATHS', settings: settings) || []
185
+ end
186
+
187
+ def pod_target_xcconfig_user_header_search_paths(configuration)
188
+ settings = pod_target_xcconfig(configuration: configuration).merge('PODS_TARGET_SRCROOT' => @package)
189
+ resolved_build_setting_value('USER_HEADER_SEARCH_PATHS', settings: settings) || []
190
+ end
191
+
192
+ def pod_target_copts(type)
193
+ setting =
194
+ case type
195
+ when :swift then 'OTHER_SWIFT_FLAGS'
196
+ when :objc then 'OTHER_CFLAGS'
197
+ else raise "#Unsupported type #{type}"
198
+ end
199
+ copts = resolved_value_by_build_setting(setting)
200
+ copts = [copts] if copts&.is_a?(String)
201
+
202
+ debug_copts = copts_for_search_paths_by_config(type, :debug)
203
+ release_copts = copts_for_search_paths_by_config(type, :release)
204
+ copts_for_search_paths =
205
+ if debug_copts.sort == release_copts.sort
206
+ debug_copts
207
+ else
208
+ copts_by_build_setting = {
209
+ build_settings_label(:debug) => debug_copts,
210
+ build_settings_label(:release) => release_copts
211
+ }
212
+ StarlarkCompiler::AST::FunctionCall.new('select', copts_by_build_setting)
213
+ end
214
+
215
+ if copts
216
+ if copts.is_a?(Array)
217
+ if copts_for_search_paths.is_a?(Array)
218
+ copts + copts_for_search_paths
219
+ else
220
+ starlark { copts_for_search_paths + copts }
221
+ end
222
+ elsif copts_for_search_paths.is_a?(Array) && copts_for_search_paths.empty?
223
+ copts
224
+ else
225
+ starlark { copts + copts_for_search_paths }
226
+ end
227
+ else
228
+ copts_for_search_paths
229
+ end
230
+ end
231
+
232
+ def copts_for_search_paths_by_config(type, configuration)
233
+ additional_flag =
234
+ case type
235
+ when :swift then '-Xcc'
236
+ when :objc then nil
237
+ else raise "#Unsupported type #{type}"
238
+ end
239
+
240
+ copts = []
241
+ pod_target_xcconfig_header_search_paths(configuration).each do |path|
242
+ iquote = "-I#{path}"
243
+ copts << additional_flag if additional_flag
244
+ copts << iquote
245
+ end
246
+
247
+ pod_target_xcconfig_user_header_search_paths(configuration).each do |path|
248
+ iquote = "-iquote#{path}"
249
+ copts << additional_flag if additional_flag
250
+ copts << iquote
251
+ end
252
+ copts
253
+ end
254
+
255
+ def pod_target_infoplists_by_build_setting
256
+ debug_plist = resolved_build_setting_value('INFOPLIST_FILE', settings: pod_target_xcconfig(configuration: :debug))
257
+ release_plist = resolved_build_setting_value('INFOPLIST_FILE', settings: pod_target_xcconfig(configuration: :release))
258
+ if debug_plist == release_plist
259
+ []
260
+ else
261
+ plist_by_build_setting = {}
262
+ plist_by_build_setting[build_settings_label(:debug)] = [debug_plist] if debug_plist
263
+ plist_by_build_setting[build_settings_label(:release)] = [release_plist] if release_plist
264
+ plist_by_build_setting
265
+ end
266
+ end
267
+
268
+ def common_pod_target_infoplists(additional_plist: nil)
269
+ debug_plist = resolved_build_setting_value('INFOPLIST_FILE', settings: pod_target_xcconfig(configuration: :debug))
270
+ release_plist = resolved_build_setting_value('INFOPLIST_FILE', settings: pod_target_xcconfig(configuration: :release))
271
+ if debug_plist == release_plist
272
+ [debug_plist, additional_plist].compact
273
+ else
274
+ [additional_plist].compact
275
+ end
276
+ end
277
+
278
+ def to_rule_kwargs
279
+ kwargs = RuleArgs.new do |args|
280
+ args
281
+ .add(:name, label)
282
+ .add(:module_name, product_module_name, defaults: [label])
283
+ .add(:module_map, !non_library_spec && file_accessors.map(&:module_map).find(&:itself)&.relative_path_from(@package_dir)&.to_s, defaults: [nil, false]).
284
+
285
+ # public headers
286
+ add(:public_headers, glob(attr: :public_headers, sorted: false).yield_self { |f| case f when Array then f.reject { |path| path.include? '.framework/' } else f end }, defaults: [[]])
287
+ .add(:private_headers, glob(attr: :private_headers).yield_self { |f| case f when Array then f.reject { |path| path.include? '.framework/' } else f end }, defaults: [[]])
288
+ .add(:pch, glob(attr: :prefix_header, return_files: true).first, defaults: [nil])
289
+ .add(:data, glob(attr: :resources, exclude_directories: 0), defaults: [[]])
290
+ .add(:resource_bundles, {}, defaults: [{}])
291
+ .add(:swift_version, uses_swift? && pod_target.swift_version, defaults: [nil, false])
292
+ .add(:swift_objc_bridging_header, swift_objc_bridging_header, defaults: [nil])
293
+
294
+ # xcconfigs
295
+ resolve_xcconfig(common_pod_target_xcconfig, default_xcconfigs: default_xcconfigs).tap do |name, xcconfig|
296
+ args
297
+ .add(:default_xcconfig_name, name, defaults: [nil])
298
+ .add(:xcconfig, xcconfig, defaults: [{}])
299
+ end
300
+ # xcconfig_by_build_setting
301
+ args.add(:xcconfig_by_build_setting, pod_target_xcconfig_by_build_setting, defaults: [{}])
302
+ end.kwargs
303
+
304
+ file_accessors.group_by { |fa| fa.spec_consumer.requires_arc.class }.tap do |fa_by_arc|
305
+ srcs = Hash.new { |h, k| h[k] = [] }
306
+ non_arc_srcs = Hash.new { |h, k| h[k] = [] }
307
+ expand = ->(g) { expand_glob(g, extensions: %w[h hh m mm swift c cc cpp]) }
308
+
309
+ Array(fa_by_arc[TrueClass]).each do |fa|
310
+ srcs[fa.spec_consumer.exclude_files] += fa.spec_consumer.source_files.flat_map(&expand)
311
+ end
312
+ Array(fa_by_arc[FalseClass]).each do |fa|
313
+ non_arc_srcs[fa.spec_consumer.exclude_files] += fa.spec_consumer.source_files.flat_map(&expand)
314
+ end
315
+ (Array(fa_by_arc[Array]) + Array(fa_by_arc[String])).each do |fa|
316
+ arc_globs = Array(fa.spec_consumer.requires_arc).flat_map(&expand)
317
+ globs = fa.spec_consumer.source_files.flat_map(&expand)
318
+
319
+ srcs[fa.spec_consumer.exclude_files] += arc_globs
320
+ non_arc_srcs[fa.spec_consumer.exclude_files + arc_globs] += globs
321
+ end
322
+
323
+ m = lambda do |h|
324
+ h.delete_if do |_, v|
325
+ v.delete_if { |g| g.include?('.framework/') }
326
+ v.empty?
327
+ end
328
+ return [] if h.empty?
329
+
330
+ h.map do |excludes, globs|
331
+ excludes = excludes.empty? ? {} : { exclude: excludes.flat_map(&method(:expand_glob)) }
332
+ starlark { function_call(:glob, globs.uniq, **excludes) }
333
+ end.reduce(&:+)
334
+ end
335
+
336
+ kwargs[:srcs] = m[srcs]
337
+ kwargs[:non_arc_srcs] = m[non_arc_srcs]
338
+ end
339
+
340
+ file_accessors.each_with_object({}) do |fa, bundles|
341
+ fa.spec_consumer.resource_bundles.each do |name, file_patterns|
342
+ bundle = bundles[name] ||= {}
343
+ patterns_by_exclude = bundle[fa.spec_consumer.exclude_files] ||= []
344
+ patterns_by_exclude.concat(file_patterns.flat_map { |g| expand_glob(g, expand_directories: true) })
345
+ end
346
+ end.tap do |bundles|
347
+ kwargs[:resource_bundles] = bundles.map do |bundle_name, patterns_by_excludes|
348
+ patterns_by_excludes.delete_if { |_, v| v.empty? }
349
+ # resources implicitly have dirs expanded by CocoaPods
350
+ resources = patterns_by_excludes.map do |excludes, globs|
351
+ excludes = excludes.empty? ? {} : { exclude: excludes.flat_map(&method(:expand_glob)) }
352
+ starlark { function_call(:glob, globs.uniq, exclude_directories: 0, **excludes) }
353
+ end.reduce(&:+)
354
+ [bundle_name, resources]
355
+ end.to_h
356
+ end
357
+
358
+ # non-propagated stuff for a target that should build.
359
+ if pod_target.should_build?
360
+ kwargs[:swift_copts] = pod_target_copts(:swift)
361
+ kwargs[:objc_copts] = pod_target_copts(:objc)
362
+ linkopts = resolved_value_by_build_setting('OTHER_LDFLAGS')
363
+ linkopts = [linkopts] if linkopts.is_a? String
364
+ kwargs[:linkopts] = linkopts || []
365
+ end
366
+
367
+ # propagated
368
+ kwargs[:defines] = []
369
+ kwargs[:other_inputs] = []
370
+ kwargs[:linking_style] = nil
371
+ kwargs[:runtime_deps] = []
372
+ kwargs[:sdk_dylibs] = file_accessors.flat_map { |fa| fa.spec_consumer.libraries }.sort.uniq
373
+ kwargs[:sdk_frameworks] = file_accessors.flat_map { |fa| fa.spec_consumer.frameworks }.sort.uniq
374
+ kwargs[:sdk_includes] = []
375
+ kwargs[:weak_sdk_frameworks] = file_accessors.flat_map { |fa| fa.spec_consumer.weak_frameworks }.sort.uniq
376
+
377
+ kwargs[:vendored_static_frameworks] = glob(attr: :vendored_static_frameworks, return_files: true)
378
+ kwargs[:vendored_dynamic_frameworks] = glob(attr: :vendored_dynamic_frameworks, return_files: true)
379
+ kwargs[:vendored_static_libraries] = glob(attr: :vendored_static_libraries, return_files: true)
380
+ kwargs[:vendored_dynamic_libraries] = glob(attr: :vendored_dynamic_libraries, return_files: true)
381
+ kwargs[:vendored_xcframeworks] = vendored_xcframeworks
382
+
383
+ # any compatible provider: CCProvider, SwiftInfo, etc
384
+ kwargs[:deps] = deps_by_config
385
+
386
+ case non_library_spec&.spec_type
387
+ when :test
388
+ kwargs.merge!(test_kwargs)
389
+ when :app
390
+ kwargs.merge!(app_kwargs)
391
+ when nil
392
+ kwargs.merge!(framework_kwargs)
393
+ end
394
+
395
+ defaults = self.defaults
396
+ kwargs.delete_if { |k, v| defaults[k] == v }
397
+ kwargs
398
+ end
399
+
400
+ def defaults
401
+ {
402
+ module_name: label,
403
+ module_map: nil,
404
+ srcs: [],
405
+ non_arc_srcs: [],
406
+ hdrs: [],
407
+ pch: nil,
408
+ data: [],
409
+ resource_bundles: {},
410
+
411
+ swift_copts: [],
412
+ objc_copts: [],
413
+ cc_copts: [],
414
+ defines: [],
415
+ linkopts: [],
416
+ other_inputs: [],
417
+ linking_style: nil,
418
+ runtime_deps: [],
419
+ sdk_dylibs: [],
420
+ sdk_frameworks: [],
421
+ sdk_includes: [],
422
+ weak_sdk_frameworks: [],
423
+
424
+ bundle_id: nil,
425
+ env: {},
426
+ infoplists_by_build_setting: [],
427
+ infoplists: [],
428
+ minimum_os_version: nil,
429
+ test_host: nil,
430
+ platforms: {},
431
+
432
+ app_icons: [],
433
+ bundle_name: nil,
434
+ entitlements: nil,
435
+ entitlements_validation: nil,
436
+ extensions: [],
437
+ frameworks: [],
438
+ ipa_post_processor: nil,
439
+ launch_images: [],
440
+ launch_storyboard: nil,
441
+ provisioning_profile: nil,
442
+ resources: [],
443
+ settings_bundle: [],
444
+ strings: [],
445
+ version: [],
446
+ watch_application: [],
447
+
448
+ vendored_static_frameworks: [],
449
+ vendored_dynamic_frameworks: [],
450
+ vendored_static_libraries: [],
451
+ vendored_dynamic_libraries: [],
452
+ vendored_xcframeworks: [],
453
+
454
+ deps: []
455
+ }
456
+ end
457
+
458
+ def deps_by_config
459
+ debug_targets = dependent_targets_by_config[:debug]
460
+ release_targets = dependent_targets_by_config[:release]
461
+
462
+ debug_labels = debug_targets.map { |dt| dt.bazel_label(relative_to: package) }
463
+ release_labels = release_targets.map { |dt| dt.bazel_label(relative_to: package) }
464
+ shared_labels = (debug_labels & release_labels).uniq
465
+
466
+ debug_only_labels = debug_labels - shared_labels
467
+ release_only_labels = release_labels - shared_labels
468
+
469
+ sorted_debug_labels = Pod::Bazel::Util.sort_labels(debug_only_labels)
470
+ sorted_release_labels = Pod::Bazel::Util.sort_labels(release_only_labels)
471
+ sorted_shared_labels = Pod::Bazel::Util.sort_labels(shared_labels)
472
+
473
+ labels_by_config = {}
474
+
475
+ if !sorted_debug_labels.empty? || !sorted_release_labels.empty?
476
+ labels_by_config[build_settings_label(:debug)] = sorted_debug_labels
477
+ labels_by_config[build_settings_label(:release)] = sorted_release_labels
478
+ end
479
+
480
+ if labels_by_config.empty? # no per-config dependency
481
+ sorted_shared_labels
482
+ elsif sorted_shared_labels.empty? # per-config dependencies exist, avoiding adding an empty array
483
+ StarlarkCompiler::AST::FunctionCall.new('select', labels_by_config)
484
+ else # both per-config and shared dependencies exist
485
+ starlark { StarlarkCompiler::AST::FunctionCall.new('select', labels_by_config) + sorted_shared_labels }
486
+ end
487
+ end
488
+
489
+ def glob(attr:, return_files: !pod_target.sandbox.local?(pod_target.pod_name), sorted: true, excludes: [], exclude_directories: 1)
490
+ if !return_files
491
+ case attr
492
+ when :public_headers then attr = :public_header_files
493
+ when :private_headers then attr = :private_header_files
494
+ end
495
+
496
+ globs = file_accessors.map(&:spec_consumer).flat_map(&attr).flat_map { |g| expand_glob(g, expand_directories: exclude_directories != 1) }
497
+ excludes += file_accessors.map(&:spec_consumer).flat_map(&:exclude_files).flat_map { |g| expand_glob(g) }
498
+ excludes = excludes.empty? ? {} : { exclude: excludes }
499
+ excludes[:exclude_directories] = exclude_directories unless exclude_directories == 1
500
+ if globs.empty?
501
+ []
502
+ else
503
+ starlark { function_call(:glob, globs, **excludes) }
504
+ end
505
+ else
506
+ file_accessors.flat_map(&attr)
507
+ .compact
508
+ .map { |path| path.relative_path_from(@package_dir).to_s }
509
+ .yield_self { |paths| sorted ? paths.sort : paths }
510
+ .uniq
511
+ end
512
+ end
513
+
514
+ def expand_glob(glob, extensions: nil, expand_directories: false)
515
+ if (m = glob.match(/\{([^\{\}]+)\}/))
516
+ m[1].split(',').flat_map do |alt|
517
+ expand_glob("#{m.pre_match}#{alt}#{m.post_match}")
518
+ end.uniq
519
+ elsif (m = glob.match(/\[([^\[\]]+)\]/))
520
+ m[1].each_char.flat_map do |alt|
521
+ expand_glob("#{m.pre_match}#{alt}#{m.post_match}")
522
+ end.uniq
523
+ elsif extensions && File.extname(glob).empty?
524
+ glob = glob.chomp('**/*') # If we reach here and the glob ends with **/*, we need to avoid duplicating it (we do not want to end up with **/*/**/*)
525
+ if File.basename(glob) == '*'
526
+ extensions.map { |ext| "#{glob}.#{ext}" }
527
+ else
528
+ extensions.map do |ext|
529
+ File.join(glob, '**', "*.#{ext}")
530
+ end
531
+ end
532
+ elsif expand_directories
533
+ if glob.end_with?('/**/*')
534
+ [glob]
535
+ elsif glob.end_with?('/*')
536
+ [glob.sub(%r{/\*$}, '/**/*')]
537
+ else
538
+ [glob, glob.chomp('/') + '/**/*']
539
+ end
540
+ else
541
+ [glob]
542
+ end
543
+ end
544
+
545
+ def framework_kwargs
546
+ {
547
+ visibility: ['//visibility:public'],
548
+ platforms: { pod_target.platform.string_name.downcase => pod_target.platform.deployment_target.to_s }
549
+ }
550
+ end
551
+
552
+ def test_kwargs
553
+ {
554
+ bundle_id: resolved_value_by_build_setting('PRODUCT_BUNDLE_IDENTIFIER'),
555
+ env: pod_target.scheme_for_spec(non_library_spec).fetch(:environment_variables, {}),
556
+ infoplists_by_build_setting: pod_target_infoplists_by_build_setting,
557
+ infoplists: common_pod_target_infoplists(additional_plist: nil_if_empty(non_library_spec.consumer(pod_target.platform).info_plist)),
558
+ minimum_os_version: pod_target.deployment_target_for_non_library_spec(non_library_spec),
559
+ test_host: test_host&.bazel_label(relative_to: package) || file_accessors.any? { |fa| fa.spec_consumer.requires_app_host? } || nil
560
+ }
561
+ end
562
+
563
+ def app_kwargs
564
+ # maps to kwargs listed for https://github.com/bazelbuild/rules_apple/blob/master/doc/rules-ios.md#ios_application
565
+ {
566
+ app_icons: [],
567
+ bundle_id: resolved_value_by_build_setting('PRODUCT_BUNDLE_IDENTIFIER') || "org.cocoapods.#{label}",
568
+ bundle_name: nil,
569
+ entitlements: resolved_value_by_build_setting('CODE_SIGN_ENTITLEMENTS', is_label_argument: true),
570
+ entitlements_validation: nil,
571
+ extensions: [],
572
+ families: app_targeted_device_families,
573
+ frameworks: [],
574
+ infoplists_by_build_setting: pod_target_infoplists_by_build_setting,
575
+ infoplists: common_pod_target_infoplists(additional_plist: nil_if_empty(non_library_spec.consumer(pod_target.platform).info_plist)),
576
+ ipa_post_processor: nil,
577
+ launch_images: [],
578
+ launch_storyboard: nil,
579
+ minimum_os_version: pod_target.deployment_target_for_non_library_spec(non_library_spec),
580
+ provisioning_profile: nil,
581
+ resources: [],
582
+ settings_bundle: [],
583
+ strings: [],
584
+ version: [],
585
+ watch_application: []
586
+ }
587
+ end
588
+
589
+ def app_targeted_device_families
590
+ # Reads the targeted device families from xconfig TARGETED_DEVICE_FAMILY. Supports both iphone and ipad by default.
591
+ device_families = resolved_value_by_build_setting('TARGETED_DEVICE_FAMILY') || '1,2'
592
+ raise 'TARGETED_DEVICE_FAMILY must be the same for both debug and release.' unless device_families.is_a? String
593
+
594
+ device_families.split(',').map do |device_family|
595
+ case device_family
596
+ when '1' then 'iphone'
597
+ when '2' then 'ipad'
598
+ else raise "Unsupported device family: #{device_family}"
599
+ end
600
+ end
601
+ end
602
+
603
+ def vendored_xcframeworks
604
+ pod_target.xcframeworks.values.flatten(1).uniq.map do |xcframework|
605
+ {
606
+ 'name' => xcframework.name,
607
+ 'slices' => xcframework.slices.map do |slice|
608
+ {
609
+ 'identifier' => slice.identifier,
610
+ 'platform' => slice.platform.name.to_s,
611
+ 'platform_variant' => slice.platform_variant.to_s,
612
+ 'supported_archs' => slice.supported_archs,
613
+ 'path' => slice.path.relative_path_from(@package_dir).to_s,
614
+ 'build_type' => { 'linkage' => slice.build_type.linkage.to_s, 'packaging' => slice.build_type.packaging.to_s }
615
+ }
616
+ end
617
+ }
618
+ end
619
+ end
620
+
621
+ def nil_if_empty(arr)
622
+ arr.empty? ? nil : arr
623
+ end
624
+
625
+ def starlark(&blk)
626
+ StarlarkCompiler::AST.build(&blk)
627
+ end
628
+ end
629
+ end
630
+ end