Xcode Breakpoints in the Nextcloud Desktop Client
February 23, 2026 • #Debugging #File Provider #KDE Craft #MacOS #Nextcloud #Xcode
When attaching Xcode to the file provider extension process of the Nextcloud desktop client, the debugger connected just fine — but every single breakpoint was displayed hollow and in dashes which means Xcode cannot associate the position with a line of code on disk. Tracking down the root cause turned into quite the adventure.
The Nextcloud desktop client ships a macOS File Provider extension written in Swift.
To clarify the following quotes and snippets: its target and binary are named FileProviderExt.
The app itself is not built through an Xcode project you open and press Run on — instead, a Swift CLI tool called mac-crafter in admin/osx/mac-crafter/ utilizes KDE Craft underneath, which in turn calls xcodebuild with a custom SYMROOT pointing into the Craft build directory.
The Xcode scheme used for development wraps the entire mac-crafter invocation as an external build tool.
That inevitable Jenga tower of build tooling sabotaged the journey of our debugging symbols.
The Binary Has No DWARF — And That Is Fine
First thing I checked: does the installed binary actually have debug information embedded in it?
dwarfdump --debug-info \
/Applications/NextcloudDev.app/Contents/PlugIns/FileProviderExt.appex/Contents/MacOS/FileProviderExtOutput: .debug_info contents: — completely empty.
A bit alarming at first glance, but this is actually perfectly normal.
Xcode strips debug info from the binary and extracts it into a separate .dSYM bundle during the build.
The binary stays lean; the symbols live elsewhere.
The question became: where is the dSYM?
Found it, buried deep in the Craft build directory at:
.mac-crafter/macos-clang-arm64/build/nextcloud-client/work/build/shell_integration/MacOSX/Debug/FileProviderExt.appex.dSYMA quick UUID check with dwarfdump --uuid confirmed it matched the installed binary — the symbols existed, Xcode just could not find them.
Spotlight Does Not Index Dot-Directories
This is the core of the problem!
Xcode does not search for dSYM bundles by walking directories manually — it uses Spotlight, via a UUID lookup.
The dSYM lives inside .mac-crafter/, a hidden directory (dot-prefix).
Spotlight skips hidden directories by default, so the dSYM bundle was completely invisible to Xcode’s symbol search, regardless of whether the UUID matched.
The dSYM Is Not in the App Bundle on Purpose
Reading the build log more carefully revealed two things:
xcodebuild is invoked with DWARF_DSYM_FILE_SHOULD_ACCOMPANY_PRODUCT=NO, so dSYMs are intentionally not copied alongside the product by the outer Xcode build.
On top of that, SYMROOT points into the Craft build directory rather than Xcode’s DerivedData, so DerivedData never sees them at all.
My first fix attempt was to add CMake install rules in shell_integration/MacOSX/CMakeLists.txt to copy the dSYMs into the app bundle:
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Debug/FileProviderExt.appex.dSYM
DESTINATION ${CMAKE_INSTALL_PREFIX}/Contents/PlugIns)The staging log showed them being installed — but they never made it into the final app.
KDE Craft Deliberately Moves dSYMs Away
Digging into KDE Craft’s Python source (craft-clone/bin/BuildSystem/BuildSystemBase.py) explained exactly why:
# __internalPostInstallHandleSymbols() -- runs after every cmake install
symbolsPattern = re.compile(r".*\.dSYM$", re.IGNORECASE)
for f in utils.filterDirectoryContent(self.imageDir(), symFilter, ...):
dest = self.symbolsImageDir() / Path(f).relative_to(self.imageDir())
utils.moveFile(f, dest) # moves to image-Debug-master-dbg/KDE Craft deliberately moves every dSYM bundle from the main image directory (image-Debug-master/) into a separate debug-symbols image (image-Debug-master-dbg/).
It is a first-class feature designed for producing separate debug-symbol packages.
The CMake install rules were therefore a dead end: they staged the dSYMs into the image directory, and Craft immediately moved them back out again before the app was assembled.
The Fix: Copy dSYMs with mac-crafter
Since KDE Craft’s packaging pipeline actively works against putting dSYMs in the image, the right place to intervene is after KDE Craft is done — in the build command implementation of mac-crafter.
By reading the dSYMs directly from the xcodebuild SYMROOT (bypassing the Craft image directory entirely), KDE Craft’s filtering becomes irrelevant.
A new section was added before the signing step, so the dSYMs are present inside the app bundle when it gets signed and then copied to its product path. After this change, the app bundle looks like this:
/Applications/NextcloudDev.app/Contents/PlugIns/
├── FileProviderExt.appex/
├── FileProviderExt.appex.dSYM/ ← now present
├── FileProviderUIExt.appex/
├── FileProviderUIExt.appex.dSYM/ ← now present
├── FinderSyncExt.appex/
└── FinderSyncExt.appex.dSYM/ ← now present/Applications is Spotlight-indexed, so Xcode now finds the dSYMs automatically via UUID lookup when attaching to the extension process.
Breakpoints resolve correctly.
Useful Tools for This Investigation
A few tools that proved genuinely helpful throughout:
dwarfdump --debug-info <binary>— inspect embedded DWARF (empty output means symbols are in a dSYM)dwarfdump --uuid <binary|dSYM>— verify the UUID match between a binary and its dSYMmdfind "com_apple_xcode_dsym_uuids == <UUID>"— check whether Spotlight has indexed a given dSYMlldb→image lookup -v -n <symbol>— inspect what source path LLDB resolves a symbol tolldb→settings set target.source-map <old> <new>— remap source paths when they differ between build machine and local checkout