Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 191 additions & 41 deletions .github/workflows/build.yml

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(Version)
GetVersionInformation(CONTOUR_VERSION CONTOUR_VERSION_STRING)
file(WRITE "${CMAKE_BINARY_DIR}/version.txt" "${CONTOUR_VERSION_STRING}")

# Before project(), which is where CMake consumes it. This is a property of the code, not
# of any one build configuration, so it lives here rather than being restated in every
# preset and CI job: 13.3 is the first macOS whose libc++ exports the floating-point
# std::to_chars that <format> needs, and we use std::format throughout. Building against
# an older target fails to compile with "'to_chars' has been explicitly marked unavailable".
if(APPLE AND NOT CMAKE_OSX_DEPLOYMENT_TARGET)
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.3" CACHE STRING "Minimum macOS version to build for")
endif()

project(contour VERSION "${CONTOUR_VERSION}" LANGUAGES CXX C)

# setting defaults
Expand Down
59 changes: 59 additions & 0 deletions cmake/MacOSNotarizeApp.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# SPDX-License-Identifier: Apache-2.0
#
# CPACK_PRE_BUILD_SCRIPTS hook: notarize and staple the staged contour.app before
# CPack hands the directory to hdiutil.
#
# Why here and not after the .dmg is built: stapling attaches the notarization ticket
# to the artifact itself. A ticket on the disk image alone covers the download, but the
# app the user drags to /Applications carries none, so its first launch depends on
# Gatekeeper reaching Apple over the network. Stapling the app *inside* the image makes
# the installed app self-sufficient. The image gets its own ticket in MacOSSignDmg.cmake.
#
# That costs a second round trip to Apple (a minute or more each), which only pays for
# itself on something people download, so it is gated on CONTOUR_MACOS_STAPLE_APP
# rather than on notarization as a whole.
#
# Variables come from the CPack config (see the APPLE branch of src/contour/CMakeLists.txt).

if(NOT CPACK_CONTOUR_STAPLE_APP)
return()
endif()

# Only the disk image carries an app bundle. Without this guard, `cpack -G TGZ` on macOS
# would submit whatever it staged to Apple.
if(NOT CPACK_GENERATOR STREQUAL "DragNDrop")
return()
endif()

# CPack stages the bundle one directory deeper than the obvious path: this project defines
# an install component, so the layout is <staging>/ALL_IN_ONE/contour.app rather than
# <staging>/contour.app. Both are checked, because that is a CPack implementation detail
# and not something worth breaking a release over.
set(_candidates "")
foreach(_pattern "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/contour.app"
"${CPACK_TEMPORARY_INSTALL_DIRECTORY}/*/contour.app")
file(GLOB _found "${_pattern}")
list(APPEND _candidates ${_found})
endforeach()
list(REMOVE_DUPLICATES _candidates)
list(LENGTH _candidates _count)

if(_count EQUAL 0)
message(FATAL_ERROR
"MacOSNotarizeApp: no contour.app found under "
"${CPACK_TEMPORARY_INSTALL_DIRECTORY} (searched it and its immediate "
"subdirectories). The app is notarized before hdiutil wraps it, so this has to "
"run after the install step has staged the bundle.")
elseif(_count GREATER 1)
message(FATAL_ERROR
"MacOSNotarizeApp: ambiguous staging layout, found several bundles: ${_candidates}")
endif()

list(GET _candidates 0 _app)

message(STATUS "Notarizing ${_app}")
execute_process(
COMMAND "${CPACK_CONTOUR_PYTHON}" "${CPACK_CONTOUR_BUNDLE_SCRIPT}" notarize "${_app}"
--keychain-profile "${CPACK_CONTOUR_NOTARY_PROFILE}"
COMMAND_ERROR_IS_FATAL ANY
)
40 changes: 40 additions & 0 deletions cmake/MacOSSignDmg.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# SPDX-License-Identifier: Apache-2.0
#
# CPACK_POST_BUILD_SCRIPTS hook: sign the finished disk image, then notarize and staple it.
#
# The signature the install step applies covers contour.app only. CPack wraps that bundle
# into a .dmg afterwards, and the image is a separate signable object -- an unsigned one
# is what Gatekeeper reports as "damaged" or "from an unidentified developer" the moment
# the download's quarantine attribute is evaluated.
#
# Variables come from the CPack config (see the APPLE branch of src/contour/CMakeLists.txt).
# CPACK_PACKAGE_FILES is set by CPack itself and lists the artifacts just produced.

foreach(_package IN LISTS CPACK_PACKAGE_FILES)
if(NOT _package MATCHES "\\.dmg$")
continue()
endif()

message(STATUS "Signing ${_package}")
execute_process(
COMMAND "${CPACK_CONTOUR_PYTHON}" "${CPACK_CONTOUR_BUNDLE_SCRIPT}" sign "${_package}"
--identity "${CPACK_CONTOUR_CODE_SIGN_IDENTITY}"
COMMAND_ERROR_IS_FATAL ANY
)

if(CPACK_CONTOUR_NOTARIZE)
message(STATUS "Notarizing ${_package}")
execute_process(
COMMAND "${CPACK_CONTOUR_PYTHON}" "${CPACK_CONTOUR_BUNDLE_SCRIPT}" notarize "${_package}"
--keychain-profile "${CPACK_CONTOUR_NOTARY_PROFILE}"
COMMAND_ERROR_IS_FATAL ANY
)

# The check a user's machine performs, run here so a broken image cannot reach a
# release: -t install is the assessment Gatekeeper applies to a downloaded archive.
execute_process(
COMMAND spctl --assess --verbose=4 --type install "${_package}"
COMMAND_ERROR_IS_FATAL ANY
)
endif()
endforeach()
38 changes: 0 additions & 38 deletions cmake/Modules/MacOSXBundleInfo.plist.in

This file was deleted.

60 changes: 56 additions & 4 deletions cmake/presets/os-macos.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@
"common.json"
],
"configurePresets": [
{
"name": "macos-signing",
"hidden": true,
"description": "The project's macOS release identity, defined once.",
"cacheVariables": {
"CODE_SIGN_CERTIFICATE_ID": "Developer ID Application: Christian Parpart (6T525MU9UR)"
}
},
{
"name": "appleclang-debug",
"displayName": "Clang Debug",
"inherits": [
"contour-common",
"macos-signing",
"debug"
],
"generator": "Ninja",
Expand All @@ -19,7 +28,6 @@
},
"cacheVariables": {
"LIBTERMINAL_BUILD_BENCH_HEADLESS": "OFF",
"CODE_SIGN_CERTIFICATE_ID": "Developer ID Application: Christian Parpart (6T525MU9UR)",
"CMAKE_PREFIX_PATH": "/opt/homebrew/opt/qt"
}
},
Expand All @@ -28,6 +36,7 @@
"displayName": "Clang Release",
"inherits": [
"contour-common",
"macos-signing",
"release"
],
"generator": "Ninja",
Expand All @@ -38,9 +47,36 @@
},
"cacheVariables": {
"LIBTERMINAL_BUILD_BENCH_HEADLESS": "OFF",
"CODE_SIGN_CERTIFICATE_ID": "Developer ID Application: Christian Parpart (6T525MU9UR)",
"CMAKE_PREFIX_PATH": "/opt/homebrew/opt/qt"
}
},
{
"name": "macos-package",
"displayName": "Clang Release (distributable .dmg)",
"description": "The one configuration that produces a shippable .dmg. CI uses this too.",
"inherits": [
"contour-common",
"macos-signing",
"release"
],
"generator": "Ninja",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"cacheVariables": {
"CONTOUR_INSTALL_TOOLS": "OFF",
"LIBTERMINAL_BUILD_BENCH_HEADLESS": "OFF",
"CONTOUR_MACOS_NOTARIZE": "ON",
"CONTOUR_MACOS_STAPLE_APP": "ON",
"CONTOUR_MACOS_NOTARY_PROFILE": "contour-notary",
"CONTOUR_MACOS_MIN_SUPPORTED": "13.3",
"CMAKE_PREFIX_PATH": "$env{QT_ROOT_DIR}",
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"VCPKG_TARGET_TRIPLET": "arm64-osx-contour",
"VCPKG_OVERLAY_TRIPLETS": "${sourceDir}/cmake/vcpkg-triplets"
}
}
],
"buildPresets": [
Expand All @@ -53,6 +89,11 @@
"name": "appleclang-release",
"displayName": "AppleClang - Release",
"configurePreset": "appleclang-release"
},
{
"name": "macos-package",
"displayName": "AppleClang - Release (distributable .dmg)",
"configurePreset": "macos-package"
}
],
"testPresets": [
Expand All @@ -66,12 +107,23 @@
"noTestsAction": "error",
"stopOnFailure": true
}
},
{
"name": "macos-package",
"configurePreset": "macos-package",
"output": {
"outputOnFailure": true
},
"execution": {
"noTestsAction": "error",
"stopOnFailure": true
}
}
],
"packagePresets": [
{
"name": "appleclang-release",
"configurePreset": "appleclang-release",
"name": "macos-package",
"configurePreset": "macos-package",
"generators": [
"DragNDrop"
]
Expand Down
29 changes: 29 additions & 0 deletions cmake/vcpkg-triplets/arm64-osx-contour.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: Apache-2.0
#
# vcpkg triplet for Contour's macOS release packaging.
#
# The point of this file is VCPKG_OSX_DEPLOYMENT_TARGET. Homebrew ships one prebuilt
# bottle per macOS release and installs the one matching the build machine, so a bundle
# assembled from Homebrew dylibs inherits the *builder's* macOS as its minimum -- macOS 26
# on a developer's up-to-date Mac. Building the same libraries from source against a
# pinned deployment target puts the floor where we decide, and keeps it there no matter
# who builds. `scripts/macos-bundle.py verify --max-minimum-os` enforces the result.
#
# @see docs/internals/macos-code-signing.md

set(VCPKG_TARGET_ARCHITECTURE arm64)
set(VCPKG_CRT_LINKAGE dynamic)

# Dynamic, deliberately. Static linkage would make the floor a non-issue and shrink the
# bundle, but cairo is LGPL-2.1/MPL: statically linking it into an Apache-2.0 binary we
# distribute would oblige us to ship relinkable objects. Dynamic linking sidesteps that
# entirely, and the deployment target below already solves the floor.
set(VCPKG_LIBRARY_LINKAGE dynamic)

set(VCPKG_CMAKE_SYSTEM_NAME Darwin)

# Must stay in sync with CMAKE_OSX_DEPLOYMENT_TARGET in the top-level CMakeLists.txt --
# 13.3 is the first macOS whose libc++ exports the floating-point std::to_chars that
# <format> needs.
set(VCPKG_OSX_DEPLOYMENT_TARGET 13.3)
set(VCPKG_OSX_ARCHITECTURES arm64)
Loading
Loading