375 lines
12 KiB
GDScript
375 lines
12 KiB
GDScript
#
|
|
# © 2024-present https://github.com/cengiz-pz
|
|
#
|
|
|
|
@tool
|
|
extends EditorPlugin
|
|
|
|
const PLUGIN_NAME: String = "AdmobPlugin"
|
|
const ANDROID_DEPENDENCIES: Array = [ "androidx.appcompat:appcompat:1.7.1", "androidx.lifecycle:lifecycle-process:2.8.3", "com.google.android.gms:play-services-ads:24.9.0" ]
|
|
const IOS_PLATFORM_VERSION: String = "14.3"
|
|
const IOS_FRAMEWORKS: Array = [ "Foundation.framework", "AppTrackingTransparency.framework" ]
|
|
const IOS_EMBEDDED_FRAMEWORKS: Array = [ ]
|
|
const IOS_LINKER_FLAGS: Array = [ "-ObjC" ]
|
|
const IOS_BUNDLE_FILES: Array = [ ]
|
|
const SPM_DEPENDENCIES: Array = [ {&"url": "https://github.com/googleads/swift-package-manager-google-mobile-ads.git", &"version": "12.14.0", &"products": ["GoogleMobileAds"]} ]
|
|
|
|
var android_export_plugin: AndroidExportPlugin
|
|
var ios_export_plugin: IosExportPlugin
|
|
|
|
|
|
func _enter_tree() -> void:
|
|
android_export_plugin = AndroidExportPlugin.new()
|
|
add_export_plugin(android_export_plugin)
|
|
ios_export_plugin = IosExportPlugin.new()
|
|
add_export_plugin(ios_export_plugin)
|
|
|
|
|
|
func _exit_tree() -> void:
|
|
remove_export_plugin(android_export_plugin)
|
|
android_export_plugin = null
|
|
remove_export_plugin(ios_export_plugin)
|
|
ios_export_plugin = null
|
|
|
|
|
|
class AndroidExportPlugin extends EditorExportPlugin:
|
|
const APP_ID_META_TAG = """
|
|
<meta-data
|
|
tools:replace="android:value"
|
|
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
|
android:value="%s"/>
|
|
"""
|
|
|
|
var _plugin_name = PLUGIN_NAME
|
|
var _export_config: AdmobAndroidExportConfig
|
|
|
|
|
|
func _supports_platform(platform: EditorExportPlatform) -> bool:
|
|
return platform is EditorExportPlatformAndroid
|
|
|
|
|
|
func _get_android_libraries(platform: EditorExportPlatform, debug: bool) -> PackedStringArray:
|
|
if debug:
|
|
return PackedStringArray(["%s/bin/debug/%s-debug.aar" % [_plugin_name, _plugin_name]])
|
|
else:
|
|
return PackedStringArray(["%s/bin/release/%s-release.aar" % [_plugin_name, _plugin_name]])
|
|
|
|
|
|
func _get_name() -> String:
|
|
return _plugin_name
|
|
|
|
|
|
func _export_begin(features: PackedStringArray, is_debug: bool, path: String, flags: int) -> void:
|
|
if _supports_platform(get_export_platform()):
|
|
_export_config = AdmobAndroidExportConfig.new()
|
|
if not _export_config.export_config_file_exists() or _export_config.load_export_config_from_file() != OK:
|
|
_export_config.load_export_config_from_node()
|
|
|
|
|
|
func _get_android_dependencies(platform: EditorExportPlatform, debug: bool) -> PackedStringArray:
|
|
var deps: PackedStringArray = PackedStringArray(ANDROID_DEPENDENCIES)
|
|
if _export_config and _export_config.enabled_mediation_networks.size() > 0:
|
|
for __network in _export_config.enabled_mediation_networks:
|
|
for __dependency in __network.android_dependencies:
|
|
deps.append(__dependency)
|
|
|
|
GmpLogger.log_info("Android dependencies: %s" % str(deps))
|
|
|
|
return deps
|
|
|
|
|
|
func _get_android_dependencies_maven_repos(platform: EditorExportPlatform, debug: bool) -> PackedStringArray:
|
|
var __custom_repos: PackedStringArray = []
|
|
|
|
if _export_config and _export_config.enabled_mediation_networks.size() > 0:
|
|
for network in _export_config.enabled_mediation_networks:
|
|
if network.android_custom_maven_repo and not network.android_custom_maven_repo.is_empty():
|
|
__custom_repos.append(network.android_custom_maven_repo)
|
|
GmpLogger.log_info(
|
|
"Added custom Maven repo for %s mediation: %s" %
|
|
[network.tag, network.android_custom_maven_repo],
|
|
)
|
|
|
|
return __custom_repos
|
|
|
|
|
|
func _get_android_manifest_application_element_contents(platform: EditorExportPlatform, debug: bool) -> String:
|
|
var __contents: String
|
|
|
|
if _export_config:
|
|
__contents = APP_ID_META_TAG % (_export_config.real_application_id if _export_config.is_real \
|
|
else _export_config.debug_application_id )
|
|
else:
|
|
GmpLogger.log_warn("Export config not found for %s!" % _plugin_name)
|
|
__contents = ""
|
|
|
|
return __contents
|
|
|
|
|
|
class IosExportPlugin extends EditorExportPlugin:
|
|
const NS_APP_TRANSPORT_SECURITY: String = """
|
|
<key>NSAppTransportSecurity</key>
|
|
<dict>
|
|
<key>NSAllowsArbitraryLoads</key>
|
|
<true/>
|
|
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
|
<true/>
|
|
</dict>
|
|
"""
|
|
|
|
var _plugin_name = PLUGIN_NAME
|
|
var _spm_dependencies = []
|
|
var _export_config: AdmobIosExportConfig
|
|
var _export_path: String
|
|
|
|
|
|
func _supports_platform(platform: EditorExportPlatform) -> bool:
|
|
return platform is EditorExportPlatformIOS
|
|
|
|
|
|
func _get_name() -> String:
|
|
return _plugin_name
|
|
|
|
|
|
func _export_begin(features: PackedStringArray, is_debug: bool, path: String, flags: int) -> void:
|
|
if _supports_platform(get_export_platform()):
|
|
_export_path = path.simplify_path()
|
|
_export_config = AdmobIosExportConfig.new()
|
|
if not _export_config.export_config_file_exists() or _export_config.load_export_config_from_file() != OK:
|
|
_export_config.load_export_config_from_node()
|
|
|
|
add_apple_embedded_platform_plist_content("<key>GADApplicationIdentifier</key>")
|
|
add_apple_embedded_platform_plist_content(
|
|
"\t<string>%s</string>" % (_export_config.real_application_id
|
|
if _export_config.is_real else _export_config.debug_application_id ),
|
|
)
|
|
|
|
if _export_config.att_enabled and _export_config.att_text and not _export_config.att_text.is_empty():
|
|
add_apple_embedded_platform_plist_content("<key>NSUserTrackingUsageDescription</key>")
|
|
add_apple_embedded_platform_plist_content("<string>%s</string>" % _export_config.att_text)
|
|
|
|
add_apple_embedded_platform_plist_content(
|
|
MediationNetwork.generate_sk_ad_network_plist(
|
|
_export_config.enabled_mediation_networks,
|
|
),
|
|
)
|
|
|
|
add_apple_embedded_platform_plist_content(NS_APP_TRANSPORT_SECURITY)
|
|
|
|
for __framework in IOS_FRAMEWORKS:
|
|
add_apple_embedded_platform_framework(__framework)
|
|
|
|
for __framework in IOS_EMBEDDED_FRAMEWORKS:
|
|
add_apple_embedded_platform_embedded_framework(__framework)
|
|
|
|
for __flag in IOS_LINKER_FLAGS:
|
|
add_apple_embedded_platform_linker_flags(__flag)
|
|
|
|
for __bundle_file in IOS_BUNDLE_FILES:
|
|
add_apple_embedded_platform_bundle_file(__bundle_file)
|
|
|
|
for __spm_dep in SPM_DEPENDENCIES:
|
|
_spm_dependencies.append(SpmDependency.new(__spm_dep))
|
|
|
|
|
|
func _end_generate_apple_embedded_project(path: String, will_build_archive: bool) -> void:
|
|
GmpLogger.log_info("Apple export project generated at: %s. Will build archive: %s"
|
|
% [path, str(will_build_archive)])
|
|
|
|
if _supports_platform(get_export_platform()):
|
|
_spm_dependencies.append_array(_get_extra_dependencies())
|
|
|
|
if _spm_dependencies.is_empty():
|
|
GmpLogger.log_info("No SPM dependencies to install. Skipping.")
|
|
else:
|
|
GmpLogger.log_info("Installing %d SPM dependencies." % _spm_dependencies.size())
|
|
_install_dependencies(path.get_base_dir(), path.get_file().get_basename())
|
|
|
|
|
|
func _get_extra_dependencies() -> Array[SpmDependency]:
|
|
var __extra_dependencies:= [] as Array[SpmDependency]
|
|
|
|
__extra_dependencies.append_array(
|
|
MediationNetwork.get_spm_dependencies(_export_config.enabled_mediation_networks))
|
|
|
|
return __extra_dependencies
|
|
|
|
|
|
func _install_dependencies(a_base_dir: String, a_project_name: String) -> void:
|
|
var __project_file_name:= "%s.xcodeproj" % a_project_name
|
|
var __project_file_path:= a_base_dir.path_join(__project_file_name)
|
|
if not DirAccess.dir_exists_absolute(__project_file_path):
|
|
GmpLogger.log_error("Xcode project '%s' does not exist! Can't install SPM dependencies."
|
|
% __project_file_path)
|
|
return
|
|
|
|
var __script_name = "add_dependency.rb"
|
|
var __add_dependency_script_path = a_base_dir.path_join(__script_name)
|
|
var __result = _generate_add_dependency_script(__add_dependency_script_path)
|
|
if __result != Error.OK:
|
|
GmpLogger.log_error("Failed to generate '%s' script with error %d!" % [__script_name, __result])
|
|
return
|
|
|
|
GmpLogger.log_info("Adding SPM dependencies to %s..." % __project_file_path)
|
|
|
|
for __spm_dep: SpmDependency in _spm_dependencies:
|
|
for __spm_dep_product: String in __spm_dep.get_products():
|
|
var exec_output: Array = []
|
|
var exec_code = OS.execute("ruby", [
|
|
__add_dependency_script_path,
|
|
__project_file_path,
|
|
__spm_dep.get_url(),
|
|
__spm_dep.get_version(),
|
|
__spm_dep_product,
|
|
], exec_output, true, false)
|
|
|
|
if exec_code == 0:
|
|
GmpLogger.log_info("Product %s for SPM dependency %s added successfully!"
|
|
% [__spm_dep_product, __spm_dep.format_to_string()])
|
|
for line in exec_output:
|
|
GmpLogger.log_info("SPM: %s" % line)
|
|
else:
|
|
GmpLogger.log_info("Failed to add product %s for SPM dependency %s !"
|
|
% [__spm_dep_product, __spm_dep.format_to_string()])
|
|
for line in exec_output:
|
|
GmpLogger.log_error("SPM: %s" % line)
|
|
|
|
GmpLogger.log_info("Resolving SPM dependencies...")
|
|
|
|
__script_name = "resolve_dependencies.sh"
|
|
var __resolve_dependencies_script_path = a_base_dir.path_join(__script_name)
|
|
__result = _generate_resolve_dependencies_script(__resolve_dependencies_script_path, a_base_dir, a_project_name)
|
|
if __result != Error.OK:
|
|
GmpLogger.log_error("Failed to generate '%s' script with error %d!" % [__script_name, __result])
|
|
return
|
|
|
|
var exec_output: Array = []
|
|
var exec_code = OS.execute(__resolve_dependencies_script_path, [], exec_output, true, false)
|
|
|
|
if exec_code == 0:
|
|
for line in exec_output:
|
|
GmpLogger.log_info("SPM: %s" % line)
|
|
GmpLogger.log_info("Resolved dependencies successfully!")
|
|
else:
|
|
for line in exec_output:
|
|
GmpLogger.log_error("SPM: %s" % line)
|
|
GmpLogger.log_info("Failed to resolve dependencies! Try manually in Xcode.")
|
|
|
|
|
|
const ADD_DEPENDENCY_RUBY_SCRIPT = """
|
|
require 'xcodeproj'
|
|
|
|
project_path = ARGV[0]
|
|
url = ARGV[1].strip
|
|
version = ARGV[2].strip
|
|
product_name = ARGV[3].strip
|
|
|
|
unless File.exist?(project_path)
|
|
puts "Error: Xcode project not found at #{project_path}"
|
|
exit 1
|
|
end
|
|
|
|
if url.empty? || version.empty? || product_name.empty?
|
|
puts "Error: url, version, and product_name must all be non-empty."
|
|
exit 1
|
|
end
|
|
|
|
begin
|
|
project = Xcodeproj::Project.open(project_path)
|
|
target = project.targets.first
|
|
|
|
if target.nil?
|
|
puts "Error: No targets found in the Xcode project."
|
|
exit 1
|
|
end
|
|
|
|
existing_dep = target.package_product_dependencies.find do |dep|
|
|
dep.product_name == product_name
|
|
end
|
|
|
|
if existing_dep
|
|
puts "Warning: Product dependency '#{product_name}' already exists in the project. Skipping add.\n\n"
|
|
else
|
|
# Reuse an existing package reference for the same URL, or create a new one
|
|
pkg = project.root_object.package_references.find do |p|
|
|
p.repositoryURL == url
|
|
end
|
|
|
|
if pkg
|
|
puts "Reusing existing package reference for '#{url}'."
|
|
else
|
|
pkg = project.new(Xcodeproj::Project::Object::XCRemoteSwiftPackageReference)
|
|
pkg.repositoryURL = url
|
|
pkg.requirement = {
|
|
'kind' => 'upToNextMajorVersion',
|
|
'minimumVersion' => version
|
|
}
|
|
project.root_object.package_references << pkg
|
|
end
|
|
|
|
# Create the product dependency and link it to the shared package reference
|
|
ref = project.new(Xcodeproj::Project::Object::XCSwiftPackageProductDependency)
|
|
ref.product_name = product_name
|
|
ref.package = pkg
|
|
target.package_product_dependencies << ref
|
|
|
|
puts "Successfully added SPM dependency '#{product_name}' " \
|
|
"(#{url} @ #{version}) to #{File.basename(project_path)}\n\n"
|
|
end
|
|
|
|
project.save
|
|
|
|
rescue => e
|
|
puts "An error occurred: #{e.message}\n\n"
|
|
exit 1
|
|
end
|
|
"""
|
|
func _generate_add_dependency_script(a_script_path: String) -> Error:
|
|
var __result = Error.OK
|
|
|
|
var __script_content = ADD_DEPENDENCY_RUBY_SCRIPT
|
|
|
|
__result = _create_script(a_script_path, __script_content)
|
|
|
|
return __result
|
|
|
|
|
|
const RESOLVE_DEPENDENCIES_BASH_SCRIPT = """
|
|
#!/bin/bash
|
|
set -e # Exit on error
|
|
|
|
xcodebuild -resolvePackageDependencies \
|
|
-project "%s.xcodeproj" \
|
|
-scheme "%s"
|
|
"""
|
|
func _generate_resolve_dependencies_script(a_script_path: String, a_base_dir: String,
|
|
a_project_name: String) -> Error:
|
|
var __result: Error = Error.OK
|
|
|
|
var __script_content = RESOLVE_DEPENDENCIES_BASH_SCRIPT \
|
|
% [ ProjectSettings.globalize_path(a_base_dir.path_join(a_project_name)), a_project_name ]
|
|
|
|
__result = _create_script(a_script_path, __script_content)
|
|
|
|
return __result
|
|
|
|
|
|
func _create_script(a_script_path: String, a_script_content: String) -> Error:
|
|
var __result: Error = Error.OK
|
|
|
|
var __script_file = FileAccess.open(a_script_path, FileAccess.WRITE)
|
|
if __script_file:
|
|
__script_file.store_string(a_script_content)
|
|
__script_file.close()
|
|
else:
|
|
__result = Error.ERR_FILE_CANT_WRITE
|
|
|
|
var chmod_output: Array = []
|
|
var chmod_code = OS.execute("chmod", ["+x", a_script_path], chmod_output, true, false)
|
|
if chmod_code != 0:
|
|
GmpLogger.log_error("Failed to chmod %s script: %s"
|
|
% [a_script_path, (chmod_output if chmod_output.size() > 0 else "Unknown error")])
|
|
__result = Error.ERR_FILE_NO_PERMISSION
|
|
|
|
return __result
|