diff --git a/.gitignore b/.gitignore index 2def5be3217..10a10093bfe 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ _wasi_sdk_fetch /site/ output/ +*.dmg .devcontainer/devcontainer-lock.json diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index 7225914a789..744ecd0eeb9 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -570,12 +570,109 @@ elseif(APPLE) # Run macdeployqt install(SCRIPT ${qt_deploy_companion} COMPONENT Runtime) - # Bundle SDL2 + # Bundle SDL2 (copy the real dylib; IMPORTED_RUNTIME_ARTIFACTS keeps + # Homebrew symlinks and breaks the app inside a .dmg). Homebrew's + # sdl2-compat also loads SDL3 from @loader_path/libSDL3.dylib at runtime. if(TARGET SDL2::SDL2) - install(IMPORTED_RUNTIME_ARTIFACTS SDL2::SDL2 - FRAMEWORK DESTINATION "${companion_app_dir}/Contents/Frameworks" - LIBRARY DESTINATION "${companion_app_dir}/Contents/Frameworks" - COMPONENT Runtime) + get_target_property(_sdl2_location SDL2::SDL2 IMPORTED_LOCATION) + if(NOT _sdl2_location) + get_target_property(_sdl2_location SDL2::SDL2 IMPORTED_LOCATION_RELEASE) + endif() + if(_sdl2_location AND _sdl2_location MATCHES "\\.framework(/|$)") + # Genuine upstream SDL2.framework (CI's setup-sdl2.sh, or a manual framework + # install): no SDL3 dlopen shim to worry about, and IMPORTED_RUNTIME_ARTIFACTS + # copies frameworks correctly — it's only the Homebrew dylib case below whose + # symlinks it mangles. This is what CI has shipped all along. + message(STATUS "Bundling SDL2 framework: ${_sdl2_location}") + install(IMPORTED_RUNTIME_ARTIFACTS SDL2::SDL2 + FRAMEWORK DESTINATION "${companion_app_dir}/Contents/Frameworks" + LIBRARY DESTINATION "${companion_app_dir}/Contents/Frameworks" + COMPONENT Runtime) + elseif(_sdl2_location) + message(STATUS "Bundling SDL2 library: ${_sdl2_location}") + get_filename_component(_sdl2_real "${_sdl2_location}" REALPATH) + get_filename_component(_sdl2_name "${_sdl2_location}" NAME) + install(FILES "${_sdl2_real}" + DESTINATION "${companion_app_dir}/Contents/Frameworks" + RENAME "${_sdl2_name}" + COMPONENT Runtime) + + set(_sdl3_real "") + find_package(SDL3 CONFIG QUIET) + if(TARGET SDL3::SDL3-shared) + get_target_property(_sdl3_location SDL3::SDL3-shared IMPORTED_LOCATION_RELEASE) + if(NOT _sdl3_location) + get_target_property(_sdl3_location SDL3::SDL3-shared IMPORTED_LOCATION) + endif() + if(_sdl3_location) + get_filename_component(_sdl3_real "${_sdl3_location}" REALPATH) + endif() + endif() + if(NOT _sdl3_real) + set(_sdl3_hints "") + if(DEFINED ENV{HOMEBREW_PREFIX}) + list(APPEND _sdl3_hints + "$ENV{HOMEBREW_PREFIX}/opt/sdl3/lib" + "$ENV{HOMEBREW_PREFIX}/lib") + endif() + find_program(_brew_EXECUTABLE brew) + if(_brew_EXECUTABLE) + execute_process( + COMMAND "${_brew_EXECUTABLE}" --prefix sdl3 + OUTPUT_VARIABLE _brew_sdl3_prefix + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _brew_sdl3_result + ) + if(_brew_sdl3_result EQUAL 0 AND _brew_sdl3_prefix) + list(APPEND _sdl3_hints "${_brew_sdl3_prefix}/lib") + endif() + endif() + find_library(_sdl3_library + NAMES SDL3.0 SDL3 + HINTS ${_sdl3_hints} + NO_DEFAULT_PATH + ) + if(NOT _sdl3_library) + find_library(_sdl3_library NAMES SDL3.0 SDL3) + endif() + if(_sdl3_library) + get_filename_component(_sdl3_real "${_sdl3_library}" REALPATH) + endif() + endif() + if(_sdl3_real) + install(FILES "${_sdl3_real}" + DESTINATION "${companion_app_dir}/Contents/Frameworks" + RENAME "libSDL3.dylib" + COMPONENT Runtime) + else() + message(WARNING "SDL3 not found; packaged Companion may crash if linked against sdl2-compat") + endif() + + install(CODE " + set(_fw \"\${CMAKE_INSTALL_PREFIX}/${companion_app_dir}/Contents/Frameworks\") + execute_process( + COMMAND install_name_tool -id \"@executable_path/../Frameworks/${_sdl2_name}\" \"\${_fw}/${_sdl2_name}\" + RESULT_VARIABLE _sdl2_id_result + ERROR_VARIABLE _sdl2_id_error + ) + if(NOT _sdl2_id_result EQUAL 0) + message(FATAL_ERROR \"install_name_tool failed for ${_sdl2_name}: \${_sdl2_id_error}\") + endif() + if(EXISTS \"\${_fw}/libSDL3.dylib\") + execute_process( + COMMAND install_name_tool -id \"@executable_path/../Frameworks/libSDL3.dylib\" \"\${_fw}/libSDL3.dylib\" + RESULT_VARIABLE _sdl3_id_result + ERROR_VARIABLE _sdl3_id_error + ) + if(NOT _sdl3_id_result EQUAL 0) + message(FATAL_ERROR \"install_name_tool failed for libSDL3.dylib: \${_sdl3_id_error}\") + endif() + endif() + " COMPONENT Runtime) + else() + message(WARNING "SDL2::SDL2 has no IMPORTED_LOCATION; SDL2 will not be bundled") + endif() endif() # Re-sign bundle after all contents are installed diff --git a/docs/building/macos-sequoia.md b/docs/building/macos-sequoia.md index a62c752dbd2..e7bc231abd4 100644 --- a/docs/building/macos-sequoia.md +++ b/docs/building/macos-sequoia.md @@ -3,22 +3,26 @@ # Install [Homebrew](https://brew.sh/) - Run command in `Terminal`: -``` + +```bash /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ``` + !!! tip - Installing Brew via the command above will automatically install the [Xcode Command Line Tools](https://mac.install.guide/commandlinetools/). If for some reason you need to do this manually, run `xcode-select —install` via the Terminal app. + Installing Brew via the command above will automatically install the [Xcode Command Line Tools](https://mac.install.guide/commandlinetools/). If for some reason you need to do this manually, run `xcode-select --install` via the Terminal app. # Install Qt 6 + !!! note If you only intend on building the firmware, and not `simu`, `companion` or `simulator`, this is not necessary, and you can skip to the next step. -``` +```bash brew install qt@6 ``` Once Qt has been installed, you should set a couple environment variables (please modify according to the real installation paths): -``` + +```bash export QTDIR=$(brew --prefix)/opt/qt@6 export QT_PLUGIN_PATH=$QTDIR/plugins ``` @@ -29,97 +33,146 @@ Please note that `QT_PLUGIN_PATH` is required to be able to run Companion from y Download and install the ARM GCC toolchain [from here](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads) (installs in `/Applications/ArmGNUToolchain/`): -- For Intel Mac: https://developer.arm.com/-/media/Files/downloads/gnu/14.2.rel1/binrel/arm-gnu-toolchain-14.2.rel1-darwin-x86_64-arm-none-eabi.pkg -- If Mac Silicon (i.e. M1-M5): https://developer.arm.com/-/media/Files/downloads/gnu/14.2.rel1/binrel/arm-gnu-toolchain-14.2.rel1-darwin-arm64-arm-none-eabi.pkg +- Intel Mac: +- Apple Silicon (M1–M5): -Please note that this installation takes care of allowing the downloaded binaries to be run and prevents them being quarantined. If you choose to install the `tar.xz` archive to another location, you will have to take care of that yourself (see https://disable-gatekeeper.github.io/ for more details). +If you install the `.tar.xz` archive manually instead of the `.pkg`, you may need to remove the macOS quarantine flag yourself. See for details. -# Install various dependencies +# Other tools -- With `brew` in a `Terminal`: -``` -brew install sdl cmake +```bash +brew install sdl2 sdl3 cmake uv ``` +!!! note + Homebrew's `sdl2` package is [sdl2-compat](https://github.com/libsdl-org/sdl2-compat), which depends on **SDL3** at runtime. Both `sdl2` and `sdl3` must be installed before building Companion or a `.dmg`. + If you plan to run the standalone simulator for debugging: -``` -brew install --cask quartz -``` +```bash +brew install --cask xquartz +``` # Download EdgeTX code - Checkout code -``` + +```bash git clone --recursive https://github.com/EdgeTX/edgetx.git ``` - Switch into the source directory: -``` + +```bash cd edgetx ``` # Install Python dependencies -Since Python 3.11 enabled the "externally managed" flag, it is recommended that you use a virtual environment. [uv](https://docs.astral.sh/uv/getting-started/installation/) is one of the easiest tools to do and manage this, and can be installed with brew. It is recommended you create the virtual environment now rather than earlier in the process, as doing it now will create it in the edgetx directory. - -- Install UV: -``` -brew install uv -``` +Since Python 3.11+, macOS uses an externally managed Python environment. Create a project virtual environment with [uv](https://docs.astral.sh/uv/getting-started/installation/): -- Create the virtual environment (and use specific version of python for this environment): -``` +```bash uv venv --python 3.14 -``` - -- Activate the virtual environment (you will need to run this whenever you want to compile in the future from a new terminal session): -``` source .venv/bin/activate +uv pip install -r requirements.txt ``` -- Install the packages: -``` -uv pip install Pillow clang lz4 jinja2 +Activate the virtual environment in every new terminal session before building: + +```bash +source .venv/bin/activate ``` -# Compile EdgeTX +# Configure the build -- Create and enter build directory: -``` +```bash mkdir -p build && cd build ``` -Configure build flags using `cmake` (in this case, for RadioMaster TX16S, [see here](https://github.com/EdgeTX/edgetx/blob/main/tools/build-common.sh) for other possible handset specific flags). -``` +Configure for RadioMaster TX16S ([other radio flags](https://github.com/EdgeTX/edgetx/blob/main/tools/build-common.sh)): + +```bash cmake -DPCB=X10 -DPCBREV=TX16S \ -DCMAKE_PREFIX_PATH=$QTDIR \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \ -DARM_TOOLCHAIN_DIR=/Applications/ArmGNUToolchain/14.2.Rel1/arm-none-eabi/bin/ .. ``` -!!! note - Please note that the variables `CMAKE_PREFIX_PATH`, `ARM_TOOLCHAIN_DIR` must be specified additionally to what is described in the other compilation HowTos: +| Variable | Purpose | +|----------|---------| +| `CMAKE_PREFIX_PATH` | Path to your Qt installation (`$QTDIR`) | +| `CMAKE_OSX_DEPLOYMENT_TARGET` | Minimum macOS version for Companion. `14.0` (Sonoma) is a convenient local-dev default; release builds (CI) target `11.0` for broader compatibility — pass `-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0` if you want a build matching what CI ships. If omitted entirely, no `-mmacosx-version-min` flag is passed and clang targets whatever OS version your Mac is running | +| `ARM_TOOLCHAIN_DIR` | Path to ARM GCC binaries. Must end with `/` | - - `CMAKE_PREFIX_PATH`: this must point to your Qt installation path. - - `ARM_TOOLCHAIN_DIR`: this must point to where ARM GCC has been installed (and MUST contain `/` at the end). +## Build firmware -Configure the compiler for firmware building (parallel limits the number of CPU cores used - you can increase this if your machine can handle more): -``` +```bash cmake --build . --target arm-none-eabi-configure --parallel 4 +cmake --build . --target firmware ``` -Build the firmware! +The firmware binary is written to `build/arm-none-eabi/firmware.bin`. + +## Build Companion + +Firmware and Companion are separate targets. Companion requires Qt, SDL2, and SDL3 (see above). + +From the `build` directory: + +```bash +source ../.venv/bin/activate +export QTDIR=$(brew --prefix)/opt/qt@6 +export QT_PLUGIN_PATH=$QTDIR/plugins + +cmake --build . --target native-configure --parallel 4 +cmake --build . --target wasi-module --parallel 4 +mkdir -p native/plugins +cp wasm/edgetx-*-simulator.wasm native/plugins/ +cmake --build . --target companion --parallel 4 ``` -cmake --build . --target firmware + +The application bundle is created at `build/native/EdgeTX Companion 3.0.app` (the version number matches your EdgeTX release). + +## Run Companion from the build directory + +```bash +export QT_PLUGIN_PATH=$(brew --prefix)/opt/qt@6/plugins +open native/*.app ``` +For the radio simulator to work when running from the build tree, copy the WASM module into the bundle: + +```bash +cp wasm/edgetx-*-simulator.wasm native/*.app/Contents/MacOS/ +``` + +## Build a distributable `.dmg` + +The `package` target lives in the `native` build tree, **not** in the top-level `build` directory. + +```bash +mkdir -p native/plugins +cp wasm/edgetx-*-simulator.wasm native/plugins/ 2>/dev/null +cmake --build native --target package +``` + +The `.dmg` is written to `build/native/edgetx-companion-.dmg` (the version number matches your EdgeTX release). + +Open it and drag **EdgeTX Companion** into `/Applications`: + +```bash +open native/edgetx-companion-*.dmg +``` + +The packaging step bundles Qt, SDL2, and SDL3 into the `.app` automatically. + # Troubleshooting ## Notes on compiling simulator plug-ins When compiling simulator plug-ins (using `cmake --build . --target libsimulator` with the target properly configured), the product of this compilation will be a `.dylib` stored in your build directory. If you want the Companion or Simulator apps to be able to use it, you will need to copy it manually into the respective directories. Here is how it should look with a couple of plug-ins copied: -``` +```bash % ls -l companion.app/Contents/MacOS/ total 411976 -rwxr-xr-x 1 etx staff 27908864 Jan 7 11:48 companion @@ -140,7 +193,8 @@ total 392616 ## Notes on possible error while trying to run build-companion.sh If you encounter this error: -``` + +```bash CPack: - Install project: EdgeTX [] CMake Error at /opt/homebrew/Cellar/cmake/3.27.7/share/cmake/Modules/BundleUtilities.cmake:458 (message): otool -l failed: 1