DALL·E: A young blonde Korean woman standing outdoors in trendy, casual clothing, holding a MacBook Air in her hands in a natural, comfortable position. She wears round, pastel gold-framed glasses. The lighting is natural and realistic, resembling a social media photo. The background is modern and natural, with bright lighting that highlights the playful and relaxed atmosphere.
Note
This guide is temporarily outdated since the release of SFML 3.
In particular, the part about bundling frameworks is only applicable to SFML 2, as SFML 3 bundles all the required frameworks into the static library. It even bundles the audio on Windows, which was previously a separate DLL ❤️
I will try to update this guide for SFML 3 once I finish my bullet-hell/shoot-em-up SFML game. However, as a workaround, you can probably scrap the following:
INSTALL_RPATH "@executable_path/../Frameworks"
BUILD_WITH_INSTALL_RPATH TRUE
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory $<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks
COMMENT "Cleaning Frameworks directory"
)
# Copy all frameworks into the app bundle
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND rsync -a ${SFML_SOURCE_DIR}/extlibs/libs-osx/Frameworks/
$<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks/
COMMENT "Copying all SFML frameworks into the app bundle"
)
There is some difference in targets, e.g., sfml-main
became SFML::Main
.
As mentioned earlier, I will get back to this once I finish my game. I added this note to help you out in the meantime.
Introduction
The CMake SFML Project Template is a great starter template for creating cross-platform applications with SFML. Unfortunately, it doesn’t include a way to package the application for native macOS deployment, i.e., as an app bundle.
For simple command-line applications that are linked statically (set(BUILD_SHARED_LIBS OFF)
), you can use install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
to make the app install to /usr/local/bin
with sudo cmake --install .
. Unfortunately, if you install a SFML app this way, it will complain about missing dynamically-linked dependencies, such as FreeType. Moreover, the app must be started from the command line, which is not user-friendly.
GUI apps are typically distributed as an app bundle that users can drag and drop into their Applications
folder. App bundles are directories with a .app
extension that contain the app’s executable, resources, and metadata. For example, your SFML CMakeSFMLProject.app
app bundle might look like this:
[/Applications] $ tree -l CMakeSFMLProject.app/
CMakeSFMLProject.app/
└── Contents
├── Frameworks
│ └── freetype.framework
│ ├── Resources -> Versions/Current/Resources
│ │ └── Info.plist
│ ├── Versions
│ │ ├── A
│ │ │ ├── Resources
│ │ │ │ └── Info.plist
│ │ │ └── freetype
│ │ └── Current -> A [recursive, not followed]
│ └── freetype -> Versions/Current/freetype
├── Info.plist
└── MacOS
└── CMakeSFMLProject
Download the CMake SFML Project Template
Follow these steps to download the project template.
Clone the repository:
git clone https://github.com/SFML/cmake-sfml-project.git
Change directory to the cloned repository:
cd cmake-sfml-project
Change to the commit used in this tutorial:
This is for simplicity, use the latest commit when building your own app (i.e., don’t checkout to this specific commit).
git checkout 969c5cd70278bd7316742bd27d9ccd7a363196e5
Modify the CMakeLists.txt File
We only need to make a few changes to the CMakeLists.txt
file to package the app as an app bundle.
Original CMakeLists.txt
:
cmake_minimum_required(VERSION 3.28)
project(CMakeSFMLProject LANGUAGES CXX)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
include(FetchContent)
FetchContent_Declare(SFML
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG 2.6.x
GIT_SHALLOW ON
EXCLUDE_FROM_ALL
SYSTEM)
FetchContent_MakeAvailable(SFML)
add_executable(main src/main.cpp)
target_link_libraries(main PRIVATE sfml-graphics)
target_compile_features(main PRIVATE cxx_std_17)
if(WIN32)
add_custom_command(
TARGET main
COMMENT "Copy OpenAL DLL"
PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${SFML_SOURCE_DIR}/extlibs/bin/$<IF:$<EQUAL:${CMAKE_SIZEOF_VOID_P},8>,x64,x86>/openal32.dll $<TARGET_FILE_DIR:main>
VERBATIM)
endif()
Modified CMakeLists.txt
:
cmake_minimum_required(VERSION 3.28)
project(CMakeSFMLProject LANGUAGES CXX)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
include(FetchContent)
FetchContent_Declare(SFML
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG 2.6.x
GIT_SHALLOW ON
EXCLUDE_FROM_ALL
SYSTEM)
FetchContent_MakeAvailable(SFML)
add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE sfml-graphics)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
# For all platforms, add install targets
# If on macOS, bundle the executable into an app bundle
if(APPLE)
# Set variables for Info.plist
set(MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME}) # Short name
set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.yourname.${PROJECT_NAME}") # com.YOURCOMPANY.YOURAPP
set(MACOSX_BUNDLE_BUNDLE_VERSION "1.0")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "1.0")
# Generate the Info.plist file
configure_file(${CMAKE_SOURCE_DIR}/Info.plist.in ${CMAKE_BINARY_DIR}/Info.plist @ONLY)
# Set macOS-specific properties
set_target_properties(${PROJECT_NAME} PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_BINARY_DIR}/Info.plist
INSTALL_RPATH "@executable_path/../Frameworks"
BUILD_WITH_INSTALL_RPATH TRUE
)
# # Copy the icon into the app bundle
# add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
# COMMAND ${CMAKE_COMMAND} -E copy
# ${CMAKE_SOURCE_DIR}/Icon.icns
# $<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Resources/Icon.icns
# COMMENT "Copying icon to the app bundle"
# )
# Clean up the Frameworks directory before copying
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory $<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks
COMMENT "Cleaning Frameworks directory"
)
# Copy all frameworks into the app bundle
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND rsync -a ${SFML_SOURCE_DIR}/extlibs/libs-osx/Frameworks/
$<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks/
COMMENT "Copying all SFML frameworks into the app bundle"
)
# # Copy only the SFML freetype framework into the app bundle
# add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
# COMMAND rsync -a ${SFML_SOURCE_DIR}/extlibs/libs-osx/Frameworks/freetype.framework
# $<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks/
# COMMENT "Copying SFML freetype framework into the app bundle"
# )
# Add install target for macOS app bundle
install(TARGETS ${PROJECT_NAME} BUNDLE DESTINATION /Applications)
else()
# Add install target for regular executable
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
if(WIN32)
add_custom_command(
TARGET ${PROJECT_NAME}
COMMENT "Copy OpenAL DLL"
PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${SFML_SOURCE_DIR}/extlibs/bin/$<IF:$<EQUAL:${CMAKE_SIZEOF_VOID_P},8>,x64,x86>/openal32.dll $<TARGET_FILE_DIR:${PROJECT_NAME}>
VERBATIM)
endif()
We also need Info.plist.in
in the root directory to generate the Info.plist
file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Name of the executable file in the bundle. -->
<key>CFBundleExecutable</key>
<string>@MACOSX_BUNDLE_BUNDLE_NAME@</string>
<!-- Unique identifier for the application, typically in reverse DNS format. -->
<key>CFBundleIdentifier</key>
<string>@MACOSX_BUNDLE_GUI_IDENTIFIER@</string>
<!-- Short name of the bundle. This is the name that appears in the Finder and other parts of the macOS user interface. -->
<key>CFBundleName</key>
<string>@MACOSX_BUNDLE_BUNDLE_NAME@</string>
<!-- Version of the bundle. This is a string that represents the build version of the application. -->
<key>CFBundleVersion</key>
<string>@MACOSX_BUNDLE_BUNDLE_VERSION@</string>
<!-- Release version number of the bundle. This is a user-visible version number. -->
<key>CFBundleShortVersionString</key>
<string>@MACOSX_BUNDLE_SHORT_VERSION_STRING@</string>
<!-- Type of bundle. For applications, this is typically APPL. -->
<key>CFBundlePackageType</key>
<string>APPL</string>
<!-- Path to the application icon file -->
<!-- <key>CFBundleIconFile</key>
<string>Icon.icns</string> -->
</dict>
</plist>
[~/dev/cmake-sfml-project] $ tree
.
├── CMakeLists.txt
├── Info.plist.in
├── LICENSE.md
├── README.md
└── src
└── main.cpp
Ok, so what’s new?
We changed all instances of main
into ${PROJECT_NAME}
to make the output executable name match the project name. This is personal preference, you can put SET(EXECUTABLE_NAME "example")
somewhere below project()
and replace ${PROJECT_NAME}
with ${EXECUTABLE_NAME}
if you prefer.
cmake_minimum_required(VERSION 3.28)
project(CMakeSFMLProject LANGUAGES CXX)
set(EXECUTABLE_NAME "hello")
We added a conditional block that checks if the platform is macOS. If it is, we set the MACOSX_BUNDLE
property to TRUE
and provide the path to the Info.plist
file. We also set the INSTALL_RPATH
property to @executable_path/../Frameworks
to tell the app where to find the SFML frameworks. We then copy all the SFML frameworks into the app bundle using add_custom_command
. The rsync
command ensures that we preserve symlinks, so we don’t make unnecessary copies of the frameworks. Finally, we add an install target for the macOS app bundle.
Note: The uncommented # Copy all frameworks into the app bundle
block copies all frameworks (graphics, audio, etc.) into the app bundle. If you don’t use audio, you can comment out the # Copy all frameworks into the app bundle
block and uncomment the # Copy only the SFML freetype framework into the app bundle
block to copy only the freetype framework. This will greatly reduce the size of the app bundle. But if you’re just starting out, copying all frameworks is a good idea.
# Copy all frameworks into the app bundle
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND rsync -a ${SFML_SOURCE_DIR}/extlibs/libs-osx/Frameworks/
$<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks/
COMMENT "Copying all SFML frameworks into the app bundle"
)
# # Copy only the SFML freetype framework into the app bundle
# add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
# COMMAND rsync -a ${SFML_SOURCE_DIR}/extlibs/libs-osx/Frameworks/freetype.framework
# $<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks/
# COMMENT "Copying SFML freetype framework into the app bundle"
# )
# # Copy all frameworks into the app bundle
# add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
# COMMAND rsync -a ${SFML_SOURCE_DIR}/extlibs/libs-osx/Frameworks/
# $<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks/
# COMMENT "Copying all SFML frameworks into the app bundle"
# )
# Copy only the SFML freetype framework into the app bundle
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND rsync -a ${SFML_SOURCE_DIR}/extlibs/libs-osx/Frameworks/freetype.framework
$<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Frameworks/
COMMENT "Copying SFML freetype framework into the app bundle"
)
Building the App Bundle
Follow these steps to build the project:
Generate the build system:
mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release
Compile the project:
To compile the project, use the following command:
cmake --build . --parallel
Now the app is compiled.
[~/dev/cmake-sfml-project/build] $ tree -L2
.
├── CMakeCache.txt
├── CMakeFiles
│ ├── 3.30.4
│ ├── CMakeConfigureLog.yaml
│ ├── CMakeDirectoryInformation.cmake
│ ├── CMakeSFMLProject.dir
│ ├── CMakeScratch
│ ├── Makefile.cmake
│ ├── Makefile2
│ ├── TargetDirectories.txt
│ ├── cmake.check_cache
│ ├── pkgRedirects
│ └── progress.marks
├── CPackConfig.cmake
├── CPackSourceConfig.cmake
├── Info.plist
├── Makefile
├── _deps
│ ├── sfml-build
│ ├── sfml-src
│ └── sfml-subbuild
├── bin
│ └── CMakeSFMLProject.app
└── cmake_install.cmake
As you can see, the Info.plist
file was generated based on the variables we set in the CMakeLists.txt
file. We now also have an app bundle in the bin
directory.
Let’s run it. You can either double-click the app bundle in Finder (bin/CMakeSFMLProject.app
) or run it from the command line:
open bin/CMakeSFMLProject.app
Installing the App Bundle
To install the app bundle to /Applications
, use the following command while in the build
directory:
sudo cmake --install .
We can now find the app in /Applications
:
[/Applications] $ tree -l CMakeSFMLProject.app/
CMakeSFMLProject.app/
└── Contents
├── Frameworks
│ └── freetype.framework
│ ├── Resources -> Versions/Current/Resources
│ │ └── Info.plist
│ ├── Versions
│ │ ├── A
│ │ │ ├── Resources
│ │ │ │ └── Info.plist
│ │ │ └── freetype
│ │ └── Current -> A [recursive, not followed]
│ └── freetype -> Versions/Current/freetype
├── Info.plist
└── MacOS
└── CMakeSFMLProject
As you can see, I only copied the freetype framework into the app bundle.
Cross-platform CI/CD
Building the app on your local machine is fine. However, Github Actions can build and package your app for macOS, GNU/Linux, and Windows, all at the same time. Setting up a CI/CD pipeline is pretty easy if you’re already using a cross-platform build system like CMake.
The CMake SFML Project Template already includes a simple .github/workflows/ci.yml
file, but we can extend it a bit.
Original .github/workflows/ci.yml
:
name: CI
on: [push, pull_request]
defaults:
run:
shell: bash
jobs:
build:
name: ${{ matrix.platform.name }} ${{ matrix.config.name }}
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false
matrix:
platform:
- { name: Windows VS2019, os: windows-2019 }
- { name: Windows VS2022, os: windows-2022 }
- { name: Linux GCC, os: ubuntu-latest }
- { name: Linux Clang, os: ubuntu-latest, flags: -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ }
- { name: macOS, os: macos-latest }
config:
- { name: Shared, flags: -DBUILD_SHARED_LIBS=TRUE }
- { name: Static, flags: -DBUILD_SHARED_LIBS=FALSE }
steps:
- name: Install Linux Dependencies
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install libxrandr-dev libxcursor-dev libudev-dev libopenal-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev
- name: Checkout
uses: actions/checkout@v4
- name: Configure
run: cmake -B build ${{matrix.platform.flags}} ${{matrix.config.flags}}
- name: Build
run: cmake --build build --config Release
Modified .github/workflows/ci.yml
:
# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform.
# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml
name: CI
on:
push:
branches:
- main
paths-ignore:
- "LICENSE"
- "README.md"
pull_request:
branches:
- main
paths-ignore:
- "LICENSE"
- "README.md"
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
# If true, cancel the workflow run if any matrix job fails.
# If false, continue to run the workflow and complete all matrix jobs, even if one or more jobs fail.
fail-fast: false
matrix:
include:
- os: macos-latest
build_type: Release
# c_compiler: clang
cpp_compiler: clang++
- os: ubuntu-latest
build_type: Release
# c_compiler: gcc
cpp_compiler: g++
- os: windows-latest
build_type: Release
# c_compiler: cl
cpp_compiler: cl
steps:
- uses: actions/checkout@v4
- name: Set reusable strings
# Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
id: strings
shell: bash
run: |
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
- name: Cache CMake build directory
uses: actions/cache@v4
with:
path: ${{ steps.strings.outputs.build-output-dir }}
key: ${{ runner.os }}-build-${{ hashFiles('CMakeLists.txt') }}-${{ hashFiles('cmake/**') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install GNU/Linux dependencies
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install libxrandr-dev libxcursor-dev libudev-dev libopenal-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
# Set "-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}" for C/C++ projects, otherwise use CXX for C++ only projects.
run: >
cmake -B ${{ steps.strings.outputs.build-output-dir }}
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
-S ${{ github.workspace }}
- name: Build
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release --parallel
I have based my CI setup on the CMake multi-platform starter workflow. I have also added caching for the build
directory, as we’re building SFML from source, which normally takes ages. I also removed the BUILD_SHARED_LIBS
option, compile flags and other stuff that I’d typically set in the CMakeLists.txt
file (refer to Final Thoughts for a working example).
Now let’s create a release action that will package the app bundle for all platforms - .github/workflows/release.yml
.
name: Release
on:
release:
types: [created]
permissions:
contents: write
jobs:
build-and-upload:
runs-on: ${{ matrix.os }}
strategy:
# If true, cancel the workflow run if any matrix job fails.
# If false, continue to run the workflow and complete all matrix jobs, even if one or more jobs fail.
fail-fast: true
matrix:
include:
- os: macos-latest
# c_compiler: clang
cpp_compiler: clang++
input_name: bin/CMakeSFMLProject.app
output_name: CMakeSFMLProject-macos-arm64.app
archive_name: CMakeSFMLProject-macos-arm64.tar.gz
archive_type: tar
- os: ubuntu-latest
# c_compiler: gcc
cpp_compiler: g++
input_name: bin/CMakeSFMLProject
output_name: CMakeSFMLProject-linux-x86_64
archive_name: CMakeSFMLProject-linux-x86_64.tar.gz
archive_type: tar
- os: windows-latest
# c_compiler: cl
cpp_compiler: cl
input_name: bin/Release/CMakeSFMLProject.exe
output_name: CMakeSFMLProject-windows-x86_64.exe
archive_name: CMakeSFMLProject-windows-x86_64.zip
archive_type: zip
steps:
- uses: actions/checkout@v4
- name: Set reusable strings
# Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
id: strings
shell: bash
run: |
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
- name: Cache CMake build directory
uses: actions/cache@v4
with:
path: ${{ steps.strings.outputs.build-output-dir }}
key: ${{ runner.os }}-build-${{ hashFiles('CMakeLists.txt') }}-${{ hashFiles('cmake/**') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install GNU/Linux dependencies
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install libxrandr-dev libxcursor-dev libudev-dev libopenal-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
# Set the project version to the tag name instead of git commit.
# Set "-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}" for C/C++ projects, otherwise use CXX for C++ only projects.
run: >
cmake -B ${{ steps.strings.outputs.build-output-dir }}
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
-DCMAKE_BUILD_TYPE=Release
-DPROJECT_VERSION="${{ github.ref_name }}"
-S ${{ github.workspace }}
- name: Build
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release --parallel
- name: Rename binary
# Rename the binary to match the platform.
working-directory: ${{ steps.strings.outputs.build-output-dir }}
shell: bash
run: |
echo "Renaming '${{ matrix.input_name }}' to '${{ matrix.output_name }}'"
mv "${{ matrix.input_name }}" "${{ matrix.output_name }}"
- name: Archive binary
uses: thedoctor0/zip-release@0.7.6
with:
type: ${{ matrix.archive_type }}
filename: "${{ matrix.archive_name }}"
directory: ${{ steps.strings.outputs.build-output-dir }}
path: ${{ matrix.output_name }}
- name: Release
# Upload the binary to the release page.
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: ${{ steps.strings.outputs.build-output-dir }}/${{ matrix.archive_name }}
This workflow will build the app, rename it to append the platform name and architecture, then archive it and upload it to the release page. I have hardcoded the names, because inferring them from the project name is a bit complicated for something you only need to setup once.
To trigger it, go to your project’s releases page and click Draft a new release
. Then, create a new git tag (e.g., v0.0.1
) and click Publish release
. The workflow will start automatically, and the packaged binaries for macOS, GNU/Linux and Windows will be uploaded automatically.
Notably, the app bundle for macOS must be a .tar.gz
archive, because the .zip
archive will not preserve the symlinks in the app bundle. Instead, each symlink will become a copy, resulting in a bloated app bundle (for freetype
, this is around +5 MB of useless copies).
Final Thoughts
Packaging an SFML app as an app bundle on macOS is relatively straightforward. You just need to copy the necessary frameworks into the app bundle.
If you want to see a working example, check out my aegyo project. It’s cross-platform SFML app for learning Korean Hangul.