How to Compile Nextcloud Desktop App on Windows (MSVC)

In this guide, we will walk through the complete process of setting up the environment, compiling dependencies, and building the Nextcloud Desktop Client from source using MSVC 2022 and Qt 6.

1. Install Qt Components

First, download the AQT INSTALL command-line tool: Download aqt_x64.exe

Use the tool to install Qt 6.9.0 and its required modules by running the following in PowerShell:

aqt install-qt windows desktop 6.9.0 win64_msvc2022_64 -m qtwebengine qtshadertools qt5compat qtwebsockets --outputdir E:/Dev/Tools/Qt-Portable

2. Download Pre-compiled Third-party Dependencies

Download the following libraries and extract them to E:/Dev/Libs:


3. Manually Compile Third-party Dependencies

Note: All commands in this section should be executed within a Developer PowerShell for VS 2022 environment.

3.1 ECM (Extra CMake Modules)

git clone https://invent.kde.org/frameworks/extra-cmake-modules.git --depth 1
cmake -B Build -S . -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="E:/Dev/Tools/Qt-Portable/6.9.0/msvc2022_64" -DCMAKE_INSTALL_PREFIX="E:/Dev/Libs/kde-ecm"
cmake --build build --target install

3.2 libLZMA

git clone https://github.com/kobolabs/liblzma.git --depth 1
cmake -B build -S . -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="E:/Dev/Libs/liblzma-install"
cmake --build build --target install

3.3 bzip2

git clone https://github.com/libarchive/bzip2.git --depth 1
cmake -B build -S . -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="E:/Dev/Libs/bzip2-install"
cmake --build build --target install

3.4 KDSingleApplication

git clone https://github.com/KDAB/KDSingleApplication.git --depth 1
cmake -B Build -S . -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="E:/Dev/Tools/Qt-Portable/6.9.0/msvc2022_64;E:/Dev/Libs/kde-ecm" -DCMAKE_INSTALL_PREFIX="E:/Dev/Libs/KDSingleApplication"
cmake --build build --target install

3.5 qtkeychain

git clone https://github.com/frankosterfeld/qtkeychain.git --depth 1
cmake -B Build -S . -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="E:/Dev/Tools/Qt-Portable/6.9.0/msvc2022_64;E:/Dev/Libs/kde-ecm" -DCMAKE_INSTALL_PREFIX="E:/Dev/Libs/qtkeychain"
cmake --build build --target install

3.6 KArchive

git clone https://invent.kde.org/frameworks/karchive.git --depth 1
cmake -B Build -S . -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="E:/Dev/Tools/Qt-Portable/6.9.0/msvc2022_64;E:/Dev/Libs/kde-ecm;E:/Dev/Libs/bzip2-install;E:/Dev/Libs/liblzma-install;E:/Dev/Libs/zlib-ng-win-x86-64-compat;E:/Dev/Libs/openssl-1.1/x64" -DCMAKE_INSTALL_PREFIX="E:/Dev/Libs/kf6archive" -DWITH_LIBZSTD=OFF
cmake --build build --target install

3.7 SQLite3

Download the source and run the build script:

curl https://sqlite.org/2026/sqlite-src-3530100.zip -o E:/Dev/Libs/sqlite-src.zip
Expand-Archive E:/Dev/Libs/sqlite-src.zip -DestinationPath E:/Dev/Libs/
# Enter the directory and build
./make.bat

3.8 libp11

Clone: git clone https://github.com/OpenSC/libp11.git --depth 1

Create Config: Create libp11.pc in the root folder:

prefix=E:/Dev/Libs/libp11
exec_prefix=${prefix}
libdir=${prefix}/src
includedir=${prefix}/src
Name: libp11
Version: 0.4.12
Libs: -L"${libdir}" -llibp11
Cflags: -I"${includedir}"

Code Fix: In src/util_uri.c, comment out the legacy calls around line 40:

#ifndef HAVE_X509_GET0_NOTBEFORE
//# define X509_get0_notBefore X509_get_notBefore
#endif

Export Fix: In src/libp11.exports, use ; to comment out PKCS11_evp_pkey_sign and PKCS11_evp_pkey_decrypt.

Build:

nmake -f Makefile.mak OPENSSL_DIR="E:/Dev/Libs/openssl-1.1/x64" BUILD_FOR=WIN64

4. Compiling the Nextcloud App

Prerequisites

  • Install Inkscape: winget install Inkscape.Inkscape --interactive
  • Download icoutils and extract to E:/Dev/Tools/icoutils-0.32.3-x86_64.

Source Code Adjustments

Modify src/libsync/clientsideencryption.cpp: Around line 43, add Windows header fixes to prevent macro conflicts:

#ifdef Q_OS_WIN
#include <windows.h>
#include <wincrypt.h>
#undef ISSUER
#undef SUBJECT
#undef X509_NAME
#undef X509_CERT_PAIR
#undef X509_EXTENSIONS
#undef OCSP_REQUEST
#undef OCSP_RESPONSE
#endif

Modify src/libsync/vfs/cfapi/shellext/thumbnailprovider.cpp: Add these headers at the top:

#include <ntstatus.h>
#define WIN32_NO_STATUS

Final Build Command

In the desktop-master root directory:

$env:PKG_CONFIG_PATH="E:/Dev/Libs/libp11"
cmake -B build -S . -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="/D_CRT_SECURE_NO_WARNINGS /D_SILENCE_CXX20_OLD_SHARED_PTR_ATOMIC_SUPPORT_DEPRECATION_WARNING /DOPENSSL_SUPPRESS_DEPRECATED /DEHsc" -DCMAKE_PREFIX_PATH="E:/Dev/Libs/kde-ecm;E:/Dev/Tools/Qt-Portable/6.9.0/msvc2022_64;E:/Dev/Libs/openssl-1.1/x64;E:/Dev/Libs/zlib-ng-win-x86-64-compat;E:/Dev/Libs/sqlite-src-3530100;E:/Dev/Libs/KDSingleApplication;E:/Dev/Libs/qtkeychain;E:/Dev/Libs/kf6archive" -DLIBP11_INCLUDE_DIR="E:/Dev/Libs/libp11/src" -DLIBP11_LIBRARY="E:/Dev/Libs/libp11/src/libp11.lib" -DSQLite3_INCLUDE_DIR="E:/Dev/Libs/sqlite-src-3530100" -DSQLite3_LIBRARY="E:/Dev/Libs/sqlite-src-3530100/sqlite3.lib" -DSVG_CONVERTER="C:/Program Files/Inkscape/bin/inkscape.exe" -DIcoTool_EXECUTABLE="E:/Dev/Tools/icoutils-0.32.3-x86_64/bin/icotool.exe" -DCMAKE_INSTALL_PREFIX="E:/desktop-master/install" -DBUILD_WITH_WEBENGINE=OFF -DBUILD_TESTING=OFF
cmake --build build --target install

5. Manual Deployment

After a successful build, follow these steps to run the application:

  1. Navigate to E:/desktop-master/install/bin and run nextcloud.exe.
  2. Dependencies: If errors occur regarding missing DLLs, copy the required Qt runtime files from E:\Dev\Tools\Qt-Portable\6.9.0\msvc2022_64\bin to your bin folder.
  3. Third-party DLLs: Collect relevant DLLs from the build directories of the dependencies in Section 3 and place them in the bin folder.
  4. Plugins: Create a folder named platforms inside the bin directory. Copy qwindows.dll from E:\Dev\Tools\Qt-Portable\6.9.0\msvc2022_64\plugins\platforms\ into it.

Controlling the C++ Runtime Linkage: A Portable Solution

A large number of open-source C++ projects choose Modern CMake as a powerful tool used for managing the building process across multiple platforms. Modern CMake refers to the practices, features, and methodologies introduced in CMake 3.x (and beyond) that simplify, improve, and modernize the build system configuration for C++ and other languages. It emphasizes clarity, reusability, and portability, and aims to make CMake easier to maintain while leveraging its full potential.

For the most part, the CMAKE_<LANG>_FLAGS CMake variable is provided for consumers to set additional compiler flags for the <LANG> programming language. For example, CMAKE_CXX_FLAGS impacts the current C++ compiler and CMAKE_C_FLAGS will take effect when building a C target in the subsequent tasks.

if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
  string(APPEND CMAKE_CXX_FLAGS " /EHsc")
else()
  string(APPEND CMAKE_CXX_FLAGS " -fexceptions")
endif()

The CMAKE_<LANG>_FLAGS variable will always override both Release and Debug targets. To set flags separately for one single build type, extra variables like CMAKE_<LANG>_FLAGS_RELEASE and CMAKE_<LANG>_FLAGS_DEBUG are introduced for such kind of usage.

if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
  string(APPEND CMAKE_CXX_FLAGS_DEBUG " /Od")
  string(APPEND CMAKE_CXX_FLAGS_RELEASE " /O2")
else()
  string(APPEND CMAKE_CXX_FLAGS_DEBUG " -O0")
  string(APPEND CMAKE_CXX_FLAGS_RELEASE " -O3")
endif()

On Windows, Visual Studio 2015 and later versions of Visual Studio all use one Universal Visual C Runtime Library, called the Universal CRT (UCRT). The UCRT is a Microsoft Windows operating system component. It’s included as part of the operating system in Windows 10 or later, and Windows Server 2016 or later. The Visual C++ Runtime Library is usually distributed with the corresponding MSVC tooling or installed independently with third-party software. The VC++ Runtime Library depends on the UCRT in the operating system.

The UCRT and the VC++ Runtime have static and dynamic libraries within MSVC and thereby the MSVC’s compiler cl.exe provides several options for linkage mode control of the two runtimes simultaneously. The options are:

Option (Release)Option (Debug)Description
/MD/MDdLinking to UCRT & VC++ Runtime statically
/MT/MTdLinking to UCRT & VC++ Runtime dynamically

For GCC on Linux or Unix platform, this compiler is designed to use options, such as -static-libstdc++ and -static-libgcc, to enable the static libraries of libstdc++.a, libgcc.a, or libc++.a. The libgcc is known as the GCC Low-level Runtime Library, which exists on some platform and GCC generates calls to routines in this library automatically, whenever it needs to perform some operation that is too complicated to emit inline code for.

For Clang on these platforms, it contains the same options as GCC has for compatibility considerations. The options in Clang are aliases to -static-libc++ and -static-compiler-rt, which are identical to those of GCC.

CompilerOptionDescription
GCC-static-libstdc++Linking to libstdc++ statically
GCC-static-libgccLinking to libgcc statically
Clang-static-libc++
-static-libstdc++
Linking to libc++ statically
Clang-static-libgcc
-static-compiler-rt
Linking to libcompiler-rt statically.

In a C++ project, it is recommended to keep the same configuration of compilation for all dependencies. Because of different compilers and option names, it is a bit complex to write CMake code in a portable way and users may set the options via -DCMAKE_<LANG>_FLAGS=xxx in the command line, that could lead to incorrect building configurations or an unexpected behavior.

A solution is to define a CMake option in convention, e.g. WITH_STATIC_RUNTIME, indicating that it is the official method to modify the runtime linkage mode.

option(WITH_STATIC_RUNTIME OFF "Linking to the C++ runtime statically.")

A user-defined CMake function is constructed here to generate flags for different compilers correspondingly, complying the aforementioned rules.

function(es_make_c_cxx_runtime_flags)
  set(options STATIC_RUNTIME)
  set(one_value_args RESULT_DEBUG_FLAGS RESULT_RELEASE_FLAGS)
  set(multi_value_args "")
  cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${one_value_args}" "${multi_value_args}")
  es_ensure_parameters(es_make_c_cxx_runtime_flags ARG RESULT_DEBUG_FLAGS RESULT_RELEASE_FLAGS)

  if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    if(ARG_STATIC_RUNTIME)
      set(debug_flag /MTd)
      set(release_flag /MT)
    else()
      set(debug_flag /MDd)
      set(release_flag /MD)
    endif()
  elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    if(ARG_STATIC_RUNTIME)
      set(debug_flag "-static-libstdc++ -static-libgcc")
      set(release_flag ${debug_flag})
    else()
      set(debug_flag "")
      set(release_flag "")
    endif()
  else()
    message(FATAL_ERROR "Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}.")
  endif()
  
  set(${ARG_RESULT_DEBUG_FLAGS} ${debug_flag} PARENT_SCOPE)
  set(${ARG_RESULT_RELEASE_FLAGS} ${release_flag} PARENT_SCOPE)
endfunction()

The es_ensure_parameters function raises a fatal error when the mandatory parameters do not exist. Here is an example showing how to use this function:

if(WITH_STATIC_RUNTIME)
  set(runtime_switch STATIC_RUNTIME)
else()
  set(runtime_switch "")
endif()

es_make_c_cxx_runtime_flags(
  ${runtime_switch}
  debug_flags
  release_flags
)

message(STATUS "debug_flags: ${debug_flags}")
message(STATUS "release_flags: ${release_flags}")

Another design goal is exception safety, that is whatever users set the flags to never corrupts the building process. A workaround is to clear the old runtime flags first and set the new flags after that. A possible implementation is to iterate all the variables to remove conflicting flags and then append the generated flags to these variables. The string(REPLACE ...) function is capable of text replacement operations.

string(REPLACE "string-to-find" "replacement" <RESULT> "source")

set(input_str "Hello world")
string(REPLACE "Hello" "Echo" output_str "${input_str}")
message(STATUS "${output_str}")

In CMake, a list is actually strings separated by semicolons, so an ordinary string can be transformed to a list after inserting semicolons within it. The CMAKE_<LANG>_FLAGS and CMAKE_<LANG>_<BUILD_TYPE> variables use spaces as delimiters and can be passed to the foreach statement by replacing all spaces with semicolons. It is viable to retrieve conflicting flags by reversing the WITH_STATIC_RUNTIME option.

if(ARG_STATIC_RUNTIME)
  set(runtime_switch STATIC_RUNTIME)
  set(reverse_runtime_switch "")
else()
  set(runtime_switch "")
  set(reverse_runtime_switch STATIC_RUNTIME)
endif()

es_make_c_cxx_runtime_flags(
  ${reverse_runtime_switch}
  RESULT_DEBUG_FLAGS reverse_debug_flags
  RESULT_RELEASE_FLAGS reverse_release_flags
)

string(REPLACE " " ";" reverse_debug_flag_list "${reverse_debug_flags}")
string(REPLACE " " ";" reverse_release_flag_list "${reverse_release_flags}")

foreach(item IN LISTS reverse_debug_flag_list)
  string(REPLACE ${item} "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
  string(REPLACE ${item} "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
  string(REPLACE ${item} "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
  string(REPLACE ${item} "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
endforeach()

foreach(item IN LISTS reverse_release_flag_list)
  string(REPLACE ${item} "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
  string(REPLACE ${item} "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
  string(REPLACE ${item} "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
  string(REPLACE ${item} "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
endforeach()

Now all the variables do not contain conflicting flags anymore and the new flags can be appended to them immediately.

es_make_c_cxx_runtime_flags(
  ${runtime_switch}
  RESULT_DEBUG_FLAGS debug_flags
  RESULT_RELEASE_FLAGS release_flags
)

string(APPEND CMAKE_C_FLAGS_DEBUG " ${debug_flags}")
string(APPEND CMAKE_CXX_FLAGS_DEBUG " ${debug_flags}")
string(APPEND CMAKE_C_FLAGS_RELEASE " ${release_flags}")
string(APPEND CMAKE_CXX_FLAGS_RELEASE " ${release_flags}")

If all the above code are located inside a CMake function, which has its own inner scope of variables. Extra set statements are necessary to copy the inside variables to the outer ones.

set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} PARENT_SCOPE)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} PARENT_SCOPE)
set(CMAKE_C_FLAGS_DEBUG ${CMAKE_C_FLAGS_DEBUG} PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG} PARENT_SCOPE)
set(CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE} PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} PARENT_SCOPE)

Well done! Everything might be OK now. The full application can be checked at my personal repo named “Cpp Essence” on GitHub. I will appreciate your stars and contributions there! Big thanks.