Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions Builder/Beyond.NET.Builder.Android/AndroidBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using Beyond.NET.Core;

namespace Beyond.NET.Builder.Android;

public class AndroidBuilder
{
public record BuildResult(
string? AndroidARM64LibraryPath,
string OutputDirectoryPath
);

public string ProductName { get; }
public string OutputDirectory { get; }
public bool BuildAndroidARM64 { get; }

private ILogger Logger => Services.Shared.LoggerService;

private string OutputProductName => ProductName;
private string OutputLibraryFileName => $"lib{OutputProductName}.so";

public AndroidBuilder(
string productName,
string outputDirectory,
bool buildAndroidARM64
)
{
ProductName = productName;
OutputDirectory = outputDirectory;
BuildAndroidARM64 = buildAndroidARM64;
}

public BuildResult Build(
string? androidARM64BuildPath
)
{
Logger.LogInformation("Building Android libraries");

string? androidARM64LibraryPath = null;

if (androidARM64BuildPath is not null && BuildAndroidARM64)
{
androidARM64LibraryPath = Path.Combine(androidARM64BuildPath, OutputLibraryFileName);

if (!File.Exists(androidARM64LibraryPath))
{
var libraryWithoutLibName = OutputLibraryFileName.Replace("lib", "");
androidARM64LibraryPath = Path.Combine(androidARM64BuildPath, libraryWithoutLibName);
}

if (!File.Exists(androidARM64LibraryPath))
{
throw new FileNotFoundException($"Android library not found at {androidARM64BuildPath}");
}

Logger.LogInformation($"Android library found at: {androidARM64LibraryPath}");
}

// Create output directory structure for Android
string androidOutputPath = Path.Combine(OutputDirectory, "android");
Directory.CreateDirectory(androidOutputPath);

if (androidARM64LibraryPath is not null)
{
string arm64OutputDir = Path.Combine(androidOutputPath, "arm64-v8a");
Directory.CreateDirectory(arm64OutputDir);
string destPath = Path.Combine(arm64OutputDir, OutputLibraryFileName);
File.Copy(androidARM64LibraryPath, destPath, true);
Logger.LogInformation($"Copied library to: {destPath}");
}

if (androidARM64BuildPath is not null && BuildAndroidARM64)
{
string debugSymbolsOutputPath = Path.Combine(androidOutputPath, "android-debug-symbols");
Directory.CreateDirectory(debugSymbolsOutputPath);

Logger.LogInformation($"Copying all files from {androidARM64BuildPath} to {debugSymbolsOutputPath}");
CopyAllFiles(androidARM64BuildPath, debugSymbolsOutputPath);

Logger.LogInformation($"Copied debug symbols to: {debugSymbolsOutputPath}");
}

Logger.LogInformation($"Android build completed. Output directory: {androidOutputPath}");

return new BuildResult(
androidARM64LibraryPath,
androidOutputPath
);
}

private void CopyAllFiles(string sourceDirectory, string destinationDirectory)
{
if (!Directory.Exists(sourceDirectory))
{
throw new DirectoryNotFoundException($"Source directory not found: {sourceDirectory}");
}

Directory.CreateDirectory(destinationDirectory);

foreach (string filePath in Directory.GetFiles(sourceDirectory))
{
string fileName = Path.GetFileName(filePath);
string destFilePath = Path.Combine(destinationDirectory, fileName);

File.Copy(filePath, destFilePath, overwrite: true);
Logger.LogDebug($"Copied: {fileName}");
}

foreach (string dirPath in Directory.GetDirectories(sourceDirectory))
{
string dirName = Path.GetFileName(dirPath);
string destDirPath = Path.Combine(destinationDirectory, dirName);

CopyAllFiles(dirPath, destDirPath);
}
}
}

56 changes: 56 additions & 0 deletions Builder/Beyond.NET.Builder.Android/AndroidPublish.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Beyond.NET.Core;

namespace Beyond.NET.Builder.Android;

public static class AndroidPublish
{
private const string BUILD_SCRIPT_NAME = "build_android.sh";

public static string Run(
string workingDirectory,
string runtimeIdentifier,
string configuration,
string? verbosityLevel
)
{
// Get the path to the build script
string scriptDirectory = Path.GetDirectoryName(typeof(AndroidPublish).Assembly.Location)!;
string scriptPath = Path.Combine(scriptDirectory, BUILD_SCRIPT_NAME);

if (!File.Exists(scriptPath))
{
throw new FileNotFoundException($"Android build script not found at: {scriptPath}");
}

// Build arguments for the script
List<string> args = new()
{
scriptPath,
workingDirectory,
runtimeIdentifier,
configuration
};

if (!string.IsNullOrEmpty(verbosityLevel))
{
args.Add(verbosityLevel);
}

// Execute the build script using bash
var bashApp = new CLIApp("/bin/bash");
var result = bashApp.Launch(
args.ToArray(),
workingDirectory
);

Exception? failure = result.FailureAsException;

if (failure is not null)
{
throw failure;
}

return result.StandardOut ?? string.Empty;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Core\Beyond.NET.Core\Beyond.NET.Core.csproj" />
<ProjectReference Include="..\Beyond.NET.Builder.DotNET\Beyond.NET.Builder.DotNET.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="build_android.sh" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

</Project>

89 changes: 89 additions & 0 deletions Builder/Beyond.NET.Builder.Android/build_android.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bash

# Script to build .NET NativeAOT for Android
# Usage: build_android.sh <working_directory> <runtime_identifier> <configuration> <verbosity>

set -e

# Parse arguments
WORKING_DIR="$1"
RUNTIME_IDENTIFIER="${2:-linux-bionic-arm64}"
CONFIGURATION="${3:-Release}"
VERBOSITY="${4:-normal}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

echo "Script is located at: $SCRIPT_DIR"

if [ -z "$WORKING_DIR" ]; then
echo "Error: Working directory is required"
echo "Usage: $0 <working_directory> [runtime_identifier] [configuration] [verbosity]"
exit 1
fi

if [ ! -d "$WORKING_DIR" ]; then
echo "Error: Working directory does not exist: $WORKING_DIR"
exit 1
fi

echo "Building .NET NativeAOT for Android"
echo " Working Directory: $WORKING_DIR"
echo " Runtime Identifier: $RUNTIME_IDENTIFIER"
echo " Configuration: $CONFIGURATION"
echo " Verbosity: $VERBOSITY"
echo ""

# Either set the "ANDROID_NDK_BIN_PATH" environment variable and point it to the following Android NDK installation directory's subpath "ndk_home/toolchains/llvm/prebuilt/platform-arch/bin"
# Or alternatively, make sure the "ANDROID_NDK_HOME" enviroment variable is set and pointing to your Android NDK installation. We'll then try to automatically detect where the bin path is.
if [ ! -d "${ANDROID_NDK_BIN_PATH}" ] ; then
echo "Warning: ANDROID_NDK_BIN_PATH enviroment variable is not set or directory does not exist. Trying to fall back to ANDROID_NDK_HOME and automatically detecting the bin directory."

if [ ! -d "${ANDROID_NDK_HOME}" ] ; then
echo "Error: ANDROID_NDK_HOME enviroment variable is not set or directory does not exist."
exit 1
fi

echo "Android NDK Home: ${ANDROID_NDK_HOME}"

# ndk_bin_common.sh sets the "HOST_TAG" environment variable to the NDK's toolchain platform
source "${ANDROID_NDK_HOME}/build/tools/ndk_bin_common.sh"
echo "Android Host Tag: ${HOST_TAG}"

ANDROID_NDK_BIN_PATH="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${HOST_TAG}/bin"

if [ ! -d "${ANDROID_NDK_BIN_PATH}" ] ; then
echo "Error: Android NDK bin Path not found at ${ANDROID_NDK_BIN_PATH}"
exit 1
fi
fi

echo "Android NDK bin Path: ${ANDROID_NDK_BIN_PATH}"

export PATH=${ANDROID_NDK_BIN_PATH}:$PATH

echo ""

# Change to working directory
cd "$WORKING_DIR"

# Run dotnet publish with Android-specific settings
echo "Running: dotnet publish -r $RUNTIME_IDENTIFIER -c $CONFIGURATION -v $VERBOSITY -p:PublishAotUsingRuntimePack=true"
echo ""

dotnet publish \
-r "$RUNTIME_IDENTIFIER" \
-c "$CONFIGURATION" \
-v "$VERBOSITY" \
-p:PublishAotUsingRuntimePack=true

EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
echo ""
echo "Android build completed successfully"
else
echo ""
echo "Android build failed with exit code $EXIT_CODE"
exit $EXIT_CODE
fi

exit 0
2 changes: 2 additions & 0 deletions Builder/Beyond.NET.Builder.DotNET/PlatformIdentifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public static class PlatformIdentifier
/// Made up, not part of .NET!
/// </summary>
public const string Apple = "apple";

public const string Android = "linux-bionic";
}
2 changes: 2 additions & 0 deletions Builder/Beyond.NET.Builder.DotNET/RuntimeIdentifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ public static class RuntimeIdentifier
/// Made up, not part of .NET!
/// </summary>
public const string APPLE_UNIVERSAL = $"{PlatformIdentifier.Apple}-{TargetIdentifier.UNIVERSAL}";

public const string Android_ARM64 = $"{PlatformIdentifier.Android}-{TargetIdentifier.ARM64}";
}
1 change: 1 addition & 0 deletions Builder/Beyond.NET.Builder/Beyond.NET.Builder.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<ItemGroup>
<ProjectReference Include="..\..\Core\Beyond.NET.Core\Beyond.NET.Core.csproj" />
<ProjectReference Include="..\Beyond.NET.Builder.Android\Beyond.NET.Builder.Android.csproj" />
<ProjectReference Include="..\Beyond.NET.Builder.Apple\Beyond.NET.Builder.Apple.csproj" />
<ProjectReference Include="..\Beyond.NET.Builder.DotNET\Beyond.NET.Builder.DotNET.csproj" />
</ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions Builder/Beyond.NET.Builder/BuildTargets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public enum BuildTargets
iOSARM64 = 1 << 2,
iOSSimulatorARM64 = 1 << 3,
iOSSimulatorX64 = 1 << 4,
AndroidARM64 = 1 << 5,

MacOSUniversal = MacOSARM64 | MacOSX64,
iOSSimulatorUniversal = iOSSimulatorARM64 | iOSSimulatorX64,
Expand Down Expand Up @@ -38,4 +39,9 @@ public static bool ContainsAnyMacOSTarget(this BuildTargets buildTargets)
return buildTargets.HasFlag(BuildTargets.MacOSARM64) ||
buildTargets.HasFlag(BuildTargets.MacOSX64);
}

public static bool ContainsAnyAndroidTarget(this BuildTargets buildTargets)
{
return buildTargets.HasFlag(BuildTargets.AndroidARM64);
}
}
Loading