2022-06-15 11:32:31 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
2022-11-28 02:42:22 -05:00
using System.Diagnostics ;
2022-06-15 11:32:31 -04:00
using System.IO ;
2023-05-30 18:38:07 -04:00
using System.Text ;
2022-06-15 11:32:31 -04:00
using EpicGames.Core ;
using Microsoft.Extensions.Logging ;
using UnrealBuildBase ;
namespace UnrealBuildTool
{
/// <summary>
/// Base class for platform-specific project generators
/// </summary>
class AndroidProjectGenerator : PlatformProjectGenerator
{
/// <summary>
2022-08-09 15:40:11 -04:00
/// Whether Android Game Development Extension is installed in the system. See https://developer.android.com/games/agde for more details.
2022-06-15 11:32:31 -04:00
/// May be disabled by using -noagde on commandline
/// </summary>
private bool AGDEInstalled = false ;
public AndroidProjectGenerator ( CommandLineArguments Arguments , ILogger Logger )
: base ( Arguments , Logger )
{
2022-11-28 02:42:22 -05:00
AGDEInstalled = false ;
if ( OperatingSystem . IsWindows ( ) & & ! Arguments . HasOption ( "-noagde" ) )
2022-06-15 11:32:31 -04:00
{
AGDEInstalled = Microsoft . Win32 . Registry . LocalMachine . OpenSubKey ( @"SOFTWARE\WOW6432Node\Google\AndroidGameDevelopmentExtension" ) ? . ValueCount > 0 ;
2022-11-28 02:42:22 -05:00
if ( ! AGDEInstalled )
2022-06-15 11:32:31 -04:00
{
2022-11-28 02:42:22 -05:00
try
{
string? programFiles86 = Environment . GetEnvironmentVariable ( "ProgramFiles(x86)" ) ;
if ( programFiles86 ! = null )
{
string vswhereExe = Path . Join ( programFiles86 , @"Microsoft Visual Studio\Installer\vswhere.exe" ) ;
if ( File . Exists ( vswhereExe ) )
{
using ( Process p = new Process ( ) )
{
ProcessStartInfo info = new ProcessStartInfo
{
FileName = vswhereExe ,
Arguments = @"-find Common7\IDE\Extensions\*\Google.VisualStudio.Android.dll" ,
RedirectStandardOutput = true ,
UseShellExecute = false
} ;
p . StartInfo = info ;
p . Start ( ) ;
AGDEInstalled = p . StandardOutput . ReadToEnd ( ) . Contains ( "Google.VisualStudio.Android.dll" ) ;
}
}
}
}
catch ( Exception ex )
{
Logger . LogInformation ( "Failed to identify AGDE installation status: {Message}" , ex . Message ) ;
}
2022-06-15 11:32:31 -04:00
}
}
}
/// <summary>
/// Enumerate all the platforms that this generator supports
/// </summary>
public override IEnumerable < UnrealTargetPlatform > GetPlatforms ( )
{
yield return UnrealTargetPlatform . Android ;
}
2023-08-02 20:27:20 -04:00
/// <inheritdoc/>
2023-08-04 14:18:11 -04:00
public override bool HasVisualStudioSupport ( VSSettings InVSSettings )
2022-06-15 11:32:31 -04:00
{
// Debugging, etc. are dependent on the TADP being installed
2022-08-09 15:40:11 -04:00
return AGDEInstalled ;
2022-06-15 11:32:31 -04:00
}
2023-08-02 20:27:20 -04:00
/// <inheritdoc/>
2023-08-04 14:18:11 -04:00
public override string GetVisualStudioPlatformName ( VSSettings InVSSettings )
2022-06-15 11:32:31 -04:00
{
2023-08-04 14:18:11 -04:00
string PlatformName = InVSSettings . Platform . ToString ( ) ;
2022-06-15 11:32:31 -04:00
2023-08-04 14:18:11 -04:00
if ( InVSSettings . Platform = = UnrealTargetPlatform . Android & & AGDEInstalled )
2022-06-15 11:32:31 -04:00
{
2024-02-16 14:47:51 -05:00
string longAbi = GetLongAbi ( InVSSettings ) ;
PlatformName = $"Android-{longAbi}" ;
2022-06-15 11:32:31 -04:00
}
return PlatformName ;
}
2023-08-04 14:18:11 -04:00
/// <inheritdoc/>
public override void GetAdditionalVisualStudioPropertyGroups ( VSSettings InVSSettings , StringBuilder ProjectFileBuilder )
2022-06-15 11:32:31 -04:00
{
2022-08-09 15:40:11 -04:00
if ( AGDEInstalled )
2022-06-15 11:32:31 -04:00
{
2023-08-04 14:18:11 -04:00
base . GetAdditionalVisualStudioPropertyGroups ( InVSSettings , ProjectFileBuilder ) ;
2022-06-15 11:32:31 -04:00
}
}
2024-02-16 14:47:51 -05:00
private string GetShortAbi ( VSSettings InVSSettings )
{
if ( InVSSettings . Architecture = = null )
{
throw new BuildException ( "Architecture cannot be null" ) ;
}
else if ( InVSSettings . Architecture = = UnrealArch . Arm64 )
{
return "arm64" ;
}
else if ( InVSSettings . Architecture = = UnrealArch . X64 )
{
return "x64" ;
}
else
{
throw new BuildException ( $"Unexpected architecture: {InVSSettings.Architecture}" ) ;
}
}
private string GetLongAbi ( VSSettings InVSSettings )
{
if ( InVSSettings . Architecture = = null )
{
throw new BuildException ( "Architecture cannot be null" ) ;
}
else if ( InVSSettings . Architecture = = UnrealArch . Arm64 )
{
return "arm64-v8a" ;
}
else if ( InVSSettings . Architecture = = UnrealArch . X64 )
{
return "x86_64" ;
}
else
{
throw new BuildException ( $"Unexpected architecture: {InVSSettings.Architecture}" ) ;
}
}
2023-08-04 14:18:11 -04:00
/// <inheritdoc/>
public override void GetVisualStudioPathsEntries ( VSSettings InVSSettings , TargetType TargetType , FileReference TargetRulesPath , FileReference ProjectFilePath , FileReference NMakeOutputPath , StringBuilder ProjectFileBuilder )
2022-06-15 11:32:31 -04:00
{
if ( AGDEInstalled )
{
2024-02-16 14:47:51 -05:00
string shortAbi = GetShortAbi ( InVSSettings ) ;
string longAbi = GetLongAbi ( InVSSettings ) ;
2022-06-15 11:32:31 -04:00
string apkLocation = Path . Combine (
Path . GetDirectoryName ( NMakeOutputPath . FullName ) ! ,
2024-02-16 14:47:51 -05:00
Path . GetFileNameWithoutExtension ( NMakeOutputPath . FullName ) + $"-{shortAbi}.apk" ) ;
2022-06-15 11:32:31 -04:00
ProjectFileBuilder . AppendLine ( $" <AndroidApkLocation>{apkLocation}</AndroidApkLocation>" ) ;
2023-10-20 11:24:28 -04:00
string intermediateRootPath = Path . GetFullPath ( Path . GetDirectoryName ( NMakeOutputPath . FullName ) + @"\..\..\Intermediate\Android\" ) ;
List < string > symbolLocations = new List < string >
{
2024-02-16 14:47:51 -05:00
Path . Combine ( intermediateRootPath , shortAbi , "jni" , longAbi ) ,
Path . Combine ( intermediateRootPath , shortAbi , "libs" , longAbi ) ,
Path . Combine ( intermediateRootPath , "LLDBSymbolsLibs" , shortAbi ) // support bDontBundleLibrariesInAPK
2023-10-20 11:24:28 -04:00
} ;
2024-04-02 20:29:22 -04:00
ProjectFileBuilder . AppendLine ( $" <AndroidSymbolDirectories>{String.Join(" ; ", symbolLocations)}</AndroidSymbolDirectories>" ) ;
2022-06-15 11:32:31 -04:00
}
2022-08-09 15:40:11 -04:00
else
2022-06-15 11:32:31 -04:00
{
2023-08-04 14:18:11 -04:00
base . GetVisualStudioPathsEntries ( InVSSettings , TargetType , TargetRulesPath , ProjectFilePath , NMakeOutputPath , ProjectFileBuilder ) ;
2022-06-15 11:32:31 -04:00
}
}
2023-08-04 14:18:11 -04:00
public override string GetExtraBuildArguments ( VSSettings InVSSettings )
2022-06-15 11:32:31 -04:00
{
2024-02-16 14:47:51 -05:00
if ( AGDEInstalled )
{
// do not need to check InPlatform since it will always be UnrealTargetPlatform.Android
return $" -ForceAPKGeneration" + base . GetExtraBuildArguments ( InVSSettings ) ;
}
else
{
return base . GetExtraBuildArguments ( InVSSettings ) ;
}
2022-06-15 11:32:31 -04:00
}
2024-01-08 07:55:34 -05:00
public override string GetVisualStudioUserFileStrings ( VisualStudioUserFileSettings VCUserFileSettings , VSSettings InVSSettings , string InConditionString , TargetRules InTargetRules , FileReference TargetRulesPath , FileReference ProjectFilePath , FileReference ? NMakeOutputPath , string ProjectName , string? ForeignUProjectPath )
2022-06-15 11:32:31 -04:00
{
2023-05-30 18:38:07 -04:00
if ( AGDEInstalled
2023-08-04 14:18:11 -04:00
& & ( InVSSettings . Platform = = UnrealTargetPlatform . Android )
2022-06-15 11:32:31 -04:00
& & ( ( InTargetRules . Type = = TargetRules . TargetType . Client ) | | ( InTargetRules . Type = = TargetRules . TargetType . Game ) ) )
{
2024-01-08 07:55:34 -05:00
StringBuilder Out = new StringBuilder ( ) ;
Out . AppendLine ( " <PropertyGroup " + InConditionString + ">" ) ;
string LldbFormatterImport = $"command script import \"" + Path.Combine(Unreal.EngineDirectory.FullName, " Extras ", " LLDBDataFormatters ", " UEDataFormatters_2ByteChars . py ") + " \ "" ;
Out . AppendLine ( $" <AndroidLldbStartupCommands>{LldbFormatterImport};$(AndroidLldbStartupCommands)</AndroidLldbStartupCommands>" ) ;
VCUserFileSettings . PatchProperty ( "AndroidLldbStartupCommands" ) ;
if ( NMakeOutputPath ! = null )
{
// It's critical to have AndroidDebugTarget here and for it to be before properties that use it (e.g. AndroidPostApkInstallCommands), otherwise MSBuild will evaluate it to empty string.
Out . AppendLine ( " <AndroidDebugTarget></AndroidDebugTarget>" ) ;
// At this stage we don't know if bDontBundleLibrariesInAPK is enabled or not, so make a fail-safe check.
2024-02-16 14:47:51 -05:00
string shortAbi = GetShortAbi ( InVSSettings ) ;
2024-01-08 07:55:34 -05:00
string PushSOScript = Path . Combine (
Path . GetDirectoryName ( NMakeOutputPath . FullName ) ! ,
2024-02-16 14:47:51 -05:00
"Push_" + Path . GetFileNameWithoutExtension ( NMakeOutputPath . FullName ) + $"-{shortAbi}_so.bat" ) ;
2024-01-08 07:55:34 -05:00
// AGDE specifies current debug target in AndroidDebugTarget property in a form of "model:serial:arch".
// AndroidDebugTarget is a special property and needs to be evaluated in-line. And the push script needs the device serial as first argument to push to the correct device.
// MSBuild Property Functions allow to invoke limited C# expression from with-in MSBuild, see https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions?view=vs-2022
// They lack conditional statements, so this expression makes a branch-less variant by always appending "::" to AndroidDebugTarget,
// so regardless of what value it has, Split(':')[1] will never throw an exception.
string GetTargetDeviceSerial = "$([System.String]::Concat($(AndroidDebugTarget), \"::\").Split(':')[1])" ;
Out . AppendLine (
$" <AndroidPostApkInstallCommands>IF EXIST {PushSOScript} {PushSOScript} {GetTargetDeviceSerial};$(AndroidPostApkInstallCommands)</AndroidPostApkInstallCommands>" ) ;
// Ensure the following properties are up to date even if .vcxproj.user already exists and are in correct order between each other.
VCUserFileSettings . PatchProperty ( "AndroidDebugTarget" , true ) ;
VCUserFileSettings . PatchProperty ( "AndroidPostApkInstallCommands" ) ;
}
Out . AppendLine ( " </PropertyGroup>" ) ;
return Out . ToString ( ) ;
2022-06-15 11:32:31 -04:00
}
2024-04-02 20:29:22 -04:00
return String . Empty ;
2022-06-15 11:32:31 -04:00
}
}
}