cocoapods-project-gen 0.1.0 → 0.2.5

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