Skip to content

Commit 6fa026e

Browse files
committed
src/cmakelists: add targets to build macos bundles
Add the target memento_bundle to build a bundle for Memento. This changes the option MEMENTO_APPBUNDLE option to MEMENTO_BUNDLE. It also makes MEMENTO_CERT a CMake option called MEMENTO_CODESIGN_IDENTITY. If all this is set, an macOS bundle is created. This approach is better than Memento 1.0's bundle code since it was very brittle. It is slow as hell though. This was primarily generated by Codex. Fixes an issue where SYSTEM_QCORO was used in some targets instead of MEMENTO_SYSTEM_QCORO.
1 parent df5bdaf commit 6fa026e

14 files changed

Lines changed: 323 additions & 26 deletions

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
- name: Install Dependencies
8080
run: |
8181
brew update || true
82-
brew install cmake sqlite3 qt6 mpv mecab mecab-ipadic json-c libzip python || true
82+
brew install git cmake sqlite3 qt6 qcoro6 mpv mecab mecab-ipadic json-c libzip || true
8383
8484
- name: Checkout Repository
8585
uses: actions/checkout@v4
@@ -88,5 +88,5 @@ jobs:
8888
run: |
8989
mkdir build
9090
cd build
91-
cmake .. -DMEMENTO_OCR_SUPPORT=OFF -DMEMENTO_MECAB_SUPPORT=ON -DMEMENTO_WERROR=ON
92-
cmake --build . -j $(nproc)
91+
cmake .. -DMEMENTO_OCR_SUPPORT=OFF -DMEMENTO_MECAB_SUPPORT=ON -DMEMENTO_SYSTEM_QCORO=ON -DMEMENTO_BUNDLE=ON -DMEMENTO_WERROR=ON
92+
cmake --build . --target memento_bundle -j$(sysctl -n hw.ncpu)

CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ endif()
3636
add_subdirectory(option)
3737

3838
# Check that dangerous options have no been toggled
39-
if(NOT APPLE AND MEMENTO_APPBUNDLE)
40-
message(FATAL_ERROR "You cannot set the MEMENTO_APPBUNDLE option to ON on non-Apple platforms.")
39+
if(NOT APPLE AND MEMENTO_BUNDLE)
40+
message(FATAL_ERROR "You cannot set the MEMENTO_BUNDLE option to ON on non-Apple platforms.")
4141
endif()
4242

4343
# Set the module path
@@ -53,7 +53,7 @@ list(
5353
"-Wall"
5454
"-Wextra"
5555
"-Wpedantic"
56-
"$<$<BOOL:${MEMENTO_APPBUNDLE}>:-DMEMENTO_APPBUNDLE=1>"
56+
"$<$<BOOL:${MEMENTO_BUNDLE}>:-DMEMENTO_BUNDLE=1>"
5757
"$<$<BOOL:${MEMENTO_ASAN}>:-fsanitize=address>"
5858
"$<$<BOOL:${MEMENTO_MECAB_SUPPORT}>:-DMEMENTO_MECAB_SUPPORT=1>"
5959
"$<$<BOOL:${MEMENTO_OCR_SUPPORT}>:-DMEMENTO_OCR_SUPPORT=1>"

README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ sudo cmake --build . --target install
146146
```
147147
1. Install the necessary tools and dependencies:
148148
```
149-
brew install git cmake sqlite3 qt6 mpv mecab mecab-ipadic json-c libzip
149+
brew install git cmake sqlite3 qt6 qcoro6 mpv mecab mecab-ipadic json-c libzip
150150
```
151151
1. Clone the repository:
152152
```
@@ -162,9 +162,28 @@ sudo cmake --build . --target install
162162
```
163163
1. The resulting executable will be:
164164
```
165-
Memento/build/src/memento.app/Contents/MacOS/memento
166-
```
167-
165+
Memento/build/src/memento
166+
```
167+
168+
### macOS Bundle
169+
170+
1. Follow steps 1 - 3 of the macOS build instructions.
171+
1. Open the **Keychain Access** app.
172+
1. Go to **Keychain Access** > **Certificate Assistant** >
173+
**Create a Certificate...** in the menubar.
174+
1. Put the name of your certificate in the 'Name' field, set the
175+
'Certificate Type' to 'Code Signing', and click 'Create'.
176+
1. Return to your terminal and input:
177+
```
178+
cd Memento
179+
mkdir build
180+
cmake -S . -B build -DMEMENTO_CODESIGN_IDENTITY="<certificate name>" -DMEMENTO_BUNDLE=ON -DCMAKE_BUILD_TYPE=Release -DMEMENTO_MECAB_SUPPORT=ON -DMEMENTO_SYSTEM_QCORO=ON
181+
cmake --build build --target memento_bundle -j$(sysctl -n hw.ncpu)
182+
```
183+
1. The resulting app bundle will located at:
184+
```
185+
Memento/build/src/Memento.app
186+
```
168187
169188
## Configuration
170189

cmake/BundleMacOS.cmake.in

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
include(BundleUtilities)
2+
3+
# BundleUtilities normally copies framework resources together with the
4+
# framework binary. Keep that behavior so copied frameworks remain valid code
5+
# bundles for codesign, but drop Homebrew Python's nested Python.app helper
6+
# afterward. That helper is not needed by Memento and points back to Homebrew,
7+
# which otherwise makes bundle verification fail.
8+
function(copy_resolved_framework_into_bundle resolved_item resolved_embedded_item)
9+
get_filename_component(resolved_dir "${resolved_item}" DIRECTORY)
10+
get_filename_component(resolved_dir "${resolved_dir}/../.." ABSOLUTE)
11+
get_filename_component(
12+
resolved_embedded_dir "${resolved_embedded_item}" DIRECTORY
13+
)
14+
get_filename_component(
15+
resolved_embedded_dir "${resolved_embedded_dir}/../.." ABSOLUTE
16+
)
17+
if(NOT EXISTS "${resolved_embedded_dir}")
18+
file(COPY "${resolved_dir}" DESTINATION "${resolved_embedded_dir}/..")
19+
endif()
20+
21+
if(resolved_embedded_item MATCHES "/Python\\.framework/")
22+
get_filename_component(
23+
python_framework_version_dir "${resolved_embedded_item}" DIRECTORY
24+
)
25+
file(REMOVE_RECURSE
26+
"${python_framework_version_dir}/Resources/Python.app"
27+
"${python_framework_version_dir}/bin"
28+
"${python_framework_version_dir}/lib"
29+
)
30+
endif()
31+
endfunction()
32+
33+
if(NOT DEFINED MEMENTO_BUNDLE_PATH)
34+
set(MEMENTO_BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/Memento.app")
35+
endif()
36+
set(MEMENTO_BUNDLE_LIBRARY_DIRS "@MEMENTO_BUNDLE_LIBRARY_DIRS@")
37+
38+
if(NOT EXISTS "${MEMENTO_BUNDLE_PATH}")
39+
message(FATAL_ERROR
40+
"Cannot bundle dependencies: ${MEMENTO_BUNDLE_PATH} does not exist."
41+
)
42+
endif()
43+
44+
message(STATUS "Bundling macOS dependencies into ${MEMENTO_BUNDLE_PATH}")
45+
fixup_bundle(
46+
"${MEMENTO_BUNDLE_PATH}"
47+
""
48+
"${MEMENTO_BUNDLE_LIBRARY_DIRS}"
49+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
if(NOT DEFINED MEMENTO_BUNDLE_PATH)
2+
set(MEMENTO_BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/Memento.app")
3+
endif()
4+
5+
if(NOT EXISTS "${MEMENTO_BUNDLE_PATH}")
6+
message(FATAL_ERROR
7+
"Cannot copy MeCab dictionary: ${MEMENTO_BUNDLE_PATH} does not exist."
8+
)
9+
endif()
10+
11+
set(MEMENTO_MECAB_DICTIONARY_DESTINATION
12+
"${MEMENTO_BUNDLE_PATH}/Contents/Resources/mecab/dic"
13+
)
14+
15+
file(MAKE_DIRECTORY "${MEMENTO_MECAB_DICTIONARY_DESTINATION}")
16+
file(COPY
17+
"@PROJECT_SOURCE_DIR@/dic/"
18+
DESTINATION "${MEMENTO_MECAB_DICTIONARY_DESTINATION}"
19+
)

cmake/DeployQtMacOS.cmake.in

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
if(NOT DEFINED MEMENTO_BUNDLE_PATH)
2+
set(MEMENTO_BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/Memento.app")
3+
endif()
4+
5+
if(NOT EXISTS "${MEMENTO_BUNDLE_PATH}")
6+
message(FATAL_ERROR
7+
"Cannot deploy Qt dependencies: ${MEMENTO_BUNDLE_PATH} does not exist."
8+
)
9+
endif()
10+
11+
message(STATUS "Deploying Qt macOS dependencies into ${MEMENTO_BUNDLE_PATH}")
12+
execute_process(
13+
COMMAND
14+
"@MEMENTO_MACDEPLOYQT_EXECUTABLE@"
15+
"${MEMENTO_BUNDLE_PATH}"
16+
"-qmldir=@PROJECT_SOURCE_DIR@/src/qml"
17+
-always-overwrite
18+
RESULT_VARIABLE MEMENTO_MACDEPLOYQT_RESULT
19+
)
20+
if(NOT MEMENTO_MACDEPLOYQT_RESULT EQUAL 0)
21+
message(WARNING
22+
"macdeployqt reported errors while staging ${MEMENTO_BUNDLE_PATH}; "
23+
"the final BundleUtilities pass will re-scan and verify the complete "
24+
"bundle."
25+
)
26+
endif()

cmake/SignMacOS.cmake.in

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
if(NOT DEFINED MEMENTO_BUNDLE_PATH)
2+
set(MEMENTO_BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/Memento.app")
3+
endif()
4+
set(MEMENTO_CODESIGN_IDENTITY "@MEMENTO_CODESIGN_IDENTITY@")
5+
6+
if(NOT EXISTS "${MEMENTO_BUNDLE_PATH}")
7+
message(FATAL_ERROR
8+
"Cannot sign app bundle: ${MEMENTO_BUNDLE_PATH} does not exist."
9+
)
10+
endif()
11+
12+
file(GLOB MEMENTO_FRAMEWORKS
13+
LIST_DIRECTORIES true
14+
"${MEMENTO_BUNDLE_PATH}/Contents/Frameworks/*.framework"
15+
)
16+
file(GLOB_RECURSE MEMENTO_DYLIBS
17+
LIST_DIRECTORIES false
18+
"${MEMENTO_BUNDLE_PATH}/Contents/Frameworks/*.dylib"
19+
"${MEMENTO_BUNDLE_PATH}/Contents/PlugIns/*.dylib"
20+
)
21+
list(SORT MEMENTO_DYLIBS)
22+
list(SORT MEMENTO_FRAMEWORKS)
23+
24+
foreach(MEMENTO_CODE_ITEM IN LISTS MEMENTO_DYLIBS)
25+
if(IS_SYMLINK "${MEMENTO_CODE_ITEM}")
26+
continue()
27+
endif()
28+
execute_process(
29+
COMMAND /usr/bin/codesign --force --sign "${MEMENTO_CODESIGN_IDENTITY}" "${MEMENTO_CODE_ITEM}"
30+
RESULT_VARIABLE MEMENTO_CODESIGN_RESULT
31+
)
32+
if(NOT MEMENTO_CODESIGN_RESULT EQUAL 0)
33+
message(FATAL_ERROR
34+
"Failed to sign nested code item: ${MEMENTO_CODE_ITEM}"
35+
)
36+
endif()
37+
endforeach()
38+
39+
foreach(MEMENTO_CODE_ITEM IN LISTS MEMENTO_FRAMEWORKS)
40+
execute_process(
41+
COMMAND /usr/bin/codesign --force --sign "${MEMENTO_CODESIGN_IDENTITY}" "${MEMENTO_CODE_ITEM}"
42+
RESULT_VARIABLE MEMENTO_CODESIGN_RESULT
43+
)
44+
if(NOT MEMENTO_CODESIGN_RESULT EQUAL 0)
45+
message(FATAL_ERROR
46+
"Failed to sign framework: ${MEMENTO_CODE_ITEM}"
47+
)
48+
endif()
49+
endforeach()
50+
51+
execute_process(
52+
COMMAND /usr/bin/codesign --force --sign "${MEMENTO_CODESIGN_IDENTITY}" "${MEMENTO_BUNDLE_PATH}"
53+
RESULT_VARIABLE MEMENTO_CODESIGN_RESULT
54+
)
55+
if(NOT MEMENTO_CODESIGN_RESULT EQUAL 0)
56+
message(FATAL_ERROR "Failed to sign app bundle: ${MEMENTO_BUNDLE_PATH}")
57+
endif()
58+
59+
execute_process(
60+
COMMAND /usr/bin/codesign --verify --strict --verbose=2 "${MEMENTO_BUNDLE_PATH}"
61+
RESULT_VARIABLE MEMENTO_CODESIGN_VERIFY_RESULT
62+
)
63+
if(NOT MEMENTO_CODESIGN_VERIFY_RESULT EQUAL 0)
64+
message(FATAL_ERROR
65+
"Signed app bundle failed verification: ${MEMENTO_BUNDLE_PATH}"
66+
)
67+
endif()

option/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Apple Build Options
2-
option(MEMENTO_APPBUNDLE "Toggle when building macOS AppBundles" OFF)
3-
option(MEMENTO_CERT "Certificate name used to sign the AppBundle" "")
2+
option(MEMENTO_BUNDLE "Toggle when building macOS Bundles" OFF)
3+
set(MEMENTO_CODESIGN_IDENTITY "" CACHE STRING "macOS code signing identity for the Bundle")
44

55
# Release Options
66
option(MEMENTO_RELEASE_BUILD "Toggle to build with release settings" OFF)

src/CMakeLists.txt

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,24 @@ add_subdirectory(util)
1616
# Executable Targets
1717
qt_add_executable(
1818
memento
19-
"$<$<BOOL:${WIN32}>:${PROJECT_SOURCE_DIR}/res/appicon.rc>"
2019
main.cpp
2120
)
21+
if(WIN32)
22+
target_sources(
23+
memento
24+
PRIVATE "${PROJECT_SOURCE_DIR}/res/appicon.rc"
25+
)
26+
endif()
27+
if(APPLE AND MEMENTO_BUNDLE)
28+
target_sources(
29+
memento
30+
PRIVATE ${PROJECT_SOURCE_DIR}/res/memento.icns
31+
)
32+
set_source_files_properties(
33+
"${PROJECT_SOURCE_DIR}/res/memento.icns"
34+
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources"
35+
)
36+
endif()
2237
configure_file(
2338
qml/util/Features.qml.in
2439
"${PROJECT_SOURCE_DIR}/src/qml/util/Features.qml"
@@ -95,10 +110,13 @@ target_include_directories(memento PRIVATE ${MEMENTO_INCLUDE_DIRS})
95110
target_compile_options(memento PRIVATE ${MEMENTO_COMPILER_FLAGS})
96111
set_target_properties(
97112
memento PROPERTIES
113+
OUTPUT_NAME "$<$<BOOL:${MEMENTO_BUNDLE}>:Memento>$<$<NOT:$<BOOL:${MEMENTO_BUNDLE}>>:memento>"
114+
MACOSX_BUNDLE_BUNDLE_NAME Memento
98115
MACOSX_BUNDLE_GUI_IDENTIFIER io.ripose_jp.Memento
99116
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
100117
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
101-
MACOSX_BUNDLE TRUE
118+
MACOSX_BUNDLE ${MEMENTO_BUNDLE}
119+
MACOSX_BUNDLE_ICON_FILE memento.icns
102120
WIN32_EXECUTABLE ${MEMENTO_WIN32_EXECUTABLE}
103121
)
104122
target_link_libraries(
@@ -132,6 +150,104 @@ install(
132150
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
133151
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
134152
)
153+
if(APPLE AND MEMENTO_BUNDLE)
154+
# BundleUtilities walks the actual linked dependency graph from the bundle.
155+
# These directories are only search hints so it can resolve @rpath entries
156+
# back to the exact libraries CMake found on this machine.
157+
set(
158+
MEMENTO_BUNDLE_LIBRARY_HINTS
159+
"${JsonC_LIBRARY}"
160+
"${libzip_LIBRARY}"
161+
"${mpv_LIBRARY}"
162+
)
163+
if(MEMENTO_MECAB_SUPPORT)
164+
list(APPEND MEMENTO_BUNDLE_LIBRARY_HINTS "${MeCab_LIBRARY}")
165+
endif()
166+
if(MEMENTO_OCR_SUPPORT AND MEMENTO_SYSTEM_MOCR)
167+
list(APPEND MEMENTO_BUNDLE_LIBRARY_HINTS "${mocr_LIBRARY}" "${mocrxx_LIBRARY}")
168+
endif()
169+
list(REMOVE_DUPLICATES MEMENTO_BUNDLE_LIBRARY_HINTS)
170+
171+
set(MEMENTO_BUNDLE_LIBRARY_DIRS "")
172+
foreach(MEMENTO_BUNDLE_LIBRARY_HINT IN LISTS MEMENTO_BUNDLE_LIBRARY_HINTS)
173+
get_filename_component(
174+
MEMENTO_BUNDLE_LIBRARY_DIR "${MEMENTO_BUNDLE_LIBRARY_HINT}" DIRECTORY
175+
)
176+
list(APPEND MEMENTO_BUNDLE_LIBRARY_DIRS "${MEMENTO_BUNDLE_LIBRARY_DIR}")
177+
endforeach()
178+
list(REMOVE_DUPLICATES MEMENTO_BUNDLE_LIBRARY_DIRS)
179+
180+
# On macOS, use macdeployqt directly. It is the native Qt deployment tool
181+
# for bundles and, unlike the generic install helper, copies QML plugins
182+
# into their final bundle layout instead of relying on staging symlinks.
183+
find_program(MEMENTO_MACDEPLOYQT_EXECUTABLE macdeployqt REQUIRED)
184+
configure_file(
185+
"${PROJECT_SOURCE_DIR}/cmake/DeployQtMacOS.cmake.in"
186+
"${PROJECT_BINARY_DIR}/DeployQtMacOS.cmake"
187+
@ONLY
188+
)
189+
install(SCRIPT "${PROJECT_BINARY_DIR}/DeployQtMacOS.cmake")
190+
191+
configure_file(
192+
"${PROJECT_SOURCE_DIR}/cmake/BundleMacOS.cmake.in"
193+
"${PROJECT_BINARY_DIR}/BundleMacOS.cmake"
194+
@ONLY
195+
)
196+
install(SCRIPT "${PROJECT_BINARY_DIR}/BundleMacOS.cmake")
197+
198+
if(MEMENTO_MECAB_SUPPORT)
199+
configure_file(
200+
"${PROJECT_SOURCE_DIR}/cmake/CopyMeCabDictionaryMacOS.cmake.in"
201+
"${PROJECT_BINARY_DIR}/CopyMeCabDictionaryMacOS.cmake"
202+
@ONLY
203+
)
204+
install(SCRIPT "${PROJECT_BINARY_DIR}/CopyMeCabDictionaryMacOS.cmake")
205+
endif()
206+
207+
add_custom_target(
208+
memento_bundle
209+
COMMAND
210+
"${CMAKE_COMMAND}"
211+
-DMEMENTO_BUNDLE_PATH=$<TARGET_BUNDLE_DIR:memento>
212+
-P "${PROJECT_BINARY_DIR}/DeployQtMacOS.cmake"
213+
COMMAND
214+
"${CMAKE_COMMAND}"
215+
-DMEMENTO_BUNDLE_PATH=$<TARGET_BUNDLE_DIR:memento>
216+
-P "${PROJECT_BINARY_DIR}/BundleMacOS.cmake"
217+
DEPENDS memento
218+
COMMENT "Bundling dependencies into Memento.app"
219+
VERBATIM
220+
)
221+
if(MEMENTO_MECAB_SUPPORT)
222+
add_custom_command(
223+
TARGET memento_bundle
224+
POST_BUILD
225+
COMMAND
226+
"${CMAKE_COMMAND}"
227+
-DMEMENTO_BUNDLE_PATH=$<TARGET_BUNDLE_DIR:memento>
228+
-P "${PROJECT_BINARY_DIR}/CopyMeCabDictionaryMacOS.cmake"
229+
VERBATIM
230+
)
231+
endif()
232+
233+
if(MEMENTO_CODESIGN_IDENTITY)
234+
configure_file(
235+
"${PROJECT_SOURCE_DIR}/cmake/SignMacOS.cmake.in"
236+
"${PROJECT_BINARY_DIR}/SignMacOS.cmake"
237+
@ONLY
238+
)
239+
install(SCRIPT "${PROJECT_BINARY_DIR}/SignMacOS.cmake")
240+
add_custom_command(
241+
TARGET memento_bundle
242+
POST_BUILD
243+
COMMAND
244+
"${CMAKE_COMMAND}"
245+
-DMEMENTO_BUNDLE_PATH=$<TARGET_BUNDLE_DIR:memento>
246+
-P "${PROJECT_BINARY_DIR}/SignMacOS.cmake"
247+
VERBATIM
248+
)
249+
endif()
250+
endif()
135251
if(UNIX AND NOT APPLE)
136252
install(
137253
FILES "${PROJECT_SOURCE_DIR}/res/memento.desktop"

0 commit comments

Comments
 (0)