cocoapods-bazel 0.1.1

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