feat: Nextcloud Developer Build from Integration Project

Introduced a new target with external build system in the NextcloudIntegration Xcode project to conveniently run mac-crafter from Xcode.

Signed-off-by: Iva Horn <iva.horn@icloud.com>
This commit is contained in:
Iva Horn 2025-10-02 15:03:59 +02:00 committed by Matthieu Gallien
parent d9fc4e8ead
commit 0d953425ca
6 changed files with 317 additions and 1 deletions

View File

@ -36,6 +36,7 @@ If you find any bugs or have any suggestion for improvement, please
> [!TIP]
> For building the client on macOS we have a tool called `mac-crafter`.
> You will find more information about it in [its dedicated README](admin/osx/mac-crafter/README.md).
> Also, please note the [README in the NextcloudIntegration project](shell_integration/MacOSX/NextcloudIntegration/README.md) which provides an even more convenient way to work on and build the desktop client on macOS by using Xcode.
#### 1. 🚀 Set up your local development environment

View File

@ -42,7 +42,15 @@ SPDX-FileCopyrightText = "2014 ownCloud GmbH, 2022 Nextcloud GmbH and Nextcloud
SPDX-License-Identifier = "GPL-2.0-or-later"
[[annotations]]
path = ["shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj", "shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/contents.xcworkspacedata", "shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist", "shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme"]
path = [
"shell_integration/MacOSX/NextcloudIntegration/.gitignore",
"shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj",
"shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/xcshareddata/xcschemes/FinderSyncExt.xcscheme",
"shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/xcshareddata/xcschemes/NextcloudDev.xcscheme",
"shell_integration/MacOSX/NextcloudIntegration/README.md"
]
precedence = "aggregate"
SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "GPL-2.0-or-later"

View File

@ -0,0 +1,40 @@
#!/bin/env zsh
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: GPL-2.0-or-later
# Read the available environment paths which include (for example) Homebrew.
for f in /etc/paths.d/*; do
while read -r line; do
export PATH="$PATH:$line"
done < "$f"
done
if [ -f "~/.zprofile" ]; then
echo "Sourcing ~/.zprofile to include possible PATH definitions..."
source "~/.zprofile"
fi
if [ -z "${CODE_SIGN_IDENTITY}" ]; then
echo "Error: CODE_SIGN_IDENTITY is not defined or is empty!"
exit 1
fi
DESKTOP_CLIENT_PROJECT_ROOT="$SOURCE_ROOT/../../.."
if [ -d "$DESKTOP_CLIENT_PROJECT_ROOT/admin/osx/mac-crafter" ]; then
cd "$DESKTOP_CLIENT_PROJECT_ROOT/admin/osx/mac-crafter"
else
echo "Error: Directory '$DESKTOP_CLIENT_PROJECT_ROOT/admin/osx/mac-crafter' does not exist!"
exit 1
fi
swift run mac-crafter \
--build-path="$DERIVED_SOURCES_DIR" \
--product-path="/Applications" \
--build-type="Debug" \
--dev \
--disable-auto-updater \
--build-file-provider-module \
--code-sign-identity="$CODE_SIGN_IDENTITY" \
"$DESKTOP_CLIENT_PROJECT_ROOT"

View File

@ -196,9 +196,13 @@
53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewDataSourceUIDelegate.swift; sourceTree = "<group>"; };
53FE14662B8F78B6006C4193 /* ShareOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareOptionsView.swift; sourceTree = "<group>"; };
AA02B2AA2E7048C600C72B34 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
AA0A6B1D2E8EA94F007F4A7A /* Craft.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = Craft.sh; sourceTree = "<group>"; };
AA1191B52E8EAFF900E21C7B /* Build.xcconfig.template */ = {isa = PBXFileReference; lastKnownFileType = text; path = Build.xcconfig.template; sourceTree = "<group>"; };
AA27A4E32E93C0D700665051 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
AA7F17E02E7017230000E928 /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = "<group>"; };
AA7F17E22E70173E0000E928 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = "<group>"; };
AA7F17E62E7038340000E928 /* NSError+FileProviderErrorCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+FileProviderErrorCode.swift"; sourceTree = "<group>"; };
AA826BEE2E8EAA7500CE49C4 /* Build.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Build.xcconfig; sourceTree = "<group>"; };
AA9987852E72B6DB00B2C428 /* NextcloudKit+clearAccountErrorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudKit+clearAccountErrorState.swift"; sourceTree = "<group>"; };
AAA69D922E3BB09900BBD44D /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
AAC00D292E37B29D006010FE /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
@ -373,6 +377,16 @@
path = FileProviderUIExt;
sourceTree = "<group>";
};
AA0A6B1C2E8EA948007F4A7A /* NextcloudDev */ = {
isa = PBXGroup;
children = (
AA826BEE2E8EAA7500CE49C4 /* Build.xcconfig */,
AA1191B52E8EAFF900E21C7B /* Build.xcconfig.template */,
AA0A6B1D2E8EA94F007F4A7A /* Craft.sh */,
);
path = NextcloudDev;
sourceTree = "<group>";
};
AA7F17DF2E70171A0000E928 /* Authentication */ = {
isa = PBXGroup;
children = (
@ -385,7 +399,9 @@
C2B573941B1CD88000303B36 = {
isa = PBXGroup;
children = (
AA27A4E32E93C0D700665051 /* README.md */,
C2B573B31B1CD91E00303B36 /* desktopclient */,
AA0A6B1C2E8EA948007F4A7A /* NextcloudDev */,
C2B573D81B1CD9CE00303B36 /* FinderSyncExt */,
538E396B27F4765000FA63D5 /* FileProviderExt */,
53B9797F2B84C81F002DA742 /* FileProviderUIExt */,
@ -466,6 +482,25 @@
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXLegacyTarget section */
AA0A62F22E8EA929007F4A7A /* NextcloudDev */ = {
isa = PBXLegacyTarget;
buildArgumentsString = "\"$(SOURCE_ROOT)/NextcloudDev/Craft.sh\"";
buildConfigurationList = AA0A62F52E8EA929007F4A7A /* Build configuration list for PBXLegacyTarget "NextcloudDev" */;
buildPhases = (
);
buildToolPath = /bin/zsh;
buildWorkingDirectory = "";
dependencies = (
);
name = NextcloudDev;
packageProductDependencies = (
);
passBuildSettingsInEnvironment = 1;
productName = NextcloudDev;
};
/* End PBXLegacyTarget section */
/* Begin PBXNativeTarget section */
538E396627F4765000FA63D5 /* FileProviderExt */ = {
isa = PBXNativeTarget;
@ -593,6 +628,9 @@
53B9797D2B84C81F002DA742 = {
CreatedOnToolsVersion = 15.2;
};
AA0A62F22E8EA929007F4A7A = {
CreatedOnToolsVersion = 26.0.1;
};
C2B573B01B1CD91E00303B36 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = 9B5WD74GWJ;
@ -739,6 +777,7 @@
538E396627F4765000FA63D5 /* FileProviderExt */,
53B9797D2B84C81F002DA742 /* FileProviderUIExt */,
53903D0B2956164F00D0B308 /* NCDesktopClientSocketKit */,
AA0A62F22E8EA929007F4A7A /* NextcloudDev */,
);
};
/* End PBXProject section */
@ -1267,6 +1306,80 @@
};
name = Release;
};
AA0A62F32E8EA929007F4A7A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AA826BEE2E8EAA7500CE49C4 /* Build.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUGGING_SYMBOLS = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
AA0A62F42E8EA929007F4A7A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AA826BEE2E8EAA7500CE49C4 /* Build.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
C2B573991B1CD88000303B36 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1584,6 +1697,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
AA0A62F52E8EA929007F4A7A /* Build configuration list for PBXLegacyTarget "NextcloudDev" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AA0A62F32E8EA929007F4A7A /* Debug */,
AA0A62F42E8EA929007F4A7A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C2B573981B1CD88000303B36 /* Build configuration list for PBXProject "NextcloudIntegration" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AA0A62F22E8EA929007F4A7A"
BuildableName = "NextcloudDev"
BlueprintName = "NextcloudDev"
ReferencedContainer = "container:NextcloudIntegration.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "NO"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<PathRunnable
runnableDebuggingMode = "0"
BundleIdentifier = "com.nextcloud.desktopclient"
FilePath = "/Applications/NextcloudDev.app">
</PathRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AA0A62F22E8EA929007F4A7A"
BuildableName = "NextcloudDev"
BlueprintName = "NextcloudDev"
ReferencedContainer = "container:NextcloudIntegration.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,73 @@
# Nextcloud macOS Integration Project
This is an Xcode project to build platform-specific components for the Nextcloud desktop client.
As an example, this includes the file provider extension.
## Nextcloud Developer Build
There is a special target in the Xcode project which integrates the `mac-crafter` command-line tool as an external build system in form of a scheme.
In other words: The "NextcloudDev" scheme enables you to build, run and debug the Nextcloud desktop client with the simple run action in Xcode.
### Requirements
- You must have an Apple Development certificate for signing in your keychain.
### Usage
1. Copy [`Build.xcconfig.template`](NextcloudDev/Build.xcconfig.template) in the "NextcloudDev" source code folder to `Build.xcconfig` in the same location and adjust the values in it to your local setup.
2. Build or run the "NextcloudDev scheme".
### Known Issues
- Right now, the project does not support signing a different app identity than the default one (`com.nextcloud.desktopclient`) which is owned by the Nextcloud GmbH development team registered with Apple.
This means that you have to be signed in with a developer account in Xcode which is part of that development team when building.
- Even when building successfully, Xcode may conclude that the build failed or at least some errors occurred during the build.
During the build, some command outputs messages with an "Error: " prefix.
Since this is the same way the compiler usually reports errors to Xcode, the latter assumes something might have gone wrong.
But no invocation exits with an error code.
Hence, the build can still complete successfully while Xcode might just misinterpret the console output.
You will see at the end of the build output log in Xcode.
### How It Works
- The "NextcloudDev" target runs the [Craft.sh](NextcloudDev/Craft.sh) shell script which is part of this Xcode project.
- `Craft.sh` prepares the execution of and finally runs [`mac-crafter`](https://github.com/nextcloud/desktop/tree/master/admin/osx/mac-crafter) which is part of the Nextcloud desktop client repository to simplify builds on macOS.
- By running `mac-crafter` with the right arguments and options, Xcode can attach to the built app with its debugger and stop at breakpoints.
One of the key factors is the `Debug` build type which flips certain switches in the CMake build scripts ([in example: app hardening or get-task-allow entitlement](https://github.com/nextcloud/desktop/pull/8474/files)).
- The built Nextcloud desktop client app bundle is not placed into a derived data directory of Xcode but `/Applications`.
The standard behavior of placing the product into Xcode's derived data directory would result in absolute reference paths within the scheme file which are not portable across devices and users due to varying user names.
### Hints
Just for reference, a few helpful snippets for inspecting state on breakpoints with the Xcode debugger.
#### Print a `QString`
```lldb
call someString.toStdString()
```
#### Print a `QStringList`
```lldb
call someStrings.join("\n").toStdString()
```
#### Attach to File Provider Extension Process
You can debug the file provider extension process(es) in Xcode by attaching to them by their binary name.
1. Select this menu item in Xcode: _Debug_ → _Attach to Process by PID or Name..._
2. Enter `FileProviderExt`.
If you would also like to debug the file provider UI extension, then you can additionally specify `FileProviderUIExt`.
3. Confirm.
4. If no process is already running, then Xcode will wait for it to be launched to attach automatically.
This usually happens when you launch the main app or set up a new account with file provider enabled.
#### Work on NextcloudFileProviderKit and NextcloudKit
You can directly debug changes to these dependencies edited from this project.
You have to have local repository clones of the packages somewhere locally, though.
Drag and drop the package folders into the project navigator of the NextcloudIntegration project.
This will cause Xcode to resolve to the local and editable package instead of a cached read-only clone from GitHub.
When you then run the build action of this root project, the local dependency is built as well.