#!/usr/bin/env bash ## INIT # Get script directory script_dir="$(cd "$(dirname "$0")" && pwd)" # Change working directory to script directory cd $script_dir # Set up log file log_file="$script_dir/build.log" rm -f $log_file ## FUNCTIONS # Function to print usage instructions help() { echo "Usage: $0 [options]" echo "Options:" echo " -h, --help Print this help and exit" echo " -i Input project name (required)" echo " -g Generated project name (default: .Generated)" echo " -o Output folder name (default: libs)" echo " -y Assume yes to all prompts" echo " --ndk Android NDK path (default: ANDROID_NDK_HOME environment variable)" echo " --no-android Disable Android build (default: false)" echo " --no-ios Disable iOS build (default: false)" echo " --input-spec Input project .NET spec (default: net8.0)" echo " --output-spec Output project .NET spec (default: net8.0)" echo " --flutter Generate flutter project" } # Function to log a message to the console and log file log() { echo "$1" echo "$1" >>$log_file } # Function to run a command and log the output run() { eval $1 2>&1 | tee -a $log_file } # Function to check if a command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Function to replace INPUT_PROJECT_NAME and GENERATED_PROJECT_NAME # by the actual project names in the passed file replace_project_names() { sed -i '' "s/INPUT_PROJECT_NAME/$input_project_name/g" $1 sed -i '' "s/GENERATED_PROJECT_NAME/$generated_project_name/g" $1 sed -i '' "s/INPUT_NET_SPEC/$input_project_net_spec/g" $1 sed -i '' "s/OUTPUT_NET_SPEC/$output_project_net_spec/g" $1 } # Function to ask for confirmation before continuing # $1: message ask_confirmation() { if [ "$assume_yes" = true ]; then return fi read -p "$1 (y/n) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi } # Generate project generate_project() { cd $script_dir # Copy template beyondnetgen.*.config.json cp template.beyondnetgen.android.config.json beyondnetgen.android.config.json replace_project_names beyondnetgen.android.config.json cp template.beyondnetgen.ios.config.json beyondnetgen.ios.config.json replace_project_names beyondnetgen.ios.config.json # Generate project log "Generating project" run "beyondnetgen beyondnetgen.android.config.json" } # Generic function to build a project build_project() { local platform="$1" local config_file="$2" local template_csproj="$3" local output_dir="$4" local library_extension="$5" local abis=("${!6}") cd "$script_dir" # Copy template config file cp "$config_file" "beyondnetgen.$platform.config.json" replace_project_names "beyondnetgen.$platform.config.json" # Generate project log "Generating $platform project" run "beyondnetgen beyondnetgen.$platform.config.json" # Copy template csproj cp "$template_csproj" "$generated_project_dir/$generated_project_name.csproj" replace_project_names "$generated_project_dir/$generated_project_name.csproj" cd "$generated_project_dir" # For each .dll in external folder add it as a reference in the generated project search_dir="$script_dir/external" for entry in "$search_dir"/*; do dll_name=$(basename "$entry") log "Adding reference to $dll_name" # Add reference to the generated project by editing the csproj file # Search for the line # and add a reference to the dll below it sed -i '' "//a \\ \\ " "$generated_project_dir/$generated_project_name.csproj" done if [ "$platform" == "android" ]; then cp $script_dir/android_fake_clang.* "$generated_project_dir/" fi # Build for each architecture for arch in "${abis[@]}"; do log "Building $platform library for $arch" run "dotnet publish -r $arch -v normal -c Release -p:DisableUnsupportedError=true -p:PublishAotUsingRuntimePack=true -p:RemoveSections=true -p:AllowUnsafeBlocks=true $generated_project_name.csproj" # Copy the generated library to the output directory mkdir -p "$output_dir/$arch" cp $generated_project_dir/bin/Release/$output_project_net_spec/$arch/native/$generated_project_name.$library_extension $output_dir/$arch/$input_project_name.$library_extension if [ "$platform" == "ios" ]; then cd $output_dir/$arch install_name_tool -id "$(pwd)/"$input_project_name.$library_extension"" $input_project_name.$library_extension cd - fi done # Copy the generated headers to the output directory log "Copying header to output directory" cp "$generated_project_dir/Generated.h" "$output_dir/$input_project_name.h" } # Definition of specific parameters for each platform android_config_file="template.beyondnetgen.android.config.json" ios_config_file="template.beyondnetgen.ios.config.json" android_template_csproj="template.android.csproj" ios_template_csproj="template.ios.csproj" ios_library_extension="dylib" android_library_extension="so" # Definition of architectures for each platform android_abis=("linux-bionic-arm64" "linux-bionic-x64") ios_abis=( "osx-x64" "osx-arm64" "ios-arm64" "iossimulator-arm64" "iossimulator-x64") # Calling the generic function to build the Android project build_android() { build_project "android" "$android_config_file" "$android_template_csproj" "$output_dir" "$android_library_extension" android_abis[@] } # Calling the generic function to build the iOS project build_ios() { build_project "ios" "$ios_config_file" "$ios_template_csproj" "$output_dir" "$ios_library_extension" ios_abis[@] } generate_flutter() { cd $script_dir # From input_project_dir generate a flutter project name flutter_project_name="$input_project_name.Flutter" # Dart project name is the same as flutter project name but in snake case (remove dots and capitalize each word) dart_project_name=$(echo $flutter_project_name | sed -E 's/([A-Z])/_\1/g' | sed -E 's/^_//g' | sed -E 's/\.//g' | tr '[:upper:]' '[:lower:]') # If flutter_project_name already exists, ask for confirmation before continuing if [ -d "$flutter_project_name" ]; then ask_confirmation "Flutter project $flutter_project_name already exists. Continue?" rm -rf $flutter_project_name fi # Create flutter project log "Creating flutter project in $flutter_project_name" run "flutter create -e --platforms android,ios --project-name $dart_project_name $flutter_project_name" # Copy generated files to flutter project log "Copying generated files to flutter project" mkdir -p $flutter_project_name/android/app/src/main/jniLibs cp $generated_project_dir/Generated.h $flutter_project_name/Generated.h cp -r libs/linux* $flutter_project_name/android/app/src/main/jniLibs/ cp ffigen.yaml $flutter_project_name/ffigen.yaml cd $flutter_project_name run "flutter pub add -d ffigen && flutter pub get && dart run ffigen --config ffigen.yaml" } ## ARGUMENTS # Set default values WANT_TO_BUILD_ANDROID=true WANT_TO_BUILD_IOS=true ONLY_GENERATE=false WANT_TO_GENERATE_FLUTTER=false # Check for arguments if [ $# -eq 0 ]; then log "No arguments supplied" help exit 1 fi # Parse arguments while [ "$1" != "" ]; do case $1 in -h | --help) help exit 0 ;; -i) shift input_project_name=$1 ;; -g) shift generated_project_name=$1 ;; -o) shift output=$1 ;; -y) assume_yes=true ;; --ndk) shift export ANDROID_NDK_HOME=$1 ;; --input-spec) shift input_project_net_spec=$1 ;; --output-spec) shift output_project_net_spec=$1 ;; --no-android) WANT_TO_BUILD_ANDROID=false ;; --no-ios) WANT_TO_BUILD_IOS=false ;; --flutter) WANT_TO_GENERATE_FLUTTER=true ;; *) log "Unknown argument: $1" help exit 1 ;; esac shift done # Check for required arguments if [ -z "$input_project_name" ]; then log "Input project name is required" help exit 1 fi # Set default values if [ -z "$generated_project_name" ]; then generated_project_name="$input_project_name.Generated" fi if [ -z "$input_project_net_spec" ]; then input_project_net_spec="net8.0" fi if [ -z "$output_project_net_spec" ]; then output_project_net_spec="net8.0" fi if [ -z "$output" ]; then output="libs" fi input_project_dir="$script_dir/$input_project_name" generated_project_dir="$script_dir/$generated_project_name" output_dir="$script_dir/$output" ## PREREQUISITES CHECK # Check for bash >= 4.0 if [ "${BASH_VERSINFO:-0}" -lt 4 ]; then log "bash >= 4.0 is required. Aborting." exit 1 fi # Check for dotnet command_exists dotnet || { log "dotnet is required but it's not installed or not in PATH. Aborting." exit 1 } # Check if dotnet is in version 8.0 or higher dotnet_version=$(dotnet --version) if [[ $dotnet_version =~ ^8\. ]]; then log "dotnet version $dotnet_version is supported" else log "dotnet version $dotnet_version is not supported. Aborting." exit 1 fi # Check for beyondnetgen command_exists beyondnetgen || { log "beyondnetgen is required but it's not installed or not in PATH. Aborting." exit 1 } # Check if host is macos, if so, check for xcode, and set BUILD_IOS to true ostype=$(uname -s) log "Host OS: $ostype" if [ "$ostype" == "Darwin" ]; then if [ "$WANT_TO_BUILD_IOS" = false ]; then log "Disabling iOS build." BUILD_IOS=false else command_exists xcodebuild || { log "Xcode is not installed or not in PATH. Disabling iOS build." BUILD_IOS=false } log "Xcode detected. Enabling iOS build." BUILD_IOS=true fi else BUILD_IOS=false fi # Check for ANDROID_NDK_HOME if [ "$ANDROID_NDK_HOME" ] && [ "$WANT_TO_BUILD_ANDROID" = true ]; then log "ANDROID_NDK_HOME is set to $ANDROID_NDK_HOME. Enabling Android build." BUILD_ANDROID=true else BUILD_ANDROID=false if [ -z "$ANDROID_NDK_HOME" ]; then log "ANDROID_NDK_HOME is not set. Disabling Android build." else log "Disabling Android build." fi fi # Check if BUILD_IOS or BUILD_ANDROID is true if [ "$BUILD_IOS" = false ] && [ "$BUILD_ANDROID" = false ]; then log "Neither iOS nor Android build is enabled." log "Only generating project." ONLY_GENERATE=true fi ## CONFIG cd $script_dir # Copy template beyondnetgen.*.config.json cp template.beyondnetgen.android.config.json beyondnetgen.android.config.json replace_project_names beyondnetgen.android.config.json cp template.beyondnetgen.ios.config.json beyondnetgen.ios.config.json replace_project_names beyondnetgen.ios.config.json # Check if output_dir exists, if not, create it if [ ! -d "$output_dir" ]; then log "Creating output directory $output_dir" mkdir -p $output_dir fi ## BUILD # Clean previous build log "Cleaning previous build..." # Ask for confirmation ask_confirmation "Will delete: $generated_project_dir, $output_dir, $input_project_dir/obj $input_project_dir/bin. Continue?" rm -r $generated_project_dir rm -r "$input_project_dir/obj" rm -r "$input_project_dir/bin" # Build input project log "Building input project" cd $input_project_dir run "dotnet publish -v normal -c Release" # Only generate project if [ "$ONLY_GENERATE" = true ]; then generate_project fi # Build Android libraries if [ "$BUILD_ANDROID" = true ]; then build_android fi # Build iOS libraries if [ "$BUILD_IOS" = true ]; then build_ios fi # Generate flutter project if [ "$WANT_TO_GENERATE_FLUTTER" = true ]; then generate_flutter fi # Check last command exit code if [ $? -ne 0 ]; then log "Build failed. See $log_file for details." exit 1 else log "Finished building. See $log_file for details." fi