Skip to content

Commit 799ce05

Browse files
committed
Add signing/notarization steps to MacOS packaging (#1106)
1 parent cffc29e commit 799ce05

18 files changed

+281
-109
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## [Upcoming Release]
88

9+
- The MacOS DMG installers should now be signed and notarized by Adam Kewley, which should make
10+
installing OpenSim Creator considerably easier on MacOS (#1106).
911

1012
## [0.6.0] - 2025/09/08
1113

docs/source/installation.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ Installing on MacOS (Sonoma 14.5 or newer)
3030

3131
.. warning::
3232

33-
OpenSim Creator's build process does not sign (notarize) its binaries, because
34-
we'd have to organize and pay a subscription for that service.
33+
**>= v0.6.0**: OpenSim Creator now codesigns and notarizes its binaries, so
34+
installation should be as easy as dragging and dropping the application.
3535

36-
For you, this means that OpenSim Creator requires extra steps that depend on
37-
your version of MacOS (Apple changes the procedure regularly - $$$).
36+
**< 0.6.0**, OpenSim Creator's build process did not sign (notarize) its
37+
binaries, because it requires organizing and paying for an Apple Developer
38+
account. Unsigned binaries can require additional installation steps:
3839

3940
**On Sequoia**: open a terminal and run ``xattr -cr /path/to/opensimcreator.dmg`` to
4041
clear any quarantine flags that MacOS added when the dmg was downloaded. Mount the

docs/source/the-release-process.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ Creator, it's usually copied into a GitHub issue:
3838
- [ ] Download release artifacts from the tagged commit CI build
3939
- [ ] Also, create a source tarball with `git archive --format=tar.xz --prefix=opensimcreator-${VERSION}/ -o opensimcreator-${VERSION}-src.tar.xz $VERSION` (or, on MacOS: git archive --format=tar --prefix=opensimcreator-${VERSION}/ $VERSION | xz > opensimcreator-${VERSION}-src.tar.xz)
4040
- [ ] You might need to configure `.tar.xz` support with `git config tar.tar.xz.command "xz -c"`
41+
- [ ] For MacOS, the release must be built on a developer's machine, and the developer should configure the build with codesigning+notarization and upload
42+
the signed+notarized binaries instead. See OSC_CODESIGN_ENABLED and OSC_NOTARIZATION_ENABLED flags in the
43+
MacOS packaging. Adam Kewley specifically has the CMake flags, password, keys etc. necessary to do this.
4144
- [ ] Unzip/rename any artifacts (see prev. releases)
4245
- [ ] Create new release on github from the tagged commit
4346
- [ ] Upload all artifacts against it

liboscar/Platform/App.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,13 @@ namespace
471471
return rv;
472472
}
473473

474+
std::filesystem::path get_current_resources_path_and_log_it(const AppSettings& settings)
475+
{
476+
auto rv = get_resource_dir_from_settings(settings);
477+
log_info("resource directory: %s", rv.string().c_str());
478+
return rv;
479+
}
480+
474481
// computes the user's data directory and also logs it to the console for user-facing feedback
475482
std::filesystem::path get_current_user_dir_and_log_it(
476483
std::string_view organization_name,
@@ -1619,7 +1626,7 @@ class osc::App::Impl final {
16191626

16201627
// initialization-time resources dir (so that it doesn't have to be fetched
16211628
// from the settings over-and-over)
1622-
std::filesystem::path resources_dir_ = get_resource_dir_from_settings(config_);
1629+
std::filesystem::path resources_dir_ = get_current_resources_path_and_log_it(config_);
16231630

16241631
// path to the directory that the application's executable is contained within
16251632
std::filesystem::path executable_dir_ = get_current_exe_dir_and_log_it();

liboscar/Platform/AppSettings.cpp

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -113,32 +113,18 @@ R"(# configuration options
113113
std::optional<std::filesystem::path> try_get_system_config_path(
114114
std::string_view application_config_file_name)
115115
{
116-
// copied from the legacy `AppConfig` implementation for backwards
117-
// compatibility with existing settings files
118-
119-
std::filesystem::path p = current_executable_directory();
120-
bool exists = false;
121-
122-
while (p.has_filename()) {
123-
const std::filesystem::path maybe_config = p / application_config_file_name;
124-
if (std::filesystem::exists(maybe_config)) {
125-
p = maybe_config;
126-
exists = true;
127-
break;
116+
for (std::filesystem::path p = current_executable_directory(); p != p.root_path(); p = p.parent_path()) {
117+
if (auto dir_path = p / application_config_file_name; std::filesystem::exists(dir_path)) {
118+
return dir_path; // e.g. `C:/Program Files/App/config.toml`
128119
}
129-
130-
// HACK: there is a file at "MacOS/$configName", which is where the settings
131-
// is relative to `current_executable_directory`.
132-
const std::filesystem::path maybe_macos_config = p / "MacOS" / application_config_file_name;
133-
if (std::filesystem::exists(maybe_macos_config)) {
134-
p = maybe_macos_config;
135-
exists = true;
136-
break;
120+
else if (auto resources_path_capped = p / "Resources" / application_config_file_name; std::filesystem::exists(resources_path_capped)) {
121+
return resources_path_capped; // e.g. `/Applications/App.app/Resources/config.toml`
122+
}
123+
else if (auto resources_path = p / "resources" / application_config_file_name; std::filesystem::exists(resources_path)) {
124+
return resources_path; // e.g. `/opt/app/resources/config.toml`
137125
}
138-
p = p.parent_path();
139126
}
140-
141-
return exists ? p : std::optional<std::filesystem::path>{};
127+
return std::nullopt;
142128
}
143129

144130
// if available, returns the path to the user-level configuration file

osc/CMakeLists.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,15 @@ include(${OSC_PACKAGING_DIR}/Packaging.cmake)
186186
#
187187
# - this config is switched out at install-time to a configuration that loads
188188
# resources from the (copied) resource directory
189-
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/osc_dev_config.toml.in" "${CMAKE_BINARY_DIR}/osc.toml")
189+
if(TRUE)
190+
set(OSC_CONFIG_RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../resources")
191+
configure_file(
192+
"${CMAKE_CURRENT_SOURCE_DIR}/osc.toml.in"
193+
"${CMAKE_BINARY_DIR}/osc.toml" # CARE: this MUST be CMAKE_BINARY_DIR: multiple executables depend on it
194+
@ONLY
195+
)
196+
unset(OSC_CONFIG_RESOURCES_DIR)
197+
endif()
190198

191199
# for development on Windows, copy all runtime dlls to the exe directory
192200
# (because Windows doesn't have an RPATH)

osc/Debian/Packaging.cmake

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,20 @@ install(
3939
#
4040
# - in contrast to the dev-centric one, this loads resources from the installation dir,
4141
# which has a known path relative to the osc executable (../resources)
42-
install(
43-
FILES "${CMAKE_CURRENT_SOURCE_DIR}/osc_installed_config.toml.in"
44-
RENAME "osc.toml"
45-
DESTINATION "."
46-
)
42+
if(TRUE)
43+
set(OSC_CONFIG_RESOURCES_DIR "resources") # relative to `osc.toml`
44+
configure_file(
45+
"${CMAKE_CURRENT_SOURCE_DIR}/osc.toml.in"
46+
"${CMAKE_CURRENT_BINARY_DIR}/generated/osc_debian.toml"
47+
@ONLY
48+
)
49+
unset(OSC_CONFIG_RESOURCES_DIR)
50+
install(
51+
FILES "${CMAKE_CURRENT_BINARY_DIR}/generated/osc_debian.toml"
52+
RENAME "osc.toml"
53+
DESTINATION "."
54+
)
55+
endif()
4756

4857
# install-time: copy `resources/` (assets) dir
4958
install(

osc/MacOS/Info.plist

Lines changed: 0 additions & 26 deletions
This file was deleted.

osc/MacOS/Info.plist.in

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleName</key>
6+
<string>osc</string>
7+
8+
<key>CFBundleDisplayName</key>
9+
<string>OpenSim Creator (osc)</string>
10+
11+
<key>CFBundleIdentifier</key>
12+
<string>com.opensimcreator.gui</string>
13+
14+
<!-- version/build number: should be unique and monotonically increase -->
15+
<key>CFBundleVersion</key>
16+
<string>@PROJECT_VERSION@</string>
17+
18+
<!-- What the user sees: can be duplicated -->
19+
<key>CFBundleShortVersionString</key>
20+
<string>@PROJECT_VERSION@</string>
21+
22+
<key>CFBundleExecutable</key>
23+
<string>osc</string>
24+
25+
<key>CFBundleIconFile</key>
26+
<string>osc</string>
27+
28+
<!-- liboscar natively supports HighDPI -->
29+
<key>NSHighResolutionCapable</key>
30+
<true/>
31+
32+
<!-- Rarely changes -->
33+
<key>CFBundleInfoDictionaryVersion</key>
34+
<string>6.0</string>
35+
<!-- Rarely changes -->
36+
<key>CFBundlePackageType</key>
37+
<string>APPL</string>
38+
<!-- Rarely changes -->
39+
<key>LSApplicationCategoryType</key>
40+
<string>public.app-category.utilities</string>
41+
</dict>
42+
</plist>

osc/MacOS/Packaging.cmake

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
#
33
# - Create a DMG (archive) installer that packages the whole application +
44
# libraries into a single directory tree that can be copied to
5-
# /Applications/osc
5+
# /Applications/osc.app
6+
7+
option(OSC_CODESIGN_ENABLED "Enable codesigning the resulting app bundle and DMG file" OFF)
8+
option(OSC_NOTARIZATION_ENABLED "Enable notarizing (xcrun notarytool) the resulting DMG file" OFF)
69

710
# set RPATH of `osc` on mac to @executable_path/lib, because dir structure is:
811
#
@@ -20,32 +23,49 @@ install(
2023
DESTINATION osc.app/Contents/MacOS/
2124
)
2225

23-
# install-time: install a user-facing `osc.toml` config file
24-
#
25-
# - in contrast to the dev-centric one, this loads resources from the installation dir,
26-
# which has a known path relative to the osc executable (../resources)
27-
install(
28-
FILES "${CMAKE_CURRENT_SOURCE_DIR}/osc_installed_config.toml.in"
29-
RENAME "osc.toml"
30-
DESTINATION osc.app/Contents/MacOS/
26+
# generate `Info.plist` file
27+
configure_file(
28+
"${CMAKE_CURRENT_SOURCE_DIR}/MacOS/Info.plist.in"
29+
"${CMAKE_CURRENT_BINARY_DIR}/generated/Info.plist"
30+
@ONLY
3131
)
3232

33-
# install-time: install an `Info.plist` file
33+
# install-time: install the generated `Info.plist` file
3434
#
3535
# it's mac-specific XML file that tells Mac OSX about where the
3636
# executable is, what the icon is, etc.
3737
install(
38-
FILES "${CMAKE_CURRENT_SOURCE_DIR}/MacOS/Info.plist"
38+
FILES "${CMAKE_CURRENT_BINARY_DIR}/generated/Info.plist"
3939
DESTINATION osc.app/Contents/
4040
)
4141

42+
# install-time: install a user-facing `osc.toml` config file
43+
#
44+
# - in contrast to the dev-centric one, this loads resources from the `osc.app/Resources/` dir,
45+
# which has a known path relative to the osc executable (../Resources/osc.toml)
46+
if(TRUE)
47+
set(OSC_CONFIG_RESOURCES_DIR ".") # relative to `osc.toml`
48+
configure_file(
49+
"${CMAKE_CURRENT_SOURCE_DIR}/osc.toml.in"
50+
"${CMAKE_CURRENT_BINARY_DIR}/generated/osc_macos.toml"
51+
@ONLY
52+
)
53+
unset(OSC_CONFIG_RESOURCES_DIR)
54+
55+
install(
56+
FILES "${CMAKE_CURRENT_BINARY_DIR}/generated/osc_macos.toml"
57+
RENAME "osc.toml"
58+
DESTINATION osc.app/Contents/Resources
59+
)
60+
endif()
61+
4262
# install-time: copy `resources/` (assets) dir
4363
install(
4464
DIRECTORY
4565
"${PROJECT_SOURCE_DIR}/resources/OpenSimCreator"
4666
"$<$<BOOL:${OSC_BUNDLE_OSCAR_DEMOS}>:${PROJECT_SOURCE_DIR}/resources/oscar_demos>"
4767
DESTINATION
48-
osc.app/Contents/MacOS/resources/
68+
osc.app/Contents/Resources
4969
)
5070

5171
# install-time: copy the Mac-specific desktop icon (.icns)
@@ -67,7 +87,54 @@ else()
6787
unset(OSC_ARCH_LOWERCASE)
6888
endif()
6989

90+
# use a DMG drag-and-drop packaging method
7091
set(CPACK_GENERATOR DragNDrop)
7192

93+
# Handle code signing (notarization is separate - below)
94+
if(OSC_CODESIGN_ENABLED)
95+
set(OSC_CODESIGN_DEVELOPER_ID "" CACHE STRING "The developer ID string for the codesigning key (e.g. 'Developer ID Application: Some Developer (XYA12398BF)')")
96+
97+
# Generate required codesigning scripts
98+
configure_file(
99+
"${CMAKE_CURRENT_SOURCE_DIR}/MacOS/codesign_osc.app.cmake.in"
100+
"${CMAKE_CURRENT_BINARY_DIR}/generated/codesign_osc.app.cmake"
101+
@ONLY
102+
)
103+
configure_file(
104+
"${CMAKE_CURRENT_SOURCE_DIR}/MacOS/codesign_osc.dmg.cmake.in"
105+
"${CMAKE_CURRENT_BINARY_DIR}/generated/codesign_osc.dmg.cmake"
106+
@ONLY
107+
)
108+
109+
# Make CPack run the generated `codesign` script on the `osc.app` bundle that CPack
110+
# temporarily creates *before* it packages the bundle into the dmg...
111+
set(CPACK_PRE_BUILD_SCRIPTS "${CMAKE_CURRENT_BINARY_DIR}/generated/codesign_osc.app.cmake")
112+
113+
# ... afterwards, make CPack run `codesign` on the resulting DMG file
114+
set(CPACK_POST_BUILD_SCRIPTS "${CMAKE_CURRENT_BINARY_DIR}/generated/codesign_osc.dmg.cmake")
115+
endif()
116+
117+
# If notarizing, assert that codesigning is enabled (you can only notarize signed code)
118+
# and run notarization on the output DMG
119+
if(OSC_NOTARIZATION_ENABLED)
120+
if(NOT OSC_CODESIGN_ENABLED)
121+
message(FATAL_ERROR "OSC_NOTARIZATION_ENABLED is ${OSC_NOTARIZATION_ENABLED} but OSC_CODESIGN_ENABLED is ${OSC_CODESIGN_ENABLED}: notarization requires code signing to be enabled")
122+
endif()
123+
124+
set(OSC_NOTARIZATION_APPLE_ID "" CACHE STRING "Apple ID of the signer (e.g. '[email protected]')")
125+
set(OSC_NOTARIZATION_TEAM_ID "" CACHE STRING "Team ID of the signer (usually, random-looking string at the end of OSC_CODESIGN_DEVELOPER_ID)")
126+
set(OSC_NOTARIZATION_PASSWORD "" CACHE STRING "App-specific password of the signer (you can create this from your apple ID account)")
127+
128+
# Generate required notarization script
129+
configure_file(
130+
"${CMAKE_CURRENT_SOURCE_DIR}/MacOS/notarize_osc.dmg.cmake.in"
131+
"${CMAKE_CURRENT_BINARY_DIR}/generated/notarize_osc.dmg.cmake"
132+
@ONLY
133+
)
134+
135+
# Make CPack run the generated notarization script after the DMG is created
136+
list(APPEND CPACK_POST_BUILD_SCRIPTS "${CMAKE_CURRENT_BINARY_DIR}/generated/notarize_osc.dmg.cmake")
137+
endif()
138+
72139
# CPack vars etc. now fully configured, so include it
73140
include(CPack)

0 commit comments

Comments
 (0)