cocoapods-project-gen 0.1.0 → 0.2.3

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.
@@ -0,0 +1,341 @@
1
+ module ProjectGen
2
+ module Helper
3
+ include Pod
4
+
5
+ def self.app_target_name(platform)
6
+ "App-#{platform.string_name}"
7
+ end
8
+
9
+ private
10
+
11
+ # The specifications matching the specified pod name
12
+ #
13
+ # @param [String] pod_name the name of the pod
14
+ #
15
+ # @return [Hash{Specification => Array<Taget>}] the specifications grouped by platform
16
+ #
17
+ def specs_for_pods
18
+ @installer.pod_targets.each_with_object({}) do |pod_target, hash|
19
+ hash[pod_target.root_spec] ||= []
20
+ hash[pod_target.root_spec] << pod_target
21
+ end
22
+ end
23
+
24
+ def setup_gen_environment
25
+ project_gen_dir.rmtree if project_gen_dir.exist?
26
+ project_gen_dir.mkpath
27
+ @original_config = Pod::Config.instance.clone
28
+ config.installation_root = project_gen_dir
29
+ config.silent = !config.verbose
30
+ end
31
+
32
+ # !@group Lint steps
33
+ def perform_linting
34
+ podspecs.each do |podspec|
35
+ linter = Pod::Specification::Linter.new(podspec)
36
+ linter.lint
37
+ @results.results.concat(linter.results.to_a)
38
+ end
39
+ end
40
+
41
+ def podspecs
42
+ return @podspecs if defined? @podspecs
43
+
44
+ additional_podspec_pods = external_podspecs ? Dir.glob(external_podspecs) : []
45
+ additional_path_pods = include_podspecs ? Dir.glob(include_podspecs) : []
46
+ @podspecs = (additional_podspec_pods + additional_path_pods).uniq.each_with_object({}) do |path, hash|
47
+ spec = Pod::Specification.from_file(path)
48
+ old_spec = hash[spec.name]
49
+ if old_spec && use_latest
50
+ hash[spec.name] = [old_spec, spec].max { |old, new| old.version <=> new.version }
51
+ else
52
+ hash[spec.name] = spec
53
+ end
54
+ end.values
55
+ end
56
+
57
+ def include_specifications
58
+ (include_podspecs ? Dir.glob(include_podspecs) : []).map do |path|
59
+ Pod::Specification.from_file(path)
60
+ end
61
+ end
62
+
63
+ # Returns a list of platforms to lint for a given Specification
64
+ #
65
+ # @return [Array<Platform>] platforms to lint for the given specification
66
+ #
67
+ def determine_platforms
68
+ return @determine_platforms if defined?(@determine_platforms) && @determine_platforms == @platform
69
+
70
+ platforms = podspecs.flat_map(&:available_platforms).uniq
71
+ platforms = platforms.map do |platform|
72
+ default = Pod::Podfile::TargetDefinition::PLATFORM_DEFAULTS[platform.name]
73
+ deployment_target = podspecs.flat_map do |library_spec|
74
+ subspecs = determine_subspecs[library_spec]
75
+ if subspecs && !subspecs.empty?
76
+ subspecs.map { |s| Pod::Version.new(s.deployment_target(platform.name) || default) }
77
+ else
78
+ Pod::Version.new(library_spec.deployment_target(platform.name) || default)
79
+ end
80
+ end.max
81
+ if platform.name == :ios && use_frameworks
82
+ minimum = Pod::Version.new('8.0')
83
+ deployment_target = [deployment_target, minimum].max
84
+ end
85
+ Pod::Platform.new(platform.name, deployment_target)
86
+ end.uniq
87
+
88
+ unless @platforms.empty?
89
+ # Validate that the platforms specified are actually supported by the spec
90
+ platforms = @platforms.map do |platform|
91
+ matching_platform = platforms.find { |p| p.name == platform.name }
92
+ unless matching_platform
93
+ raise Informative, "Platform `#{platform}` is not supported by specification `#{spec}`."
94
+ end
95
+
96
+ matching_platform
97
+ end.uniq
98
+ end
99
+ @platform = platforms
100
+ @determine_platforms = platforms
101
+ end
102
+
103
+ def determine_subspecs
104
+ return @determine_subspecs if defined? @determine_subspecs
105
+ return {} if @only_subspecs.nil?
106
+
107
+ subspecs = @only_subspecs.dup
108
+ ha = podspecs.each_with_object({}) do |podspec, hash|
109
+ return hash if subspecs.empty?
110
+
111
+ base_name = podspec.name
112
+ s_s = []
113
+ subspecs.delete_if { |ss| s_s << podspec.subspec_by_name(ss, false) if ss.split('/').shift == base_name }
114
+ s_s.compact!
115
+ hash[podspec] = s_s unless s_s.empty?
116
+ end
117
+ subspecs.each { |s| results.warning('subspecs', "#{s} should use NAME/NAME.") }
118
+ @determine_subspecs = ha
119
+ end
120
+
121
+ def validate_vendored_dynamic_frameworks
122
+ platform = determine_platforms.find { |pl| pl.name == :ios }
123
+ targets = relative_pod_targets_from_platfrom(platform)
124
+ targets.flat_map(&:file_accessors).each do |file_accessor|
125
+ deployment_target = platform.deployment_target
126
+ dynamic_frameworks = file_accessor.vendored_dynamic_frameworks
127
+ dynamic_libraries = file_accessor.vendored_dynamic_libraries
128
+ if (dynamic_frameworks.count.positive? || dynamic_libraries.count.positive?) && platform.name == :ios &&
129
+ (deployment_target.nil? || deployment_target.major < 8)
130
+ error('dynamic', 'Dynamic frameworks and libraries are only supported on iOS 8.0 and onwards.')
131
+ end
132
+ end
133
+ end
134
+
135
+ def create_app_project
136
+ p_path = project_gen_dir + 'App.xcodeproj'
137
+ app_project = if p_path.exist?
138
+ Xcodeproj::Project.open(p_path)
139
+ else
140
+ Xcodeproj::Project.new(File.join(project_gen_dir, 'App.xcodeproj'))
141
+ end
142
+ determine_platforms.each do |platform|
143
+ app_target = Pod::Generator::AppTargetHelper.add_app_target(app_project, platform.name,
144
+ platform.deployment_target.to_s, Helper.app_target_name(platform))
145
+ sandbox = Pod::Sandbox.new(config.sandbox_root)
146
+ info_plist_path = app_project.path.dirname.+("App/#{Helper.app_target_name(platform)}-Info.plist")
147
+ Pod::Installer::Xcode::PodsProjectGenerator::TargetInstallerHelper.create_info_plist_file_with_sandbox(sandbox,
148
+ info_plist_path,
149
+ app_target,
150
+ '1.0.0',
151
+ Pod::Platform.new(platform.name),
152
+ :appl,
153
+ build_setting_value: "$(SRCROOT)/App/#{Helper.app_target_name(platform)}-Info.plist")
154
+ Pod::Generator::AppTargetHelper.add_swift_version(app_target, derived_swift_version)
155
+ app_target.build_configurations.each do |config|
156
+ # Lint will fail if a AppIcon is set but no image is found with such name
157
+ # Happens only with Static Frameworks enabled but shouldn't be set anyway
158
+ config.build_settings.delete('ASSETCATALOG_COMPILER_APPICON_NAME')
159
+ # Ensure this is set generally but we have seen an issue with ODRs:
160
+ # see: https://github.com/CocoaPods/CocoaPods/issues/10933
161
+ config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = 'org.cocoapods.${PRODUCT_NAME:rfc1034identifier}'
162
+ end
163
+ end
164
+ app_project.save
165
+ app_project.recreate_user_schemes
166
+ end
167
+
168
+ # It creates a podfile in memory and builds a library containing the pod
169
+ # for all available platforms with xcodebuild.
170
+ #
171
+ def install_pod
172
+ %i[validate_targets generate_pods_project integrate_user_project
173
+ perform_post_install_actions].each { |m| @installer.send(m) }
174
+ configure_pod_targets(@installer.target_installation_results)
175
+
176
+ determine_platforms.each do |platform|
177
+ validate_dynamic_framework_support(platform.name, @installer.aggregate_targets, platform.deployment_target.to_s)
178
+ end
179
+ @installer.pods_project.save
180
+ end
181
+
182
+ # @param [Array<Hash{String, TargetInstallationResult}>] target_installation_results
183
+ # The installation results to configure
184
+ #
185
+ def configure_pod_targets(target_installation_results)
186
+ target_installation_results.first.values.each do |pod_target_installation_result|
187
+ pod_target = pod_target_installation_result.target
188
+ native_target = pod_target_installation_result.native_target
189
+ native_target.build_configuration_list.build_configurations.each do |build_configuration|
190
+ (build_configuration.build_settings['OTHER_CFLAGS'] ||= '$(inherited)') << ' -Wincomplete-umbrella'
191
+ next unless pod_target.uses_swift?
192
+
193
+ # The Swift version for the target being validated can be overridden by `--swift-version` or the
194
+ # `.swift-version` file so we always use the derived Swift version.
195
+ #
196
+ # For dependencies, if the derived Swift version is supported then it is the one used. Otherwise, the Swift
197
+ # version for dependencies is inferred by the target that is integrating them.
198
+ swift_version = pod_target.spec_swift_versions.map(&:to_s).find do |v|
199
+ v == derived_swift_version
200
+ end || pod_target.swift_version
201
+ build_configuration.build_settings['SWIFT_VERSION'] = swift_version
202
+ end
203
+ pod_target_installation_result.test_specs_by_native_target.each do |test_native_target, test_spec|
204
+ next unless pod_target.uses_swift_for_spec?(test_spec)
205
+
206
+ test_native_target.build_configuration_list.build_configurations.each do |build_configuration|
207
+ swift_version = pod_target == validation_pod_target ? derived_swift_version : pod_target.swift_version
208
+ build_configuration.build_settings['SWIFT_VERSION'] = swift_version
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ # Produces an error of dynamic frameworks were requested but are not supported by the deployment target
215
+ #
216
+ # @param [Array<AggregateTarget>] aggregate_targets
217
+ # The aggregate targets installed by the installer
218
+ #
219
+ # @param [String,Version] deployment_target
220
+ # The deployment target of the installation
221
+ #
222
+ def validate_dynamic_framework_support(platform_name, aggregate_targets, deployment_target)
223
+ return unless platform_name == :ios
224
+ return unless deployment_target.nil? || Pod::Version.new(deployment_target).major < 8
225
+
226
+ aggregate_targets.each do |target|
227
+ next unless target.pod_targets.any?(&:uses_swift?)
228
+
229
+ uses_xctest = target.spec_consumers.any? do |c|
230
+ (c.frameworks + c.weak_frameworks).include? 'XCTest'
231
+ end
232
+ unless uses_xctest
233
+ error('swift',
234
+ 'Swift support uses dynamic frameworks and is therefore only supported on iOS > 8.')
235
+ end
236
+ end
237
+ end
238
+
239
+ # @return [Boolean]
240
+ #
241
+ def validated?
242
+ results.result_type != :error && (results.result_type != :warning || allow_warnings)
243
+ end
244
+
245
+ # Returns the pod target for the pod being relatived. Installation must have occurred before this can be invoked.
246
+ #
247
+ def relative_pod_targets_from_platfrom(platform)
248
+ @installer.pod_targets.select { |pt| pt.platform.name == platform.name }
249
+ end
250
+
251
+ # @param [String] platform_name
252
+ # the name of the platform, which should be declared
253
+ # in the Podfile.
254
+ #
255
+ # @param [String] deployment_target
256
+ # the deployment target, which should be declared in
257
+ # the Podfile.
258
+ #
259
+ # @param [Boolean] use_frameworks
260
+ # whether frameworks should be used for the installation
261
+ #
262
+ # @param [Array<String>] test_spec_names
263
+ # the test spec names to include in the podfile.
264
+ #
265
+ # @return [Podfile] a podfile that requires the specification on the
266
+ # current platform.
267
+ #
268
+ # @note The generated podfile takes into account whether the linter is
269
+ # in local mode.
270
+ #
271
+ def podfile_from_spec(use_frameworks = true, use_modular_headers = false, use_static_frameworks = false)
272
+ urls = source_urls
273
+ all_podspec_pods = podspecs
274
+ platforms = determine_platforms
275
+ d_subspecs = determine_subspecs
276
+ Pod::Podfile.new do
277
+ install! 'cocoapods', deterministic_uuids: false, warn_for_unused_master_specs_repo: false
278
+ # By default inhibit warnings for all pods, except the one being validated.
279
+ inhibit_all_warnings!
280
+ urls.each { |u| source(u) }
281
+ platforms.each do |platform|
282
+ app_name = ProjectGen::Helper.app_target_name(platform)
283
+ target(app_name) do
284
+ if use_static_frameworks
285
+ use_frameworks!(linkage: :static)
286
+ else
287
+ use_frameworks!(use_frameworks)
288
+ end
289
+ use_modular_headers! if use_modular_headers
290
+ platform(platform.name, platform.deployment_target.to_s)
291
+
292
+ all_podspec_pods.each do |podspec|
293
+ subspecs = d_subspecs[podspec]
294
+ if subspecs && !subspecs.empty?
295
+ subspecs.each do |s|
296
+ if s.supported_on_platform?(platform)
297
+ pod s.name, podspec: s.defined_in_file.to_s,
298
+ inhibit_warnings: false
299
+ end
300
+ end
301
+ elsif podspec.supported_on_platform?(platform)
302
+ pod podspec.name, podspec: podspec.defined_in_file.to_s,
303
+ inhibit_warnings: false
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
309
+ end
310
+
311
+ def add_app_project_import
312
+ app_project = Xcodeproj::Project.open(project_gen_dir + 'App.xcodeproj')
313
+ app_project.targets.each do |app_target|
314
+ platform = determine_platforms.find { |pl| pl.name == app_target.platform_name }
315
+ pod_targets = relative_pod_targets_from_platfrom(platform)
316
+ pod_targets.each do |pod_target|
317
+ Pod::Generator::AppTargetHelper.add_app_project_import(app_project, app_target, pod_target, platform.name,
318
+ Helper.app_target_name(platform))
319
+ end
320
+ Pod::Generator::AppTargetHelper.add_xctest_search_paths(app_target) if pod_targets.any? do |pt|
321
+ pt.spec_consumers.any? do |c|
322
+ c.frameworks.include?('XCTest') || c.weak_frameworks.include?('XCTest')
323
+ end
324
+ end
325
+ Pod::Generator::AppTargetHelper.add_empty_swift_file(app_project, app_target) if pod_targets.any?(&:uses_swift?)
326
+ app_project.save
327
+ Xcodeproj::XCScheme.share_scheme(app_project.path, Helper.app_target_name(platform))
328
+ pod_targets.each do |pod_target|
329
+ if shares_pod_target_xcscheme?(pod_target)
330
+ Xcodeproj::XCScheme.share_scheme(@installer.pods_project.path,
331
+ pod_target.label)
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ def shares_pod_target_xcscheme?(pod_target)
338
+ Pathname.new(@installer.pods_project.path + pod_target.label).exist?
339
+ end
340
+ end
341
+ end
@@ -0,0 +1,134 @@
1
+ module ProjectGen
2
+ module SwiftModule
3
+ # @return [String] the SWIFT_VERSION within the .swift-version file or nil.
4
+ #
5
+ def dot_swift_version(podspec)
6
+ file = podspec.defined_in_file
7
+ swift_version_path = file.dirname + '.swift-version'
8
+ return unless swift_version_path.exist?
9
+
10
+ swift_version_path.read.strip
11
+ end
12
+
13
+ # @return [String] The derived Swift version to use for validation. The order of precedence is as follows:
14
+ # - The `--swift-version` parameter is always checked first and honored if passed.
15
+ # - The `swift_versions` DSL attribute within the podspec, in which case the latest version is always chosen.
16
+ # - The Swift version within the `.swift-version` file if present.
17
+ # - If none of the above are set then the `#DEFAULT_SWIFT_VERSION` is used.
18
+ #
19
+ def derived_swift_version
20
+ @derived_swift_version ||= if swift_version
21
+ swift_version
22
+ else
23
+ version = podspecs.map do |podspec|
24
+ podspec.swift_versions.max || dot_swift_version(podspec)
25
+ end.compact.max
26
+ if version
27
+ version.to_s
28
+ else
29
+ Constants::DEFAULT_SWIFT_VERSION
30
+ end
31
+ end
32
+ end
33
+
34
+ # Performs validation for the version of Swift used during validation.
35
+ #
36
+ # An error will be displayed if the user has provided a `swift_versions` attribute within the podspec but is also
37
+ # using either `--swift-version` parameter or a `.swift-version` file with a Swift version that is not declared
38
+ # within the attribute.
39
+ #
40
+ # The user will be warned that the default version of Swift was used if the following things are true:
41
+ # - The project uses Swift at all
42
+ # - The user did not supply a Swift version via a parameter
43
+ # - There is no `swift_versions` attribute set within the specification
44
+ # - There is no `.swift-version` file present either.
45
+ #
46
+ def validate_swift_version
47
+
48
+ specs_for_pods.each_pair do |spec, pod_targets|
49
+ next unless pod_targets.any?(&:uses_swift?)
50
+
51
+ spec_swift_versions = spec.swift_versions.map(&:to_s)
52
+
53
+ dot_swift = dot_swift_version(spec)
54
+ unless spec_swift_versions.empty?
55
+ message = nil
56
+ if !dot_swift.nil? && !spec_swift_versions.include?(dot_swift)
57
+ message = "Specification `#{spec.name}` specifies inconsistent `swift_versions` (#{spec_swift_versions.map do |s|
58
+ "`#{s}`"
59
+ end.to_sentence}) compared to the one present in your `.swift-version` file (`#{dot_swift_version}`). " \
60
+ 'Please remove the `.swift-version` file which is now deprecated and only use the `swift_versions` attribute within your podspec.'
61
+ elsif !swift_version.nil? && !spec_swift_versions.include?(swift_version)
62
+ message = "Specification `#{spec.name}` specifies inconsistent `swift_versions` (#{spec_swift_versions.map do |s|
63
+ "`#{s}`"
64
+ end.to_sentence}) compared to the one passed during gen (`#{swift_version}`)."
65
+ end
66
+ unless message.nil?
67
+ @results.error('swift', message)
68
+ break
69
+ end
70
+ end
71
+
72
+ if swift_version.nil? && spec.swift_versions.empty?
73
+ if !dot_swift.nil?
74
+ # The user will be warned to delete the `.swift-version` file in favor of the `swift_versions` DSL attribute.
75
+ # This is intentionally not a lint warning since we do not want to break existing setups and instead just soft
76
+ # deprecate this slowly.
77
+ #
78
+ Pod::UI.warn 'Usage of the `.swift_version` file has been deprecated! Please delete the file and use the ' \
79
+ "`swift_versions` attribute within your podspec instead.\n".yellow
80
+ else
81
+ results.warning('swift',
82
+ 'The generator used ' \
83
+ "Swift `#{Constants::DEFAULT_SWIFT_VERSION}` by default because no Swift version was specified. " \
84
+ 'To specify a Swift version during validation, add the `swift_versions` attribute in your podspec. ' \
85
+ 'Note that usage of a `.swift-version` file is now deprecated.')
86
+ end
87
+ end
88
+ end
89
+ end
90
+ # Adds a shell script phase, intended only for library targets that contain swift,
91
+ # to copy the ObjC compatibility header (the -Swift.h file that the swift compiler generates)
92
+ # to the built products directory. Additionally, the script phase copies the module map, appending a `.Swift`
93
+ # submodule that references the (moved) compatibility header. Since the module map has been moved, the umbrella header
94
+ # is _also_ copied, so that it is sitting next to the module map. This is necessary for a successful archive build.
95
+ #
96
+ # @param [PBXNativeTarget] native_target
97
+ # the native target to add the Swift static library script phase into.
98
+ #
99
+ # @return [Void]
100
+ #
101
+ def add_swift_library_compatibility_header(targets)
102
+ targets.select(&:build_as_library?).each do |target|
103
+ relative_module_map_path = target.module_map_path.relative_path_from(target.sandbox.root)
104
+ relative_umbrella_header_path = target.umbrella_header_path.relative_path_from(target.sandbox.root)
105
+ shell_script = <<-SH.strip_heredoc
106
+ COMPATIBILITY_HEADER_ROOT_PATH="${SRCROOT}/${PRODUCT_MODULE_NAME}/#{Constants::COPY_LIBRARY_SWIFT_HEADERS}"
107
+ COPY_MODULE_MAP_PATH="${COMPATIBILITY_HEADER_ROOT_PATH}/${PRODUCT_MODULE_NAME}.modulemap"
108
+ ditto "${PODS_ROOT}/#{relative_module_map_path}" "${COPY_MODULE_MAP_PATH}"
109
+ UMBRELLA_HEADER_PATH="${PODS_ROOT}/#{relative_umbrella_header_path}"
110
+ if test -f "$UMBRELLA_HEADER_PATH"; then
111
+ ditto "$UMBRELLA_HEADER_PATH" "${COMPATIBILITY_HEADER_ROOT_PATH}"
112
+ fi
113
+ SH
114
+
115
+ target.root_spec.script_phases ||= []
116
+ target.root_spec.script_phases += [{ name: 'Copy Copy generated module header', script: shell_script }]
117
+ next unless target.uses_swift?
118
+
119
+ shell_script = <<-SH.strip_heredoc
120
+ COMPATIBILITY_HEADER_ROOT_PATH="${SRCROOT}/${PRODUCT_MODULE_NAME}/#{Constants::COPY_LIBRARY_SWIFT_HEADERS}"
121
+ COPY_COMPATIBILITY_HEADER_PATH="${COMPATIBILITY_HEADER_ROOT_PATH}/${PRODUCT_MODULE_NAME}-Swift.h"#{' '}
122
+ COPY_MODULE_MAP_PATH="${COMPATIBILITY_HEADER_ROOT_PATH}/${PRODUCT_MODULE_NAME}.modulemap"
123
+ ditto "${DERIVED_SOURCES_DIR}/${PRODUCT_MODULE_NAME}-Swift.h" "${COPY_COMPATIBILITY_HEADER_PATH}"#{' '}
124
+ ditto "${BUILT_PRODUCTS_DIR}/${PRODUCT_MODULE_NAME}.swiftmodule" "${COMPATIBILITY_HEADER_ROOT_PATH}/${PRODUCT_MODULE_NAME}.swiftmodule"#{' '}
125
+ printf "\\n\\nmodule ${PRODUCT_MODULE_NAME}.Swift {\\n header \\"${PRODUCT_MODULE_NAME}-Swift.h\\"\\n requires objc\\n}\\n" >> "${COPY_MODULE_MAP_PATH}"
126
+ SH
127
+ target.root_spec.script_phases += [{
128
+ name: 'Copy Copy generated module and compatibility header',
129
+ script: shell_script
130
+ }]
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,93 @@
1
+ require 'cocoapods/target/pod_target'
2
+
3
+ module ProjectGen
4
+
5
+ require 'delegate'
6
+
7
+ class GenTarget < DelegateClass(Pod::PodTarget)
8
+ def initialize(target)
9
+ super(target)
10
+ target.uses_swift?
11
+ end
12
+ end
13
+
14
+ module ProductHelper
15
+
16
+ def version
17
+ root_spec.version
18
+ end
19
+
20
+ def label
21
+ target.label
22
+ end
23
+
24
+ def scope_suffix
25
+ target.scope_suffix
26
+ end
27
+
28
+ def static_library_name
29
+ "lib#{Utils.remove_target_scope_suffix(label, scope_suffix)}.a"
30
+ end
31
+
32
+ def pod_dir
33
+ root_name = module_basename
34
+ sandbox.sources_root + root_name
35
+ end
36
+
37
+ def build_as_library?
38
+ target.build_as_library?
39
+ end
40
+
41
+ def uses_swift?
42
+ target.uses_swift?
43
+ end
44
+
45
+ def build_as_framework?
46
+ target.build_as_framework?
47
+ end
48
+
49
+ def module_basename
50
+ Pod::Specification.root_name(pod_name)
51
+ end
52
+
53
+ def product_name
54
+ target.product_name
55
+ end
56
+
57
+ def pod_name
58
+ target.pod_name
59
+ end
60
+
61
+ def product_type
62
+ target.product_type
63
+ end
64
+
65
+ def file_accessors
66
+ target.file_accessors
67
+ end
68
+
69
+ def sandbox
70
+ target.sandbox
71
+ end
72
+
73
+ def xcframework_product_name
74
+ "#{xcframework_name}.xcframework"
75
+ end
76
+
77
+ def xcframework_name
78
+ pod_name
79
+ end
80
+
81
+ def root_spec
82
+ target.root_spec
83
+ end
84
+
85
+ def product_path
86
+ product_root.join("#{module_basename}-#{product_type}-#{version}")
87
+ end
88
+
89
+ def xcframework_product_path
90
+ product_path.join(xcframework_product_name)
91
+ end
92
+ end
93
+ end