diff --git a/.github/workflows/emu-build-all-macos.yml b/.github/workflows/emu-build-all-macos.yml new file mode 100644 index 00000000..9805bf0d --- /dev/null +++ b/.github/workflows/emu-build-all-macos.yml @@ -0,0 +1,70 @@ +name: Build all emu variants (macos) + +on: + workflow_call: + # needed since it allows this to become a reusable workflow + workflow_dispatch: + # allows manual trigger + +permissions: + contents: write + +jobs: + deps: + name: Restore or build deps + if: ${{ !cancelled() }} + uses: ./.github/workflows/emu-deps-macos.yml + + emu-regular-x64-release: + name: Regular x64 (release) + needs: [ deps ] + if: ${{ !cancelled() }} + uses: ./.github/workflows/emu-build-variant-macos.yml + with: + emu-variant: regular + debug: false + + emu-regular-x64-debug: + name: Regular x64 (debug) + needs: [ deps ] + if: ${{ !cancelled() }} + uses: ./.github/workflows/emu-build-variant-macos.yml + with: + emu-variant: regular + debug: true + + emu-exp-x64-release: + name: Experimental x64 (release) + needs: [ deps ] + if: ${{ !cancelled() }} + uses: ./.github/workflows/emu-build-variant-macos.yml + with: + emu-variant: exp + debug: false + + emu-exp-x64-debug: + name: Experimental x64 (debug) + needs: [ deps ] + if: ${{ !cancelled() }} + uses: ./.github/workflows/emu-build-variant-macos.yml + with: + emu-variant: exp + debug: true + + tools-x64-release: + name: Tools x64 (release) + needs: [ deps ] + if: ${{ !cancelled() }} + uses: ./.github/workflows/emu-build-variant-macos.yml + with: + emu-variant: tools + debug: false + + tools-x64-debug: + name: Tools x64 (debug) + needs: [ deps ] + if: ${{ !cancelled() }} + uses: ./.github/workflows/emu-build-variant-macos.yml + with: + emu-variant: tools + debug: true diff --git a/.github/workflows/emu-build-variant-macos.yml b/.github/workflows/emu-build-variant-macos.yml new file mode 100644 index 00000000..bb8f57e7 --- /dev/null +++ b/.github/workflows/emu-build-variant-macos.yml @@ -0,0 +1,111 @@ +name: Build emu variant (macos) + +on: + workflow_call: + inputs: + emu-variant: + description: | + Which variant of the emu to build: + regular: build the regular version of the emu + exp: build the experimental version of the emu + tools: build the tools only + required: true + type: string + + debug: + description: | + build mode + true: build in debug mode + false: build in release mode + required: true + type: boolean + +permissions: + contents: write + +env: + DEPS_CACHE_KEY: emu-deps-macos + DEPS_CACHE_DIR: build/deps/macos + + PACKAGE_BASE_DIR: "build/package/macos" + THIRD_PARTY_BASE_DIR: 'third-party' + +jobs: + build: + runs-on: macos-13 + if: ${{ !cancelled() }} + + steps: +### setup build vars + - name: Setup build vars + shell: bash + run: | + echo "env file = '$GITHUB_ENV'" + echo "workspace = '${{ github.workspace }}'" + + build_switches="" + if [[ "${{ inputs.emu-variant }}" = "regular" ]]; then + build_switches="+lib-64 +client-64" + elif [[ "${{ inputs.emu-variant }}" = "exp" ]]; then + build_switches="+exp-lib-64 +exp-client-64" + elif [[ "${{ inputs.emu-variant }}" = "tools" ]]; then + build_switches="+tool-clientldr +tool-itf-64 +tool-lobby-64" + else + echo "[X] invalid emu variant '${{ inputs.emu-variant }}'" >&2 + exit 1 + fi + echo "build_switches=$build_switches" >>"$GITHUB_ENV" + + build_mode="" + if [[ "${{ inputs.debug }}" = "true" ]]; then + build_mode="debug" + else + build_mode="release" + fi + echo "build_mode=$build_mode" >>"$GITHUB_ENV" + + - name: Checkout branch + uses: actions/checkout@v4 + +### deps + - name: Restore deps + id: emu-deps-cache-step + uses: actions/cache@v4 + with: + key: ${{ env.DEPS_CACHE_KEY }} + path: ${{ env.DEPS_CACHE_DIR }} + +## mandatory macos packages, installed via sudo apt install ... + - name: Install required packages + shell: bash + run: sudo chmod 777 build_macos_deps.sh && ./build_macos_deps.sh -verbose -packages_only + +## extra helpers/tools, these are not built inside the deps build dir + - name: Clone third-party build helpers (build/macos) + uses: actions/checkout@v4 + with: + ref: 'third-party/build/macos' + path: "${{env.THIRD_PARTY_BASE_DIR}}/build/macos" + +### fix folder permissions! not sure why this fails + # nested subdirs "build/macos/release" cause permission problems + - name: Give all permissions to repo folder + shell: bash + working-directory: ${{ github.workspace }} + run: sudo chmod -R 777 "${{ github.workspace }}" + +### build target(s) + - name: Build target(s) + shell: bash + working-directory: ${{ github.workspace }} + run: "sudo chmod 777 build_macos.sh && ./build_macos.sh -j 3 -verbose ${{ env.build_mode }} clean +build_str ${{ github.sha }} ${{ env.build_switches }}" + +### upload artifact/package to github Actions (for targets) + - name: Upload build package (for targets) + uses: actions/upload-artifact@v4 + with: + name: "emu-macos-${{ inputs.emu-variant }}-64-${{ env.build_mode }}-${{ github.sha }}" + path: "build/macos/" + if-no-files-found: 'error' + compression-level: 9 + retention-days: 1 diff --git a/build_macos.sh b/build_macos.sh new file mode 100644 index 00000000..76293253 --- /dev/null +++ b/build_macos.sh @@ -0,0 +1,528 @@ +#!/usr/bin/env bash + +### make sure to cd to the emu src dir +required_files=( + "dll/dll.cpp" + "dll/steam_client.cpp" + "controller/gamepad.c" + "sdk/steam/isteamclient.h" +) + +for emu_file in "${required_files[@]}"; do + if [ ! -f "$emu_file" ]; then + echo "[X] Invalid emu directory, change directory to emu's src dir (missing file '$emu_file')" >&2 + exit 1 + fi +done + +BUILD_LIB64=0 +BUILD_EXP_LIB64=0 + +BUILD_CLIENT64=0 +BUILD_EXPT_CLIENT64=0 +BUILD_TOOL_CLIENT_LDR=0 + +BUILD_TOOL_FIND_ITFS64=0 +BUILD_TOOL_LOBBY64=0 + +BUILD_LIB_NET_SOCKETS_64=0 + +# < 0: deduce, > 1: force +PARALLEL_THREADS_OVERRIDE=-1 + +CMD_BUILD_STR='' + +# 0 = release, 1 = debug, otherwise error +BUILD_TYPE=-1 + +CLEAN_BUILD=0 + +VERBOSE=0 + +for (( i=1; i<=$#; i++ )); do + var="${!i}" + if [[ "$var" = "-j" ]]; then + i=$((i+1)) + PARALLEL_THREADS_OVERRIDE="${!i}" + [[ "$PARALLEL_THREADS_OVERRIDE" =~ ^[0-9]+$ ]] || { + echo "[X] Invalid arg after -j, expected a number" >&2; + exit 1; + } + #echo "[?] Overriding parralel build jobs count with $PARALLEL_THREADS_OVERRIDE" + elif [[ "$var" = "+build_str" ]]; then + i=$((i+1)) + CMD_BUILD_STR="${!i}" + [[ -z "$CMD_BUILD_STR" ]] && { + echo "[X] Expected a build string" >&2; + exit 1; + } + elif [[ "$var" = "+lib-64" ]]; then + BUILD_LIB64=1 + elif [[ "$var" = "+exp-lib-64" ]]; then + BUILD_EXP_LIB64=1 + elif [[ "$var" = "+client-64" ]]; then + BUILD_CLIENT64=1 + elif [[ "$var" = "+exp-client-64" ]]; then + BUILD_EXPT_CLIENT64=1 + elif [[ "$var" = "+tool-clientldr" ]]; then + BUILD_TOOL_CLIENT_LDR=1 + elif [[ "$var" = "+tool-itf-64" ]]; then + BUILD_TOOL_FIND_ITFS64=1 + elif [[ "$var" = "+tool-lobby-64" ]]; then + BUILD_TOOL_LOBBY64=1 + elif [[ "$var" = "+lib-netsockets-64" ]]; then + BUILD_LIB_NET_SOCKETS_64=1 + elif [[ "$var" = "-verbose" ]]; then + VERBOSE=1 + elif [[ "$var" = "clean" ]]; then + CLEAN_BUILD=1 + elif [[ "$var" = "release" ]]; then + BUILD_TYPE=0 + elif [[ "$var" = "debug" ]]; then + BUILD_TYPE=1 + else + echo "[X] Invalid arg: $var" >&2 + exit 1 + fi +done + +# use 70% +build_threads="$(( $(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 0) * 70 / 100 ))" +[[ $PARALLEL_THREADS_OVERRIDE -gt 0 ]] && build_threads="$PARALLEL_THREADS_OVERRIDE" +[[ $build_threads -lt 1 ]] && build_threads=1 + +# build type +optimize_level="" +dbg_level="" +dbg_defs="" +linker_strip_dbg_symbols='' +build_folder="" +if [[ "$BUILD_TYPE" = "0" ]]; then + optimize_level="-O2" + dbg_level="-g0" + dbg_defs="-DEMU_RELEASE_BUILD -DNDEBUG" + linker_strip_dbg_symbols='-Wl,-S' + build_folder="release" +elif [[ "$BUILD_TYPE" = "1" ]]; then + optimize_level="-Og" + dbg_level="-g3" + dbg_defs="" + linker_strip_dbg_symbols="" + build_folder="debug" +else + echo "[X] You must specify any of: [release debug]" >&2 + exit 1 +fi + +build_root_dir="build/macos/$build_folder" +build_root_64="$build_root_dir/x64" +build_root_experimental="$build_root_dir/experimental" +build_root_tools="$build_root_dir/tools" + +# common stuff +deps_dir="build/deps/macos" +libs_dir="libs" +tools_dir='tools' +build_temp_dir="build/tmp/macos" +protoc_out_dir="dll/proto_gen/macos" +third_party_dir="third-party" +third_party_build_dir="$third_party_dir/build/macos" + +protoc_exe_64="$deps_dir/protobuf/install64/bin/protoc" + +parallel_exe="$third_party_build_dir/rush/rush" + +# https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html +common_compiler_args="-std=c++17 -fvisibility=hidden -fexceptions -fno-jump-tables -fno-char8_t" + +# third party dependencies (include folder + folder containing .a file) +ssq_inc="$deps_dir/libssq/include" +ssq_lib64="$deps_dir/libssq/build64" + +curl_inc64="$deps_dir/curl/install64/include" +curl_lib64="$deps_dir/curl/install64/lib" + +protob_inc64="$deps_dir/protobuf/install64/include" +protob_lib64="$deps_dir/protobuf/install64/lib" + +zlib_inc64="$deps_dir/zlib/install64/include" +zlib_lib64="$deps_dir/zlib/install64/lib" + +mbedtls_inc64="$deps_dir/mbedtls/install64/include" +mbedtls_lib64="$deps_dir/mbedtls/install64/lib" + +overlay_inc64="$deps_dir/ingame_overlay/install64/include" +overlay_lib64="$deps_dir/ingame_overlay/install64/lib" +overlay_sys_lib64="$deps_dir/ingame_overlay/deps/System/install64/lib" +overlay_detour_lib64="$deps_dir/ingame_overlay/deps/mini_detour/install64/lib" + +# directories to use for #include +release_incs_both=( + "$ssq_inc" + "$libs_dir" + "$protoc_out_dir" + "$libs_dir/utfcpp" + "controller" + "dll" + "sdk" + "overlay_experimental" + "crash_printer" + "helpers" +) +release_incs64=( + "${release_incs_both[@]}" + "$curl_inc64" + "$protob_inc64" + "$zlib_inc64" + "$mbedtls_inc64" +) + +# directories where libraries (.a or .so) will be looked up +release_libs_dir64=( + "$ssq_lib64" + "$curl_lib64" + "$protob_lib64" + "$zlib_lib64" + "$mbedtls_lib64" + + "$overlay_lib64" + "$overlay_sys_lib64" + "$overlay_detour_lib64" +) + +# libraries to link with, either static ".a" or dynamic ".so" (name only) +# if it's called libXYZ.a, then only write "XYZ" +# for static libs make sure to build a PIC lib +# each will be prefixed with -l, ex: -lpthread +release_libs=( + "pthread" + "dl" + "ssq" + "z" # libz library + "curl" + "protobuf-lite" + "mbedcrypto" +) + +# common source files used everywhere, just for convinience, you still have to provide a complete list later +release_src=( + "dll/*.cpp" + "$protoc_out_dir/*.cc" + "crash_printer/linux.cpp" + "helpers/common_helpers.cpp" +) + +emu_build_string="$CMD_BUILD_STR" +[[ -z "$emu_build_string" ]] && { + emu_build_string="$(date +'%m%d%Y-%H%M')" +} + +# https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html +# additional #defines +common_defs="-DGNUC -DUTF_CPP_CPLUSPLUS=201703L -DCURL_STATICLIB -D'EMU_BUILD_STRING=$emu_build_string'" +release_defs="$dbg_defs $common_defs" + +# errors to ignore during build +release_ignore_warn="-Wno-switch" + +[ ! -d "$deps_dir" ] && { + echo "[X] Dependencies dir \"$deps_dir\" was not found" >&2; + exit 1; +} + +[ ! -f "$protoc_exe_64" ] && { + echo "[X] protobuff compiler wasn't found - 64" >&2; + exit 1; +} + +[ ! -f "$parallel_exe" ] && { + echo "[X] tool to run parallel compilation jobs '$parallel_exe' wasn't found" >&2; + exit 1; +} + +chmod 777 "$parallel_exe" + +echo "[?] All build operations will use $build_threads parallel jobs" + +last_code=0 + +empty_arr=() + +function build_for () { + + local is_exe=$( [[ "$1" != 0 ]] && { echo 1; } || { echo 0; } ) + local out_filepath="${2:?'missing output filepath'}" + local -n all_src=$3 + local -n extra_inc_dirs=$4 + local extra_defs="$5" + local -n extra_libs=$6 + + [[ "${#all_src[@]}" = "0" ]] && { + echo [X] "No source files specified" >&2; + return 1; + } + + # https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html + local cpiler_pic_pie='-fPIC' + [[ $is_exe = 1 ]] && cpiler_pic_pie='-fPIE' + + # https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html + # https://www.baeldung.com/linux/library-convert-static-to-shared + local linker_pic_pie='-shared' + [[ $is_exe = 1 ]] && linker_pic_pie='-pie' + + local tmp_work_dir="${out_filepath##*/}" + tmp_work_dir="$build_temp_dir/${tmp_work_dir%.*}" + echo " -- Cleaning compilation directory '$tmp_work_dir/'" + rm -f -r "$tmp_work_dir" + mkdir -p "$tmp_work_dir" || { + echo [X] "Failed to create compilation directory" >&2; + return 1; + } + + local build_cmds=() + local -A objs_dirs=() + for src_file in $( echo "${all_src[@]}" ); do + [[ -f "$src_file" ]] || { + echo "[X] source file "$src_file" wasn't found" >&2; + return 1; + } + + # https://stackoverflow.com/a/9559024 + local obj_dir=$( [[ -d "${src_file%/*}" ]] && echo "${src_file%/*}" || echo '.' ) + obj_dir="$tmp_work_dir/$obj_dir" + + local obj_name="${src_file##*/}" + obj_name="${obj_name%.*}.o" + + build_cmds+=("$src_file<>$obj_dir/$obj_name") + objs_dirs["$obj_dir"]="$obj_dir" + + mkdir -p "$obj_dir" + done + + [[ "${#build_cmds[@]}" = "0" ]] && { + echo [X] "No valid source files were found" >&2; + return 1; + } + + local target_incs=() + local target_libs_dirs=() + target_incs=("${release_incs64[@]}") + target_libs_dirs=("${release_libs_dir64[@]}") + target_incs=( + "${target_incs[@]}" + "${extra_inc_dirs[@]}" + ) + # wrap each -ImyIncDir with single quotes -> '-ImyIncDir' + target_incs=("${target_incs[@]/#/\'-I}") + target_incs=("${target_incs[@]/%/\'}") + + echo " -- Compiling object files with $build_threads parallel jobs inside directory '$tmp_work_dir/'" + local build_cmd="clang++ -c -x c++ $common_compiler_args $cpiler_pic_pie $optimize_level $dbg_level $release_ignore_warn $release_defs $extra_defs ${target_incs[@]} '{1}' '-o{2}'" + + if [[ $VERBOSE = 1 ]]; then + printf '%s\n' "${build_cmds[@]}" | "$parallel_exe" --dry-run -j$build_threads -d '<>' -k "$build_cmd" + echo; + fi + + printf '%s\n' "${build_cmds[@]}" | "$parallel_exe" -j$build_threads -d '<>' -k "$build_cmd" + exit_code=$? + echo " -- Exit code = $exit_code" + echo; echo; + sleep 1 + [[ $exit_code = 0 ]] || { + rm -f -r "$tmp_work_dir"; + sleep 1 + return $exit_code; + } + + echo " -- Linking all object files from '$tmp_work_dir/' to produce '$out_filepath'" + local obj_files=() + for obj_file in $(echo "${objs_dirs[@]/%//*.o}" ); do + [[ -f "$obj_file" ]] && obj_files+=("$obj_file") + done + # if no files were added + [[ "${#obj_files[@]}" -gt 0 ]] || { + echo "[X] No files to link" >&2; + return 1; + } + + local out_dir="${out_filepath%/*}" + [[ "$out_dir" = "$out_filepath" ]] && out_dir='.' + mkdir -p "$out_dir" + + local target_libs=( + "${release_libs[@]}" + "${extra_libs[@]}" + ) + + # https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/developer_guide/gcc-using-libraries#gcc-using-libraries_using-both-static-dynamic-library-gcc + # https://linux.die.net/man/1/ld + if [[ $VERBOSE = 1 ]]; then + echo "clang++ $common_compiler_args $cpiler_pic_pie $optimize_level $dbg_level $linker_pic_pie $linker_strip_dbg_symbols -o" "$out_filepath" "${obj_files[@]}" "${target_libs_dirs[@]/#/-L}" "-Wl,--whole-archive -Wl,-Bstatic" "${target_libs[@]/#/-l}" "-Wl,-Bdynamic -Wl,--no-whole-archive -Wl,--exclude-libs,ALL" + echo; + fi + + clang++ $common_compiler_args $cpiler_pic_pie $optimize_level $dbg_level $linker_pic_pie $linker_strip_dbg_symbols -o "$out_filepath" "${obj_files[@]}" "${target_libs_dirs[@]/#/-L}" -Wl,--whole-archive -Wl,-Bstatic "${target_libs[@]/#/-l}" -Wl,-Bdynamic -Wl,--no-whole-archive -Wl,--exclude-libs,ALL + exit_code=$? + echo " -- Exit code = $exit_code" + echo; echo; + rm -f -r "$tmp_work_dir" + sleep 1 + return $exit_code + +} + +function cleanup () { + rm -f -r "$build_temp_dir" +} + +if [[ "$CLEAN_BUILD" = "1" ]]; then + echo // cleaning previous build + [[ -d "$build_root_dir" ]] && rm -f -r "$build_root_dir" + echo; echo; +fi + + +## tools +if [[ "$BUILD_TOOL_CLIENT_LDR" = "1" ]]; then + [[ -d "$build_root_tools/steamclient_loader/" ]] || mkdir -p "$build_root_tools/steamclient_loader/" + cp -f "$tools_dir"/steamclient_loader/linux/* "$build_root_tools/steamclient_loader/" + echo; echo; +fi + + +### x64 build +cleanup + +echo // invoking protobuf compiler - 64 +rm -f -r "$protoc_out_dir" +mkdir -p "$protoc_out_dir" +"$protoc_exe_64" ./dll/*.proto -I./dll/ --cpp_out="$protoc_out_dir/" +last_code=$((last_code + $?)) +echo; echo; + +if [[ "$BUILD_LIB64" = "1" ]]; then + echo // building shared lib libsteam_api.so - 64 + all_src_files=( + "${release_src[@]}" + "controller/*.c" + ) + build_for 0 "$build_root_64/libsteam_api.so" all_src_files empty_arr '-DCONTROLLER_SUPPORT' empty_arr + last_code=$((last_code + $?)) +fi + +if [[ "$BUILD_CLIENT64" = "1" ]]; then + echo // building shared lib steamclient.so - 64 + all_src_files=( + "${release_src[@]}" + "controller/*.c" + ) + build_for 0 "$build_root_64/steamclient.so" all_src_files empty_arr '-DCONTROLLER_SUPPORT -DSTEAMCLIENT_DLL' empty_arr + last_code=$((last_code + $?)) +fi + +if [[ "$BUILD_EXP_LIB64" = "1" ]]; then + echo // building shared lib experimental libsteam_api.so - 64 + all_src_files=( + "${release_src[@]}" + "controller/*.c" + "overlay_experimental/*.cpp" + ) + extra_incs=( + "$overlay_inc64" + ) + extra_link_libs=( + "ingame_overlay" + "system" # ingame_overlay dependency + "mini_detour" # ingame_overlay dependency + ) + build_for 0 "$build_root_experimental/x64/libsteam_api.so" all_src_files extra_incs '-DCONTROLLER_SUPPORT -DEMU_OVERLAY -DImTextureID=ImU64' extra_link_libs + last_code=$((last_code + $?)) +fi + +if [[ "$BUILD_EXPT_CLIENT64" = "1" ]]; then + echo // building shared lib experimental steamclient.so - 64 + all_src_files=( + "${release_src[@]}" + "controller/*.c" + "overlay_experimental/*.cpp" + ) + extra_incs=( + "$overlay_inc64" + ) + extra_link_libs=( + "ingame_overlay" + "system" # ingame_overlay dependency + "mini_detour" # ingame_overlay dependency + ) + build_for 0 "$build_root_experimental/x64/steamclient.so" all_src_files extra_incs '-DCONTROLLER_SUPPORT -DSTEAMCLIENT_DLL -DEMU_OVERLAY -DImTextureID=ImU64' extra_link_libs + last_code=$((last_code + $?)) +fi + +if [[ "$BUILD_TOOL_LOBBY64" = "1" ]]; then + echo // building executable lobby_connect_x64 - 64 + all_src_files=( + "${release_src[@]}" + "$tools_dir/lobby_connect/lobby_connect.cpp" + ) + build_for 1 "$build_root_tools/lobby_connect/lobby_connect_x64" all_src_files empty_arr '-DNO_DISK_WRITES -DLOBBY_CONNECT' empty_arr + last_code=$((last_code + $?)) +fi + +if [[ "$BUILD_TOOL_FIND_ITFS64" = "1" ]]; then + echo // building executable generate_interfaces_file_x64 - 64 + all_src_files=( + "$tools_dir/generate_interfaces/generate_interfaces.cpp" + ) + build_for 1 "$build_root_tools/find_interfaces/generate_interfaces_file_x64" all_src_files empty_arr '' empty_arr + last_code=$((last_code + $?)) +fi + +if [[ "$BUILD_LIB_NET_SOCKETS_64" = "1" ]]; then + echo // building shared lib steamnetworkingsockets64.so - 64 + all_src_files=( + "networking_sockets_lib/steamnetworkingsockets.cpp" + ) + build_for 0 "$build_root_dir/networking_sockets_lib/steamnetworkingsockets64.so" all_src_files empty_arr '' empty_arr + last_code=$((last_code + $?)) +fi + + +# cleanup +cleanup + + +# copy configs + examples +if [[ $last_code = 0 ]]; then + echo "// copying readmes + files examples" + mkdir -p "$build_root_dir/" + cp -f -r "post_build/steam_settings.EXAMPLE/" "$build_root_dir/" + cp -f "post_build/README.release.md" "$build_root_dir/" + cp -f "CHANGELOG.md" "$build_root_dir/" + if [[ $BUILD_TYPE = 1 ]]; then + cp -f "post_build/README.debug.md" "$build_root_dir/" + fi + if [[ -d "$build_root_tools/find_interfaces" ]]; then + mkdir -p "$build_root_tools/find_interfaces/" + cp -f "post_build/README.find_interfaces.md" "$build_root_tools/find_interfaces/" + fi + if [[ -d "$build_root_tools/lobby_connect" ]]; then + mkdir -p "$build_root_tools/lobby_connect/" + cp -f "post_build/README.lobby_connect.md" "$build_root_tools/lobby_connect/" + fi +else + echo "[X] Not copying readmes or files examples due to previous errors" >&2 +fi +echo; echo; + + +echo; +if [[ $last_code = 0 ]]; then + echo "[GG] no failures" +else + echo "[XX] general failure" >&2 +fi + +exit $last_code