Skip to content

Commit 49dda07

Browse files
committed
adding android build support
1 parent 30544b6 commit 49dda07

File tree

15 files changed

+704
-65
lines changed

15 files changed

+704
-65
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using Beyond.NET.Core;
2+
3+
namespace Beyond.NET.Builder.Android;
4+
5+
public class AndroidBuilder
6+
{
7+
public record BuildResult(
8+
string? AndroidARM64LibraryPath,
9+
string OutputDirectoryPath
10+
);
11+
12+
public string ProductName { get; }
13+
public string OutputDirectory { get; }
14+
public bool BuildAndroidARM64 { get; }
15+
16+
private ILogger Logger => Services.Shared.LoggerService;
17+
18+
private string OutputProductName => ProductName;
19+
private string OutputLibraryFileName => $"lib{OutputProductName}.so";
20+
21+
public AndroidBuilder(
22+
string productName,
23+
string outputDirectory,
24+
bool buildAndroidARM64
25+
)
26+
{
27+
ProductName = productName;
28+
OutputDirectory = outputDirectory;
29+
BuildAndroidARM64 = buildAndroidARM64;
30+
}
31+
32+
public BuildResult Build(
33+
string? androidARM64BuildPath
34+
)
35+
{
36+
Logger.LogInformation("Building Android libraries");
37+
38+
string? androidARM64LibraryPath = null;
39+
40+
if (androidARM64BuildPath is not null && BuildAndroidARM64)
41+
{
42+
androidARM64LibraryPath = Path.Combine(androidARM64BuildPath, OutputLibraryFileName);
43+
44+
if (!File.Exists(androidARM64LibraryPath))
45+
{
46+
var libraryWithoutLibName = OutputLibraryFileName.Replace("lib", "");
47+
androidARM64LibraryPath = Path.Combine(androidARM64BuildPath, libraryWithoutLibName);
48+
}
49+
50+
if (!File.Exists(androidARM64LibraryPath))
51+
{
52+
throw new FileNotFoundException($"Android library not found at {androidARM64BuildPath}");
53+
}
54+
55+
Logger.LogInformation($"Android library found at: {androidARM64LibraryPath}");
56+
}
57+
58+
// Create output directory structure for Android
59+
string androidOutputPath = Path.Combine(OutputDirectory, "android");
60+
Directory.CreateDirectory(androidOutputPath);
61+
62+
if (androidARM64LibraryPath is not null)
63+
{
64+
string arm64OutputDir = Path.Combine(androidOutputPath, "arm64-v8a");
65+
Directory.CreateDirectory(arm64OutputDir);
66+
string destPath = Path.Combine(arm64OutputDir, OutputLibraryFileName);
67+
File.Copy(androidARM64LibraryPath, destPath, true);
68+
Logger.LogInformation($"Copied library to: {destPath}");
69+
}
70+
71+
if (androidARM64BuildPath is not null && BuildAndroidARM64)
72+
{
73+
string debugSymbolsOutputPath = Path.Combine(androidOutputPath, "android-debug-symbols");
74+
Directory.CreateDirectory(debugSymbolsOutputPath);
75+
76+
Logger.LogInformation($"Copying all files from {androidARM64BuildPath} to {debugSymbolsOutputPath}");
77+
CopyAllFiles(androidARM64BuildPath, debugSymbolsOutputPath);
78+
79+
Logger.LogInformation($"Copied debug symbols to: {debugSymbolsOutputPath}");
80+
}
81+
82+
Logger.LogInformation($"Android build completed. Output directory: {androidOutputPath}");
83+
84+
return new BuildResult(
85+
androidARM64LibraryPath,
86+
androidOutputPath
87+
);
88+
}
89+
90+
private void CopyAllFiles(string sourceDirectory, string destinationDirectory)
91+
{
92+
if (!Directory.Exists(sourceDirectory))
93+
{
94+
throw new DirectoryNotFoundException($"Source directory not found: {sourceDirectory}");
95+
}
96+
97+
Directory.CreateDirectory(destinationDirectory);
98+
99+
foreach (string filePath in Directory.GetFiles(sourceDirectory))
100+
{
101+
string fileName = Path.GetFileName(filePath);
102+
string destFilePath = Path.Combine(destinationDirectory, fileName);
103+
104+
File.Copy(filePath, destFilePath, overwrite: true);
105+
Logger.LogDebug($"Copied: {fileName}");
106+
}
107+
108+
foreach (string dirPath in Directory.GetDirectories(sourceDirectory))
109+
{
110+
string dirName = Path.GetFileName(dirPath);
111+
string destDirPath = Path.Combine(destinationDirectory, dirName);
112+
113+
CopyAllFiles(dirPath, destDirPath);
114+
}
115+
}
116+
}
117+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using Beyond.NET.Core;
2+
3+
namespace Beyond.NET.Builder.Android;
4+
5+
public static class AndroidPublish
6+
{
7+
private const string BUILD_SCRIPT_NAME = "build_android.sh";
8+
9+
public static string Run(
10+
string workingDirectory,
11+
string runtimeIdentifier,
12+
string configuration,
13+
string? verbosityLevel
14+
)
15+
{
16+
// Get the path to the build script
17+
string scriptDirectory = Path.GetDirectoryName(typeof(AndroidPublish).Assembly.Location)!;
18+
string scriptPath = Path.Combine(scriptDirectory, BUILD_SCRIPT_NAME);
19+
20+
if (!File.Exists(scriptPath))
21+
{
22+
throw new FileNotFoundException($"Android build script not found at: {scriptPath}");
23+
}
24+
25+
// Make sure script is executable
26+
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
27+
{
28+
try
29+
{
30+
var chmodApp = new CLIApp("chmod");
31+
var chmodResult = chmodApp.Launch(
32+
new[] { "+x", scriptPath },
33+
workingDirectory
34+
);
35+
}
36+
catch
37+
{
38+
// Ignore chmod errors - script might already be executable
39+
}
40+
}
41+
42+
// Build arguments for the script
43+
List<string> args = new()
44+
{
45+
scriptPath,
46+
workingDirectory,
47+
runtimeIdentifier,
48+
configuration
49+
};
50+
51+
if (!string.IsNullOrEmpty(verbosityLevel))
52+
{
53+
args.Add(verbosityLevel);
54+
}
55+
56+
// Execute the build script using bash
57+
var bashApp = new CLIApp("/bin/bash");
58+
var result = bashApp.Launch(
59+
args.ToArray(),
60+
workingDirectory
61+
);
62+
63+
Exception? failure = result.FailureAsException;
64+
65+
if (failure is not null)
66+
{
67+
throw failure;
68+
}
69+
70+
return result.StandardOut ?? string.Empty;
71+
}
72+
}
73+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\..\Core\Beyond.NET.Core\Beyond.NET.Core.csproj" />
11+
<ProjectReference Include="..\Beyond.NET.Builder.DotNET\Beyond.NET.Builder.DotNET.csproj" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<None Include="build_android.sh" CopyToOutputDirectory="PreserveNewest" />
16+
</ItemGroup>
17+
18+
</Project>
19+
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env bash
2+
3+
# Script to build .NET NativeAOT for Android
4+
# Usage: build_android.sh <working_directory> <runtime_identifier> <configuration> <verbosity>
5+
6+
set -e
7+
8+
# Parse arguments
9+
WORKING_DIR="$1"
10+
RUNTIME_IDENTIFIER="${2:-linux-bionic-arm64}"
11+
CONFIGURATION="${3:-Release}"
12+
VERBOSITY="${4:-normal}"
13+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14+
15+
echo "Script is located at: $SCRIPT_DIR"
16+
17+
if [ -z "$WORKING_DIR" ]; then
18+
echo "Error: Working directory is required"
19+
echo "Usage: $0 <working_directory> [runtime_identifier] [configuration] [verbosity]"
20+
exit 1
21+
fi
22+
23+
if [ ! -d "$WORKING_DIR" ]; then
24+
echo "Error: Working directory does not exist: $WORKING_DIR"
25+
exit 1
26+
fi
27+
28+
echo "Building .NET NativeAOT for Android"
29+
echo " Working Directory: $WORKING_DIR"
30+
echo " Runtime Identifier: $RUNTIME_IDENTIFIER"
31+
echo " Configuration: $CONFIGURATION"
32+
echo " Verbosity: $VERBOSITY"
33+
echo ""
34+
35+
# 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"
36+
# 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.
37+
if [ ! -d "${ANDROID_NDK_BIN_PATH}" ] ; then
38+
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."
39+
40+
if [ ! -d "${ANDROID_NDK_HOME}" ] ; then
41+
echo "Error: ANDROID_NDK_HOME enviroment variable is not set or directory does not exist."
42+
exit 1
43+
fi
44+
45+
echo "Android NDK Home: ${ANDROID_NDK_HOME}"
46+
47+
# ndk_bin_common.sh sets the "HOST_TAG" environment variable to the NDK's toolchain platform
48+
source "${ANDROID_NDK_HOME}/build/tools/ndk_bin_common.sh"
49+
echo "Android Host Tag: ${HOST_TAG}"
50+
51+
ANDROID_NDK_BIN_PATH="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${HOST_TAG}/bin"
52+
53+
if [ ! -d "${ANDROID_NDK_BIN_PATH}" ] ; then
54+
echo "Error: Android NDK bin Path not found at ${ANDROID_NDK_BIN_PATH}"
55+
exit 1
56+
fi
57+
fi
58+
59+
echo "Android NDK bin Path: ${ANDROID_NDK_BIN_PATH}"
60+
61+
export PATH=${ANDROID_NDK_BIN_PATH}:$PATH
62+
63+
echo ""
64+
65+
# Change to working directory
66+
cd "$WORKING_DIR"
67+
68+
# Run dotnet publish with Android-specific settings
69+
echo "Running: dotnet publish -r $RUNTIME_IDENTIFIER -c $CONFIGURATION -v $VERBOSITY -p:PublishAotUsingRuntimePack=true"
70+
echo ""
71+
72+
dotnet publish \
73+
-r "$RUNTIME_IDENTIFIER" \
74+
-c "$CONFIGURATION" \
75+
-v "$VERBOSITY" \
76+
-p:PublishAotUsingRuntimePack=true
77+
78+
EXIT_CODE=$?
79+
80+
if [ $EXIT_CODE -eq 0 ]; then
81+
echo ""
82+
echo "✅ Android build completed successfully"
83+
else
84+
echo ""
85+
echo "❌ Android build failed with exit code $EXIT_CODE"
86+
exit $EXIT_CODE
87+
fi
88+
89+
exit 0

Builder/Beyond.NET.Builder.DotNET/PlatformIdentifier.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ public static class PlatformIdentifier
1010
/// Made up, not part of .NET!
1111
/// </summary>
1212
public const string Apple = "apple";
13+
14+
public const string Android = "linux-bionic";
1315
}

Builder/Beyond.NET.Builder.DotNET/RuntimeIdentifier.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@ public static class RuntimeIdentifier
2828
/// Made up, not part of .NET!
2929
/// </summary>
3030
public const string APPLE_UNIVERSAL = $"{PlatformIdentifier.Apple}-{TargetIdentifier.UNIVERSAL}";
31+
32+
public const string Android_ARM64 = $"{PlatformIdentifier.Android}-{TargetIdentifier.ARM64}";
3133
}

Builder/Beyond.NET.Builder/Beyond.NET.Builder.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<ItemGroup>
1111
<ProjectReference Include="..\..\Core\Beyond.NET.Core\Beyond.NET.Core.csproj" />
12+
<ProjectReference Include="..\Beyond.NET.Builder.Android\Beyond.NET.Builder.Android.csproj" />
1213
<ProjectReference Include="..\Beyond.NET.Builder.Apple\Beyond.NET.Builder.Apple.csproj" />
1314
<ProjectReference Include="..\Beyond.NET.Builder.DotNET\Beyond.NET.Builder.DotNET.csproj" />
1415
</ItemGroup>

Builder/Beyond.NET.Builder/BuildTargets.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public enum BuildTargets
1010
iOSARM64 = 1 << 2,
1111
iOSSimulatorARM64 = 1 << 3,
1212
iOSSimulatorX64 = 1 << 4,
13+
AndroidARM64 = 1 << 5,
1314

1415
MacOSUniversal = MacOSARM64 | MacOSX64,
1516
iOSSimulatorUniversal = iOSSimulatorARM64 | iOSSimulatorX64,
@@ -38,4 +39,9 @@ public static bool ContainsAnyMacOSTarget(this BuildTargets buildTargets)
3839
return buildTargets.HasFlag(BuildTargets.MacOSARM64) ||
3940
buildTargets.HasFlag(BuildTargets.MacOSX64);
4041
}
42+
43+
public static bool ContainsAnyAndroidTarget(this BuildTargets buildTargets)
44+
{
45+
return buildTargets.HasFlag(BuildTargets.AndroidARM64);
46+
}
4147
}

0 commit comments

Comments
 (0)