diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..b3a9c21c7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,80 @@ +--- +BasedOnStyle: LLVM +AccessModifierOffset: '-2' +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: 'false' +AlignConsecutiveDeclarations: 'false' +AlignEscapedNewlines: Left +AlignOperands: 'true' +AlignTrailingComments: 'true' +AllowAllArgumentsOnNextLine: 'true' +AllowAllConstructorInitializersOnNextLine: 'false' +AllowAllParametersOfDeclarationOnNextLine: 'false' +AllowShortBlocksOnASingleLine: 'true' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Always +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: 'false' +AlwaysBreakTemplateDeclarations: 'Yes' +BinPackArguments: 'true' +BinPackParameters: 'false' +BreakBeforeBinaryOperators: All +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: 'true' +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +BreakStringLiterals: 'true' +ColumnLimit: '150' +CompactNamespaces: 'false' +ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' +ContinuationIndentWidth: '2' +DerivePointerAlignment: 'false' +DisableFormat: 'false' +FixNamespaceComments: 'true' +IncludeBlocks: Regroup +IndentCaseLabels: 'true' +IndentPPDirectives: BeforeHash +IndentWidth: '2' +IndentWrappedFunctionNames: 'false' +KeepEmptyLinesAtTheStartOfBlocks: 'true' +Language: Cpp +MaxEmptyLinesToKeep: '2' +NamespaceIndentation: Inner +PointerAlignment: Left +SortIncludes: 'true' +SortUsingDeclarations: 'true' +SpaceAfterLogicalNot: 'true' +SpaceAfterTemplateKeyword: 'false' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeCpp11BracedList: 'true' +SpaceBeforeCtorInitializerColon: 'false' +SpaceBeforeInheritanceColon: 'true' +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: 'true' +SpaceInEmptyParentheses: 'false' +SpacesBeforeTrailingComments: '1' +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInContainerLiterals: 'false' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +Standard: Cpp11 +TabWidth: '2' +UseTab: Never + +... diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 000000000..4055f49ac --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,3 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,abbrev=40,match=v[0-9]*,exclude=v*[!0-9.]*)$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..a62ac6f26 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,24 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +# Force UNIX line endings, so you can use the same repo e.g., on Win and WSL +* text=auto eol=lf +# batch files specifically need to have CRLF (win endings) +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf + +# Denote all files that are truly binary and should not be modified. +# Images +*.png binary +*.jpg binary +# XML files with potential byte-counting indices that will lose function +# if the windows CR byte is added +*.mzML binary +*.mzXML binary +# reference images of the ImageCreator tests, application icons, the UTF-16 registry file in tools/ +*.bmp binary +*.ico binary +*.icns binary +*.reg binary + +# git archive fills in the commit and its release tag, so that a build from an archive (the +# Release workflow's source tarball, GitHub's source archives) knows its version (CMakeLists.txt). +.git_archival.txt export-subst diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml new file mode 100644 index 000000000..dc8279e4f --- /dev/null +++ b/.github/actions/build/action.yml @@ -0,0 +1,270 @@ +name: "Build OpenMS-TOPP-GUI" +description: >- + Configures, builds and tests the GUI layer with the CI preset of the runner + against an OpenMS core (see the openms-core action), with ccache. +inputs: + compiler: + description: "The name of the compiler to use (cl.exe, xcode, g++, clang++)" + required: true + prefix_path: + description: "CMAKE_PREFIX_PATH: the OpenMS core and Qt (';'-separated)" + required: true + topp_bin_dir: + description: "OPENMS_TOPP_BIN_DIR: the TOPP tools of the core (FuzzyDiff, ...)" + required: false + default: "" + examples_dir: + description: "OPENMS_EXAMPLES_DIR: the example data of the core (TOPPAS workflows)" + required: false + default: "" + core_version_fullstring: + description: "OPENMS_CORE_VERSION_FULLSTRING: the full version string of the core" + required: false + default: "" + preset: + description: >- + The CMake preset to configure, build and test with. By default the CI preset + of the runner (linux-x64-ci, macos-arm64-ci, windows-x64-ci, ...). + required: false + default: "" + run_tests: + description: "Whether to run the test suite after the build (ctest --preset)" + required: false + default: "true" + cmake_args: + description: "Further arguments for the configure step (one per line)" + required: false + default: "" + macosx_deployment_target: + description: >- + The oldest macOS the binaries run on (-mmacosx-version-min). Keep it at the + runner's macOS, as for OpenMS: the Homebrew Qt the package bundles is built + for that version. + default: "15.0" +outputs: + version: + description: "The discovered compiler version number" + value: ${{ steps.compiler.outputs.ver }} + preset: + description: "The resolved CMake preset name" + value: ${{ steps.preset.outputs.name }} +runs: + using: "composite" + steps: + + ############################################################################ + # NOTE: x64 is hardcoded. No support for 32bit. + # Skipped when the openms-core action has set up the environment already + # (VCINSTALLDIR): vcvars run twice can make PATH too long for cmd. + - name: Emulate a Visual Studio shell (Windows) + if: ${{ runner.os == 'Windows' && env.VCINSTALLDIR == '' }} + uses: egor-tensin/vs-shell@v2 + with: { arch: x64 } + + ############################################################################ + - name: Compute the Compiler Version + id: compiler + env: + compiler: ${{ inputs.compiler }} + shell: bash + run: | + case ${compiler:-gcc} in + clang*) + ver=$(clang -dumpversion) + name=$(type -p clang++) + cc_name=$(type -p clang) + ;; + + cl*) + ver=$(cl.exe 2>&1 | + grep -E 'Version [0-9.]+' | + sed -E 's/^.* Version ([0-9.]+) .*$/\1/') + name=$compiler + cc_name=$compiler + ;; + + gcc | g++) + ver=$(gcc -dumpversion) + name=$(type -p g++) + cc_name=$(type -p gcc) + ;; + + xcode) + ver=$(clang -dumpversion) + name=$(xcrun -f clang++) + cc_name=$(xcrun -f clang) + ;; + + *) + ver="0.0" + name=$compiler + cc_name=$compiler + ;; + esac + + major=$(cut -d. -f1 <<<"$ver") + + { + echo "ver=$ver" + echo "major=$major" + echo "name=$name" + } >>"$GITHUB_OUTPUT" + + { + # All remaining steps should use this compiler. + echo "CXX=$name" + echo "CC=$cc_name" + } >>"$GITHUB_ENV" + + ############################################################################ + # The tests and the documentation run the GUI applications, which then need + # no display (src/tests sets it per test as well). + - name: Set CI-specific Environment Variables + shell: bash + run: echo "QT_QPA_PLATFORM=offscreen" >>"$GITHUB_ENV" + + ############################################################################ + # This is only really needed so we can set cache keys. + - name: Extract the PR Number or Branch Name + id: pr + shell: bash + run: echo "name=${{ github.event.pull_request && github.event.number || github.ref_name }}" >>"$GITHUB_OUTPUT" + + ############################################################################ + # Restore and save are separate steps so that every run saves a fresh cache + # (under a key unique to the run) while the restore-keys ladder finds the + # newest one: of the branch or pull request, else of develop (the default + # branch, whose caches every pull request can read), else any in scope. + - name: Try to Fetch Existing Build Cache + id: ccache-restore + uses: actions/cache/restore@v5 + with: + path: .ccache + key: ${{ runner.os }}-${{ runner.arch }}-${{ inputs.compiler }}-${{ steps.compiler.outputs.major }}-ccache-${{ steps.pr.outputs.name }}-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: | + ${{ runner.os }}-${{ runner.arch }}-${{ inputs.compiler }}-${{ steps.compiler.outputs.major }}-ccache-${{ steps.pr.outputs.name }}- + ${{ runner.os }}-${{ runner.arch }}-${{ inputs.compiler }}-${{ steps.compiler.outputs.major }}-ccache-develop- + ${{ runner.os }}-${{ runner.arch }}-${{ inputs.compiler }}-${{ steps.compiler.outputs.major }}-ccache- + + ############################################################################ + - name: Configure Build Caching + id: ccache-configure + shell: bash + env: + # MSVC objects are considerably larger than the gcc/clang ones + maxsize: ${{ runner.os == 'Windows' && '1G' || '500M' }} + run: | + mkdir -p "$GITHUB_WORKSPACE/.ccache" + + { + echo "CCACHE_COMPRESS=true" + echo "CCACHE_COMPRESSLEVEL=6" + echo "CCACHE_DIR=$GITHUB_WORKSPACE/.ccache" + echo "CCACHE_MAXSIZE=$maxsize" + echo "CMAKE_C_COMPILER_LAUNCHER=ccache" + echo "CMAKE_CXX_COMPILER_LAUNCHER=ccache" + } >>"$GITHUB_ENV" + + ############################################################################ + # Reset the hit/miss counters (not the cached objects) so the statistics + # printed after the build describe this run only. + - name: Reset ccache Statistics + id: ccache-reset + shell: bash + run: | + ccache --version + ccache --zero-stats + + ############################################################################ + # The preset input, or else the CI preset of the runner. + - name: Resolve CMake presets + id: preset + shell: bash + env: + preset: ${{ inputs.preset }} + run: | + if [[ -n "$preset" ]]; then + echo "name=$preset" >> "$GITHUB_OUTPUT" + exit 0 + fi + case "$RUNNER_OS-$RUNNER_ARCH" in + Linux-X64) preset=linux-x64-ci ;; + Linux-ARM64) preset=linux-arm64-ci ;; + macOS-ARM64) preset=macos-arm64-ci ;; + Windows-X64) preset=windows-x64-ci ;; + *) + echo >&2 "ERROR: no CI preset mapped for $RUNNER_OS-$RUNNER_ARCH" + exit 1 + ;; + esac + echo "name=$preset" >> "$GITHUB_OUTPUT" + + ############################################################################ + - name: Build + id: build + shell: bash + env: + PRESET: ${{ steps.preset.outputs.name }} + PREFIX_PATH: ${{ inputs.prefix_path }} + TOPP_BIN_DIR: ${{ inputs.topp_bin_dir }} + EXAMPLES_DIR: ${{ inputs.examples_dir }} + CORE_VERSION_FULLSTRING: ${{ inputs.core_version_fullstring }} + CMAKE_ARGS: ${{ inputs.cmake_args }} + # Also sets LSMinimumSystemVersion of the GUI apps and the installer's minimum macOS. + MACOSX_DEPLOYMENT_TARGET: "${{ inputs.macosx_deployment_target }}" + run: | + set -euxo pipefail + + options=( + "-DCMAKE_PREFIX_PATH=$PREFIX_PATH" + "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install" + ) + if [[ -n "$TOPP_BIN_DIR" ]]; then + options+=("-DOPENMS_TOPP_BIN_DIR=$TOPP_BIN_DIR") + fi + if [[ -n "$EXAMPLES_DIR" ]]; then + options+=("-DOPENMS_EXAMPLES_DIR=$EXAMPLES_DIR") + fi + if [[ -n "$CORE_VERSION_FULLSTRING" ]]; then + options+=("-DOPENMS_CORE_VERSION_FULLSTRING=$CORE_VERSION_FULLSTRING") + fi + while IFS= read -r arg; do + if [[ -n "$arg" ]]; then + options+=("$arg") + fi + done <<<"$CMAKE_ARGS" + + cmake --preset "$PRESET" "${options[@]}" + cmake --build --preset "$PRESET" + + - name: Verify public headers + if: ${{ runner.os == 'Linux' && runner.arch == 'X64' && inputs.preset == '' }} + shell: bash + run: cmake --build --preset "${{ steps.preset.outputs.name }}" --target all_verify_interface_header_sets + + ############################################################################ + # Hit rate and cache size for this run. A diagnostic that must never decide + # the fate of the job (an older ccache without --verbose would otherwise skip + # the tests). + - name: Report ccache Statistics + if: ${{ !cancelled() && steps.ccache-configure.outcome == 'success' && steps.ccache-reset.outcome == 'success' }} + continue-on-error: true + shell: bash + run: ccache --show-stats --verbose + + ############################################################################ + - name: Test + if: ${{ inputs.run_tests == 'true' }} + shell: bash + run: ctest --preset "${{ steps.preset.outputs.name }}" + + ############################################################################ + # Saved only when the build succeeded (a failed build would pin a partial + # cache as the newest of the branch); test failures still save. + - name: Save Build Cache + if: ${{ !cancelled() && steps.build.outcome == 'success' }} + uses: actions/cache/save@v5 + with: + path: .ccache + # Reuse the restore step's primary key so the two can never drift apart. + key: ${{ steps.ccache-restore.outputs.cache-primary-key }} diff --git a/.github/actions/dependencies/action.yml b/.github/actions/dependencies/action.yml new file mode 100644 index 000000000..3121f2553 --- /dev/null +++ b/.github/actions/dependencies/action.yml @@ -0,0 +1,91 @@ +name: "Install the Dependencies of the GUI Layer" +description: >- + Qt, the build tools and ccache for building OpenMS-TOPP-GUI. + The OpenMS core itself comes from the openms-core action. +inputs: + compiler: + description: "The name of the compiler to use (cl.exe, xcode, g++, clang++)" + required: true + doc: + description: "'true' also installs doxygen for the documentation" + required: false + default: "false" +outputs: + cmake_prefix: + description: "Paths to add to CMAKE_PREFIX_PATH (';'-separated, may be empty)" + value: ${{ steps.deps-windows.outputs.cmake_prefix }}${{ steps.deps-linux.outputs.cmake_prefix }}${{ steps.deps-macos.outputs.cmake_prefix }} +runs: + using: "composite" + steps: + - name: Install Qt (Windows) + if: runner.os == 'Windows' + uses: jurplel/install-qt-action@v4 + with: + version: '6.8.3' ## Qt 6.8.3 ships no msvc2026 build; the win64_msvc2022_64 binaries are + ## ABI-compatible with the VS2026 (v145) toolchain (shared v14x ABI). + arch: 'win64_msvc2022_64' + cache: 'false' + archives: 'qtsvg qtimageformats qtbase' + + - name: "Dependencies: Windows" + id: deps-windows + if: runner.os == 'Windows' + shell: bash + env: + DOC: ${{ inputs.doc }} + run: | + args=() + if [[ "$DOC" != "true" ]]; then + args+=(--skip-doc-deps) + fi + bash tools/ci/deps-windows.sh "${args[@]}" + echo "C:\Program Files (x86)\NSIS" >> "$GITHUB_PATH" + # install-qt-action puts Qt's bin/ on PATH, which CMake searches; naming the + # prefix as well makes the lookup independent of that + echo "cmake_prefix=$(cygpath -m "$QT_ROOT_DIR")" >>"$GITHUB_OUTPUT" + + - name: "Dependencies: Linux" + id: deps-linux + if: runner.os == 'Linux' + shell: bash + env: + COMPILER: ${{ inputs.compiler }} + DOC: ${{ inputs.doc }} + run: | + args=() + if [[ "$DOC" != "true" ]]; then + args+=(--skip-doc-deps) + fi + bash tools/ci/deps-ubuntu.sh "${args[@]}" + + if [[ "$COMPILER" == clang* ]]; then + major_ver=$(clang -dumpversion | cut -d. -f1) + # libomp: the OpenMS package requires OpenMP of its consumers (OpenMP::OpenMP_CXX is + # PUBLIC), and clang finds it only with LLVM's runtime + sudo apt-get -qq install -y "libomp-${major_ver}-dev" "llvm-${major_ver}" + for tool in /usr/bin/llvm-*-"${major_ver}"; do + base=$(basename "$tool" "-${major_ver}") + sudo ln -sf "$tool" "/usr/local/bin/${base}" + done + fi + + echo "cmake_prefix=" >>"$GITHUB_OUTPUT" + + - name: "Dependencies: macOS" + id: deps-macos + if: runner.os == 'macOS' + env: + # otherwise brew updates the preinstalled packages that depend on what it installs + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 + DOC: ${{ inputs.doc }} + shell: bash + run: | + args=() + if [[ "$DOC" != "true" ]]; then + args+=(--skip-doc-deps) + fi + bash tools/ci/deps-macos.sh "${args[@]}" + + brew_path=$(brew --prefix) + echo "$brew_path/bin" >>"$GITHUB_PATH" + echo "cmake_prefix=$brew_path" >>"$GITHUB_OUTPUT" diff --git a/.github/actions/openms-core/action.yml b/.github/actions/openms-core/action.yml new file mode 100644 index 000000000..de2cd0597 --- /dev/null +++ b/.github/actions/openms-core/action.yml @@ -0,0 +1,515 @@ +name: "Provide the OpenMS Core" +description: >- + Makes an OpenMS core (libraries, CLI layer, headers, CMake package, shared data + and the TOPP tools) available to the job, built without the GUI, and tells where + it is. The GUI layer is then configured with + -DCMAKE_PREFIX_PATH=";" -DOPENMS_TOPP_BIN_DIR= + -DOPENMS_EXAMPLES_DIR= -DOPENMS_CORE_VERSION_FULLSTRING=. + + Modes: + source build OpenMS/OpenMS at the input 'ref' with its own presets and vcpkg + (WITH_GUI=OFF while that option exists, BUILD_TOPP_TOOLS=ON) and install + it into a fixed folder below RUNNER_TEMP. The installation is kept in the + Actions cache, keyed by the OpenMS commit, so a commit is built only once + per runner type. + sdk download the OpenMS SDK of a nightly or release from archive.openms.de + and install the OpenMS installer of the same folder silently (the SDK has + no TOPP tools and no example data). + + THE DEFAULT MODE (used when 'mode' is empty) is set in the first step below, + DEFAULT_MODE: 'source', because the nightlies and releases of OpenMS up to now are + built WITH_GUI=ON: their tool registry lists ExecutePipeline and ImageCreator, + which cmake/find_openms.cmake rejects (both registries in one installation make + every TOPP tool fail). Once OpenMS no longer has the WITH_GUI option and a nightly + without the GUI is published, change it to 'sdk'. That is the only place to + change: the workflows pass a mode only where they need a specific one. + +inputs: + mode: + description: "'source', 'sdk', or empty for the default (see the description of this action)" + required: false + default: "" + ref: + description: "source: branch, tag or commit of OpenMS/OpenMS to build" + required: false + default: "develop" + exact: + description: >- + source: 'true' builds the current commit of 'ref' unless it is cached already. + 'false' takes the newest cached build of 'ref' if there is one, even if 'ref' + has moved on since (the nightly workflow refreshes it every day), so a pull + request does not wait for a build of the core. + required: false + default: "false" + channel: + description: "sdk: 'nightly', 'release' or a release candidate channel 'rc'" + required: false + default: "nightly" + version: + description: >- + sdk: the folder on the archive server: 'latest', a nightly date YYYY.MM.DD or a + release X.Y.Z + required: false + default: "latest" + sdk: + description: >- + sdk: 'false' installs only the OpenMS installer (prefix_path is then empty), for + testing the installers of this repository on top of it + required: false + default: "true" + ssh_key: + description: "source: SSH private key for the vcpkg binary cache on the archive server" + required: false + ssh_host: + description: "source: SSH host for the vcpkg binary cache" + required: false + ssh_port: + description: "source: SSH port for the vcpkg binary cache" + required: false + ssh_user: + description: "source: SSH user for the vcpkg binary cache" + required: false + +outputs: + prefix_path: + description: "CMAKE_PREFIX_PATH entries (';'-separated) of the core and its build dependencies" + value: ${{ steps.source-result.outputs.prefix_path || steps.sdk-result.outputs.prefix_path }} + bin_dir: + description: "The directory with the TOPP tools of the core (OPENMS_TOPP_BIN_DIR)" + value: ${{ steps.source-result.outputs.bin_dir || steps.sdk-result.outputs.bin_dir }} + examples_dir: + description: "The example data of the core, share/OpenMS/examples (OPENMS_EXAMPLES_DIR)" + value: ${{ steps.source-result.outputs.examples_dir || steps.sdk-result.outputs.examples_dir }} + version_fullstring: + description: "The full version string of the core, e.g. 3.7.0-pre-nightly-2026-11-06 (OPENMS_CORE_VERSION_FULLSTRING)" + value: ${{ steps.source-result.outputs.version_fullstring || steps.sdk-result.outputs.version_fullstring }} + install_dir: + description: "sdk: where the OpenMS installer installed the core" + value: ${{ steps.source-result.outputs.install_dir || steps.sdk-result.outputs.install_dir }} + commit: + description: "source: the OpenMS commit the core was built from" + value: ${{ steps.source-result.outputs.commit || steps.sdk-result.outputs.commit }} + +runs: + using: "composite" + steps: + + ############################################################################ + # Common + - name: Check the Inputs and Choose the Folders + id: setup + shell: bash + env: + MODE: ${{ inputs.mode }} + # The default mode: 'source' while the OpenMS nightlies still contain the GUI, + # then 'sdk' (see the description of this action). + DEFAULT_MODE: source + run: | + mode=${MODE:-$DEFAULT_MODE} + case "$mode" in + source|sdk) ;; + *) echo >&2 "ERROR: unknown mode '$mode' (source or sdk)"; exit 1 ;; + esac + echo "OpenMS core mode: $mode" + echo "mode=$mode" >>"$GITHUB_OUTPUT" + # forward slashes on Windows: these paths go into CMake and into the cache action + tmp=$RUNNER_TEMP + if [[ "$RUNNER_OS" == "Windows" ]]; then + tmp=$(cygpath -m "$RUNNER_TEMP") + fi + { + # the installation of a source build and its vcpkg dependencies; the + # libraries are linked with absolute RPATHs into it, so a cached build is + # restored to exactly this place + echo "core_dir=$tmp/openms-core" + echo "build_dir=$tmp/openms-core-build" + echo "download_dir=$tmp/openms-core-download" + } >>"$GITHUB_OUTPUT" + + ############################################################################ + # Mode 'source' + - name: Resolve the OpenMS Commit + id: commit + if: steps.setup.outputs.mode == 'source' + shell: bash + env: + REF: ${{ inputs.ref }} + run: | + if [[ "$REF" =~ ^[0-9a-f]{40}$ ]]; then + sha=$REF + else + refs=$(git ls-remote https://github.com/OpenMS/OpenMS.git \ + "refs/heads/$REF" "refs/tags/$REF" "refs/tags/$REF^{}") + # an annotated tag is listed twice; its commit is the peeled entry + sha=$(awk -v r="refs/tags/$REF^{}" '$2 == r { print $1 }' <<<"$refs") + if [[ -z "$sha" ]]; then + sha=$(awk 'NR == 1 { print $1 }' <<<"$refs") + fi + if [[ -z "$sha" ]]; then + echo >&2 "ERROR: OpenMS/OpenMS has no branch or tag '$REF'" + exit 1 + fi + fi + echo "OpenMS $REF is at $sha" + { + echo "sha=$sha" + # bump the version to drop all cached cores, e.g. after a change of the + # configuration below or when the GUI layer needs a newer core (v2: cores + # from before OpenMS/OpenMS#10844 lack the PATH lookup of TOPP tools). The + # ref ends with ':', which no git ref name contains, so the prefix of + # 'develop' does not match 'develop-foo'. + echo "key_prefix=openms-core-v2-$RUNNER_OS-$RUNNER_ARCH-$REF:" + } >>"$GITHUB_OUTPUT" + + - name: Restore a Cached Build of the Core + id: core-cache + if: steps.setup.outputs.mode == 'source' + uses: actions/cache/restore@v5 + with: + path: | + ${{ steps.setup.outputs.core_dir }} + !${{ steps.setup.outputs.core_dir }}/vcpkg_installed/*/debug + !${{ steps.setup.outputs.core_dir }}/vcpkg_installed/*/tools + key: ${{ steps.commit.outputs.key_prefix }}${{ steps.commit.outputs.sha }} + restore-keys: ${{ inputs.exact != 'true' && steps.commit.outputs.key_prefix || '' }} + + - name: Check out OpenMS + if: steps.setup.outputs.mode == 'source' && steps.core-cache.outputs.cache-matched-key == '' + uses: actions/checkout@v6 + with: + repository: OpenMS/OpenMS + # the branch or tag rather than the commit: the full version string of the + # build is made from the name of the branch (e.g. 3.7.0-pre-nightly-) + ref: ${{ inputs.ref }} + path: _openms_core + persist-credentials: false + + - name: Install the Build Dependencies of the Core + if: steps.setup.outputs.mode == 'source' && steps.core-cache.outputs.cache-matched-key == '' + shell: bash + env: + # otherwise brew updates the preinstalled packages that depend on what it installs + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 + run: | + cd _openms_core || exit 1 + # the vcpkg registry; its history is needed for the version overrides + git submodule update --init vcpkg + case "$RUNNER_OS" in + Linux) + bash tools/ci/deps-ubuntu.sh --skip-gui-deps + ;; + macOS) + bash tools/ci/deps-macos.sh --skip-doc-deps --skip-gui-deps + { + echo "$(brew --prefix)/bin" + echo "$(brew --prefix flex)/bin" + echo "$(brew --prefix bison)/bin" + } >>"$GITHUB_PATH" + ;; + Windows) + # cmake and ninja come with the dependencies of this repository. The + # vcpkg cache action needs rsync (in the version the server expects; its + # package also provides the ssh.exe the action uses) and rclone (its + # read-only fallback without the secrets), as in tools/ci/deps-windows.sh + # of OpenMS. + work_dir=$(mktemp -d) + git clone https://github.com/OpenMS/chocolatey-packages.git "$work_dir/chocolatey-packages" + (cd "$work_dir/chocolatey-packages" && make && + cd build && choco install -y --no-progress -s . rclone rsync) + rm -rf "$work_dir" + ;; + esac + + # skipped when the job has a Visual Studio environment already (VCINSTALLDIR): + # vcvars run twice can make PATH too long for cmd + - name: Emulate a Visual Studio Shell (Windows) + if: steps.setup.outputs.mode == 'source' && steps.core-cache.outputs.cache-matched-key == '' && runner.os == 'Windows' && env.VCINSTALLDIR == '' + uses: egor-tensin/vs-shell@v2 + with: { arch: x64 } + + # The vcpkg binary cache of the core build on the archive server. Its name + # differs from the one of the OpenMS repository (vcpkg-cache), so the two never + # overwrite each other's archives. + - name: Restore the vcpkg Binary Cache + if: steps.setup.outputs.mode == 'source' && steps.core-cache.outputs.cache-matched-key == '' + uses: OpenMS/ci-tools/actions/rsync-cache@b0622a9a8ecfe12fa6e896795f45fddad12faa6c + with: + name: vcpkg-cache-openms-topp-gui + path: ${{ github.workspace }}/.vcpkg-cache + key: ${{ runner.os }}-${{ runner.arch }}-core-vcpkg-${{ hashFiles('_openms_core/vcpkg.json', '_openms_core/vcpkg-configuration.json') }} + ssh_key: ${{ inputs.ssh_key }} + ssh_host: ${{ inputs.ssh_host }} + ssh_port: ${{ inputs.ssh_port }} + ssh_user: ${{ inputs.ssh_user }} + + - name: Point vcpkg at the Local Binary Cache + if: steps.setup.outputs.mode == 'source' && steps.core-cache.outputs.cache-matched-key == '' + shell: bash + run: | + mkdir -p "$GITHUB_WORKSPACE/.vcpkg-cache" + echo "VCPKG_BINARY_SOURCES=clear;files,$GITHUB_WORKSPACE/.vcpkg-cache,readwrite" >>"$GITHUB_ENV" + + - name: Set up vcpkg + if: steps.setup.outputs.mode == 'source' && steps.core-cache.outputs.cache-matched-key == '' + uses: lukka/run-vcpkg@v11 + with: + vcpkgDirectory: ${{ github.workspace }}/_openms_core/vcpkg + vcpkgJsonGlob: _openms_core/vcpkg.json + doNotCache: true + + - name: Build and Install the Core + id: core-build + if: steps.setup.outputs.mode == 'source' && steps.core-cache.outputs.cache-matched-key == '' + shell: bash + env: + CORE_DIR: ${{ steps.setup.outputs.core_dir }} + BUILD_DIR: ${{ steps.setup.outputs.build_dir }} + # the same minimum macOS as the GUI layer (see the build action) + MACOSX_DEPLOYMENT_TARGET: "15.0" + # the compiler cache of this repository's build is not used for the core + CMAKE_C_COMPILER_LAUNCHER: "" + CMAKE_CXX_COMPILER_LAUNCHER: "" + run: | + set -euxo pipefail + cd _openms_core || exit 1 + + case "$RUNNER_OS-$RUNNER_ARCH" in + Linux-X64) preset=linux-x64-ci ;; + Linux-ARM64) preset=linux-arm64-ci ;; + macOS-ARM64) preset=macos-arm64-ci ;; + Windows-X64) preset=windows-x64-ci ;; + *) echo >&2 "ERROR: no OpenMS preset for $RUNNER_OS-$RUNNER_ARCH"; exit 1 ;; + esac + + # The CI preset of OpenMS, reduced to what the GUI layer needs: the libraries + # and the TOPP tools, no tests, no documentation, and none of the optional + # dependencies that take long to build (ONNX Runtime, the Thermo bridge). + options=( + "-DCMAKE_INSTALL_PREFIX=$CORE_DIR/install" + "-DVCPKG_INSTALLED_DIR=$CORE_DIR/vcpkg_installed" + "-DVCPKG_MANIFEST_FEATURES=isospec;eol-bspline;simde;nlohmann-json;sqlitecpp;opentims" + -DBUILD_TOPP_TOOLS=ON + -DINSTALL_OPENMS_EXAMPLES=ON + -DENABLE_CLASS_TESTING=OFF + -DENABLE_TOPP_TESTING=OFF + -DENABLE_DOCS=OFF + -DENABLE_TUTORIALS=OFF + -DOPENMS_TEST_INSTALLED_CONSUMER=OFF + -DOPENMS_VERIFY_INTERFACE_HEADER_SETS=OFF + -DWITH_ONNX=OFF + -DWITH_THERMO_RAW=OFF + -DPACKAGE_TYPE=none + ) + # an OpenMS that still has its own GUI is built without it + if grep -q 'option(WITH_GUI' CMakeLists.txt; then + options+=(-DWITH_GUI=OFF) + fi + if [[ "$RUNNER_OS" == "macOS" ]]; then + options+=("-DCMAKE_PREFIX_PATH=$(brew --prefix)") + fi + # CMake with Ninja prefers c++/g++ over cl.exe, and the Windows runners have + # MinGW compilers on PATH (the build action of OpenMS sets CC/CXX as well) + if [[ "$RUNNER_OS" == "Windows" ]]; then + export CC=cl.exe CXX=cl.exe + # vcpkg builds the ports below its own directory, which is deeper here + # (D:\a\OpenMS-TOPP-GUI\OpenMS-TOPP-GUI\_openms_core\vcpkg) than in the + # OpenMS repository; the deepest object paths of aws-sdk-cpp then exceed + # MAX_PATH and cl.exe fails with C1083. A short build tree root avoids it. + options+=("-DVCPKG_INSTALL_OPTIONS=--x-buildtrees-root=C:/vcpkg-bt") + fi + + cmake --preset "$preset" -B "$BUILD_DIR" "${options[@]}" + cmake --build "$BUILD_DIR" + + rm -rf "$CORE_DIR/install" + for component in library library_cli \ + headers OpenMS_headers OpenMS_CLI_headers OpenSwathAlgo_headers thirdparty_headers \ + cmake cmake_cli share examples Applications; do + cmake --install "$BUILD_DIR" --component "$component" + done + + # what later runs that restore this build need to know + cache_var() { sed -n "s/^$1:[A-Z]*=//p" "$BUILD_DIR/CMakeCache.txt" | head -n 1; } + fullstring=$(cache_var CF_OPENMS_PACKAGE_VERSION_FULLSTRING) + { + echo "commit=$(git rev-parse HEAD)" + echo "triplet=$(cache_var VCPKG_TARGET_TRIPLET)" + echo "version_fullstring=$fullstring" + } | tee "$CORE_DIR/openms-core.txt" + echo "commit=$(git rev-parse HEAD)" >>"$GITHUB_OUTPUT" + + # Saved right away, so the build is kept even when the tests of the GUI fail. + # The key names the commit that was built (the branch may have moved since the + # lookup above). + - name: Save the Build of the Core + if: steps.setup.outputs.mode == 'source' && steps.core-build.outcome == 'success' + uses: actions/cache/save@v5 + with: + path: | + ${{ steps.setup.outputs.core_dir }} + !${{ steps.setup.outputs.core_dir }}/vcpkg_installed/*/debug + !${{ steps.setup.outputs.core_dir }}/vcpkg_installed/*/tools + key: ${{ steps.commit.outputs.key_prefix }}${{ steps.core-build.outputs.commit }} + + - name: Describe the Core (source) + id: source-result + if: steps.setup.outputs.mode == 'source' + shell: bash + env: + CORE_DIR: ${{ steps.setup.outputs.core_dir }} + run: | + info="$CORE_DIR/openms-core.txt" + if [[ ! -f "$info" ]]; then + echo >&2 "ERROR: no OpenMS core in $CORE_DIR" + exit 1 + fi + get() { sed -n "s/^$1=//p" "$info"; } + triplet=$(get triplet) + deps="$CORE_DIR/vcpkg_installed/$triplet" + echo "Using OpenMS $(get version_fullstring) (commit $(get commit))" + if [[ "$RUNNER_OS" == "Windows" ]]; then + # the DLLs of the vcpkg dependencies, for the TOPP tools and the tests + cygpath -w "$deps/bin" >>"$GITHUB_PATH" + fi + { + # the vcpkg tree: the shared libraries the core links (found through its package) + echo "prefix_path=$CORE_DIR/install;$deps" + echo "bin_dir=$CORE_DIR/install/bin" + echo "examples_dir=$CORE_DIR/install/share/OpenMS/examples" + echo "version_fullstring=$(get version_fullstring)" + echo "commit=$(get commit)" + echo "install_dir=$CORE_DIR/install" + } >>"$GITHUB_OUTPUT" + + ############################################################################ + # Mode 'sdk' + - name: Find the SDK and the Installer + id: sdk-resolve + if: steps.setup.outputs.mode == 'sdk' + shell: bash + env: + CHANNEL: ${{ inputs.channel }} + VERSION: ${{ inputs.version }} + NEED_SDK: ${{ inputs.sdk }} + run: | + bash "$GITHUB_ACTION_PATH/resolve-sdk.sh" "$CHANNEL" "$VERSION" "$RUNNER_OS" "$RUNNER_ARCH" "$NEED_SDK" | + tee -a "$GITHUB_OUTPUT" + + - name: Download the SDK and the Installer + if: steps.setup.outputs.mode == 'sdk' + shell: bash + env: + DOWNLOAD_DIR: ${{ steps.setup.outputs.download_dir }} + SDK_URL: ${{ steps.sdk-resolve.outputs.sdk_url }} + SDK_NAME: ${{ steps.sdk-resolve.outputs.sdk_name }} + INSTALLER_URL: ${{ steps.sdk-resolve.outputs.installer_url }} + INSTALLER_NAME: ${{ steps.sdk-resolve.outputs.installer_name }} + run: | + set -euo pipefail + mkdir -p "$DOWNLOAD_DIR" + fetch() { + curl --fail --silent --show-error --location --retry 3 --retry-all-errors -o "$DOWNLOAD_DIR/$2" "$1" + } + fetch "$INSTALLER_URL" "$INSTALLER_NAME" + if [[ -n "$SDK_URL" ]]; then + fetch "$SDK_URL" "$SDK_NAME" + fi + ls -l "$DOWNLOAD_DIR" + + - name: Extract the SDK + id: sdk-extract + if: steps.setup.outputs.mode == 'sdk' && inputs.sdk == 'true' + shell: bash + env: + CORE_DIR: ${{ steps.setup.outputs.core_dir }} + DOWNLOAD_DIR: ${{ steps.setup.outputs.download_dir }} + SDK_NAME: ${{ steps.sdk-resolve.outputs.sdk_name }} + run: | + set -euo pipefail + rm -rf "$CORE_DIR" + mkdir -p "$CORE_DIR" + case "$SDK_NAME" in + *.zip) 7z x -y -bd "-o$CORE_DIR" "$DOWNLOAD_DIR/$SDK_NAME" >/dev/null ;; + *.tar.gz) tar -xzf "$DOWNLOAD_DIR/$SDK_NAME" -C "$CORE_DIR" ;; + esac + # the archive has a single top-level folder named like the archive + sdk_dir="$CORE_DIR/${SDK_NAME%.zip}" + sdk_dir="${sdk_dir%.tar.gz}" + if [[ ! -d "$sdk_dir" ]]; then + echo >&2 "ERROR: the SDK archive has no folder ${sdk_dir##*/}" + ls "$CORE_DIR" + exit 1 + fi + echo "sdk_dir=$sdk_dir" >>"$GITHUB_OUTPUT" + + - name: Install the OpenMS Installer (Linux) + id: install-linux + if: steps.setup.outputs.mode == 'sdk' && runner.os == 'Linux' + shell: bash + env: + DOWNLOAD_DIR: ${{ steps.setup.outputs.download_dir }} + INSTALLER_NAME: ${{ steps.sdk-resolve.outputs.installer_name }} + run: | + sudo apt-get -qq update + sudo apt-get -qq install -y --no-install-recommends "$DOWNLOAD_DIR/$INSTALLER_NAME" + echo "install_dir=/usr" >>"$GITHUB_OUTPUT" + + - name: Install the OpenMS Installer (macOS) + id: install-macos + if: steps.setup.outputs.mode == 'sdk' && runner.os == 'macOS' + shell: bash + env: + DOWNLOAD_DIR: ${{ steps.setup.outputs.download_dir }} + INSTALLER_NAME: ${{ steps.sdk-resolve.outputs.installer_name }} + FULLSTRING: ${{ steps.sdk-resolve.outputs.version_fullstring }} + run: | + sudo installer -pkg "$DOWNLOAD_DIR/$INSTALLER_NAME" -target / + # the prefix of the OpenMS package (cmake/package_mac_productbuild.cmake of OpenMS) + echo "install_dir=/Applications/OpenMS-$FULLSTRING" >>"$GITHUB_OUTPUT" + + - name: Install the OpenMS Installer (Windows) + id: install-windows + if: steps.setup.outputs.mode == 'sdk' && runner.os == 'Windows' + shell: pwsh + env: + DOWNLOAD_DIR: ${{ steps.setup.outputs.download_dir }} + INSTALLER_NAME: ${{ steps.sdk-resolve.outputs.installer_name }} + run: | + $target = 'C:\OpenMS-core' + # NSIS: /S silent, which needs the install mode (NsisMultiUser): /allusers, so + # that the GUI installer finds the installation under HKLM; /D the folder + # (last, unquoted) + $p = Start-Process -FilePath (Join-Path $env:DOWNLOAD_DIR $env:INSTALLER_NAME) ` + -ArgumentList '/S', '/allusers', "/D=$target" -Wait -PassThru + if ($p.ExitCode -ne 0) { throw "The OpenMS installer failed with exit code $($p.ExitCode)" } + if (-not (Test-Path (Join-Path $target 'bin'))) { throw "The OpenMS installer left no $target\bin" } + "install_dir=C:/OpenMS-core" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + + - name: Describe the Core (sdk) + id: sdk-result + if: steps.setup.outputs.mode == 'sdk' + shell: bash + env: + SDK_DIR: ${{ steps.sdk-extract.outputs.sdk_dir }} + INSTALL_DIR: ${{ steps.install-linux.outputs.install_dir }}${{ steps.install-macos.outputs.install_dir }}${{ steps.install-windows.outputs.install_dir }} + FULLSTRING: ${{ steps.sdk-resolve.outputs.version_fullstring }} + run: | + set -euo pipefail + bin_dir="$INSTALL_DIR/bin" + if [[ ! -e "$bin_dir/FileInfo" && ! -e "$bin_dir/FileInfo.exe" ]]; then + echo >&2 "ERROR: the OpenMS installer installed no TOPP tools into $bin_dir" + exit 1 + fi + if [[ "$RUNNER_OS" == "Windows" ]]; then + # the core's DLLs for the TOPP tools and the tests + cygpath -w "$bin_dir" >>"$GITHUB_PATH" + fi + echo "Using OpenMS $FULLSTRING: SDK ${SDK_DIR:-(none)}, installation $INSTALL_DIR" + { + echo "prefix_path=$SDK_DIR" + echo "bin_dir=$bin_dir" + echo "examples_dir=$INSTALL_DIR/share/OpenMS/examples" + echo "version_fullstring=$FULLSTRING" + echo "install_dir=$INSTALL_DIR" + echo "commit=" + } >>"$GITHUB_OUTPUT" diff --git a/.github/actions/openms-core/resolve-sdk.sh b/.github/actions/openms-core/resolve-sdk.sh new file mode 100755 index 000000000..25436fb4f --- /dev/null +++ b/.github/actions/openms-core/resolve-sdk.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- +# +# Find the OpenMS SDK and the OpenMS installer of one platform on the archive +# server, as published by the release workflow of OpenMS: +# +# ///OpenMS-SDK--. +# ///OpenMS-- +# +# Usage: resolve-sdk.sh > [sdk] +# +# version nightly: YYYY.MM.DD (YYYY-MM-DD is accepted), otherwise X.Y.Z, +# latest: the newest folder of the channel that has both files +# sdk 'true' (default) or 'false': with false only the installer is +# looked for (sdk_name and sdk_url are then empty) +# +# Prints shell assignments (version, sdk_name, sdk_url, installer_name, +# installer_url, version_fullstring), e.g. for >> "$GITHUB_OUTPUT". +# OPENMS_ARCHIVE_URL overrides the base URL. +set -euo pipefail + +if [[ $# -lt 4 || $# -gt 5 ]]; then + echo >&2 "Usage: $0 > [sdk]" + exit 2 +fi + +channel=$1 +version=$2 +os=$3 +arch=$4 +need_sdk=${5:-true} +base=${OPENMS_ARCHIVE_URL:-https://archive.openms.de/openms/OpenMSInstaller} + +# nightly folders are dates; those of release and of the release candidates +# (rc1, rc2, ... as named by the version action) are versions +case "$channel" in + nightly) folder_regex='^[0-9]{4}\.[0-9]{2}\.[0-9]{2}$' ;; + release|rc[0-9]*) folder_regex='^[0-9]+\.[0-9]+\.[0-9]+$' ;; + *) echo >&2 "ERROR: unknown channel '$channel' (nightly, release or rc)"; exit 2 ;; +esac + +# the platform names of the SDK (tools/ci/package_sdk.sh of OpenMS) and of the +# installers (cmake/package_*.cmake of OpenMS) +case "$os-$arch" in + Linux-X64) sdk_suffix=Linux-x86_64.tar.gz; installer_suffix=Debian-Linux-x86_64.deb ;; + Linux-ARM64) sdk_suffix=Linux-aarch64.tar.gz; installer_suffix=Debian-Linux-aarch64.deb ;; + macOS-ARM64) sdk_suffix=macOS-arm64.tar.gz; installer_suffix=macOS-Silicon.pkg ;; + macOS-X64) sdk_suffix=macOS-x86_64.tar.gz; installer_suffix=macOS-Intel.pkg ;; + Windows-X64) sdk_suffix=Windows-x64.zip; installer_suffix=Win64.exe ;; + *) echo >&2 "ERROR: no OpenMS packages for $os-$arch"; exit 2 ;; +esac + +# file and folder names of a directory listing of the archive server +listing() { + curl --fail --silent --show-error --location --retry 3 "$1/" | + grep -oE 'href="[^"?/][^"]*"' | sed -e 's/^href="//' -e 's/"$//' +} + +# the SDK and the installer in one folder; returns 1 if one of them is missing +find_in_folder() { + local folder=$1 files sdk installer + files=$(listing "$base/$channel/$folder") || return 1 + sdk=$(grep -xF "OpenMS-SDK-$folder-$sdk_suffix" <<<"$files" || true) + # the installer carries the full version string, which need not contain the + # folder name (a nightly is named after the date of its last commit) + installer=$(grep -E "^OpenMS-[0-9]+\.[0-9]+\.[0-9]+[^/]*-${installer_suffix//./\\.}\$" <<<"$files" | + grep -v '^OpenMS-SDK-' | grep -v '^OpenMS-GUI-' | sort -V | tail -n 1 || true) + if [[ -z "$installer" ]] || [[ "$need_sdk" == true && -z "$sdk" ]]; then + return 1 + fi + local sdk_url="" + if [[ "$need_sdk" == true ]]; then + sdk_url="$base/$channel/$folder/$sdk" + else + sdk="" + fi + local fullstring=${installer#OpenMS-} + fullstring=${fullstring%-"$installer_suffix"} + printf 'version=%s\n' "$folder" + printf 'sdk_name=%s\n' "$sdk" + printf 'sdk_url=%s\n' "$sdk_url" + printf 'installer_name=%s\n' "$installer" + printf 'installer_url=%s\n' "$base/$channel/$folder/$installer" + printf 'version_fullstring=%s\n' "$fullstring" +} + +if [[ "$version" == latest ]]; then + # newest first; a folder of a release or nightly that is still being uploaded, + # or whose build failed on this platform, is passed over + mapfile -t folders < <(listing "$base/$channel" | sed 's#/$##' | grep -E "$folder_regex" | sort -rV | head -n 10) + for folder in "${folders[@]}"; do + if find_in_folder "$folder"; then + exit 0 + fi + echo >&2 "note: $base/$channel/$folder has no complete set for $os-$arch, trying the one before" + done + echo >&2 "ERROR: none of the newest folders of $base/$channel has the SDK ($sdk_suffix) and the installer ($installer_suffix)" + exit 1 +fi + +if [[ "$channel" == nightly ]]; then + version=${version//-/.} +fi +if ! grep -qE "$folder_regex" <<<"$version"; then + echo >&2 "ERROR: '$version' is not a version of the $channel channel" + exit 2 +fi +if ! find_in_folder "$version"; then + echo >&2 "ERROR: $base/$channel/$version has no SDK OpenMS-SDK-$version-$sdk_suffix and installer OpenMS--$installer_suffix" + exit 1 +fi diff --git a/.github/actions/rsync/action.yml b/.github/actions/rsync/action.yml new file mode 100644 index 000000000..087d37b7e --- /dev/null +++ b/.github/actions/rsync/action.yml @@ -0,0 +1,115 @@ +name: "Prepare for rsync" +description: "Prepare the environment for running rsync" + +inputs: + key: + description: "The text of the SSH key, or empty if there is no ssh key available" + required: false + user: + description: "The SSH user" + required: false + host: + description: "The SSH host name or IP address" + required: false + port: + description: "The port number that SSH is running on" + required: false + src: + description: "The source directory, file, glob to upload" + required: true + dst: + description: "The remote directory to upload into" + required: true + mkpath: + description: "Should rsync create the destination directory?" + required: false + latest: + description: "If set, create a 'latest' symlink to the given location" + required: false + latest_dst: + description: "The remote location to store the latest symlink. Defaults to the parent of dst." + required: false + +outputs: + have_key: + description: "Set to 'true' if a SSH key is present and rsync can be called" + value: ${{ steps.ssh-key.outputs.have_key }} + +runs: + using: "composite" + steps: + + - name: "Prepare for rsync" + id: ssh-key + shell: bash + env: + SSH_KEY_TEXT: ${{ inputs.key }} + SSH_PORT: ${{ inputs.port }} + run: | + output=$HOME/.ssh/private.key + + if [[ -n "$SSH_KEY_TEXT" ]]; then + mkdir -p "$(dirname "$output")" + cat <<<"$SSH_KEY_TEXT" >"$output" + chmod 0600 "$output" + + { + echo "have_key=true" + echo "ssh=ssh -i $output -p $SSH_PORT -o StrictHostKeyChecking=accept-new" + } >>"$GITHUB_OUTPUT" + else + { + echo "have_key=false" + echo "ssh=echo" + } >>"$GITHUB_OUTPUT" + fi + + - name: "Upload via rsync" + if: steps.ssh-key.outputs.have_key == 'true' + shell: bash + env: + SSH_CMD: ${{ steps.ssh-key.outputs.ssh }} + SSH_USER: ${{ inputs.user }} + SSH_HOST: ${{ inputs.host }} + SRC_DIR: ${{ inputs.src }} + DST_DIR: ${{ inputs.dst }} + MKPATH: ${{ inputs.mkpath }} + LATEST: ${{ inputs.latest }} + LATEST_DST: ${{ inputs.latest_dst }} + run: | + if [[ -z "${SSH_USER:-}" ]] || [[ -z "${SSH_HOST:-}" ]]; then + echo >&2 "ERROR: missing user or host input!" + exit 1 + fi + + rsync_options=( + "--progress" + "-avz" + "-e" "$SSH_CMD" + ) + + if [[ "${MKPATH:-false}" == "true" ]]; then + rsync_options+=("--mkpath") + fi + + # Allow globing on the src directory: + # shellcheck disable=SC2086 + rsync \ + "${rsync_options[@]}" \ + $SRC_DIR \ + "${SSH_USER}@${SSH_HOST}:${DST_DIR}" + + if [[ -n "${LATEST:-}" ]]; then + ln -s "$LATEST" latest + + if [[ -z "${LATEST_DST:-}" ]]; then + LATEST_DST=$(dirname "$DST_DIR") + fi + + rsync \ + "${rsync_options[@]}" \ + "latest" \ + "${SSH_USER}@${SSH_HOST}:${LATEST_DST}" + + rm latest + fi diff --git a/.github/actions/version/action.yml b/.github/actions/version/action.yml new file mode 100644 index 000000000..929274029 --- /dev/null +++ b/.github/actions/version/action.yml @@ -0,0 +1,87 @@ +name: "Parse a Version Number from a Tag or Input" +description: "Extract a version number from a Git tag for the OpenMS release process" + +inputs: + version: + description: "If set, parse this version number instead of `GITHUB_REF_NAME`" + required: false + fallback: + description: "Text to use when this version is not a release. Defaults to nightly." + required: false + +outputs: + has_version: + description: "Set to 'true' if a valid version number was found" + value: ${{ steps.version.outputs.has_version }} + version: + description: "The discovered version number" + value: ${{ steps.version.outputs.version }} + type: + description: "The word 'release' or the fallback" + value: ${{ steps.version.outputs.type }} + name: + description: "Release name followed by version number" + value: ${{ steps.version.outputs.name }} + major: + description: "The major version number or 0" + value: ${{ steps.version.outputs.major }} + minor: + description: "The minor version number or 0" + value: ${{ steps.version.outputs.minor }} + patch: + description: "The patch version number or 0" + value: ${{ steps.version.outputs.patch }} + +runs: + using: "composite" + steps: + + - name: "Parse Version Number" + id: version + shell: bash + env: + VERSION_ALT: ${{ inputs.version }} + VERSION_FALLBACK: ${{ inputs.fallback }} + run: | + VERSION=$GITHUB_REF_NAME + + if [[ -n "${VERSION_ALT:-}" ]]; then + if [[ "$VERSION_ALT" =~ ^v ]]; then + VERSION=$VERSION_ALT + else + VERSION="v${VERSION_ALT}" + fi + fi + + if [[ "$VERSION" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + major="${BASH_REMATCH[1]}" + minor="${BASH_REMATCH[2]}" + patch="${BASH_REMATCH[3]}" + has_version=true + type=release + elif [[ "$VERSION" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)-?(.+)$ ]]; then + major="${BASH_REMATCH[1]}" + minor="${BASH_REMATCH[2]}" + patch="${BASH_REMATCH[3]}" + has_version=false # Not an official release + type="${BASH_REMATCH[4]}" # Stuff like "rc2" + else + major="$(date +%Y)" + minor="$(date +%m)" + patch="$(date +%d)" + has_version=false + type=${VERSION_FALLBACK:-nightly} + fi + + { + version="${major}.${minor}.${patch}" + + echo "has_version=$has_version" + echo "major=$major" + echo "minor=$minor" + echo "patch=$patch" + echo "version=$version" + echo "type=$type" + echo "name=${type}-${version}" + + } >>"$GITHUB_OUTPUT" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 000000000..797428097 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,69 @@ +name: "Nightly" + +# Every night the GUI layer is built and tested against the current OpenMS +# develop branch, built from source. The CI of OpenMS no longer compiles any GUI +# code, so this is where a change of the OpenMS API that breaks the GUI shows up. +# Each run also refreshes the cached build of OpenMS develop that the pull +# requests of this repository use. +# +# Triggers: +# - schedule: after the nightly of OpenMS (its update_nightly workflow starts at +# 01:07 UTC and its release workflow publishes the installers about an hour +# later). +# - repository_dispatch, event type 'openms-core-nightly': OpenMS can start this +# workflow at the end of its nightly release workflow, e.g. +# gh api repos/OpenMS/OpenMS-TOPP-GUI/dispatches \ +# -f event_type=openms-core-nightly +# with a token that may dispatch workflows in this repository. client_payload.ref +# (optional, e.g. -f 'client_payload[ref]=develop') is the OpenMS branch, tag or +# commit to build (default: develop). Only a build of develop is the cached core +# of the pull requests; a commit gets a cache entry of its own and a full version +# string without a branch name. +# - workflow_dispatch. +# +# The nightly installers (release.yml) are only built when the repository variable +# GUI_NIGHTLY_INSTALLERS is 'true': they are installed on top of the OpenMS nightly +# installers of the same day, which must not contain the GUI themselves (until +# OpenMS has dropped it, an installation would have two copies of every GUI tool). +on: + schedule: + - cron: "23 4 * * *" + repository_dispatch: + types: [openms-core-nightly] + workflow_dispatch: + inputs: + core_ref: + description: "Branch, tag or commit of OpenMS/OpenMS to build and test against" + type: string + default: "develop" + installers: + description: "Also build (and deploy) the nightly installers" + type: boolean + default: false + +permissions: + contents: read + +concurrency: + group: nightly-openms-core + cancel-in-progress: false + +jobs: + test: + name: "Test against OpenMS ${{ inputs.core_ref || github.event.client_payload.ref || 'develop' }}" + uses: ./.github/workflows/test.yml + with: + core_mode: source + core_ref: ${{ inputs.core_ref || github.event.client_payload.ref || 'develop' }} + core_exact: true + secrets: inherit + + installers: + name: "Nightly Installers" + if: ${{ (github.event_name == 'workflow_dispatch' && inputs.installers) || (github.event_name != 'workflow_dispatch' && vars.GUI_NIGHTLY_INSTALLERS == 'true') }} + uses: ./.github/workflows/release.yml + permissions: + contents: write + with: + deploy: true + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..741243436 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,568 @@ +name: "Release" + +# Builds, tests and packages the GUI layer on every platform, checks each package +# by installing it on top of the OpenMS installer it belongs to, uploads the +# packages to the archive server next to the OpenMS installers +# (/OpenMSInstaller///, e.g. nightly/2026.11.07 or release/3.7.0) +# and, for a tag vX.Y.Z, publishes a GitHub release. +# +# The OpenMS core the packages are built against: the tag vX.Y.Z of OpenMS for a +# tag of this repository, otherwise the branch nightly (the commit of today's +# OpenMS nightly installers), unless the input core_ref says otherwise. The smoke +# test takes the OpenMS installer of the same version from the archive server +# (release/X.Y.Z, or nightly/). +on: + workflow_dispatch: + inputs: + deploy: + description: >- + Upload the packages to the archive server and publish a tagged release. + Off, the workflow only builds and keeps its artifacts on the run: a dry + run of the release workflow on any branch. + type: boolean + default: true + core_ref: + description: "Branch, tag or commit of OpenMS/OpenMS to build against (default: vX.Y.Z for a release, else nightly)" + type: string + default: "" + core_mode: + description: "Where the OpenMS core comes from: source, sdk, or empty for the default of the openms-core action" + type: string + default: "" + # used by nightly.yml for the nightly installers + workflow_call: + inputs: + deploy: + type: boolean + default: true + core_ref: + type: string + default: "" + core_mode: + type: string + default: "" + push: + tags: + - 'v[0-9]*' + +permissions: + contents: read + +jobs: + version: + name: "Version" + runs-on: ubuntu-latest + outputs: + has_version: ${{ steps.version.outputs.has_version }} + version: ${{ steps.version.outputs.version }} + version_name: ${{ steps.version.outputs.name }} + version_type: ${{ steps.version.outputs.type }} + core_ref: ${{ steps.core.outputs.ref }} + core_channel: ${{ steps.core.outputs.channel }} + deploy: ${{ steps.core.outputs.deploy }} + steps: + - uses: actions/checkout@v6 + with: + sparse-checkout: .github/actions + persist-credentials: false + + - name: Parse the Version Number + id: version + uses: ./.github/actions/version + + - name: Choose the OpenMS Core + id: core + shell: bash + env: + CORE_REF: ${{ inputs.core_ref }} + TYPE: ${{ steps.version.outputs.type }} + EVENT: ${{ github.event_name }} + DEPLOY: ${{ inputs.deploy }} + run: | + # a tag of this repository is built against the same tag of OpenMS (a + # release or a release candidate), everything else against today's nightly + if [[ -z "$CORE_REF" ]]; then + if [[ "$GITHUB_REF_TYPE" == tag ]]; then + CORE_REF=$GITHUB_REF_NAME + else + CORE_REF=nightly + fi + fi + # a push of a tag always deploys; otherwise the input decides + if [[ "$EVENT" == push || "$DEPLOY" == true ]]; then + deploy=true + else + deploy=false + fi + { + echo "ref=$CORE_REF" + # the folder of the OpenMS installers on the archive server: the channel + # is the version type (nightly, release, rc), the folder the version + echo "channel=$TYPE" + echo "deploy=$deploy" + } | tee -a "$GITHUB_OUTPUT" + + build-and-test: + name: "Build, Test, and Package" + needs: version + strategy: + fail-fast: false + matrix: + include: + # Only one entry per operating system allowed. + - os: windows-2025 + compiler: cl.exe + - os: macos-15 + compiler: xcode + - os: ubuntu-24.04 + compiler: g++ + - os: ubuntu-24.04-arm + compiler: g++ + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Install Dependencies + id: deps + uses: ./.github/actions/dependencies + with: + compiler: ${{ matrix.compiler }} + doc: "true" + + - name: Provide the OpenMS Core + id: core + uses: ./.github/actions/openms-core + with: + mode: ${{ inputs.core_mode }} + ref: ${{ needs.version.outputs.core_ref }} + exact: "true" + channel: ${{ needs.version.outputs.core_channel }} + version: ${{ needs.version.outputs.version }} + ssh_key: ${{ secrets.ARCHIVE_RRSYNC_SSH }} + ssh_host: ${{ secrets.ARCHIVE_RRSYNC_HOST }} + ssh_port: ${{ secrets.ARCHIVE_RRSYNC_PORT }} + ssh_user: ${{ secrets.ARCHIVE_RRSYNC_USER }} + + - name: Build and Test the GUI Layer + id: build + uses: ./.github/actions/build + with: + compiler: ${{ matrix.compiler }} + prefix_path: ${{ steps.core.outputs.prefix_path }};${{ steps.deps.outputs.cmake_prefix }} + topp_bin_dir: ${{ steps.core.outputs.bin_dir }} + examples_dir: ${{ steps.core.outputs.examples_dir }} + core_version_fullstring: ${{ steps.core.outputs.version_fullstring }} + # the packages include the documentation of the GUI tools (component doc_gui) + cmake_args: -DENABLE_DOCS=ON + + # A failure here does not stop the release: the packages are then made without + # the documentation (component doc_gui installs only what was built). + - name: Build the Documentation + id: doc + continue-on-error: true + shell: bash + env: + PRESET: ${{ steps.build.outputs.preset }} + run: cmake --build "build/$PRESET" --target doc + + - name: Report a Failed Documentation Build + if: steps.doc.outcome == 'failure' + shell: bash + run: echo "::warning::The documentation could not be built; the packages of ${{ runner.os }} ${{ runner.arch }} do not contain it." + + # One copy of the html for the archive (deploy-docs below). + - name: Upload the Documentation + if: steps.doc.outcome == 'success' && runner.os == 'Linux' && runner.arch == 'X64' + uses: actions/upload-artifact@v7 + with: + name: documentation-gui + path: build/${{ steps.build.outputs.preset }}/doc/html-gui + + - name: Compute Package Type + id: package-info + shell: bash + run: | + if [[ "$RUNNER_OS" == "Linux" ]]; then + echo "type=deb" >>"$GITHUB_OUTPUT" + elif [[ "$RUNNER_OS" == "macOS" ]]; then + echo "type=pkg" >>"$GITHUB_OUTPUT" + elif [[ "$RUNNER_OS" == "Windows" ]]; then + echo "type=nsis" >>"$GITHUB_OUTPUT" + else + echo >&2 "ERROR: unrecognized RUNNER_OS=$RUNNER_OS" + exit 1 + fi + + - name: Import Apple Developer Certificates + id: apple-certs + if: runner.os == 'macOS' + shell: bash + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERT }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_PASSWORD }} + INSTALLER_CERTIFICATE: ${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_CERT }} + INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + if [[ -n "$APPLE_CERTIFICATE" ]]; then + echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 + echo "$INSTALLER_CERTIFICATE" | base64 --decode > installer_certificate.p12 + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security list-keychains -d user -s build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security set-keychain-settings -t 3600 -u build.keychain + security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productbuild + security import installer_certificate.p12 -k build.keychain -P "$INSTALLER_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign -T /usr/bin/productbuild + security set-key-partition-list -S apple-tool:,apple:,codesign:,productsign:,productbuild: -s -k "$KEYCHAIN_PASSWORD" build.keychain + rm -f certificate.p12 installer_certificate.p12 + + echo "=== Available signing identities ===" + security find-identity -v -p codesigning build.keychain + echo "has_cert=true" >>"$GITHUB_OUTPUT" + else + echo "has_cert=false" >>"$GITHUB_OUTPUT" + fi + + - name: Package + shell: bash + env: + PACKAGE_TYPE: ${{ steps.package-info.outputs.type }} + HAVE_APPLE_CERT: ${{ runner.os == 'macOS' && steps.apple-certs.outputs.has_cert }} + SIGNING_IDENTITY: "Developer ID Application: OpenMS Inc. (C64UCGJ5PL)" + SIGNING_EMAIL: "apple@openms.de" + CPACK_PRODUCTBUILD_IDENTITY_NAME: "Developer ID Installer: OpenMS Inc. (C64UCGJ5PL)" + PRESET: ${{ steps.build.outputs.preset }} + MACOSX_DEPLOYMENT_TARGET: "15.0" + run: | + set -euo pipefail + build_dir="$GITHUB_WORKSPACE/build/$PRESET" + + cmake_options=( + "-DPACKAGE_TYPE=$PACKAGE_TYPE" + ) + + if [[ "$HAVE_APPLE_CERT" == "true" ]]; then + cmake_options+=( + "-DSIGNING_IDENTITY=$SIGNING_IDENTITY" + "-DSIGNING_EMAIL=$SIGNING_EMAIL" + "-DCPACK_PRODUCTBUILD_IDENTITY_NAME=$CPACK_PRODUCTBUILD_IDENTITY_NAME" + ) + fi + + # makensis.exe opens every staged file through the Win32 file APIs, which + # are bound by MAX_PATH (260 characters). CPack stages below the build + # directory by default; a short, fixed staging directory keeps the paths + # well below the limit. The installer is moved back next to the other + # build output below. + if [[ "$RUNNER_OS" == "Windows" ]]; then + rm -rf "/c/cpk" + mkdir -p "/c/cpk" + cmake_options+=("-DCPACK_PACKAGE_DIRECTORY=C:/cpk") + fi + + cmake "${cmake_options[@]}" "$build_dir" + + if ! cmake --build "$build_dir" --target dist; then + if [[ "$RUNNER_OS" == "Windows" ]]; then + cat /c/cpk/_CPack_Packages/win64/NSIS/NSISOutput.log || true + fi + exit 1 + fi + + if [[ "$RUNNER_OS" == "Windows" ]]; then + mv /c/cpk/*.exe "$build_dir/" + fi + ls -l "$build_dir"/*.exe "$build_dir"/*.deb "$build_dir"/*.pkg 2>/dev/null || true + + # Submits the signed package to Apple for notarization and staples the ticket. + - name: Notarize macOS Package + if: runner.os == 'macOS' && steps.apple-certs.outputs.has_cert == 'true' + shell: bash + env: + APPLE_APP_SPECIFIC_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_NOTARIZATION_PASSWORD }} + TEAM_ID: "C64UCGJ5PL" + PRESET: ${{ steps.build.outputs.preset }} + run: | + echo "=== Starting macOS package notarization ===" + cd "build/$PRESET" + + PKG_FILE=$(find . -maxdepth 1 -name 'OpenMS-GUI-*.pkg' | head -n 1) + if [[ -z "$PKG_FILE" ]]; then + echo "Error: No .pkg file found in build directory" + ls -la + exit 1 + fi + + echo "Found package: $PKG_FILE" + + "$GITHUB_WORKSPACE/cmake/MacOSX/notarize.sh" \ + "$PKG_FILE" \ + "de.openms.gui" \ + "apple@openms.de" \ + "APPLE_APP_SPECIFIC_NOTARIZATION_PASSWORD" \ + "$TEAM_ID" \ + "$GITHUB_WORKSPACE/build/$PRESET" + + echo "=== Notarization complete ===" + + echo "=== Verifying package signing and notarization ===" + pkgutil --check-signature "$PKG_FILE" || echo "Warning: pkgutil verification returned non-zero" + spctl -a -vvv -t install "$PKG_FILE" || echo "Warning: spctl assessment returned non-zero" + + - name: Upload Packages as Artifacts + uses: actions/upload-artifact@v7 + with: + name: ${{ format('installer-{0}-{1}-{2}', needs.version.outputs.version_name, runner.os, runner.arch) }} + if-no-files-found: error + path: | + ${{ github.workspace }}/build/${{ steps.build.outputs.preset }}/OpenMS-GUI-*.exe + ${{ github.workspace }}/build/${{ steps.build.outputs.preset }}/OpenMS-GUI-*.deb + ${{ github.workspace }}/build/${{ steps.build.outputs.preset }}/OpenMS-GUI-*.pkg + + # Installs each package the way users do: the OpenMS installer of the same + # version first, then the GUI package into its installation, and starts the GUI + # tools without a display. + smoke-test: + name: "Smoke Test the Package" + needs: + - version + - build-and-test + strategy: + fail-fast: false + matrix: + os: [windows-2025, macos-15, ubuntu-24.04, ubuntu-24.04-arm] + runs-on: ${{ matrix.os }} + env: + QT_QPA_PLATFORM: offscreen + steps: + - uses: actions/checkout@v6 + with: + sparse-checkout: .github/actions + persist-credentials: false + + - name: Download the Package + uses: actions/download-artifact@v8 + with: + name: ${{ format('installer-{0}-{1}-{2}', needs.version.outputs.version_name, runner.os, runner.arch) }} + path: ${{ runner.temp }}/gui-package + + - name: Install the OpenMS Installer + id: core + uses: ./.github/actions/openms-core + with: + mode: sdk + sdk: "false" + channel: ${{ needs.version.outputs.core_channel }} + version: ${{ needs.version.outputs.version }} + + # An OpenMS installer from before the split ships the GUI tools itself; the + # GUI package cannot be installed on top of it. + - name: Check the OpenMS Installation + id: presplit + shell: bash + env: + INSTALL_DIR: ${{ steps.core.outputs.install_dir }} + run: | + # (the macOS installer has the app bundles in the root of its folder, not in bin/) + if [[ -e "$INSTALL_DIR/bin/TOPPView" || -e "$INSTALL_DIR/bin/TOPPView.exe" || -e "$INSTALL_DIR/TOPPView.app" ]]; then + echo "::warning::The OpenMS installer still contains the GUI tools (an OpenMS from before the split); the smoke test of the GUI package is skipped." + echo "skip=true" >>"$GITHUB_OUTPUT" + else + echo "skip=false" >>"$GITHUB_OUTPUT" + fi + + - name: Install the GUI Package (Linux) + if: runner.os == 'Linux' && steps.presplit.outputs.skip == 'false' + shell: bash + run: | + sudo apt-get -qq install -y --no-install-recommends "$RUNNER_TEMP"/gui-package/OpenMS-GUI-*.deb + + - name: Install the GUI Package (macOS) + if: runner.os == 'macOS' && steps.presplit.outputs.skip == 'false' + shell: bash + run: | + for pkg in "$RUNNER_TEMP"/gui-package/OpenMS-GUI-*.pkg; do + sudo installer -pkg "$pkg" -target / + done + + # The add-on installer finds the OpenMS installation in the registry. Silent mode + # needs the install mode of that installation (all users, see openms-core). + - name: Install the GUI Package (Windows) + if: runner.os == 'Windows' && steps.presplit.outputs.skip == 'false' + shell: pwsh + run: | + $exe = Get-ChildItem "$env:RUNNER_TEMP\gui-package\OpenMS-GUI-*.exe" | Select-Object -First 1 + $p = Start-Process -FilePath $exe.FullName -ArgumentList '/S', '/allusers' -Wait -PassThru + if ($p.ExitCode -ne 0) { throw "The GUI installer failed with exit code $($p.ExitCode)" } + + - name: Run the GUI Tools + if: steps.presplit.outputs.skip == 'false' + shell: bash + env: + INSTALL_DIR: ${{ steps.core.outputs.install_dir }} + run: | + set -euo pipefail + bin="$INSTALL_DIR/bin" + # the executables of the GUI applications (on macOS inside the bundles, which the + # package installs into the root of the OpenMS folder, next to bin/) + app() { + local candidate + for candidate in "$INSTALL_DIR/$1.app/Contents/MacOS/$1" "$bin/$1.exe" "$bin/$1"; do + if [[ -f "$candidate" ]]; then + echo "$candidate" + return + fi + done + echo >&2 "ERROR: $1 is not installed in $INSTALL_DIR" + ls -l "$INSTALL_DIR" "$bin" >&2 + return 1 + } + for tool in TOPPView ExecutePipeline; do + exe=$(app "$tool") + echo "=== $exe --help" + "$exe" --help + done + # the GUI TOPP tools are registered next to those of OpenMS + "$(app FileInfo)" --help >/dev/null + + deploy-installer: + name: "Deploy Installer" + if: ${{ needs.version.outputs.deploy == 'true' }} + runs-on: ubuntu-latest + needs: + - version + - build-and-test + - smoke-test + steps: + - uses: actions/checkout@v6 + with: + sparse-checkout: .github/actions + persist-credentials: false + + - name: Download the Installer + uses: actions/download-artifact@v8 + with: + pattern: installer-* + merge-multiple: true + path: ${{ github.workspace }}/downloads + + # Into the folder of the OpenMS installers of the same version. The 'latest' + # link of that folder belongs to OpenMS and is not touched. + - name: Upload Installer + uses: ./.github/actions/rsync + with: + key: ${{ secrets.ARCHIVE_RRSYNC_SSH }} + user: ${{ secrets.ARCHIVE_RRSYNC_USER }} + host: ${{ secrets.ARCHIVE_RRSYNC_HOST }} + port: ${{ secrets.ARCHIVE_RRSYNC_PORT }} + src: ${{ github.workspace }}/downloads/* + dst: /OpenMSInstaller/${{ needs.version.outputs.version_type }}/${{ needs.version.outputs.version }} + # rsync creates only the last directory of dst by itself; the folder of + # a release exists if OpenMS published its installers first, the + # nightly/ folder exists anyway + mkpath: ${{ needs.version.outputs.has_version }} + + # Next to the html of OpenMS of the same version: /Documentation///html-gui. + # The GUI applications link there (Help menu) when no local documentation is installed. + deploy-docs: + name: "Deploy Documentation" + if: ${{ needs.version.outputs.deploy == 'true' }} + runs-on: ubuntu-latest + needs: + - version + - build-and-test + steps: + - uses: actions/checkout@v6 + with: + sparse-checkout: .github/actions + persist-credentials: false + + - name: Download the Documentation + id: download + continue-on-error: true + uses: actions/download-artifact@v8 + with: + name: documentation-gui + path: ${{ github.workspace }}/html-gui + + - name: Upload Documentation + if: steps.download.outcome == 'success' + uses: ./.github/actions/rsync + with: + key: ${{ secrets.ARCHIVE_RRSYNC_SSH }} + user: ${{ secrets.ARCHIVE_RRSYNC_USER }} + host: ${{ secrets.ARCHIVE_RRSYNC_HOST }} + port: ${{ secrets.ARCHIVE_RRSYNC_PORT }} + src: ${{ github.workspace }}/html-gui + dst: /Documentation/${{ needs.version.outputs.version_type }}/${{ needs.version.outputs.version }} + mkpath: ${{ needs.version.outputs.has_version }} + + publish-release: + name: "Publish Release" + if: ${{ needs.version.outputs.has_version == 'true' && needs.version.outputs.deploy == 'true' }} + runs-on: ubuntu-latest + permissions: + contents: write + needs: + - version + - deploy-installer + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + sparse-checkout: | + CHANGELOG + + # The section of this version in the CHANGELOG: from its banner + # ("---- OpenMS-TOPP-GUI X.Y.Z ...") to the banner of the previous release. + - name: Create Release Notes + env: + VERSION: ${{ needs.version.outputs.version }} + shell: bash + run: | + python3 - <<'EOF' + import os + import re + + version = os.environ["VERSION"] + lines = open("CHANGELOG", encoding="utf-8").read().splitlines() + banners = [i for i, l in enumerate(lines) if re.match(r"^----\s+OpenMS-TOPP-GUI\s+\S+", l)] + start = next((i for i in banners + if re.match(r"^----\s+OpenMS-TOPP-GUI\s+" + re.escape(version) + r"\b", lines[i])), None) + if start is None: + raise SystemExit(f"CHANGELOG has no section for OpenMS-TOPP-GUI {version}") + # the body starts after the closing line of dashes of the banner + body = start + 1 + while body < len(lines) and lines[body].startswith("----"): + body += 1 + # and ends before the opening line of dashes of the next banner + later = [i for i in banners if i > start] + end = later[0] - 1 if later else len(lines) + while end > body and not lines[end - 1].strip("- "): + end -= 1 + notes = "\n".join(lines[body:end]).strip("\n") + header = (f"OpenMS-TOPP-GUI {version}, the graphical tools of OpenMS (TOPPView, TOPPAS, " + f"INIFileEditor). Install it on top of OpenMS {version}: the packages are add-ons " + f"to the OpenMS installers of the same version.\n\n") + open("release-notes.txt", "w", encoding="utf-8").write(header + notes + "\n") + EOF + cat release-notes.txt + + - name: Download the Installer + uses: actions/download-artifact@v8 + with: + pattern: installer-* + merge-multiple: true + path: ${{ github.workspace }}/downloads + + - name: Publish the Release + uses: softprops/action-gh-release@v3 + with: + name: Release ${{ needs.version.outputs.version }} + body_path: release-notes.txt + make_latest: true + files: downloads/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..2d36e268b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,118 @@ +name: "Build and Test" + +# Builds and tests the GUI layer against an OpenMS core without GUI; where the +# core comes from is decided by .github/actions/openms-core (default: built from +# OpenMS develop, the newest cached build of it for pushes and pull requests). +on: + workflow_dispatch: + inputs: + core_mode: + description: "Where the OpenMS core comes from: source, sdk, or empty for the default of the openms-core action" + type: string + default: "" + core_ref: + description: "source: branch, tag or commit of OpenMS/OpenMS (empty: OPENMS_CORE_DEFAULT_REF below)" + type: string + default: "" + core_exact: + description: "source: build the current commit of core_ref instead of taking the newest cached build of it" + type: boolean + default: false + core_channel: + description: "sdk: nightly or release" + type: string + default: "nightly" + core_version: + description: "sdk: latest, a nightly date YYYY.MM.DD or a release X.Y.Z" + type: string + default: "latest" + # used by nightly.yml + workflow_call: + inputs: + core_mode: + type: string + default: "" + core_ref: + type: string + default: "develop" + core_exact: + type: boolean + default: false + core_channel: + type: string + default: "nightly" + core_version: + type: string + default: "latest" + push: + branches: + - develop + - nightly + - release/* + pull_request: + branches: + - develop + +permissions: + contents: read + +# Cancel superseded PR runs without queuing or cancelling non-PR runs. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + # The OpenMS branch the core is built from when no core_ref is given. + OPENMS_CORE_DEFAULT_REF: develop + +jobs: + build-and-test: + strategy: + fail-fast: false + matrix: + include: + - os: windows-2025 + compiler: cl.exe + - os: macos-15 + compiler: xcode + - os: ubuntu-24.04 + compiler: g++ + - os: ubuntu-24.04-arm + compiler: g++ + - os: ubuntu-24.04 + compiler: clang++ + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Install Dependencies + id: deps + uses: ./.github/actions/dependencies + with: + compiler: ${{ matrix.compiler }} + + - name: Provide the OpenMS Core + id: core + uses: ./.github/actions/openms-core + with: + mode: ${{ inputs.core_mode }} + ref: ${{ inputs.core_ref || env.OPENMS_CORE_DEFAULT_REF }} + exact: ${{ inputs.core_exact && 'true' || 'false' }} + channel: ${{ inputs.core_channel || 'nightly' }} + version: ${{ inputs.core_version || 'latest' }} + ssh_key: ${{ secrets.ARCHIVE_RRSYNC_SSH }} + ssh_host: ${{ secrets.ARCHIVE_RRSYNC_HOST }} + ssh_port: ${{ secrets.ARCHIVE_RRSYNC_PORT }} + ssh_user: ${{ secrets.ARCHIVE_RRSYNC_USER }} + + - name: Build and Test the GUI Layer + uses: ./.github/actions/build + with: + compiler: ${{ matrix.compiler }} + prefix_path: ${{ steps.core.outputs.prefix_path }};${{ steps.deps.outputs.cmake_prefix }} + topp_bin_dir: ${{ steps.core.outputs.bin_dir }} + examples_dir: ${{ steps.core.outputs.examples_dir }} + core_version_fullstring: ${{ steps.core.outputs.version_fullstring }} diff --git a/.github/workflows/update_version_numbers.yml b/.github/workflows/update_version_numbers.yml new file mode 100644 index 000000000..702628654 --- /dev/null +++ b/.github/workflows/update_version_numbers.yml @@ -0,0 +1,89 @@ +name: Update the version number + +# After a release tag vX.Y[.Z], open a pull request against develop that sets the +# next minor version (X.Y+1.0) with tools/update_version_numbers.sh. The version of +# this repository is always that of the OpenMS core it is built against, so the +# pull request is merged when OpenMS develop has moved to the same version. +on: + push: + tags: + - 'v[0-9]*' + +permissions: + contents: write + pull-requests: write + +jobs: + updating_package_version_number: + runs-on: ubuntu-latest + steps: + - name: Checkout develop branch + uses: actions/checkout@v6 + with: + ref: develop + + - name: Calculate next minor version and update files + env: + TAG_NAME: ${{ github.ref_name }} + run: | + echo "Triggered by tag: $TAG_NAME" + + # v. with an optional .; anything else (e.g. a + # release candidate) is not a release + if [[ "$TAG_NAME" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?$ ]]; then + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + PATCH="${BASH_REMATCH[3]#.}" + else + echo "Tag '$TAG_NAME' is not a release (vX.Y or vX.Y.Z); nothing to do." + exit 0 + fi + + NEW_MAJOR="$MAJOR" + NEW_MINOR=$((MINOR + 1)) + NEW_PATCH=0 + NEW_VERSION=${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH} + + echo "Bumping version from $MAJOR.$MINOR.${PATCH:-0} to $NEW_VERSION" + + bash tools/update_version_numbers.sh "$NEW_MAJOR" "$NEW_MINOR" "$NEW_PATCH" + + echo "NEW_VERSION=$NEW_VERSION" >> "$GITHUB_ENV" + + - name: Commit, Push, and PR + if: env.NEW_VERSION != '' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_NAME: update_package_number + TAG_NAME: ${{ github.ref_name }} + ACTOR: ${{ github.actor }} + run: | + # Nothing changed: no pull request. + if git diff --exit-code; then + echo "No changes detected. Skipping PR creation." + exit 0 + fi + + FULL_BRANCH_NAME=${BRANCH_NAME}_${NEW_VERSION} + + git config --global user.name "$ACTOR" + git config --global user.email "${ACTOR}@users.noreply.github.com" + + git checkout -b "$FULL_BRANCH_NAME" + git add . + git commit -m "Updated OpenMS-TOPP-GUI version number to $NEW_VERSION" + + # force push, so an open pull request of this branch is brought up to date + git push origin "$FULL_BRANCH_NAME" --force + + PR_EXISTS=$(gh pr list --head "$FULL_BRANCH_NAME" --base develop --state open --json number --jq '.[].number') + + if [ -z "$PR_EXISTS" ]; then + gh pr create \ + --title "[GHA] Updated OpenMS-TOPP-GUI version number to $NEW_VERSION" \ + --body "This PR was automatically generated by GitHub Actions following the tag push of **$TAG_NAME** to bump the version number on \`develop\` to $NEW_VERSION. Merge it once OpenMS develop is at $NEW_VERSION as well: this repository is built against the OpenMS core of the same version." \ + --base develop \ + --head "$FULL_BRANCH_NAME" + else + echo "PR #${PR_EXISTS} already exists for branch $FULL_BRANCH_NAME. Branch was force-pushed; PR is now up to date." + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..76dd7043a --- /dev/null +++ b/.gitignore @@ -0,0 +1,213 @@ +# Build artifacts and generated files +.ccache/ +*.cached +*.deb +*.rpm +*.pyc +*.swp +# Temporary files generated during the unit tests have $NAME.tmp.$EXTENSION pattern +*.tmp.* +*.tmp + +*.user +**/.DS_Store +_CPack_Packages +_deps +/bin +/lib + +#These are the typical build directories +/build +/build-*/ +/OpenMS-TOPP-GUI-build/ + +DartConfiguration.tcl +install_manifest.txt +Makefile +moc_*.cxx* + +qrc_resources.cxx +qrc_resources.cpp +resources.qrc.depends + +# Autogenerated files and directories (an in-source build) +/src/openms_gui/*_autogen +/src/tests/class_tests/openms_gui/*_autogen +/src/openms_gui/include/OpenMS/VISUAL/OpenMS_GUIConfig.h +/src/openms_gui/qrc_resources.cpp +/src/tests/class_tests/*/include/OpenMS/test_config.h + +# Various test outputs +/src/tests/toppas/TOPPAS_out/ +/src/tests/toppas/*.trf +/src/tests/class_tests/Testing/ +/Testing/ + +thirdparty_licenses.txt + +# Generated CMake files +cmake-*-build +cmake-build-* +cmake_install.cmake +CMakeCache.txt +CMakeDoxyfile.in +CMakeDoxygenDefaults.cmake +CMakeFiles +CPackConfig.cmake +CPackSourceConfig.cmake +CTestTestfile.cmake +OpenMSGUIConfig.cmake +OpenMSGUIConfigVersion.cmake +OpenMSGUITargets.cmake +# An in-source build copies cmake/Modules/ to the build root +/Modules/ + +# IDE and editor files +*.code-workspace +.cache/* +.clion.* +.idea +.qt/ +.vs +.vscode + +# Documentation build outputs (an in-source build) +/doc/html-gui/ +/doc/doxygen/Doxyfile +/doc/doxygen/doxygen-error.log +/doc/doxygen/parameters/output/ +Tutorial_ParamEditor_out.ini + +# Log files +TOPP.log +TOPPAS.log + +# Security - Credentials, tokens, and secrets +.env +.env.* +.env.local +.env.*.local +*.pem +*.key +*.p12 +*.pfx +*.crt +*.cer +*.der +!*.crt.example +!*.key.example +credentials.json +credentials.yaml +credentials.yml +secrets.json +secrets.yaml +secrets.yml +secrets.toml +**/secrets/ +**/credentials/ +.secrets +.credentials +*.secret +*.secrets + +# AWS +.aws/ +aws_credentials +.boto + +# SSH keys +id_rsa +id_rsa.pub +id_dsa +id_dsa.pub +id_ecdsa +id_ecdsa.pub +id_ed25519 +id_ed25519.pub +*.ppk + +# API tokens and keys +.npmrc +.pypirc +.netrc +.htpasswd +.htaccess +*.token +api_key* +apikey* +auth.json +auth.yaml +auth.yml + +# Docker secrets +docker-compose.override.yml +docker-compose.*.yml +!docker-compose.example.yml + +# Kubernetes +kubeconfig +*.kubeconfig +kube/config + +# OAuth +oauth_token* +oauth-token* +client_secret* +client-secret* + +# GPG +*.gpg +secring.* +pubring.* +trustdb.* + +# Terraform +*.tfstate +*.tfstate.* +*.tfvars +!*.tfvars.example +.terraform/ + +# Ansible +*.vault +vault.yml +vault.yaml +group_vars/*/vault* +host_vars/*/vault* + +# IDE credential storage +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml + +# Shell history (may contain credentials) +.bash_history +.zsh_history +.sh_history + +# Local configuration that may contain secrets +config.local.* +settings.local.* +*.local.json +*.local.yaml +*.local.yml +*.local.toml + +# Jupyter notebooks checkpoints (may contain credentials in outputs) +.ipynb_checkpoints/ + +# GitHub tokens +.github_token +github_token* + +# Claude/AI tokens and session files +.claude_token +.anthropic_key +.claude/scheduled_tasks.lock + +# Link checker cache +.lycheecache + +# Lock files from external tools +*.fasta.lock diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..e9bbe0099 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,184 @@ +# OpenMS-TOPP-GUI Agent Notes + +This file provides context and instructions for coding agents working on OpenMS-TOPP-GUI, the Qt GUI layer +of OpenMS. It follows the [AGENTS.md](https://agents.md) standard. + +## What this repository is + +- The OpenMS_GUI library (`src/openms_gui`, namespace `OpenMS`, headers `OpenMS/VISUAL/...`) and the GUI + tools built on it: **TOPPView**, **TOPPAS**, **INIFileEditor** (GUI applications) and **ExecutePipeline**, + **ImageCreator** (TOPP tools that need Qt; registered in `share/OpenMS/TOOLS/OpenMS-GUI.tsv`). +- It is built against an **installed OpenMS of the same version** (`find_package(OpenMS EXACT CONFIG + COMPONENTS CLI)` in `cmake/find_openms.cmake`): the OpenMS SDK or an OpenMS built from source and installed. + OpenMS itself is Qt-free; its library, TOPP tools and pyOpenMS are developed in + [OpenMS/OpenMS](https://github.com/OpenMS/OpenMS). +- The GUI layer is installed **into the OpenMS prefix** (same `bin/`, `lib/`, `share/OpenMS`): libOpenMS resolves + the shared data, the tool registry and sibling TOPP executables relative to the executable. +- The layout mirrors OpenMS (`src/openms_gui`, `src/tests`, `doc`, `cmake`) and the moved files kept their history, + so commits can be cherry-picked between the repositories. + +## Critical Constraints + +**NEVER do these things:** +- Change the OpenMS core from here: a change of OpenMS API, tool registry or install layout needs a PR in + OpenMS/OpenMS first (and a GUI release can only use what the OpenMS release of the same version has). +- Install or package anything the OpenMS core ships (its libraries, TOPP tools, share data, MSVC runtime). +- Commit secrets, credentials, or `.env` files +- Add `using namespace` or `using std::...` in header files +- Skip tests when making code changes + +**Before opening a pull request, always build the changes locally and run the relevant tests.** + +## Quick Commands + +```bash +# Configure against an installed OpenMS (or the SDK) and Qt +cmake -S . -B ../OpenMS-TOPP-GUI-build -G Ninja -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_PREFIX_PATH=";" \ + -DOPENMS_TOPP_BIN_DIR="/bin" # for FuzzyDiff and pipeline tests + +# Build everything (includes tests) +cmake --build ../OpenMS-TOPP-GUI-build -j$(nproc) + +# Run tests with verbose output (no display needed: the tests set QT_QPA_PLATFORM=offscreen) +ctest --test-dir ../OpenMS-TOPP-GUI-build -R TOPPView -V + +# Documentation (doxygen): ../OpenMS-TOPP-GUI-build/doc/html-gui/index.html +cmake --build ../OpenMS-TOPP-GUI-build --target doc +``` + +## Repository Layout + +``` +OpenMS-TOPP-GUI/ +├── src/ +│ ├── openms_gui/ # OpenMS_GUI library and the GUI tools +│ │ ├── include/OpenMS/VISUAL/ # Headers (.h) +│ │ ├── source/VISUAL/ # Implementation (.cpp), .ui files, ICONS/ (resources.qrc), STYLE/ (style sheet, style.qrc) +│ │ │ └── APPLICATIONS/GUITOOLS/ # main() of TOPPView, TOPPAS, INIFileEditor, ExecutePipeline, +│ │ │ # ImageCreator (+ their doxygen tool pages, icons, mac bundle data) +│ │ └── includes.cmake # source and header lists +│ ├── testframework/ # vendored copy of the OpenMS test framework (not installed) +│ └── tests/ +│ ├── class_tests/openms_gui/ # GUI class tests (Qt TestLib) +│ ├── topp/ # tests of ExecutePipeline / ImageCreator +│ ├── toppas/ # TOPPAS pipeline tests (ExecutePipeline) +│ └── external/ # installed-consumer test +├── share/OpenMS/ # DESKTOP (Linux desktop files) +├── doc/ # doxygen (GUI tool pages, TOPPAS tutorial), documenters, code examples +├── cmake/ # build, install and packaging (trimmed copies of OpenMS cmake/) +└── tools/ # CI scripts, update_version_numbers.sh +``` + +## Build and Install + +- **CMake minimum**: 3.24; **C++ standard**: C++23 (as OpenMS). Out-of-tree builds. +- The version in `CMakeLists.txt` (`OPENMS_GUI_VERSION`) must equal the version of the OpenMS it is built against; + `tools/update_version_numbers.sh` bumps it. +- An OpenMS whose installation already contains a GUI (an OpenMS built with the former `WITH_GUI=ON`, whose + `OpenMS.tsv` lists ExecutePipeline/ImageCreator) is refused at configure time: both registries would name the + same tools. +- Variables of the OpenMS build system keep their names (`OPENMS_PACKAGE_VERSION*`, `OPENMS_HOST_DIRECTORY`, + `INSTALL_*_DIR`), so moved code works unchanged. Install dirs equal those of OpenMS. +- When adding or removing a header or source of the library, update `src/openms_gui/includes.cmake`. +- Public headers are a `FILE_SET HEADERS` of `OpenMS_GUI`; Qt is a PUBLIC dependency + (`ALLOWED_PUBLIC_INCLUDES` in `src/openms_gui/CMakeLists.txt`). +- Export set `OpenMSGUITargets`, CMake package `OpenMSGUI` (`cmake/OpenMSGUIConfig.cmake.in`), install components + `library_gui`, `OpenMS_GUI_headers`, `cmake_gui`, `GUIApplications`, `Dependencies`, `doc_gui`. +- A new TOPP tool that needs Qt goes into `GUI_TOPP_TOOLS` in `src/openms_gui/CMakeLists.txt` (declared with + `openms_topp_tool()`, which writes it into `OpenMS-GUI.tsv`). + +**Required dependencies:** OpenMS (same version, core + CLI layer), Qt 6.1+ (Core, Gui, Widgets, Svg, +OpenGLWidgets; PrintSupport on macOS; WebEngineWidgets optional). + +### Qt6 +- If Qt6 is not found: `-DCMAKE_PREFIX_PATH=''` +- WebEngineWidgets optional; without it (or with `-DNO_WEBENGINE_WIDGETS=ON`) the JavaScript views of TOPPView are + disabled (configure warning only). +- Headless runs: `QT_QPA_PLATFORM=offscreen` (or `minimal`). The tests and the documenters set it themselves. +- macOS: remove older Qt versions if they interfere; Qt6 needs `PrintSupport` for the platform plugin. + +### Platforms +- **Windows**: Visual Studio environment only (no MSYS/MinGW), 64-bit, short build paths; never mix + Release/Debug libraries. The installers are add-ons to the OpenMS installer of the same version. +- **macOS**: Apple Clang (Xcode 16+). TOPPView, TOPPAS and INIFileEditor are app bundles; code signing and + notarization like OpenMS. +- **Linux**: distribution Qt; the packages (`openms-gui`) depend on the `openms` package and ship no Qt. + +## Testing + +- GUI class tests: `src/tests/class_tests/openms_gui/source/` (Qt TestLib), listed in + `src/tests/class_tests/openms_gui/CMakeLists.txt`. +- Tests of ExecutePipeline/ImageCreator: `src/tests/topp/`; TOPPAS pipelines: `src/tests/toppas/`. +- `FuzzyDiff` and the TOPP tools the pipelines run come from OpenMS (`OPENMS_TOPP_BIN_DIR`). The SDK has no TOPP + tools and no examples: tests that need them are left out with a configure message. Do not "fix" that by + vendoring tools or data. +- Running a GUI TOPP tool from the build tree by hand needs + `OPENMS_TOOL_REGISTRY_PATH=/share/OpenMS/TOOLS` (TOPPBase refuses unregistered tools). +- Run with `ctest`, use `-R` for a subset, `-V/-VV` for verbosity, `-C` for multi-config generators. + +## Coding Conventions + +The conventions of OpenMS apply (https://openms.readthedocs.io/en/latest/manual/develop.html): +- 2 spaces, no tabs, Unix line endings; braces on their own lines, always braces; `.clang-format` in the root. +- Classes PascalCase, methods lowerCamel, variables snake_case, private/protected members end with `_`. +- `OPENMS_GUI_DLLAPI` on all exported non-template classes/functions of the GUI library. +- No `using namespace` in headers; OpenMS uses `std::string`; convert to/from `QString` at the Qt boundary + (`toQString()`, `fromQString()` in OpenMS_GUI). +- Doxygen: `/** */` with `@` tags, `@brief` required; GUI classes go into the groups of + `doc/doxygen/public/Modules.doxygen` (`Visual`, `PlotWidgets`, `TOPPView_elements`, `TOPPAS_elements`, `Dialogs`). + A class with parameters (DefaultParamHandler) gets `@htmlinclude OpenMS_.parameters` and an entry in + `doc/doxygen/parameters/GUIParamDocumenter.cpp`. +- Each file starts with the license header: + ```cpp + // Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin + // SPDX-License-Identifier: BSD-3-Clause + // + // -------------------------------------------------------------------------- + // $Maintainer: Your Name $ + // $Authors: Original Author, Your Name $ + // -------------------------------------------------------------------------- + ``` + (CMake files: the same with `#`.) +- Comments: simple, useful, plain English. + +## Documentation + +- Tool pages are the `@page TOPP_` blocks in `src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/.cpp`; + `GUI.doxygen` (main page) lists them. The TOPPAS tutorial is `doc/doxygen/public/TOPPAS.doxygen`. +- The `doc` target runs `GUIParamDocumenter` and `GUIToolDocumenter` (they start the GUI tools offscreen) and then + doxygen. `@ref TOPP_` links need the tag file of the OpenMS documentation (`OPENMS_DOXYGEN_TAGFILE`); + without it doxygen warns about them, which is expected. +- Installed into `share/doc/html-gui`; online at + `https://archive.openms.de/openms/Documentation/|nightly/latest>/html-gui/`. TOPPView/TOPPAS + open these pages with F1 (`GUIHelpers::openURL`). +- User documentation (TOPPView tutorials etc.) is the OpenMS ReadTheDocs manual, maintained in OpenMS/OpenMS. + +## Contribution Workflow and Commit Messages + +- Gitflow; use forks and open PRs against `develop`. +- Commit format: `[TAG1,TAG2] short summary` (<=120 chars, <=80 preferred), blank line, longer description, and + `Fixes #N`/`Closes #N` when applicable. Tags: NOP, DOC, COMMENT, API, INTERNAL, FEATURE, FIX, TEST, FORMAT, + PARAM, IO, LOG, GUI, RESOURCE, BUILD. +- Refer to issues of OpenMS as `OpenMS/OpenMS#N`. +- PR checklist: update `AUTHORS`, run/extend tests, link the OpenMS PR a change depends on. +- Minimize pushes on open PRs (CI is heavy). + +## Common Gotchas + +1. **Version mismatch**: OpenMS of another version is not accepted (`EXACT`); build against the matching + release or the nightly SDK of `develop`. +2. **Data path of a source-built OpenMS**: libOpenMS looks in its compiled-in paths first, so a GUI linked + against an OpenMS build tree reads that tree's `share/OpenMS`. +3. **GUI tests need a display**: they set `QT_QPA_PLATFORM=offscreen`; set it yourself when running tools by hand. +4. **Template methods with 2+ args in OpenMS-style tests**: wrap in parentheses for `START_SECTION`. +5. **Windows**: keep build paths short; the OpenMS and Qt DLLs must be on `PATH` to run tools from the build tree. + +## Key Documentation + +- `README.md` - overview, build, test, package +- `CONTRIBUTING.md` - contribution guidelines +- [OpenMS user manual](https://openms.readthedocs.io/en/latest) +- [OpenMS API reference](https://archive.openms.de/openms/Documentation/release/latest/html/index.html) +- [GUI documentation](https://archive.openms.de/openms/Documentation/release/latest/html-gui/index.html) +- [OpenMS developer FAQ](https://archive.openms.de/openms/Documentation/release/latest/html/developer_faq.html) diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..77cc03c88 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,143 @@ +========================================================================= + OpenMS -- Open Source Mass Spectrometry +========================================================================= + +The following authors contributed to OpenMS at some point. For the +authors contributing to a specific piece of code, please refer to +the authors tag in the respective file header. + - Achal Bajpai + - Adithya Kammati + - Aditya Muzumdar + - Aditya Sarna + - Ahmed Khalil + - Alen Saric + - Alexandra Scherbart + - Alexandra Zerck + - Alivia Hossain + - Amanda Wein + - Andreas Bertsch + - Andreas Simon + - Anton Kriese + - Anton Pervukhin + - Axel Walter + - Bastian Blank + - Chenghao Zhu + - Chirag Dave + - Chris Bauer + - Chris Bielow + - Christian Ehrlich + - Christie Mathews + - Clemens Groepl + - Cornelia Friedle + - Daniel Jameson + - Darren Kessner + - David Voigt + - David Wojnar + - Dhanmoni Nath + - Dilek Dere + - Dimitri Schachmann + - Dominik Schmitz + - Douglas McCloskey + - Emil Paulitz + - Erhan Kenar + - Eugen Netz + - Eva Lange + - Fabian Aicheler + - Fabian Kriegel + - Felix Droop + - Florian Zeller + - Frederic Lehnert + - George Rosenberger + - Guillaume Belz + - Hannes Roest + - Hendrik Beschorner + - Hendrik Brauer + - Hendrik Weisser + - Holger Franken + - Holger Plattfaut + - Immanuel Luhn + - Jang Jang Jin + - Jeremi Maciejewski + - Jihyung Kim + - Johannes Junker + - Johannes Veit + - Johannes von Kleist + - Johan Teleman + - Joshua Charkow + - Juliane Schmachtenberg + - Julianus Pfeuffer + - Julia Thueringer + - Justin Sing + - Katharina Albers + - Karnajeet Gosavi + - Khue Nguyen + - Knut Reinert + - Krrish Verma + - Kyowon Jeong + - Lars Nilse + - Lenny Kovac + - Leon Bichmann + - Leon Kuchenbecker + - Lucia Espona + - Luis Jacob Keller + - Lukas Mueller + - Lukas Zimmermann + - Marcel Schilling + - Marc Sturm + - Markus Apel + - Markus Mueller + - Martin Langwisch + - Mateusz Łącki + - Mathias Walzer + - Matteo Pilz + - Matthew The + - Matthias Bernt + - Michael R. Crusoe + - Michał Startek + - Mohamed Ali + - Nico Pfeifer + - Nidhish Chauhan + - Nora Heese + - Ole Schulz-Trieglaff + - Oliver Alka + - Oliver Kohlbacher + - Pasquale Domenico Colaianni + - Patricia Scheil + - Patrick Boschmann + - Peter J. Jones + - Peter Kunszt + - Petra Gutenbrunner + - Radu Suciu + - Ralf Gabriels + - Rene Hussong + - Rick Helmus + - Ruben Grünberg + - Samuel Wein + - Sandro Andreotti + - Santhil + - Shubham Padkonde + - Simon Gene Gottlieb + - Steffen Sass + - Stephan Aiche + - Sven Nahnsen + - Svetlana Kutuzova + - Swenja Wagner + - Tapasweni Pathak + - Taraneh Strunk + - Till Englert + - Tilman Aurich + - Timo Sachsenberg + - Tinatin Kasradze + - Tom David Müller + - Tom Lukas Lankenau + - Tom Smith + - Tom Waschischeck + - Uwe Schmitt + - Valentin Noske + - Vincent Musch + - Vipul Patel + - Virginia Rossow + - Volker Mosthaf + - Witold Wolski + - Xiao Liang + - Yasset Perez-Riverol diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 000000000..105279333 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,42 @@ + +Glossary: +OpenMS - Name of the project and our C++ library +OpenMS-TOPP-GUI - this repository: the Qt GUI layer of OpenMS (OpenMS_GUI library and the GUI tools) +TOPP - "The OpenMS PiPeline", a collection of chainable tools for flexible HPLC/MS workflows +TOPPAS - "The OpenMS PiPeline Assistant", a graphical tool to interactively build and run HPLC/MS workflows +TOPPView - Versatile viewer for HPLC/MS data +INI file - parameter configuration file, holding custom parameter settings for TOPP tools +INIFileEditor - graphical parameter editor for INI files +PR - Pull Request (on GitHub), i.e. integration of a new feature or bugfix +#, e.g. #12 - a reference to an issue or pull request of this repository on GitHub, visit e.g. + https://github.com/OpenMS/OpenMS-TOPP-GUI/pull/XXXX (replace XXXX with number of interest) for details; + OpenMS/OpenMS# refers to the OpenMS repository + +Each release of OpenMS-TOPP-GUI is built against, and requires, the OpenMS release of the same version. +Changes of the GUI tools before 3.7.0 are listed in the CHANGELOG of OpenMS (https://github.com/OpenMS/OpenMS). + + + +------------------------------------------------------------------------------------------ +---- OpenMS-TOPP-GUI 3.7.0 (under development) ---- +---- requires OpenMS 3.7.0 ---- +------------------------------------------------------------------------------------------ + +Build System: + - BREAKING: The GUI layer of OpenMS (the OpenMS_GUI library, TOPPView, TOPPAS, INIFileEditor and the TOPP + tools that need Qt, ExecutePipeline and ImageCreator) moved from the OpenMS repository into this repository, + with the history of its files. It is built against an installed OpenMS of the same version (the OpenMS SDK + or an installation built from source without the former WITH_GUI option); OpenMS itself no longer depends + on Qt. + - The GUI layer is installed into the OpenMS installation: its CMake package OpenMSGUI (target + OpenMS::OpenMS_GUI) is also found through find_package(OpenMS COMPONENTS GUI), and its tool registry + share/OpenMS/TOOLS/OpenMS-GUI.tsv adds ExecutePipeline and ImageCreator to the TOPP tools of OpenMS. + - Installers of their own, as add-ons to the OpenMS installers of the same version: + OpenMS-GUI--Win64.exe, OpenMS-GUI--macOS-.pkg and the Linux packages + openms-gui (.deb, .rpm), which depend on the openms package. + +Documentation: + - The documentation of the GUI tools, the TOPPAS tutorial and the class documentation of the GUI library are + built here (target 'doc') and installed into share/doc/html-gui; online at + https://archive.openms.de/openms/Documentation//html-gui/. + The F1 help of TOPPView and TOPPAS opens it. diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000..e83f6d32e --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,177 @@ +cff-version: 1.2.0 +message: "If you use the OpenMS GUI tools (TOPPView, TOPPAS, INIFileEditor) in your research, please cite OpenMS using the metadata from this file." +title: "OpenMS-TOPP-GUI: the graphical tools of OpenMS" +type: software +url: "https://www.openms.de" +repository-code: "https://github.com/OpenMS/OpenMS-TOPP-GUI" +license: BSD-3-Clause +preferred-citation: + type: article + title: "OpenMS 3 enables reproducible analysis of large-scale mass spectrometry data" + authors: + - family-names: Pfeuffer + given-names: Julianus + - family-names: Bielow + given-names: Chris + - family-names: Wein + given-names: Samuel + - family-names: Jeong + given-names: Kyowon + - family-names: Netz + given-names: Eugen + - family-names: Walter + given-names: Axel + - family-names: Alka + given-names: Oliver + - family-names: Nilse + given-names: Lars + - family-names: Colaianni + given-names: Pasquale Domenico + - family-names: McCloskey + given-names: Douglas + - family-names: Kim + given-names: Jihyung + - family-names: Rosenberger + given-names: George + - family-names: Bichmann + given-names: Leon + - family-names: Walzer + given-names: Mathias + - family-names: Veit + given-names: Johannes + - family-names: Boudaud + given-names: Bertrand + - family-names: Bernt + given-names: Matthias + - family-names: Patikas + given-names: Nikolaos + - family-names: Pilz + given-names: Matteo + - family-names: Startek + given-names: "Micha\u0142 Piotr" + - family-names: Kutuzova + given-names: Svetlana + - family-names: Heumos + given-names: Lukas + - family-names: Charkow + given-names: Joshua + - family-names: Sing + given-names: Justin Cyril + - family-names: Feroz + given-names: Ayesha + - family-names: Siraj + given-names: Arslan + - family-names: Weisser + given-names: Hendrik + - family-names: Dijkstra + given-names: Tjeerd M. H. + - family-names: "P\xe9rez-Riverol" + given-names: Yasset + - family-names: "R\xf6st" + given-names: Hannes + - family-names: Kohlbacher + given-names: Oliver + - family-names: Sachsenberg + given-names: Timo + journal: "Nature Methods" + volume: 21 + issue: 3 + start: 365 + end: 367 + year: 2024 + doi: "10.1038/s41592-024-02197-7" +references: + - type: article + title: "OpenMS: a flexible open-source software platform for mass spectrometry data analysis" + authors: + - family-names: "R\xf6st" + given-names: Hannes L. + - family-names: Sachsenberg + given-names: Timo + - family-names: Aiche + given-names: Stephan + - family-names: Bielow + given-names: Chris + - family-names: Weisser + given-names: Hendrik + - family-names: Aicheler + given-names: Fabian + - family-names: Andreotti + given-names: Sandro + - family-names: Ehrlich + given-names: Hans-Christian + - family-names: Gutenbrunner + given-names: Petra + - family-names: Kenar + given-names: Erhan + - family-names: Liang + given-names: Xiao + - family-names: Nahnsen + given-names: Sven + - family-names: Nilse + given-names: Lars + - family-names: Pfeuffer + given-names: Julianus + - family-names: Rosenberger + given-names: George + - family-names: Rurik + given-names: Marc + - family-names: Schmitt + given-names: Uwe + - family-names: Veit + given-names: Johannes + - family-names: Walzer + given-names: Mathias + - family-names: Wojnar + given-names: David + - family-names: Wolski + given-names: Witold E. + - family-names: Schilling + given-names: Oliver + - family-names: Choudhary + given-names: Jyoti S. + - family-names: "Malmstr\xf6m" + given-names: Lars + - family-names: Aebersold + given-names: Ruedi + - family-names: Reinert + given-names: Knut + - family-names: Kohlbacher + given-names: Oliver + journal: "Nature Methods" + volume: 13 + issue: 9 + start: 741 + end: 748 + year: 2016 + doi: "10.1038/nmeth.3959" + - type: article + title: "OpenMS - an open-source software framework for mass spectrometry" + authors: + - family-names: Sturm + given-names: Marc + - family-names: Bertsch + given-names: Andreas + - family-names: "Gr\xf6pl" + given-names: Clemens + - family-names: Hildebrandt + given-names: Andreas + - family-names: Hussong + given-names: Rene + - family-names: Lange + given-names: Eva + - family-names: Pfeifer + given-names: Nico + - family-names: Schulz-Trieglaff + given-names: Ole + - family-names: Zerck + given-names: Alexandra + - family-names: Reinert + given-names: Knut + - family-names: Kohlbacher + given-names: Oliver + journal: "BMC Bioinformatics" + volume: 9 + start: 163 + year: 2008 + doi: "10.1186/1471-2105-9-163" diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..2954b4b4c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,31 @@ +# OpenMS-TOPP-GUI Development + +The Qt GUI layer of OpenMS (OpenMS_GUI, TOPPView, TOPPAS, INIFileEditor, ExecutePipeline, +ImageCreator), built against an installed OpenMS of the same version. See `AGENTS.md`. + +- Build folder: `OpenMS-TOPP-GUI-build/` +- Configure: `cmake -S . -B OpenMS-TOPP-GUI-build -G Ninja -DCMAKE_PREFIX_PATH=";"` +- Build: `cmake --build OpenMS-TOPP-GUI-build -j$(nproc)` +- Test: `ctest --test-dir OpenMS-TOPP-GUI-build --output-on-failure` +- Docs: `cmake --build OpenMS-TOPP-GUI-build --target doc` (needs doxygen; output in `doc/html-gui/`) + +## Build Gotchas + +- **OpenMS must have the same version** as `CMakeLists.txt` (`find_package(OpenMS ... EXACT)`) and must + be built without its former GUI: a core whose tool registry lists ExecutePipeline/ImageCreator is refused. +- **CMAKE_PREFIX_PATH**: Use `;` separator with `-D` flag, `:` with env var on Unix. +- **FuzzyDiff and TOPP tools** come from OpenMS (`-DOPENMS_TOPP_BIN_DIR=/bin`). The SDK has + none: tests that need them are left out with a configure message, not failed. +- **No display needed**: the tests set `QT_QPA_PLATFORM=offscreen`; do the same when running GUI tools by hand. +- **Running GUI TOPP tools from the build tree** needs `OPENMS_TOOL_REGISTRY_PATH=/share/OpenMS/TOOLS` + (the OpenMS registry does not list them; TOPPBase refuses unregistered tools). +- A GUI built against a source-built OpenMS reads that OpenMS's source `share/OpenMS` first (compiled-in path). + +## Guidelines + +- When CI tests fail, always investigate the root cause first before patching reference files or test expectations. Never bulk-replace expected values or regenerate test outputs as a first approach. +- Never dismiss a test failure as "acceptable behavior" or "pre-existing/unrelated" without explicit user confirmation. +- Prefer existing APIs of OpenMS and OpenMS_GUI over new helpers; explore the code before non-trivial changes. +- Changes that need a change in OpenMS (API, tool registry, install layout) need a PR in OpenMS/OpenMS first. +- Keep PR descriptions up to date with the current state of the PR. +- Do not add `CHANGELOG` entries in PRs unless asked; the maintainers update it for a release. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..4a0a40d03 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,306 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ +# OpenMS-TOPP-GUI: the Qt GUI layer of OpenMS (the OpenMS_GUI library, TOPPView, +# TOPPAS, INIFileEditor and the Qt-based TOPP tools ExecutePipeline and +# ImageCreator), built against an installed OpenMS core of the same version. +# +# cmake -S . -B build -DCMAKE_PREFIX_PATH=";" +# +# The OpenMS installation must provide the core and CLI layers (OpenMS::OpenMS, +# OpenMS::OpenMS_CLI), e.g. the OpenMS SDK of the matching release or nightly. +#------------------------------------------------------------------------------ +cmake_minimum_required(VERSION 3.24 FATAL_ERROR) + +## The version of this repository is the version of the OpenMS core it is built +## against (find_package(OpenMS ... EXACT) below). tools/update_version_numbers.sh +## bumps it together with the test data. +set(OPENMS_GUI_VERSION 3.7.0) + +# Handle default build type +get_property(_openms_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT _openms_multi_config AND NOT DEFINED CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build.") +endif() + +project("OpenMS_TOPP_GUI" VERSION ${OPENMS_GUI_VERSION} LANGUAGES CXX) + +## AutoMoc does not need the compiler's predefined macros (and collecting them is slow) +set(CMAKE_AUTOMOC_COMPILER_PREDEFINES OFF) + +#------------------------------------------------------------------------------ +# Module path +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/Modules") +## CMake looks for the NSIS script template in the module path. +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/Windows") + +if (MINGW OR MSYS) + message(FATAL_ERROR "MSYS and/or MinGW are not supported! Please use a Visual Studio environment!") +endif() + +#------------------------------------------------------------------------------ +# Options +#------------------------------------------------------------------------------ +option(ENABLE_CLASS_TESTING "Build the class tests of the GUI library." ON) +option(ENABLE_TOPP_TESTING "Add the tests of the GUI TOPP tools (ExecutePipeline, ImageCreator)." ON) +option(ENABLE_PIPELINE_TESTING "Add the TOPPAS pipeline tests (ExecutePipeline on the example workflows)." ON) +option(HAS_XSERVER "Indicates if a display is available (or QT_QPA_PLATFORM=offscreen is used). If OFF, the tests that open windows are disabled." ON) +option(ENABLE_DOCS "Add the documentation targets (doc, doc_class_only, ...)." ON) +option(ENABLE_TUTORIALS "Build the GUI code examples of the tutorials." ON) +option(NO_WEBENGINE_WIDGETS "Do not use QtWebEngineWidgets. Disables the JavaScript views in TOPPView." OFF) +## The packages do not deploy QtWebEngine (its helper process and resources), so they are +## always built without it. A normal variable for this configure only: the cache entry is +## kept, so a later configure with PACKAGE_TYPE none uses QtWebEngineWidgets again. +if(DEFINED PACKAGE_TYPE AND NOT "${PACKAGE_TYPE}" STREQUAL "none" AND NOT NO_WEBENGINE_WIDGETS) + message(STATUS "Packaging: QtWebEngineWidgets is not deployed, building with NO_WEBENGINE_WIDGETS=ON") + set(NO_WEBENGINE_WIDGETS ON) +endif() +option(OPENMS_TEST_INSTALLED_CONSUMER "Add a test that installs the GUI layer next to the OpenMS core and builds an external project against it." OFF) + +#------------------------------------------------------------------------------ +# Version information +#------------------------------------------------------------------------------ +set(OPENMS_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") +set(OPENMS_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") +set(OPENMS_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") +set(OPENMS_PACKAGE_VERSION "${PROJECT_VERSION}") +message(STATUS "Building OpenMS-TOPP-GUI ${OPENMS_PACKAGE_VERSION}") + +#------------------------------------------------------------------------------ +# Host directories (the names of the OpenMS build system, used by the moved code) +set(OPENMS_HOST_DIRECTORY "${PROJECT_SOURCE_DIR}") +set(OPENMS_HOST_BINARY_DIRECTORY "${PROJECT_BINARY_DIR}") + +#------------------------------------------------------------------------------ +# Compiler checks and flags +#------------------------------------------------------------------------------ +include(cmake/min_compiler_versions.cmake) +include(cmake/compiler_flags.cmake) + +if (NOT CMAKE_CXX_VISIBILITY_PRESET) + set(CMAKE_CXX_VISIBILITY_PRESET "hidden") +endif() +set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) + +option(ENABLE_IPO "Enable interprocedural optimizations (LTO)" OFF) +if(ENABLE_IPO) + include(CheckIPOSupported) + check_ipo_supported(RESULT _ipo_supported OUTPUT _ipo_output) + if(_ipo_supported) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(SEND_ERROR "IPO/LTO is not supported: ${_ipo_output}") + endif() +endif() + +option(ADDRESS_SANITIZER "Enable AddressSanitizer mode (quite slow)." OFF) +include(${PROJECT_SOURCE_DIR}/cmake/AddressSanitizer.cmake) + +# we build shared libraries +set(BUILD_SHARED_LIBS true) + +#------------------------------------------------------------------------------ +# CMake utilities +#------------------------------------------------------------------------------ +include(${PROJECT_SOURCE_DIR}/cmake/build_system_macros.cmake) +include(${PROJECT_SOURCE_DIR}/cmake/install_macros.cmake) +include(${PROJECT_SOURCE_DIR}/cmake/doc_macros.cmake) +include(${PROJECT_SOURCE_DIR}/cmake/add_library_macros.cmake) +include(${PROJECT_SOURCE_DIR}/cmake/export_macros.cmake) +include(${PROJECT_SOURCE_DIR}/cmake/topp_tool_macros.cmake) + +#------------------------------------------------------------------------------ +# Package type +set(VALID_PACKAGE_TYPES "none" # make/make install + "deb" # Debian package, installed on top of the openms package + "rpm" # RPM package, installed on top of the openms package + "pkg" # macOS productbuild installer, installed into the OpenMS folder + "nsis") # Windows installer, installed into the OpenMS installation +set(PACKAGE_TYPE "none" CACHE STRING "Package type: ${VALID_PACKAGE_TYPES}") +message(STATUS "Package type is '${PACKAGE_TYPE}'") +is_valid_package(${PACKAGE_TYPE}) + +## The install component of the GUI applications (and everything else that +## belongs to them at run time: the tool registry entry, the style sheet, ...). +set(OPENMS_GUI_APPLICATIONS_COMPONENT GUIApplications) + +## macOS code signing and notarization configuration +## SIGNING_IDENTITY - Developer ID Application certificate identity +## SIGNING_EMAIL - Apple ID email for notarization +## CPACK_PRODUCTBUILD_IDENTITY_NAME - Developer ID Installer certificate identity (for .pkg) +## APPLE_APP_SPECIFIC_NOTARIZATION_PASSWORD environment variable - app-specific password +if("${PACKAGE_TYPE}" STREQUAL "pkg") + if (NOT DEFINED SIGNING_IDENTITY OR "${SIGNING_IDENTITY}" STREQUAL "" OR "${SIGNING_IDENTITY}" STREQUAL "-") + message(WARNING "Trying to create a macOS package without SIGNING_IDENTITY being set. " + "Using the ad hoc identity. The package will NOT be notarized.") + set(CPACK_BUNDLE_APPLE_CERT_APP "-") + else() + set(CPACK_BUNDLE_APPLE_CERT_APP "${SIGNING_IDENTITY}") + message(STATUS "Code signing identity: ${SIGNING_IDENTITY}") + endif() + if (NOT DEFINED SIGNING_EMAIL OR "${SIGNING_EMAIL}" STREQUAL "") + message(STATUS "SIGNING_EMAIL not set. Notarization will be skipped.") + endif() +endif() + +#------------------------------------------------------------------------------ +# Output directories. Everything that runs goes to bin/ (GUI applications, GUI +# TOPP tools, tests, documenters), so on Windows the DLLs only need to be +# copied there once. +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") +set(OPENMS_BINARY_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +set(OPENMS_SHARE_DIR "${PROJECT_SOURCE_DIR}/share/OpenMS") + +## The registry of the GUI TOPP tools, OpenMS-GUI.tsv. ToolHandler reads every *.tsv +## of share/OpenMS/TOOLS, so installed next to the core's OpenMS.tsv it adds these tools. +set(OPENMS_TOOL_REGISTRY_BUILD_DIR "${PROJECT_BINARY_DIR}/share/OpenMS/TOOLS") +set(OPENMS_TOOL_REGISTRY_BUILD_FILE "${OPENMS_TOOL_REGISTRY_BUILD_DIR}/OpenMS-GUI.tsv") + +#------------------------------------------------------------------------------ +# Installation directories. They must equal those of the OpenMS core: the GUI +# layer is installed into the core's prefix (libOpenMS resolves the share +# directory, the tool registry and the TOPP executables relative to the +# executable). +set(INSTALL_BIN_DIR bin CACHE PATH "Installation directory for executables") +set(INSTALL_SHARE_DIR share/OpenMS CACHE PATH "Installation directory for shared data") +set(INSTALL_DOC_DIR share/doc CACHE PATH "Installation directory for documentation") +set(INSTALL_INCLUDE_DIR include CACHE PATH "Installation directory for header files") +if(WIN32) + set(DEF_INSTALL_CMAKE_DIR CMake) + set(DEF_INSTALL_LIB_DIR bin) +else() + set(DEF_INSTALL_CMAKE_DIR lib/cmake/OpenMSGUI) + set(DEF_INSTALL_LIB_DIR lib) +endif() +set(INSTALL_CMAKE_DIR ${DEF_INSTALL_CMAKE_DIR} CACHE PATH "Installation directory for the CMake package of the GUI library") +set(INSTALL_LIB_DIR ${DEF_INSTALL_LIB_DIR} CACHE PATH "Installation directory for libraries") +set(INSTALL_PLUGIN_SUBDIR plugins CACHE PATH "Installation subdirectory under the lib dir for Qt plugins") +set(INSTALL_PLUGIN_DIR ${INSTALL_LIB_DIR}/${INSTALL_PLUGIN_SUBDIR}) +file(RELATIVE_PATH INSTALL_LIB_PATH_REL_TO_BIN /${INSTALL_BIN_DIR} /${INSTALL_LIB_DIR}) +if(INSTALL_LIB_PATH_REL_TO_BIN STREQUAL "") + ## lib == bin (Windows): the plugins are next to the executables + set(QT_PLUGIN_PATH_REL_TO_BIN ${INSTALL_PLUGIN_SUBDIR}) +else() + set(QT_PLUGIN_PATH_REL_TO_BIN ${INSTALL_LIB_PATH_REL_TO_BIN}/${INSTALL_PLUGIN_SUBDIR}) +endif() + +#------------------------------------------------------------------------------ +# RPATH (as in the OpenMS core) +if(UNIX) + if(NOT DEFINED CMAKE_SKIP_BUILD_RPATH) + set(CMAKE_SKIP_BUILD_RPATH FALSE) + endif() + if(NOT DEFINED CMAKE_MACOSX_RPATH) + set(CMAKE_MACOSX_RPATH TRUE) + endif() + if(NOT DEFINED CMAKE_BUILD_WITH_INSTALL_RPATH) + set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + set(CMAKE_BUILD_WITH_INSTALL_NAME_DIR FALSE) + endif() + + # A plain installation finds the libraries it was linked against (the OpenMS + # core, Qt) through their directories in the install RPATH. Packages are + # installed into the core's prefix and use the relative RPATH below instead. + if (NOT DEFINED CMAKE_INSTALL_RPATH_USE_LINK_PATH) + if (APPLE) + if (NOT "${PACKAGE_TYPE}" STREQUAL "pkg") + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + endif() + elseif ("${PACKAGE_TYPE}" STREQUAL "none") + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + endif() + endif() + + if(NOT DEFINED CMAKE_INSTALL_RPATH) + if (APPLE) + if(NOT "${PACKAGE_TYPE}" STREQUAL "pkg") + set(CMAKE_INSTALL_RPATH "@executable_path/${INSTALL_LIB_PATH_REL_TO_BIN}/;@loader_path/;${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + else() + set(CMAKE_INSTALL_RPATH "@executable_path/${INSTALL_LIB_PATH_REL_TO_BIN}/") + endif() + else() + set(CMAKE_INSTALL_RPATH "$ORIGIN/${INSTALL_LIB_PATH_REL_TO_BIN}/") + endif() + endif() +endif() + +if (MSVC) + ## OpenMS_GUId.dll etc. in debug mode, like the core + if(NOT DEFINED CMAKE_DEBUG_POSTFIX) + set(CMAKE_DEBUG_POSTFIX "d") + endif() +endif() + +#------------------------------------------------------------------------------ +# Dependencies: the installed OpenMS core and Qt +#------------------------------------------------------------------------------ +include(cmake/third_party_notices.cmake) +include(cmake/find_openms.cmake) + +if(ENABLE_DOCS) + find_package(Doxygen) +endif() + +#------------------------------------------------------------------------------ +# The code +#------------------------------------------------------------------------------ +## here, not in src/, so that ctest finds the tests from the top of the build tree +enable_testing() +add_subdirectory(src) + +#------------------------------------------------------------------------------ +# Documentation +#------------------------------------------------------------------------------ +if(ENABLE_DOCS OR ENABLE_TUTORIALS) + add_subdirectory(doc) +endif() + +#------------------------------------------------------------------------------ +# CMake package of the GUI library (OpenMSGUIConfig.cmake) +#------------------------------------------------------------------------------ +openms_export_targets() +install_export_targets() + +#------------------------------------------------------------------------------ +# Installation and packaging +#------------------------------------------------------------------------------ +## The packages install the GUI layer into the installation of the OpenMS core of the same +## version (cmake/package_general.cmake). A plain 'cmake --install' (PACKAGE_TYPE none) +## installs it into CMAKE_INSTALL_PREFIX, which should be the core's prefix too. +include(cmake/third_party_licenses.cmake) + +if(NOT "${PACKAGE_TYPE}" STREQUAL "none") + include(cmake/package_general.cmake) + if("${PACKAGE_TYPE}" STREQUAL "pkg") + set(CPACK_GENERATOR "productbuild") + include(cmake/package_mac_productbuild.cmake) + elseif("${PACKAGE_TYPE}" STREQUAL "rpm") + set(CPACK_GENERATOR "RPM") + include(cmake/package_rpm.cmake) + elseif("${PACKAGE_TYPE}" STREQUAL "deb") + set(CPACK_GENERATOR "DEB") + include(cmake/package_deb.cmake) + elseif("${PACKAGE_TYPE}" STREQUAL "nsis") + set(CPACK_GENERATOR "NSIS") + include(cmake/package_nsis.cmake) + endif() + include(CPack) + include(cmake/package_components.cmake) +endif() + +## share/OpenMS/THIRD-PARTY-NOTICES-GUI.txt from the license files recorded above (and the +## copyright file of the Debian package) +openms_install_third_party_notices() + +#------------------------------------------------------------------------------ +# Final messages +#------------------------------------------------------------------------------ +include(cmake/messages.cmake) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..c055b4350 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,445 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 24, + "patch": 0 + }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/${presetName}", + "description": "Common settings. The OpenMS core (and on macOS/Windows Qt) is found through CMAKE_PREFIX_PATH: pass -DCMAKE_PREFIX_PATH=\";\" or set the environment variable CMAKE_PREFIX_PATH (list separator ':' on Linux/macOS, ';' on Windows). A compiler launcher such as ccache is taken from the environment variables CMAKE_C_COMPILER_LAUNCHER / CMAKE_CXX_COMPILER_LAUNCHER, which CMake reads by itself.", + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "CMAKE_COLOR_DIAGNOSTICS": "ON" + } + }, + { + "name": "linux-base", + "hidden": true, + "inherits": "base", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "macos-base", + "hidden": true, + "inherits": "base", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "windows-base", + "hidden": true, + "inherits": "base", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "linux-x64-base", + "hidden": true, + "inherits": "linux-base" + }, + { + "name": "linux-arm64-base", + "hidden": true, + "inherits": "linux-base" + }, + { + "name": "macos-arm64-base", + "hidden": true, + "inherits": "macos-base", + "description": "OpenMP_ROOT is where Homebrew on Apple silicon keeps its keg-only libomp: an OpenMS built from source with it needs it again in its consumers (find_dependency(OpenMP) of OpenMSConfig.cmake). The OpenMS SDK bundles its libomp and points FindOpenMP at it itself. Homebrew's Qt (qtbase, qtsvg) is found with -DCMAKE_PREFIX_PATH=\"...;$(brew --prefix)\".", + "cacheVariables": { + "OpenMP_ROOT": "/opt/homebrew/opt/libomp" + } + }, + { + "name": "windows-x64-base", + "hidden": true, + "inherits": "windows-base", + "description": "Windows x64: run from a Visual Studio developer shell (x64). The OpenMS core must be a Release (/MD) build or SDK for the Release presets; the Debug preset needs the Debug SDK (/MDd)." + }, + { + "name": "debug-base", + "hidden": true, + "inherits": "base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "release-base", + "hidden": true, + "inherits": "base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "relwithdebinfo-base", + "hidden": true, + "inherits": "base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "ci-base", + "hidden": true, + "description": "CI: all tests, the installed-consumer test, no Qt WebEngine (not available from every Qt source CI uses), no documentation (the release workflow enables it where needed).", + "cacheVariables": { + "ENABLE_CLASS_TESTING": "ON", + "ENABLE_TOPP_TESTING": "ON", + "ENABLE_PIPELINE_TESTING": "ON", + "OPENMS_TEST_INSTALLED_CONSUMER": "ON", + "NO_WEBENGINE_WIDGETS": "ON", + "ENABLE_DOCS": "OFF", + "ENABLE_TUTORIALS": "ON" + } + }, + { + "name": "linux-x64-debug", + "displayName": "Linux x64 Debug", + "description": "OpenMS-TOPP-GUI on Linux x64, Debug", + "inherits": [ + "linux-x64-base", + "debug-base" + ] + }, + { + "name": "linux-x64-relwithdebinfo", + "displayName": "Linux x64 RelWithDebInfo", + "description": "OpenMS-TOPP-GUI on Linux x64, RelWithDebInfo", + "inherits": [ + "linux-x64-base", + "relwithdebinfo-base" + ] + }, + { + "name": "linux-x64-release", + "displayName": "Linux x64 Release", + "description": "OpenMS-TOPP-GUI on Linux x64, Release", + "inherits": [ + "linux-x64-base", + "release-base" + ] + }, + { + "name": "linux-arm64-debug", + "displayName": "Linux arm64 Debug", + "description": "OpenMS-TOPP-GUI on Linux arm64, Debug", + "inherits": [ + "linux-arm64-base", + "debug-base" + ] + }, + { + "name": "linux-arm64-relwithdebinfo", + "displayName": "Linux arm64 RelWithDebInfo", + "description": "OpenMS-TOPP-GUI on Linux arm64, RelWithDebInfo", + "inherits": [ + "linux-arm64-base", + "relwithdebinfo-base" + ] + }, + { + "name": "linux-arm64-release", + "displayName": "Linux arm64 Release", + "description": "OpenMS-TOPP-GUI on Linux arm64, Release", + "inherits": [ + "linux-arm64-base", + "release-base" + ] + }, + { + "name": "macos-arm64-debug", + "displayName": "macOS arm64 Debug", + "description": "OpenMS-TOPP-GUI on macOS arm64, Debug", + "inherits": [ + "macos-arm64-base", + "debug-base" + ] + }, + { + "name": "macos-arm64-relwithdebinfo", + "displayName": "macOS arm64 RelWithDebInfo", + "description": "OpenMS-TOPP-GUI on macOS arm64, RelWithDebInfo", + "inherits": [ + "macos-arm64-base", + "relwithdebinfo-base" + ] + }, + { + "name": "macos-arm64-release", + "displayName": "macOS arm64 Release", + "description": "OpenMS-TOPP-GUI on macOS arm64, Release", + "inherits": [ + "macos-arm64-base", + "release-base" + ] + }, + { + "name": "windows-x64-debug", + "displayName": "Windows x64 Debug", + "description": "OpenMS-TOPP-GUI on Windows x64, Debug", + "inherits": [ + "windows-x64-base", + "debug-base" + ] + }, + { + "name": "windows-x64-relwithdebinfo", + "displayName": "Windows x64 RelWithDebInfo", + "description": "OpenMS-TOPP-GUI on Windows x64, RelWithDebInfo", + "inherits": [ + "windows-x64-base", + "relwithdebinfo-base" + ] + }, + { + "name": "windows-x64-release", + "displayName": "Windows x64 Release", + "description": "OpenMS-TOPP-GUI on Windows x64, Release", + "inherits": [ + "windows-x64-base", + "release-base" + ] + }, + { + "name": "linux-x64-ci", + "displayName": "Linux x64 CI", + "description": "The CI preset on Linux x64 (Release)", + "inherits": [ + "linux-x64-base", + "release-base", + "ci-base" + ], + "cacheVariables": { + "OPENMS_VERIFY_INTERFACE_HEADER_SETS": "ON" + } + }, + { + "name": "linux-arm64-ci", + "displayName": "Linux arm64 CI", + "description": "The CI preset on Linux arm64 (Release)", + "inherits": [ + "linux-arm64-base", + "release-base", + "ci-base" + ] + }, + { + "name": "macos-arm64-ci", + "displayName": "macOS arm64 CI", + "description": "The CI preset on macOS arm64 (Release)", + "inherits": [ + "macos-arm64-base", + "release-base", + "ci-base" + ] + }, + { + "name": "windows-x64-ci", + "displayName": "Windows x64 CI", + "description": "The CI preset on Windows x64 (Release)", + "inherits": [ + "windows-x64-base", + "release-base", + "ci-base" + ] + } + ], + "buildPresets": [ + { + "name": "linux-x64-debug", + "configurePreset": "linux-x64-debug" + }, + { + "name": "linux-x64-relwithdebinfo", + "configurePreset": "linux-x64-relwithdebinfo" + }, + { + "name": "linux-x64-release", + "configurePreset": "linux-x64-release" + }, + { + "name": "linux-arm64-debug", + "configurePreset": "linux-arm64-debug" + }, + { + "name": "linux-arm64-relwithdebinfo", + "configurePreset": "linux-arm64-relwithdebinfo" + }, + { + "name": "linux-arm64-release", + "configurePreset": "linux-arm64-release" + }, + { + "name": "macos-arm64-debug", + "configurePreset": "macos-arm64-debug" + }, + { + "name": "macos-arm64-relwithdebinfo", + "configurePreset": "macos-arm64-relwithdebinfo" + }, + { + "name": "macos-arm64-release", + "configurePreset": "macos-arm64-release" + }, + { + "name": "windows-x64-debug", + "configurePreset": "windows-x64-debug" + }, + { + "name": "windows-x64-relwithdebinfo", + "configurePreset": "windows-x64-relwithdebinfo" + }, + { + "name": "windows-x64-release", + "configurePreset": "windows-x64-release" + }, + { + "name": "linux-x64-ci", + "configurePreset": "linux-x64-ci" + }, + { + "name": "linux-arm64-ci", + "configurePreset": "linux-arm64-ci" + }, + { + "name": "macos-arm64-ci", + "configurePreset": "macos-arm64-ci" + }, + { + "name": "windows-x64-ci", + "configurePreset": "windows-x64-ci" + } + ], + "testPresets": [ + { + "name": "test-base", + "hidden": true, + "output": { + "outputOnFailure": true + }, + "execution": { + "noTestsAction": "error" + }, + "environment": { + "QT_QPA_PLATFORM": "offscreen" + } + }, + { + "name": "test-base-ci", + "hidden": true, + "execution": { + "jobs": 4 + } + }, + { + "name": "linux-x64-debug", + "configurePreset": "linux-x64-debug", + "inherits": "test-base" + }, + { + "name": "linux-x64-relwithdebinfo", + "configurePreset": "linux-x64-relwithdebinfo", + "inherits": "test-base" + }, + { + "name": "linux-x64-release", + "configurePreset": "linux-x64-release", + "inherits": "test-base" + }, + { + "name": "linux-arm64-debug", + "configurePreset": "linux-arm64-debug", + "inherits": "test-base" + }, + { + "name": "linux-arm64-relwithdebinfo", + "configurePreset": "linux-arm64-relwithdebinfo", + "inherits": "test-base" + }, + { + "name": "linux-arm64-release", + "configurePreset": "linux-arm64-release", + "inherits": "test-base" + }, + { + "name": "macos-arm64-debug", + "configurePreset": "macos-arm64-debug", + "inherits": "test-base" + }, + { + "name": "macos-arm64-relwithdebinfo", + "configurePreset": "macos-arm64-relwithdebinfo", + "inherits": "test-base" + }, + { + "name": "macos-arm64-release", + "configurePreset": "macos-arm64-release", + "inherits": "test-base" + }, + { + "name": "windows-x64-debug", + "configurePreset": "windows-x64-debug", + "inherits": "test-base" + }, + { + "name": "windows-x64-relwithdebinfo", + "configurePreset": "windows-x64-relwithdebinfo", + "inherits": "test-base" + }, + { + "name": "windows-x64-release", + "configurePreset": "windows-x64-release", + "inherits": "test-base" + }, + { + "name": "linux-x64-ci", + "configurePreset": "linux-x64-ci", + "inherits": [ + "test-base", + "test-base-ci" + ] + }, + { + "name": "linux-arm64-ci", + "configurePreset": "linux-arm64-ci", + "inherits": [ + "test-base", + "test-base-ci" + ] + }, + { + "name": "macos-arm64-ci", + "configurePreset": "macos-arm64-ci", + "inherits": [ + "test-base", + "test-base-ci" + ] + }, + { + "name": "windows-x64-ci", + "configurePreset": "windows-x64-ci", + "inherits": [ + "test-base", + "test-base-ci" + ] + } + ] +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..f65bfef1f --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +### OpenMS Code of Conduct + +#### Code of Conduct (Summary View) + +Below is a summary of the OpenMS Code of Conduct. + +We are dedicated to providing a welcoming and supportive environment for all people, regardless of background or identity. By participating in this community, participants accept to abide by OpenMS' Code of Conduct and accept the procedures by which any Code of Conduct incidents are resolved. Any form of behaviour to exclude, intimidate, or cause discomfort is a violation of the Code of Conduct. In order to foster a positive and professional learning environment we encourage the following kinds of behaviours in all platforms and events: + +* Use welcoming and inclusive language +* Be respectful of different viewpoints and experiences +* Gracefully accept constructive criticism +* Focus on what is best for the community +* Show courtesy and respect towards other community members + +If you believe someone is violating the Code of Conduct, we ask that you report it to the Code of Conduct Committee, who will take the appropriate action to address the situation. + +#### Code of Conduct (Detailed View) + +Part 1. Introduction + +OpenMS is a community-led project. We value the involvement of everyone in the community. We are committed to creating a friendly and respectful place for learning, teaching and contributing. All participants in our events and communications are expected to show respect and courtesy to others. + +To make clear what is expected, everyone participating in OpenMS activities is required to conform to the Code of Conduct. This Code of Conduct applies to all spaces managed by OpenMS including, but not limited to, workshops, email lists, and online forums such as GitHub, Slack and Twitter. Workshop hosts are expected to assist with the enforcement of the Code of Conduct. + +The OpenMS Code of Conduct Committee is responsible for enforcing the Code of Conduct. It can be contacted by emailing [open-ms-c_o_c@lists.sourceforge.net](mailto:open-ms-c_o_c@lists.sourceforge.net). +All reports will be reviewed by the Code of Conduct Committee and will be kept confidential. + +Part 2. OpenMS Code of Conduct + +OpenMS is dedicated to providing a welcoming and supportive environment for all people, regardless of background or identity. As such, we do not tolerate behaviour that is disrespectful to our teachers or learners or that excludes, intimidates, or causes discomfort to others. We do not tolerate discrimination or harassment based on characteristics that include, but are not limited to, gender identity and expression, sexual orientation, disability, physical appearance, body size, citizenship, nationality, ethnic or social origin, pregnancy, familial status, veteran status, genetic information, religion or belief (or lack thereof), membership of a national minority, property, age, education, socio-economic status, technical choices, and experience level. + +Everyone who participates in OpenMS activities is required to conform to this Code of Conduct. It applies to all spaces managed by OpenMS including, but not limited to, workshops, email lists, and online forums such as GitHub, Gitter and Twitter. Workshop hosts are expected to assist with the enforcement of the Code of Conduct. By participating, participants indicate their acceptance of the procedures by which OpenMS resolves any Code of Conduct incidents, which may include storage and processing of their personal information. + +Part 2.1 Expected behaviour + +All participants in our events and communications are expected to show respect and courtesy to others. All interactions should be professional regardless of platform: either online or in-person. In order to foster a positive and professional learning environment we encourage the following kinds of behaviours in all OpenMS events and platforms: + +* Use welcoming and inclusive language +* Be respectful of different viewpoints and experiences +* Gracefully accept constructive criticism +* Focus on what is best for the community +* Show courtesy and respect towards other community members + +Note: See the [four social rules](https://www.recurse.com/manual#sub-sec-social-rules) for further recommendations. + +Part 2.2 Unacceptable behaviour + +Examples of unacceptable behaviour by participants at any OpenMS event/platform include: + +- written or verbal comments which have the effect of excluding people on the basis of membership of any specific group +- causing someone to fear for their safety, such as through stalking, following, or intimidation +- violent threats or language directed against another person +- the display of sexual or violent images +- unwelcome sexual attention +- nonconsensual or unwelcome physical contact +- sustained disruption of talks, events or communications +- insults or put downs +- sexist, racist, homophobic, transphobic, ableist, or exclusionary jokes +- excessive swearing +- incitement to violence, suicide, or self-harm +- continuing to initiate interaction (including photography or recording) with someone after being asked to stop +- publication of private communication without consent + +Part 2.3 Consequences of Unacceptable behaviour + +Participants who are asked to stop any inappropriate behaviour are expected to comply immediately. This applies to any OpenMS events and platforms, either online or in-person. If a participant engages in behaviour that violates this code of conduct, the organisers may warn the offender, ask them to leave the event or platform (without refund), or engage OpenMS Code of Conduct Committee to investigate the Code of Conduct violation and impose appropriate sanctions. + +#### About this Document + +This document is adapted from guidelines written by the [The Carpentries Project](https://github.com/carpentries/handbook/blob/master/topic_folders/policies/code-of-conduct.md), which was itself based on the [Django Project](https://www.djangoproject.com/conduct/enforcement-manual/) and [Ada Initiative](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Responding_to_reports) template and the [PyCon 2013 Procedure for Handling Harassment Incidents](https://us.pycon.org/2013/about/code-of-conduct/harassment-incidents/). + +[licensed CC BY 3.0](http://creativecommons.org/licenses/by/3.0/) + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5ab869e58 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +Help us to make OpenMS better and become part of the OpenMS open-source community. + +This repository holds the graphical tools of OpenMS (TOPPView, TOPPAS, INIFileEditor, ExecutePipeline, ImageCreator) and the OpenMS_GUI library. The OpenMS library and the TOPP command line tools are developed in [OpenMS/OpenMS](https://github.com/OpenMS/OpenMS); a change that needs both repositories needs a pull request in each, the one in OpenMS first. + +This document is displayed because you either opened an issue or you want to provide your code as a pull request for inclusion into OpenMS. Please take a look at the appropriate section below to find some details on how we handle this process. +When interacting with other developers, users or anyone else from our community, please adhere to +[our CODE OF CONDUCT](CODE_OF_CONDUCT.md) + +> **New to open source or the project?** Start with our [Contributor Onboarding Guide](https://openms.de/contribute/) for a beginner-friendly introduction. + +# Reporting an Issue: + +You most likely came here to: + - report bugs or annoyances + - pose questions + - point out missing documentation + - request new features + +To do so, open a [new issue](https://github.com/OpenMS/OpenMS-TOPP-GUI/issues/new/choose) describing the bug, your question, etc. + +If you found a bug, e.g. an OpenMS tool crashes during data processing, it is essential to provide some basic information: + - the OpenMS version you are running (and of the GUI tools, if they differ) + - the platform you are running OpenMS on (Windows 10, ...) + - how you installed OpenMS (e.g., conda, binary installers, self compiled) + - a description on how to reproduce the bug + - relevant tool output (e.g., error messages) + - data to repoduce the bug (If possible as a GitHub gist. Other platforms like Dropbox, Google Drive links also work. If you can't share the data publicly please indicate this and we will contact you in private.) + +If you are an official OpenMS team member: + - label your issue using github labels (e.g. as: question, defect) that indicate the type of issue and which components of OpenMS (blue labels) are affected. The severity is usually assigned by OpenMS maintainers and used internally to e.g. indicate if a bug is a blocker for a new release. + +# Opening a Pull Request + +Before getting started we recommend taking a look at the developer guide: +https://openms.readthedocs.io/en/latest/manual/develop.html + +Before you open the pull request, make sure you + - adhere to [our coding conventions](https://openms.readthedocs.io/en/latest/manual/develop.html#coding-conventions) + - have [unit tests and functional tests](https://openms.readthedocs.io/en/latest/manual/develop.html#automated-unit-tests) - GUI class tests use Qt TestLib, see `src/tests/class_tests/openms_gui/source/` + - have [proper documentation](https://openms.readthedocs.io/en/latest/manual/develop/developer-faq.html#doxygen-documentation) - check it with the `doc` target (see README.md) +A core developer will review your changes to the main development branch (develop) and approve them (or ask for modifications). You may indicate the prefered reviewer(s) by adding links to them in a comment section (e.g., @cbielow @hendrikweisser @hroest @jpfeuffer @timosachsenberg) + +Also consider getting in contact with the core developers early. They might provide additional guidance and valuable information on how your specific aim is achieved. This might give you a head start in, for example, developing novel tools or algorithms. + +Happy coding! + +--- + +## Additional Resources + +For detailed coding conventions, architectural guidelines, and technical documentation, see the [OpenMS Developer Guide](https://openms.readthedocs.io/en/latest/contribute-to-openms/index.html). diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..713de5ecd --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +-------------------------------------------------------------------------- + OpenMS -- Open-Source Mass Spectrometry +-------------------------------------------------------------------------- +Copyright OpenMS Inc. -- Eberhard Karls University Tuebingen, +ETH Zurich, and Freie Universitaet Berlin 2002-present. + +This software is released under a three-clause BSD license: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of any author or any participating institution + may be used to endorse or promote products derived from this software + without specific prior written permission. +For a full list of authors, refer to the file AUTHORS. +-------------------------------------------------------------------------- +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING +INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..ea46b8867 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2002-present, OpenMS Inc. -- Eberhard Karls University Tuebingen, ETH Zurich, and Freie Universitaet Berlin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/License.txt b/License.txt new file mode 100644 index 000000000..ca86fe667 --- /dev/null +++ b/License.txt @@ -0,0 +1,54 @@ +----------------------------------------------------------------- + OpenMS -- Open-Source Mass Spectrometry +----------------------------------------------------------------- +Copyright OpenMS Inc. -- Eberhard Karls University Tuebingen, +ETH Zurich, and Freie Universitaet Berlin 2002-present. + +This software is released under a three-clause BSD license: + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + * Neither the name of any author or any participating + institution may be used to endorse or promote products + derived from this software without specific prior written + permission. +For a full list of authors, refer to the file AUTHORS. +----------------------------------------------------------------- +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE +CONTRIBUTING INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +The source code of the OpenMS GUI tools is available at +https://github.com/OpenMS/OpenMS-TOPP-GUI, that of OpenMS via +our homepage https://www.openms.de + +Third-party software. The installers of the OpenMS GUI tools +contain the Qt libraries and plugins (https://www.qt.io), +licensed under the GNU Lesser General Public License v3.0 +(LGPL-3.0-only), and on macOS the libraries Qt depends on; +the Linux packages use the Qt of the distribution instead. +THIRD-PARTY-NOTICES-GUI.txt in the share/OpenMS folder of an +installation lists that software and gives its license, +copyright and notice texts; the license files are in +share/OpenMS/LICENSES. The Qt source code is available at +https://download.qt.io/archive/qt/. + +The source code contains third-party code too: the CMake module +GetGitRevisionDescription +(https://github.com/rpavlik/cmake-modules, Boost Software +License 1.0, http://www.boost.org/LICENSE_1_0.txt). diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..0152625a8 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,24 @@ +## Description + + + +## Checklist +- [ ] Make sure that you are listed in the AUTHORS file +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] New and existing unit tests pass locally with my changes +- [ ] If this needs a change in OpenMS (OpenMS/OpenMS), the PR there is linked + +No need to edit the CHANGELOG file: the maintainers update it for a release. + +### How can I get additional information on failed tests during CI +
+ Click to expand +If your PR is failing you can check out + +- The details of the action statuses at the end of the PR or the "Checks" tab. +- The log of the failed job: `ctest --output-on-failure` prints the output of each failed test. + +
+ +--- +:warning: Note: Once you opened a PR try to minimize the number of *pushes* to it as every push will trigger CI (automated builds and test) and is rather heavy on our infrastructure (e.g., if several pushes per day are performed). diff --git a/README.md b/README.md new file mode 100644 index 000000000..f72429341 --- /dev/null +++ b/README.md @@ -0,0 +1,156 @@ +# OpenMS-TOPP-GUI + +[![License (3-Clause BSD)](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg?style=flat-square)](https://opensource.org/licenses/BSD-3-Clause) +[![Documentation](https://img.shields.io/static/v1?style=flat-square&message=Doxygen&color=2C4AA8&logo=ReadTheDocs&logoColor=FFFFFF&label=GUI%20docs)](https://archive.openms.de/openms/Documentation/release/latest/html-gui/index.html) +[![User manual](https://img.shields.io/static/v1?style=flat-square&message=ReadTheDocs&color=8CA1AF&logo=ReadTheDocs&logoColor=FFFFFF&label=User%20manual)](https://openms.readthedocs.io/en/latest/index.html) + +The graphical tools of [OpenMS](https://github.com/OpenMS/OpenMS), the open-source software for +LC-MS data management and analysis: + +- **TOPPView**: viewer and analysis tool for MS and LC-MS data (spectra, chromatograms, ion mobility, + peak maps, features, identifications). +- **TOPPAS**: assistant for designing and running workflows of TOPP tools. +- **INIFileEditor**: editor for the parameter (INI) files of TOPP tools. +- **ExecutePipeline**: runs TOPPAS workflows on the command line. +- **ImageCreator**: writes an LC-MS map as an image. +- **OpenMS_GUI**: the Qt library behind these tools (plot widgets, dialogs, TOPPView/TOPPAS + components), usable in applications of your own. + +## Relation to OpenMS + +OpenMS itself (the library, the TOPP command line tools, pyOpenMS) has no Qt dependency. This +repository is the layer on top of it: + +- **Same version.** A release of OpenMS-TOPP-GUI is built against the OpenMS release of the same + version (`find_package(OpenMS EXACT)`), and nightly builds against OpenMS `develop`. + The version in `CMakeLists.txt` is bumped together with OpenMS. +- **Layered installation.** The GUI layer is installed *into the OpenMS installation* (same `bin/`, + `lib/`, `share/OpenMS`): the GUI tools find the shared data, the TOPP tool registry and the TOPP + tools next to them there. Its tool registry `share/OpenMS/TOOLS/OpenMS-GUI.tsv` adds + ExecutePipeline and ImageCreator to the TOPP tools of OpenMS. +- **CMake package.** The library is installed as the CMake package `OpenMSGUI` + (`find_package(OpenMSGUI)`, target `OpenMS::OpenMS_GUI`). With the GUI layer installed into the + OpenMS prefix, `find_package(OpenMS COMPONENTS GUI)` finds it as well. + +The paths of this repository mirror those of OpenMS (`src/openms_gui`, `src/tests`, `doc`, `cmake`), +and the history of the moved files was kept, so changes can be cherry-picked between the two. + +## Installation for users + +1. Install OpenMS first, from the [OpenMS installers](https://openms.readthedocs.io/en/latest/about/installation.html) + or the [archive](https://archive.openms.de/openms/OpenMSInstaller/). +2. Then install the GUI installer of the **same version** from the + [archive](https://archive.openms.de/openms/OpenMSInstaller/) (`OpenMS-GUI--...`): + - **Windows**: `OpenMS-GUI--Win64.exe` installs into the OpenMS installation folder and + adds the start menu entries and file associations. Uninstalling OpenMS removes it as well. + - **macOS**: `OpenMS-GUI--macOS-.pkg` installs TOPPView, TOPPAS and + INIFileEditor into the OpenMS folder in `/Applications`. + - **Linux**: the `openms-gui` package (`.deb` / `.rpm`) depends on the `openms` package of the + same version. + +Tool documentation: [GUI documentation](https://archive.openms.de/openms/Documentation/release/latest/html-gui/index.html), +TOPPView user guide and tutorials: [openms.readthedocs.io](https://openms.readthedocs.io/en/latest/index.html). + +## Building + +Requirements: + +- An installed OpenMS of the same version with the core and CLI layers (`OpenMS::OpenMS`, + `OpenMS::OpenMS_CLI`): the OpenMS SDK of the release or nightly, or OpenMS built from source and + installed. OpenMS must be built **without** its former GUI (OpenMS 3.6 and older nightlies built with + `WITH_GUI=ON` are refused at configure time). +- Qt 6.1 or newer: Core, Gui, Widgets, Svg, OpenGLWidgets (macOS also PrintSupport); WebEngineWidgets + optional (JavaScript views of TOPPView; `-DNO_WEBENGINE_WIDGETS=ON` builds without it). +- CMake 3.24 or newer, a C++23 compiler (as for OpenMS). +- Optional: doxygen for the documentation. + +```bash +cmake -S OpenMS-TOPP-GUI -B OpenMS-TOPP-GUI-build -G Ninja \ + -DCMAKE_PREFIX_PATH=";" +cmake --build OpenMS-TOPP-GUI-build -j$(nproc) +``` + +To install, install into the prefix of the OpenMS installation it was built against (see +*Layered installation* above): + +```bash +cmake --install OpenMS-TOPP-GUI-build --prefix "" +``` + +On Linux, the distribution's Qt works (Ubuntu: `qt6-base-dev libqt6svg6-dev libqt6opengl6-dev`, see +`tools/ci/deps-ubuntu.sh`). + +Important CMake options: + +| Option | Default | Meaning | +|---|---|---| +| `ENABLE_CLASS_TESTING`, `ENABLE_TOPP_TESTING`, `ENABLE_PIPELINE_TESTING` | `ON` | class tests of the GUI library, tests of the GUI TOPP tools, TOPPAS pipeline tests | +| `OPENMS_TOPP_BIN_DIR` | `bin/` of the found OpenMS | where `FuzzyDiff` and the TOPP tools of OpenMS are; tests that need them are left out when they are missing (the SDK has no TOPP tools) | +| `OPENMS_EXAMPLES_DIR` | `share/OpenMS/examples` of the found OpenMS | example workflows and data of OpenMS for the pipeline tests; they are left out when there are none (the SDK has no examples) | +| `HAS_XSERVER` | `ON` | `OFF` leaves out the tests that open windows | +| `NO_WEBENGINE_WIDGETS` | `OFF` | build without Qt WebEngine | +| `ENABLE_DOCS` | `ON` | documentation targets (`doc`), if doxygen is found | +| `OPENMS_DOXYGEN_TAGFILE` | found `share/doc/OpenMS.tag` | tag file of the OpenMS documentation (`=`), for links to TOPP tools and OpenMS classes | +| `ENABLE_TUTORIALS` | `ON` | GUI code examples (`Tutorials_build`) | +| `PACKAGE_TYPE` | `none` | `deb`, `rpm`, `pkg` or `nsis` to build an installer with the target `dist` | +| `OPENMS_CORE_VERSION_FULLSTRING` | | full version string of the OpenMS core, for the package names when the OpenMS CMake package does not provide it | + +## Testing + +```bash +ctest --test-dir OpenMS-TOPP-GUI-build --output-on-failure +``` + +The tests set `QT_QPA_PLATFORM=offscreen` themselves, so no display is needed. The tests of +ExecutePipeline and ImageCreator compare against reference files with `FuzzyDiff` and the pipeline +tests run TOPP tools, both from `OPENMS_TOPP_BIN_DIR`: point it to the `bin/` of an OpenMS +installation with TOPP tools when building against the SDK. The pipeline tests also use the example +workflows and data in `OPENMS_EXAMPLES_DIR` (default: `share/OpenMS/examples` of the found OpenMS): +the SDK has none, so point it to the `share/OpenMS/examples` of a full OpenMS installation as well. + +## Documentation + +```bash +cmake --build OpenMS-TOPP-GUI-build --target doc +``` + +runs the GUI documenters (they start the GUI tools, offscreen) and doxygen, and writes +`OpenMS-TOPP-GUI-build/doc/html-gui/index.html`: the pages of the GUI tools, the TOPPAS tutorial and +the classes of the GUI library. It is installed into `share/doc/html-gui` (component `doc_gui`), next to +`share/doc/html` of OpenMS. TOPPView and TOPPAS open it from there with F1 and fall back to the online +copy at `https://archive.openms.de/openms/Documentation//html-gui/`. + +## Packaging + +```bash +cmake -S OpenMS-TOPP-GUI -B build-package -DPACKAGE_TYPE=deb -DCMAKE_PREFIX_PATH="..." +cmake --build build-package -j$(nproc) +cmake --build build-package --target dist +``` + +`dist` only packages what is built, so build first. Pass `-DOPENMS_CORE_VERSION_FULLSTRING=` (e.g. `3.7.0-pre-nightly-2026-11-06`) when the OpenMS CMake package does not provide +it: it is the version the package requires (Debian: `openms (= 3.7.0~nightly.2026.11.06)`) and part of +its file name. + +The packages are add-ons to the OpenMS packages of the same version, named like them with +`OpenMS-GUI-` in place of `OpenMS-` (e.g. `OpenMS-GUI--Win64.exe`). They contain the GUI +library, the GUI tools, their shared data and the Qt libraries (except on Linux, which uses the +distribution's Qt), never anything OpenMS ships. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md). Pull requests go against `develop`. Issues of the GUI tools can +be reported here or in the [OpenMS issue tracker](https://github.com/OpenMS/OpenMS/issues). + +## Citing + +Please cite OpenMS, see [CITATION.cff](CITATION.cff): + +Pfeuffer, J., Bielow, C., Wein, S. et al. OpenMS 3 enables reproducible analysis of large-scale mass +spectrometry data. *Nat Methods* 21, 365–367 (2024). https://doi.org/10.1038/s41592-024-02197-7 + +## License + +BSD 3-Clause, see [LICENSE](LICENSE). The installers contain Qt, which is licensed under the LGPL 3.0 +(see `share/OpenMS/THIRD-PARTY-NOTICES-GUI.txt` of an installation). diff --git a/cmake/AddressSanitizer.cmake b/cmake/AddressSanitizer.cmake new file mode 100644 index 000000000..73c5d4d20 --- /dev/null +++ b/cmake/AddressSanitizer.cmake @@ -0,0 +1,60 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Hannes Roest $ +# $Authors: Hannes Roest, Timo Sachsenberg $ +# -------------------------------------------------------------------------- + + +#------------------------------------------------------------------------------ +# This cmake file enables the AddressSanitizer and UndefinedBehaviorSanitizer +# see http://clang.llvm.org/docs/AddressSanitizer.html and https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html +# http://en.wikipedia.org/wiki/AddressSanitizer + +# Do the check for support in the beginning and NOT FOR EVERY TARGET +#------------------------------------------------------------------------------ +if(ADDRESS_SANITIZER) + if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + # add compiler flag + if (MSVC AND MSVC_VERSION LESS 1920) + message(FATAL_ERROR "AddressSanitizer is not supported for MSVC versions < 2019.") + endif() + else() + message(FATAL_ERROR "AddressSanitizer is supported for OpenMS debug mode only.") + endif() + message(STATUS "AddressSanitizer enabled. Adding flags to every target.") +endif() + + +function(add_asan_to_target TARGET_NAME_ARG) + if(ADDRESS_SANITIZER) + if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + # add compiler flag + if (MSVC) + # add AddressSanitizer for compiler and linker + target_compile_options("${TARGET_NAME_ARG}" + PUBLIC + /fsanitize=address + ) + target_link_options("${TARGET_NAME_ARG}" + PUBLIC + /fsanitize=address + ) + else() + # add AddressSanitizer for compiler and linker + target_compile_options("${TARGET_NAME_ARG}" + PUBLIC + -fsanitize=address,undefined + -fno-sanitize-recover=all + -fno-sanitize=vptr + -fno-omit-frame-pointer) + target_link_options("${TARGET_NAME_ARG}" + PUBLIC + -fsanitize=address,undefined + -fno-sanitize-recover=all + -fno-sanitize=vptr) + endif() + endif() + endif() +endfunction() diff --git a/cmake/MacOSX/README.md b/cmake/MacOSX/README.md new file mode 100644 index 000000000..2f50a7ef4 --- /dev/null +++ b/cmake/MacOSX/README.md @@ -0,0 +1,112 @@ +## macOS Code Signing and Notarization + +The macOS package of the OpenMS GUI is code signed and notarized to ensure users can run it without security warnings from Gatekeeper. + +The GUI package installs into the folder of the OpenMS core package of the same version, and its +applications load the libraries of the core. With the hardened runtime, macOS loads only libraries +signed by the same team, so both packages have to be signed with the certificates of the same team +(OpenMS Inc., `C64UCGJ5PL`). + +### Prerequisites + +1. **Apple Developer Account** ($99/year) - https://developer.apple.com +2. **Xcode** installed with developer tools (`xcode-select --install`) +3. **Certificates** exported from Xcode: + - **Developer ID Application** - for signing binaries + - **Developer ID Installer** - for signing .pkg installers + +### Required GitHub Secrets + +For CI/CD, the following secrets must be configured in your GitHub repository. + +**Note:** Only certificates and passwords need to be secrets. Identity strings and email addresses are public information. + +| Secret | Description | +|--------|-------------| +| `APPLE_DEVELOPER_ID_APPLICATION_CERT` | Base64-encoded Developer ID Application .p12 certificate | +| `APPLE_DEVELOPER_ID_APPLICATION_PASSWORD` | Password for the Application certificate | +| `APPLE_DEVELOPER_ID_INSTALLER_CERT` | Base64-encoded Developer ID Installer .p12 certificate | +| `APPLE_DEVELOPER_ID_INSTALLER_PASSWORD` | Password for the Installer certificate | +| `APPLE_APP_SPECIFIC_NOTARIZATION_PASSWORD` | App-specific password for notarization | +| `KEYCHAIN_PASSWORD` | Password for the temporary CI keychain | + +The following are configured directly in the workflow (not secrets): +- **Signing Identity**: `Developer ID Application: OpenMS Inc. (C64UCGJ5PL)` +- **Installer Identity**: `Developer ID Installer: OpenMS Inc. (C64UCGJ5PL)` +- **Email**: `apple@openms.de` +- **Team ID**: `C64UCGJ5PL` (extracted from the identity string) + +### Exporting Certificates + +1. Open **Xcode → Settings → Accounts** +2. Select your Team and click **Manage Certificates** +3. Create or select **Developer ID Application** certificate +4. Right-click → **Export Certificate** → save as `.p12` with a password +5. Base64 encode for CI: `base64 -i certificate.p12 | pbcopy` +6. Repeat for **Developer ID Installer** certificate + +### Finding Your Identity Strings and Team ID + +Run this command to see your available signing identities: + +```shell +security find-identity -v -p codesigning +``` + +Output example: +``` +1) ABC123XYZ... "Developer ID Application: OpenMS Inc. (C64UCGJ5PL)" +2) DEF456ABC... "Developer ID Installer: OpenMS Inc. (C64UCGJ5PL)" + 2 valid identities found +``` + +- The full quoted string is your identity (e.g., `Developer ID Application: OpenMS Inc. (C64UCGJ5PL)`) +- The 10-character code in parentheses is your Team ID (e.g., `C64UCGJ5PL`) + +### Creating an App-Specific Password + +For notarization, create an app-specific password: + +1. Go to https://appleid.apple.com +2. Sign in → Security → App-Specific Passwords → Generate +3. Use this password for `APPLE_APP_SPECIFIC_NOTARIZATION_PASSWORD` + +### Local Testing + +To test code signing locally: + +```shell +# Sign a binary +codesign --force --options runtime --timestamp -s "Developer ID Application: Your Name (TEAMID)" /path/to/binary + +# Verify signature +codesign -dv --verbose=4 /path/to/binary + +# Notarize a package (requires CODESIGNPW environment variable) +export CODESIGNPW="your-app-specific-password" +./cmake/MacOSX/notarize.sh package.pkg de.openms.gui your@email.com CODESIGNPW TEAMID . + +# Verify notarization +spctl -a -vvv -t install package.pkg +``` + +### Troubleshooting + +**"The specified item could not be found in the keychain"** +- Ensure the certificate is imported in the keychain +- Verify the identity string matches exactly (run `security find-identity -v`) + +**Notarization fails with "Invalid"** +- Check if all binaries inside the package are signed with hardened runtime +- Get detailed log: `xcrun notarytool log --apple-id EMAIL --password PW --team-id TEAM log.json` + +**"Developer cannot be verified" warning for users** +- The package was not notarized or stapling failed +- Users can override: System Settings → Privacy & Security → Allow + +### Files in this Directory + +- `notarize.sh` - Script for notarizing packages (.pkg, .dmg, .zip) using notarytool: + `notarize.sh [log_folder]` +- `fix_dependencies.rb` - Script to fix library paths for distribution (cmake/package_mac_productbuild.cmake) +- `openms_logo_corner_small.png` - Background of the installer diff --git a/cmake/MacOSX/fix_dependencies.rb b/cmake/MacOSX/fix_dependencies.rb new file mode 100755 index 000000000..35397c3d2 --- /dev/null +++ b/cmake/MacOSX/fix_dependencies.rb @@ -0,0 +1,576 @@ +#!/usr/bin/ruby + +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Stephan Aiche $ +# $Authors: Stephan Aiche $ +# -------------------------------------------------------------------------- + +require 'getoptlong' +require 'pathname' +require 'set' +require 'fileutils' + +##### GLOBAL VARIABLES +$handledLibraries = Set.new +$lib_dir = nil +$bin_dir = nil +$plugin_dir = nil +$install_name_tool=`which install_name_tool`.strip +$DEBUG = false +$currentIndent=0 +$executableId="@executable_path/" +$EXTRACTFW = false +$auto_relative = true +$nocopy = false +$target_rpaths = nil + +############################################################################### +def debug(message) + if($DEBUG) + prefix="" + for index in 0 ... $currentIndent + prefix += "--" + end + puts "#{prefix}> #{message}" + end +end + +############################################################################### +def fixable(name, path) + if File.directory?(path + name) + return false + elsif name.match(/^\./) + return false + elsif name.match(/\.app$/) + return false + else + filename = "#{path}/#{name}" + debug filename.to_s + otool_out=`otool -L #{filename}` + ## Otool returns exit code 0 even if it is not an object file -> check output + return !( otool_out =~ /.*object file.*/ ) + end +end + +############################################################################### +def isFramework(path) + return path.match(/\.framework/) +end + +############################################################################### +def cleanOtoolEntry(otool_line) + return `echo "#{otool_line}" | tr -d ' \t' | sed 's/(.*//g'`.strip +end + +############################################################################### +def extractInstallName(otool_output) + # Skip file basename + otool_output.delete_at(0).strip + # Save install name (id) + id = otool_output.delete_at(0).strip + # clean id + id = cleanOtoolEntry(id) + # return id and remaining otool output = dependencies + return id, otool_output +end + +##### METHODS USED TO HANDLE THE BUNDLING + +############################################################################### +def fixLoadPath(oldpath,libpath,target) + debug "#{$install_name_tool} -change #{oldpath} #{$executableId}#{libpath} #{target}" + `#{$install_name_tool} -change #{oldpath} #{$executableId}#{libpath} #{target}` +end + +############################################################################### +def fixId(target, libname) + debug "#{$install_name_tool} -id #{$executableId}#{libname} #{target}" + `#{$install_name_tool} -id #{$executableId}#{libname} #{target}` +end + +############################################################################### +def adjustRpaths(target, newpaths) + return if newpaths.nil? + # list existing rpaths + existing = %x[otool -l #{target} | grep -A2 LC_RPATH | grep path | sed -n "s/^.*path\\s*\\(.*\\)(offset.*\\$/\\1/p"].split(/\n/).map(&:strip) + existing.each do |rp| + next if rp.nil? or rp.empty? + debug "#{$install_name_tool} -delete_rpath #{rp} #{target}" + `#{$install_name_tool} -delete_rpath "#{rp}" "#{target}"` + end + + # add only the requested rpaths + newpaths.each do |rp| + next if rp.nil? or rp.empty? + debug "#{$install_name_tool} -add_rpath #{rp} #{target}" + `#{$install_name_tool} -add_rpath "#{rp}" "#{target}"` + end +end + + +############################################################################### +def handleDependencies(otool_out, targetPath, currentLib, rpaths) + rpath = %x[otool -l #{currentLib} | grep -A2 LC_RPATH | grep path | sed -n "s/^.*path\\s*\\(.*\\)(offset.*\$/\\1/p"] + rpaths += rpath.split(/\n/).map(&:strip) + + for index in 0 ... otool_out.size + fix_lib=cleanOtoolEntry(otool_out[index]) + + # (\/usr\/lib|\/System) + if fix_lib.match(/^(\/usr\/lib|\/System)/) + debug "Ignoring system-lib: #{fix_lib}" + elsif $nocopy + if isFramework(fix_lib) + # get framework path (name of dir containing .framework) + frameworkDir=fix_lib.to_s.gsub(/(.*\.framework).*/,'\1') + frameworkName=frameworkDir.to_s.gsub(/(.*\/)([^\/]*\.framework)/,'\2') + + # the actual lib name + internFrameworkDir=fix_lib.to_s.gsub(/(.*\.framework)\/(.*)/,'\2') + libname=frameworkName+'/'+internFrameworkDir + fixLoadPath(fix_lib, libname, currentLib) + else + fixLoadPath(fix_lib, File.basename(fix_lib), currentLib) + end + elsif fix_lib.start_with?("@loader") or fix_lib.start_with?("@executable") + if $EXTRACTFW + # only fix loading of this library if it does not start with executable/loader_path. + # It basically means it should be fixed already. If it was not we will not be able to + # find it anyway. + fixLoadPath(fix_lib, File.basename(fix_lib), currentLib) + else + debug "Ignoring libs that are referenced from a relative reference (#{fix_lib})" + if not fix_lib.start_with?($executableId) + puts "Warning: (#{fix_lib}) does not match the requested prefix, though." + end + end + elsif not fix_lib.match(/\//) # we need a path here, otherwise it is a lib in the same directory + # TODO combine with previous if-case? this is basically relative + debug "We do not fix libs which are in the same directory: #{fix_lib}" + else + newPath="" + libname="" + if isFramework(fix_lib) + _, libname = handleFramework(fix_lib, $lib_dir, rpaths) + else + _, libname = handleDyLib(fix_lib, $lib_dir, rpaths) + end + # fix loading of this library + fixLoadPath(fix_lib, libname, currentLib) + end + end +end + +############################################################################### +def getDylibName(lib) + return File.basename(lib) +end + +############################################################################### +def copyLib(lib, targetPath) + libBasename=File.basename(lib) + # new path + newPath="#{targetPath}/#{libBasename}" + + if not File.exist?(newPath) and not lib.to_s.start_with?($lib_dir.to_s) + debug "Copy #{libBasename} from #{lib} to #{targetPath}" + `cp #{lib} #{targetPath}` + `chmod u+rw #{newPath}` + else + debug "#{libBasename} exists in #{targetPath} or subfolder" + end + + return newPath, libBasename +end + +############################################################################### +def getFrameworkName(frameworkPath) + # get framework path (name of dir containing .framework) + frameworkDir=frameworkPath.gsub(/(.*\.framework).*/,'\1') + frameworkName=frameworkDir.gsub(/(.*\/)([^\/]*\.framework)/,'\2') + return frameworkName +end + +############################################################################### +def getFrameworkDir(frameworkPath) + frameworkDir=frameworkPath.gsub(/(.*\.framework).*/,'\1') + return frameworkDir +end + +############################################################################### +def copyFramework(frameworkPath, targetPath) + # get framework path (name of dir containing .framework) + frameworkDir=frameworkPath.to_s.gsub(/(.*\.framework).*/,'\1') + frameworkName=frameworkDir.to_s.gsub(/(.*\/)([^\/]*\.framework)/,'\2') + + # the actual lib name + internFrameworkDir=frameworkPath.to_s.gsub(/(.*\.framework)\/(.*)/,'\2') + libname=frameworkName+'/'+internFrameworkDir + # the new path + newFrameworkPath="#{targetPath}/#{libname}" + + if not File.exist?(targetPath + frameworkName) and not frameworkPath.to_s.start_with?($lib_dir.to_s) + debug "Copy fw #{frameworkName} from #{frameworkDir} to #{targetPath}" + # preserve symlinks + `cp -R #{frameworkDir} #{targetPath}` + # adjust rights + `chmod -R u+rwX #{newFrameworkPath}` + else + debug "fw #{frameworkName} already exists in #{targetPath} or subfolder" + end + + return newFrameworkPath, libname +end + +############################################################################### +def copyLibFromFramework(frameworkPath, targetPath) + libname=File.basename(frameworkPath) + + # the new path + newFrameworkPath="#{targetPath}/#{libname}" + + if not File.exist?(targetPath + libname) and not frameworkPath.to_s.start_with?($lib_dir.to_s) + debug "Copy lib #{libname} from #{frameworkPath} to #{targetPath}" + # preserve symlinks + `cp #{frameworkPath} #{newFrameworkPath}` + # adjust rights + `chmod u+rwX #{newFrameworkPath}` + else + debug "lib #{libname} already exists in #{targetPath} or subfolder" + end + + return newFrameworkPath, libname +end + +############################################################################### +def handleFramework(frameworkPath, targetPath, rpaths) + $currentIndent+=1 + + debug "RPATHS: #{rpaths}" + + if frameworkPath.to_s.start_with?("@rpath") + for index in 0 ... rpaths.size + if File.file?(frameworkPath.gsub("@rpath", rpaths[index].strip)) + frameworkPath = frameworkPath.gsub("@rpath", rpaths[index].strip) + debug "Found lib with rpath at #{frameworkPath}" + break + end + end + end + + if not frameworkPath.to_s.start_with?("@rpath") + if $EXTRACTFW + newFrameWorkPath, libname = copyLibFromFramework(frameworkPath, targetPath) + else + # copy framework to target directory + newFrameWorkPath, libname = copyFramework(frameworkPath, targetPath) + end + + if not $handledLibraries.include?(libname) + # run otool + otool_out=`otool -L #{frameworkPath}`.strip.split(/\n/) + # strips first two lines + id, otool_out = extractInstallName(otool_out) + + # update the install_name (-id) + fixId(newFrameWorkPath,libname) + debug "Handle FW #{frameworkPath} -> #{id}" + + # check the actual dependencies (lines 3++) + handleDependencies(otool_out, targetPath, newFrameWorkPath, rpaths) + + # adjust rpaths on the copied/handled framework if requested + begin + adjustRpaths(newFrameWorkPath, $target_rpaths) + rescue => e + debug "adjustRpaths failed for #{newFrameWorkPath}: #{e}" + end + + # mark as processed + $handledLibraries.add(libname) + else + debug "Already fixed #{libname}" + end + else + debug "RPath but no lib found. Assuming fixed already: #{frameworkPath}" + if !$EXTRACTFW + # But at least adjust the libname which is used in handleDependencies to fix loading. + # get framework path (name of dir containing .framework) + frameworkDir=frameworkPath.to_s.gsub(/(.*\.framework).*/,'\1') + frameworkName=frameworkDir.to_s.gsub(/(.*\/)([^\/]*\.framework)/,'\2') + + # the actual lib name + internFrameworkDir=frameworkPath.to_s.gsub(/(.*\.framework)\/(.*)/,'\2') + libname=frameworkName+'/'+internFrameworkDir + else + libname=File.basename(frameworkPath) + end + # the new path + newFrameworkPath="#{targetPath}/#{libname}" + begin + adjustRpaths(newFrameworkPath, $target_rpaths) + rescue => e + debug "adjustRpaths failed for assumed framework path #{newFrameworkPath}: #{e}" + end + end + + # update ids of dependencies + $currentIndent-=1 + + return newFrameWorkPath, libname +end + +############################################################################### +def handleDyLib(dylibPath, targetPath, rpaths) + $currentIndent+=1 + + debug "RPATHS: #{rpaths}" + + if dylibPath.to_s.start_with?("@rpath") + for index in 0 ... rpaths.size + if File.file?(dylibPath.gsub("@rpath", rpaths[index].strip)) + dylibPath = dylibPath.gsub("@rpath", rpaths[index].strip) + debug "Found lib with rpath at #{dylibPath}" + break + end + end + end + + if not dylibPath.to_s.start_with?("@rpath") + # copy if necessary + newDyLibPath,libname=copyLib(dylibPath, targetPath) + + if not $handledLibraries.include?(libname) + # run otool + otoolD_out=`otool -D #{dylibPath}`.strip.split(/\n/) + has_install_name = otoolD_out.length() > 1 + otool_out=`otool -L #{dylibPath}`.strip.split(/\n/) + + # update install_name (-id) of current DyLib + if has_install_name + # strips first two lines + id, otool_out = extractInstallName(otool_out) + fixId(newDyLibPath, libname) + debug "Fix install_name of DYLIB #{dylibPath} --> #{id}" + else + otool_out.delete_at(0).strip + end + + # check the actual dependencies (lines 3++) + handleDependencies(otool_out, targetPath, newDyLibPath, rpaths) + + # adjust rpaths on the copied/handled dylib if requested + begin + adjustRpaths(newDyLibPath, $target_rpaths) + rescue => e + debug "adjustRpaths failed for #{newDyLibPath}: #{e}" + end + + # mark as processed + $handledLibraries.add(libname) + else + debug "Already fixed #{libname}. Only change load path." + end + + # readjust + $currentIndent-=1 + + return newDyLibPath,libname + + else + + debug "RPath but no lib found. Assuming fixed already: #{dylibPath}" + libname=File.basename(dylibPath) + $currentIndent-=1 + + newDyLibPath="#{targetPath}/#{libname}" + begin + adjustRpaths(newDyLibPath, $target_rpaths) + rescue => e + debug "adjustRpaths failed for assumed path #{newDyLibPath}: #{e}" + end + return newDyLibPath,libname + + end +end + +############################################################################### +def handleBinary(binaryPath) + $currentIndent+=1 + + debug "Fixing binary #{binaryPath}" + + rpath = %x[otool -l #{binaryPath} | grep -A2 LC_RPATH | grep path | sed -n "s/^.*path\\s*\\(.*\\)(offset.*\$/\\1/p"] + rpaths = rpath.split(/\n/).map(&:strip) + + # no copy, no id change; juts run otool + otool_out=`otool -L #{binaryPath}`.strip.split(/\n/) + + # remove 1st line containing the binary name + otool_out.delete_at(0) + + # handle all referenced libraries and copy them if necessary to + # the lib ref path + # TODO parse RPATH? + handleDependencies(otool_out, $lib_dir, binaryPath, rpaths) + + # readjust + $currentIndent-=1 +end + +############################################################################### +############################################################################### +############################################################################### + +##### INSTANTIATE CMD PARSER +opts = GetoptLong.new( + [ '--help', '-h', GetoptLong::NO_ARGUMENT ], + [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], + [ '--lib-path', '-l', GetoptLong::OPTIONAL_ARGUMENT ], + [ '--install-name-tool', '-i', GetoptLong::OPTIONAL_ARGUMENT ], + [ '--bin-path', '-b', GetoptLong::OPTIONAL_ARGUMENT ], + [ '--plugin-path', '-p', GetoptLong::OPTIONAL_ARGUMENT ], + [ '--path-prefix', '-e', GetoptLong::OPTIONAL_ARGUMENT ], + [ '--rpaths', '-r', GetoptLong::OPTIONAL_ARGUMENT ], + [ '--extract-from-framework', '-f', GetoptLong::NO_ARGUMENT], + [ '--no-auto-relative', '-n', GetoptLong::NO_ARGUMENT], + [ '--no-copy', '-c', GetoptLong::NO_ARGUMENT] +) + +usage = "#{File.basename($0)} --bin-path PATH-TO-BINARIES --lib-path PATH-TO-STORE-LIBRARIES +-h, --help: + show help +-i, --install-name-tool: + specify the install-name-tool manually +-l, --lib-path: + the target path where the handled libraries should be stored and possibly already libraries exist +-b, --bin-path: + the path were all the binaries that should be handled are located +-p, --plugin-path: + the path were optional plugin libraries like from Qt are located (experimental) +-e, --path-prefix: + the prefix that is added to the new install_name (default: @executable_path/) +-f, --extract-from-framework: + extract the linked libraries from their Framework folder. CAUTION: this does not copy Headers and Resources. Not tested with already present frameworks. +-n, --no-auto-relative: + use path-prefix as is, without adding the relative path between bin path and lib path. +-c, --no-copy: + do not copy dependencies, only fix up loading to relative paths + -r, --rpaths: + comma-separated list of rpaths to set on fixed/copied libraries. All existing rpaths will be removed and only these will be added. +-v, --verbose: + increase verbosity +" + +opts.each do |opt, arg| + case opt + when '--help' + puts usage.to_s + when '--install-name-tool' + $install_name_tool = arg + puts "Use #{install_name_tool} to fix binaries in #{bin}" + when '--rpaths' + if not arg.nil? + $target_rpaths = arg.split(',').map(&:strip) + else + $target_rpaths = [] + end + when '--lib-path' + $lib_dir = Pathname.new(arg).realpath + when '--bin-path' + $bin_dir = Pathname.new(arg).realpath + when '--plugin-path' + $plugin_dir = Pathname.new(arg).realpath + when '--path-prefix' + $executableId = arg + when '--extract-from-framework' + $EXTRACTFW = true + when '--no-auto-relative' + $auto_relative = false + when '--no-copy' + $nocopy = true + when '--verbose' + $DEBUG = true + end +end + +if $lib_dir.nil? and $bin_dir.nil? + puts "Please provide at least a lib path or bin path with no-copy" + puts usage.to_s + exit 1 +elsif $lib_dir.nil? and not $bin_dir.nil? and not $nocopy + puts "If you only specify a bin_dir, no-copy must be active." + puts usage.to_s + exit 1 +elsif !$lib_dir.nil? and !$bin_dir.nil? + if $auto_relative + $executableId = $executableId + $lib_dir.relative_path_from($bin_dir).to_s + end + if not $executableId.empty? + $executableId += "/" + end + puts "Substituting prefix to find libs with:" + puts $executableId +end + + +# fix libraries contained in lib-path +# recurse two-levels to capture the libraries inside the frameworks +if !$lib_dir.nil? + debug "HANDLING LIB DIR" + Dir.chdir($lib_dir.to_s) do + lib_files = Dir.glob(["*","*/*"]) + for content in lib_files + if ($plugin_dir.nil? or not ($lib_dir+content).to_s.start_with?($plugin_dir.to_s)) + if fixable(content, $lib_dir) + if isFramework(content) + handleFramework($lib_dir + content, $lib_dir, []) + elsif (content.end_with?(".dylib") or content.end_with?(".so")) + # TODO what to do with extracted Qt libs. They dont have an ending. + handleDyLib($lib_dir + content, $lib_dir, []) + else + debug "Skipped #{$lib_dir + content} -- No lib or framework?" + end + else + debug "Skipped #{$lib_dir + content} -- Otool not executable on it." + end + else + debug "Skipped #{$lib_dir + content} -- Part of plugin_dir." + end + end + end +end + +# fix binary references +if !$bin_dir.nil? + debug "HANDLING BIN DIR" + for content in Dir.entries($bin_dir) + if fixable(content, $bin_dir) + if (content.end_with?(".dylib") or content.end_with?(".so")) + debug "Handle dylib #{$bin_dir + content}" + handleDyLib($bin_dir + content, $bin_dir, []) + else + debug "Handle binary #{$bin_dir + content}" + handleBinary($bin_dir + content) + end + else + debug "Skipped #{$bin_dir + content} -- No binary or object?" + end + end +end + +## TODO does not really work yet. Would copy all plugin dependencies into plugin_dir +## but it SHOULD copy them into lib and relink relatively if necessary. +## Currently we just copy the plugins and hope they have dependencies that are covered +## by the usual libs. Should work for Qt (our only plugin). +if !$plugin_dir.nil? + debug "HANDLING PLUGIN DIR" + for content in Dir.glob("#{$plugin_dir}/**/*.dylib") + debug "Handle dylib #{$plugin_dir + content}" + handleDyLib($plugin_dir + content, File.dirname($plugin_dir + content), []) + end +end + diff --git a/cmake/MacOSX/notarize.sh b/cmake/MacOSX/notarize.sh new file mode 100755 index 000000000..dc013774a --- /dev/null +++ b/cmake/MacOSX/notarize.sh @@ -0,0 +1,203 @@ +#!/bin/bash +# macOS notarization script using notarytool (requires Xcode 13+ / macOS 11.3+) +# altool was deprecated and unsupported after Fall 2023. +# +# Usage: notarize.sh [log_folder] +# +# Arguments: +# bundle_pkg - The package to notarize (.dmg, .pkg, .zip, or .app) +# bundle_id - The bundle identifier (e.g., de.openms.gui) +# apple_id - Apple ID email for notarization +# password_env_var - Environment variable name containing app-specific password +# team_id - The Apple Developer Team ID (e.g., C64UCGJ5PL) +# log_folder - Optional: folder for log files (defaults to current directory) + +# Exit on error and fail on any error in a pipeline +set -e +set -o pipefail + +BUNDLE_PKG="$1" +BUNDLE_ID="$2" +ASC_USERNAME="$3" +ASC_PASSWORD_ENVVAR="$4" +ASC_TEAMID="$5" +LOG_FOLDER="${6:-.}" + +NOTARIZE_LOG="$LOG_FOLDER/notarize.log" + +mkdir -p "$LOG_FOLDER" +touch "$NOTARIZE_LOG" + +REMOVE_PKG=false +IS_ZIP=false + +echo "=== macOS Notarization Script ===" +echo "Bundle: $BUNDLE_PKG" +echo "Bundle ID: $BUNDLE_ID" +echo "Apple ID: $ASC_USERNAME" +echo "Log folder: $LOG_FOLDER" + +# Validate inputs +if [[ -z "$BUNDLE_PKG" ]] || [[ -z "$BUNDLE_ID" ]] || [[ -z "$ASC_USERNAME" ]] || [[ -z "$ASC_PASSWORD_ENVVAR" ]] || [[ -z "$ASC_TEAMID" ]]; then + echo "Error: Missing required arguments" + echo "Usage: $0 [log_folder]" + exit 1 +fi + +# Check that the password environment variable is set +if [[ -z "${!ASC_PASSWORD_ENVVAR}" ]]; then + echo "Error: Environment variable '$ASC_PASSWORD_ENVVAR' is not set or empty" + exit 1 +fi + +# Determine file type and prepare for notarization +# Only notarize the outermost container (zip, pkg, dmg) +if [[ $BUNDLE_PKG == *.dmg ]]; then + BUNDLE_FILE=$BUNDLE_PKG + echo "Notarizing DMG: $BUNDLE_PKG" +elif [[ $BUNDLE_PKG == *.pkg ]]; then + BUNDLE_FILE=$BUNDLE_PKG + echo "Notarizing PKG: $BUNDLE_PKG" +elif [[ $BUNDLE_PKG == *.zip ]]; then + # For zip files, we need to unzip to staple, then re-zip + BUNDLE_FILE="$BUNDLE_PKG" + IS_ZIP=true + echo "Notarizing ZIP: $BUNDLE_PKG (will staple contents)" +elif [[ $BUNDLE_PKG == *.app ]]; then + # Apps need to be zipped for upload, then unzipped for stapling + BUNDLE_FILE=$BUNDLE_PKG + BUNDLE_PKG="${BUNDLE_PKG}.zip" + echo "Zipping app bundle for notarization..." + ditto -c -k --rsrc --keepParent "$BUNDLE_FILE" "$BUNDLE_PKG" + REMOVE_PKG=true + echo "Notarizing APP (via ZIP): $BUNDLE_PKG" +else + echo "Error: Unsupported filetype for notarization: $BUNDLE_PKG" + echo "Supported types: .dmg, .pkg, .zip, .app" + exit 1 +fi + +# Verify the file exists +if [[ ! -f "$BUNDLE_PKG" ]]; then + echo "Error: File not found: $BUNDLE_PKG" + exit 1 +fi + +echo "" +echo "=== Submitting for notarization ===" + +# Submit for notarization using notarytool +# --wait makes the command block until notarization is complete +# +# The Apple notary service (and the runner's network path to it) occasionally +# drops the connection mid-poll with a transient error such as "The Internet +# connection appears to be offline" even though the upload itself succeeded. +# Retry the whole submit+wait a few times with a backoff before giving up, so +# a single transient blip doesn't fail an otherwise-good build. +MAX_SUBMIT_ATTEMPTS=3 +RETRY_DELAY_SECONDS=60 +SUBMIT_EXIT=1 + +for attempt in $(seq 1 "$MAX_SUBMIT_ATTEMPTS"); do + echo "Submission attempt $attempt of $MAX_SUBMIT_ATTEMPTS..." + + set +e + xcrun notarytool submit "$BUNDLE_PKG" \ + --apple-id "$ASC_USERNAME" \ + --password "${!ASC_PASSWORD_ENVVAR}" \ + --team-id "$ASC_TEAMID" \ + --wait \ + 2>&1 | tee "$NOTARIZE_LOG" + SUBMIT_EXIT=${PIPESTATUS[0]} + set -e + + if [[ $SUBMIT_EXIT -eq 0 ]]; then + break + fi + + echo "Submission attempt $attempt failed (exit $SUBMIT_EXIT)." + if [[ $attempt -lt $MAX_SUBMIT_ATTEMPTS ]]; then + echo "Retrying in ${RETRY_DELAY_SECONDS}s..." + sleep "$RETRY_DELAY_SECONDS" + fi +done + +if [[ $SUBMIT_EXIT -eq 0 ]]; then + + echo "" + echo "=== Notarization submission completed ===" + + # Check if notarization was successful + if grep -q "status: Accepted" "$NOTARIZE_LOG"; then + echo "Notarization successful!" + + # Staple the notarization ticket to the bundle + echo "" + echo "=== Stapling notarization ticket ===" + + # Note: You cannot staple a .zip file directly + # If the original was a zip, we need to handle it differently + if [[ "$IS_ZIP" = true ]]; then + echo "Warning: Cannot staple a .zip file. The notarization is stored with Apple." + echo "Users will need to be online for Gatekeeper to verify the notarization." + else + if xcrun stapler staple "$BUNDLE_FILE"; then + echo "Stapling successful!" + + # Verify the stapling worked + echo "" + echo "=== Verifying notarization ===" + xcrun stapler validate "$BUNDLE_FILE" || echo "Warning: Stapler validation returned non-zero" + else + echo "Warning: Stapling failed, but notarization was successful." + echo "Users will need to be online for Gatekeeper to verify." + fi + fi + + # Clean up temporary zip if we created one + if [ "$REMOVE_PKG" = true ]; then + rm -f "$BUNDLE_PKG" + fi + + echo "" + echo "=== Notarization complete ===" + exit 0 + else + echo "Error: Notarization failed!" + echo "Check the log for details: $NOTARIZE_LOG" + + # Try to get the submission ID and fetch detailed logs + SUBMISSION_ID=$(grep -o 'id: [a-f0-9-]*' "$NOTARIZE_LOG" | head -1 | cut -d' ' -f2) + if [[ -n "$SUBMISSION_ID" ]]; then + echo "" + echo "=== Fetching detailed notarization log ===" + xcrun notarytool log "$SUBMISSION_ID" \ + --apple-id "$ASC_USERNAME" \ + --password "${!ASC_PASSWORD_ENVVAR}" \ + --team-id "$ASC_TEAMID" \ + "$LOG_FOLDER/notarization_details.json" 2>&1 || true + + if [[ -f "$LOG_FOLDER/notarization_details.json" ]]; then + echo "Detailed log saved to: $LOG_FOLDER/notarization_details.json" + cat "$LOG_FOLDER/notarization_details.json" + fi + fi + + # Clean up temporary zip if we created one + if [ "$REMOVE_PKG" = true ]; then + rm -f "$BUNDLE_PKG" + fi + + exit 1 + fi +else + echo "Error: notarytool submission failed after $MAX_SUBMIT_ATTEMPTS attempts!" + cat "$NOTARIZE_LOG" + + # Clean up temporary zip if we created one + if [ "$REMOVE_PKG" = true ]; then + rm -f "$BUNDLE_PKG" + fi + + exit 1 +fi diff --git a/cmake/MacOSX/openms_logo_corner_small.png b/cmake/MacOSX/openms_logo_corner_small.png new file mode 100644 index 000000000..8f8a2ff0c Binary files /dev/null and b/cmake/MacOSX/openms_logo_corner_small.png differ diff --git a/cmake/Modules/CPack.distribution.dist.in b/cmake/Modules/CPack.distribution.dist.in new file mode 100644 index 000000000..170585fee --- /dev/null +++ b/cmake/Modules/CPack.distribution.dist.in @@ -0,0 +1,32 @@ + + + @CPACK_PACKAGE_NAME@ + + + + + @CPACK_OPENMS_ALLOWED_OS_VERSIONS@ + + +@CPACK_APPLE_PKG_INSTALLER_CONTENT@ + diff --git a/cmake/Modules/CheckLibArchitecture.cmake b/cmake/Modules/CheckLibArchitecture.cmake new file mode 100644 index 000000000..52e661029 --- /dev/null +++ b/cmake/Modules/CheckLibArchitecture.cmake @@ -0,0 +1,75 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Chris Bielow $ +# $Authors: Chris Bielow $ +# -------------------------------------------------------------------------- + + +## Check if link libraries have right architecture (.lib can be either a static or link library for a dll) +## Motivation: if 32bit libs are accidentally linked against a 64bit OpenMS, weird linker errors (symbol not found, etc) will appear. +## These are really hard to track down, so we check in advance. +function(check_lib_architecture link_libs) + + if (MSVC) + message(STATUS " Checking architecture of link libraries ...") + + ## check for 'dumpbin.exe' which comes with every VS to extract architecture + find_program(PROG_DUMPBIN "dumpbin" DOC "dumpbin allows to extract information from (link) libraries") + if (NOT PROG_DUMPBIN) + message(FATAL_ERROR "\n\nCould not find 'dumpbin.exe' (${PROG_DUMPBIN})! Please make sure it's in %PATH%.\n\n") + endif() + set(LIB_ARCH_TIMEOUT 3 CACHE STRING "Timeout in seconds for running dumpbin.exe to find lib architecture.") + + foreach(lib_file ${${link_libs}}) + if (${lib_file} MATCHES "\\.lib$") ## ends with '.lib' + get_filename_component(LIB_VAR_N "${lib_file}" NAME) + set(LIB_VAR "LIB_ARCH_CHECKED_${LIB_VAR_N}") + if (${LIB_VAR} EQUAL 1) + ## Architecture of ${LIB_VAR_N} was checked already. Force re-check by setting '${LIB_VAR}' to zero. + else() + set(${LIB_VAR} "0" CACHE STRING "Checking of link library ${LIB_VAR_N} architecture (32/64bit): 1 if done (will not be repeated), 0 otherwise.") + + ## call 'dumpbin.exe' on each lib + execute_process(COMMAND "${PROG_DUMPBIN}" "/HEADERS" "${lib_file}" + RESULT_VARIABLE DUMP_RESULT + OUTPUT_VARIABLE PIPE_OUT_LIB + ERROR_VARIABLE PIPE_OUT_LIB + TIMEOUT ${LIB_ARCH_TIMEOUT}) ## three seconds should be enough to get the first lines, which suffice to know what we need; big libs may need much longer! + ## do not check '${DUMP_RESULT} EQUAL 0' here since a timeout could set it to 'Process terminated due to timeout' + ## get a shorter version for (faster) regex matching below + string(LENGTH "${PIPE_OUT_LIB}" PIPE_OUT_LIB_LEN) + if (${PIPE_OUT_LIB_LEN} LESS 601) + set(PIPE_OUT_LIB_S "${PIPE_OUT_LIB}") + else() + string(SUBSTRING "${PIPE_OUT_LIB}" 0 600 PIPE_OUT_LIB_S) + endif() + ## check if substring is long enough + if (NOT PIPE_OUT_LIB_S MATCHES "[Mm]achine") + message(STATUS " - Timeout before data could be acquired. Data was:\n'${PIPE_OUT_LIB_S}'\n--\nMake sure your machine is not too busy or increase 'LIB_ARCH_TIMEOUT'.") + else() + ## we expect consistent architectures, output can be + ## 'Machine : 8664 (x64)' + ## or + ## '8664 machine (x64)' + ## (similar for x86) + set(M_64bit "Machine : 8664 \\(x64\\)|8664 machine \\(x64\\)") + set(M_32bit "Machine : 14C \\(x86\\)|14C machine \\(x86\\)") + if ((OPENMS_64BIT_ARCHITECTURE EQUAL 1) AND NOT (${PIPE_OUT_LIB_S} MATCHES ${M_64bit})) + message(FATAL_ERROR "\n####Extracted from lib:\n####${PIPE_OUT_LIB_S}\n####\n\nWe detected an external 32-bit link library ('${lib_file}') which cannot be linked to this 64-bit OpenMS! Find or compile a 64-bit version and run CMake again! Evidence see above. Expected substring regex was '${M_64bit}'.") + elseif((OPENMS_64BIT_ARCHITECTURE EQUAL 0) AND NOT(${PIPE_OUT_LIB_S} MATCHES ${M_32bit})) + message(FATAL_ERROR "\n####Extracted from lib:####\n${PIPE_OUT_LIB_S}\n####\n\nWe detected an external 64-bit link library ('${lib_file}') which cannot be linked to this 32-bit OpenMS! Find or compile a 32-bit version and run CMake again! Evidence see above. Expected substring was '${M_32bit}'.") + endif() + ## prevent this function from running its (expensive) body during next call to cmake + set(${LIB_VAR} "1" CACHE STRING "Checking of link library ${LIB_VAR_N} architecture (32/64bit): 1 if done (will not be repeated), 0 otherwise." FORCE) + endif() ## data was long enough + endif() ## checked already + endif() ## .lib-file + endforeach(lib_file) + message(STATUS " Checking architecture of link libraries ... done") + else() + ## not implemented yet for Linux/MacOSX + endif() + + endfunction() diff --git a/cmake/Modules/GetGitRevisionDescription.cmake b/cmake/Modules/GetGitRevisionDescription.cmake new file mode 100644 index 000000000..5960017a0 --- /dev/null +++ b/cmake/Modules/GetGitRevisionDescription.cmake @@ -0,0 +1,264 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ ...]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +function(get_git_head_revision _refspecvar _hashvar) + set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories + set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") + get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) + if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) + # We have reached the root directory, we are not in git + set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + return() + endif() + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + endwhile() + # check if this is a submodule + if(NOT IS_DIRECTORY ${GIT_DIR}) + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) + endif() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${GIT_DIR}/HEAD") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" + @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) + set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + return() + endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + describe + ${hash} + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(git_get_exact_tag _var) + git_describe(out --exact-match ${ARGN}) + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +## An optional 4th argument reports whether every git call succeeded. The metadata +## strings cannot signal that on their own: a branch may be named like a sentinel. +function(git_short_info _refspecvar _hashvar _lc_datevar) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + if(ARGC GREATER 3) + set(${ARGV3} FALSE PARENT_SCOPE) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_refspecvar} "GIT-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "GIT-NOTFOUND" PARENT_SCOPE) + set(${_lc_datevar} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_refspecvar} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + set(${_lc_datevar} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + return() + endif() + + set(_ok TRUE) + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + rev-parse + --short=7 + ${hash} + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + set(_ok FALSE) + endif() + + set(${_hashvar} "${out}" PARENT_SCOPE) + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + rev-parse + --abbrev-ref + HEAD + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + set(_ok FALSE) + endif() + + set(${_refspecvar} "${out}" PARENT_SCOPE) + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + log + -n 1 + --simplify-by-decoration + --pretty=%ai + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + set(_ok FALSE) + endif() + + set(${_lc_datevar} "${out}" PARENT_SCOPE) + + if(ARGC GREATER 3) + set(${ARGV3} ${_ok} PARENT_SCOPE) + endif() +endfunction() + +## Reads what git archive filled into <_file>, the source tree's .git_archival.txt (export-subst +## in .gitattributes): the commit, its date and the nearest release tag (git describe; empty if +## there is none). <_foundvar> is TRUE only in a source tree made by git archive; in a checkout +## the file still holds its $Format placeholders. Hash and date come in the formats of +## git_short_info. src/tests/git_archival/run.cmake tests it. +function(git_archival_info _file _describevar _hashvar _lc_datevar _foundvar) + set(${_foundvar} FALSE PARENT_SCOPE) + if(NOT EXISTS "${_file}") + return() + endif() + file(STRINGS "${_file}" lines) + set(hash "") + set(lc_date "") + set(describe "") + foreach(line IN LISTS lines) + if(line MATCHES "^node: ([0-9a-f]+)$") + set(hash "${CMAKE_MATCH_1}") + elseif(line MATCHES "^node-date: (.+)$") + set(lc_date "${CMAKE_MATCH_1}") + elseif(line MATCHES "^describe-name: (.*)$") + set(describe "${CMAKE_MATCH_1}") + endif() + endforeach() + if(NOT hash) + return() + endif() + ## git before 2.35 knows no describe options and leaves the placeholder as it is + if(describe MATCHES "^%\\(") + message(WARNING "${_file}: the git that made this archive did not fill in describe-name " + "(that needs git 2.35 or later), so the build cannot tell a release.") + set(describe "") + endif() + string(SUBSTRING "${hash}" 0 7 hash) + ## 2026-09-30T08:25:00+02:00 (%cI) -> 2026-09-30 08:25:00 +0200 (%ai); since git 2.45, %cI + ## writes UTC as Z + string(REGEX REPLACE "Z$" "+00:00" lc_date "${lc_date}") + string(REGEX REPLACE "^([0-9-]+)T([0-9:]+)([+-][0-9][0-9]):?([0-9][0-9])$" "\\1 \\2 \\3\\4" lc_date "${lc_date}") + set(${_describevar} "${describe}" PARENT_SCOPE) + set(${_hashvar} "${hash}" PARENT_SCOPE) + set(${_lc_datevar} "${lc_date}" PARENT_SCOPE) + set(${_foundvar} TRUE PARENT_SCOPE) +endfunction() diff --git a/cmake/Modules/GetGitRevisionDescription.cmake.in b/cmake/Modules/GetGitRevisionDescription.cmake.in new file mode 100644 index 000000000..888ce13aa --- /dev/null +++ b/cmake/Modules/GetGitRevisionDescription.cmake.in @@ -0,0 +1,38 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") + configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + set(HEAD_HASH "${HEAD_REF}") + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/cmake/OpenMSGUIConfig.cmake.in b/cmake/OpenMSGUIConfig.cmake.in new file mode 100644 index 000000000..68ca1cb49 --- /dev/null +++ b/cmake/OpenMSGUIConfig.cmake.in @@ -0,0 +1,69 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Julianus Pfeuffer, Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +@PACKAGE_INIT@ + +### CMake package configuration of the OpenMS GUI library (OpenMS-TOPP-GUI). +### +### find_package(OpenMSGUI CONFIG REQUIRED) +### target_link_libraries(myapp PRIVATE OpenMS::OpenMS_GUI) +### +### Provides the imported target OpenMS::OpenMS_GUI (and the alias OpenMS_GUI). It +### links the OpenMS core and CLI layers (OpenMS::OpenMS, OpenMS::OpenMS_CLI) and +### Qt 6, which this file finds first. The OpenMS core's own package delegates its +### component GUI here, so find_package(OpenMS CONFIG COMPONENTS GUI) is equivalent. +### +### Variables: +### OpenMSGUI_VERSION version of the GUI layer (= the OpenMS version) +### OpenMSGUI_QT_COMPONENTS the Qt 6 modules OpenMS_GUI links publicly +### OPENMS_GUI_DATA_DIR, OPENMS_GUI_BIN_DIR, OPENMS_GUI_LIB_DIR +### directories of this installation + +if(CMAKE_VERSION VERSION_LESS 3.24) + set(OpenMSGUI_FOUND FALSE) + set(OpenMSGUI_NOT_FOUND_MESSAGE + "OpenMSGUI requires CMake 3.24 or newer in the consuming project (running ${CMAKE_VERSION}).") + return() +endif() + +## evaluated before any dependency lookup: a nested configure_package_config_file() +## package resets PACKAGE_PREFIX_DIR +set(OPENMS_GUI_DATA_DIR "@PACKAGE_INSTALL_SHARE_DIR@") +set(OPENMS_GUI_BIN_DIR "@PACKAGE_INSTALL_BIN_DIR@") +set(OPENMS_GUI_LIB_DIR "@PACKAGE_INSTALL_LIB_DIR@") +set(OpenMSGUI_QT_COMPONENTS @OPENMS_GUI_PACKAGE_QT_MODULES@) + +include(CMakeFindDependencyMacro) + +## The OpenMS core with its CLI layer, of exactly the version the GUI layer was built +## against (it is compiled against the core's headers). Usually installed into the same +## prefix (lib/cmake/OpenMS next to lib/cmake/OpenMSGUI, or CMake/ for both on Windows). +## Not searched again when OpenMS was found already, e.g. when OpenMSConfig.cmake +## delegates its component GUI here; its version is checked instead. +if(NOT TARGET OpenMS::OpenMS_CLI) + find_dependency(OpenMS @OPENMS_GUI_CORE_VERSION@ EXACT CONFIG COMPONENTS CLI + HINTS "${CMAKE_CURRENT_LIST_DIR}/../OpenMS" "${CMAKE_CURRENT_LIST_DIR}") +elseif(NOT "${OpenMS_VERSION}" VERSION_EQUAL "@OPENMS_GUI_CORE_VERSION@") + set(OpenMSGUI_FOUND FALSE) + string(CONCAT OpenMSGUI_NOT_FOUND_MESSAGE + "OpenMSGUI @OPENMS_PACKAGE_VERSION@ was built against OpenMS @OPENMS_GUI_CORE_VERSION@ and needs exactly that " + "version, but OpenMS '${OpenMS_VERSION}' (${OpenMS_DIR}) was found already.") + return() +endif() + +## the Qt 6 modules of the public link interface; a newer Qt 6 is binary compatible +find_dependency(Qt6 @Qt6_VERSION@ COMPONENTS ${OpenMSGUI_QT_COMPONENTS}) + +if(NOT TARGET OpenMS::OpenMS_GUI) + include("${CMAKE_CURRENT_LIST_DIR}/@_OPENMS_GUI_EXPORT_FILE@") +endif() +if(NOT TARGET OpenMS_GUI) + add_library(OpenMS_GUI ALIAS OpenMS::OpenMS_GUI) +endif() + +check_required_components(OpenMSGUI) diff --git a/cmake/OpenMSPackageDescriptionFile.txt b/cmake/OpenMSPackageDescriptionFile.txt new file mode 100644 index 000000000..dc2eb5af4 --- /dev/null +++ b/cmake/OpenMSPackageDescriptionFile.txt @@ -0,0 +1,10 @@ +This package contains the graphical applications of OpenMS: + +TOPPView - a viewer for mass spectrometry data +TOPPAS - an editor and runner for pipelines of TOPP tools +INIFileEditor - an editor for the parameter files of the TOPP tools + +and the TOPP tools that need the GUI library (ExecutePipeline, ImageCreator). +It is installed into the installation of OpenMS of the same version. + +For more information visit our website at www.OpenMS.de. diff --git a/cmake/OpenMSPackageResourceReadme.txt b/cmake/OpenMSPackageResourceReadme.txt new file mode 100644 index 000000000..d93d66fa2 --- /dev/null +++ b/cmake/OpenMSPackageResourceReadme.txt @@ -0,0 +1,3 @@ +The OpenMS GUI is an add-on to OpenMS: it is installed into the folder of the OpenMS installation of the same version, which has to be installed first (for all users, in /Applications). TOPPView, TOPPAS and INIFileEditor are added there as applications; ExecutePipeline and ImageCreator join the other TOPP tools in its bin folder. + +For more information visit our website at www.OpenMS.de. diff --git a/cmake/OpenMSPackageResourceWelcomeFile.txt b/cmake/OpenMSPackageResourceWelcomeFile.txt new file mode 100644 index 000000000..6e0b9e3e9 --- /dev/null +++ b/cmake/OpenMSPackageResourceWelcomeFile.txt @@ -0,0 +1,9 @@ +Welcome to the installation of the OpenMS GUI. + +TOPPView - a viewer for mass spectrometry data +TOPPAS - an editor and runner for pipelines of TOPP tools +INIFileEditor - an editor for the parameter files of the TOPP tools + +The OpenMS GUI requires OpenMS of the same version, which has to be installed first. + +For more information visit our website at www.OpenMS.de. diff --git a/cmake/Windows/Include/AdvUninstLog2.nsh b/cmake/Windows/Include/AdvUninstLog2.nsh new file mode 100644 index 000000000..e5186dcae --- /dev/null +++ b/cmake/Windows/Include/AdvUninstLog2.nsh @@ -0,0 +1,416 @@ + ;_____________________________ HEADER FILE BEGIN ____________________________ + + # Advanced Uninstall Log NSIS header + # Version 1.0 2007-01-31 + # By Red Wine (http://nsis.sf.net/User:Red_Wine) + + # Usage: See included examples Uninstall_Log_Default_UI.nsi - Uninstall_Log_Modern_UI.nsi + +!verbose push + !verbose 3 + +!ifndef ADVANCED_UNINSTALL.LOG_NSH + !define ADVANCED_UNINSTALL.LOG_NSH + +!ifndef PRODUCT_UNINST_ROOT | PRODUCT_UNINST_KEY + !error "You must properly define both PRODUCT_UNINST_ROOT and PRODUCT_UNINST_KEY" +!endif + +!ifndef UNINSTALL_LOG + !define UNINSTALL_LOG "uninstall" +!endif + +!ifndef UNINST_LOG_VERBOSE + !define UNINST_LOG_VERBOSE "3" +!endif + +!verbose pop + +!echo "Advanced Uninstall Log NSIS header v1.0 2007-01-31 by Red Wine (http://nsis.sf.net/User:Red_Wine)" + +!verbose push + !verbose ${UNINST_LOG_VERBOSE} + +!define UNINST_EXE "$INSTDIR\${UNINSTALL_LOG}.exe" +!define UNINST_DAT "$INSTDIR\${UNINSTALL_LOG}.dat" +!define UNLOG_PART "$PLUGINSDIR\part." +!define UNLOG_TEMP "$PLUGINSDIR\unlog.tmp" +!define EXCLU_LIST "$PLUGINSDIR\exclude.tmp" +!define UNLOG_HEAD "=========== Uninstaller Log please do not edit this file ===========" + +Var TargetDir + + var unlog_tmp_0 + var unlog_tmp_1 + var unlog_tmp_2 + var unlog_tmp_3 + var unlog_error + +!include FileFunc.nsh +!include TextFunc.nsh + +!insertmacro Locate +!insertmacro un.Locate +!insertmacro DirState +!insertmacro un.DirState +!insertmacro FileJoin +!insertmacro TrimNewLines +!insertmacro un.TrimNewLines + +;.............................. Uninstaller Macros .............................. + + + +Function un.RemoveEmptyDirs + Pop $9 + + FindFirst $0 $1 "$9\*" + StrCmp $0 "" End + Loop: + StrCmp $1 "" End + StrCmp $1 "." Next + StrCmp $1 ".." Next + Push $0 + Push $1 + Push $9 + Push "$9\$1\" + Call un.RemoveEmptyDirs + Pop $9 + Pop $1 + Pop $0 + + RMDir "$9\$1" + Next: + FindNext $0 $1 + Goto Loop + End: + FindClose $0 + RMDir "$9" +FunctionEnd + +!macro UNINSTALL.NEW_UNINSTALL + StrCpy $TargetDir $INSTDIR + FileOpen $unlog_tmp_0 "${UNINST_DAT}" r + + push "UninstallBegin" + UninstallLoop: + ClearErrors #ClearErrors from displaying + FileRead $unlog_tmp_0 $R0 #Reads string lines from a file and stores them in $R0 + IfErrors UninstallEnd #If EOF has been reached + ${un.TrimNewLines} "$R0" $R0 + IfFileExists "$R0\*.*" UninstallDirectory UninstallFile + UninstallDirectory: + push $R0 #store the directory for later until the files are deleted + goto UninstallLoop + UninstallFile: + Delete "$R0" #delete file + goto UninstallLoop + UninstallEnd: + FileClose $unlog_tmp_0 + + Delete "${UNINST_DAT}" + Delete "${UNINST_EXE}" + + pop $R0 + ${DoWhile} $R0 != "UninstallBegin" + Push $R0 + Call un.RemoveEmptyDirs + pop $R0 + ${loop} +!macroend + +!macro INTERACTIVE_UNINSTALL + !verbose push + !verbose ${UNINST_LOG_VERBOSE} + + !ifdef INTERACTIVE_UNINSTALL + !error "INTERACTIVE_UNINSTALL is already defined" + !endif + + var unlog_tmp_4 + var unlog_tmp_5 + + !define INTERACTIVE_UNINSTALL + + !ifdef INTERACTIVE_UNINSTALL & UNATTENDED_UNINSTALL + !error "You must insert either Interactive or Unattended Uninstall neither both, neither none." + !endif + + !ifdef UnLog_Uninstall_CallBackFunc + !undef UnLog_Uninstall_CallBackFunc + !endif + + !ifndef UnLog_Uninstall_CallBackFunc + !insertmacro UNINSTALL.LOG_UNINSTALL_INTERACTIVE + !define UnLog_Uninstall_CallBackFunc "un._LocateCallBack_Function_Interactive" + !endif + + !verbose pop +!macroend + + +!macro UNATTENDED_UNINSTALL + !verbose push + !verbose ${UNINST_LOG_VERBOSE} + + !ifdef UNATTENDED_UNINSTALL + !error "UNATTENDED_UNINSTALL is already defined" + !endif + + !define UNATTENDED_UNINSTALL + + !ifdef INTERACTIVE_UNINSTALL & UNATTENDED_UNINSTALL + !error "You must insert either Interactive or Unattended Uninstall neither both, neither none." + !endif + + !ifdef UnLog_Uninstall_CallBackFunc + !undef UnLog_Uninstall_CallBackFunc + !endif + + !ifndef UnLog_Uninstall_CallBackFunc + !insertmacro UNINSTALL.LOG_UNINSTALL_UNATTENDED + !define UnLog_Uninstall_CallBackFunc "un._LocateCallBack_Function_Unattended" + !endif + + !verbose pop +!macroend + + +!macro UNINSTALL.LOG_UNINSTALL_UNATTENDED + + Function un._LocateCallBack_Function_Unattended + start: + FileRead $unlog_tmp_2 "$unlog_tmp_3" ${NSIS_MAX_STRLEN} + ${un.TrimNewLines} "$unlog_tmp_3" "$unlog_tmp_3" + StrCmp "$unlog_tmp_3" "$R9" islog + IfErrors nolog + goto start + + islog: + IfFileExists "$R9\*.*" isdir + + isfile: + Delete "$R9" + goto end + + isdir: + RmDir "$R9" + IntOp $unlog_tmp_1 $unlog_tmp_1 + 1 + goto end + + nolog: + ClearErrors + StrCmp "$R9" "${UNINST_EXE}" isfile + StrCmp "$R9" "${UNINST_DAT}" isfile + + end: + FileSeek $unlog_tmp_2 0 SET + Push $unlog_tmp_0 + FunctionEnd + +!macroend + + +!macro UNINSTALL.LOG_UNINSTALL_INTERACTIVE + + Function un._LocateCallBack_Function_Interactive + start: + FileRead $unlog_tmp_2 "$unlog_tmp_3" ${NSIS_MAX_STRLEN} + ${un.TrimNewLines} "$unlog_tmp_3" "$unlog_tmp_3" + StrCmp "$unlog_tmp_3" "$R9" islog + IfErrors nolog + goto start + + islog: + IfFileExists "$R9\*.*" isdir + + isfile: + Delete "$R9" + goto end + + isdir: + RmDir "$R9" + IntOp $unlog_tmp_1 $unlog_tmp_1 + 1 + goto end + + nolog: + ClearErrors + FileSeek $unlog_tmp_4 0 SET + read: + FileRead $unlog_tmp_4 "$unlog_tmp_3" + ${un.TrimNewLines} "$unlog_tmp_3" "$unlog_tmp_3" + StrCmp "$unlog_tmp_3" "$R9" end + IfErrors +2 + goto read + ClearErrors + StrCmp "$R9" "${UNINST_EXE}" isfile + StrCmp "$R9" "${UNINST_DAT}" isfile + IfFileExists "$R9\*.*" msgdir + + MessageBox MB_ICONQUESTION|MB_YESNO \ + 'Delete File "$R9"?' /SD IDNO IDYES isfile IDNO nodel + + msgdir: + MessageBox MB_ICONQUESTION|MB_YESNO \ + 'Delete Directory "$R9"?' /SD IDNO IDYES isdir IDNO nodel + + nodel: + FileSeek $unlog_tmp_4 0 END + FileWrite $unlog_tmp_4 "$R9$\r$\n" + + end: + FileSeek $unlog_tmp_2 0 SET + Push $unlog_tmp_0 + FunctionEnd + +!macroend + +;................................. Installer Macros ................................. + +!macro UNINSTALL.LOG_INSTALL_UNATTENDED + + Function _LocateCallBack_Function_Install + loop: + FileRead $unlog_tmp_2 "$unlog_tmp_3" ${NSIS_MAX_STRLEN} + ${TrimNewLines} "$unlog_tmp_3" "$unlog_tmp_3" + IfErrors 0 +4 + ClearErrors + FileSeek $unlog_tmp_2 0 SET + goto next + StrCmp "$R9" "$unlog_tmp_3" end + goto loop + next: + FileWrite $unlog_tmp_1 "$R9$\r$\n" + end: + Push $unlog_tmp_0 + FunctionEnd + +!macroend + + +!ifdef UnLog_Install_Func_CallBack + !undef UnLog_Install_Func_CallBack +!endif + +!ifndef UnLog_Install_Func_CallBack + !insertmacro UNINSTALL.LOG_INSTALL_UNATTENDED + !define UnLog_Install_Func_CallBack "_LocateCallBack_Function_Install" +!endif + + +!macro UNINSTALL.LOG_PREPARE_INSTALL + !verbose push + !verbose ${UNINST_LOG_VERBOSE} + + Push $0 + Push $1 + ClearErrors + ReadRegStr "$0" ${PRODUCT_UNINST_ROOT} "${PRODUCT_UNINST_KEY}" "${UNINSTALL_LOG}Directory" + IfErrors next + ${DirState} "$0" $1 + StrCmp "$1" "-1" next + StrCmp "$1" "0" next + IfFileExists "$0\${UNINSTALL_LOG}.dat" next + MessageBox MB_ICONEXCLAMATION|MB_OK \ + "Previous installation detected at $0.$\n\ + Required file ${UNINSTALL_LOG}.dat is missing.$\n$\nIt is highly recommended \ + to select an empty directory and perform a fresh installation." /SD IDOK + StrCpy $unlog_error "error" + + next: + ClearErrors + StrCmp "$PLUGINSDIR" "" 0 +2 + InitPluginsDir + + GetTempFileName "$1" + FileOpen $0 "$1" w + FileWrite $0 "${UNLOG_HEAD}$\r$\n" + FileClose $0 + Rename "$1" "${UNLOG_TEMP}" + Pop $1 + Pop $0 + + !verbose pop +!macroend + + +!macro UNINSTALL.LOG_UPDATE_INSTALL + !verbose push + !verbose ${UNINST_LOG_VERBOSE} + + Delete "${UNINST_DAT}" + Rename "${UNLOG_TEMP}" "${UNINST_DAT}" + WriteUninstaller "${UNINST_EXE}" + WriteRegStr ${PRODUCT_UNINST_ROOT} "${PRODUCT_UNINST_KEY}" "${UNINSTALL_LOG}.dat" "${UNINST_DAT}" + WriteRegStr ${PRODUCT_UNINST_ROOT} "${PRODUCT_UNINST_KEY}" "${UNINSTALL_LOG}Directory" "$INSTDIR" + + !verbose pop +!macroend + + +!define uninstall.log_install "!insertmacro UNINSTALL.LOG_INSTALL" + +!macro UNINSTALL.LOG_INSTALL FileOpenWrite FileOpenRead TargetDir + !verbose push + !verbose ${UNINST_LOG_VERBOSE} + + FileOpen $unlog_tmp_1 "${FileOpenWrite}" w + FileOpen $unlog_tmp_2 "${FileOpenRead}" r + + ${Locate} "${TargetDir}" "/L=FD" "${UnLog_Install_Func_CallBack}" + + StrCmp $unlog_error "error" 0 +2 + ClearErrors + + IfErrors 0 +2 + MessageBox MB_ICONEXCLAMATION|MB_OK "Error creating ${UNINSTALL_LOG} Log." /SD IDOK + + FileClose $unlog_tmp_1 + FileClose $unlog_tmp_2 + + !verbose pop +!macroend + + +!define uninstall.log_mergeID "!insertmacro UNINSTALL.LOG_MERGE" + +!macro UNINSTALL.LOG_MERGE UnlogPart + !verbose push + !verbose ${UNINST_LOG_VERBOSE} + + ${FileJoin} "${UNLOG_TEMP}" "${UnlogPart}" "${UNLOG_TEMP}" + + !verbose pop +!macroend + + +!macro UNINSTALL.LOG_OPEN_INSTALL + !verbose push + !verbose ${UNINST_LOG_VERBOSE} + + StrCmp $unlog_error "error" +2 + ${uninstall.log_install} "${EXCLU_LIST}" "${UNINST_DAT}" "$OUTDIR" + + !verbose pop +!macroend + + +!macro UNINSTALL.LOG_CLOSE_INSTALL + !verbose push + !verbose ${UNINST_LOG_VERBOSE} + + !define ID ${__LINE__} + + ${uninstall.log_install} "${UNLOG_PART}${ID}" "${EXCLU_LIST}" "$OUTDIR" + ${uninstall.log_mergeID} "${UNLOG_PART}${ID}" + + !undef ID + + !verbose pop +!macroend + +!endif + +!verbose pop + ;_____________________________ HEADER FILE END ____________________________ + diff --git a/cmake/Windows/Include/IncludeScript_Misc.nsh b/cmake/Windows/Include/IncludeScript_Misc.nsh new file mode 100644 index 000000000..37500d8a3 --- /dev/null +++ b/cmake/Windows/Include/IncludeScript_Misc.nsh @@ -0,0 +1,60 @@ +############################## +### register filetypes (code borrowed from VLC project) +## +## The associations are written to Software\Classes of the registry hive of the install +## mode (SHCTX: HKLM for all users, HKCU for the current user), so that a current-user +## installation needs no administrator rights for them and the uninstaller of the same mode +## finds them again. + +;; Registers one extension $R0 (e.g. ".mzData") for the program $R1 (e.g. TOPPView) +Function RegisterExtension + ; back up the old value for the extension + ReadRegStr $1 SHCTX "Software\Classes\$R0" "" + StrCmp $1 "" NoBackup + StrCmp $1 "OPENMS$R0" "NoBackup" + WriteRegStr SHCTX "Software\Classes\$R0" "OPENMS.backup" $1 +NoBackup: + WriteRegStr SHCTX "Software\Classes\$R0" "" "OPENMS$R0" + WriteRegStr SHCTX "Software\Classes\OPENMS$R0" "" "OPENMS data file ($R0)" + WriteRegStr SHCTX "Software\Classes\OPENMS$R0\shell\open\command" "" '"$INSTDIR\bin\$R1.exe" "%1"' + WriteRegStr SHCTX "Software\Classes\OPENMS$R0\DefaultIcon" "" '"$INSTDIR\share\OpenMS\OpenMS_$R1.ico",0' +FunctionEnd + +;; Removes one extension that OpenMS owns, restoring the backed up value. +Function un.RegisterExtension + ReadRegStr $1 SHCTX "Software\Classes\$R0" "" + StrCmp $1 "OPENMS$R0" 0 NoOwn ; only do this if we own it + ; Read the old value from Backup + ReadRegStr $1 SHCTX "Software\Classes\$R0" "OPENMS.backup" + StrCmp $1 "" 0 Restore ; if backup="" then delete the whole key + DeleteRegKey SHCTX "Software\Classes\$R0" + Goto NoOwn +Restore: + WriteRegStr SHCTX "Software\Classes\$R0" "" $1 + DeleteRegValue SHCTX "Software\Classes\$R0" "OPENMS.backup" +NoOwn: + DeleteRegKey SHCTX "Software\Classes\OPENMS$R0" ;Delete key with association settings +FunctionEnd + +!macro RegisterExtensionSection EXT PROGRAMEXE + Section ${EXT} + SectionIn 1 3 + Push $R0 + Push $R1 + StrCpy $R0 ${EXT} + StrCpy $R1 ${PROGRAMEXE} + Call RegisterExtension + Pop $R1 + Pop $R0 + SectionEnd +!macroend + +!macro UnRegisterExtensionSection EXT PROGRAMEXE + Push $R0 + Push $R1 + StrCpy $R0 ${EXT} + StrCpy $R1 ${PROGRAMEXE} + Call un.RegisterExtension + Pop $R1 + Pop $R0 +!macroend diff --git a/cmake/Windows/Include/NsisMultiUser.nsh b/cmake/Windows/Include/NsisMultiUser.nsh new file mode 100644 index 000000000..de0bac8c9 --- /dev/null +++ b/cmake/Windows/Include/NsisMultiUser.nsh @@ -0,0 +1,1154 @@ +/* + +NsisMultiUser.nsh - NSIS plugin that allows "per-user" (no admin required) and "per-machine" (asks elevation *only when necessary*) installations + +Full source code, documentation and demos at https://github.com/Drizin/NsisMultiUser/ + +Copyright 2016-2018 Ricardo Drizin, Alex Mitev + +*/ + +!verbose push +!verbose 3 + +; Standard NSIS header files +!include nsDialogs.nsh +!include LogicLib.nsh +!include x64.nsh +!include WinVer.nsh +!include FileFunc.nsh +!include UAC.nsh +!include StrFunc.nsh + +RequestExecutionLevel user ; will ask elevation only if necessary + +; exit and error codes +!define MULTIUSER_ERROR_INVALID_PARAMETERS 666660 ; invalid command-line parameters +!define MULTIUSER_ERROR_ELEVATION_NOT_ALLOWED 666661 ; elevation is restricted by MULTIUSER_INSTALLMODE_ALLOW_ELEVATION or MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT +!define MULTIUSER_ERROR_NOT_INSTALLED 666662 ; returned from uninstaller when no version is installed +!define MULTIUSER_ERROR_RUN_UNINSTALLER_FAILED 666663 ; returned from installer if executing the uninstaller failed +!define MULTIUSER_ERROR_ELEVATION_FAILED 666666 ; returned by the outer instance when the inner instance cannot start (user aborted elevation dialog, Logon service not running, UAC is not supported by the OS, user without admin priv. is used in the runas dialog), or started, but was not admin +!define MULTIUSER_INNER_INSTANCE_BACK 666667 ; returned by the inner instance when the user presses the Back button on the first visible page (display outer instance) + +!macro MULTIUSER_INIT_VARS + ; required defines + !ifndef PRODUCT_NAME | VERSION | PROGEXE + !error "Should define all variables: PRODUCT_NAME, VERSION, PROGEXE" + !endif + + ; optional defines + ; COMPANY_NAME - stored in uninstall info in registry + ; CONTACT - stored in uninstall info in registry + ; COMMENTS - stored in uninstall info in registry + ; URL_INFO_ABOUT - stored as the Support Link in the uninstall info of the registry, and when not included, the Help Link as well. + ; URL_HELP_LINK - stored as the Help Link in the uninstall info of the registry. + ; URL_UPDATE_INFO - stored as the Update Information in the uninstall info of the registry. + ; MULTIUSER_INSTALLMODE_NO_HELP_DIALOG - don't show help dialog + + !define /ifndef MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS 1 ; 0 or 1 - whether user can install BOTH per-user and per-machine; this only affects the texts and the required elevation on the page, the actual uninstall of previous version has to be implemented by script + !define /ifndef MULTIUSER_INSTALLMODE_ALLOW_ELEVATION 1 ; 0 or 1, allow UAC screens in the (un)installer - if set to 0 and user is not admin, per-machine radiobutton will be disabled, or if elevation is always required, (un)installer will exit with an error code (and message if not silent) + !if "${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION}" == "" ; old code - just defined with no value, equivalent to 1 + !define /redef MULTIUSER_INSTALLMODE_ALLOW_ELEVATION 1 + !endif + !define /ifndef MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT 0 ; 0 or 1, (only available if MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 1) allow UAC screens in the (un)installer in silent mode; if set to 0 and user is not admin and elevation is always required, (un)installer will exit with an error code + !if ${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION} = 0 + !if ${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT} = 1 + !error "MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT can be set only when MULTIUSER_INSTALLMODE_ALLOW_ELEVATION is set!" + !endif + !endif + !define /ifndef MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 0 ; 0 or 1, (only available if MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 1 and there are 0 or 2 installations on the system) when running as user and is set to 1, per-machine installation is pre-selected, otherwise per-user installation + !if "${MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS}" == "" ; old code - just defined with no value, equivalent to 1 + !define /redef MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 1 + !endif + !define /ifndef MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER 0 ; 0 or 1, (only available if there are 0 or 2 installations on the system) when running as admin and is set to 1, per-user installation is pre-selected, otherwise per-machine installation + !if "${MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER}" == "" ; old code - just defined with no value, equivalent to 1 + !define /redef MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER 1 + !endif + !define /ifndef MULTIUSER_INSTALLMODE_64_BIT 0 ; set to 1 for 64-bit installers + !define /ifndef MULTIUSER_INSTALLMODE_INSTDIR "${PRODUCT_NAME}" ; suggested name of directory to install (under $PROGRAMFILES32/$PROGRAMFILES64 or $LOCALAPPDATA) + + !define /ifndef MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY "${PRODUCT_NAME}" ; registry key for UNINSTALL info, placed under [HKLM|HKCU]\Software\Microsoft\Windows\CurrentVersion\Uninstall (can be ${PRODUCT_NAME} or some {GUID}) + !define /ifndef MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY "Microsoft\Windows\CurrentVersion\Uninstall\${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY}" ; registry key where InstallLocation is stored, placed under [HKLM|HKCU]\Software (can be ${PRODUCT_NAME} or some {GUID}) + !define MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY}" ; full path to registry key storing uninstall information displayed in Windows installed programs list + !define MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY_PATH "Software\${MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY}" ; full path to registry key where InstallLocation is stored + !define /ifndef UNINSTALL_FILENAME "uninstall.exe" ; name of uninstaller + !define /ifndef MULTIUSER_INSTALLMODE_DISPLAYNAME "${PRODUCT_NAME} ${VERSION}" ; display name in Windows uninstall list of programs + !define /ifndef MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "InstallLocation" ; name of the registry value containing install directory + + !ifdef MULTIUSER_INSTALLMODE_FUNCTION + !define MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION ${MULTIUSER_INSTALLMODE_FUNCTION} ; old code - changed function name + !undef MULTIUSER_INSTALLMODE_FUNCTION + !endif + + ; Variables + Var MultiUser.Privileges ; Current user level: "Admin", "Power" (up to Windows XP), or else regular user. + Var MultiUser.InstallMode ; Current Install Mode ("AllUsers" or "CurrentUser") + Var IsAdmin ; 0 or 1, initialized via UserInfo::GetAccountType + Var IsInnerInstance ; 0 or 1, initialized via UAC_IsInnerInstance + Var HasPerMachineInstallation ; 0 or 1 + Var HasPerUserInstallation ; 0 or 1 + Var HasCurrentModeInstallation ; 0 or 1 + Var PerMachineInstallationVersion ; contains version number of empty string "" + Var PerUserInstallationVersion ; contains version number of empty string "" + Var PerMachineInstallationFolder + Var PerUserInstallationFolder + Var PerMachineUninstallString + Var PerUserUninstallString + Var PerMachineOptionAvailable ; 0 or 1: 0 means only per-user radio button is enabled on page, 1 means both; will be 0 only when MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 0 and user is not admin + Var InstallShowPagesBeforeComponents ; 0 or 1, when 0, use it to hide all pages before Components inside the installer when running as inner instance + Var DisplayDialog ; (internal) + Var PreFunctionCalled ; (internal) + Var CmdLineInstallMode ; contains command-line install mode set via /allusers and /currentusers parameters + Var CmdLineDir ; contains command-line directory set via /D parameter + + ; interface variables + Var MultiUser.InstallModePage + Var MultiUser.InstallModePage.Text + Var MultiUser.InstallModePage.AllUsers + Var MultiUser.InstallModePage.CurrentUser + !ifdef UMUI_SYSVERSION + Var MultiUser.InstallModePage.AllUsersLabel + Var MultiUser.InstallModePage.CurrentUserLabel + !endif + Var MultiUser.InstallModePage.Description +!macroend + +!macro MULTIUSER_UNINIT_VARS + !ifdef MULTIUSER_INSTALLMODE_UNFUNCTION + !define MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION ${MULTIUSER_INSTALLMODE_UNFUNCTION} ; old code - changed function name + !undef MULTIUSER_INSTALLMODE_UNFUNCTION + !else ifdef MULTIUSER_INSTALLMODE_CHANGE_MODE_UNFUNCTION + !define MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION ${MULTIUSER_INSTALLMODE_CHANGE_MODE_UNFUNCTION} ; old code - changed function name + !undef MULTIUSER_INSTALLMODE_CHANGE_MODE_UNFUNCTION + !endif + + ; Variables + Var /GLOBAL UninstallShowBackButton ; 0 or 1, use it to show/hide the Back button on the first visible page of the uninstaller +!macroend + +/****** Modern UI 2 page ******/ +!macro MULTIUSER_PAGE UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX + !ifdef MULTIUSER_${UNINSTALLER_PREFIX}PAGE_INSTALLMODE + !error "You cannot insert MULTIUSER_${UNINSTALLER_PREFIX}PAGE_INSTALLMODE more than once!" + !endif + !define MULTIUSER_${UNINSTALLER_PREFIX}PAGE_INSTALLMODE + + #${${UNINSTALLER_PREFIX}StrRep} + + !insertmacro MULTIUSER_${UNINSTALLER_PREFIX}INIT_VARS + + !ifmacrodef MUI_${UNINSTALLER_PREFIX}PAGE_INIT + !insertmacro MUI_${UNINSTALLER_PREFIX}PAGE_INIT + !endif + + !insertmacro MULTIUSER_FUNCTION_INSTALLMODEPAGE "${UNINSTALLER_PREFIX}" "${UNINSTALLER_FUNCPREFIX}" + + PageEx ${UNINSTALLER_FUNCPREFIX}custom + PageCallbacks ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModePre ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeLeave + PageExEnd + + !ifmacrodef MUI_${UNINSTALLER_PREFIX}PAGE_END + !insertmacro MUI_${UNINSTALLER_PREFIX}PAGE_END ; MUI1 MUI_UNPAGE_END macro + !endif +!macroend + +!macro MULTIUSER_PAGE_INSTALLMODE ; create install page - called by user script + !insertmacro MULTIUSER_PAGE "" "" +!macroend + +!macro MULTIUSER_UNPAGE_INSTALLMODE ; create uninstall page - called by user script + !ifndef MULTIUSER_PAGE_INSTALLMODE + !error "You have to insert MULTIUSER_PAGE_INSTALLMODE before MULTIUSER_UNPAGE_INSTALLMODE!" + !endif + !insertmacro MULTIUSER_PAGE "UN" "un." +!macroend + +/****** Installer/uninstaller initialization ******/ +!macro MULTIUSER_LANGUAGE_INIT ; called by user script after the last MUI_LANGUAGE call + !include "NsisMultiUserLang.nsh" +!macroend + +!macro MULTIUSER_INIT ; called by user script in .onInit (after MULTIUSER_PAGE_INSTALLMODE) + !ifdef MULTIUSER_INIT + !error "MULTIUSER_INIT already inserted!" + !endif + !define MULTIUSER_INIT + + !ifndef MULTIUSER_PAGE_INSTALLMODE + !error "You have to insert MULTIUSER_PAGE_INSTALLMODE!" + !endif + + Call MultiUser.InitChecks +!macroend + +!macro MULTIUSER_UNINIT ; called by user script in un.onInit (after MULTIUSER_UNPAGE_INSTALLMODE) + !ifdef MULTIUSER_UNINIT + !error "MULTIUSER_UNINIT already inserted!" + !endif + !define MULTIUSER_UNINIT + + !ifndef MULTIUSER_PAGE_INSTALLMODE | MULTIUSER_UNPAGE_INSTALLMODE + !error "You have to insert both MULTIUSER_PAGE_INSTALLMODE and MULTIUSER_UNPAGE_INSTALLMODE!" + !endif + + Call un.MultiUser.InitChecks +!macroend + +!macro MULTIUSER_SET_ERROR ERROR ; only use in MultiUser.InitChecks! + SetErrorLevel ${ERROR} + ${if} ${silent} + Quit + ${else} + StrCpy $InstallShowPagesBeforeComponents 0 + System::Store L + Return + ${endif} +!macroend + +/****** Functions ******/ +!macro MULTIUSER_FUNCTION_INSTALLMODEPAGE UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${if} $MultiUser.InstallMode == "AllUsers" + Return + ${endif} + + StrCpy $MultiUser.InstallMode "AllUsers" + + SetShellVarContext all + + StrCpy $HasCurrentModeInstallation "$HasPerMachineInstallation" + + ${if} $CmdLineDir != "" + StrCpy $INSTDIR $CmdLineDir + ${elseif} $PerMachineInstallationFolder != "" + ${andif} $PerMachineInstallationVersion == "${VERSION}" + StrCpy $INSTDIR $PerMachineInstallationFolder + ${else} + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ; Set default installation location for installer + ${if} ${MULTIUSER_INSTALLMODE_64_BIT} = 0 + StrCpy $INSTDIR "$PROGRAMFILES32\${MULTIUSER_INSTALLMODE_INSTDIR}" + ${else} + StrCpy $INSTDIR "$PROGRAMFILES64\${MULTIUSER_INSTALLMODE_INSTDIR}" + ${endif} + !endif + ${endif} + + !ifdef MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION + Call "${MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION}" + !endif + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${if} $MultiUser.InstallMode == "CurrentUser" + Return + ${endif} + + StrCpy $MultiUser.InstallMode "CurrentUser" + + SetShellVarContext current + + StrCpy $HasCurrentModeInstallation "$HasPerUserInstallation" + + ${if} $CmdLineDir != "" + StrCpy $INSTDIR $CmdLineDir + ${elseif} $PerUserInstallationFolder != "" + ${andif} $PerUserInstallationVersion == "${VERSION}" + StrCpy $INSTDIR $PerUserInstallationFolder + ${else} + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ; Set default installation location for installer + ${if} "$LOCALAPPDATA" != "" + ; There is a shfolder.dll that emulates CSIDL_LOCAL_APPDATA for older versions of shell32.dll which doesn't support it (pre-Win2k versions) + ; This shfolder.dll is bundeled with IE5 (or as part of Platform SDK Redistributable) that can be installed on NT4 and NSIS (at least since v3.01) will use it instead of shell32.dll if it is available + StrCpy $INSTDIR "$LOCALAPPDATA\${MULTIUSER_INSTALLMODE_INSTDIR}" + ${else} + ; When shfolder.dll is unavailable on NT4 (and so $LOCALAPPDATA returns nothing), local AppData path can still be queried here using registry + ReadRegStr $INSTDIR HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" "Local AppData" + ${if} "$INSTDIR" == "" + StrCpy $INSTDIR "$PROGRAMFILES32\${MULTIUSER_INSTALLMODE_INSTDIR}" ; there's no 64-bit of Windows before 2000 (i.e. NT4) + ${else} + StrCpy $INSTDIR "$INSTDIR\${MULTIUSER_INSTALLMODE_INSTDIR}" + ${endif} + ${endif} + !endif + ${endif} + + !ifdef MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION + Call "${MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION}" + !undef MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION + !endif + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.GetPos + StrCpy $2 $PreFunctionCalled ; if not PreFunctionCalled, we cannot get position + + ${if} $2 = 1 + System::Call "*(i, i, i, i) p .r3" ; allocate RECT struct + + System::Call "User32::GetWindowRect(p $HWNDPARENT, i r3)" + + System::Call '*$3(i .r0, i .r1, i, i)' + + System::Free $3 + ${endif} + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.SetPos + System::Call "User32::SetWindowPos(p $HWNDPARENT, i 0, i $0, i $1, i 0, i 0, i 0x5)" + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckPageElevationRequired + ; check if elevation on page is always required, return result in $0 + ; when this function is called from InitChecks, InstallMode is "" + ; and when called from InstallModeLeave/SetShieldsAndTexts, InstallMode is not empty + StrCpy $0 0 + ${if} $IsAdmin = 0 + ${if} $MultiUser.InstallMode == "AllUsers" + StrCpy $0 1 + ${else} + !if "${UNINSTALLER_FUNCPREFIX}" == "" ; installer + !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} = 0 + ${if} $HasPerMachineInstallation = 1 + ${andif} $HasPerUserInstallation = 0 + ; has to uninstall the per-machine installation, which requires admin rights + ; but signle-user installs of standard users shouldn't be elevated (run as another user) + StrCpy $0 2 + ${endif} + !endif + !endif + ${endif} + ${endif} + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckElevationAllowed + ${if} ${silent} + StrCpy $0 "${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT}" + ${else} + StrCpy $0 "${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION}" + ${endif} + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.Elevate + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckElevationAllowed + + ${if} $0 = 0 + Return + ${endif} + + HideWindow + !insertmacro UAC_RunElevated + ${if} $0 = 0 + ; if inner instance was started ($1 = 1), return code of the elevated fork process is in $2 as well as set via SetErrorLevel + ; NOTE: the error level may have a value MULTIUSER_ERROR_ELEVATION_FAILED (but not MULTIUSER_ERROR_ELEVATION_NOT_ALLOWED) + ${if} $1 <> 1 ; process did not start - return MULTIUSER_ERROR_ELEVATION_FAILED + SetErrorLevel ${MULTIUSER_ERROR_ELEVATION_FAILED} + ${endif} + ${else} ; process did not start - return MULTIUSER_ERROR_ELEVATION_FAILED or Win32 error code stored in $0 + ${if} $0 = 1223 ; user aborted elevation dialog - translate to MULTIUSER_ERROR_ELEVATION_FAILED for easier processing + ${orif} $0 = 1062 ; Logon service not running - translate to MULTIUSER_ERROR_ELEVATION_FAILED for easier processing + StrCpy $0 ${MULTIUSER_ERROR_ELEVATION_FAILED} + ${endif} + SetErrorLevel $0 + ${endif} + Quit + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InitChecks + System::Store S + + ; Installer initialization - check privileges and set default install mode + StrCpy $MultiUser.InstallMode "" + StrCpy $PerMachineOptionAvailable 1 + StrCpy $InstallShowPagesBeforeComponents 1 + StrCpy $DisplayDialog 1 + StrCpy $PreFunctionCalled 0 + StrCpy $CmdLineInstallMode "" + StrCpy $CmdLineDir "" + + ${if} ${RunningX64} ; fix for https://github.com/Drizin/NsisMultiUser/issues/11 + ${if} ${MULTIUSER_INSTALLMODE_64_BIT} = 0 + ; HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall gets redirected to HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall, + ; for HKCU there's no redirection + SetRegView 32 ; someday, when NSIS is 64-bit... + ${else} + SetRegView 64 + ${endif} + ${endif} + + UserInfo::GetAccountType + Pop $MultiUser.Privileges + ${if} $MultiUser.Privileges == "Admin" + ${orif} $MultiUser.Privileges == "Power" ; under XP (and earlier?), Power users can install programs, but UAC_IsAdmin returns false + StrCpy $IsAdmin 1 + ${else} + StrCpy $IsAdmin 0 + ${endif} + + ${if} ${UAC_IsInnerInstance} + StrCpy $IsInnerInstance 1 + ${else} + StrCpy $IsInnerInstance 0 + ${endif} + + ; initialize PerXXXInstallationFolder, PerXXXInstallationVersion, PerXXXUninstallString variables + ReadRegStr $PerMachineInstallationFolder HKLM "${MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY_PATH}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" ; "InstallLocation" + ReadRegStr $PerMachineInstallationVersion HKLM "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}" "DisplayVersion" + ReadRegStr $PerMachineUninstallString HKLM "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}" "UninstallString" ; contains the /currentuser or /allusers parameter + ${if} $PerMachineInstallationFolder == "" + StrCpy $HasPerMachineInstallation 0 + ${else} + StrCpy $HasPerMachineInstallation 1 + ${endif} + + ReadRegStr $PerUserInstallationFolder HKCU "${MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY_PATH}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" ; "InstallLocation" + !insertmacro MULTIUSER_GetCurrentUserString $0 + ReadRegStr $PerUserInstallationVersion HKCU "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "DisplayVersion" + ReadRegStr $PerUserUninstallString HKCU "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "UninstallString" ; contains the /currentuser or /allusers parameter + ${if} $PerUserInstallationFolder == "" + StrCpy $HasPerUserInstallation 0 + ${else} + StrCpy $HasPerUserInstallation 1 + ${if} $PerUserInstallationVersion == "" + ${andif} $0 != "" + ; support old versions that did not have MULTIUSER_GetCurrentUserString + ReadRegStr $PerUserInstallationVersion HKCU "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}" "DisplayVersion" + ReadRegStr $PerUserUninstallString HKCU "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}" "UninstallString" ; contains the /currentuser or /allusers parameter + ${endif} + ${endif} + + ; get all parameters + ${GetParameters} $R0 + + ; initialize CmdLineInstallMode and CmdLineDir, needed also if we are the inner instance (UAC passes all parameters from the outer instance) + ; note: the loading of the /D parameter depends on AllowRootDirInstall, see https://sourceforge.net/p/nsis/bugs/1176/ + ${GetOptions} $R0 "/allusers" $R1 + ${ifnot} ${errors} + StrCpy $CmdLineInstallMode "AllUsers" + ${endif} + + ${GetOptions} $R0 "/currentuser" $R1 + ${ifnot} ${errors} + ${if} $CmdLineInstallMode != "" + !insertmacro MULTIUSER_SET_ERROR ${MULTIUSER_ERROR_INVALID_PARAMETERS} + ${endif} + StrCpy $CmdLineInstallMode "CurrentUser" + ${endif} + + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ${if} "$INSTDIR" != "" ; if $INSTDIR is not empty here in the installer, it's initialized with the value of the /D command-line parameter + StrCpy $CmdLineDir "$INSTDIR" + ${endif} + !endif + + ; initialize $InstallShowPagesBeforeComponents and $UninstallShowBackButton + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ${if} $IsInnerInstance = 1 + StrCpy $InstallShowPagesBeforeComponents 0 ; we hide pages only if we're the inner instance (the outer instance always shows them) + ${endif} + !else + ${if} $CmdLineInstallMode == "" + ${andif} $HasPerMachineInstallation = 1 + ${andif} $HasPerUserInstallation = 1 + StrCpy $UninstallShowBackButton 1 ; make sure we show Back button only if dialog was displayed, i.e. uninstaller did not elevate in the beginning (see when MultiUser.Elevate is called) + ${else} + StrCpy $UninstallShowBackButton 0 + ${endif} + !endif + + ${if} $IsInnerInstance = 1 + ; check if the inner instance has admin rights + ${if} $IsAdmin = 0 + SetErrorLevel ${MULTIUSER_ERROR_ELEVATION_FAILED} ; special return value for outer instance so it knows we did not have admin rights + Quit + ${endif} + + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ; set language to the one used in the outer instance (installer only, for uninstaller the outer and inner instance might have different language, + ; or there might be no current user installation when the outer uninstaller invokes the inner instance + !insertmacro UAC_AsUser_GetGlobalVar $LANGUAGE + + !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} = 0 + !insertmacro UAC_AsUser_GetGlobal $0 $MultiUser.InstallMode + ${if} $0 == "CurrentUser" + ; the inner instance was elevated because there is installation per-machine, which needs to be removed and requires admin rights, + ; but the user selected per-user installation in the outer instance, set context to CurrentUser + Call MultiUser.InstallMode.CurrentUser + StrCpy $DisplayDialog 0 + System::Store L + Return + ${endif} + !endif + !endif + + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers ; Inner Process (and Admin) - set to AllUsers + StrCpy $DisplayDialog 0 + System::Store L + Return + ${endif} + + ; process /? parameter + !ifndef MULTIUSER_INSTALLMODE_NO_HELP_DIALOG ; define MULTIUSER_INSTALLMODE_NO_HELP_DIALOG to display your own help dialog (new options, return codes, etc.) + ${GetOptions} $R0 "/?" $R1 + ${ifnot} ${errors} + MessageBox MB_ICONINFORMATION "Usage:$\r$\n\ + $\r$\n\ + /allusers$\t- (un)install for all users, case-insensitive$\r$\n\ + /currentuser - (un)install for current user only, case-insensitive$\r$\n\ + /uninstall$\t- (installer only) run uninstaller, requires /allusers or /currentuser, case-insensitive$\r$\n\ + /S$\t- silent mode, requires /allusers or /currentuser, case-sensitive$\r$\n\ + /D$\t- (installer only) set install directory, must be last parameter, without quotes, case-sensitive$\r$\n\ + /?$\t- display this message$\r$\n\ + $\r$\n\ + $\r$\n\ + Return codes (decimal):$\r$\n\ + $\r$\n\ + 0$\t- normal execution (no error)$\r$\n\ + 1$\t- (un)installation aborted by user (Cancel button)$\r$\n\ + 2$\t- (un)installation aborted by script$\r$\n\ + 666660$\t- invalid command-line parameters$\r$\n\ + 666661$\t- elevation is not allowed by defines$\r$\n\ + 666662$\t- uninstaller detected there's no installed version$\r$\n\ + 666663$\t- executing uninstaller from the installer failed$\r$\n\ + 666666$\t- cannot start elevated instance$\r$\n\ + other$\t- Windows error code when trying to start elevated instance" + SetErrorLevel 0 + Quit + ${endif} + !endif + + ; process /uninstall parameter + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ${GetOptions} $R0 "/uninstall" $R1 + ${ifnot} ${errors} + ${if} $CmdLineInstallMode == "" + !insertmacro MULTIUSER_SET_ERROR ${MULTIUSER_ERROR_INVALID_PARAMETERS} + ${elseif} $CmdLineInstallMode == "AllUsers" + ${if} $HasPerMachineInstallation = 0 + !insertmacro MULTIUSER_SET_ERROR ${MULTIUSER_ERROR_NOT_INSTALLED} + ${endif} + StrCpy $0 "$PerMachineInstallationFolder" + ${else} + ${if} $HasPerUserInstallation = 0 + !insertmacro MULTIUSER_SET_ERROR ${MULTIUSER_ERROR_NOT_INSTALLED} + ${endif} + StrCpy $0 "$PerUserInstallationFolder" + ${endif} + + ; NOTES: + ; - the _? param stops the uninstaller from copying itself to the temporary directory, which is the only way for waiting to work + ; - $R0 passes the original parameters from the installer to the uninstaller (together with /uninstall so that uninstaller knows installer is running and skips opitional single instance checks) + ; - using ExecWait fails if the new process requires elevation, see http://forums.winamp.com/showthread.php?p=3080202&posted=1#post3080202, so we use ExecShellWait + ExecShellWait "open" "$0\${UNINSTALL_FILENAME}" "$R0 _?=$0" + ${if} ${errors} + SetErrorLevel ${MULTIUSER_ERROR_RUN_UNINSTALLER_FAILED} + ${else} + SetErrorLevel 0 + ${endif} + Quit + ${endif} + !endif + + ; check for limitations + ${if} ${silent} + ${andif} $CmdLineInstallMode == "" + SetErrorLevel ${MULTIUSER_ERROR_INVALID_PARAMETERS} ; one of the /allusers or /currentuser parameters is required in silent mode + Quit + ${endif} + + !if "${UNINSTALLER_FUNCPREFIX}" != "" + ${if} $HasPerMachineInstallation = 0 + ${andif} $HasPerUserInstallation = 0 + !insertmacro MULTIUSER_SET_ERROR ${MULTIUSER_ERROR_NOT_INSTALLED} + ${endif} + !endif + + ; process /allusers and /currentuser parameters (both silent and non-silent mode, installer and uninstaller) + ${if} $CmdLineInstallMode != "" + ${ifnot} ${IsNT} ; Not running Windows NT, (so it's Windows 95/98/ME), so per-user installation not supported + ${andif} $CmdLineInstallMode == "CurrentUser" + !insertmacro MULTIUSER_SET_ERROR ${MULTIUSER_ERROR_INVALID_PARAMETERS} + ${endif} + + ${if} $CmdLineInstallMode == "AllUsers" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + + !if "${UNINSTALLER_FUNCPREFIX}" != "" + ${if} $HasCurrentModeInstallation = 0 + !insertmacro MULTIUSER_SET_ERROR ${MULTIUSER_ERROR_NOT_INSTALLED} + ${endif} + !endif + + !if "${UNINSTALLER_FUNCPREFIX}" != "" + StrCpy $DisplayDialog 0 ; uninstaller - don't display dialog when there is /allusers or /currentuser parameter + !else + ${if} ${silent} + StrCpy $DisplayDialog 0 + ${endif} + !endif + + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckPageElevationRequired + ${if} $0 = 1 + ${if} $DisplayDialog = 0 ; if we are not displaying the dialog (uninstaller or silent mode) and elevation is required, Elevate now (or Quit with an error) + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.Elevate + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckElevationAllowed ; if we are displaying the dialog and elevation is required, check if elevation is allowed + ${endif} + ${if} $0 = 0 + !insertmacro MULTIUSER_SET_ERROR ${MULTIUSER_ERROR_ELEVATION_NOT_ALLOWED} + ${endif} + ${endif} + System::Store L + Return + ${endif} + + ; the rest of the code is executed only when there are no /allusers and /currentuser parameters and in non-silent mode + ${ifnot} ${IsNT} ; Not running Windows NT, (so it's Windows 95/98/ME), so per-user installation not supported + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + StrCpy $DisplayDialog 0 + System::Store L + Return + ${endif} + + ; check if elevation on page is always required (installer only) + !if "${UNINSTALLER_FUNCPREFIX}" == "" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckPageElevationRequired + ${if} $0 = 1 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckElevationAllowed + ${if} $0 = 0 + !insertmacro MULTIUSER_SET_ERROR ${MULTIUSER_ERROR_ELEVATION_NOT_ALLOWED} + ${endif} + ${endif} + !endif + + ; if elevation is not allowed and user is not admin, disable the per-machine option + !if ${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION} = 0 + ${if} $IsAdmin = 0 + StrCpy $PerMachineOptionAvailable 0 + ${endif} + !endif + + ; if there's only one installed version + ; when uninstaller is invoked from the "add/remove programs", Windows will automatically start uninstaller elevated if uninstall keys are in HKLM + ${if} $HasPerMachineInstallation = 1 + ${andif} $HasPerUserInstallation = 0 + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ${if} $PerMachineOptionAvailable = 1 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + !else + ${if} $IsAdmin = 0 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.Elevate ; if $PerMachineOptionAvailable = 0 (i.e. MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 0), Elevate will call CheckElevationAllowed, which checks if MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 0 + ${if} $0 = 0 + !insertmacro MULTIUSER_SET_ERROR ${MULTIUSER_ERROR_ELEVATION_NOT_ALLOWED} + ${endif} + ${endif} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + StrCpy $DisplayDialog 0 + !endif + ${elseif} $HasPerMachineInstallation = 0 + ${andif} $HasPerUserInstallation = 1 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + !if "${UNINSTALLER_FUNCPREFIX}" != "" + StrCpy $DisplayDialog 0 + !endif + ${else} ; if there is no installed version (installer only), or there are 2 installations - we always display the dialog + ${if} $IsAdmin = 1 ; If running as admin, default to per-machine installation (unless default is forced by MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER) + !if ${MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER} = 0 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + !else + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + !endif + ${else} ; if not running as admin, default to per-user installation (unless default is forced by MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS) + !if ${MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS} = 0 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + !else + ${if} $PerMachineOptionAvailable = 1 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + !endif + ${endif} + ${endif} + + System::Store L + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.ShowErrorMessage + Push $0 + + GetErrorLevel $0 + + ${if} $0 = -1 + Pop $0 + Return + ${endif} + + ${Switch} $0 + ${Case} ${MULTIUSER_ERROR_INVALID_PARAMETERS} + MessageBox MB_ICONSTOP "$(MULTIUSER_INVALID_PARAMS)" /SD IDOK + Quit + ${Case} ${MULTIUSER_ERROR_NOT_INSTALLED} + MessageBox MB_ICONSTOP "$(MULTIUSER_NOT_INSTALLED)" /SD IDOK + Quit + ${Case} ${MULTIUSER_ERROR_ELEVATION_NOT_ALLOWED} + MessageBox MB_ICONSTOP "$(MULTIUSER_RUN_AS_ADMIN)" /SD IDOK + Quit + ${EndSwitch} + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModePre + System::Store S + + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.ShowErrorMessage + + ${if} $IsInnerInstance = 1 + ${if} $PreFunctionCalled = 0 ; inner instance is displayed + ; set position of inner instance + !insertmacro UAC_AsUser_Call Function ${UNINSTALLER_FUNCPREFIX}MultiUser.GetPos ${UAC_SYNCREGISTERS} + ${if} $2 = 1 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.SetPos + ${endif} + ${else} ; user pressed Back button on the first visible page in the inner instance - display outer instance + ; set position of outer instance + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.GetPos + !insertmacro UAC_AsUser_Call Function ${UNINSTALLER_FUNCPREFIX}MultiUser.SetPos ${UAC_SYNCREGISTERS} + + SetErrorLevel ${MULTIUSER_INNER_INSTANCE_BACK} + Quit + ${endif} + ${endif} + StrCpy $PreFunctionCalled 1 + + ${if} $DisplayDialog = 0 + System::Store L + Abort + ${endif} + + !ifmacrodef MUI_HEADER_TEXT + !if "${UNINSTALLER_FUNCPREFIX}" == "" + !insertmacro MUI_HEADER_TEXT "$(MULTIUSER_PAGE_TITLE)" "$(MULTIUSER_INSTALL_PAGE_SUBTITLE)" + !else + !insertmacro MUI_HEADER_TEXT "$(MULTIUSER_PAGE_TITLE)" "$(MULTIUSER_UNINSTALL_PAGE_SUBTITLE)" + !endif + !endif + + !ifdef MUI_PAGE_CUSTOMFUNCTION_PRE + Call "${MUI_PAGE_CUSTOMFUNCTION_PRE}" + !undef MUI_PAGE_CUSTOMFUNCTION_PRE + !endif + nsDialogs::Create 1018 + Pop $MultiUser.InstallModePage + + ; default was MULTIUSER_TEXT_INSTALLMODE_TITLE "Choose Users" + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ${NSD_CreateLabel} 0 0 100% 24u "$(MULTIUSER_INSTALL_HEADER)" + !else + ${NSD_CreateLabel} 0 0 100% 24u "$(MULTIUSER_UNINSTALL_HEADER)" + !endif + Pop $MultiUser.InstallModePage.Text + + !ifdef UMUI_SYSVERSION + StrCpy $0 "$(MULTIUSER_ALL_USERS_UMUI)" + ${NSD_CreateRadioButton} 30u 30% 10u 8u "" + Pop $MultiUser.InstallModePage.AllUsers + + System::Call "advapi32::GetUserName(t. r1, *i ${NSIS_MAX_STRLEN})" + ${${UNINSTALLER_PREFIX}StrRep} "$1" "$(MULTIUSER_CURRENT_USER_UMUI)" "{USER}" "$1" + ${NSD_CreateRadioButton} 30u 45% 10u 8u "" + Pop $MultiUser.InstallModePage.CurrentUser + + ; We create the radio buttons with empty text and create separate labels, because radio button font color can't be changed with XP Styles turned on, + ; which creates problems with UMUI themes, see http://forums.winamp.com/showthread.php?p=3079742#post3079742 + ; shortcuts (&) for labels don't work and cause strange behaviour in NSIS - going to another page, etc. + ${NSD_CreateLabel} 44u 30% 280u 16u "$0" + Pop $MultiUser.InstallModePage.AllUsersLabel + nsDialogs::SetUserData $MultiUser.InstallModePage.AllUsersLabel $MultiUser.InstallModePage.AllUsers + ${NSD_CreateLabel} 44u 45% 280u 8u "$1" + Pop $MultiUser.InstallModePage.CurrentUserLabel + nsDialogs::SetUserData $MultiUser.InstallModePage.CurrentUserLabel $MultiUser.InstallModePage.CurrentUser + + ${if} $PerMachineOptionAvailable = 0 ; install per-machine is not available + SendMessage $MultiUser.InstallModePage.AllUsersLabel ${WM_SETTEXT} 0 "STR:$0$\r$\n($(MULTIUSER_RUN_AS_ADMIN))" ; only when $PerMachineOptionAvailable = 0, we add that comment to the disabled control itself + ${orif} $CmdLineInstallMode != "" + EnableWindow $MultiUser.InstallModePage.AllUsersLabel 0 ; start out disabled + EnableWindow $MultiUser.InstallModePage.AllUsers 0 ; start out disabled + ${endif} + + ${if} $CmdLineInstallMode != "" + EnableWindow $MultiUser.InstallModePage.CurrentUserLabel 0 + EnableWindow $MultiUser.InstallModePage.CurrentUser 0 + ${endif} + + ; bind to label click + ${NSD_OnClick} $MultiUser.InstallModePage.CurrentUserLabel ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionLabelClick + ${NSD_OnClick} $MultiUser.InstallModePage.AllUsersLabel ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionLabelClick + !else + StrCpy $0 "$(MULTIUSER_ALL_USERS)" + + System::Call "advapi32::GetUserName(t. r1, *i ${NSIS_MAX_STRLEN})" + ${${UNINSTALLER_PREFIX}StrRep} "$1" "$(MULTIUSER_CURRENT_USER)" "{USER}" "$1" + + ${NSD_CreateRadioButton} 30u 30% 280u 16u "$0" + Pop $MultiUser.InstallModePage.AllUsers + + ${NSD_CreateRadioButton} 30u 45% 280u 8u "$1" + Pop $MultiUser.InstallModePage.CurrentUser + + ${if} $PerMachineOptionAvailable = 0 ; install per-machine is not available + SendMessage $MultiUser.InstallModePage.AllUsers ${WM_SETTEXT} 0 "STR:$0$\r$\n($(MULTIUSER_RUN_AS_ADMIN))" ; only when $PerMachineOptionAvailable = 0, we add that comment to the disabled control itself + ${orif} $CmdLineInstallMode != "" + EnableWindow $MultiUser.InstallModePage.AllUsers 0 ; start out disabled + ${endif} + + ${if} $CmdLineInstallMode != "" + EnableWindow $MultiUser.InstallModePage.CurrentUser 0 + ${endif} + !endif + + ; bind to radiobutton change + ${NSD_OnClick} $MultiUser.InstallModePage.CurrentUser ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionClick + ${NSD_OnClick} $MultiUser.InstallModePage.AllUsers ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionClick + + ${NSD_CreateLabel} 0u -32u 100% 32u "" ; will hold up to 4 lines of text + Pop $MultiUser.InstallModePage.Description + + ${if} $MultiUser.InstallMode == "AllUsers" ; setting selected radio button + SendMessage $MultiUser.InstallModePage.AllUsers ${BM_SETCHECK} ${BST_CHECKED} 0 ; select radio button + ${else} + SendMessage $MultiUser.InstallModePage.CurrentUser ${BM_SETCHECK} ${BST_CHECKED} 0 ; select radio button + ${endif} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.SetShieldAndTexts ; simulating click on the control will change $INSTDIR and reset a possible user selection + + !ifmacrodef UMUI_IOPAGEBGTRANSPARENT_INIT ; UMUI, apply theme to controls + !ifndef USE_MUIEx ; for MUIEx, applying themes causes artifacts + !insertmacro UMUI_IOPAGEBGTRANSPARENT_INIT $MultiUser.InstallModePage + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.Text + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.AllUsers + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.AllUsersLabel + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.CurrentUser + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.CurrentUserLabel + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.Description + !endif + !endif + + System::Store L + + !ifdef MUI_PAGE_CUSTOMFUNCTION_SHOW + Call "${MUI_PAGE_CUSTOMFUNCTION_SHOW}" + !undef MUI_PAGE_CUSTOMFUNCTION_SHOW + !endif + + nsDialogs::Show + + !if "${UNINSTALLER_FUNCPREFIX}" == "" + Push $0 + GetDlgItem $0 $HWNDPARENT 1 + SendMessage $0 ${BCM_SETSHIELD} 0 0 ; hide SHIELD on page leave (InstallModeLeave is called only on Next button click) + Pop $0 + !endif + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeLeave + System::Store S + + !if ${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION} = 1 ; if elevation is allowed + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckPageElevationRequired + + ${if} $0 = 1 + HideWindow + !insertmacro UAC_RunElevated + ;MessageBox MB_OK "[$0]/[$1]/[$2]/[$3]" + + ; http://nsis.sourceforge.net/UAC_plug-in + ${Switch} $0 + ${Case} 0 + ${Switch} $1 + ${Case} 1 ; Started an elevated child process successfully, exit code is in $2 + ${Switch} $2 + ${Case} ${MULTIUSER_ERROR_ELEVATION_FAILED} ; the inner instance was not admin after all - stay on page + MessageBox MB_ICONSTOP "$(MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED)" /SD IDOK + ${Break} + ${Case} ${MULTIUSER_INNER_INSTANCE_BACK} ; if user pressed Back button on the first visible page of the inner instance - stay on page + ${Break} + ${Default} ; all other cases - Quit + ; return code of the elevated fork process is in $2 as well as set via SetErrorLevel + Quit + ${EndSwitch} + ${Break} + ${Case} 3 ; RunAs completed successfully, but with a non-admin user - stay on page + MessageBox MB_ICONSTOP "$(MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED)" /SD IDOK + ${Break} + ${Default} ; 0 - UAC is not supported by the OS, OR 2 - The process is already running @ HighIL (Member of admin group) - stay on page + MessageBox MB_ICONSTOP "$(MULTIUSER_ELEVATION_NOT_SUPPORTED)" /SD IDOK + ${EndSwitch} + ${Break} + ${Case} 1223 ; user aborted elevation dialog - stay on page + ${Break} + ${Case} 1062 ; Logon service not running - stay on page + MessageBox MB_ICONSTOP "$(MULTIUSER_LOGON_SERVICE_NOT_RUNNING)" /SD IDOK + ${Break} + ${Default} ; anything else should be treated as a fatal error - stay on page + ${${UNINSTALLER_PREFIX}StrRep} "$0" "$(MULTIUSER_ELEVATION_ERROR)" "{ERROR}" "$0" + MessageBox MB_ICONSTOP "$0" /SD IDOK + ${EndSwitch} + + ; clear the error level set by UAC for inner instance, so that outer instance returns its own error level when exits (the error level is not reset by NSIS if once set and >= 0) + ; see http://forums.winamp.com/showthread.php?p=3079116&posted=1#post3079116 + SetErrorLevel -1 + BringToFront + Abort ; Stay on page + ${endif} + !endif + + System::Store L + + !ifdef MUI_PAGE_CUSTOMFUNCTION_LEAVE + Call "${MUI_PAGE_CUSTOMFUNCTION_LEAVE}" + !undef MUI_PAGE_CUSTOMFUNCTION_LEAVE + !endif + FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.SetShieldAndTexts + System::Store S + + StrCpy $0 "$MultiUser.InstallMode" + ; if necessary, display text for different install mode rather than the actual one in $MultiUser.InstallMode + !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} = 0 + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ${if} $MultiUser.InstallMode == "AllUsers" ; user selected "all users" + ${if} $HasPerMachineInstallation = 0 + ${andif} $HasPerUserInstallation = 1 + StrCpy $0 "CurrentUser" ; display information for the "current user" installation + ${endif} + ${elseif} $HasPerMachineInstallation = 1 + ${andif} $HasPerUserInstallation = 0 ; user selected "current user" + StrCpy $0 "AllUsers" ; display information for the "all users" installation + ${endif} + !endif + !endif + + ; set label text + StrCpy $1 "" + ${if} $0 == "AllUsers" ; all users + ${if} $HasPerMachineInstallation = 1 + ${${UNINSTALLER_PREFIX}StrRep} "$1" "$(MULTIUSER_INSTALLED_ALL_USERS)" "{VERSION}" "$PerMachineInstallationVersion" + ${${UNINSTALLER_PREFIX}StrRep} "$1" "$1" "{FOLDER}" "$PerMachineInstallationFolder" + + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ${if} $PerMachineInstallationVersion == "${VERSION}" + ${if} $MultiUser.InstallMode == "AllUsers" + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$(MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS)" "{VERSION}" "$PerMachineInstallationVersion" + ${else} + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$(MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER)" "{VERSION}" "$PerMachineInstallationVersion" + ${endif} + ${else} + ${if} $MultiUser.InstallMode == "AllUsers" + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$(MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS)" "{OLD_VERSION}" "$PerMachineInstallationVersion" + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$2" "{VERSION}" "${VERSION}" + ${else} + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$(MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER)" "{OLD_VERSION}" "$PerMachineInstallationVersion" + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$2" "{VERSION}" "${VERSION}" + ${endif} + ${endif} + StrCpy $1 "$1$\r$\n$2" + !endif + ${else} + StrCpy $1 "$(MULTIUSER_NEW_INSTALLATION_ALL_USERS)" + ${endif} + ${else} ; current user + ${if} $HasPerUserInstallation = 1 + ${${UNINSTALLER_PREFIX}StrRep} "$1" "$(MULTIUSER_INSTALLED_CURRENT_USER)" "{VERSION}" "$PerUserInstallationVersion" + ${${UNINSTALLER_PREFIX}StrRep} "$1" "$1" "{FOLDER}" "$PerUserInstallationFolder" + + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ${if} $PerUserInstallationVersion == "${VERSION}" + ${if} $MultiUser.InstallMode == "AllUsers" + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$(MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS)" "{VERSION}" "$PerUserInstallationVersion" + ${else} + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$(MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER)" "{VERSION}" "$PerUserInstallationVersion" + ${endif} + ${else} + ${if} $MultiUser.InstallMode == "AllUsers" + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$(MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS)" "{OLD_VERSION}" "$PerUserInstallationVersion" + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$2" "{VERSION}" "${VERSION}" + ${else} + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$(MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER)" "{OLD_VERSION}" "$PerUserInstallationVersion" + ${${UNINSTALLER_PREFIX}StrRep} "$2" "$2" "{VERSION}" "${VERSION}" + ${endif} + ${endif} + StrCpy $1 "$1$\r$\n$2" + !endif + ${else} + StrCpy $1 "$(MULTIUSER_NEW_INSTALLATION_CURRENT_USER)" + ${endif} + ${endif} + + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckPageElevationRequired + ${if} $0 = 1 + StrCpy $1 "$1 $(MULTIUSER_ADMIN_CREDENTIALS_REQUIRED)" + ${elseif} $0 = 2 + StrCpy $1 "$1 $(MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED)" + StrCpy $0 0 + ${endif} + + SendMessage $MultiUser.InstallModePage.Description ${WM_SETTEXT} 0 "STR:$1" + + GetDlgItem $1 $hwndParent 1 ; get item 1 (next button) at parent window, store in $1 - (0 is back, 1 is next .. what about CANCEL? http://nsis.sourceforge.net/Buttons_Header ) + + SendMessage $1 ${BCM_SETSHIELD} 0 $0 ; display/hide SHIELD (Windows Vista and above) + + System::Store L + FunctionEnd + + !ifdef UMUI_SYSVERSION + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionLabelClick + Exch $0 ; get clicked control's HWND, which is on the stack in $0 + nsDialogs::GetUserData $0 + Pop $0 + + ${NSD_Uncheck} $MultiUser.InstallModePage.AllUsers + ${NSD_Uncheck} $MultiUser.InstallModePage.CurrentUser + ${NSD_Check} $0 ; ${NSD_Check} will check both radio buttons without the above 2 lines + ${NSD_SetFocus} $0 + Push $0 + ; ${NSD_Check} doesn't call Click event + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionClick + + Pop $0 + FunctionEnd + !endif + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionClick + Exch $0 ; get clicked control's HWND, which is on the stack in $0 + + ; set InstallMode + ${if} $0 = $MultiUser.InstallModePage.AllUsers + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.SetShieldAndTexts + + Pop $0 + FunctionEnd +!macroend + +!macro MULTIUSER_GetCurrentUserString VAR + StrCpy ${VAR} "" + !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} <> 0 + ${if} $MultiUser.InstallMode == "CurrentUser" + ${orif} $MultiUser.InstallMode == "" ; called from InitChecks + StrCpy ${VAR} " (current user)" + ${endif} + !endif +!macroend + +!macro MULTIUSER_RegistryAddInstallInfo + Push $0 + Push $1 + Push $2 + Push $3 + Push $4 + + ; Write the installation path into the registry + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY_PATH}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" "$INSTDIR" ; "InstallLocation" + + ; Write the uninstall keys for Windows + ; Workaround for Windows issue: if the uninstall key names are the same in HKLM and HKCU, Windows displays only one entry in the add/remove programs dialog; + ; this will create 2 different keys in HKCU (MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY_PATH and MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH), + ; but that's OK, both will be removed by uninstaller + !insertmacro MULTIUSER_GetCurrentUserString $0 + + ${if} $MultiUser.InstallMode == "AllUsers" ; setting defaults + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}" "DisplayName" "${MULTIUSER_INSTALLMODE_DISPLAYNAME}" + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}" "UninstallString" '"$INSTDIR\${UNINSTALL_FILENAME}" /allusers' + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}" "QuietUninstallString" '"$INSTDIR\${UNINSTALL_FILENAME}" /allusers /S' + ${else} + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "DisplayName" "${MULTIUSER_INSTALLMODE_DISPLAYNAME} (current user)" ; "add/remove programs" will show if installation is per-user + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "UninstallString" '"$INSTDIR\${UNINSTALL_FILENAME}" /currentuser' + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "QuietUninstallString" '"$INSTDIR\${UNINSTALL_FILENAME}" /currentuser /S' + ${endif} + + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "DisplayVersion" "${VERSION}" + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "DisplayIcon" "$INSTDIR\${PROGEXE},0" + !ifdef COMPANY_NAME + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "Publisher" "${COMPANY_NAME}" + !endif + !ifdef CONTACT + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "Contact" "${CONTACT}" + !endif + !ifdef COMMENTS + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "Comments" "${COMMENTS}" + !endif + !ifdef URL_INFO_ABOUT + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "URLInfoAbout" "${URL_INFO_ABOUT}" + !endif + !ifdef URL_HELP_LINK + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "HelpLink" "${URL_HELP_LINK}" + !endif + !ifdef URL_UPDATE_INFO + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "URLUpdateInfo" "${URL_UPDATE_INFO}" + !endif + WriteRegDWORD SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "NoModify" 1 + WriteRegDWORD SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "NoRepair" 1 + + ; Write InstallDate string value in 'YYYYMMDD' format. + ; Without it, Windows gets the date from the registry key metadata, which might be inaccurate. + System::Call /NOUNLOAD "*(&i2,&i2,&i2,&i2,&i2,&i2,&i2,&i2) i .r4" + System::Call /NOUNLOAD "kernel32::GetLocalTime(i)i(r4)" + System::Call /NOUNLOAD "*$4(&i2,&i2,&i2,&i2,&i2,&i2,&i2,&i2)i(.r1,.r2,,.r3,,,,)" + System::Free $4 + IntCmp $2 9 0 0 +2 + StrCpy $2 "0$2" + IntCmp $3 9 0 0 +2 + StrCpy $3 "0$3" + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "InstallDate" "$1$2$3" + + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +!macroend + +!macro MULTIUSER_RegistryAddInstallSizeInfo + Push $0 + Push $1 + Push $2 + Push $3 + + !insertmacro MULTIUSER_GetCurrentUserString $0 + + ${GetSize} "$INSTDIR" "/S=0K" $1 $2 $3 ; get folder size, convert to KB + IntFmt $1 "0x%08X" $1 + WriteRegDWORD SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" "EstimatedSize" "$1" + + Pop $3 + Pop $2 + Pop $1 + Pop $0 +!macroend + +!macro MULTIUSER_RegistryRemoveInstallInfo + Push $0 + + ; Remove registry keys + DeleteRegKey SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}" + !insertmacro MULTIUSER_GetCurrentUserString $0 + ${if} "$0" != "" + DeleteRegKey SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY_PATH}$0" + ${endif} + DeleteRegKey SHCTX "${MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY_PATH}" + + Pop $0 +!macroend + +!verbose pop diff --git a/cmake/Windows/Include/NsisMultiUserLang.nsh b/cmake/Windows/Include/NsisMultiUserLang.nsh new file mode 100644 index 000000000..add12a478 --- /dev/null +++ b/cmake/Windows/Include/NsisMultiUserLang.nsh @@ -0,0 +1,2038 @@ +!ifdef LANG_ENGLISH + LangString MULTIUSER_PAGE_TITLE ${LANG_ENGLISH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_ENGLISH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_ENGLISH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_ENGLISH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_ENGLISH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_ENGLISH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_ENGLISH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_ENGLISH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_ENGLISH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_ENGLISH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_ENGLISH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_ENGLISH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_ENGLISH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_ENGLISH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_ENGLISH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_ENGLISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_ENGLISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_ENGLISH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_ENGLISH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_ENGLISH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_ENGLISH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_ENGLISH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_ENGLISH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_ENGLISH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_ENGLISH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_ENGLISH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_ENGLISH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_AFRIKAANS + LangString MULTIUSER_PAGE_TITLE ${LANG_AFRIKAANS} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_AFRIKAANS} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_AFRIKAANS} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_AFRIKAANS} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_AFRIKAANS} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_AFRIKAANS} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_AFRIKAANS} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_AFRIKAANS} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_AFRIKAANS} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_AFRIKAANS} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_AFRIKAANS} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_AFRIKAANS} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_AFRIKAANS} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_AFRIKAANS} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_AFRIKAANS} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_AFRIKAANS} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_AFRIKAANS} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_AFRIKAANS} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_AFRIKAANS} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_AFRIKAANS} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_AFRIKAANS} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_AFRIKAANS} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_AFRIKAANS} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_AFRIKAANS} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_AFRIKAANS} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_AFRIKAANS} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_AFRIKAANS} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_ALBANIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_ALBANIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_ALBANIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_ALBANIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_ALBANIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_ALBANIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_ALBANIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_ALBANIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_ALBANIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_ALBANIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_ALBANIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_ALBANIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_ALBANIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_ALBANIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_ALBANIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_ALBANIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_ALBANIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_ALBANIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_ALBANIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_ALBANIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_ALBANIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_ALBANIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_ALBANIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_ALBANIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_ALBANIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_ALBANIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_ALBANIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_ALBANIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_ARABIC + LangString MULTIUSER_PAGE_TITLE ${LANG_ARABIC} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_ARABIC} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_ARABIC} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_ARABIC} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_ARABIC} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_ARABIC} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_ARABIC} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_ARABIC} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_ARABIC} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_ARABIC} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_ARABIC} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_ARABIC} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_ARABIC} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_ARABIC} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_ARABIC} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_ARABIC} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_ARABIC} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_ARABIC} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_ARABIC} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_ARABIC} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_ARABIC} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_ARABIC} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_ARABIC} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_ARABIC} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_ARABIC} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_ARABIC} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_ARABIC} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_ARMENIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_ARMENIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_ARMENIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_ARMENIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_ARMENIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_ARMENIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_ARMENIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_ARMENIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_ARMENIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_ARMENIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_ARMENIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_ARMENIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_ARMENIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_ARMENIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_ARMENIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_ARMENIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_ARMENIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_ARMENIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_ARMENIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_ARMENIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_ARMENIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_ARMENIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_ARMENIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_ARMENIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_ARMENIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_ARMENIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_ARMENIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_ARMENIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_ASTURIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_ASTURIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_ASTURIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_ASTURIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_ASTURIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_ASTURIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_ASTURIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_ASTURIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_ASTURIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_ASTURIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_ASTURIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_ASTURIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_ASTURIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_ASTURIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_ASTURIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_ASTURIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_ASTURIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_ASTURIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_ASTURIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_ASTURIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_ASTURIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_ASTURIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_ASTURIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_ASTURIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_ASTURIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_ASTURIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_ASTURIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_ASTURIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_BASQUE + LangString MULTIUSER_PAGE_TITLE ${LANG_BASQUE} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_BASQUE} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_BASQUE} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_BASQUE} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_BASQUE} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_BASQUE} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_BASQUE} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_BASQUE} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_BASQUE} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_BASQUE} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_BASQUE} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_BASQUE} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_BASQUE} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_BASQUE} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_BASQUE} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_BASQUE} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_BASQUE} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_BASQUE} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_BASQUE} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_BASQUE} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_BASQUE} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_BASQUE} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_BASQUE} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_BASQUE} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_BASQUE} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_BASQUE} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_BASQUE} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_BELARUSIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_BELARUSIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_BELARUSIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_BELARUSIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_BELARUSIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_BELARUSIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_BELARUSIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_BELARUSIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_BELARUSIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_BELARUSIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_BELARUSIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_BELARUSIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_BELARUSIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_BELARUSIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_BELARUSIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_BELARUSIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_BELARUSIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_BELARUSIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_BELARUSIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_BELARUSIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_BELARUSIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_BELARUSIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_BELARUSIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_BELARUSIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_BELARUSIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_BELARUSIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_BELARUSIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_BELARUSIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_BOSNIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_BOSNIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_BOSNIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_BOSNIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_BOSNIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_BOSNIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_BOSNIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_BOSNIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_BOSNIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_BOSNIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_BOSNIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_BOSNIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_BOSNIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_BOSNIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_BOSNIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_BOSNIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_BOSNIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_BOSNIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_BOSNIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_BOSNIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_BOSNIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_BOSNIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_BOSNIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_BOSNIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_BOSNIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_BOSNIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_BOSNIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_BOSNIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_BRETON + LangString MULTIUSER_PAGE_TITLE ${LANG_BRETON} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_BRETON} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_BRETON} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_BRETON} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_BRETON} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_BRETON} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_BRETON} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_BRETON} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_BRETON} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_BRETON} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_BRETON} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_BRETON} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_BRETON} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_BRETON} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_BRETON} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_BRETON} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_BRETON} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_BRETON} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_BRETON} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_BRETON} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_BRETON} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_BRETON} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_BRETON} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_BRETON} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_BRETON} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_BRETON} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_BRETON} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_BULGARIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_BULGARIAN} "Избор на потребители" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_BULGARIAN} "Изберете за кои потребители да се инсталира $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_BULGARIAN} "Изберете за кои потребители да се премахне $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_BULGARIAN} "Изберете дали да инсталирате $(^NameDA) за всички потребители или за текущия потребител." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_BULGARIAN} "$(^NameDA) е инсталиран едновременно за всички потребители и за текущия потребител.$\r$\nИзберете коя инсталация премахнете." + LangString MULTIUSER_ALL_USERS ${LANG_BULGARIAN} "За &всеки, който използва този компютър (всички потребители)$\r$\n(Забележка: Програмата за инсталиране ще се рестартира с повишени привилегии)" + LangString MULTIUSER_CURRENT_USER ${LANG_BULGARIAN} "За &мен ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_BULGARIAN} "За всеки, който използва този компютър (всички потребители)$\r$\n(Забележка: Програмата за инсталиране ще се рестартира с повишени привилегии)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_BULGARIAN} "За мен ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_BULGARIAN} "Нова инсталация за всички потребители." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_BULGARIAN} "Нова инсталация за текущия потребител." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_BULGARIAN} "Версия {VERSION} е инсталирана за всички потребители в $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_BULGARIAN} "Версия {VERSION} е инсалирана за текущия потребител в $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_BULGARIAN} "Преинсталиране на версия {VERSION} за всички потребители." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_BULGARIAN} "Преинсталиране на версия {VERSION} за текущия потребител." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_BULGARIAN} "Деинсталиране на версия {OLD_VERSION} и инсталиране на версия {VERSION} за всички потребители." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_BULGARIAN} "Деинсталиране на версия {OLD_VERSION} и инсталиране на версия {VERSION} за текущия потребител." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_BULGARIAN} "Трябва да стартирате тази програма като администратор." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_BULGARIAN} "Изисква се администраторска идентификация." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_BULGARIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_BULGARIAN} "Невалидна комбинация от параметри." + LangString MULTIUSER_NOT_INSTALLED ${LANG_BULGARIAN} "Няма инсталация на $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_BULGARIAN} "Операционната система не поддържа инсталации за текущия потребител." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_BULGARIAN} "Трябва да влезете с профил, който е член на администраторската група, за да продължите." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_BULGARIAN} "Операционната система не поддържа повишаване на привилегиите." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_BULGARIAN} "Не е възможно повишаване на привилегиите, услугата Secondary Logon не е пусната." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_BULGARIAN} "Не е възможно повишаване на привилегиите, грешка {ERROR}." +!endif + +!ifdef LANG_CATALAN + LangString MULTIUSER_PAGE_TITLE ${LANG_CATALAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_CATALAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_CATALAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_CATALAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_CATALAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_CATALAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_CATALAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_CATALAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_CATALAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_CATALAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_CATALAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_CATALAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_CATALAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_CATALAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_CATALAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_CATALAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_CATALAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_CATALAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_CATALAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_CATALAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_CATALAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_CATALAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_CATALAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_CATALAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_CATALAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_CATALAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_CATALAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_CORSICAN + LangString MULTIUSER_PAGE_TITLE ${LANG_CORSICAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_CORSICAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_CORSICAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_CORSICAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_CORSICAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_CORSICAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_CORSICAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_CORSICAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_CORSICAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_CORSICAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_CORSICAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_CORSICAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_CORSICAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_CORSICAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_CORSICAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_CORSICAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_CORSICAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_CORSICAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_CORSICAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_CORSICAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_CORSICAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_CORSICAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_CORSICAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_CORSICAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_CORSICAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_CORSICAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_CORSICAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_CROATIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_CROATIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_CROATIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_CROATIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_CROATIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_CROATIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_CROATIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_CROATIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_CROATIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_CROATIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_CROATIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_CROATIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_CROATIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_CROATIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_CROATIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_CROATIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_CROATIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_CROATIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_CROATIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_CROATIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_CROATIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_CROATIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_CROATIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_CROATIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_CROATIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_CROATIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_CROATIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_CROATIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_CZECH + LangString MULTIUSER_PAGE_TITLE ${LANG_CZECH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_CZECH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_CZECH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_CZECH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_CZECH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_CZECH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_CZECH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_CZECH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_CZECH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_CZECH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_CZECH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_CZECH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_CZECH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_CZECH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_CZECH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_CZECH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_CZECH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_CZECH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_CZECH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_CZECH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_CZECH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_CZECH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_CZECH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_CZECH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_CZECH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_CZECH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_CZECH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_DANISH + LangString MULTIUSER_PAGE_TITLE ${LANG_DANISH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_DANISH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_DANISH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_DANISH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_DANISH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_DANISH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_DANISH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_DANISH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_DANISH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_DANISH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_DANISH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_DANISH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_DANISH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_DANISH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_DANISH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_DANISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_DANISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_DANISH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_DANISH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_DANISH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_DANISH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_DANISH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_DANISH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_DANISH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_DANISH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_DANISH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_DANISH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_DUTCH + LangString MULTIUSER_PAGE_TITLE ${LANG_DUTCH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_DUTCH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_DUTCH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_DUTCH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_DUTCH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_DUTCH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_DUTCH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_DUTCH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_DUTCH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_DUTCH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_DUTCH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_DUTCH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_DUTCH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_DUTCH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_DUTCH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_DUTCH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_DUTCH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_DUTCH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_DUTCH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_DUTCH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_DUTCH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_DUTCH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_DUTCH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_DUTCH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_DUTCH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_DUTCH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_DUTCH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_ESPERANTO + LangString MULTIUSER_PAGE_TITLE ${LANG_ESPERANTO} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_ESPERANTO} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_ESPERANTO} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_ESPERANTO} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_ESPERANTO} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_ESPERANTO} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_ESPERANTO} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_ESPERANTO} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_ESPERANTO} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_ESPERANTO} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_ESPERANTO} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_ESPERANTO} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_ESPERANTO} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_ESPERANTO} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_ESPERANTO} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_ESPERANTO} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_ESPERANTO} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_ESPERANTO} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_ESPERANTO} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_ESPERANTO} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_ESPERANTO} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_ESPERANTO} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_ESPERANTO} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_ESPERANTO} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_ESPERANTO} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_ESPERANTO} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_ESPERANTO} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_ESTONIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_ESTONIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_ESTONIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_ESTONIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_ESTONIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_ESTONIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_ESTONIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_ESTONIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_ESTONIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_ESTONIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_ESTONIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_ESTONIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_ESTONIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_ESTONIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_ESTONIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_ESTONIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_ESTONIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_ESTONIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_ESTONIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_ESTONIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_ESTONIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_ESTONIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_ESTONIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_ESTONIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_ESTONIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_ESTONIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_ESTONIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_ESTONIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_FARSI + LangString MULTIUSER_PAGE_TITLE ${LANG_FARSI} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_FARSI} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_FARSI} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_FARSI} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_FARSI} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_FARSI} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_FARSI} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_FARSI} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_FARSI} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_FARSI} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_FARSI} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_FARSI} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_FARSI} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_FARSI} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_FARSI} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_FARSI} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_FARSI} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_FARSI} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_FARSI} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_FARSI} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_FARSI} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_FARSI} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_FARSI} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_FARSI} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_FARSI} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_FARSI} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_FARSI} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_FINNISH + LangString MULTIUSER_PAGE_TITLE ${LANG_FINNISH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_FINNISH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_FINNISH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_FINNISH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_FINNISH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_FINNISH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_FINNISH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_FINNISH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_FINNISH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_FINNISH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_FINNISH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_FINNISH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_FINNISH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_FINNISH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_FINNISH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_FINNISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_FINNISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_FINNISH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_FINNISH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_FINNISH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_FINNISH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_FINNISH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_FINNISH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_FINNISH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_FINNISH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_FINNISH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_FINNISH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_FRENCH + LangString MULTIUSER_PAGE_TITLE ${LANG_FRENCH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_FRENCH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_FRENCH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_FRENCH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_FRENCH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_FRENCH} "For &anyone who uses this computer (all users)$\r$\n(Note: Le programme d'installation redémarrera avec des privilèges élevés)" + LangString MULTIUSER_CURRENT_USER ${LANG_FRENCH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_FRENCH} "For anyone who uses this computer (all users)$\r$\n(Note: Le programme d'installation redémarrera avec des privilèges élevés)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_FRENCH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_FRENCH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_FRENCH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_FRENCH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_FRENCH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_FRENCH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_FRENCH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_FRENCH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_FRENCH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_FRENCH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_FRENCH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_FRENCH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_FRENCH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_FRENCH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_FRENCH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_FRENCH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_FRENCH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_FRENCH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_FRENCH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_GALICIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_GALICIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_GALICIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_GALICIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_GALICIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_GALICIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_GALICIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_GALICIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_GALICIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_GALICIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_GALICIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_GALICIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_GALICIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_GALICIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_GALICIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_GALICIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_GALICIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_GALICIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_GALICIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_GALICIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_GALICIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_GALICIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_GALICIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_GALICIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_GALICIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_GALICIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_GALICIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_GALICIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_GEORGIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_GEORGIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_GEORGIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_GEORGIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_GEORGIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_GEORGIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_GEORGIAN} "For anyone who uses this computer (all users)" + LangString MULTIUSER_ALL_USERS ${LANG_GEORGIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_GEORGIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_GEORGIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_GEORGIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_GEORGIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_GEORGIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_GEORGIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_GEORGIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_GEORGIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_GEORGIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_GEORGIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_GEORGIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_GEORGIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_GEORGIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_GEORGIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_GEORGIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_GEORGIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_GEORGIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_GEORGIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_GEORGIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_GEORGIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_GERMAN + ; Translation on 2019-03-29 done by: https://github.com/Tobias-B-Besemer + LangString MULTIUSER_PAGE_TITLE ${LANG_GERMAN} "Wähle Benutzer" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_GERMAN} "Wähle für welche Benutzer $(^NameDA) installiert werden soll." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_GERMAN} "Wähle für welche Benutzer $(^NameDA) entfernt werden soll." + LangString MULTIUSER_INSTALL_HEADER ${LANG_GERMAN} "Selektiere entweder $(^NameDA) zu installieren für alle Benutzer, oder für aktuellen Benutzer." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_GERMAN} "$(^NameDA) ist installiert für beides, für alle Benutzer und für aktuellen Benutzer.$\r$\nWähle, welche Installation entfernt werden soll." + LangString MULTIUSER_ALL_USERS ${LANG_GERMAN} "Für &jeden, der diesen Computer benutzt (alle Benutzer)$\r$\n(Hinweis: Das Installationsprogramm wird mit erhöhten Rechten neu gestartet)" + LangString MULTIUSER_CURRENT_USER ${LANG_GERMAN} "Für &mich ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_GERMAN} "Für jeden, der diesen Computer benutzt (alle Benutzer)$\r$\n(Hinweis: Das Installationsprogramm wird mit erhöhten Rechten neu gestartet)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_GERMAN} "Für mich ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_GERMAN} "Frische Installation für alle Benutzer." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_GERMAN} "Frische Installation für aktuellen Benutzer." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_GERMAN} "Version {VERSION} ist installiert für alle Benutzer in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_GERMAN} "Version {VERSION} ist installiert für aktuellen Benutzer in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_GERMAN} "Neuinstallation Version {VERSION} für alle Benutzer." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_GERMAN} "Neuinstallation Version {VERSION} für aktuellen Benutzer." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_GERMAN} "Deinstallation Version {OLD_VERSION} und Installation Version {VERSION} für alle Benutzer." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_GERMAN} "Deinstallation Version {OLD_VERSION} und Installation Version {VERSION} für aktuellen Benutzer." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_GERMAN} "Du musst dieses Programm als Administrator ausführen." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_GERMAN} "Administrator Anmeldedaten benötigt." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_GERMAN} "Administrator Anmeldedaten benötigt für Deinstallation." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_GERMAN} "Unzulässige Kombination von Parametern." + LangString MULTIUSER_NOT_INSTALLED ${LANG_GERMAN} "Dort ist keine Installation von $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_GERMAN} "Das Betriebssystem unterstützt nicht Aktuelle-Benutzer-Installationen." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_GERMAN} "Du musst Dich mit einem Konto, dass ein Mitglied der Administratoren Gruppe ist, anmelden um fortzufahren." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_GERMAN} "Das Betriebssystem unterstützt nicht Hochstuffung." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_GERMAN} "Hochstuffung nicht möglich, zweiter Anmelde-Service läuft nicht." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_GERMAN} "Hochstuffung nicht möglich, Fehler {ERROR}." +!endif + +!ifdef LANG_GREEK + LangString MULTIUSER_PAGE_TITLE ${LANG_GREEK} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_GREEK} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_GREEK} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_GREEK} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_GREEK} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_GREEK} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_GREEK} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_GREEK} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_GREEK} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_GREEK} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_GREEK} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_GREEK} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_GREEK} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_GREEK} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_GREEK} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_GREEK} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_GREEK} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_GREEK} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_GREEK} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_GREEK} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_GREEK} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_GREEK} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_GREEK} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_GREEK} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_GREEK} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_GREEK} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_GREEK} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_HEBREW + LangString MULTIUSER_PAGE_TITLE ${LANG_HEBREW} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_HEBREW} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_HEBREW} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_HEBREW} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_HEBREW} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_HEBREW} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_HEBREW} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_HEBREW} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_HEBREW} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_HEBREW} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_HEBREW} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_HEBREW} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_HEBREW} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_HEBREW} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_HEBREW} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_HEBREW} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_HEBREW} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_HEBREW} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_HEBREW} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_HEBREW} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_HEBREW} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_HEBREW} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_HEBREW} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_HEBREW} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_HEBREW} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_HEBREW} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_HEBREW} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_HUNGARIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_HUNGARIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_HUNGARIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_HUNGARIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_HUNGARIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_HUNGARIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_HUNGARIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_HUNGARIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_HUNGARIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_HUNGARIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_HUNGARIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_HUNGARIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_HUNGARIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_HUNGARIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_HUNGARIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_HUNGARIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_HUNGARIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_HUNGARIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_HUNGARIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_HUNGARIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_HUNGARIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_HUNGARIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_HUNGARIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_HUNGARIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_HUNGARIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_HUNGARIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_ICELANDIC + LangString MULTIUSER_PAGE_TITLE ${LANG_ICELANDIC} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_ICELANDIC} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_ICELANDIC} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_ICELANDIC} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_ICELANDIC} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_ICELANDIC} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_ICELANDIC} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_ICELANDIC} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_ICELANDIC} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_ICELANDIC} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_ICELANDIC} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_ICELANDIC} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_ICELANDIC} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_ICELANDIC} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_ICELANDIC} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_ICELANDIC} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_ICELANDIC} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_ICELANDIC} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_ICELANDIC} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_ICELANDIC} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_ICELANDIC} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_ICELANDIC} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_ICELANDIC} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_ICELANDIC} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_ICELANDIC} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_ICELANDIC} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_ICELANDIC} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_INDONESIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_INDONESIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_INDONESIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_INDONESIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_INDONESIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_INDONESIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_INDONESIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_INDONESIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_INDONESIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_INDONESIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_INDONESIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_INDONESIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_INDONESIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_INDONESIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_INDONESIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_INDONESIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_INDONESIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_INDONESIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_INDONESIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_INDONESIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_INDONESIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_INDONESIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_INDONESIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_INDONESIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_INDONESIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_INDONESIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_INDONESIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_INDONESIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_IRISH + LangString MULTIUSER_PAGE_TITLE ${LANG_IRISH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_IRISH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_IRISH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_IRISH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_IRISH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_IRISH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_IRISH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_IRISH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_IRISH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_IRISH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_IRISH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_IRISH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_IRISH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_IRISH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_IRISH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_IRISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_IRISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_IRISH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_IRISH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_IRISH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_IRISH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_IRISH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_IRISH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_IRISH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_IRISH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_IRISH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_IRISH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_ITALIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_ITALIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_ITALIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_ITALIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_ITALIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_ITALIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_ITALIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_ITALIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_ITALIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_ITALIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_ITALIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_ITALIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_ITALIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_ITALIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_ITALIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_ITALIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_ITALIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_ITALIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_ITALIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_ITALIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_ITALIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_ITALIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_ITALIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_ITALIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_ITALIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_ITALIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_JAPANESE + LangString MULTIUSER_PAGE_TITLE ${LANG_JAPANESE} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_JAPANESE} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_JAPANESE} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_JAPANESE} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_JAPANESE} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_JAPANESE} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_JAPANESE} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_JAPANESE} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_JAPANESE} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_JAPANESE} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_JAPANESE} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_JAPANESE} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_JAPANESE} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_JAPANESE} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_JAPANESE} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_JAPANESE} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_JAPANESE} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_JAPANESE} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_JAPANESE} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_JAPANESE} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_JAPANESE} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_JAPANESE} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_JAPANESE} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_JAPANESE} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_JAPANESE} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_JAPANESE} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_JAPANESE} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_KOREAN + LangString MULTIUSER_PAGE_TITLE ${LANG_KOREAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_KOREAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_KOREAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_KOREAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_KOREAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_KOREAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_KOREAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_KOREAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_KOREAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_KOREAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_KOREAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_KOREAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_KOREAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_KOREAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_KOREAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_KOREAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_KOREAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_KOREAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_KOREAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_KOREAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_KOREAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_KOREAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_KOREAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_KOREAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_KOREAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_KOREAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_KOREAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_KURDISH + LangString MULTIUSER_PAGE_TITLE ${LANG_KURDISH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_KURDISH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_KURDISH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_KURDISH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_KURDISH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_KURDISH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_KURDISH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_KURDISH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_KURDISH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_KURDISH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_KURDISH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_KURDISH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_KURDISH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_KURDISH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_KURDISH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_KURDISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_KURDISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_KURDISH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_KURDISH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_KURDISH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_KURDISH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_KURDISH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_KURDISH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_KURDISH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_KURDISH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_KURDISH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_KURDISH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_LATVIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_LATVIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_LATVIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_LATVIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_LATVIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_LATVIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_LATVIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_LATVIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_LATVIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_LATVIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_LATVIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_LATVIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_LATVIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_LATVIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_LATVIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_LATVIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_LATVIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_LATVIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_LATVIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_LATVIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_LATVIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_LATVIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_LATVIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_LATVIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_LATVIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_LATVIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_LATVIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_LATVIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_LITHUANIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_LITHUANIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_LITHUANIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_LITHUANIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_LITHUANIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_LITHUANIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_LITHUANIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_LITHUANIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_LITHUANIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_LITHUANIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_LITHUANIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_LITHUANIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_LITHUANIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_LITHUANIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_LITHUANIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_LITHUANIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_LITHUANIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_LITHUANIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_LITHUANIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_LITHUANIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_LITHUANIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_LITHUANIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_LITHUANIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_LITHUANIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_LITHUANIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_LITHUANIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_LITHUANIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_LITHUANIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_LUXEMBOURGISH + LangString MULTIUSER_PAGE_TITLE ${LANG_LUXEMBOURGISH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_LUXEMBOURGISH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_LUXEMBOURGISH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_LUXEMBOURGISH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_LUXEMBOURGISH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_LUXEMBOURGISH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_LUXEMBOURGISH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_LUXEMBOURGISH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_LUXEMBOURGISH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_LUXEMBOURGISH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_LUXEMBOURGISH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_LUXEMBOURGISH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_LUXEMBOURGISH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_LUXEMBOURGISH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_LUXEMBOURGISH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_LUXEMBOURGISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_LUXEMBOURGISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_LUXEMBOURGISH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_LUXEMBOURGISH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_LUXEMBOURGISH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_LUXEMBOURGISH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_LUXEMBOURGISH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_LUXEMBOURGISH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_LUXEMBOURGISH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_LUXEMBOURGISH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_LUXEMBOURGISH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_LUXEMBOURGISH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_MACEDONIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_MACEDONIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_MACEDONIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_MACEDONIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_MACEDONIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_MACEDONIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_MACEDONIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_MACEDONIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_MACEDONIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_MACEDONIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_MACEDONIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_MACEDONIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_MACEDONIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_MACEDONIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_MACEDONIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_MACEDONIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_MACEDONIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_MACEDONIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_MACEDONIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_MACEDONIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_MACEDONIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_MACEDONIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_MACEDONIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_MACEDONIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_MACEDONIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_MACEDONIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_MACEDONIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_MACEDONIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_MALAY + LangString MULTIUSER_PAGE_TITLE ${LANG_MALAY} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_MALAY} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_MALAY} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_MALAY} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_MALAY} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_MALAY} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_MALAY} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_MALAY} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_MALAY} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_MALAY} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_MALAY} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_MALAY} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_MALAY} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_MALAY} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_MALAY} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_MALAY} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_MALAY} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_MALAY} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_MALAY} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_MALAY} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_MALAY} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_MALAY} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_MALAY} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_MALAY} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_MALAY} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_MALAY} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_MALAY} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_MONGOLIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_MONGOLIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_MONGOLIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_MONGOLIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_MONGOLIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_MONGOLIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_MONGOLIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_MONGOLIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_MONGOLIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_MONGOLIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_MONGOLIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_MONGOLIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_MONGOLIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_MONGOLIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_MONGOLIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_MONGOLIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_MONGOLIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_MONGOLIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_MONGOLIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_MONGOLIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_MONGOLIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_MONGOLIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_MONGOLIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_MONGOLIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_MONGOLIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_MONGOLIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_MONGOLIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_MONGOLIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_NORWEGIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_NORWEGIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_NORWEGIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_NORWEGIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_NORWEGIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_NORWEGIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_NORWEGIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_NORWEGIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_NORWEGIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_NORWEGIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_NORWEGIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_NORWEGIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_NORWEGIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_NORWEGIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_NORWEGIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_NORWEGIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_NORWEGIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_NORWEGIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_NORWEGIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_NORWEGIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_NORWEGIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_NORWEGIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_NORWEGIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_NORWEGIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_NORWEGIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_NORWEGIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_NORWEGIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_NORWEGIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_NORWEGIANNYNORSK + LangString MULTIUSER_PAGE_TITLE ${LANG_NORWEGIANNYNORSK} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_NORWEGIANNYNORSK} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_NORWEGIANNYNORSK} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_NORWEGIANNYNORSK} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_NORWEGIANNYNORSK} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_NORWEGIANNYNORSK} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_NORWEGIANNYNORSK} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_NORWEGIANNYNORSK} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_NORWEGIANNYNORSK} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_NORWEGIANNYNORSK} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_NORWEGIANNYNORSK} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_NORWEGIANNYNORSK} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_NORWEGIANNYNORSK} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_NORWEGIANNYNORSK} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_NORWEGIANNYNORSK} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_NORWEGIANNYNORSK} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_NORWEGIANNYNORSK} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_NORWEGIANNYNORSK} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_NORWEGIANNYNORSK} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_NORWEGIANNYNORSK} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_NORWEGIANNYNORSK} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_NORWEGIANNYNORSK} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_NORWEGIANNYNORSK} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_NORWEGIANNYNORSK} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_NORWEGIANNYNORSK} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_NORWEGIANNYNORSK} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_NORWEGIANNYNORSK} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_PASHTO + LangString MULTIUSER_PAGE_TITLE ${LANG_PASHTO} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_PASHTO} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_PASHTO} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_PASHTO} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_PASHTO} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_PASHTO} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_PASHTO} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_PASHTO} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_PASHTO} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_PASHTO} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_PASHTO} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_PASHTO} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_PASHTO} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_PASHTO} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_PASHTO} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_PASHTO} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_PASHTO} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_PASHTO} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_PASHTO} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_PASHTO} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_PASHTO} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_PASHTO} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_PASHTO} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_PASHTO} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_PASHTO} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_PASHTO} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_PASHTO} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_POLISH + LangString MULTIUSER_PAGE_TITLE ${LANG_POLISH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_POLISH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_POLISH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_POLISH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_POLISH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_POLISH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_POLISH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_POLISH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_POLISH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_POLISH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_POLISH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_POLISH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_POLISH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_POLISH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_POLISH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_POLISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_POLISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_POLISH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_POLISH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_POLISH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_POLISH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_POLISH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_POLISH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_POLISH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_POLISH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_POLISH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_POLISH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_PORTUGUESE + LangString MULTIUSER_PAGE_TITLE ${LANG_PORTUGUESE} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_PORTUGUESE} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_PORTUGUESE} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_PORTUGUESE} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_PORTUGUESE} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_PORTUGUESE} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_PORTUGUESE} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_PORTUGUESE} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_PORTUGUESE} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_PORTUGUESE} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_PORTUGUESE} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_PORTUGUESE} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_PORTUGUESE} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_PORTUGUESE} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_PORTUGUESE} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_PORTUGUESE} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_PORTUGUESE} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_PORTUGUESE} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_PORTUGUESE} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_PORTUGUESE} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_PORTUGUESE} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_PORTUGUESE} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_PORTUGUESE} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_PORTUGUESE} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_PORTUGUESE} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_PORTUGUESE} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_PORTUGUESE} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_PORTUGUESEBR + LangString MULTIUSER_PAGE_TITLE ${LANG_PORTUGUESEBR} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_PORTUGUESEBR} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_PORTUGUESEBR} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_PORTUGUESEBR} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_PORTUGUESEBR} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_PORTUGUESEBR} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_PORTUGUESEBR} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_PORTUGUESEBR} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_PORTUGUESEBR} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_PORTUGUESEBR} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_PORTUGUESEBR} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_PORTUGUESEBR} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_PORTUGUESEBR} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_PORTUGUESEBR} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_PORTUGUESEBR} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_PORTUGUESEBR} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_PORTUGUESEBR} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_PORTUGUESEBR} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_PORTUGUESEBR} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_PORTUGUESEBR} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_PORTUGUESEBR} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_PORTUGUESEBR} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_PORTUGUESEBR} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_PORTUGUESEBR} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_PORTUGUESEBR} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_PORTUGUESEBR} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_PORTUGUESEBR} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_ROMANIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_ROMANIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_ROMANIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_ROMANIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_ROMANIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_ROMANIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_ROMANIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_ROMANIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_ROMANIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_ROMANIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_ROMANIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_ROMANIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_ROMANIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_ROMANIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_ROMANIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_ROMANIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_ROMANIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_ROMANIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_ROMANIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_ROMANIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_ROMANIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_ROMANIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_ROMANIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_ROMANIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_ROMANIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_ROMANIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_ROMANIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_ROMANIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_RUSSIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_RUSSIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_RUSSIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_RUSSIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_RUSSIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_RUSSIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_RUSSIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_RUSSIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_RUSSIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_RUSSIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_RUSSIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_RUSSIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_RUSSIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_RUSSIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_RUSSIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_RUSSIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_RUSSIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_RUSSIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_RUSSIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_RUSSIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_RUSSIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_RUSSIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_RUSSIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_RUSSIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_RUSSIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_RUSSIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_RUSSIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_RUSSIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_SCOTSGAELIC + LangString MULTIUSER_PAGE_TITLE ${LANG_SCOTSGAELIC} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_SCOTSGAELIC} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_SCOTSGAELIC} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_SCOTSGAELIC} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_SCOTSGAELIC} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_SCOTSGAELIC} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_SCOTSGAELIC} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_SCOTSGAELIC} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_SCOTSGAELIC} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_SCOTSGAELIC} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_SCOTSGAELIC} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_SCOTSGAELIC} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_SCOTSGAELIC} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_SCOTSGAELIC} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_SCOTSGAELIC} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_SCOTSGAELIC} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_SCOTSGAELIC} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_SCOTSGAELIC} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_SCOTSGAELIC} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_SCOTSGAELIC} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_SCOTSGAELIC} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_SCOTSGAELIC} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_SCOTSGAELIC} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_SCOTSGAELIC} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_SCOTSGAELIC} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_SERBIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_SERBIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_SERBIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_SERBIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_SERBIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_SERBIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_SERBIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_SERBIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_SERBIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_SERBIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_SERBIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_SERBIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_SERBIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_SERBIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_SERBIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_SERBIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_SERBIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_SERBIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_SERBIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_SERBIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_SERBIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_SERBIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_SERBIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_SERBIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_SERBIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_SERBIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_SERBIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_SERBIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_SERBIANLATIN + LangString MULTIUSER_PAGE_TITLE ${LANG_SERBIANLATIN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_SERBIANLATIN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_SERBIANLATIN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_SERBIANLATIN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_SERBIANLATIN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_SERBIANLATIN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_SERBIANLATIN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_SERBIANLATIN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_SERBIANLATIN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_SERBIANLATIN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_SERBIANLATIN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_SERBIANLATIN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_SERBIANLATIN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_SERBIANLATIN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_SERBIANLATIN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_SERBIANLATIN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_SERBIANLATIN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_SERBIANLATIN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_SERBIANLATIN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_SERBIANLATIN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_SERBIANLATIN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_SERBIANLATIN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_SERBIANLATIN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_SERBIANLATIN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_SERBIANLATIN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_SIMPCHINESE + LangString MULTIUSER_PAGE_TITLE ${LANG_SIMPCHINESE} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_SIMPCHINESE} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_SIMPCHINESE} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_SIMPCHINESE} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_SIMPCHINESE} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_SIMPCHINESE} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_SIMPCHINESE} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_SIMPCHINESE} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_SIMPCHINESE} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_SIMPCHINESE} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_SIMPCHINESE} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_SIMPCHINESE} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_SIMPCHINESE} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_SIMPCHINESE} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_SIMPCHINESE} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_SIMPCHINESE} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_SIMPCHINESE} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_SIMPCHINESE} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_SIMPCHINESE} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_SIMPCHINESE} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_SIMPCHINESE} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_SIMPCHINESE} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_SIMPCHINESE} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_SIMPCHINESE} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_SIMPCHINESE} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_SIMPCHINESE} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_SIMPCHINESE} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_SLOVAK + LangString MULTIUSER_PAGE_TITLE ${LANG_SLOVAK} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_SLOVAK} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_SLOVAK} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_SLOVAK} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_SLOVAK} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_SLOVAK} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_SLOVAK} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_SLOVAK} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_SLOVAK} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_SLOVAK} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_SLOVAK} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_SLOVAK} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_SLOVAK} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_SLOVAK} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_SLOVAK} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_SLOVAK} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_SLOVAK} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_SLOVAK} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_SLOVAK} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_SLOVAK} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_SLOVAK} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_SLOVAK} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_SLOVAK} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_SLOVAK} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_SLOVAK} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_SLOVAK} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_SLOVAK} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_SLOVENIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_SLOVENIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_SLOVENIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_SLOVENIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_SLOVENIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_SLOVENIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_SLOVENIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_SLOVENIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_SLOVENIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_SLOVENIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_SLOVENIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_SLOVENIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_SLOVENIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_SLOVENIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_SLOVENIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_SLOVENIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_SLOVENIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_SLOVENIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_SLOVENIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_SLOVENIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_SLOVENIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_SLOVENIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_SLOVENIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_SLOVENIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_SLOVENIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_SLOVENIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_SLOVENIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_SLOVENIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_SPANISH + LangString MULTIUSER_PAGE_TITLE ${LANG_SPANISH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_SPANISH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_SPANISH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_SPANISH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_SPANISH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_SPANISH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_SPANISH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_SPANISH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_SPANISH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_SPANISH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_SPANISH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_SPANISH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_SPANISH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_SPANISH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_SPANISH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_SPANISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_SPANISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_SPANISH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_SPANISH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_SPANISH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_SPANISH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_SPANISH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_SPANISH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_SPANISH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_SPANISH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_SPANISH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_SPANISH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_SPANISHINTERNATIONAL + LangString MULTIUSER_PAGE_TITLE ${LANG_SPANISHINTERNATIONAL} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_SPANISHINTERNATIONAL} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_SPANISHINTERNATIONAL} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_SPANISHINTERNATIONAL} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_SPANISHINTERNATIONAL} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_SPANISHINTERNATIONAL} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_SPANISHINTERNATIONAL} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_SPANISHINTERNATIONAL} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_SPANISHINTERNATIONAL} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_SPANISHINTERNATIONAL} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_SPANISHINTERNATIONAL} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_SPANISHINTERNATIONAL} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_SPANISHINTERNATIONAL} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_SPANISHINTERNATIONAL} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_SPANISHINTERNATIONAL} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_SPANISHINTERNATIONAL} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_SPANISHINTERNATIONAL} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_SPANISHINTERNATIONAL} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_SPANISHINTERNATIONAL} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_SPANISHINTERNATIONAL} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_SPANISHINTERNATIONAL} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_SPANISHINTERNATIONAL} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_SPANISHINTERNATIONAL} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_SPANISHINTERNATIONAL} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_SPANISHINTERNATIONAL} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_SPANISHINTERNATIONAL} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_SPANISHINTERNATIONAL} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_SWEDISH + LangString MULTIUSER_PAGE_TITLE ${LANG_SWEDISH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_SWEDISH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_SWEDISH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_SWEDISH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_SWEDISH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_SWEDISH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_SWEDISH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_SWEDISH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_SWEDISH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_SWEDISH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_SWEDISH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_SWEDISH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_SWEDISH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_SWEDISH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_SWEDISH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_SWEDISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_SWEDISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_SWEDISH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_SWEDISH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_SWEDISH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_SWEDISH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_SWEDISH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_SWEDISH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_SWEDISH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_SWEDISH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_SWEDISH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_SWEDISH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_TATAR + LangString MULTIUSER_PAGE_TITLE ${LANG_TATAR} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_TATAR} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_TATAR} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_TATAR} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_TATAR} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_TATAR} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_TATAR} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_TATAR} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_TATAR} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_TATAR} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_TATAR} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_TATAR} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_TATAR} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_TATAR} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_TATAR} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_TATAR} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_TATAR} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_TATAR} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_TATAR} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_TATAR} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_TATAR} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_TATAR} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_TATAR} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_TATAR} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_TATAR} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_TATAR} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_TATAR} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_THAI + LangString MULTIUSER_PAGE_TITLE ${LANG_THAI} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_THAI} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_THAI} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_THAI} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_THAI} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_THAI} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_THAI} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_THAI} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_THAI} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_THAI} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_THAI} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_THAI} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_THAI} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_THAI} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_THAI} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_THAI} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_THAI} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_THAI} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_THAI} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_THAI} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_THAI} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_THAI} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_THAI} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_THAI} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_THAI} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_THAI} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_THAI} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_TRADCHINESE + LangString MULTIUSER_PAGE_TITLE ${LANG_TRADCHINESE} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_TRADCHINESE} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_TRADCHINESE} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_TRADCHINESE} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_TRADCHINESE} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_TRADCHINESE} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_TRADCHINESE} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_TRADCHINESE} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_TRADCHINESE} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_TRADCHINESE} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_TRADCHINESE} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_TRADCHINESE} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_TRADCHINESE} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_TRADCHINESE} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_TRADCHINESE} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_TRADCHINESE} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_TRADCHINESE} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_TRADCHINESE} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_TRADCHINESE} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_TRADCHINESE} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_TRADCHINESE} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_TRADCHINESE} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_TRADCHINESE} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_TRADCHINESE} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_TRADCHINESE} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_TRADCHINESE} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_TRADCHINESE} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_TURKISH + LangString MULTIUSER_PAGE_TITLE ${LANG_TURKISH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_TURKISH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_TURKISH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_TURKISH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_TURKISH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_TURKISH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_TURKISH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_TURKISH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_TURKISH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_TURKISH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_TURKISH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_TURKISH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_TURKISH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_TURKISH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_TURKISH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_TURKISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_TURKISH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_TURKISH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_TURKISH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_TURKISH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_TURKISH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_TURKISH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_TURKISH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_TURKISH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_TURKISH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_TURKISH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_TURKISH} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_UKRAINIAN + LangString MULTIUSER_PAGE_TITLE ${LANG_UKRAINIAN} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_UKRAINIAN} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_UKRAINIAN} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_UKRAINIAN} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_UKRAINIAN} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_UKRAINIAN} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_UKRAINIAN} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_UKRAINIAN} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_UKRAINIAN} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_UKRAINIAN} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_UKRAINIAN} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_UKRAINIAN} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_UKRAINIAN} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_UKRAINIAN} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_UKRAINIAN} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_UKRAINIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_UKRAINIAN} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_UKRAINIAN} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_UKRAINIAN} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_UKRAINIAN} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_UKRAINIAN} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_UKRAINIAN} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_UKRAINIAN} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_UKRAINIAN} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_UKRAINIAN} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_UKRAINIAN} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_UKRAINIAN} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_UZBEK + LangString MULTIUSER_PAGE_TITLE ${LANG_UZBEK} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_UZBEK} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_UZBEK} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_UZBEK} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_UZBEK} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_UZBEK} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_UZBEK} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_UZBEK} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_UZBEK} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_UZBEK} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_UZBEK} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_UZBEK} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_UZBEK} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_UZBEK} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_UZBEK} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_UZBEK} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_UZBEK} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_UZBEK} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_UZBEK} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_UZBEK} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_UZBEK} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_UZBEK} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_UZBEK} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_UZBEK} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_UZBEK} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_UZBEK} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_UZBEK} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_VIETNAMESE + LangString MULTIUSER_PAGE_TITLE ${LANG_VIETNAMESE} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_VIETNAMESE} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_VIETNAMESE} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_VIETNAMESE} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_VIETNAMESE} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_VIETNAMESE} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_VIETNAMESE} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_VIETNAMESE} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_VIETNAMESE} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_VIETNAMESE} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_VIETNAMESE} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_VIETNAMESE} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_VIETNAMESE} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_VIETNAMESE} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_VIETNAMESE} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_VIETNAMESE} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_VIETNAMESE} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_VIETNAMESE} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_VIETNAMESE} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_VIETNAMESE} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_VIETNAMESE} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_VIETNAMESE} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_VIETNAMESE} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_VIETNAMESE} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_VIETNAMESE} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_VIETNAMESE} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_VIETNAMESE} "Unable to elevate, error {ERROR}." +!endif + +!ifdef LANG_WELSH + LangString MULTIUSER_PAGE_TITLE ${LANG_WELSH} "Choose Users" + LangString MULTIUSER_INSTALL_PAGE_SUBTITLE ${LANG_WELSH} "Choose for which users to install $(^NameDA)." + LangString MULTIUSER_UNINSTALL_PAGE_SUBTITLE ${LANG_WELSH} "Choose for which users to remove $(^NameDA)." + LangString MULTIUSER_INSTALL_HEADER ${LANG_WELSH} "Select whether to install $(^NameDA) for all users or for current user." + LangString MULTIUSER_UNINSTALL_HEADER ${LANG_WELSH} "$(^NameDA) is installed both for all users and for current user.$\r$\nSelect which installation to remove." + LangString MULTIUSER_ALL_USERS ${LANG_WELSH} "For &anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER ${LANG_WELSH} "For &me ({USER})" + LangString MULTIUSER_ALL_USERS_UMUI ${LANG_WELSH} "For anyone who uses this computer (all users)$\r$\n(Note: The installer will restart with elevated privileges)" + LangString MULTIUSER_CURRENT_USER_UMUI ${LANG_WELSH} "For me ({USER})" + LangString MULTIUSER_NEW_INSTALLATION_ALL_USERS ${LANG_WELSH} "Fresh install for all users." + LangString MULTIUSER_NEW_INSTALLATION_CURRENT_USER ${LANG_WELSH} "Fresh install for current user." + LangString MULTIUSER_INSTALLED_ALL_USERS ${LANG_WELSH} "Version {VERSION} is installed for all users in $\"{FOLDER}$\"." + LangString MULTIUSER_INSTALLED_CURRENT_USER ${LANG_WELSH} "Version {VERSION} is installed for current user in $\"{FOLDER}$\"." + LangString MULTIUSER_REINSTALL_SAME_VERSION_ALL_USERS ${LANG_WELSH} "Reinstall version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_SAME_VERSION_CURRENT_USER ${LANG_WELSH} "Reinstall version {VERSION} for current user." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_ALL_USERS ${LANG_WELSH} "Uninstall version {OLD_VERSION} and install version {VERSION} for all users." + LangString MULTIUSER_REINSTALL_DIFF_VERSION_CURRENT_USER ${LANG_WELSH} "Uninstall version {OLD_VERSION} and install version {VERSION} for current user." + LangString MULTIUSER_RUN_AS_ADMIN ${LANG_WELSH} "You need to run this program as administrator." + LangString MULTIUSER_ADMIN_CREDENTIALS_REQUIRED ${LANG_WELSH} "Administrator credentials required." + LangString MULTIUSER_ADMIN_UNINSTALL_CREDENTIALS_REQUIRED ${LANG_WELSH} "Administrator credentials required for uninstall." + ; error messages - not so important + LangString MULTIUSER_INVALID_PARAMS ${LANG_WELSH} "Invalid combination of paramaters." + LangString MULTIUSER_NOT_INSTALLED ${LANG_WELSH} "There is no installation of $(^NameDA)." + LangString MULTIUSER_INSTALLATION_FOR_CURRENT_USER_NOT_SUPPORTED ${LANG_WELSH} "The operating system doesn't support current user installations." + LangString MULTIUSER_ADMIN_ACCOUNT_LOGIN_REQUIRED ${LANG_WELSH} "You need to login with an account that is a member of the administrators group to continue." + LangString MULTIUSER_ELEVATION_NOT_SUPPORTED ${LANG_WELSH} "The operating system doesn't support elevation." + LangString MULTIUSER_LOGON_SERVICE_NOT_RUNNING ${LANG_WELSH} "Unable to elevate, Secondary Logon service not running." + LangString MULTIUSER_ELEVATION_ERROR ${LANG_WELSH} "Unable to elevate, error {ERROR}." +!endif diff --git a/cmake/Windows/Include/UAC.nsh b/cmake/Windows/Include/UAC.nsh new file mode 100644 index 000000000..08979aba9 --- /dev/null +++ b/cmake/Windows/Include/UAC.nsh @@ -0,0 +1,299 @@ +/*** UAC Plug-in *** + +Interactive User (MediumIL) Admin user (HighIL) +***[Setup.exe]************* ***[Setup.exe]************** +* * * * +* +++[.OnInit]+++++++++++ * * +++[.OnInit]++++++++++++ * +* + UAC_RunElevated >---+-+----> * + + * +* + NSIS.Quit + * * + + * +* +++++++++++++++++++++++ * * ++++++++++++++++++++++++ * +* * * * +* * * * +* +++[Section]+++++++++++ * * +++[Section]++++++++++++ * +* + + * /--+-+- +** +** Get integrity level of current process +** +**/ +!macro UAC_GetIntegrityLevel outvar +UAC::_ 6 +!if "${outvar}" != "s" + Pop ${outvar} +!endif +!macroend + + + +/* UAC_IsAdmin +** +** Is the current process running with administrator privileges? Result in $0 +** +** ${If} ${UAC_IsAdmin} ... +** +**/ +!macro UAC_IsAdmin +UAC::_ 2 +!macroend +!define UAC_IsAdmin `"" UAC_IsAdmin ""` +!macro _UAC_IsAdmin _a _b _t _f +!insertmacro _UAC_MakeLL_Cmp _!= 0 2s +!macroend + + + +/* UAC_IsInnerInstance +** +** Does the current process have a NSIS/UAC parent process that is part of the elevation operation? +** +** ${If} ${UAC_IsInnerInstance} ... +** +**/ +!macro UAC_IsInnerInstance +UAC::_ 3 +!macroend +!define UAC_IsInnerInstance `"" UAC_IsInnerInstance ""` +!macro _UAC_IsInnerInstance _a _b _t _f +!insertmacro _UAC_MakeLL_Cmp _!= 0 3s +!macroend + + + +/* UAC_PageElevation_OnInit, UAC_PageElevation_OnGuiInit, +** +** Helper macros for elevation on a custom elevation page, see the DualMode example for more information. +** +**/ +!macro UAC_Notify_OnGuiInit +UAC::_ 4 +!macroend +!macro UAC_PageElevation_OnGuiInit +!insertmacro UAC_Notify_OnGuiInit +!macroend +!macro UAC_PageElevation_OnInit +UAC::_ 5 +${IfThen} ${Errors} ${|} Quit ${|} +!macroend + + + +/* UAC_AsUser_Call +** +** Calls a function or label in the user process instance. +** All the UAC_AsUser_* macros use this helper macro. +** +**/ +!define UAC_SYNCREGISTERS 0x1 +;define UAC_SYNCSTACK 0x2 +!define UAC_SYNCOUTDIR 0x4 +!define UAC_SYNCINSTDIR 0x8 +;define UAC_CLEARERRFLAG 0x10 +!macro UAC_AsUser_Call type name flags +push $0 +Get${type}Address $0 ${name} +!verbose push +!verbose ${UAC_VERBOSE} +!insertmacro _UAC_ParseDefineFlagsToInt _UAC_AsUser_Call__flags ${flags} +!verbose pop +StrCpy $0 "1$0:${_UAC_AsUser_Call__flags}" +!undef _UAC_AsUser_Call__flags +Exch $0 +UAC::_ +!macroend + + + +/* +** UAC_AsUser_GetSection +*/ +!macro UAC_AsUser_GetSection secprop secidx outvar +!insertmacro _UAC_AsUser_GenOp ${outvar} SectionGet${secprop} ${secidx} "" +!macroend + + + +/* +** UAC_AsUser_GetGlobalVar +** UAC_AsUser_GetGlobal +*/ +!macro UAC_AsUser_GetGlobalVar var +!insertmacro _UAC_AsUser_GenOp ${var} StrCpy "" ${var} +!macroend +!macro UAC_AsUser_GetGlobal outvar srcvar +!insertmacro _UAC_AsUser_GenOp ${outvar} StrCpy "" ${srcvar} +!macroend + + + +/* +** UAC_AsUser_ExecShell +** +** Call ExecShell in the user process instance. +** +*/ +!macro UAC_AsUser_ExecShell verb command params workdir show +!insertmacro _UAC_IncL +goto _UAC_L_E_${__UAC_L} +_UAC_L_F_${__UAC_L}: +ExecShell "${verb}" "${command}" '${params}' ${show} +return +_UAC_L_E_${__UAC_L}: +!if "${workdir}" != "" + push $outdir + SetOutPath "${workdir}" +!endif +!insertmacro UAC_AsUser_Call Label _UAC_L_F_${__UAC_L} ${UAC_SYNCREGISTERS}|${UAC_SYNCOUTDIR}|${UAC_SYNCINSTDIR} #|${UAC_CLEARERRFLAG} +!if "${workdir}" != "" + pop $outdir + SetOutPath $outdir +!endif +!macroend + + + +!macro _UAC_MakeLL_Cmp cmpop cmp pluginparams +!insertmacro _LOGICLIB_TEMP +UAC::_ ${pluginparams} +pop $_LOGICLIB_TEMP +!insertmacro ${cmpop} $_LOGICLIB_TEMP ${cmp} `${_t}` `${_f}` +!macroend +!macro _UAC_definemath def val1 op val2 +!define /math _UAC_definemath "${val1}" ${op} ${val2} +!ifdef ${def} + !undef ${def} +!endif +!define ${def} "${_UAC_definemath}" +!undef _UAC_definemath +!macroend +!macro _UAC_ParseDefineFlags_orin parse outflags +!searchparse /noerrors ${${parse}} "" _UAC_ParseDefineFlags_orin_f1 "|" _UAC_ParseDefineFlags_orin_f2 +!define _UAC_ParseDefineFlags_orin_this ${_UAC_ParseDefineFlags_orin_f1} +!undef ${parse} +!define ${parse} ${_UAC_ParseDefineFlags_orin_f2} +!define _UAC_ParseDefineFlags_orin_saveout ${${outflags}} +!undef ${outflags} +!define /math ${outflags} "${_UAC_ParseDefineFlags_orin_saveout}" | "${_UAC_ParseDefineFlags_orin_this}" +!undef _UAC_ParseDefineFlags_orin_saveout +!undef _UAC_ParseDefineFlags_orin_this +!ifdef _UAC_ParseDefineFlags_orin_f1 + !undef _UAC_ParseDefineFlags_orin_f1 +!endif +!ifdef _UAC_ParseDefineFlags_orin_f2 + !undef _UAC_ParseDefineFlags_orin_f2 +!endif +!macroend +!macro _UAC_ParseDefineFlags_Begin _outdef _in +!define _UAC_PDF${_outdef}_parse "${_in}" +!define _UAC_PDF${_outdef}_flags "" +!define _UAC_PDF${_outdef}_r 0 +!insertmacro _UAC_ParseDefineFlags_orin _UAC_PDF${_outdef}_parse _UAC_PDF${_outdef}_flags ;0x1 +!insertmacro _UAC_ParseDefineFlags_orin _UAC_PDF${_outdef}_parse _UAC_PDF${_outdef}_flags ;0x2 +!insertmacro _UAC_ParseDefineFlags_orin _UAC_PDF${_outdef}_parse _UAC_PDF${_outdef}_flags ;0x4 +!insertmacro _UAC_ParseDefineFlags_orin _UAC_PDF${_outdef}_parse _UAC_PDF${_outdef}_flags ;0x8 +!insertmacro _UAC_ParseDefineFlags_orin _UAC_PDF${_outdef}_parse _UAC_PDF${_outdef}_flags ;0x10 +!macroend +!macro _UAC_ParseDefineFlags_End _outdef +!define ${_outdef} ${_UAC_PDF${_outdef}_r} +!undef _UAC_PDF${_outdef}_r +!undef _UAC_PDF${_outdef}_flags +!undef _UAC_PDF${_outdef}_parse +!macroend +!macro _UAC_ParseDefineFlags_IncludeFlag _outdef flag +!if ${_UAC_PDF${_outdef}_flags} & ${flag} + !insertmacro _UAC_definemath _UAC_PDF${_outdef}_r ${_UAC_PDF${_outdef}_r} | ${flag} +!endif +!macroend +!macro _UAC_ParseDefineFlagsToInt _outdef _in +!insertmacro _UAC_ParseDefineFlags_Begin _UAC_ParseDefineFlagsToInt_tmp "${_in}" +!define ${_outdef} ${_UAC_PDF_UAC_ParseDefineFlagsToInt_tmp_flags} +!insertmacro _UAC_ParseDefineFlags_End _UAC_ParseDefineFlagsToInt_tmp +!undef _UAC_ParseDefineFlagsToInt_tmp +!macroend +!macro _UAC_IncL +!insertmacro _UAC_definemath __UAC_L "${__UAC_L}" + 1 +!macroend +!macro _UAC_AsUser_GenOp outvar op opparam1 opparam2 +!define _UAC_AUGOGR_ID _UAC_AUGOGR_OP${outvar}${op}${opparam1}${opparam2} +!ifndef ${_UAC_AUGOGR_ID} ;Has this exact action been done before? + !if ${outvar} == $0 + !define ${_UAC_AUGOGR_ID} $1 + !else + !define ${_UAC_AUGOGR_ID} $0 + !endif + !if "${opparam1}" == "" + !define _UAC_AUGOGR_OPP1 ${${_UAC_AUGOGR_ID}} + !define _UAC_AUGOGR_OPP2 ${opparam2} + !else + !define _UAC_AUGOGR_OPP1 ${opparam1} + !define _UAC_AUGOGR_OPP2 ${${_UAC_AUGOGR_ID}} + !endif + goto ${_UAC_AUGOGR_ID}_C + ${_UAC_AUGOGR_ID}_F: + ${op} ${_UAC_AUGOGR_OPP1} ${_UAC_AUGOGR_OPP2} + return + ${_UAC_AUGOGR_ID}_C: + !undef _UAC_AUGOGR_OPP1 + !undef _UAC_AUGOGR_OPP2 +!endif +push ${${_UAC_AUGOGR_ID}} +!insertmacro UAC_AsUser_Call Label ${_UAC_AUGOGR_ID}_F ${UAC_SYNCREGISTERS} +StrCpy ${outvar} ${${_UAC_AUGOGR_ID}} +pop ${${_UAC_AUGOGR_ID}} +!undef _UAC_AUGOGR_ID +!macroend + + + +!verbose pop +!endif /* UAC_HDR__INC */ \ No newline at end of file diff --git a/cmake/Windows/NSIS.template.in b/cmake/Windows/NSIS.template.in new file mode 100644 index 000000000..7c5d3eabd --- /dev/null +++ b/cmake/Windows/NSIS.template.in @@ -0,0 +1,475 @@ +############################################################################## +## Windows installer of the OpenMS GUI (TOPPView, TOPPAS, INIFileEditor, the +## GUI TOPP tools and Qt). +## +## It is an add-on to the installation of the OpenMS core of the same version: +## it reads the folder and the version of that installation from the registry +## (written by the core installer through NsisMultiUser), installs into the same +## folder, in the same mode (all users or current user), and refuses to run +## without it. It keeps its own uninstall log and uninstaller +## (uninstall-gui.exe) and its own entry in the list of installed programs, so +## that it removes exactly the files it installed. The core installer runs +## uninstall-gui.exe first when it is uninstalled. +## +## CPack configures this file (cmake/package_nsis.cmake): @CPACK_...@ references +## are replaced at packaging time, and the files come from the CPack staging +## tree, which holds the GUI layer only. +############################################################################## + +Unicode true +Name "@CPACK_PACKAGE_NAME@" +BrandingText "@CPACK_PACKAGE_FILE_NAME@" +OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@" +SetCompressor /SOLID lzma + +## Long version has to have 4 levels... +!define VERSION_LONG "@CPACK_PACKAGE_VERSION@.0" +## the full version string, e.g. 3.7.0-pre-nightly-2026-11-07; the core installer writes +## the same string as DisplayVersion +!define VERSION "@CPACK_OPENMS_VERSION@" + +!define OPENMSDIRSRC "@CPACK_OPENMS_PROJECT_SOURCE_DIR@" +!define NSISRESOURCES "@CPACK_OPENMS_PROJECT_SOURCE_DIR@/cmake/Windows" + +## The staging tree of this packaging run +!define OPENMSPACKAGEDIR "@CPACK_TEMPORARY_DIRECTORY@" +!define OPENMSBINDIR "@CPACK_TEMPORARY_DIRECTORY@/@CPACK_OPENMS_INSTALL_BIN_DIR@" +!define OPENMSSHAREDIR "@CPACK_TEMPORARY_DIRECTORY@/@CPACK_OPENMS_INSTALL_SHARE_DIR@" +!define OPENMSDOCDIR "@CPACK_TEMPORARY_DIRECTORY@/@CPACK_OPENMS_INSTALL_DOC_DIR@" +!define OPENMSINCLUDEDIR "@CPACK_TEMPORARY_DIRECTORY@/@CPACK_OPENMS_INSTALL_INCLUDE_DIR@" +!define OPENMSCMAKEDIR "@CPACK_TEMPORARY_DIRECTORY@/@CPACK_OPENMS_INSTALL_CMAKE_DIR@" + +!addplugindir "${NSISRESOURCES}/Plugins/x86-unicode" +!addincludedir "${NSISRESOURCES}/Include" + +################## +### SCRIPT ### +################## + +# which extensions to connect to TOPPView and TOPPAS +!macro OpenMSGUIExtensions _action + !insertmacro ${_action} ".mzData" "TOPPView" + !insertmacro ${_action} ".mzXML" "TOPPView" + !insertmacro ${_action} ".mzML" "TOPPView" + !insertmacro ${_action} ".sqMass" "TOPPView" + !insertmacro ${_action} ".dta" "TOPPView" + !insertmacro ${_action} ".dta2D" "TOPPView" + !insertmacro ${_action} ".cdf" "TOPPView" + !insertmacro ${_action} ".idXML" "TOPPView" + !insertmacro ${_action} ".featureXML" "TOPPView" + !insertmacro ${_action} ".consensusXML" "TOPPView" + !insertmacro ${_action} ".toppas" "TOPPAS" +!macroend + +## The installation of the OpenMS core: the uninstall key NsisMultiUser writes for it, under +## HKLM (all users) or HKCU (current user). InstallLocation is in this key, DisplayVersion in +## it for an all-users installation and in " (current user)" for a current-user one. +!define CORE_PRODUCT_NAME "OpenMS" +!define CORE_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${CORE_PRODUCT_NAME}" +## The folder of the core's Start menu entries +!define SMFOLDER "OpenMS" + +!define APPNAME "OpenMS-GUI" +!define PRODUCT_NAME "${APPNAME}" +# For the icon used by MultiUser plugin, relative to INSTDIR +!define PROGEXE "bin\TOPPView.exe" +# Uninstall key of this installer (NsisMultiUser and AdvUninstLog) +!define PRODUCT_UNINST_ROOT SHCTX +!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" +!define COMPANY "OpenMS Developer Team" +!define COMPANY_NAME "${COMPANY}" +!define URL "https://www.OpenMS.de" +!define URL_INFO_ABOUT "${URL}" + +# Own uninstall log and uninstaller: the core's (Uninstall.dat, uninstall.exe) are in the same folder +!define UNINSTALL_LOG "Uninstall-GUI" +!define UNINSTALL_FILENAME "uninstall-gui.exe" + +# MultiUser defines +!define MULTIUSER_INSTALLMODE_INSTDIR "OpenMS-${VERSION}" # only a fallback: .onInit sets INSTDIR to the core's folder +!define MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 1 +!define MULTIUSER_INSTALLMODE_64_BIT 1 +!define MULTIUSER_INSTALLMODE_NO_HELP_DIALOG 1 # We define our own with additional params later + +!include MUI2.nsh +!include Sections.nsh +!include FileFunc.nsh +!include LogicLib.nsh + +## NsisMultiUser uses StrRep in the installer and in the uninstaller; newer NSIS versions +## need the functions declared before. +!include StrFunc.nsh +${StrRep} +${UnStrRep} + +# Private includes from repo +!include IncludeScript_Misc.nsh +!include AdvUninstLog2.nsh +!include NsisMultiUser.nsh + +# For AdvUninstalllog (alternative INTERACTIVE_UNINSTALL will trigger message boxes for each file) +!insertmacro UNATTENDED_UNINSTALL + +;-------------------------------- +;Interface Configuration + +!define MUI_ICON "${NSISRESOURCES}\OpenMS.ico" +!define MUI_UNICON "${NSISRESOURCES}\OpenMS.ico" +!define MUI_HEADERIMAGE +!define MUI_HEADERIMAGE_BITMAP "${NSISRESOURCES}\images\header.bmp" +!define MUI_HEADERIMAGE_UNBITMAP "${NSISRESOURCES}\images\header-uninstall.bmp" +!define MUI_HEADERIMAGE_RIGHT +!define MUI_ABORTWARNING +!define MUI_WELCOMEFINISHPAGE_BITMAP "${NSISRESOURCES}\images\wizard.bmp" +!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${NSISRESOURCES}\images\wizard-uninstall.bmp" + +!insertmacro RefreshShellIcons +!insertmacro un.RefreshShellIcons + +# UI settings +!define MUI_FINISHPAGE_NOAUTOCLOSE +!define MUI_LICENSEPAGE_RADIOBUTTONS +!define MUI_UNFINISHPAGE_NOAUTOCLOSE + +# Remember the installer language +!define MUI_LANGDLL_REGISTRY_ROOT SHCTX +!define MUI_LANGDLL_REGISTRY_KEY "${PRODUCT_UNINST_KEY}" +!define MUI_LANGDLL_REGISTRY_VALUENAME "InstallerLanguage" + +# Installer pages. There is no directory page: the GUI goes into the core's folder. +!define MUI_WELCOMEPAGE_TEXT "This wizard installs the OpenMS GUI ${VERSION} (TOPPView, TOPPAS, INIFileEditor and the TOPP tools that need the GUI library) into the installation of OpenMS ${VERSION} in$\r$\n$\r$\n$INSTDIR$\r$\n$\r$\nClose all OpenMS applications before continuing.$\r$\n$\r$\n$_CLICK" +!insertmacro MUI_PAGE_WELCOME +!define MUI_PAGE_CUSTOMFUNCTION_SHOW licpageshow +!insertmacro MUI_PAGE_LICENSE "${OPENMSDIRSRC}\License.txt" +## Required by NsisMultiUser; .onInit chooses the mode of the core installation, so the page +## is never shown. +!insertmacro MULTIUSER_PAGE_INSTALLMODE +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +# Uninstaller pages +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MULTIUSER_UNPAGE_INSTALLMODE +!insertmacro MUI_UNPAGE_INSTFILES + +# Installer languages +!insertmacro MUI_LANGUAGE English +!insertmacro MUI_LANGUAGE German +!insertmacro MUI_LANGUAGE French +!insertmacro MULTIUSER_LANGUAGE_INIT + +# Custom MUI Function to set font of license display page +# to a mono spaced one. +# from http://stackoverflow.com/questions/8424172/nsis-display-license-with-a-monospace-font +Function licpageshow + FindWindow $0 "#32770" "" $HWNDPARENT + CreateFont $1 "Consolas" "8" + GetDlgItem $0 $0 1000 + SendMessage $0 ${WM_SETFONT} $1 1 +FunctionEnd + +# predefined installation modes +InstType "Recommended" #1 +InstType "Minimum" #2 +InstType "Full" #3 + +CRCCheck on +ShowInstDetails hide +ShowUninstDetails hide +VIProductVersion ${VERSION_LONG} +VIAddVersionKey /LANG=${LANG_ENGLISH} ProductName "${PRODUCT_NAME}" +VIAddVersionKey /LANG=${LANG_ENGLISH} ProductVersion "${VERSION}" +VIAddVersionKey /LANG=${LANG_ENGLISH} CompanyName "${COMPANY}" +VIAddVersionKey /LANG=${LANG_ENGLISH} CompanyWebsite "${URL}" +VIAddVersionKey /LANG=${LANG_ENGLISH} FileVersion "${VERSION}" +VIAddVersionKey /LANG=${LANG_ENGLISH} FileDescription "OpenMS GUI installer" +VIAddVersionKey /LANG=${LANG_ENGLISH} LegalCopyright "" + +# Installer sections +Section "GUI applications" SEC_GUI + SectionIn 1 2 3 RO + SetOverwrite on + + # The GUI executables, OpenMS_GUI.dll, Qt with its plugins and qt.conf. + # The import library goes with the development files. + SetOutPath "$INSTDIR\bin" + !insertmacro UNINSTALL.LOG_OPEN_INSTALL + File /r /x "*.lib" "${OPENMSBINDIR}\*.*" + !insertmacro UNINSTALL.LOG_CLOSE_INSTALL + + # The tool registry of the GUI TOPP tools, the style sheet, the Qt licenses and + # THIRD-PARTY-NOTICES-GUI.txt; the icons of the associated file types + SetOutPath "$INSTDIR\share\OpenMS" + !insertmacro UNINSTALL.LOG_OPEN_INSTALL + File /r "${OPENMSSHAREDIR}\*.*" + File "${NSISRESOURCES}\OpenMS_TOPPView.ico" + File "${NSISRESOURCES}\OpenMS_TOPPAS.ico" + !insertmacro UNINSTALL.LOG_CLOSE_INSTALL +SectionEnd + +!if /FileExists "${OPENMSDOCDIR}\html-gui\index.html" +Section "Documentation" SEC_Doc + SectionIn 1 3 + SetOverwrite on + # the folder itself, so that the log has it and the uninstaller removes it + SetOutPath "$INSTDIR\share\doc" + !insertmacro UNINSTALL.LOG_OPEN_INSTALL + File /r "${OPENMSDOCDIR}\html-gui" + !insertmacro UNINSTALL.LOG_CLOSE_INSTALL +SectionEnd +!endif + +!if /FileExists "${OPENMSINCLUDEDIR}\OpenMS\*.*" +Section /o "Development files" SEC_Dev + SectionIn 3 + SetOverwrite on + # headers, import library and CMake package of the GUI library + SetOutPath "$INSTDIR\include" + !insertmacro UNINSTALL.LOG_OPEN_INSTALL + File /r "${OPENMSINCLUDEDIR}\*.*" + !insertmacro UNINSTALL.LOG_CLOSE_INSTALL + !if /FileExists "${OPENMSBINDIR}\*.lib" + SetOutPath "$INSTDIR\bin" + !insertmacro UNINSTALL.LOG_OPEN_INSTALL + File "${OPENMSBINDIR}\*.lib" + !insertmacro UNINSTALL.LOG_CLOSE_INSTALL + !endif + !if /FileExists "${OPENMSCMAKEDIR}\*.cmake" + SetOutPath "$INSTDIR\@CPACK_OPENMS_INSTALL_CMAKE_DIR@" + !insertmacro UNINSTALL.LOG_OPEN_INSTALL + File /r "${OPENMSCMAKEDIR}\*.*" + !insertmacro UNINSTALL.LOG_CLOSE_INSTALL + !endif +SectionEnd +!endif + +# Start menu entries, in the folder of the core's entries +Section "-StartMenu" SEC_StartMenu + CreateDirectory "$SMPROGRAMS\${SMFOLDER}" + # the working directory of the shortcuts + SetOutPath "$INSTDIR\bin" + CreateShortcut "$SMPROGRAMS\${SMFOLDER}\TOPPView.lnk" "$INSTDIR\bin\TOPPView.exe" + CreateShortcut "$SMPROGRAMS\${SMFOLDER}\TOPPAS.lnk" "$INSTDIR\bin\TOPPAS.exe" + CreateShortcut "$SMPROGRAMS\${SMFOLDER}\INIFileEditor.lnk" "$INSTDIR\bin\INIFileEditor.exe" + CreateShortcut "$SMPROGRAMS\${SMFOLDER}\Uninstall OpenMS GUI.lnk" "$INSTDIR\${UNINSTALL_FILENAME}" "/$MultiUser.InstallMode" +SectionEnd + +SectionGroup "Register File Extensions" SEC_RegisterExt + # windows registry info: http://support.microsoft.com/kb/256986 + !insertmacro OpenMSGUIExtensions RegisterExtensionSection + + Section "-RegFileRefreshInternal" SEC_RegFileRefreshInternal + SectionIn 1 3 + ${RefreshShellIcons} + SectionEnd +SectionGroupEnd + +Section "-post" SEC_Post + SetOutPath $INSTDIR + WriteUninstaller "$INSTDIR\${UNINSTALL_FILENAME}" + # InstallLocation, DisplayVersion, UninstallString, ... in the uninstall key of the GUI + !insertmacro MULTIUSER_RegistryAddInstallInfo + + # The size of what this installer added (NsisMultiUser would measure the whole folder, + # the core included) + Push $0 + Push $1 + Push $2 + StrCpy $1 0 + SectionGetSize ${SEC_GUI} $2 + IntOp $1 $1 + $2 + !ifdef SEC_Doc + ${If} ${SectionIsSelected} ${SEC_Doc} + SectionGetSize ${SEC_Doc} $2 + IntOp $1 $1 + $2 + ${EndIf} + !endif + !ifdef SEC_Dev + ${If} ${SectionIsSelected} ${SEC_Dev} + SectionGetSize ${SEC_Dev} $2 + IntOp $1 $1 + $2 + ${EndIf} + !endif + !insertmacro MULTIUSER_GetCurrentUserString $0 + WriteRegDWORD SHCTX "${PRODUCT_UNINST_KEY}$0" "EstimatedSize" $1 + Pop $2 + Pop $1 + Pop $0 +SectionEnd + +## Sets $CoreMode ("AllUsers" or "CurrentUser") and $CoreDir to the installation of the OpenMS +## core this installer belongs to, or quits with a message if there is none of this version. +Var CoreMode +Var CoreDir +Function FindCoreInstallation + Push $0 + Push $1 + Push $2 + Push $3 + + # all users + ReadRegStr $0 HKLM "${CORE_UNINST_KEY}" "InstallLocation" + ReadRegStr $1 HKLM "${CORE_UNINST_KEY}" "DisplayVersion" + # current user + ReadRegStr $2 HKCU "${CORE_UNINST_KEY}" "InstallLocation" + ReadRegStr $3 HKCU "${CORE_UNINST_KEY} (current user)" "DisplayVersion" + ${If} $3 == "" + ReadRegStr $3 HKCU "${CORE_UNINST_KEY}" "DisplayVersion" + ${EndIf} + + StrCpy $CoreMode "" + # The mode given on the command line (required in silent mode) must be that of the core. + ${If} $CmdLineInstallMode == "AllUsers" + ${If} $0 != "" + ${AndIf} $1 == "${VERSION}" + StrCpy $CoreMode "AllUsers" + StrCpy $CoreDir $0 + ${EndIf} + ${ElseIf} $CmdLineInstallMode == "CurrentUser" + ${If} $2 != "" + ${AndIf} $3 == "${VERSION}" + StrCpy $CoreMode "CurrentUser" + StrCpy $CoreDir $2 + ${EndIf} + ${ElseIf} $0 != "" + ${AndIf} $1 == "${VERSION}" + StrCpy $CoreMode "AllUsers" + StrCpy $CoreDir $0 + ${ElseIf} $2 != "" + ${AndIf} $3 == "${VERSION}" + StrCpy $CoreMode "CurrentUser" + StrCpy $CoreDir $2 + ${EndIf} + + ${If} $CoreMode == "" + StrCpy $R9 "" + ${If} $0 != "" + StrCpy $R9 "$R9$\r$\nFound: OpenMS $1 for all users in $0" + ${EndIf} + ${If} $2 != "" + StrCpy $R9 "$R9$\r$\nFound: OpenMS $3 for the current user in $2" + ${EndIf} + ${If} $CmdLineInstallMode != "" + StrCpy $R9 "$R9$\r$\n(looked only for an installation $CmdLineInstallMode, as given on the command line)" + ${EndIf} + MessageBox MB_ICONSTOP "The OpenMS GUI ${VERSION} is an add-on to OpenMS ${VERSION}, which is not installed.$\r$\n$R9$\r$\n$\r$\nInstall OpenMS ${VERSION} first (${URL}), then run this installer again." /SD IDOK + SetErrorLevel 2 + Quit + ${EndIf} + + ${IfNot} ${FileExists} "$CoreDir\bin\OpenMS.dll" + MessageBox MB_ICONSTOP "The registry names $CoreDir as the folder of OpenMS ${VERSION}, but $CoreDir\bin\OpenMS.dll does not exist. Reinstall OpenMS ${VERSION}, then run this installer again." /SD IDOK + SetErrorLevel 2 + Quit + ${EndIf} + + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + +# Installer functions +Function .onInit + # Command line options + Push $R0 + Push $R1 + ${GetParameters} $R0 + ClearErrors + ${GetOptions} $R0 '/?' $R1 + ${IfNot} ${Errors} + MessageBox MB_ICONINFORMATION "Usage:$\r$\n\ + $\r$\n\ + /allusers$\t- install for all users (where OpenMS is installed for all users)$\r$\n\ + /currentuser$\t- install for the current user (where OpenMS is installed for the current user)$\r$\n\ + /S$\t- silent mode, requires /allusers or /currentuser, case-sensitive$\r$\n\ + /?$\t- display this message$\r$\n\ + $\r$\n\ + The OpenMS GUI is installed into the installation of OpenMS ${VERSION}, which has to be installed first.$\r$\n\ + $\r$\n\ + Return codes (decimal):$\r$\n\ + $\r$\n\ + 0$\t- normal execution (no error)$\r$\n\ + 1$\t- installation aborted by user (Cancel button)$\r$\n\ + 2$\t- installation aborted by script (e.g. OpenMS ${VERSION} is not installed)$\r$\n\ + 666660$\t- invalid command-line parameters$\r$\n\ + 666661$\t- elevation is not allowed by defines$\r$\n\ + 666666$\t- cannot start elevated instance$\r$\n\ + other$\t- Windows error code when trying to start elevated instance" + SetErrorLevel 0 + Quit + ${EndIf} + Pop $R1 + Pop $R0 + + !insertmacro MULTIUSER_INIT + + Call FindCoreInstallation + + # The mode of the core installation: all users needs administrator rights, so a + # non-elevated instance restarts itself elevated (and the elevated one gets here again). + ${If} $CoreMode == "AllUsers" + ${If} $IsAdmin = 0 + Call MultiUser.Elevate + # still here: elevation is not allowed (silent mode) + MessageBox MB_ICONSTOP "OpenMS ${VERSION} is installed for all users. Run this installer as administrator." /SD IDOK + SetErrorLevel 666661 + Quit + ${EndIf} + Call MultiUser.InstallMode.AllUsers + ${Else} + Call MultiUser.InstallMode.CurrentUser + ${EndIf} + StrCpy $INSTDIR $CoreDir + # the install mode page + StrCpy $DisplayDialog 0 + + !insertmacro UNINSTALL.LOG_PREPARE_INSTALL + !insertmacro MUI_LANGDLL_DISPLAY +FunctionEnd + +Function .onInstSuccess + !insertmacro UNINSTALL.LOG_UPDATE_INSTALL +FunctionEnd + +# set the component descriptions after the Sections have been defined! +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN +!insertmacro MUI_DESCRIPTION_TEXT ${SEC_GUI} "TOPPView, TOPPAS, INIFileEditor, ExecutePipeline and ImageCreator, the GUI library and Qt" +!ifdef SEC_Doc +!insertmacro MUI_DESCRIPTION_TEXT ${SEC_Doc} "Documentation of the GUI applications and the GUI library" +!endif +!ifdef SEC_Dev +!insertmacro MUI_DESCRIPTION_TEXT ${SEC_Dev} "Headers, import library and CMake package of the GUI library, for programs that use it" +!endif +!insertmacro MUI_DESCRIPTION_TEXT ${SEC_RegisterExt} "Open certain file types (e.g., '.mzML') with TOPPView and '.toppas' with TOPPAS" +!insertmacro MUI_FUNCTION_DESCRIPTION_END + +################ +## UNINSTALL ### +################ + +Section "-Uninstall" + # remove the file associations + !insertmacro OpenMSGUIExtensions UnRegisterExtensionSection + ${un.RefreshShellIcons} + + # remove the Start menu entries; the folder only if the core has none left in it + Delete "$SMPROGRAMS\${SMFOLDER}\TOPPView.lnk" + Delete "$SMPROGRAMS\${SMFOLDER}\TOPPAS.lnk" + Delete "$SMPROGRAMS\${SMFOLDER}\INIFileEditor.lnk" + Delete "$SMPROGRAMS\${SMFOLDER}\Uninstall OpenMS GUI.lnk" + RmDir "$SMPROGRAMS\${SMFOLDER}" + + # delete the installed files: only those in the log, never a file of the core + !insertmacro UNINSTALL.NEW_UNINSTALL + + # Remove the uninstaller from registry as the very last step - if sth. goes wrong, let the user run it again + !insertmacro MULTIUSER_RegistryRemoveInstallInfo +SectionEnd + +# UnInstaller functions +Function un.onInit + !insertmacro MULTIUSER_UNINIT + !insertmacro MUI_UNGETLANGUAGE +FunctionEnd diff --git a/cmake/Windows/NSIS_Readme.txt b/cmake/Windows/NSIS_Readme.txt new file mode 100644 index 000000000..09082bbb0 --- /dev/null +++ b/cmake/Windows/NSIS_Readme.txt @@ -0,0 +1,11 @@ +The NSIS script of the Windows installer of the OpenMS GUI (NSIS.template.in, configured by +CPack, see cmake/package_nsis.cmake) and what it needs besides a standard NSIS 3 installation: + + Include/NsisMultiUser.nsh, NsisMultiUserLang.nsh all-users/current-user installation (NsisMultiUser) + Include/UAC.nsh, Plugins/x86-unicode/UAC.dll elevation, used by NsisMultiUser (UAC plug-in, UAC_License.txt) + Include/AdvUninstLog2.nsh uninstall log: the uninstaller removes only the logged files + Include/IncludeScript_Misc.nsh file associations + images/, *.ico installer graphics and icons + +Tested with NSIS 3.09 (the standard build; the GUI installer needs neither the large-string +build nor UltraModernUI, which the core installer uses). diff --git a/cmake/Windows/OpenMS.ico b/cmake/Windows/OpenMS.ico new file mode 100644 index 000000000..568c7619d Binary files /dev/null and b/cmake/Windows/OpenMS.ico differ diff --git a/cmake/Windows/Plugins/x86-unicode/UAC.dll b/cmake/Windows/Plugins/x86-unicode/UAC.dll new file mode 100755 index 000000000..97e7d15ea Binary files /dev/null and b/cmake/Windows/Plugins/x86-unicode/UAC.dll differ diff --git a/cmake/Windows/UAC_License.txt b/cmake/Windows/UAC_License.txt new file mode 100644 index 000000000..8f5ef7ece --- /dev/null +++ b/cmake/Windows/UAC_License.txt @@ -0,0 +1,14 @@ +This software is provided 'as-is', without any express or implied warranty. + +ZLIB/LIBPNG LICENSE +------------------- + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. diff --git a/cmake/Windows/images/header-uninstall.bmp b/cmake/Windows/images/header-uninstall.bmp new file mode 100644 index 000000000..1672afa6d Binary files /dev/null and b/cmake/Windows/images/header-uninstall.bmp differ diff --git a/cmake/Windows/images/header.bmp b/cmake/Windows/images/header.bmp new file mode 100644 index 000000000..c74fbdd51 Binary files /dev/null and b/cmake/Windows/images/header.bmp differ diff --git a/cmake/Windows/images/wizard-uninstall.bmp b/cmake/Windows/images/wizard-uninstall.bmp new file mode 100644 index 000000000..097d09429 Binary files /dev/null and b/cmake/Windows/images/wizard-uninstall.bmp differ diff --git a/cmake/Windows/images/wizard.bmp b/cmake/Windows/images/wizard.bmp new file mode 100644 index 000000000..196a5b7a7 Binary files /dev/null and b/cmake/Windows/images/wizard.bmp differ diff --git a/cmake/add_library_macros.cmake b/cmake/add_library_macros.cmake new file mode 100644 index 000000000..aa416da52 --- /dev/null +++ b/cmake/add_library_macros.cmake @@ -0,0 +1,211 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Julianus Pfeuffer $ +# $Authors: Stephan Aiche, Chris Bielow, Julianus Pfeuffer $ +# -------------------------------------------------------------------------- + +# required modules +include(CMakeParseArguments) +include(GenerateExportHeader) +include(CheckLibArchitecture) + +#------------------------------------------------------------------------------ +## export a single option indicating if libraries should be build as unity +## build +option(ENABLE_UNITYBUILD "Build the libraries as unity builds: batches of OPENMS_UNITY_BUILD_BATCH_SIZE source files are compiled as one translation unit (CMake UNITY_BUILD)." OFF) +set(OPENMS_UNITY_BUILD_BATCH_SIZE 8 CACHE STRING + "With ENABLE_UNITYBUILD: number of source files combined into one translation unit (0: all of a library)") +option(OPENMS_VERIFY_INTERFACE_HEADER_SETS + "Compile OpenMS public headers individually with the all_verify_interface_header_sets target" OFF) + +#------------------------------------------------------------------------------ +## Unity builds (ENABLE_UNITYBUILD) +## +## Most of the compile time of an OpenMS source file goes into parsing the headers it +## includes, and neighbouring files include mostly the same ones. Compiling batches of +## OPENMS_UNITY_BUILD_BATCH_SIZE files (in source list order, i.e. mostly from the same +## directory) as one translation unit parses those headers once per batch. A change to one +## file recompiles its batch, so small batches keep incremental builds and parallelism. +## +## Files cannot be combined if their file-local names (anonymous namespaces, static +## functions, macros) collide; such files are excluded with the source file property +## SKIP_UNITY_BUILD_INCLUSION in the library's CMakeLists.txt. + +#------------------------------------------------------------------------------ +# openms_add_library() +# Create an OpenMS library, install it, register for export of targets, and +# export all required variables for later usage in the build system. +# +# Signature: +# openms_add_library(TARGET_NAME OpenMS +# SOURCE_FILES +# HEADER_FILES
+# (will be installed with the library) +# BASE_DIRS +# PRIVATE_INCLUDES +# EXTERNAL_INCLUDES +# (will be added with -isystem if available) +# LINK_LIBRARIES +# PRIVATE_LINK_LIBRARIES +# ALLOWED_PUBLIC_INCLUDES +# DLL_EXPORT_PATH +# EXPORT_SET ) +# +# Besides TARGET_NAME the library is available as OpenMS::TARGET_NAME, the name +# under which it is exported (install(EXPORT ... NAMESPACE OpenMS::)), so +# in-tree code and consumers of the installed package can link the same name. +# The library and its export go into the install components of EXPORT_SET; the +# headers always go into the component TARGET_NAME_headers. +# +# On Windows the DLLs the library needs at run time (the OpenMS core, Qt) are copied +# next to it into bin/, where all executables of this build (applications, tests, +# documenters) are, so they run from the build tree without changing PATH. +function(openms_add_library) + #------------------------------------------------------------------------------ + # parse arguments to function + set(options ) + set(oneValueArgs TARGET_NAME DLL_EXPORT_PATH EXPORT_SET) + set(multiValueArgs BASE_DIRS PRIVATE_INCLUDES EXTERNAL_INCLUDES SOURCE_FILES HEADER_FILES LINK_LIBRARIES PRIVATE_LINK_LIBRARIES + ALLOWED_PUBLIC_INCLUDES) + ## make above arguments available as variables, e.g. ${openms_add_library_PRIVATE_LINK_LIBRARIES} + cmake_parse_arguments(openms_add_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + + #------------------------------------------------------------------------------ + # Status message for configure output + message(STATUS "Adding library ${openms_add_library_TARGET_NAME}") + + #------------------------------------------------------------------------------ + # Preserve the include-directory cache consumed by class tests. + set(${openms_add_library_TARGET_NAME}_INCLUDE_DIRECTORIES ${openms_add_library_BASE_DIRS} + ${openms_add_library_EXTERNAL_INCLUDES} + CACHE INTERNAL "${openms_add_library_TARGET_NAME} include directories" FORCE) + + #------------------------------------------------------------------------------ + # Add the library + add_library(${openms_add_library_TARGET_NAME} ${openms_add_library_SOURCE_FILES}) + add_library(OpenMS::${openms_add_library_TARGET_NAME} ALIAS ${openms_add_library_TARGET_NAME}) + + if(ENABLE_UNITYBUILD) + message(STATUS "Unity build for ${openms_add_library_TARGET_NAME} (batches of ${OPENMS_UNITY_BUILD_BATCH_SIZE} files)") + set_target_properties(${openms_add_library_TARGET_NAME} PROPERTIES + UNITY_BUILD ON + UNITY_BUILD_MODE BATCH + UNITY_BUILD_BATCH_SIZE ${OPENMS_UNITY_BUILD_BATCH_SIZE}) + endif() + + set_target_properties(${openms_add_library_TARGET_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) + set_target_properties(${openms_add_library_TARGET_NAME} PROPERTIES VISIBILITY_INLINES_HIDDEN 1) + # AUTOMOC is chosen by the library's own CMakeLists.txt. Merely finding Qt + # elsewhere must not enable Qt code generation for non-GUI libraries. + + #------------------------------------------------------------------------------ + # Include directories + # Public include directories come from the header file set and its install destination. + + # TODO actually we shouldn't need to add these external includes. They should propagate through target_link_library if they are public + target_include_directories(${openms_add_library_TARGET_NAME} SYSTEM PUBLIC + "$" + ) + target_include_directories(${openms_add_library_TARGET_NAME} SYSTEM PRIVATE ${openms_add_library_PRIVATE_INCLUDES}) + + target_compile_features(${openms_add_library_TARGET_NAME} PUBLIC cxx_std_23) + + # Add compiler flags using the new helper function + openms_add_library_compiler_flags(${openms_add_library_TARGET_NAME}) + + if(ADDRESS_SANITIZER) + add_asan_to_target(${openms_add_library_TARGET_NAME}) + endif() + + + set_target_properties(${openms_add_library_TARGET_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) + set_target_properties(${openms_add_library_TARGET_NAME} PROPERTIES VISIBILITY_INLINES_HIDDEN 1) + + #------------------------------------------------------------------------------ + # Generate export header if requested + if(openms_add_library_DLL_EXPORT_PATH) + ## this snipped creates 'OpenMSConfig.h' in the build tree + set(_CONFIG_H "include/${openms_add_library_DLL_EXPORT_PATH}${openms_add_library_TARGET_NAME}Config.h") + string(TOUPPER ${openms_add_library_TARGET_NAME} _TARGET_UPPER_CASE) + include(GenerateExportHeader) + generate_export_header(${openms_add_library_TARGET_NAME} + EXPORT_MACRO_NAME ${_TARGET_UPPER_CASE}_DLLAPI + EXPORT_FILE_NAME ${_CONFIG_H}) + + string(REGEX REPLACE "/" "\\\\" _fixed_path ${openms_add_library_DLL_EXPORT_PATH}) + + # add generated header to visual studio + source_group("Header Files\\${_fixed_path}" FILES ${_CONFIG_H}) + list(APPEND openms_add_library_HEADER_FILES "${CMAKE_CURRENT_BINARY_DIR}/${_CONFIG_H}") + endif() + + # Both configured headers and generate_export_header() output belong to the + # public interface. Listing them here also makes them visible to IDEs/AUTOMOC. + list(REMOVE_DUPLICATES openms_add_library_HEADER_FILES) + openms_validate_public_headers(${openms_add_library_HEADER_FILES} + ALLOW ${openms_add_library_ALLOWED_PUBLIC_INCLUDES}) + target_sources(${openms_add_library_TARGET_NAME} PUBLIC FILE_SET HEADERS + BASE_DIRS ${openms_add_library_BASE_DIRS} + FILES ${openms_add_library_HEADER_FILES}) + if(OPENMS_VERIFY_INTERFACE_HEADER_SETS) + set_target_properties(${openms_add_library_TARGET_NAME} PROPERTIES + VERIFY_INTERFACE_HEADER_SETS ON) + endif() + + #------------------------------------------------------------------------------ + # Link library against other libraries + if(openms_add_library_LINK_LIBRARIES) + ## check for consistent lib arch (e.g. all 64bit)? + check_lib_architecture(openms_add_library_LINK_LIBRARIES) + target_link_libraries(${openms_add_library_TARGET_NAME} PUBLIC ${openms_add_library_LINK_LIBRARIES}) + list(LENGTH openms_add_library_LINK_LIBRARIES _library_count) + endif() + + if (openms_add_library_PRIVATE_LINK_LIBRARIES) + ## check for consistent lib arch (e.g. all 64bit)? + check_lib_architecture(openms_add_library_PRIVATE_LINK_LIBRARIES) + target_link_libraries(${openms_add_library_TARGET_NAME} PRIVATE ${openms_add_library_PRIVATE_LINK_LIBRARIES}) + list(LENGTH openms_add_library_PRIVATE_LINK_LIBRARIES _private_library_count) + endif() + + #------------------------------------------------------------------------------ + # Export libraries (self + dependencies) + set(${openms_add_library_TARGET_NAME}_LIBRARIES + ${openms_add_library_TARGET_NAME} + ${openms_add_library_LINK_LIBRARIES} + CACHE + INTERNAL "${openms_add_library_TARGET_NAME} libraries" FORCE) + + #------------------------------------------------------------------------------ + # we also want to install the library (into the components of its export set) + if(NOT openms_add_library_EXPORT_SET) + set(openms_add_library_EXPORT_SET ${OPENMS_GUI_EXPORT_SET}) + endif() + install_library(${openms_add_library_TARGET_NAME} HEADERS EXPORT_SET ${openms_add_library_EXPORT_SET}) + + #------------------------------------------------------------------------------ + # register for the build-tree export of the same export set + openms_register_export_target(${openms_add_library_TARGET_NAME} ${openms_add_library_EXPORT_SET}) + + #------------------------------------------------------------------------------ + # On Windows copy the DLLs the library depends on (OpenMS core, Qt) next to it + if(WIN32) + ## never empty: the library always links the OpenMS core and Qt DLLs + add_custom_command(TARGET ${openms_add_library_TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + COMMAND_EXPAND_LISTS + ) + endif() + + #------------------------------------------------------------------------------ + # Status message for configure output + message(STATUS "Adding library ${openms_add_library_TARGET_NAME} - SUCCESS") +endfunction() diff --git a/cmake/build_system_macros.cmake b/cmake/build_system_macros.cmake new file mode 100644 index 000000000..9aff6ee81 --- /dev/null +++ b/cmake/build_system_macros.cmake @@ -0,0 +1,28 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Stephan Aiche, Chris Bielow $ +# $Authors: Chris Bielow, Stephan Aiche $ +# -------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ +## Checks if the user supplied package type is valid and aborts if not +## @param package_type The given package type +macro(is_valid_package package_type) + list(FIND VALID_PACKAGE_TYPES ${package_type} list_pos) + if( ${list_pos} EQUAL -1 ) + message(STATUS "The PACKAGE_TYPE ${package_type} is invalid") + message(STATUS "Valid PACKAGE_TYPEs are:") + foreach( _vpt ${VALID_PACKAGE_TYPES} ) + message(STATUS " * ${_vpt}") + endforeach() + message(FATAL_ERROR "Aborting ...") + endif() +endmacro() + +#------------------------------------------------------------------------------ +## The OpenMS core links its TOPP tools with mimalloc on Windows (WITH_MIMALLOC). The GUI +## TOPP tools do not need it; the macro is kept so the moved code works unchanged. +macro(openms_link_mimalloc target) +endmacro() diff --git a/cmake/cmake_findQt.cmake b/cmake/cmake_findQt.cmake index 21d0a630a..eb1c065d9 100644 --- a/cmake/cmake_findQt.cmake +++ b/cmake/cmake_findQt.cmake @@ -7,66 +7,62 @@ # -------------------------------------------------------------------------- #------------------------------------------------------------------------------ -# Finds the Qt6 modules of the GUI library (WITH_GUI). Included by -# cmake/cmake_findExternalLibs.cmake and, for a build of the GUI against an -# installed OpenMS library, by cmake/find_installed_openms.cmake. -#------------------------------------------------------------------------------ -# QT (only needed for GUI) +# Finds the Qt6 modules of the GUI library. Included by cmake/find_openms.cmake. #------------------------------------------------------------------------------ SET(QT_MIN_VERSION "6.1.0") -if (WITH_GUI) - find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Core QUIET) +find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Core QUIET) - IF (Qt6Core_FOUND) - message(STATUS "Found Qt ${Qt6Core_VERSION}") - ELSE() - message(FATAL_ERROR "Qt6Core not found — required when WITH_GUI=ON. Use -DWITH_GUI=OFF to build without GUI.") - ENDIF() +IF (Qt6Core_FOUND) + message(STATUS "Found Qt ${Qt6Core_VERSION}") +ELSE() + message(FATAL_ERROR "Qt6Core (>= ${QT_MIN_VERSION}) not found. Add the Qt installation to CMAKE_PREFIX_PATH.") +ENDIF() - # -------------------------------------------------------------------------- - # Find additional Qt libs - #--------------------------------------------------------------------------- - set (TEMP_OpenMS_GUI_QT_COMPONENTS Gui Widgets Svg OpenGLWidgets) +# -------------------------------------------------------------------------- +# Find additional Qt libs +#--------------------------------------------------------------------------- +set (TEMP_OpenMS_GUI_QT_COMPONENTS Gui Widgets Svg OpenGLWidgets) - # On macOS the platform plugin of QT requires PrintSupport. We link - # so it's packaged via the bundling/dependency tools/scripts - if (APPLE) - set (TEMP_OpenMS_GUI_QT_COMPONENTS ${TEMP_OpenMS_GUI_QT_COMPONENTS} PrintSupport) - endif() +# On macOS the platform plugin of QT requires PrintSupport. We link +# so it's packaged via the bundling/dependency tools/scripts +if (APPLE) + set (TEMP_OpenMS_GUI_QT_COMPONENTS ${TEMP_OpenMS_GUI_QT_COMPONENTS} PrintSupport) +endif() - set(OpenMS_GUI_QT_COMPONENTS ${TEMP_OpenMS_GUI_QT_COMPONENTS} CACHE INTERNAL "QT components for GUI lib") +set(OpenMS_GUI_QT_COMPONENTS ${TEMP_OpenMS_GUI_QT_COMPONENTS} CACHE INTERNAL "QT components for GUI lib") - if(NOT NO_WEBENGINE_WIDGETS) - set(OpenMS_GUI_QT_COMPONENTS_OPT WebEngineWidgets) - endif() +if(NOT NO_WEBENGINE_WIDGETS) + set(OpenMS_GUI_QT_COMPONENTS_OPT WebEngineWidgets) +endif() - find_package(Qt6 REQUIRED COMPONENTS ${OpenMS_GUI_QT_COMPONENTS}) +find_package(Qt6 REQUIRED COMPONENTS ${OpenMS_GUI_QT_COMPONENTS}) - IF (NOT Qt6Widgets_FOUND OR NOT Qt6Gui_FOUND OR NOT Qt6Svg_FOUND) - message(STATUS "Qt6Widgets not found!") - message(FATAL_ERROR "To find a custom Qt installation use: cmake <..more options..> -DCMAKE_PREFIX_PATH='' ") - ENDIF() - find_package(Qt6 QUIET COMPONENTS ${OpenMS_GUI_QT_COMPONENTS_OPT}) +IF (NOT Qt6Widgets_FOUND OR NOT Qt6Gui_FOUND OR NOT Qt6Svg_FOUND) + message(STATUS "Qt6Widgets not found!") + message(FATAL_ERROR "To find a custom Qt installation use: cmake <..more options..> -DCMAKE_PREFIX_PATH='' ") +ENDIF() +find_package(Qt6 QUIET COMPONENTS ${OpenMS_GUI_QT_COMPONENTS_OPT}) - # TODO only works if WebEngineWidgets is the only optional component - set(OpenMS_GUI_QT_FOUND_COMPONENTS_OPT) - if(Qt6WebEngineWidgets_FOUND) - list(APPEND OpenMS_GUI_QT_FOUND_COMPONENTS_OPT "WebEngineWidgets") - else() - message(WARNING "Qt6WebEngineWidgets not found or disabled, disabling JS Views in TOPPView!") - endif() +# TODO only works if WebEngineWidgets is the only optional component +set(OpenMS_GUI_QT_FOUND_COMPONENTS_OPT) +if(Qt6WebEngineWidgets_FOUND) + list(APPEND OpenMS_GUI_QT_FOUND_COMPONENTS_OPT "WebEngineWidgets") +else() + message(WARNING "Qt6WebEngineWidgets not found or disabled, disabling JS Views in TOPPView!") +endif() - # The GUI applications derive from TOPPBase and discover tools through ToolHandler, - # so the tool framework is part of the GUI library's public link interface. - set(OpenMS_GUI_DEP_LIBRARIES "OpenMS" "OpenMS_CLI") +# The GUI applications derive from TOPPBase and discover tools through ToolHandler, +# so the tool framework is part of the GUI library's public link interface. +set(OpenMS_GUI_DEP_LIBRARIES OpenMS::OpenMS OpenMS::OpenMS_CLI) - foreach(COMP IN LISTS OpenMS_GUI_QT_COMPONENTS) - list(APPEND OpenMS_GUI_DEP_LIBRARIES "Qt6::${COMP}") - endforeach() +foreach(COMP IN LISTS OpenMS_GUI_QT_COMPONENTS) + list(APPEND OpenMS_GUI_DEP_LIBRARIES "Qt6::${COMP}") +endforeach() - foreach(COMP IN LISTS OpenMS_GUI_QT_FOUND_COMPONENTS_OPT) - list(APPEND OpenMS_GUI_DEP_LIBRARIES "Qt6::${COMP}") - endforeach() +foreach(COMP IN LISTS OpenMS_GUI_QT_FOUND_COMPONENTS_OPT) + list(APPEND OpenMS_GUI_DEP_LIBRARIES "Qt6::${COMP}") +endforeach() -endif() +## Qt's own include directories are system includes (no warnings from Qt headers) +set(QT_INCLUDE_DIRS_NO_SYSTEM OFF) diff --git a/cmake/compiler_flags.cmake b/cmake/compiler_flags.cmake new file mode 100644 index 000000000..2a22c00a7 --- /dev/null +++ b/cmake/compiler_flags.cmake @@ -0,0 +1,220 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Stephan Aiche, Chris Bielow $ +# $Authors: Andreas Bertsch, Chris Bielow, Stephan Aiche $ +# -------------------------------------------------------------------------- + + +#------------------------------------------------------------------------------ +# This cmake file handles all the project specific compiler flags +# It defines variables and options and provides functions to apply flags to targets + +#------------------------------------------------------------------------------ +# PART 1: Define variables and options +#------------------------------------------------------------------------------ + +# Custom compile flags +if (MY_CXX_FLAGS) ## do not change this name! it's used in configh.cmake + message(STATUS "Custom compile flags: '${MY_CXX_FLAGS}' will be added to targets") +endif() + +# SIMD extensions +set(x64_CPU "x86|AMD64") ## CMake returns 'x86-64' on Linux and 'AMD64' on Windows.. +message(STATUS "Processor is : ${CMAKE_SYSTEM_PROCESSOR}") + +# ARM processors: SIMDe will do the right thing upon detecting ARM +# https://github.com/simd-everywhere/simde/blob/master/simde/simde-arch.h#L117 +# (neon instructions compile without error even if no compile flag is given -- as opposed to x64 intrinsics) + +# Compiler-specific options +if(CMAKE_COMPILER_IS_GNUCXX) + # GCC options + option(ENABLE_GCC_WERROR "Enable -Werror on gcc compilers" OFF) + if(ENABLE_GCC_WERROR) + message(STATUS "Enable -Werror for gcc - note that this may not work on all compilers and system settings!") + endif() +elseif(MSVC) + # MSVC options +elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Clang options + set(CMAKE_COMPILER_IS_CLANG true CACHE INTERNAL "Is CLang compiler (clang++)") +else() + # Intel compiler options + set(CMAKE_COMPILER_IS_INTELCXX true CACHE INTERNAL "Is Intel C++ compiler (icpc)") +endif() + +# Platform-dependent options +include(CheckCXXCompilerFlag) +if(NOT WIN32) # we only want fPIC on non-windows systems (fPIC is implicitly true there) + CHECK_CXX_COMPILER_FLAG("-fPIC" WITH_FPIC) + if(WITH_FPIC) + message(STATUS "Position-independent code (-fPIC) is supported and will be applied to targets") + endif() +endif() + +# Conversion warnings +set(CXX_WARN_CONVERSION OFF CACHE BOOL "Enables warnings for type conversion problems (GCC only)") +message(STATUS "Compiler checks for conversion: ${CXX_WARN_CONVERSION}") + +#------------------------------------------------------------------------------ +# PART 2: Functions to apply compiler flags to targets +#------------------------------------------------------------------------------ + +# Function to add compiler flags to a target with proper PUBLIC/PRIVATE visibility +function(openms_add_compiler_flags target_name) + #------------------------------------------------------------------------------ + # PUBLIC flags (propagated to dependent targets) + #------------------------------------------------------------------------------ + + # Language standard + target_compile_features(${target_name} PUBLIC cxx_std_23) + + # Position-independent code + if(NOT WIN32 AND WITH_FPIC) + target_compile_options(${target_name} PUBLIC -fPIC) + endif() + + # Essential preprocessor definitions + if(MSVC) + target_compile_definitions(${target_name} PUBLIC + NOMINMAX # keep windows.h from defining min/max macros + ) + endif() + + # C++23 workaround for ARM: std::float16_t/float32_t/float64_t + # conflict with ARM NEON typedefs of the same name when 'using namespace std;' + # is active. Suppress only the three macros that conflict with NEON typedefs. + # Do NOT suppress __STDCPP_FLOAT128_T__ or __STDCPP_BFLOAT16_T__ as those + # have no NEON conflict and are needed by Boost.Math. + if(NOT MSVC AND CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") + target_compile_options(${target_name} PUBLIC + -U__STDCPP_FLOAT16_T__ -U__STDCPP_FLOAT32_T__ -U__STDCPP_FLOAT64_T__ + ) + endif() + + # SIMD extensions (PUBLIC for binary compatibility) + # MSVC x64 defaults to SSE2 (128-bit); do NOT use /arch:AVX here because + # AVX's 256-bit reductions change Eigen's floating-point evaluation order + # vs the 128-bit paths used by GCC/Clang, causing cross-platform numeric + # divergence in sensitive algorithms (e.g. normalizedCrossCorrelation). + if(NOT MSVC AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES "${x64_CPU}") + target_compile_options(${target_name} PUBLIC -mssse3) + endif() + + # When full OpenMP isn't available (typically Apple clang without libomp), + # multithreading.cmake sets OPENMS_OMP_SIMD_FALLBACK_FLAG to -fopenmp-simd + # so #pragma omp simd vectorization still works inside OpenMS. Applied PRIVATE: + # no installed public OpenMS header contains `#pragma omp simd`, so downstream + # consumers don't need this flag. Propagating it PUBLIC could break downstream + # builds whose compiler doesn't accept -fopenmp-simd. + # Not gated on architecture: -fopenmp-simd is useful on ARM too (NEON). + if(DEFINED OPENMS_OMP_SIMD_FALLBACK_FLAG) + target_compile_options(${target_name} PRIVATE ${OPENMS_OMP_SIMD_FALLBACK_FLAG}) + endif() + + #------------------------------------------------------------------------------ + # PRIVATE flags (not propagated to dependent targets) + #------------------------------------------------------------------------------ + + # Warning controls + if(CMAKE_COMPILER_IS_GNUCXX) + target_compile_options(${target_name} PRIVATE + -Wall -Wextra + -Wno-unknown-pragmas + -ffp-contract=off + -Wno-unused-function + -Wno-psabi + ) + + if(ENABLE_GCC_WERROR) + target_compile_options(${target_name} PRIVATE -Werror) + endif() + + if(CMAKE_GENERATOR STREQUAL "Eclipse CDT4 - Unix Makefiles") + target_compile_options(${target_name} PRIVATE -fmessage-length=0) + endif() + elseif(MSVC) + target_compile_options(${target_name} PRIVATE + /wd4251 /wd4275 # disable dll-interface warning + /wd4996 # disable deprecated functions warning + /wd4661 # disable explicit template instantiation request warning + /wd4503 # disable decorated name length exceeded warning9 + /bigobj # for large object files + /MP # use multiple CPU cores + ) + + target_compile_definitions(${target_name} PRIVATE + _SCL_SECURE_NO_WARNINGS + _CRT_SECURE_NO_WARNINGS + _CRT_SECURE_NO_DEPRECATE + ) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(${target_name} PRIVATE + -ffp-contract=off + -Wall -Wextra + -Wno-sign-conversion + -Wno-global-constructors + -Wno-exit-time-destructors + -Wno-weak-vtables + -Wno-documentation-unknown-command + -Wno-undef + -Wno-documentation + -Wno-source-uses-openmp + -Wno-old-style-cast + -Wno-unknown-warning-option + -Wno-double-promotion + -Wno-unused-template + -Wno-conversion + -Wno-float-equal + -Wno-switch-enum + -Wno-missing-prototypes + -Wno-missing-variable-declarations + -Wno-deprecated + -Wno-covered-switch-default + -Wno-date-time + -Wno-missing-noreturn + ) + endif() + + # Conversion warnings + if(CXX_WARN_CONVERSION AND CMAKE_COMPILER_IS_GNUCXX) + target_compile_options(${target_name} PRIVATE -Wconversion) + endif() + + # Custom flags + if(MY_CXX_FLAGS) + separate_arguments(MY_CXX_FLAGS_LIST UNIX_COMMAND "${MY_CXX_FLAGS}") + target_compile_options(${target_name} PRIVATE ${MY_CXX_FLAGS_LIST}) + endif() +endfunction() + +# Function to add library-specific compiler flags +function(openms_add_library_compiler_flags target_name) + # Add common flags first (both PUBLIC and PRIVATE) + openms_add_compiler_flags(${target_name}) + + # Library-specific PRIVATE flags + if(CMAKE_COMPILER_IS_GNUCXX) + target_compile_options(${target_name} PRIVATE -Wno-non-virtual-dtor) + endif() + + # Address sanitizer (PRIVATE) + if(ADDRESS_SANITIZER) + add_asan_to_target(${target_name}) + endif() +endfunction() + +# Function to add executable-specific compiler flags +function(openms_add_executable_compiler_flags target_name) + # Add common flags first (both PUBLIC and PRIVATE) + openms_add_compiler_flags(${target_name}) + + # Executable-specific PRIVATE flags can be added here + + # Address sanitizer (PRIVATE) + if(ADDRESS_SANITIZER) + add_asan_to_target(${target_name}) + endif() +endfunction() diff --git a/cmake/cpack_clean_runpath.cmake b/cmake/cpack_clean_runpath.cmake new file mode 100644 index 000000000..e972c6d58 --- /dev/null +++ b/cmake/cpack_clean_runpath.cmake @@ -0,0 +1,85 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +# Removes empty entries from the RUNPATH (or RPATH) of every ELF file in the CPack +# staging directory. CPack runs this script (CPACK_PRE_BUILD_SCRIPTS, set in +# cmake/package_deb.cmake) after it has installed the files and before it builds +# the package; it is not included at configure time. +# +# The dynamic loader reads an empty entry as the current working directory, so no +# packaged file may carry one. They appear when the binaries were linked under a +# different configuration than the one being packaged, which is what the release +# workflow does: it builds and tests with PACKAGE_TYPE=none, then reconfigures with +# the package type and runs cpack without relinking. CMake pads a build-tree RPATH +# with ':' to the length of the install RPATH, and the install RPATH of +# PACKAGE_TYPE=none also names the directories of the libraries it links (the OpenMS +# core, Qt; top-level CMakeLists.txt), so it is far longer than that of a package. +# When installing, CMake replaces only the padding it now expects and keeps the rest, +# so the packaged file got $ORIGIN/../lib/ followed by dozens of empty entries. +# Relinking before packaging would avoid the padding, but 'all' also contains the +# documentation target, which is always out of date and would regenerate the whole +# documentation. + +## Without it the glob below would start at the file system root. +if(NOT IS_DIRECTORY "${CPACK_TEMPORARY_DIRECTORY}") + message(FATAL_ERROR "cpack_clean_runpath.cmake: CPACK_TEMPORARY_DIRECTORY ('${CPACK_TEMPORARY_DIRECTORY}') " + "is not a directory; this script is meant to be run by CPack (CPACK_PRE_BUILD_SCRIPTS).") +endif() + +cmake_policy(PUSH) +## Unset policies take their OLD behavior in a CPack script: CMP0009 NEW keeps +## GLOB_RECURSE from following symlinks out of the staging directory. +cmake_policy(VERSION 3.24) + +## A function, so that none of its variables reach the CPack generator code that +## runs in this scope afterwards. +function(_openms_clean_runpath _staging_dir) + file(GLOB_RECURSE _staged_files LIST_DIRECTORIES false "${_staging_dir}/*") + foreach(_file IN LISTS _staged_files) + ## Editing through a symlink could reach a file outside the staging directory; + ## a staged target is edited under its own name. + if(IS_SYMLINK "${_file}") + continue() + endif() + + ## READ_ELF only sets the variables it finds something for. + unset(_not_elf) + unset(_rpath) + unset(_runpath) + file(READ_ELF "${_file}" RPATH _rpath RUNPATH _runpath CAPTURE_ERROR _not_elf) + if(_not_elf) + continue() + endif() + ## The loader ignores RPATH when RUNPATH is present. + if(DEFINED _runpath) + set(_old "${_runpath}") + elseif(DEFINED _rpath) + set(_old "${_rpath}") + else() + continue() + endif() + + ## READ_ELF returns the entries as a list; compare them as the loader sees them. + string(REPLACE ";" ":" _old "${_old}") + string(REGEX REPLACE "::+" ":" _new "${_old}") + string(REGEX REPLACE "^:|:$" "" _new "${_new}") + if(_new STREQUAL _old) + continue() + endif() + + if(_new STREQUAL "") + file(RPATH_REMOVE FILE "${_file}") + else() + file(RPATH_SET FILE "${_file}" NEW_RPATH "${_new}") + endif() + endforeach() +endfunction() + +_openms_clean_runpath("${CPACK_TEMPORARY_DIRECTORY}") + +cmake_policy(POP) diff --git a/cmake/cpack_deb_t64_dependencies.cmake b/cmake/cpack_deb_t64_dependencies.cmake new file mode 100644 index 000000000..107144adf --- /dev/null +++ b/cmake/cpack_deb_t64_dependencies.cmake @@ -0,0 +1,275 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Julianus Pfeuffer $ +# $Authors: Julianus Pfeuffer $ +# -------------------------------------------------------------------------- + +# Adds the pre-t64 name of each renamed library package to the Depends of every DEB. +# CPack runs this script (CPACK_POST_BUILD_SCRIPTS, set in cmake/package_deb.cmake) +# after it has written the packages and before it copies them to the build tree; it +# is not included at configure time. +# +# Debian and Ubuntu (from 24.04) renamed the library packages whose ABI involves +# time_t when they moved their 32-bit architectures to a 64-bit time_t: libqt6core6 +# became libqt6core6t64, and so on. dpkg-shlibdeps names the packages installed on the +# build host, so a package built on such a release depends on the new names, which +# older releases do not have. Later releases went back to the old names for many of +# these libraries: Debian 13 and Ubuntu 25.10 and 26.04 ship libqt6gui6 and +# libqt6openglwidgets6, with no t64 package. On architectures whose time_t was 64-bit +# already (amd64, arm64, ...) the ABI did not change, and the renamed package provides +# and replaces its old name. There this script adds the old name as an alternative, +# with the same version constraint: +# libqt6core6t64 (>= 6.2.2) -> libqt6core6t64 (>= 6.2.2) | libqt6core6 (>= 6.2.2) +# The old names come from the build host's dpkg database, which dpkg-shlibdeps has +# just read. On armhf, where the ABI changed, the renamed packages do not provide +# their old names, and the dependencies stay as they are. A package built on a release +# before the renaming needs nothing: on those architectures the renamed packages +# provide the names it depends on. +# +# Only the control member of the package is replaced, in place; the data member is +# kept byte for byte. + +cmake_policy(PUSH) +cmake_policy(VERSION 3.24) + +## The name that the installed package : was renamed from: one it both +## provides and replaces, and that starts with without its t64 suffix. Some +## packages also provide and replace names from earlier renames (libext2fs2t64 those of +## libext2fs2 and of e2fslibs, which it replaced in 2018); only the t64 rename's own +## old names are wanted. Empty if there is none. +function(_openms_deb_renamed_from _dpkg_query _name _arch _out) + set(${_out} "" PARENT_SCOPE) + execute_process( + COMMAND "${_dpkg_query}" --show "--showformat=\${Provides}\\n\${Replaces}" "${_name}:${_arch}" + OUTPUT_VARIABLE _value + RESULT_VARIABLE _result + ERROR_VARIABLE _error + ) + ## dpkg-shlibdeps found the package in this database moments ago, so this should + ## not happen; it leaves the dependency strict rather than failing the package. + if(NOT _result EQUAL 0 OR NOT _value MATCHES "^([^\n]*)\n([^\n]*)") + message(WARNING "cpack_deb_t64_dependencies.cmake: cannot query ${_name}:${_arch}, so its dependency stays as it is: ${_error}") + return() + endif() + set(_provides_field "${CMAKE_MATCH_1}") + set(_replaces_field "${CMAKE_MATCH_2}") + + ## Relations are comma-separated; drop the version and architecture qualifiers. + set(_replaces) + string(REPLACE "," ";" _entries "${_replaces_field}") + foreach(_entry IN LISTS _entries) + if(_entry MATCHES "^[ \t]*([^ \t(:]+)") + list(APPEND _replaces "${CMAKE_MATCH_1}") + endif() + endforeach() + + string(REGEX REPLACE "t64$" "" _stem "${_name}") + set(_names) + string(REPLACE "," ";" _entries "${_provides_field}") + foreach(_entry IN LISTS _entries) + if(_entry MATCHES "^[ \t]*([^ \t(:]+)") + set(_provided "${CMAKE_MATCH_1}") + string(FIND "${_provided}" "${_stem}" _position) + if(_position EQUAL 0 AND _provided IN_LIST _replaces) + list(APPEND _names "${_provided}") + endif() + endif() + endforeach() + set(${_out} "${_names}" PARENT_SCOPE) +endfunction() + +## Adds the alternatives to the package , using as scratch space. +## Uses the tools found by _openms_deb_add_t64_alternatives(). +function(_openms_deb_rewrite _package _work_dir) + get_filename_component(_package_name "${_package}" NAME) + file(MAKE_DIRECTORY "${_work_dir}/control") + + execute_process( + COMMAND "${_dpkg_deb}" --ctrl-tarfile "${_package}" + OUTPUT_FILE "${_work_dir}/control.tar" + RESULT_VARIABLE _result + ERROR_VARIABLE _error + ) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "Could not read the control archive of ${_package}: ${_error}") + endif() + ## The members in their order, so that the rebuilt archive lists them the same way. + execute_process( + COMMAND "${_tar}" --list --file "${_work_dir}/control.tar" + OUTPUT_VARIABLE _control_members + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _result + ERROR_VARIABLE _error + ) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "Could not list the control archive of ${_package}: ${_error}") + endif() + string(REPLACE "\n" ";" _control_members "${_control_members}") + execute_process( + COMMAND "${_tar}" --extract --preserve-permissions --file "${_work_dir}/control.tar" -C "${_work_dir}/control" + RESULT_VARIABLE _result + ERROR_VARIABLE _error + ) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "Could not extract the control archive of ${_package}: ${_error}") + endif() + + set(_control_file "${_work_dir}/control/control") + file(READ "${_control_file}" _control) + if(NOT _control MATCHES "(^|\n)Architecture:[ \t]*([^\r\n]*)") + message(FATAL_ERROR "No Architecture field in the control file of ${_package}") + endif() + set(_arch "${CMAKE_MATCH_2}") + if(NOT _control MATCHES "(^|\n)Depends:[ \t]*([^\r\n]*)") + return() + endif() + set(_depends_field "${CMAKE_MATCH_0}") + set(_depends_prefix "${CMAKE_MATCH_1}") + set(_depends "${CMAKE_MATCH_2}") + + string(REPLACE "," ";" _dependencies "${_depends}") + set(_new_dependencies) + foreach(_dependency IN LISTS _dependencies) + string(STRIP "${_dependency}" _dependency) + ## dpkg-shlibdeps writes "name" or "name (op version)"; entries that already have + ## alternatives are left alone. + if(_dependency MATCHES "^([a-z0-9][a-z0-9+.-]*t64)([ \t]*\\([^)]*\\))?$") + set(_version "${CMAKE_MATCH_2}") + _openms_deb_renamed_from("${_dpkg_query}" "${CMAKE_MATCH_1}" "${_arch}" _old_names) + foreach(_old_name IN LISTS _old_names) + string(APPEND _dependency " | ${_old_name}${_version}") + endforeach() + endif() + list(APPEND _new_dependencies "${_dependency}") + endforeach() + list(JOIN _new_dependencies ", " _new_depends) + if(_new_depends STREQUAL _depends) + return() + endif() + + string(REPLACE "${_depends_field}" "${_depends_prefix}Depends: ${_new_depends}" _control "${_control}") + file(WRITE "${_control_file}" "${_control}") + + ## CPack writes the control archive as control.tar.gz. Rebuild it the same way: + ## same members in the same order, owned by root, dated no later than + ## SOURCE_DATE_EPOCH if that is set, and compressed without a time stamp. tar takes + ## anything after --mtime=@ without an error, so a value that is not a number of + ## seconds is left out rather than passed on. + set(_mtime) + set(_epoch "$ENV{SOURCE_DATE_EPOCH}") + if(_epoch MATCHES "^[0-9]+$") + set(_mtime "--mtime=@${_epoch}" --clamp-mtime) + elseif(NOT _epoch STREQUAL "") + message(WARNING "cpack_deb_t64_dependencies.cmake: SOURCE_DATE_EPOCH ('${_epoch}') is not a number of seconds; " + "the control archive of ${_package_name} keeps the times of its files.") + endif() + file(MAKE_DIRECTORY "${_work_dir}/member") + execute_process( + COMMAND "${_tar}" --create --file "${_work_dir}/member/control.tar" --format=gnu + --owner=0 --group=0 --numeric-owner ${_mtime} --no-recursion + -C "${_work_dir}/control" ${_control_members} + RESULT_VARIABLE _result + ERROR_VARIABLE _error + ) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "Could not rebuild the control archive of ${_package}: ${_error}") + endif() + execute_process( + COMMAND "${_gzip}" -9 --no-name "${_work_dir}/member/control.tar" + RESULT_VARIABLE _result + ERROR_VARIABLE _error + ) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "Could not compress the control archive of ${_package}: ${_error}") + endif() + + execute_process( + COMMAND "${_ar}" t "${_package}" + OUTPUT_VARIABLE _members + RESULT_VARIABLE _result + ERROR_VARIABLE _error + ) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "Could not list the members of ${_package}: ${_error}") + endif() + if(NOT _members MATCHES "(^|\n)control\\.tar\\.gz\n") + message(FATAL_ERROR "${_package} has no control.tar.gz member; its members are:\n${_members}") + endif() + + ## 'ar r' replaces the member in place, keeping the order dpkg requires. ar writes the + ## new archive to a temporary file of its own and renames it over the package, so an + ## interrupted run leaves the package as it was. D: no time stamps or owners; S: no + ## symbol table, which would become the first member. + execute_process( + COMMAND "${_ar}" rDS "${_package}" control.tar.gz + WORKING_DIRECTORY "${_work_dir}/member" + RESULT_VARIABLE _result + ERROR_VARIABLE _error + ) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "Could not replace the control archive of ${_package}: ${_error}") + endif() + + ## Check the result as dpkg reads it. + execute_process( + COMMAND "${_ar}" t "${_package}" + OUTPUT_VARIABLE _new_members + RESULT_VARIABLE _members_result + ERROR_VARIABLE _members_error + ) + execute_process( + COMMAND "${_dpkg_deb}" --field "${_package}" Depends + OUTPUT_VARIABLE _written_depends + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _field_result + ERROR_VARIABLE _field_error + ) + if(NOT _members_result EQUAL 0 OR NOT _new_members STREQUAL _members + OR NOT _field_result EQUAL 0 OR NOT _written_depends STREQUAL _new_depends) + message(FATAL_ERROR "The rewritten ${_package} does not read back as expected.\n" + "Members: ${_new_members}${_members_error}\nDepends: ${_written_depends}${_field_error}") + endif() + message(STATUS "cpack_deb_t64_dependencies.cmake: ${_package_name}: Depends: ${_new_depends}") +endfunction() + +## A function, so that none of its variables reach the CPack generator code that +## runs in this scope afterwards. +function(_openms_deb_add_t64_alternatives) + ## CPack also runs post-build scripts for the source package and other generators; + ## without a DEB there is nothing to do, and none of the tools below is needed. + set(_debs) + foreach(_package IN LISTS ARGN) + if(_package MATCHES "\\.deb$") + list(APPEND _debs "${_package}") + endif() + endforeach() + if(NOT _debs) + return() + endif() + + find_program(_dpkg_deb dpkg-deb REQUIRED NO_CACHE) + find_program(_dpkg_query dpkg-query REQUIRED NO_CACHE) + find_program(_ar ar REQUIRED NO_CACHE) + find_program(_tar tar REQUIRED NO_CACHE) + find_program(_gzip gzip REQUIRED NO_CACHE) + + foreach(_package IN LISTS _debs) + ## CPack passes the packages with their full path in its own scratch directory; the + ## working directory goes next to them, so it never lands outside that directory. + if(NOT IS_ABSOLUTE "${_package}" OR NOT EXISTS "${_package}") + message(FATAL_ERROR "cpack_deb_t64_dependencies.cmake: '${_package}' is not the full path of a package; " + "this script is meant to be run by CPack (CPACK_POST_BUILD_SCRIPTS).") + endif() + get_filename_component(_work_dir "${_package}" DIRECTORY) + string(APPEND _work_dir "/_openms_deb_t64") + file(REMOVE_RECURSE "${_work_dir}") + _openms_deb_rewrite("${_package}" "${_work_dir}") + file(REMOVE_RECURSE "${_work_dir}") + endforeach() +endfunction() + +_openms_deb_add_t64_alternatives(${CPACK_PACKAGE_FILES}) + +cmake_policy(POP) diff --git a/cmake/doc_macros.cmake b/cmake/doc_macros.cmake new file mode 100644 index 000000000..66d5add91 --- /dev/null +++ b/cmake/doc_macros.cmake @@ -0,0 +1,27 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Stephan Aiche $ +# $Authors: Stephan Aiche $ +# -------------------------------------------------------------------------- + +# clear variable to avoid accumulation +set(OPENMS_DOCUMENTATION_DIRECTORIES "" + CACHE + INTERNAL + "List of paths to be searched when the API documentation is generated") + + +#------------------------------------------------------------------------------ +# Registers the given path in the documentation system +# +# @param path_to_document Path containing header files that should be documented +macro(openms_doc_path path_to_document) + set(OPENMS_DOCUMENTATION_DIRECTORIES + ${path_to_document} + ${OPENMS_DOCUMENTATION_DIRECTORIES} + CACHE + INTERNAL + "List of paths to be searched when the API documentation is generated") +endmacro() diff --git a/cmake/export_macros.cmake b/cmake/export_macros.cmake new file mode 100644 index 000000000..47481c4d1 --- /dev/null +++ b/cmake/export_macros.cmake @@ -0,0 +1,70 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Stephan Aiche, Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +include(CMakePackageConfigHelpers) + +#------------------------------------------------------------------------------ +# The CMake package of the GUI layer, OpenMSGUI: +# OpenMSGUIConfig.cmake finds the OpenMS core (COMPONENTS CLI) and Qt, then +# includes the targets below +# OpenMSGUIConfigVersion.cmake +# OpenMSGUITargets.cmake OpenMS::OpenMS_GUI +# installed into ${INSTALL_CMAKE_DIR} (lib/cmake/OpenMSGUI, Windows: CMake) in +# component cmake_gui. The OpenMS core's own OpenMSConfig.cmake delegates its +# component GUI to this package, so find_package(OpenMS COMPONENTS GUI) works +# once the GUI layer is installed into the same prefix. + +set(_OPENMS_GUI_EXPORT_FILE "${OPENMS_GUI_EXPORT_SET}.cmake") + +# clear the lists before we refill them (one list of targets per export set) +foreach(_export_set IN LISTS OPENMS_EXPORT_SETS) + set(_OPENMS_EXPORT_TARGETS_${_export_set} "" CACHE INTERNAL "Targets exported in ${_export_set}.") +endforeach() + +# openms_register_export_target( []) +# Records that the export set (OpenMSGUITargets when omitted) has targets. +macro(openms_register_export_target target_name) + set(_export_set ${ARGN}) + if("${_export_set}" STREQUAL "") + set(_export_set ${OPENMS_GUI_EXPORT_SET}) + endif() + if(NOT "${_export_set}" IN_LIST OPENMS_EXPORT_SETS) + message(FATAL_ERROR "openms_register_export_target(${target_name}): unknown export set '${_export_set}' (known: ${OPENMS_EXPORT_SETS})") + endif() + set(_OPENMS_EXPORT_TARGETS_${_export_set} ${_OPENMS_EXPORT_TARGETS_${_export_set}} ${target_name} + CACHE INTERNAL "Targets exported in ${_export_set}.") + unset(_export_set) +endmacro() + +# Writes OpenMSGUIConfig.cmake and OpenMSGUIConfigVersion.cmake into the build tree and +# installs them. They refer to the installation layout, so the build tree itself is not +# usable as OpenMSGUI_DIR; install the GUI layer (next to the core) instead. +macro(openms_export_targets) + ## the Qt modules OpenMS_GUI links publicly; a consumer must find the same ones + set(OPENMS_GUI_PACKAGE_QT_MODULES Core ${OpenMS_GUI_QT_COMPONENTS} ${OpenMS_GUI_QT_FOUND_COMPONENTS_OPT}) + ## the OpenMS core the GUI layer is compiled against (find_openms.cmake: EXACT) + set(OPENMS_GUI_CORE_VERSION "${OpenMS_VERSION}") + + configure_package_config_file( + "${OPENMS_HOST_DIRECTORY}/cmake/OpenMSGUIConfig.cmake.in" + "${PROJECT_BINARY_DIR}/OpenMSGUIConfig.cmake" + INSTALL_DESTINATION ${INSTALL_CMAKE_DIR} + PATH_VARS INSTALL_SHARE_DIR INSTALL_LIB_DIR INSTALL_BIN_DIR INSTALL_INCLUDE_DIR + ) + + ## a consumer may ask for the GUI layer of 3.7, like for the core; the core it needs is + ## exactly the one it was built against, which OpenMSGUIConfig.cmake checks + write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/OpenMSGUIConfigVersion.cmake" + VERSION ${OPENMS_PACKAGE_VERSION} + COMPATIBILITY SameMinorVersion + ) + + install_file(${PROJECT_BINARY_DIR}/OpenMSGUIConfig.cmake ${INSTALL_CMAKE_DIR} cmake_gui) + install_file(${PROJECT_BINARY_DIR}/OpenMSGUIConfigVersion.cmake ${INSTALL_CMAKE_DIR} cmake_gui) +endmacro() diff --git a/cmake/find_openms.cmake b/cmake/find_openms.cmake new file mode 100644 index 000000000..e4cd981d0 --- /dev/null +++ b/cmake/find_openms.cmake @@ -0,0 +1,135 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Julianus Pfeuffer, Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ +# The dependencies of the GUI layer: +# - the installed OpenMS core with its CLI layer (OpenMS::OpenMS, OpenMS::OpenMS_CLI), +# of exactly the version of this repository: the GUI is compiled against its headers +# - Qt 6 +# Point CMAKE_PREFIX_PATH (or OpenMS_DIR) at the OpenMS installation, e.g. the OpenMS SDK. +#------------------------------------------------------------------------------ + +## An OpenMS whose prefix already holds an installed GUI layer would load its targets +## (OpenMS::OpenMS_GUI) as well; this build provides that target itself. +set(OpenMS_IGNORE_GUI_LAYER TRUE) +find_package(OpenMS ${OPENMS_PACKAGE_VERSION} EXACT CONFIG REQUIRED COMPONENTS CLI) + +## A core from before the split may have been built with its own GUI library. Its +## imported OpenMS::OpenMS_GUI would clash with the library built here, and its installed +## headers would compete with those of this source tree. +if(TARGET OpenMS::OpenMS_GUI) + message(FATAL_ERROR "The OpenMS installation in ${OpenMS_DIR} already includes a GUI library " + "(OpenMS::OpenMS_GUI). Build OpenMS-TOPP-GUI against an OpenMS installation without it " + "(an OpenMS built without WITH_GUI, or installed without the components library_gui and " + "cmake_gui, e.g. the OpenMS SDK).") +endif() +## The same for a core whose tool registry lists the GUI TOPP tools (an OpenMS built with +## WITH_GUI=ON registers them in its OpenMS.tsv even when the GUI library is not installed). +## ToolHandler refuses to start any tool when a name is registered twice, so with the +## registry of this layer next to it every OpenMS tool would fail. +## The registry of this layer itself (OpenMS-GUI.tsv, there when the GUI layer is already +## installed into the core's prefix) lists them as well and is not checked. +file(GLOB _openms_core_registries "${OPENMS_DATA_DIR}/TOOLS/*.tsv") +list(FILTER _openms_core_registries EXCLUDE REGEX "/OpenMS-GUI\\.tsv$") +foreach(_registry IN LISTS _openms_core_registries) + file(STRINGS "${_registry}" _gui_tools_in_core REGEX "^(ExecutePipeline|ImageCreator)\t") + if(_gui_tools_in_core) + message(FATAL_ERROR "The tool registry of the OpenMS installation, ${_registry}, already lists the " + "GUI TOPP tools (ExecutePipeline, ImageCreator): that OpenMS was built with its own GUI " + "(WITH_GUI=ON). Build OpenMS-TOPP-GUI against an OpenMS built without the GUI.") + endif() +endforeach() + +message(STATUS "Building against OpenMS ${OpenMS_VERSION} in ${OpenMS_DIR}") +message(STATUS " shared data of that installation: ${OPENMS_DATA_DIR}") +message(STATUS " executables of that installation: ${OPENMS_BIN_DIR}") + +#------------------------------------------------------------------------------ +# The full version string (e.g. 3.7.0-pre-nightly-2026-11-06). The packages are installed +# into the folder of the core installation, whose name carries this string, so the core's +# string is used whenever it is known: exported by its OpenMSConfig.cmake, or given with +# -DOPENMS_CORE_VERSION_FULLSTRING (CI takes it from the name of the core installer). +# Only otherwise is it derived from this repository's git state, as the core does. +set(OPENMS_CORE_VERSION_FULLSTRING "" CACHE STRING + "Full version string of the OpenMS core installation (e.g. 3.7.0-pre-nightly-2026-11-06). Empty: take it from the core's CMake package, or derive it from git.") + +include(GetGitRevisionDescription) +option(GIT_TRACKING "Embed git information (branch, revision) into the version strings of the packages." ON) +if(GIT_TRACKING) + git_archival_info("${PROJECT_SOURCE_DIR}/.git_archival.txt" + _gui_archival_describe OPENMS_GIT_SHORT_SHA1 OPENMS_GIT_LC_DATE OPENMS_GIT_ARCHIVAL_FOUND) + if(OPENMS_GIT_ARCHIVAL_FOUND) + if(_gui_archival_describe STREQUAL "v${OPENMS_PACKAGE_VERSION}") + set(OPENMS_GIT_SHORT_REFSPEC "${_gui_archival_describe}") + else() + set(OPENMS_GIT_SHORT_REFSPEC "exported") + endif() + set(OPENMS_GIT_INFO_FOUND TRUE) + else() + git_short_info(OPENMS_GIT_SHORT_REFSPEC OPENMS_GIT_SHORT_SHA1 OPENMS_GIT_LC_DATE OPENMS_GIT_INFO_FOUND) + if(OPENMS_GIT_INFO_FOUND AND OPENMS_GIT_SHORT_REFSPEC STREQUAL "HEAD") + git_get_exact_tag(_gui_release_tag --tags --match "v${OPENMS_PACKAGE_VERSION}") + if(_gui_release_tag STREQUAL "v${OPENMS_PACKAGE_VERSION}") + set(OPENMS_GIT_SHORT_REFSPEC "${_gui_release_tag}") + endif() + endif() + endif() +endif() +if(NOT GIT_TRACKING OR NOT OPENMS_GIT_INFO_FOUND) + if(NOT DEFINED OPENMS_GIT_SHORT_SHA1) + set(OPENMS_GIT_SHORT_SHA1 "exported") + endif() + if(NOT DEFINED OPENMS_GIT_SHORT_REFSPEC OR OPENMS_GIT_SHORT_REFSPEC STREQUAL "") + set(OPENMS_GIT_SHORT_REFSPEC "exported") + endif() + if(NOT DEFINED OPENMS_GIT_LC_DATE) + set(OPENMS_GIT_LC_DATE "1970-01-01") + endif() +endif() + +if(OPENMS_CORE_VERSION_FULLSTRING) + set(OPENMS_PACKAGE_VERSION_FULLSTRING "${OPENMS_CORE_VERSION_FULLSTRING}") + set(_gui_fullstring_source "OPENMS_CORE_VERSION_FULLSTRING") +elseif(DEFINED OpenMS_VERSION_FULLSTRING AND NOT OpenMS_VERSION_FULLSTRING STREQUAL "") + set(OPENMS_PACKAGE_VERSION_FULLSTRING "${OpenMS_VERSION_FULLSTRING}") + set(_gui_fullstring_source "the OpenMS CMake package") +else() + ## derived from this repository's git state, exactly as the core does it + if(OPENMS_GIT_SHORT_REFSPEC MATCHES "disabled" OR OPENMS_GIT_SHORT_REFSPEC MATCHES "exported") + string(TIMESTAMP _gui_date "%Y%m%d") + set(OPENMS_PACKAGE_VERSION_FULLSTRING "${OPENMS_PACKAGE_VERSION}-pre-${OPENMS_GIT_SHORT_REFSPEC}-${_gui_date}") + elseif(OPENMS_GIT_SHORT_REFSPEC MATCHES "release*" OR OPENMS_GIT_SHORT_REFSPEC MATCHES "master" + OR OPENMS_GIT_SHORT_REFSPEC STREQUAL "v${OPENMS_PACKAGE_VERSION}") + set(OPENMS_PACKAGE_VERSION_FULLSTRING "${OPENMS_PACKAGE_VERSION}") + else() + string(REGEX REPLACE "[/_]" "-" _gui_refspec "${OPENMS_GIT_SHORT_REFSPEC}") + string(REGEX REPLACE " .*$" "" _gui_lc_date "${OPENMS_GIT_LC_DATE}") + set(OPENMS_PACKAGE_VERSION_FULLSTRING "${OPENMS_PACKAGE_VERSION}-pre-${_gui_refspec}-${_gui_lc_date}") + endif() + set(_gui_fullstring_source "the git state of this repository") + if(NOT "${PACKAGE_TYPE}" STREQUAL "none") + message(WARNING "The full version string of the OpenMS core is unknown (its CMake package does not " + "export it), so the package takes '${OPENMS_PACKAGE_VERSION_FULLSTRING}' from ${_gui_fullstring_source}. " + "The packages are installed into the folder of the core installation, whose name carries the " + "core's string: pass -DOPENMS_CORE_VERSION_FULLSTRING=.") + endif() +endif() +## the part after "-pre-" (empty for a release) +string(REGEX REPLACE "^${OPENMS_PACKAGE_VERSION}(-pre-)?" "" OPENMS_PACKAGE_VERSION_PRERELEASE_IDENTIFIER + "${OPENMS_PACKAGE_VERSION_FULLSTRING}") +message(STATUS " full version string ${OPENMS_PACKAGE_VERSION_FULLSTRING} (from ${_gui_fullstring_source})") + +#------------------------------------------------------------------------------ +# Where the core's TOPP tools are (FuzzyDiff and the tools the TOPPAS pipelines run). +# The OpenMS SDK carries no executables; the tests that need them are then left out. +set(OPENMS_TOPP_BIN_DIR "${OPENMS_BIN_DIR}" CACHE PATH + "Directory with the TOPP tools of the OpenMS core (FuzzyDiff, FileInfo, ...), used by the tests.") + +#------------------------------------------------------------------------------ +# Qt +include(${PROJECT_SOURCE_DIR}/cmake/cmake_findQt.cmake) diff --git a/cmake/generate_applications_component_plist.cmake b/cmake/generate_applications_component_plist.cmake index 05ae64f44..0ccd64fd8 100644 --- a/cmake/generate_applications_component_plist.cmake +++ b/cmake/generate_applications_component_plist.cmake @@ -6,20 +6,20 @@ # $Authors: Julianus Pfeuffer $ # -------------------------------------------------------------------------- -## Writes the component property list that pkgbuild gets for the Applications component -## (PLIST of cpack_add_component in cmake/package_components.cmake) and sets +## Writes the component property list that pkgbuild gets for the component of the GUI +## applications (PLIST of cpack_add_component in cmake/package_components.cmake) and sets ## APPLICATIONS_COMPONENT_PLIST to it. Included by cmake/package_mac_productbuild.cmake, ## after CPACK_PACKAGING_INSTALL_PREFIX is set. ## ## pkgbuild marks every app bundle relocatable by default. The installer then looks for a ## bundle with the same identifier anywhere on the disk and updates that copy instead of ## installing into the package's folder. Our identifiers (de.openms.) are the same in -## every version, so the apps of a new OpenMS ended up in the folder of an older one, or in -## a build tree. pkgbuild reads BundleIsRelocatable only from this plist, not from the +## every version, so the apps of a new OpenMS ended up in the folder of an older one (next to +## a core of another version), or in a build tree. pkgbuild reads BundleIsRelocatable only from this plist, not from the ## Info.plist of a bundle. The other keys keep the values pkgbuild uses by default. ## add_mac_app_bundle() (src/openms_gui/add_mac_bundle.cmake) records every bundle it -## installs; there are none without WITH_GUI. +## installs. get_property(_openms_app_bundles GLOBAL PROPERTY OPENMS_APP_BUNDLES) if(NOT _openms_app_bundles) return() @@ -28,8 +28,8 @@ endif() ## The path is relative to the root pkgbuild packages, which holds the install prefix: ## Applications/OpenMS-/TOPPView.app string(REGEX REPLACE "^/" "" _openms_bundle_dir "${CPACK_PACKAGING_INSTALL_PREFIX}") -## Packages of other branches carry the branch name in the version (CMakeLists.txt), and a -## branch name may contain characters that XML reserves; & first. +## Packages of other branches carry the branch name in the version (cmake/find_openms.cmake), +## and a branch name may contain characters that XML reserves; & first. string(REPLACE "&" "&" _openms_bundle_dir "${_openms_bundle_dir}") string(REPLACE "<" "<" _openms_bundle_dir "${_openms_bundle_dir}") string(REPLACE ">" ">" _openms_bundle_dir "${_openms_bundle_dir}") diff --git a/cmake/install_desktop.cmake b/cmake/install_desktop.cmake new file mode 100644 index 000000000..5ddba1797 --- /dev/null +++ b/cmake/install_desktop.cmake @@ -0,0 +1,57 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ +# Desktop integration on Linux (freedesktop.org), from share/OpenMS/DESKTOP: +# .desktop -> share/applications (menu entries) +# .appdata.xml -> share/metainfo (AppStream metadata, software centers) +# icons//.png -> share/icons/hicolor//apps/.png +# The desktop entries name their icon by its theme name (Icon=TOPPView), which the icon +# theme resolves in hicolor. Installed with the GUI applications; the paths are relative +# to the install prefix (/usr for the Debian and RPM packages). +#------------------------------------------------------------------------------ +if(NOT UNIX OR APPLE) + return() +endif() + +set(_desktop_dir "${OPENMS_HOST_DIRECTORY}/share/OpenMS/DESKTOP") +set(_desktop_build_dir "${OPENMS_HOST_BINARY_DIRECTORY}/share/applications") +file(GLOB _desktop_files "${_desktop_dir}/*.desktop") +## written when configuring: configure again when they change +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${_desktop_files}) +foreach(_desktop_file IN LISTS _desktop_files) + get_filename_component(_app "${_desktop_file}" NAME_WE) + file(READ "${_desktop_file}" _entry) + ## the icon by its theme name, not by a path that nothing installs + string(REGEX REPLACE "(^|\n)Icon=[^\n]*" "\\1Icon=${_app}" _entry "${_entry}") + ## trailing white space is part of a value for the desktop entry parsers + string(REGEX REPLACE "[ \t]+(\n|$)" "\\1" _entry "${_entry}") + file(WRITE "${_desktop_build_dir}/${_app}.desktop" "${_entry}") + install(FILES "${_desktop_build_dir}/${_app}.desktop" + DESTINATION share/applications + COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) +endforeach() + +file(GLOB _appdata_files "${_desktop_dir}/*.appdata.xml") +install(FILES ${_appdata_files} + DESTINATION share/metainfo + COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) + +file(GLOB _icon_sizes LIST_DIRECTORIES true RELATIVE "${_desktop_dir}/icons" "${_desktop_dir}/icons/*x*") +foreach(_size IN LISTS _icon_sizes) + file(GLOB _icons "${_desktop_dir}/icons/${_size}/*.png") + install(FILES ${_icons} + DESTINATION share/icons/hicolor/${_size}/apps + COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) +endforeach() + +unset(_desktop_dir) +unset(_desktop_build_dir) +unset(_desktop_files) +unset(_appdata_files) +unset(_icon_sizes) diff --git a/cmake/install_macros.cmake b/cmake/install_macros.cmake new file mode 100644 index 000000000..787c5ed51 --- /dev/null +++ b/cmake/install_macros.cmake @@ -0,0 +1,205 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Julianus Pfeuffer $ +# $Authors: Stephan Aiche, Julianus Pfeuffer $ +# -------------------------------------------------------------------------- + +# a collection of wrapper for install functions that allows easier usage +# throughout the OpenMS build system + +#------------------------------------------------------------------------------ +# The installed OpenMS package is layered: the core (export set OpenMSTargets, +# components library/cmake), the CLI layer (OpenMSCLITargets, library_cli/cmake_cli), +# both installed by OpenMS, and the GUI layer built here: +# +# layer export set libraries cmake files targets +# GUI OpenMSGUITargets library_gui cmake_gui OpenMS::OpenMS_GUI (links the CLI layer) +# +# The headers are in OpenMS_GUI_headers. The package of the GUI layer is OpenMSGUI +# (OpenMSGUIConfig.cmake, see cmake/export_macros.cmake). +set(OPENMS_GUI_EXPORT_SET "OpenMSGUITargets") +set(OPENMS_EXPORT_SETS ${OPENMS_GUI_EXPORT_SET}) +set(${OPENMS_GUI_EXPORT_SET}_LIBRARY_COMPONENT library_gui) +set(${OPENMS_GUI_EXPORT_SET}_CMAKE_COMPONENT cmake_gui) + +## The runtime dependency set of everything installed here. Packages install it +## (cmake/package_general.cmake) without what the OpenMS core ships itself. +set(OPENMS_GUI_DEPS OPENMS_GUI_DEPS) + +#------------------------------------------------------------------------------ +# Installs the library lib_target_name into the library component of its export +# set and adds it to that export set. +# +# install_library( [HEADERS] [EXPORT_SET ]) +# +# @param lib_target_name The target name of the library that should be installed +# @param EXPORT_SET One of ${OPENMS_EXPORT_SETS}; OpenMSGUITargets when omitted +# @param HEADERS Install the public HEADERS file set in _headers +function(install_library lib_target_name) + cmake_parse_arguments(_install_library "HEADERS" "EXPORT_SET" "" ${ARGN}) + if(_install_library_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "install_library(${lib_target_name}): unexpected arguments ${_install_library_UNPARSED_ARGUMENTS}") + endif() + if(NOT _install_library_EXPORT_SET) + set(_install_library_EXPORT_SET ${OPENMS_GUI_EXPORT_SET}) + endif() + if(NOT _install_library_EXPORT_SET IN_LIST OPENMS_EXPORT_SETS) + message(FATAL_ERROR "install_library(${lib_target_name}): unknown export set '${_install_library_EXPORT_SET}' (known: ${OPENMS_EXPORT_SETS})") + endif() + set(_component ${${_install_library_EXPORT_SET}_LIBRARY_COMPONENT}) + set(_header_install_args) + if(_install_library_HEADERS) + set(_header_install_args FILE_SET HEADERS DESTINATION ${INSTALL_INCLUDE_DIR} + COMPONENT ${lib_target_name}_headers) + endif() + install(TARGETS ${lib_target_name} + RUNTIME_DEPENDENCY_SET ${OPENMS_GUI_DEPS} + EXPORT ${_install_library_EXPORT_SET} + LIBRARY DESTINATION ${INSTALL_LIB_DIR} COMPONENT ${_component} + ARCHIVE DESTINATION ${INSTALL_LIB_DIR} COMPONENT ${_component} + RUNTIME DESTINATION ${INSTALL_LIB_DIR} COMPONENT ${_component} + ${_header_install_args} + ) + # MSVC writes the debug information of a DLL to a .pdb file next to it (in a Debug + # or RelWithDebInfo build; a Release build has none, hence OPTIONAL). + # TARGET_PDB_FILE is only defined for shared libraries and executables. + get_target_property(_type ${lib_target_name} TYPE) + if(MSVC AND _type STREQUAL "SHARED_LIBRARY") + install(FILES $ + DESTINATION ${INSTALL_LIB_DIR} COMPONENT ${_component} OPTIONAL) + endif() +endfunction() + +#------------------------------------------------------------------------------ +# What every installed header may include, as regular expressions matched against the +# whole include name. A library adds the headers of its own PUBLIC dependencies with +# openms_add_library(... ALLOWED_PUBLIC_INCLUDES ...), e.g. Qt for the GUI layer. +set(OPENMS_PUBLIC_INCLUDE_ALLOWLIST + "OpenMS/.*" # OpenMS' own headers + "[a-z_]+" # the C++ standard library: , , ... + "sys/.*" "windows\\.h" "winternl\\.h" # operating-system headers + "omp\\.h") # OpenMP, a PUBLIC dependency of libOpenMS + +#------------------------------------------------------------------------------ +# Reject every include of an installed header that is not on the allowlist above. +# openms_validate_public_headers(
... [ALLOW ...]) +# +# All dependencies of OpenMS are installed into one prefix, so an include of a PRIVATE +# dependency resolves through a sibling package's include directory on the machine that +# builds OpenMS, and the compiler header checks pass. It only fails downstream, where the +# package is missing. An allowlist rather than a list of known private dependencies means +# that a dependency added later is rejected too, without anyone having to list it. +# Keep this configure-time guard independent of the header installation method. +function(openms_validate_public_headers) + cmake_parse_arguments(PARSE_ARGV 0 _validate "" "" "ALLOW") + set(_allowed ${OPENMS_PUBLIC_INCLUDE_ALLOWLIST} ${_validate_ALLOW}) + list(JOIN _allowed "|" _allowed_regex) + set(_allowed_regex "^(${_allowed_regex})$") + foreach(_header IN LISTS _validate_UNPARSED_ARGUMENTS) + # Header lists are relative to the calling source directory. + # Generated headers in the build tree may not exist yet at this point; they come from OpenMS' + # own templates, so only what is already on disk is scanned. + set(_header_to_scan "${_header}") + if (NOT IS_ABSOLUTE "${_header_to_scan}") + set(_header_to_scan "${CMAKE_CURRENT_SOURCE_DIR}/${_header_to_scan}") + endif() + if (EXISTS "${_header_to_scan}" AND NOT IS_DIRECTORY "${_header_to_scan}") + file(STRINGS "${_header_to_scan}" _include_lines REGEX "^[ \t]*#[ \t]*include[ \t]*[<\"]") + set(_rejected) + foreach(_line IN LISTS _include_lines) + string(REGEX REPLACE "^[ \t]*#[ \t]*include[ \t]*[<\"]([^>\"]*)[>\"].*$" "\\1" _include "${_line}") + if (NOT _include MATCHES "${_allowed_regex}") + list(APPEND _rejected "${_include}") + endif() + endforeach() + if (_rejected) + list(JOIN _rejected ", " _rejected) + message(FATAL_ERROR "Installed header ${_header} includes ${_rejected}, which downstream " + "builds cannot rely on. For a PRIVATE dependency, keep the include in the .cpp file " + "or expose OpenMS types instead. For a PUBLIC one, which OpenMSGUIConfig.cmake " + "finds for consumers, allow its headers with " + "openms_add_library(... ALLOWED_PUBLIC_INCLUDES ).") + endif() + endif() + endforeach() +endfunction() + +#------------------------------------------------------------------------------ +# Installs the tool tool_target_name +# +# install_tool( [COMPONENT ]) +# +# @param tool_target_name The target name of the tool that should be installed +# @param COMPONENT The install component; ${OPENMS_GUI_APPLICATIONS_COMPONENT} +# (see the top-level CMakeLists.txt) when omitted. +function(install_tool tool_target_name) + cmake_parse_arguments(_install_tool "" "COMPONENT" "" ${ARGN}) + if(_install_tool_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "install_tool(${tool_target_name}): unexpected arguments ${_install_tool_UNPARSED_ARGUMENTS}") + endif() + if(NOT _install_tool_COMPONENT) + set(_install_tool_COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) + endif() + install(TARGETS ${tool_target_name} RUNTIME_DEPENDENCY_SET ${OPENMS_GUI_DEPS} + RUNTIME DESTINATION ${INSTALL_BIN_DIR} COMPONENT ${_install_tool_COMPONENT} + BUNDLE DESTINATION ${INSTALL_BIN_DIR} COMPONENT ${_install_tool_COMPONENT} + ) +endfunction() + +#------------------------------------------------------------------------------ +# Installs a given directory +# @param directory The directory to install +# @param destination The destination (relative to the prefix) where it should be installed +# @param component The component to which to the directory belongs +macro(install_directory directory destination component) + install(DIRECTORY ${directory} + DESTINATION ${destination} + COMPONENT ${component} + FILE_PERMISSIONS OWNER_WRITE OWNER_READ + GROUP_READ + WORLD_READ + DIRECTORY_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_READ + WORLD_EXECUTE WORLD_READ + REGEX "^\\..*" EXCLUDE ## Exclude hidden files (svn, git, DSStore) + REGEX ".*\\/\\..*" EXCLUDE ## Exclude hidden files in subdirectories + ) +endmacro() + +#------------------------------------------------------------------------------ +# Installs a given file +# @param directory The file to install +# @param destination The destination (relative to the prefix) where it should be installed +# @param component The component to which to the file belongs +macro(install_file file destination component) + install(FILES ${file} + DESTINATION ${destination} + COMPONENT ${component}) +endmacro() + +#------------------------------------------------------------------------------ +# Execute the given code while executing the install target +# @param code_snippet The code to execute +# @param component The component to which the code will be associated +macro(install_code code_snippet component) + install(CODE ${code_snippet} + COMPONENT ${component}) +endmacro() + +#------------------------------------------------------------------------------ +# Installs the exported targets of the GUI layer, OpenMSGUITargets.cmake, in +# component cmake_gui. Consumers see the library as OpenMS::OpenMS_GUI; it refers +# to the core and CLI layers as OpenMS::OpenMS and OpenMS::OpenMS_CLI, which +# OpenMSGUIConfig.cmake finds first. +macro(install_export_targets ) + foreach(_export_set IN LISTS OPENMS_EXPORT_SETS) + if(_OPENMS_EXPORT_TARGETS_${_export_set}) + install(EXPORT ${_export_set} + NAMESPACE OpenMS:: + DESTINATION ${INSTALL_CMAKE_DIR} + COMPONENT ${${_export_set}_CMAKE_COMPONENT}) + endif() + endforeach() +endmacro() diff --git a/cmake/messages.cmake b/cmake/messages.cmake new file mode 100644 index 000000000..51631b5f2 --- /dev/null +++ b/cmake/messages.cmake @@ -0,0 +1,37 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Stephan Aiche, Chris Bielow, Julianus Pfeuffer $ +# -------------------------------------------------------------------------- + +# The 'targets' target lists the most important targets. +add_custom_target(targets + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "==========================================================================" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "The following targets are available:" + COMMAND ${CMAKE_COMMAND} -E echo " all/ALL_BUILD builds the GUI library, the GUI tools and the tests" + COMMAND ${CMAKE_COMMAND} -E echo " OpenMS_GUI builds the GUI library" + COMMAND ${CMAKE_COMMAND} -E echo " GUI builds the GUI tools (TOPPView, TOPPAS, INIFileEditor, ExecutePipeline, ImageCreator)" + COMMAND ${CMAKE_COMMAND} -E echo " test/RUN_TESTS runs the tests (ctest)" + COMMAND ${CMAKE_COMMAND} -E echo " Tutorials_build builds the GUI code examples of the tutorials" + COMMAND ${CMAKE_COMMAND} -E echo " doc builds the documentation of the GUI layer" + COMMAND ${CMAKE_COMMAND} -E echo " dist builds the installer (configure with -DPACKAGE_TYPE=...)" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "==========================================================================" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMENT "The most important targets of OpenMS-TOPP-GUI" + VERBATIM) + +message(STATUS "") +message(STATUS "-----------------------------------------------------------------") +message(STATUS "") +message(STATUS "You have successfully configured OpenMS-TOPP-GUI ${OPENMS_PACKAGE_VERSION}") +message(STATUS "against OpenMS ${OpenMS_VERSION} (${OpenMS_DIR}).") +message(STATUS "") +message(STATUS "For a list of the most important targets, build the target 'targets'.") +message(STATUS "") +message(STATUS "-----------------------------------------------------------------") +message(STATUS "") diff --git a/cmake/min_compiler_versions.cmake b/cmake/min_compiler_versions.cmake new file mode 100644 index 000000000..b6b603aac --- /dev/null +++ b/cmake/min_compiler_versions.cmake @@ -0,0 +1,100 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Chris Bielow $ +# $Authors: Chris Bielow $ +# -------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ +# Single source of truth for the minimum supported compiler versions. +# +# The same minimums as the OpenMS core (cmake/min_compiler_versions.cmake there): +# the GUI layer is compiled against the core's headers. The checks at the bottom +# abort configuration with a readable message on an unsupported compiler. +#------------------------------------------------------------------------------ + +# --- MSVC --------------------------------------------------------------------- +# Several spellings of the same release, because each consumer needs a different one: +# OPENMS_MIN_MSVC_VERSION - the MSVC_VERSION integer, for the check below +# OPENMS_MIN_MSVC_TOOLSET - the toolset number, as reported by %VCToolsVersion% +# OPENMS_MIN_MSVC_IDE - the VS release, which is what users actually install +# OPENMS_MIN_MSVC_YEAR - the VS product year, as it appears in the product name +# and in the default install path +# OPENMS_MIN_MSVC_GENERATOR - the matching CMake generator name +# +# The binding constraint is Apache Arrow. Toolsets older than +# 14.44 carry a bug in the standard library's formatter for zoned_time +# durations coarser than seconds (LWG-4124, fixed by microsoft/STL#5155), which +# Arrow's std::chrono backend trips over while compiling strftime for Date32. +# The failure surfaces as an unreadable template error inside , hence +# the explicit guard. +set(OPENMS_MIN_MSVC_VERSION 1944) +set(OPENMS_MIN_MSVC_TOOLSET "14.44") +set(OPENMS_MIN_MSVC_IDE "17.14") +set(OPENMS_MIN_MSVC_YEAR "2022") +# The generator name embeds the VS major version (the "17" of 17.14) and the product +# year, so it changes whenever either does. Derived rather than spelled out, so there +# is still only one place to edit. +string(REGEX REPLACE "\\..*$" "" _openms_msvc_major "${OPENMS_MIN_MSVC_IDE}") +set(OPENMS_MIN_MSVC_GENERATOR "Visual Studio ${_openms_msvc_major} ${OPENMS_MIN_MSVC_YEAR}") +unset(_openms_msvc_major) + +# --- GCC / Clang / AppleClang ------------------------------------------------- +# Driven by C++23 feature support. +set(OPENMS_MIN_GCC "13") +set(OPENMS_MIN_CLANG "17") +# AppleClang reports its own version scheme; 16 is what ships with Xcode 16. +set(OPENMS_MIN_APPLECLANG "16") +set(OPENMS_MIN_XCODE "16") + +# --- CMake -------------------------------------------------------------------- +# Not a compiler, but the same "documentation must not drift from the build +# system" problem, and the reason for the sync comment on cmake_minimum_required() +# in the top-level CMakeLists.txt. Read it back from CMake itself rather than +# repeating the number, so it can never disagree. +# +# This is captured at top-level scope, which is also the only scope that declares +# a cmake_minimum_required() -- see the note there on why subdirectories must not +# declare their own. +set(OPENMS_MIN_CMAKE "${CMAKE_MINIMUM_REQUIRED_VERSION}") + +#------------------------------------------------------------------------------ +# Enforcement +#------------------------------------------------------------------------------ +# Test CMAKE_CXX_COMPILER_ID rather than the MSVC variable: MSVC is also true +# for clang-cl, which has its own version scheme and is covered by the Clang +# branch below. +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if(MSVC_VERSION LESS OPENMS_MIN_MSVC_VERSION) + message(FATAL_ERROR + "OpenMS requires Visual Studio ${OPENMS_MIN_MSVC_YEAR} version ${OPENMS_MIN_MSVC_IDE} or later " + "(MSVC toolset ${OPENMS_MIN_MSVC_TOOLSET}+, MSVC_VERSION >= ${OPENMS_MIN_MSVC_VERSION}), " + "but found MSVC_VERSION ${MSVC_VERSION} (cl.exe ${CMAKE_CXX_COMPILER_VERSION}).\n" + "\n" + "Please update Visual Studio.") + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS OPENMS_MIN_GCC) + message(FATAL_ERROR + "OpenMS requires GCC ${OPENMS_MIN_GCC} or later for C++23 support, " + "but found ${CMAKE_CXX_COMPILER_VERSION}.") + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS OPENMS_MIN_APPLECLANG) + message(FATAL_ERROR + "OpenMS requires Apple Clang ${OPENMS_MIN_APPLECLANG} or later " + "(ships with Xcode ${OPENMS_MIN_XCODE}) for C++23 support, " + "but found ${CMAKE_CXX_COMPILER_VERSION}.") + endif() +elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS OPENMS_MIN_CLANG) + message(FATAL_ERROR + "OpenMS requires Clang ${OPENMS_MIN_CLANG} or later for C++23 support, " + "but found ${CMAKE_CXX_COMPILER_VERSION}.") + endif() +else() + message(STATUS + "Compiler '${CMAKE_CXX_COMPILER_ID}' (${CMAKE_CXX_COMPILER_VERSION}) is not one of the " + "officially supported compilers. No minimum version is enforced; the build may or may not work.") +endif() diff --git a/cmake/package_components.cmake b/cmake/package_components.cmake new file mode 100644 index 000000000..1750f08c0 --- /dev/null +++ b/cmake/package_components.cmake @@ -0,0 +1,76 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Julianus Pfeuffer $ +# $Authors: Julianus Pfeuffer $ +# -------------------------------------------------------------------------- + +## The install components of the GUI layer. The OpenMS core (libraries, TOPP tools, shared +## data) is not part of these packages: it is installed by the core's own package, which +## the GUI packages require. +## library_gui libOpenMS_GUI (cmake/install_macros.cmake) +## GUIApplications TOPPView, TOPPAS, INIFileEditor, ExecutePipeline, ImageCreator, the +## tool registry OpenMS-GUI.tsv, the style sheet and, on Linux, the +## desktop integration +## Dependencies Windows and macOS: Qt and its plugins, qt.conf, their licenses +## doc_gui the documentation of the GUI layer (if it was built) +## OpenMS_GUI_headers, cmake_gui +## the headers and the CMake package, for programs that use the GUI +## library (the macOS package offers them, unselected by default) +cpack_add_install_type(recommended DISPLAY_NAME "Recommended") +cpack_add_install_type(full DISPLAY_NAME "Full") +cpack_add_install_type(minimal DISPLAY_NAME "Minimal") + +set(_openms_gui_library_depends) +if(OPENMS_GUI_BUNDLES_QT) + cpack_add_component(Dependencies + DISPLAY_NAME "Qt" + DESCRIPTION "The Qt libraries and plugins the GUI applications need." + REQUIRED + INSTALL_TYPES recommended full minimal + ) + set(_openms_gui_library_depends DEPENDS Dependencies) +endif() +cpack_add_component(library_gui + DISPLAY_NAME "GUI library" + DESCRIPTION "The GUI library (OpenMS_GUI), needed by TOPPView, TOPPAS and the other GUI applications." + REQUIRED + ${_openms_gui_library_depends} + INSTALL_TYPES recommended full minimal + ) +## The macOS package hands pkgbuild a component plist for the app bundles, which keeps the +## installer from relocating them (cmake/generate_applications_component_plist.cmake). +set(_openms_applications_plist) +if(APPLICATIONS_COMPONENT_PLIST) + set(_openms_applications_plist PLIST "${APPLICATIONS_COMPONENT_PLIST}") +endif() +cpack_add_component(${OPENMS_GUI_APPLICATIONS_COMPONENT} + DISPLAY_NAME "OpenMS GUI applications" + DESCRIPTION "TOPPView, TOPPAS, INIFileEditor and the TOPP tools that need the GUI library (ExecutePipeline, ImageCreator)." + REQUIRED + DEPENDS library_gui + INSTALL_TYPES recommended full minimal + ${_openms_applications_plist} + ) +cpack_add_component(doc_gui + DISPLAY_NAME "Documentation" + DESCRIPTION "Documentation of the GUI applications and the GUI library." + INSTALL_TYPES recommended full + ) +cpack_add_component(OpenMS_GUI_headers + DISPLAY_NAME "GUI library headers" + DESCRIPTION "The headers of the GUI library, for programs that use it." + DEPENDS library_gui + DISABLED + INSTALL_TYPES full + ) +cpack_add_component(cmake_gui + DISPLAY_NAME "GUI library CMake package" + DESCRIPTION "The CMake package of the GUI library (OpenMSGUIConfig.cmake), for programs that use it." + DEPENDS OpenMS_GUI_headers + DISABLED + INSTALL_TYPES full + ) +unset(_openms_gui_library_depends) +unset(_openms_applications_plist) diff --git a/cmake/package_deb.cmake b/cmake/package_deb.cmake new file mode 100644 index 000000000..87045e0bd --- /dev/null +++ b/cmake/package_deb.cmake @@ -0,0 +1,88 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Julianus Pfeuffer $ +# $Authors: Julianus Pfeuffer $ +# -------------------------------------------------------------------------- + +## The Debian package openms-gui. It installs into /usr (CPack's default prefix), like the +## package openms of the core, and depends on exactly the openms of the same version. + +set(CPACK_DEBIAN_PACKAGE_NAME "openms-gui") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "OpenMS Inc ") +## The core's file name with "OpenMS-" replaced by "OpenMS-GUI-" +if((DEFINED ENV{CPACK_PACKAGE_FILE_NAME}) AND (NOT "$ENV{CPACK_PACKAGE_FILE_NAME}" STREQUAL "")) + set(CPACK_PACKAGE_FILE_NAME "$ENV{CPACK_PACKAGE_FILE_NAME}") +else() + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${OPENMS_PACKAGE_VERSION_FULLSTRING}-Debian-Linux-${CMAKE_SYSTEM_PROCESSOR}") +endif() + +## The control version as the core's package has it: a package built from anything but a +## release (a nightly, a branch) carries its prerelease identifier after a '~', e.g. +## 3.7.0~nightly.2026.11.07. dpkg sorts '~' before everything, so it sorts below the release. +## Without a Debian revision the version may not contain '-', so every character outside +## [A-Za-z0-9.+~] becomes '.'. +set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}") +if(OPENMS_PACKAGE_VERSION_PRERELEASE_IDENTIFIER) + string(REGEX REPLACE "[^A-Za-z0-9.+~]" "." _openms_deb_prerelease "${OPENMS_PACKAGE_VERSION_PRERELEASE_IDENTIFIER}") + set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}~${_openms_deb_prerelease}") +endif() + +## The GUI is built against the headers of exactly this core and installed into its prefix, +## so it needs the openms package of the same version. CPack adds what dpkg-shlibdeps derives +## (the distribution's Qt) to this. +set(CPACK_DEBIAN_PACKAGE_DEPENDS "openms (= ${CPACK_DEBIAN_PACKAGE_VERSION})") +## Until 3.7.0 the package openms contained the GUI (/usr/bin/TOPPView, libOpenMS_GUI.so, +## share/OpenMS/GUISTYLE, ...); the GUI files now belong to openms-gui. +set(CPACK_DEBIAN_PACKAGE_REPLACES "openms (<< 3.7.0~)") +set(CPACK_DEBIAN_PACKAGE_BREAKS "openms (<< 3.7.0~)") + +set(CPACK_DEBIAN_ARCHIVE_TYPE "gnutar") +set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") +set(CPACK_DEBIAN_PACKAGE_SECTION "science") +set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://www.openms.de") + +## Derive the dependencies from the built binaries. libOpenMS and libOpenMS_CLI come from the +## package openms, which records no shlibs information for them; with the core's library +## directory (the SDK's, or /usr/lib for the installed package) as a private directory, +## dpkg-shlibdeps accepts them without a package; the Depends above covers them. +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS "${OPENMS_LIB_DIR}") +set(CPACK_DEBIAN_PACKAGE_DEBUG ON) + +## Some libraries reach the staging tree with empty RUNPATH entries, which the loader reads +## as the current working directory (the release workflow builds with PACKAGE_TYPE=none and +## packages after reconfiguring; the script explains how). The script removes them. +list(APPEND CPACK_PRE_BUILD_SCRIPTS "${CMAKE_CURRENT_LIST_DIR}/cpack_clean_runpath.cmake") +## dpkg-shlibdeps names the packages of the build host, which in Ubuntu 24.04 carry the t64 +## suffix of the 64-bit time_t transition (libqt6gui6t64). The script adds the old names as +## alternatives, for the releases that do not have the t64 packages (see the script). +list(APPEND CPACK_POST_BUILD_SCRIPTS "${CMAKE_CURRENT_LIST_DIR}/cpack_deb_t64_dependencies.cmake") + +## One package with the GUI applications and the library; Qt comes from the distribution. +## The headers and the CMake package are not packaged, as in the core's package. +set(CPACK_COMPONENTS_ALL library_gui ${OPENMS_GUI_APPLICATIONS_COMPONENT}) +if(ENABLE_DOCS) + list(APPEND CPACK_COMPONENTS_ALL doc_gui) +endif() +## Without component packaging, CPack installs everything into one package: the list above +## only takes effect with CPACK_DEB_COMPONENT_INSTALL and an all-in-one package. +set(CPACK_DEB_COMPONENT_INSTALL ON) +set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) + +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "graphical applications of OpenMS for LC/MS data") +## CPack indents the lines and turns empty ones into " ." +set(CPACK_PACKAGE_DESCRIPTION "OpenMS is a package for LC/MS data management and analysis. + +This package contains the graphical applications of OpenMS: TOPPView, a viewer +for mass spectrometry data, TOPPAS, an editor and runner for pipelines of TOPP +tools, INIFileEditor, an editor for the parameter files of the TOPP tools, and +the TOPP tools that need the GUI library (ExecutePipeline, ImageCreator). The +TOPP tools and the OpenMS library are in the package openms.") + +## Create own target because you cannot "depend" on the internal target 'package' +add_custom_target(dist + COMMAND cpack -G ${CPACK_GENERATOR} + COMMENT "Building ${CPACK_GENERATOR} package" +) diff --git a/cmake/package_general.cmake b/cmake/package_general.cmake new file mode 100644 index 000000000..128814cf0 --- /dev/null +++ b/cmake/package_general.cmake @@ -0,0 +1,177 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Julianus Pfeuffer $ +# $Authors: Stephan Aiche, Julianus Pfeuffer $ +# -------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ +# General definitions of the GUI packages (all package types). +# +# The packages of this repository are add-ons: they install the GUI layer into the +# installation of the OpenMS core of the same version (same prefix, same bin/, lib/ and +# share/OpenMS), because libOpenMS resolves the shared data, the tool registry and the +# TOPP executables relative to the executable. They must therefore never contain a +# file the core installs itself. +#------------------------------------------------------------------------------ +set(CPACK_PACKAGE_NAME "OpenMS-GUI") +set(CPACK_PACKAGE_VENDOR "OpenMS.de") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "OpenMS GUI - TOPPView, TOPPAS and INIFileEditor for mass spectrometry") +set(CPACK_PACKAGE_VERSION "${OPENMS_PACKAGE_VERSION}") +set(CPACK_PACKAGE_VERSION_MAJOR "${OPENMS_PACKAGE_VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${OPENMS_PACKAGE_VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${OPENMS_PACKAGE_VERSION_PATCH}") +## the folder of the core installation, which the packages install into +set(CPACK_PACKAGE_INSTALL_DIRECTORY "OpenMS-${OPENMS_PACKAGE_VERSION_FULLSTRING}") +set(CPACK_PACKAGE_DESCRIPTION_FILE ${PROJECT_SOURCE_DIR}/cmake/OpenMSPackageDescriptionFile.txt) +set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/License.txt) +set(CPACK_RESOURCE_FILE_WELCOME ${PROJECT_SOURCE_DIR}/cmake/OpenMSPackageResourceWelcomeFile.txt) +set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/cmake/OpenMSPackageResourceReadme.txt) +set(CPACK_STRIP_FILES TRUE) # to save some space in the installers + +## The logo shown by the macOS installer +set(OPENMS_LOGOSMALL_NAME openms_logo_corner_small.png) + +## Windows and macOS packages bundle Qt; the Linux packages depend on the Qt of the distribution. +if(WIN32 OR APPLE) + set(OPENMS_GUI_BUNDLES_QT TRUE) +else() + set(OPENMS_GUI_BUNDLES_QT FALSE) +endif() + +## QtWebEngine needs its helper process (QtWebEngineProcess), resources and translations next +## to the libraries, which nothing here deploys. A runner that happens to have WebEngine must +## not produce a TOPPView that fails at startup. +if(OPENMS_GUI_BUNDLES_QT AND Qt6WebEngineWidgets_FOUND) + message(FATAL_ERROR "Qt6WebEngineWidgets was found, but the ${PACKAGE_TYPE} package cannot deploy QtWebEngine. " + "Configure the package build with -DNO_WEBENGINE_WIDGETS=ON.") +endif() + +#------------------------------------------------------------------------------ +# Runtime dependencies (Qt and the libraries it needs) +#------------------------------------------------------------------------------ +## Everything the OpenMS core ships is left out: its libraries (OpenMS, OpenMS_CLI, +## OpenSwathAlgo), the OpenMP runtime and every other file in the library and executable +## directories of the core installation this is built against (the libraries the core +## bundles). Otherwise the GUI package would overwrite them, a Debian package would +## conflict with the core's, and the Windows uninstaller of the GUI would delete them. +## +## PRE_EXCLUDE_REGEXES are matched against the names of the dependencies as the binaries +## record them (libOpenMS.so, @rpath/libOpenMS.dylib, openms.dll; on Windows in lower +## case), before CMake resolves them, so that their own dependencies are not followed. +## POST_EXCLUDE_REGEXES are matched against the resolved paths. +function(_openms_gui_regex_escape out_var text) + string(REGEX REPLACE "([][+.*()^$?|\\\\{}])" "\\\\\\1" _escaped "${text}") + set(${out_var} "${_escaped}" PARENT_SCOPE) +endfunction() + +set(_core_pre_exclude + ## the libraries of the core (OpenMS_GUI is built here and installed by library_gui) + "(^|[/\\\\])(lib)?(openms|openms_cli|openswathalgo|openms_gui|OpenMS|OpenMS_CLI|OpenSwathAlgo|OpenMS_GUI)d?\\.(dll|dylib|so)" + ## the OpenMP runtime of the core + "(^|[/\\\\])(lib)?(omp|gomp|iomp5|vcomp[0-9]*d?)\\.") +set(_core_post_exclude "") +foreach(_core_dir IN ITEMS "${OPENMS_LIB_DIR}" "${OPENMS_BIN_DIR}") + if(NOT IS_DIRECTORY "${_core_dir}") + continue() + endif() + ## by path (also as the real path, in case the prefix is reached through a link) ... + file(REAL_PATH "${_core_dir}" _core_dir_real) + foreach(_dir IN ITEMS "${_core_dir}" "${_core_dir_real}") + _openms_gui_regex_escape(_dir_regex "${_dir}") + list(APPEND _core_post_exclude "^${_dir_regex}/") + endforeach() + ## ... and by name, so that a library of the same name found elsewhere (in the Qt or + ## Homebrew prefix) is not shipped either: the installed core provides it. + file(GLOB _core_files LIST_DIRECTORIES true RELATIVE "${_core_dir}" + "${_core_dir}/*.dll" "${_core_dir}/*.dylib" "${_core_dir}/*.framework") + foreach(_core_file IN LISTS _core_files) + if(WIN32) + string(TOLOWER "${_core_file}" _core_file) + endif() + _openms_gui_regex_escape(_name_regex "${_core_file}") + if(_core_file MATCHES "\\.framework$") + list(APPEND _core_pre_exclude "(^|/)${_name_regex}/") + else() + list(APPEND _core_pre_exclude "(^|[/\\\\])${_name_regex}$") + endif() + endforeach() +endforeach() +list(REMOVE_DUPLICATES _core_pre_exclude) +list(REMOVE_DUPLICATES _core_post_exclude) + +if(WIN32) + set(PRE_EXCLUDE + ## system DLLs, direct dependencies of the GUI binaries + "api-ms" "ext-ms" + ## detected as dependencies, but cannot be resolved (CMake folds the names to lower case) + "hvsi" "pdmutilities" + ## the MSVC runtime, which the core installer ships (InstallRequiredSystemLibraries) + "vcruntime" "msvcp" "concrt" "vccorlib" "ucrtbase") + ## every DLL of C:\Windows\System32, however CMake spells the folder + set(POST_EXCLUDE ".*[\\/][Ss][Yy][Ss][Tt][Ee][Mm]32[\\/].*") +elseif(APPLE) + set(PRE_EXCLUDE "/usr/lib" "/System/") + set(POST_EXCLUDE) +endif() +list(APPEND PRE_EXCLUDE ${_core_pre_exclude}) +list(APPEND POST_EXCLUDE ${_core_post_exclude}) +## the build tree of this project: OpenMS_GUI is installed by library_gui. Not on Windows: +## there the build copies the Qt DLLs next to the binaries (add_library_macros.cmake), and a +## DLL in the folder of the binary is resolved there first, so excluding the build tree +## would drop Qt from the package (OpenMS_GUI is excluded by name above). These copies are +## then also the first place searched for the dependencies of the Qt plugins (below): +## the same DLL found in two folders is a fatal conflict. +if(NOT WIN32) + _openms_gui_regex_escape(_build_dir_regex "${PROJECT_BINARY_DIR}") + list(APPEND POST_EXCLUDE "^${_build_dir_regex}/") +endif() + +if(OPENMS_GUI_BUNDLES_QT) + ## Where the dependencies are looked for: the prefixes of CMAKE_PREFIX_PATH and the Qt + ## installation (on Windows, DLLs have no RPATH that would tell). + list(TRANSFORM CMAKE_PREFIX_PATH APPEND "/bin" OUTPUT_VARIABLE _dep_bin_dirs) + list(TRANSFORM CMAKE_PREFIX_PATH APPEND "/lib" OUTPUT_VARIABLE _dep_lib_dirs) + set(RUNTIME_DEP_SEARCH_DIRS ${_dep_bin_dirs} ${_dep_lib_dirs}) + if(WIN32) + list(PREPEND RUNTIME_DEP_SEARCH_DIRS "$") + endif() + if(QT6_INSTALL_PREFIX) + list(APPEND RUNTIME_DEP_SEARCH_DIRS "${QT6_INSTALL_PREFIX}/bin" "${QT6_INSTALL_PREFIX}/lib") + endif() + + ## The bundled libraries are writable by their owner only, like any installed library. + ## The packaging steps that modify them (code signing on macOS) run as their owner. + install(RUNTIME_DEPENDENCY_SET ${OPENMS_GUI_DEPS} + DESTINATION ${INSTALL_LIB_DIR} + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE + COMPONENT Dependencies + PRE_EXCLUDE_REGEXES ${PRE_EXCLUDE} + POST_EXCLUDE_REGEXES ${POST_EXCLUDE} + DIRECTORIES ${RUNTIME_DEP_SEARCH_DIRS}) +endif() + +#------------------------------------------------------------------------------ +# Third-party licenses of what the package bundles: Qt and, on macOS, the Homebrew +# formulae Qt comes from (cmake/third_party_licenses.cmake). They are listed in +# share/OpenMS/THIRD-PARTY-NOTICES-GUI.txt (cmake/third_party_notices.cmake), next to the +# THIRD-PARTY-NOTICES.txt of the core. +#------------------------------------------------------------------------------ +if(OPENMS_GUI_BUNDLES_QT) + set(_openms_homebrew_formulae "") + set(_openms_homebrew_prefix "") + if(APPLE) + set(_openms_homebrew_paths) + foreach(_openms_qt_component IN ITEMS Core ${OpenMS_GUI_QT_COMPONENTS}) + list(APPEND _openms_homebrew_paths "${Qt6${_openms_qt_component}_DIR}") + endforeach() + openms_homebrew_formulae_of(_openms_homebrew_formulae _openms_homebrew_prefix ${_openms_homebrew_paths}) + endif() + openms_install_third_party_licenses(QT_VERSION "${Qt6Core_VERSION}" + HOMEBREW_PREFIX "${_openms_homebrew_prefix}" + HOMEBREW_FORMULAE ${_openms_homebrew_formulae}) +endif() diff --git a/cmake/package_mac_productbuild.cmake b/cmake/package_mac_productbuild.cmake new file mode 100644 index 000000000..edd4e2c44 --- /dev/null +++ b/cmake/package_mac_productbuild.cmake @@ -0,0 +1,160 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Julianus Pfeuffer $ +# $Authors: Julianus Pfeuffer $ +# -------------------------------------------------------------------------- + +## The macOS installer of the GUI (productbuild). It installs into the folder of the OpenMS +## core package of the same version, /Applications/OpenMS-: the app bundles +## into its root (next to lib/), ExecutePipeline and ImageCreator into its bin/, the GUI +## library, Qt and its plugins into its lib/. The bundles are not self-contained: they load +## Qt and the OpenMS libraries from lib/. +## +## For info about this CPack generator and its capabilities, see the CMake documentation +## For info about productbuild and the flat package format see https://matthew-brett.github.io/docosx/flat_packages.html + +## The folder of the core package: its name is "OpenMS-", not "${CPACK_PACKAGE_NAME}-". +set(CPACK_PACKAGING_INSTALL_PREFIX "/Applications/OpenMS-${OPENMS_PACKAGE_VERSION_FULLSTRING}") + +## The component plist that keeps the installer from relocating the app bundles; it needs +## the install prefix (APPLICATIONS_COMPONENT_PLIST, used by package_components.cmake). +include(${PROJECT_SOURCE_DIR}/cmake/generate_applications_component_plist.cmake) + +## The component packages are de.openms.gui., distinct from the core's +## de.openms.. +set(CPACK_PRODUCTBUILD_IDENTIFIER "de.openms.gui") +set(CPACK_PRODUCTBUILD_RESOURCES_DIR ${PROJECT_SOURCE_DIR}/cmake/MacOSX) +set(CPACK_PRODUCTBUILD_BACKGROUND ${OPENMS_LOGOSMALL_NAME}) +set(CPACK_PRODUCTBUILD_BACKGROUND_ALIGNMENT "bottomleft") +set(CPACK_PRODUCTBUILD_BACKGROUND_SCALING "none") + +## Only the system domain (/Applications of the chosen volume): the GUI has to go where the +## core is, and the installation check of the Distribution file (below) can only look for it +## there; an installation into the user's home could end up next to no core at all. +set(CPACK_PRODUCTBUILD_DOMAINS TRUE) +set(CPACK_PRODUCTBUILD_DOMAINS_ANYWHERE TRUE) +set(CPACK_PRODUCTBUILD_DOMAINS_ROOT TRUE) +set(CPACK_PRODUCTBUILD_DOMAINS_USER FALSE) + +## The Distribution file comes from cmake/Modules/CPack.distribution.dist.in: CPack looks for +## that template in CMAKE_MODULE_PATH before its own. Ours is CMake's plus a , +## whose script accepts a volume only if the core is installed there (lib/libOpenMS.dylib +## under the install prefix), and which holds the : the installer refuses +## a macOS older than the deployment target, which package builds set through +## MACOSX_DEPLOYMENT_TARGET. Only CPACK_* variables reach the template; CPack copies them into +## CPackConfig.cmake without escaping, so they contain no double quotes. +string(REGEX REPLACE "^/" "" _openms_core_library "${CPACK_PACKAGING_INSTALL_PREFIX}/${INSTALL_LIB_DIR}/libOpenMS.dylib") +## a JavaScript string literal in single quotes, in a CDATA section +string(REPLACE "\\" "\\\\" _openms_core_library "${_openms_core_library}") +string(REPLACE "'" "\\'" _openms_core_library "${_openms_core_library}") +string(REPLACE "]]>" "]]\\x3e" _openms_core_library "${_openms_core_library}") +set(CPACK_OPENMS_CORE_LIBRARY "${_openms_core_library}") +string(REGEX REPLACE "[^A-Za-z0-9._+~-]" "_" CPACK_OPENMS_VERSION "${OPENMS_PACKAGE_VERSION_FULLSTRING}") +if(CMAKE_OSX_DEPLOYMENT_TARGET) + set(CPACK_OPENMS_ALLOWED_OS_VERSIONS + "") +else() + set(CPACK_OPENMS_ALLOWED_OS_VERSIONS "") + message(WARNING "CMAKE_OSX_DEPLOYMENT_TARGET is not set, so the installer will not check the macOS version. Set MACOSX_DEPLOYMENT_TARGET to the macOS the core package targets.") +endif() + +if(NOT DEFINED CPACK_PRODUCTBUILD_IDENTITY_NAME) + message(WARNING "CPACK_PRODUCTBUILD_IDENTITY_NAME not set. PKG will not be signed. Make sure to specify an identity with a Developer ID: Installer certificate (not Application certificate).") +endif() + +set(MACOS_TARGET_ARCHS ${CMAKE_OSX_ARCHITECTURES}) +if (NOT MACOS_TARGET_ARCHS) + # Warning: if cmake is a subprocess of a process that is run under Rosetta, it will yield + # x86_64 (but probably also build for it. Therefore it should be fine.) + set(MACOS_TARGET_ARCHS ${CMAKE_HOST_SYSTEM_PROCESSOR}) +endif() +if (MACOS_TARGET_ARCHS STREQUAL "x86_64") + set(ARCH_SUFFIX "Intel") +elseif (MACOS_TARGET_ARCHS STREQUAL "arm64") + set(ARCH_SUFFIX "Silicon") +elseif ("x86_64" IN_LIST MACOS_TARGET_ARCHS AND "arm64" IN_LIST MACOS_TARGET_ARCHS) + set(ARCH_SUFFIX "Universal") +else () + set(ARCH_SUFFIX "Unknown") + message(WARNING "Couldn't determine MACOS_TARGET_ARCHS.") +endif() + +## The core's file name with "OpenMS-" replaced by "OpenMS-GUI-" +if((DEFINED ENV{CPACK_PACKAGE_FILE_NAME}) AND (NOT "$ENV{CPACK_PACKAGE_FILE_NAME}" STREQUAL "")) + set(CPACK_PACKAGE_FILE_NAME "$ENV{CPACK_PACKAGE_FILE_NAME}") +else() + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${OPENMS_PACKAGE_VERSION_FULLSTRING}-macOS-${ARCH_SUFFIX}") +endif() + +#------------------------------------------------------------------------------ +# Fixing the load paths and signing +#------------------------------------------------------------------------------ +## productbuild installs every component into a staging prefix of its own, so each +## component fixes and signs what it holds itself. Signing has to come after fixing, which +## invalidates signatures. The app bundles are fixed and signed in add_mac_bundle.cmake, +## when they are installed (their install rules run before the ones here). +## fix_dependencies.rb only rewrites the load paths to @rpath/ (-n -c): the runtime +## dependency set (package_general.cmake) has collected the libraries already. +set(_openms_fix "${OPENMS_HOST_DIRECTORY}/cmake/MacOSX/fix_dependencies.rb") +## A secure timestamp is only requested for a real identity ('-' signs ad hoc, without one) +if("${CPACK_BUNDLE_APPLE_CERT_APP}" STREQUAL "-") + set(_openms_codesign_timestamp "") +else() + set(_openms_codesign_timestamp "--timestamp") +endif() +## codesign every file under (no symbolic links) with the identifier . +macro(_openms_gui_sign_files _dir _id_prefix _component) + install(CODE " + file(GLOB_RECURSE _sign_files LIST_DIRECTORIES false \"\${CMAKE_INSTALL_PREFIX}/${_dir}/*\") + foreach(_sign_file IN LISTS _sign_files) + if(IS_SYMLINK \"\${_sign_file}\") + continue() + endif() + get_filename_component(_sign_name \"\${_sign_file}\" NAME) + execute_process(COMMAND codesign --force --options runtime ${_openms_codesign_timestamp} -i ${_id_prefix}.\${_sign_name} --sign \"${CPACK_BUNDLE_APPLE_CERT_APP}\" \"\${_sign_file}\" + RESULT_VARIABLE _sign_result OUTPUT_VARIABLE _sign_out ERROR_VARIABLE _sign_out) + message(STATUS \"\${_sign_out}\") + if(NOT _sign_result EQUAL 0) + message(FATAL_ERROR \"codesign failed for \${_sign_file} (\${_sign_result})\") + endif() + endforeach()" + COMPONENT ${_component}) +endmacro() +## fix_dependencies.rb ; fails the installation when the script fails (a function: +## the arguments refer to \${CMAKE_INSTALL_PREFIX}, which a macro would expand here) +function(_openms_gui_fix_dependencies _component) + install(CODE " + execute_process(COMMAND ${_openms_fix} ${ARGN} RESULT_VARIABLE _fix_result) + if(NOT _fix_result EQUAL 0) + message(FATAL_ERROR \"fix_dependencies.rb failed (\${_fix_result})\") + endif()" + COMPONENT ${_component}) +endfunction() + +## libOpenMS_GUI +_openms_gui_fix_dependencies(library_gui -l \${CMAKE_INSTALL_PREFIX}/${INSTALL_LIB_DIR}/ -e @rpath/ -n -c) +_openms_gui_sign_files(${INSTALL_LIB_DIR} de.openms.TOPP.libs library_gui) + +## Qt and the libraries it needs, the Qt plugins +_openms_gui_fix_dependencies(Dependencies -l \${CMAKE_INSTALL_PREFIX}/${INSTALL_LIB_DIR}/ -p \${CMAKE_INSTALL_PREFIX}/${INSTALL_PLUGIN_DIR}/ -e @rpath/ -n -c) +_openms_gui_sign_files(${INSTALL_LIB_DIR} de.openms.TOPP.libs Dependencies) + +## ExecutePipeline, ImageCreator +_openms_gui_fix_dependencies(${OPENMS_GUI_APPLICATIONS_COMPONENT} -b \${CMAKE_INSTALL_PREFIX}/${INSTALL_BIN_DIR}/ -e @rpath/ -n -c) +_openms_gui_sign_files(${INSTALL_BIN_DIR} de.openms.TOPP ${OPENMS_GUI_APPLICATIONS_COMPONENT}) + +## The components of the package (productbuild installs each listed component by name, +## EXCLUDE_FROM_ALL or not). The headers and the CMake package are offered unselected. +set(CPACK_COMPONENTS_ALL Dependencies library_gui ${OPENMS_GUI_APPLICATIONS_COMPONENT} + OpenMS_GUI_headers cmake_gui) +if(ENABLE_DOCS) + list(APPEND CPACK_COMPONENTS_ALL doc_gui) +endif() + +## Create own target because you cannot "depend" on the internal target 'package' +add_custom_target(dist + COMMAND cpack -G ${CPACK_GENERATOR} + COMMENT "Building ${CPACK_GENERATOR} package" +) diff --git a/cmake/package_nsis.cmake b/cmake/package_nsis.cmake new file mode 100644 index 000000000..99cedd523 --- /dev/null +++ b/cmake/package_nsis.cmake @@ -0,0 +1,62 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Julianus Pfeuffer $ +# $Authors: Julianus Pfeuffer, Chris Bielow $ +# -------------------------------------------------------------------------- + +## The Windows installer of the GUI: an add-on to the installation of the OpenMS core of the +## same version. The script is cmake/Windows/NSIS.template.in (CPack finds it in the module +## path); it reads the folder of the core installation from the registry and installs into it. +## +## No MSVC runtime is installed (InstallRequiredSystemLibraries): the core installer ships +## it. Build the GUI with the toolset of the core (or an older one), so that the runtime of +## the core is new enough. + +if (CMAKE_VS_PLATFORM_NAME MATCHES ".*Win32.*" OR CMAKE_GENERATOR MATCHES ".*Win32.*") + message(FATAL_ERROR "There is no 32-bit OpenMS installer, so there is none of the GUI either.") +endif() + +## Only CPACK_-prefixed variables reach CPackConfig.cmake and so the substitution of the +## @CPACK_...@ references in the template at packaging time. +set(CPACK_OPENMS_PROJECT_SOURCE_DIR "${PROJECT_SOURCE_DIR}") +## The version string of the core installation: the core installer writes it to the +## registry as DisplayVersion, and the GUI installer requires it there. +set(CPACK_OPENMS_VERSION "${OPENMS_PACKAGE_VERSION_FULLSTRING}") +set(CPACK_OPENMS_INSTALL_BIN_DIR "${INSTALL_BIN_DIR}") +set(CPACK_OPENMS_INSTALL_SHARE_DIR "${INSTALL_SHARE_DIR}") +set(CPACK_OPENMS_INSTALL_DOC_DIR "${INSTALL_DOC_DIR}") +set(CPACK_OPENMS_INSTALL_INCLUDE_DIR "${INSTALL_INCLUDE_DIR}") +set(CPACK_OPENMS_INSTALL_CMAKE_DIR "${INSTALL_CMAKE_DIR}") + +## The template copies straight from the staging tree, which holds every component in the +## usual folders bin/, share/, ... +set(CPACK_COMPONENT_ALL_IN_ONE 1) +set(CPACK_COMPONENTS_ALL_GROUPS_IN_ONE_PACKAGE 1) +set(CPACK_MONOLITHIC_INSTALL 1) + +## The core's file name with "OpenMS-" replaced by "OpenMS-GUI-" +if((DEFINED ENV{CPACK_PACKAGE_FILE_NAME}) AND (NOT "$ENV{CPACK_PACKAGE_FILE_NAME}" STREQUAL "")) + set(CPACK_PACKAGE_FILE_NAME "$ENV{CPACK_PACKAGE_FILE_NAME}") +else() + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${OPENMS_PACKAGE_VERSION_FULLSTRING}-Win64") +endif() +set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}/cmake/Windows/OpenMS.ico") + +## Create own target because you cannot "depend" on the internal target 'package' +add_custom_target(dist + COMMAND cpack -G ${CPACK_GENERATOR} + COMMENT "Building ${CPACK_GENERATOR} package" +) + +## Signs the installer with a code signing certificate of the certificate store (the +## Windows release workflow does not sign yet). +if (DEFINED SIGNING_IDENTITY AND NOT "${SIGNING_IDENTITY}" STREQUAL "") + add_custom_target(signed_dist + COMMAND signtool sign /v /n "${SIGNING_IDENTITY}" /t http://timestamp.digicert.com ${CPACK_PACKAGE_FILE_NAME}.exe + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Signing ${CPACK_PACKAGE_FILE_NAME}.exe with '${SIGNING_IDENTITY}'" + DEPENDS dist + ) +endif() diff --git a/cmake/package_rpm.cmake b/cmake/package_rpm.cmake new file mode 100644 index 000000000..65124055a --- /dev/null +++ b/cmake/package_rpm.cmake @@ -0,0 +1,65 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Julianus Pfeuffer $ +# $Authors: Julianus Pfeuffer $ +# -------------------------------------------------------------------------- + +## The RPM package openms-gui. It installs into /usr (CPack's default prefix), like the +## package openms of the core, and requires exactly the openms of the same version. + +set(CPACK_RPM_PACKAGE_NAME "openms-gui") +set(CPACK_RPM_PACKAGE_VENDOR "OpenMS Inc ") +## The core's file name with "OpenMS-" replaced by "OpenMS-GUI-" +if((DEFINED ENV{CPACK_PACKAGE_FILE_NAME}) AND (NOT "$ENV{CPACK_PACKAGE_FILE_NAME}" STREQUAL "")) + set(CPACK_PACKAGE_FILE_NAME "$ENV{CPACK_PACKAGE_FILE_NAME}") +else() + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${OPENMS_PACKAGE_VERSION_FULLSTRING}-RedHat-Linux-${CMAKE_SYSTEM_PROCESSOR}") +endif() + +## Version and release as the core's RPM has them (CPack's defaults: the numeric version and +## release 1), so that the requirement below matches it. +set(CPACK_RPM_PACKAGE_REQUIRES "openms = ${CPACK_PACKAGE_VERSION}") +## Until 3.7.0 the package openms contained the GUI; these files now belong to openms-gui. +set(CPACK_RPM_PACKAGE_CONFLICTS "openms < 3.7.0") + +set(CPACK_RPM_PACKAGE_DEBUG ON) + +## One package with the GUI applications and the library; Qt comes from the distribution. +set(CPACK_COMPONENTS_ALL library_gui ${OPENMS_GUI_APPLICATIONS_COMPONENT}) +if(ENABLE_DOCS) + list(APPEND CPACK_COMPONENTS_ALL doc_gui) +endif() +set(CPACK_RPM_COMPONENT_INSTALL ON) +set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) + +## The directories the package shares with the system and with the openms package are not +## owned by it. +list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION + /usr/share/OpenMS /usr/share/OpenMS/TOOLS /usr/share/doc + /usr/share/applications /usr/share/metainfo /usr/share/icons /usr/share/icons/hicolor) +foreach(_size 16 32 48 64 128 256) + list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION + /usr/share/icons/hicolor/${_size}x${_size} /usr/share/icons/hicolor/${_size}x${_size}/apps) +endforeach() + +set(CPACK_RPM_PACKAGE_LICENSE "BSD-3-Clause") +set(CPACK_RPM_PACKAGE_URL "https://www.openms.de") +set(CPACK_RPM_PACKAGE_AUTOREQPROV ON) +set(CPACK_RPM_COMPRESSION_TYPE xz) +set(CPACK_RPM_INSTALL_WITH_EXEC ON) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "graphical applications of OpenMS for LC/MS data") +set(CPACK_RPM_PACKAGE_DESCRIPTION "OpenMS is a package for LC/MS data management and analysis. + +This package contains the graphical applications of OpenMS: TOPPView, a viewer for mass +spectrometry data, TOPPAS, an editor and runner for pipelines of TOPP tools, INIFileEditor, +an editor for the parameter files of the TOPP tools, and the TOPP tools that need the GUI +library (ExecutePipeline, ImageCreator). The TOPP tools and the OpenMS library are in the +package openms.") + +## Create own target because you cannot "depend" on the internal target 'package' +add_custom_target(dist + COMMAND cpack -G ${CPACK_GENERATOR} + COMMENT "Building ${CPACK_GENERATOR} package" +) diff --git a/cmake/third_party_licenses.cmake b/cmake/third_party_licenses.cmake new file mode 100644 index 000000000..e2a9ce3d2 --- /dev/null +++ b/cmake/third_party_licenses.cmake @@ -0,0 +1,188 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +## The Windows and macOS packages of the GUI bundle Qt, and the licenses of Qt require their +## texts to accompany the binaries. openms_install_third_party_licenses(), called by +## cmake/package_general.cmake, installs them under share/OpenMS/LICENSES in the component +## Dependencies, which holds the bundled libraries: +## - Qt/: the GUI uses Qt under the LGPL version 3, which requires its text, the text of the +## GPL version 3 that it supplements, and a note on where to get the source code of the +## bundled Qt. The Linux packages depend on the distribution's Qt and install none of this. +## - homebrew//: on macOS, Qt and the libraries it needs (glib, ICU, freetype, ...) +## come from Homebrew. Homebrew installs the license files of a formula into its keg, and +## these are installed for the given formulae and every installed formula they depend on. +## +## Each installed file is recorded for share/OpenMS/THIRD-PARTY-NOTICES-GUI.txt, which gathers +## the texts in one file (cmake/third_party_notices.cmake). + +## Sets to the Homebrew formulae whose kegs hold the given paths, and +## to the prefix of the Homebrew installation they belong to. +function(openms_homebrew_formulae_of formulae_var prefix_var) + set(_formulae) + set(_prefixes) + foreach(_path IN LISTS ARGN) + if(NOT EXISTS "${_path}") + continue() + endif() + ## /opt/ and the files linked into /lib resolve to the keg, + ## /Cellar//. + file(REAL_PATH "${_path}" _real) + if(_real MATCHES "^(.*)/Cellar/([^/]+)/[^/]+(/|$)") + list(APPEND _prefixes "${CMAKE_MATCH_1}") + list(APPEND _formulae "${CMAKE_MATCH_2}") + endif() + endforeach() + list(REMOVE_DUPLICATES _formulae) + list(REMOVE_DUPLICATES _prefixes) + list(LENGTH _prefixes _count) + if(_count GREATER 1) + ## A Mac can have two Homebrew installations, /opt/homebrew and /usr/local. + message(FATAL_ERROR "The paths ${ARGN} belong to more than one Homebrew installation: " + "${_prefixes}") + endif() + set(${formulae_var} "${_formulae}" PARENT_SCOPE) + set(${prefix_var} "${_prefixes}" PARENT_SCOPE) +endfunction() + +## openms_install_third_party_licenses([QT_VERSION ] +## [HOMEBREW_PREFIX HOMEBREW_FORMULAE ...]) +## QT_VERSION the version of the Qt the package bundles; empty if it bundles none +## HOMEBREW_PREFIX the prefix of the Homebrew installation the formulae come from +## HOMEBREW_FORMULAE the Homebrew formulae the package bundles libraries of; their +## dependencies are added +function(openms_install_third_party_licenses) + cmake_parse_arguments(PARSE_ARGV 0 arg "" "QT_VERSION;HOMEBREW_PREFIX" "HOMEBREW_FORMULAE") + set(_licenses_dir "${INSTALL_SHARE_DIR}/LICENSES") + + if(arg_QT_VERSION) + set(_qt_licenses "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/third_party_licenses/Qt") + ## The variables README.txt.in refers to. + set(QT_LICENSE_VERSION "${arg_QT_VERSION}") + string(REGEX MATCH "^[0-9]+\\.[0-9]+" QT_LICENSE_MAJOR_MINOR "${arg_QT_VERSION}") + set(QT_LICENSE_HOMEBREW_NOTE "") + if(arg_HOMEBREW_FORMULAE) + string(CONCAT QT_LICENSE_HOMEBREW_NOTE + "\nOn macOS, Qt comes from Homebrew. The Homebrew formulae, including any patches\n" + "Homebrew applies to Qt, are at https://github.com/Homebrew/homebrew-core; ../homebrew\n" + "holds the license files of the formulae whose libraries this package bundles.\n") + endif() + configure_file("${_qt_licenses}/README.txt.in" + "${PROJECT_BINARY_DIR}/third_party_licenses/Qt/README.txt" @ONLY) + set(_qt_files "${_qt_licenses}/LGPL-3.0-only.txt" + "${_qt_licenses}/GPL-3.0-only.txt" + "${PROJECT_BINARY_DIR}/third_party_licenses/Qt/README.txt") + install(FILES ${_qt_files} + DESTINATION "${_licenses_dir}/Qt" + COMPONENT Dependencies) + foreach(_file IN LISTS _qt_files) + get_filename_component(_name "${_file}" NAME) + openms_add_third_party_notice("LICENSES/Qt/${_name}" "${_file}") + endforeach() + message(STATUS "Packaging the Qt ${arg_QT_VERSION} licenses") + endif() + + if(arg_HOMEBREW_FORMULAE) + ## The package bundles libraries of these formulae and their dependencies, so configuring + ## fails rather than leave their license files out. The brew of the installation the + ## formulae come from is used, not the one on the PATH: a brew answers for its own + ## installation only, and a Mac can have two. + set(_brew "${arg_HOMEBREW_PREFIX}/bin/brew") + if(NOT arg_HOMEBREW_PREFIX OR NOT EXISTS "${_brew}") + message(FATAL_ERROR "The package bundles libraries of the Homebrew formulae " + "${arg_HOMEBREW_FORMULAE}, but '${_brew}', which is needed to collect " + "their license files, does not exist.") + endif() + ## --union: the dependencies of any of the formulae, not only those they all share. + ## Both the dependencies recorded in the installed kegs (--installed) and those the + ## formulae declare are collected. The recorded ones alone miss formulae: in the Release + ## run of 2026-10-08, 'brew deps --installed' left out exactly the dependencies that + ## 'brew install qtbase' had upgraded in the same run (glib, harfbuzz, pcre2, libpng, + ## cairo, ...), although the package bundles their libraries. homebrew-core's CI checks + ## that a formula declares every formula it links against, so the declared ones cover + ## what is bundled. + set(_dependencies) + foreach(_installed_only "--installed" "") + execute_process(COMMAND "${_brew}" deps ${_installed_only} --union ${arg_HOMEBREW_FORMULAE} + OUTPUT_VARIABLE _deps + RESULT_VARIABLE _result + ERROR_VARIABLE _error + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "'brew deps ${_installed_only} --union' failed (${_error}), so the " + "license files of the dependencies of the Homebrew formulae " + "${arg_HOMEBREW_FORMULAE}, whose libraries the package bundles, " + "cannot be collected.") + endif() + string(REGEX REPLACE "[ \t\r\n]+" ";" _deps "${_deps}") + list(APPEND _dependencies ${_deps}) + endforeach() + set(_formulae ${arg_HOMEBREW_FORMULAE} ${_dependencies}) + list(REMOVE_DUPLICATES _formulae) + list(SORT _formulae) + + set(_without_license) + set(_not_installed) + foreach(_formula IN LISTS _formulae) + execute_process(COMMAND "${_brew}" --prefix "${_formula}" + OUTPUT_VARIABLE _prefix + RESULT_VARIABLE _result + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + ## 'brew --prefix' names the opt link of a formula whether or not it is installed. A + ## declared dependency that is not installed (an optional one, or one the formula + ## gained after it was installed) cannot have a library in the package. + if(NOT _result EQUAL 0 OR NOT IS_DIRECTORY "${_prefix}") + list(APPEND _not_installed "${_formula}") + continue() + endif() + file(REAL_PATH "${_prefix}" _keg) + ## Homebrew copies a formula's top-level license files (COPYING, LICENSE.md, ...) + ## into the root of its keg; some formulae install them to share/doc/ or + ## share/licenses/ instead. The first of these folders that has any is used, + ## as tools/ci/collect_wheel_licenses.py does for the wheels. + set(_license_files) + foreach(_folder "${_keg}" "${_keg}/share/doc/${_formula}" "${_keg}/share/licenses/${_formula}") + file(GLOB _keg_files LIST_DIRECTORIES false "${_folder}/*") + foreach(_file IN LISTS _keg_files) + get_filename_component(_name "${_file}" NAME) + string(TOUPPER "${_name}" _name) + if(_name MATCHES "^(COPYING|COPYRIGHT|LICENSE|LICENCE|NOTICE)") + list(APPEND _license_files "${_file}") + endif() + endforeach() + if(_license_files) + break() + endif() + endforeach() + if(_license_files) + install(FILES ${_license_files} + DESTINATION "${_licenses_dir}/homebrew/${_formula}" + COMPONENT Dependencies) + foreach(_file IN LISTS _license_files) + get_filename_component(_name "${_file}" NAME) + openms_add_third_party_notice("LICENSES/homebrew/${_formula}/${_name}" "${_file}") + endforeach() + else() + list(APPEND _without_license "${_formula}") + endif() + endforeach() + list(LENGTH _formulae _count) + list(LENGTH _not_installed _count_not_installed) + math(EXPR _count "${_count} - ${_count_not_installed}") + message(STATUS "Packaging the license files of ${_count} Homebrew formulae") + if(_not_installed) + message(STATUS "Declared Homebrew dependencies that are not installed: ${_not_installed}") + endif() + if(_without_license) + ## Expected for Qt's own formulae, whose keg holds its licenses only in a LICENSES + ## directory; the Qt licenses are installed above. + message(STATUS "No license file in the Homebrew kegs of: ${_without_license}") + endif() + endif() +endfunction() diff --git a/cmake/third_party_licenses/Qt/GPL-3.0-only.txt b/cmake/third_party_licenses/Qt/GPL-3.0-only.txt new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/cmake/third_party_licenses/Qt/GPL-3.0-only.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/cmake/third_party_licenses/Qt/LGPL-3.0-only.txt b/cmake/third_party_licenses/Qt/LGPL-3.0-only.txt new file mode 100644 index 000000000..0a041280b --- /dev/null +++ b/cmake/third_party_licenses/Qt/LGPL-3.0-only.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/cmake/third_party_licenses/Qt/README.txt.in b/cmake/third_party_licenses/Qt/README.txt.in new file mode 100644 index 000000000..e424e05a9 --- /dev/null +++ b/cmake/third_party_licenses/Qt/README.txt.in @@ -0,0 +1,14 @@ +The Qt @QT_LICENSE_VERSION@ libraries and plugins in this package (the modules Core, Gui, +Widgets, Svg and the others the OpenMS GUI needs) are Copyright (C) The Qt Company Ltd. and +other contributors. The OpenMS GUI uses them under the GNU Lesser General Public License, +version 3 (LGPL-3.0-only.txt in this directory), which supplements the GNU General +Public License, version 3 (GPL-3.0-only.txt in this directory). + +The source code of Qt @QT_LICENSE_VERSION@ is available from + https://download.qt.io/official_releases/qt/@QT_LICENSE_MAJOR_MINOR@/@QT_LICENSE_VERSION@/submodules/ +or, once that release has been archived, from + https://download.qt.io/archive/qt/@QT_LICENSE_MAJOR_MINOR@/@QT_LICENSE_VERSION@/submodules/ + +Qt contains third-party code under other licenses; they are listed at +https://doc.qt.io/qt-6/licenses-used-in-qt.html. +@QT_LICENSE_HOMEBREW_NOTE@ \ No newline at end of file diff --git a/cmake/third_party_notices.cmake b/cmake/third_party_notices.cmake new file mode 100644 index 000000000..122d10198 --- /dev/null +++ b/cmake/third_party_notices.cmake @@ -0,0 +1,240 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +## share/OpenMS/THIRD-PARTY-NOTICES-GUI.txt holds the license, copyright and notice texts of the +## third-party software the GUI packages bundle, in one file. The GUI layer is installed into +## the folder of the OpenMS core, which has its own THIRD-PARTY-NOTICES.txt, hence the name. +## The file first lists the components, grouped by where they come from, each with the numbers +## of the texts that apply to it. The texts follow, each only once: components whose texts are +## identical share a number. The files the texts come from stay where they are installed, in +## share/OpenMS/LICENSES. +## +## cmake/third_party_licenses.cmake records the files it installs with +## openms_add_third_party_notice(). openms_install_third_party_notices(), which the top-level +## CMakeLists.txt calls last, writes the list of recorded files when configuring, and the +## notices from that list when installing. Nothing is recorded where the GUI bundles nothing +## (Linux, a plain installation), and then no notices file is installed. +## +## The Debian package installs the GUI's license, followed by the notices if there are any, as +## /usr/share/doc/openms-gui/copyright, the file Debian's policy asks every package to have. + +## The functions below keep these policies wherever they are called from, also in the install +## script, which sets none. include() gives this file a policy scope of its own, so the setting +## does not leak into the including project. +cmake_policy(VERSION 3.24) + +set(OPENMS_THIRD_PARTY_NOTICES_FILE "THIRD-PARTY-NOTICES-GUI.txt") + +## openms_add_third_party_notice( ) +## Records that the installation contains as share/OpenMS/. +function(openms_add_third_party_notice path file) + set_property(GLOBAL APPEND PROPERTY OPENMS_THIRD_PARTY_NOTICES "${path}|${file}") +endfunction() + +## openms_install_third_party_notices() +## Installs share/OpenMS/THIRD-PARTY-NOTICES-GUI.txt, made from the files recorded so far, in +## the component Dependencies (with the libraries it is about), and for a Debian package the +## copyright file in the component of the GUI applications. +function(openms_install_third_party_notices) + set(_dir "${PROJECT_BINARY_DIR}/third_party_notices") + get_property(_notices GLOBAL PROPERTY OPENMS_THIRD_PARTY_NOTICES) + list(JOIN _notices "\n" _list) + file(WRITE "${_dir}/files.txt" "${_list}\n") + set(_debian_copyright "") + ## PACKAGE_TYPE, not CPACK_GENERATOR: include(CPack) resets the latter to its defaults. + if("${PACKAGE_TYPE}" STREQUAL "deb") + set(_debian_copyright "LICENSE \"${OPENMS_HOST_DIRECTORY}/License.txt\" LICENSE_OUTPUT \"${_dir}/copyright\"") + endif() + list(LENGTH _notices _count) + if(_count GREATER 0) + install(CODE " + include(\"${CMAKE_CURRENT_FUNCTION_LIST_FILE}\") + openms_write_third_party_notices(OUTPUT \"${_dir}/${OPENMS_THIRD_PARTY_NOTICES_FILE}\" + FILE_LIST \"${_dir}/files.txt\")" + COMPONENT Dependencies) + install(FILES "${_dir}/${OPENMS_THIRD_PARTY_NOTICES_FILE}" + DESTINATION "${INSTALL_SHARE_DIR}" + COMPONENT Dependencies) + message(STATUS "${OPENMS_THIRD_PARTY_NOTICES_FILE}: ${_count} license, copyright and notice files") + endif() + if(_debian_copyright) + install(CODE " + include(\"${CMAKE_CURRENT_FUNCTION_LIST_FILE}\") + openms_write_third_party_notices(FILE_LIST \"${_dir}/files.txt\" ${_debian_copyright})" + COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) + ## Debian policy 12.5: /usr/share/doc//copyright, neither compressed nor a + ## symbolic link. + install(FILES "${_dir}/copyright" + DESTINATION "share/doc/${CPACK_DEBIAN_PACKAGE_NAME}" + COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) + endif() +endfunction() + +## Sets to the content of with Unix line ends and without trailing +## whitespace, and to TRUE if is not a text file, i.e. has a NUL byte in +## its first 8 KiB. file(READ) drops carriage returns, so the length of what it reads says +## nothing. +function(_openms_third_party_notice_text text_var binary_var file) + file(READ "${file}" _head LIMIT 8192 HEX) + ## " ...", so that " 00" can only match a whole byte. + string(REGEX REPLACE "(..)" " \\1" _head "${_head}") + string(FIND "${_head}" " 00" _nul) + if(NOT _nul EQUAL -1) + set(${binary_var} TRUE PARENT_SCOPE) + set(${text_var} "" PARENT_SCOPE) + return() + endif() + file(READ "${file}" _text) + string(REPLACE "\r" "" _text "${_text}") + string(REGEX REPLACE "[ \t\n]+$" "" _text "${_text}") + set(${binary_var} FALSE PARENT_SCOPE) + set(${text_var} "${_text}" PARENT_SCOPE) +endfunction() + +## Sets to the number of the group belongs to (the order of the groups in +## the notices) and to the name of its component. +function(_openms_third_party_notice_component group_var component_var path) + get_filename_component(_name "${path}" NAME) + if(path MATCHES "^LICENSES/Qt/") + set(_group 1) + set(_component "Qt") + elseif(path MATCHES "^LICENSES/homebrew/(.+)$") + set(_group 2) + get_filename_component(_component "${CMAKE_MATCH_1}" DIRECTORY) + else() + set(_group 3) + set(_component "${path}") + endif() + set(${group_var} "${_group}" PARENT_SCOPE) + set(${component_var} "${_component}" PARENT_SCOPE) +endfunction() + +## openms_write_third_party_notices([OUTPUT ] FILE_LIST +## [LICENSE LICENSE_OUTPUT ]) +## Writes the notices for the files named in (lines "|", as +## openms_install_third_party_notices() writes them) to . With LICENSE, also writes +## LICENSE_OUTPUT: , followed by the notices if there are any. +function(openms_write_third_party_notices) + cmake_parse_arguments(PARSE_ARGV 0 arg "" "OUTPUT;FILE_LIST;LICENSE;LICENSE_OUTPUT" "") + set(_entries) + file(STRINGS "${arg_FILE_LIST}" _entries ENCODING UTF-8) + + ## Order: by group, then by path; "||". + set(_sorted) + foreach(_entry IN LISTS _entries) + string(REPLACE "|" ";" _entry "${_entry}") + list(GET _entry 0 _path) + list(GET _entry 1 _file) + if(NOT EXISTS "${_file}") + message(FATAL_ERROR "${OPENMS_THIRD_PARTY_NOTICES_FILE}: ${_file} (installed as ${_path}) does not exist") + endif() + _openms_third_party_notice_component(_group _component "${_path}") + list(APPEND _sorted "${_group}|${_path}|${_file}") + endforeach() + list(REMOVE_DUPLICATES _sorted) + list(SORT _sorted) + + set(_notices_text "") + if(_sorted) + ## Numbers the distinct texts and collects, per component, the numbers of its texts. + set(_hashes) ## hash of each numbered text; its number is its index + 1 + set(_text_files) ## the file each numbered text is read from + set(_text_paths) ## and where it is installed + set(_components) ## "|", in order + foreach(_entry IN LISTS _sorted) + string(REPLACE "|" ";" _entry "${_entry}") + list(GET _entry 1 _path) + list(GET _entry 2 _file) + _openms_third_party_notice_component(_group _component "${_path}") + set(_key "${_group}|${_component}") + string(MD5 _refs "${_key}") + set(_refs "_refs_${_refs}") + if(NOT _key IN_LIST _components) + list(APPEND _components "${_key}") + set(${_refs} "") + endif() + _openms_third_party_notice_text(_text _binary "${_file}") + if(_binary) + string(APPEND ${_refs} " ${_path} (not a text file)") + continue() + endif() + ## Texts that differ only in white space (line breaks, indentation) are the same text. + string(REGEX REPLACE "[ \t\n]+" " " _hash "${_text}") + string(SHA256 _hash "${_hash}") + list(FIND _hashes "${_hash}" _index) + if(_index EQUAL -1) + list(APPEND _hashes "${_hash}") + list(APPEND _text_files "${_file}") + list(APPEND _text_paths "${_path}") + list(LENGTH _hashes _number) + else() + math(EXPR _number "${_index} + 1") + endif() + if(NOT "${${_refs}}" MATCHES " \\[${_number}\\]( |$)") + string(APPEND ${_refs} " [${_number}]") + endif() + endforeach() + + set(_titles "Qt" "Libraries from Homebrew" "Other") + set(_rule "==========================================================================================") + string(CONCAT _notices_text + "THIRD-PARTY SOFTWARE NOTICES OF THE OPENMS GUI\n" + "\n" + "This installation of the OpenMS GUI (TOPPView, TOPPAS, INIFileEditor and the TOPP tools that\n" + "need the GUI library) contains the third-party software listed below. Each component is\n" + "followed by the numbers of the license, copyright and notice texts that apply to it. The\n" + "texts come after the list, each only once: components whose texts are identical share a\n" + "number. Paths are relative to share/OpenMS, the folder in which the installation keeps the\n" + "same files as their projects distribute them. THIRD-PARTY-NOTICES.txt next to this file\n" + "lists the third-party software of the OpenMS core.\n") + set(_current_group "") + foreach(_key IN LISTS _components) + string(REPLACE "|" ";" _parts "${_key}") + list(GET _parts 0 _group) + list(GET _parts 1 _component) + if(NOT _group STREQUAL _current_group) + math(EXPR _title_index "${_group} - 1") + list(GET _titles ${_title_index} _title) + string(APPEND _notices_text "\n${_title}\n") + set(_current_group "${_group}") + endif() + string(MD5 _refs "${_key}") + set(_refs "_refs_${_refs}") + string(LENGTH "${_component}" _length) + set(_padding " ") + if(_length LESS 44) + math(EXPR _pad "44 - ${_length}") + string(REPEAT " " ${_pad} _padding) + endif() + string(APPEND _notices_text " ${_component}${_padding}${${_refs}}\n") + endforeach() + + set(_number 0) + foreach(_file IN LISTS _text_files) + list(GET _text_paths ${_number} _path) + math(EXPR _number "${_number} + 1") + _openms_third_party_notice_text(_text _binary "${_file}") + string(APPEND _notices_text "\n\n${_rule}\n[${_number}] ${_path}\n${_rule}\n\n${_text}\n") + endforeach() + list(LENGTH _components _component_count) + list(LENGTH _text_files _text_count) + endif() + + if(arg_OUTPUT) + file(WRITE "${arg_OUTPUT}.tmp" "${_notices_text}") + file(RENAME "${arg_OUTPUT}.tmp" "${arg_OUTPUT}") + message(STATUS "Wrote ${arg_OUTPUT}: ${_component_count} components, ${_text_count} texts") + endif() + if(arg_LICENSE) + file(READ "${arg_LICENSE}" _license) + if(_notices_text) + set(_license "${_license}\n\n${_rule}\n\n${_notices_text}") + endif() + file(WRITE "${arg_LICENSE_OUTPUT}" "${_license}") + endif() +endfunction() diff --git a/cmake/topp_tool_macros.cmake b/cmake/topp_tool_macros.cmake new file mode 100644 index 000000000..7c004cfd5 --- /dev/null +++ b/cmake/topp_tool_macros.cmake @@ -0,0 +1,148 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +# Declaration of TOPP tools, and generation of the tool registry from those declarations. +# +# openms_topp_tool( ) is the single place a TOPP tool is declared. It says +# both that the tool exists (so the build produces it) and which category TOPPAS groups it +# under (so ToolHandler can list it). The registry of the GUI layer, +# share/OpenMS/TOOLS/OpenMS-GUI.tsv, is written from these declarations by +# openms_write_tool_registry() (see src/CMakeLists.txt). ToolHandler reads every *.tsv in +# that directory, so installed next to the core's OpenMS.tsv it adds the GUI TOPP tools. + +#------------------------------------------------------------------------------ +# Forget the tools of an earlier configure of this build directory. +# +# The declarations accumulate in cache variables so that they survive the directory scopes +# they are made in (src/openms_gui). +# Cache variables also survive a reconfigure, so without this every configure would append +# the whole list again. +function(openms_reset_topp_tools) + set(TOPP_TOOLS "" CACHE INTERNAL "OpenMS' TOPP tools" FORCE) + set(TOPP_TOOL_CATEGORIES "" CACHE INTERNAL "TOPPAS category of the entry of TOPP_TOOLS at the same index" FORCE) +endfunction() + +#------------------------------------------------------------------------------ +# openms_topp_tool( ) +# +# @param Name the tool, built from .cpp or from the subdirectory / +# @param Category the TOPPAS category, e.g. "Misc". Use one of the categories of the +# OpenMS core (doc/doxygen/public/TOPP.doxygen of OpenMS). +function(openms_topp_tool name category) + if(ARGN) + message(FATAL_ERROR "openms_topp_tool(${name}): unexpected arguments '${ARGN}'. Usage: openms_topp_tool( )") + endif() + if("${name}" STREQUAL "") + message(FATAL_ERROR "openms_topp_tool(): the tool name may not be empty") + endif() + if("${category}" STREQUAL "") + message(FATAL_ERROR "openms_topp_tool(${name}): a category is required; it is what TOPPAS groups the tool under") + endif() + if(name IN_LIST TOPP_TOOLS) + message(FATAL_ERROR "openms_topp_tool(${name}): this tool is already declared. ToolHandler refuses to start any " + "tool when a name is registered twice, so the duplicate is rejected here instead.") + endif() + ## Some workflow systems read a slash in a category as a subcategory separator. + if(category MATCHES "/") + message(FATAL_ERROR "openms_topp_tool(${name}): the category '${category}' contains a '/', which some workflow " + "systems read as a subcategory separator. Use a plain category name.") + endif() + ## ';' would split the entry into two list elements and desynchronize it from TOPP_TOOLS. + if(category MATCHES ";") + message(FATAL_ERROR "openms_topp_tool(${name}): the category '${category}' contains a ';'.") + endif() + + set(TOPP_TOOLS ${TOPP_TOOLS} "${name}" + CACHE INTERNAL "OpenMS' TOPP tools" FORCE) + set(TOPP_TOOL_CATEGORIES ${TOPP_TOOL_CATEGORIES} "${category}" + CACHE INTERNAL "TOPPAS category of the entry of TOPP_TOOLS at the same index" FORCE) +endfunction() + +#------------------------------------------------------------------------------ +# openms_write_tool_registry( [TOOLS ...]) +# +# Write the registry of every tool declared so far. Call it once, after every +# add_subdirectory() that declares tools. +# +# TOOLS restricts the file to the given subset of the declared tools. +# +# Without any tool to list, no file is written and a file an earlier configure of this build +# directory left behind is removed: a build of the libraries alone registers nothing. +# +# The format is the tab-separated one the rest of share/OpenMS uses for tabular data, read +# back by ToolHandler through CsvFile: +# +# # comment +# \t[\t;...] +# +# The third column is the list of '-type' sub-modes a tool offers and is omitted by tools +# that have none, which is all of them today. +function(openms_write_tool_registry out_file) + cmake_parse_arguments(PARSE_ARGV 1 _registry "" "" "TOOLS") + if(_registry_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "openms_write_tool_registry(${out_file}): unexpected arguments '${_registry_UNPARSED_ARGUMENTS}'") + endif() + list(LENGTH TOPP_TOOLS _tool_count) + list(LENGTH TOPP_TOOL_CATEGORIES _category_count) + if(NOT _tool_count EQUAL _category_count) + message(FATAL_ERROR "The TOPP tool declarations are inconsistent: ${_tool_count} tools but ${_category_count} " + "categories. Both lists are only ever appended to by openms_topp_tool().") + endif() + + ## Ordered by tool name, so a reconfigure that declares the same tools produces a + ## byte-identical file. Sorting the names themselves rather than "namecategory" pairs: + ## any separator sorts somewhere among the letters and would order 'FooBar' before 'Foo'. + set(_sorted_names ${TOPP_TOOLS}) + if("TOOLS" IN_LIST ARGN) + foreach(_name IN LISTS _registry_TOOLS) + if(NOT _name IN_LIST TOPP_TOOLS) + message(FATAL_ERROR "openms_write_tool_registry(${out_file}): '${_name}' is not a declared tool") + endif() + endforeach() + set(_sorted_names ${_registry_TOOLS}) + endif() + list(LENGTH _sorted_names _tool_count) + if(_tool_count EQUAL 0) + file(REMOVE "${out_file}") + message(STATUS "TOPP tool registry: no tools, ${out_file} not written") + return() + endif() + list(SORT _sorted_names CASE INSENSITIVE) + + set(_content +"# GENERATED FILE, do not edit and do not commit. +# +# The TOPP tool registry of the GUI layer (OpenMS-TOPP-GUI): what ToolHandler lists, and the +# category TOPPAS groups each tool under. Written by openms_write_tool_registry() +# (cmake/topp_tool_macros.cmake) from the openms_topp_tool() declarations in +# src/openms_gui/CMakeLists.txt. The tools of the OpenMS core are in OpenMS.tsv next to it. +# +# A tool describes itself (parameters, formats, documentation) through its own binary, via +# -write_ctd, so the registry carries nothing but the name and the category. Tools built +# outside this repository register themselves by installing their own *.tsv alongside this +# one; a name may be registered only once across all of them. +# +# \t +") + foreach(_name IN LISTS _sorted_names) + ## openms_topp_tool() rejects a duplicate name, so this finds the one declaration. + list(FIND TOPP_TOOLS "${_name}" _index) + list(GET TOPP_TOOL_CATEGORIES ${_index} _category) + string(APPEND _content "${_name}\t${_category}\n") + endforeach() + + ## Only rewrite on a real change, so a reconfigure does not restamp the file. + set(_previous "") + if(EXISTS "${out_file}") + file(READ "${out_file}" _previous) + endif() + if(NOT _previous STREQUAL _content) + file(WRITE "${out_file}" "${_content}") + endif() + message(STATUS "TOPP tool registry: ${_tool_count} tools -> ${out_file}") +endfunction() diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 000000000..0e5d7f50f --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,203 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Stephan Aiche, Chris Bielow $ +# $Authors: Andreas Bertsch, Chris Bielow, Stephan Aiche $ +# -------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ +# Documentation of the GUI layer (doxygen html of the GUI library, the pages of +# the GUI tools, the TOPPAS tutorial) and the GUI code examples of the tutorials. +# +# Targets (only with ENABLE_DOCS=ON and doxygen found): +# doc the GUI documenters, then doxygen -> /doc/html-gui +# doc_class_only doxygen only (reuses the output of earlier documenter runs) +# doc_param_internal / doc_progs the documenters / building them +# Target (ENABLE_TUTORIALS=ON): Tutorials_build +# +# The html is installed into ${INSTALL_DOC_DIR}/html-gui (component doc_gui), +# next to html/ of the OpenMS core when both are installed into one prefix. +#------------------------------------------------------------------------------ + +project("OpenMS_TOPP_GUI_doc") + +#------------------------------------------------------------------------------ +# helper macro +#------------------------------------------------------------------------------ +macro(make_doxygen_path doxygen_style_list_var doc_path_list_var) + set(${doxygen_style_list_var} "") + foreach(_doc_path ${${doc_path_list_var}}) + set(${doxygen_style_list_var} "\"${_doc_path}\" ${${doxygen_style_list_var}}") + endforeach() +endmacro() + +if(ENABLE_DOCS AND NOT Doxygen_FOUND) + message(STATUS "Doxygen not found. Disabling all documentation targets!") +elseif(ENABLE_DOCS) + message(STATUS "Documentation is built (-D ENABLE_DOCS=On).") + + ## doxygen links @ref TOPP_ and the classes of the OpenMS core through the tag file of + ## the core documentation (OpenMS.tag, installed into share/doc of an OpenMS installation). + ## Without it these references stay plain text and doxygen warns about them. + set(_core_tagfile "${OPENMS_DOC_DIR}/OpenMS.tag") + if(OPENMS_PACKAGE_VERSION_FULLSTRING STREQUAL OPENMS_PACKAGE_VERSION) + set(_core_doc_url "https://archive.openms.de/openms/Documentation/release/${OPENMS_PACKAGE_VERSION}/html") + else() + set(_core_doc_url "https://archive.openms.de/openms/Documentation/nightly/latest/html") + endif() + if(OPENMS_DOC_DIR AND EXISTS "${_core_tagfile}") + set(_default_tagfile "${_core_tagfile}=${_core_doc_url}") + else() + set(_default_tagfile "") + endif() + set(OPENMS_DOXYGEN_TAGFILE "${_default_tagfile}" CACHE STRING + "Doxygen tag file of the OpenMS core documentation, as =, e.g. /share/doc/OpenMS.tag=${_core_doc_url}. Empty: no links into the core documentation.") + if(OPENMS_DOXYGEN_TAGFILE) + message(STATUS " linking the OpenMS documentation through ${OPENMS_DOXYGEN_TAGFILE}") + else() + message(STATUS " no tag file of the OpenMS documentation (OPENMS_DOXYGEN_TAGFILE): references to TOPP tools and core classes are not linked") + endif() + + #------------------------------------------------------------------------------ + # doc programs: they write the generated parts of the pages to doxygen/parameters/output + # GUIToolDocumenter: TOPP_.cli / .html of the GUI tools (runs the tools) + # GUIParamDocumenter: OpenMS_.parameters of the GUI classes with parameters + #------------------------------------------------------------------------------ + set(OpenMS_doc_executables) + include(doxygen/parameters/executables.cmake) + + foreach(i ${OpenMS_doc_executables}) + add_executable(${i} EXCLUDE_FROM_ALL doxygen/parameters/${i}.cpp) + set_target_properties(${i} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/doxygen/parameters") + endforeach() + target_link_libraries(GUIToolDocumenter OpenMS::OpenMS_CLI) + target_link_libraries(GUIParamDocumenter OpenMS_GUI) + + # collection target; the tool documenter runs the GUI tools + add_custom_target(doc_progs) + add_dependencies(doc_progs ${OpenMS_doc_executables} GUI) + + #------------------------------------------------------------------------------ + # Initialize variables needed for a proper doxygen configuration + #------------------------------------------------------------------------------ + set(CF_OPENMS_BIN_PATH ${PROJECT_BINARY_DIR}) + set(CF_OPENMS_SRC_PATH ${OPENMS_HOST_DIRECTORY}) + set(CF_OPENMS_PACKAGE_VERSION ${OPENMS_PACKAGE_VERSION_FULLSTRING}) + set(CF_OPENMS_DOXYGEN_TAGFILE "${OPENMS_DOXYGEN_TAGFILE}") + if(CF_OPENMS_DOXYGEN_TAGFILE) + set(CF_OPENMS_DOXYGEN_TAGFILE "\"${CF_OPENMS_DOXYGEN_TAGFILE}\"") + endif() + + # make it a doxygen conform list + make_doxygen_path(CF_OPENMS_DOCUMENTATION_DIRECTORIES OPENMS_DOCUMENTATION_DIRECTORIES) + make_doxygen_path(CF_OPENMS_DOCUMENTATION_STRIP_INCLUDES OPENMS_DOCUMENTATION_DIRECTORIES) + make_doxygen_path(CF_STRIP_PATH OPENMS_DOCUMENTATION_DIRECTORIES) + + ## DOT (Graphviz) is not used (HAVE_DOT = NO); its path is only filled in when present, to + ## avoid a warning about a missing dot tool. + if (TARGET Doxygen::dot) + get_target_property(CF_DOXYGEN_DOT_EXECUTABLE Doxygen::dot IMPORTED_LOCATION) + else() + set(CF_DOXYGEN_DOT_EXECUTABLE "") + endif() + + configure_file(${PROJECT_SOURCE_DIR}/doxygen/Doxyfile.in ${PROJECT_BINARY_DIR}/doxygen/Doxyfile) + + #------------------------------------------------------------------------------ + # Run time environment of the documenters, as for the tests (src/tests/CMakeLists.txt): + # the GUI library needs a QApplication (offscreen without a display), the GUI TOPP + # tools need the tool registry of this build, libOpenMS the share/OpenMS of the core + # (OPENMS_DATA_PATH, its last resort, e.g. for a moved SDK), and on Windows the DLLs + # of this build, the OpenMS core and Qt, and the Qt plugins must be found. + #------------------------------------------------------------------------------ + list(TRANSFORM CMAKE_PREFIX_PATH APPEND "/bin" OUTPUT_VARIABLE DEP_BIN_DIRS) + list(TRANSFORM CMAKE_PREFIX_PATH APPEND "/lib" OUTPUT_VARIABLE DEP_LIB_DIRS) + set(_doc_path_dirs "$" ${OPENMS_BIN_DIR} ${OPENMS_LIB_DIR} + "$" ${DEP_BIN_DIRS} ${DEP_LIB_DIRS}) + if(WIN32) + string(REPLACE ";" "\\;" _doc_path "${_doc_path_dirs};$ENV{PATH}") + else() + string(REPLACE ";" ":" _doc_path "${_doc_path_dirs}:$ENV{PATH}") + endif() + if(DEFINED ENV{QT_QPA_PLATFORM}) + set(_doc_qpa "$ENV{QT_QPA_PLATFORM}") + else() + set(_doc_qpa "offscreen") + endif() + set(_doc_env ${CMAKE_COMMAND} -E env + "PATH=${_doc_path}" + "QT_QPA_PLATFORM=${_doc_qpa}" + "OPENMS_TOOL_REGISTRY_PATH=${OPENMS_TOOL_REGISTRY_BUILD_DIR}" + "OPENMS_DATA_PATH=${OPENMS_DATA_DIR}") + ## the build tree has no qt.conf: point Qt to its plugins (/platforms/qwindows.dll) + if(WIN32 AND TARGET Qt6::QWindowsIntegrationPlugin) + list(APPEND _doc_env "QT_PLUGIN_PATH=$>") + endif() + + # where the GUI tools are (with multi-config generators, in a subdirectory per configuration) + set(_gui_bin_dir "$") + + add_custom_target(doc_param_internal + COMMAND ${CMAKE_COMMAND} -E make_directory doxygen/parameters/output/ + COMMAND ${CMAKE_COMMAND} -E echo "Writing the parameter documentation of the GUI classes" + COMMAND ${CMAKE_COMMAND} -E chdir doxygen/parameters/ ${_doc_env} $ + COMMAND ${CMAKE_COMMAND} -E echo "Writing the command line and INI documentation of the GUI tools" + COMMAND ${CMAKE_COMMAND} -E chdir doxygen/parameters/ ${_doc_env} $ ${_gui_bin_dir} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Build the parameter documentation of the GUI layer" + VERBATIM) + add_dependencies(doc_param_internal doc_progs) + + #------------------------------------------------------------------------------ + # doxygen + #------------------------------------------------------------------------------ + set(_DOC_HTML_CODE + COMMAND ${CMAKE_COMMAND} -E remove_directory html-gui + COMMAND ${CMAKE_COMMAND} -E chdir ${PROJECT_BINARY_DIR} $ doxygen/Doxyfile + COMMAND ${CMAKE_COMMAND} -E echo "The documentation has been created: ${PROJECT_BINARY_DIR}/html-gui/index.html" + COMMAND ${CMAKE_COMMAND} -E echo "Doxygen warnings are in ${PROJECT_BINARY_DIR}/doxygen/doxygen-error.log" + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Build the doxygen documentation of the GUI layer" + ) + + # full documentation: documenters, then doxygen + add_custom_target(doc ${_DOC_HTML_CODE} VERBATIM) + add_dependencies(doc doc_param_internal) + + # doxygen only, e.g. when editing the documentation + add_custom_target(doc_class_only + ${_DOC_HTML_CODE} + COMMAND ${CMAKE_COMMAND} -E echo "NOTE: the generated tool and parameter documentation was not updated (target 'doc' does that)." + VERBATIM) + + #------------------------------------------------------------------------------ + # Install documentation (if it was built) + install(DIRECTORY ${PROJECT_BINARY_DIR}/html-gui + DESTINATION ${INSTALL_DOC_DIR} + COMPONENT doc_gui + OPTIONAL + FILE_PERMISSIONS OWNER_WRITE OWNER_READ + GROUP_READ + WORLD_READ + DIRECTORY_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_READ + WORLD_EXECUTE WORLD_READ + REGEX "^\\..*" EXCLUDE ## Exclude hidden files (svn, git, DSStore) + REGEX ".*\\/\\..*" EXCLUDE ## Exclude hidden files in subdirectories + REGEX ".*\\.map\$" EXCLUDE ## Exclude temporary map files + REGEX ".*\\.md5\$" EXCLUDE ## Exclude temporary md5 files + ) +else() + message(STATUS "Documentation is not built (-D ENABLE_DOCS=Off).") +endif() + +#------------------------------------------------------------------------------ +# Code examples of the tutorials that use the GUI library +#------------------------------------------------------------------------------ +if(ENABLE_TUTORIALS AND "${PACKAGE_TYPE}" STREQUAL "none") + add_subdirectory(code_examples) + message(STATUS "GUI code examples are built (target Tutorials_build, -D ENABLE_TUTORIALS=On).") +elseif(ENABLE_TUTORIALS) + message(STATUS "GUI code examples are not built (PACKAGE_TYPE=${PACKAGE_TYPE}, must be 'none').") +endif() diff --git a/doc/code_examples/CMakeLists.txt b/doc/code_examples/CMakeLists.txt new file mode 100644 index 000000000..de35c37bc --- /dev/null +++ b/doc/code_examples/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Stephan Aiche $ +# $Authors: Stephan Aiche $ +# -------------------------------------------------------------------------- + +# CMake sub-project for the code examples of the GUI library + +project("OpenMS_TOPP_GUI_code_examples") + +set(GUI_EXAMPLES_executables) + +# get the actual code examples +include(${CMAKE_CURRENT_LIST_DIR}/executables.cmake) + +# add the targets (no tests: they open windows and wait for the user) +foreach(i ${GUI_EXAMPLES_executables}) + add_executable(${i} ${CMAKE_CURRENT_LIST_DIR}/${i}.cpp) + target_link_libraries(${i} OpenMS_GUI) + # the data the examples load, from this directory (not from the OpenMS core) + target_compile_definitions(${i} PRIVATE GUI_TUTORIAL_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/data") +endforeach(i) + +# add collection target +add_custom_target(Tutorials_build) +add_dependencies(Tutorials_build ${GUI_EXAMPLES_executables}) diff --git a/doc/code_examples/Tutorial_GUI_ParamEditor.cpp b/doc/code_examples/Tutorial_GUI_ParamEditor.cpp index 3b012cdc3..17c9ea8a3 100644 --- a/doc/code_examples/Tutorial_GUI_ParamEditor.cpp +++ b/doc/code_examples/Tutorial_GUI_ParamEditor.cpp @@ -6,7 +6,6 @@ #include #include #include -#include // exotic header for path to tutorial data #include #include @@ -16,14 +15,15 @@ using namespace std; Int main(int argc, const char** argv) { - std::string tutorial_data_path(OPENMS_DOC_PATH + std::string("/code_examples/")); - + // GUI_TUTORIAL_DATA_PATH: doc/code_examples/data of the source tree (set in CMakeLists.txt) + std::string tutorial_data_path(GUI_TUTORIAL_DATA_PATH); + QApplication app(argc, const_cast(argv)); Param param; ParamXMLFile paramFile; - paramFile.load(tutorial_data_path + "/data/Tutorial_ParamEditor.ini", param); + paramFile.load(tutorial_data_path + "/Tutorial_ParamEditor.ini", param); ParamEditor editor(nullptr); editor.load(param); diff --git a/doc/code_examples/Tutorial_GUI_Plot1D.cpp b/doc/code_examples/Tutorial_GUI_Plot1D.cpp index 56a057152..b5774f98c 100644 --- a/doc/code_examples/Tutorial_GUI_Plot1D.cpp +++ b/doc/code_examples/Tutorial_GUI_Plot1D.cpp @@ -10,7 +10,6 @@ #include #include #include -#include // exotic header for path to tutorial data #include #include @@ -20,7 +19,8 @@ using namespace std; Int main(int argc, const char** argv) { - std::string tutorial_data_path(OPENMS_DOC_PATH + std::string("/code_examples/data/Tutorial_Spectrum1D.dta")); + // GUI_TUTORIAL_DATA_PATH: doc/code_examples/data of the source tree (set in CMakeLists.txt) + std::string tutorial_data_path(GUI_TUTORIAL_DATA_PATH + std::string("/Tutorial_Spectrum1D.dta")); QApplication app(argc, const_cast(argv)); diff --git a/doc/code_examples/data/Tutorial_Spectrum1D.dta b/doc/code_examples/data/Tutorial_Spectrum1D.dta new file mode 100755 index 000000000..9fbe7f019 --- /dev/null +++ b/doc/code_examples/data/Tutorial_Spectrum1D.dta @@ -0,0 +1,26 @@ +1745.22 3 +139.42 318.52 +149.93 61870.99 +169.65 62074.22 +189.30 53737.85 +202.28 49410.25 +207.82 17038.71 +219.72 73629.98 +230.02 47218.89 +231.51 89935.22 +263.88 81685.67 +318.38 11876.49 +362.22 91984.30 +389.84 35049.17 +489.86 55259.42 +508.47 63411.68 +521.44 42096.49 +546.98 7133.92 +562.72 47540.11 +579.61 63350.29 +600.60 29075.85 +628.64 41030.62 +629.66 51153.79 +630.37 31411.63 +652.64 26967.56 +712.18 29235.72 diff --git a/doc/code_examples/executables.cmake b/doc/code_examples/executables.cmake new file mode 100644 index 000000000..a5aa8b7d8 --- /dev/null +++ b/doc/code_examples/executables.cmake @@ -0,0 +1,22 @@ +# -------------------------------------------------------------------------- +# the directory name +set(directory doc/code_examples) + +# -------------------------------------------------------------------------- +# list all filenames of the directory here +set(executables_list +Tutorial_GUI_ListEditor +Tutorial_GUI_ParamEditor +Tutorial_GUI_Plot1D +) + +# pass source file list to the upper instance +set(GUI_EXAMPLES_executables ${GUI_EXAMPLES_executables} ${executables_list}) + +# -------------------------------------------------------------------------- +# add filenames to Visual Studio solution tree +set(sources_VS) +foreach(i ${executables_list}) + list(APPEND sources_VS "${i}.cpp") +endforeach(i) +source_group("EXAMPLES" FILES ${sources_VS}) diff --git a/doc/doxygen/Doxyfile.in b/doc/doxygen/Doxyfile.in new file mode 100644 index 000000000..ddc278918 --- /dev/null +++ b/doc/doxygen/Doxyfile.in @@ -0,0 +1,266 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# Doxygen configuration of the documentation of the GUI layer (doc/CMakeLists.txt +# configures it into /doc/doxygen/Doxyfile). Only change Doxyfile.in. +# Settings not listed here have doxygen's default value (doxygen -x shows the +# difference to the defaults, doxygen -g a fully commented file). +# +# Inputs: doc/doxygen/public (main page, TOPPAS tutorial, module groups), the +# headers of the GUI library and the GUI applications (registered with +# openms_doc_path() in src/openms_gui/CMakeLists.txt). The generated tool and +# parameter documentation is read from /doc/doxygen/parameters/output. +# TAGFILES links the documentation of the OpenMS core (OPENMS_DOXYGEN_TAGFILE). + +#--------------------------------------------------------------------------- +# Project +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "OpenMS GUI" +PROJECT_NUMBER = "@CF_OPENMS_PACKAGE_VERSION@" +PROJECT_BRIEF = "TOPPView, TOPPAS, INIFileEditor and the OpenMS_GUI library" +PROJECT_LOGO = @CF_OPENMS_SRC_PATH@/doc/doxygen/common/OpenMS_75x55_transparent.png +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = @CF_STRIP_PATH@ +STRIP_FROM_INC_PATH = @CF_OPENMS_DOCUMENTATION_STRIP_INCLUDES@ +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +JAVADOC_BANNER = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 2 +ALIASES = "improvement=\xrefitem improvement \"Improvement\" \"Improvements List\"" \ + "experimental=\xrefitem experimental \"Experimental classes\" \"Experimental List\"" +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +OPTIMIZE_OUTPUT_SLICE = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +TOC_INCLUDE_HEADINGS = 5 +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = YES +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = YES +TYPEDEF_HIDES_STRUCT = NO + +#--------------------------------------------------------------------------- +# Build +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_PRIV_VIRTUAL = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = YES +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = YES +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Warnings +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = YES +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = "@CF_OPENMS_BIN_PATH@/doxygen/doxygen-error.log" + +#--------------------------------------------------------------------------- +# Input +#--------------------------------------------------------------------------- +INPUT = "@CF_OPENMS_SRC_PATH@/doc/doxygen/public/" \ + @CF_OPENMS_DOCUMENTATION_DIRECTORIES@ ## already contains quotes. Do NOT add extra quotes to this variable. +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.cpp \ + *.C \ + *.h \ + *.doxygen +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = */.git/* \ + *_moc* \ + */UIC/* \ + */backup/* \ + */test/* +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = "@CF_OPENMS_SRC_PATH@/doc/code_examples/" \ + "@CF_OPENMS_BIN_PATH@/doxygen/parameters/output/" +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = "@CF_OPENMS_SRC_PATH@/doc/doxygen/images/" +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = "@CF_OPENMS_BIN_PATH@/html-gui" +HTML_FILE_EXTENSION = .html +HTML_HEADER = "@CF_OPENMS_SRC_PATH@/doc/doxygen/common/header.html" +HTML_FOOTER = "@CF_OPENMS_SRC_PATH@/doc/doxygen/common/footer.html" +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = "@CF_OPENMS_SRC_PATH@/doc/doxygen/doxygen-awesome-css/doxygen-awesome.css" \ + "@CF_OPENMS_SRC_PATH@/doc/doxygen/doxygen-awesome-css/custom.css" \ + "@CF_OPENMS_SRC_PATH@/doc/doxygen/common/style_ini.css" +HTML_EXTRA_FILES = "@CF_OPENMS_SRC_PATH@/doc/doxygen/common/favicon.ico" \ + "@CF_OPENMS_SRC_PATH@/doc/doxygen/common/selectversion.js" +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_DYNAMIC_MENUS = NO +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +GENERATE_HTMLHELP = NO +GENERATE_QHP = NO +GENERATE_ECLIPSEHELP = NO +DISABLE_INDEX = NO +GENERATE_TREEVIEW = YES +ENUM_VALUES_PER_LINE = 4 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +USE_MATHJAX = YES +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Other output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +GENERATE_RTF = NO +GENERATE_MAN = NO +GENERATE_XML = NO +GENERATE_DOCBOOK = NO +GENERATE_AUTOGEN_DEF = NO +GENERATE_PERLMOD = NO + +#--------------------------------------------------------------------------- +# Preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = "OPENMS_DLLAPI:=" \ + "OPENMS_GUI_DLLAPI:=" +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# External references +#--------------------------------------------------------------------------- +TAGFILES = @CF_OPENMS_DOXYGEN_TAGFILE@ +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = NO +EXTERNAL_PAGES = NO + +#--------------------------------------------------------------------------- +# Diagrams +#--------------------------------------------------------------------------- +HIDE_UNDOC_RELATIONS = NO +HAVE_DOT = NO +CLASS_GRAPH = YES +COLLABORATION_GRAPH = NO +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = NO +INCLUDED_BY_GRAPH = NO +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = NO +DIRECTORY_GRAPH = NO +DOT_PATH = "@CF_DOXYGEN_DOT_EXECUTABLE@" diff --git a/doc/doxygen/common/OpenMS_75x55_transparent.png b/doc/doxygen/common/OpenMS_75x55_transparent.png new file mode 100644 index 000000000..85b290205 Binary files /dev/null and b/doc/doxygen/common/OpenMS_75x55_transparent.png differ diff --git a/doc/doxygen/common/favicon.ico b/doc/doxygen/common/favicon.ico new file mode 100644 index 000000000..87c5030af Binary files /dev/null and b/doc/doxygen/common/favicon.ico differ diff --git a/doc/doxygen/common/footer.html b/doc/doxygen/common/footer.html new file mode 100644 index 000000000..a24bf2b9b --- /dev/null +++ b/doc/doxygen/common/footer.html @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/doc/doxygen/common/header.html b/doc/doxygen/common/header.html new file mode 100644 index 000000000..1aee518d3 --- /dev/null +++ b/doc/doxygen/common/header.html @@ -0,0 +1,66 @@ + + + + + + + + +$projectname: $title +$title + + + +$treeview +$search +$mathjax + + + +$extrastylesheet + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
$projectname + + + + + +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
+
+ + diff --git a/doc/doxygen/common/selectversion.js b/doc/doxygen/common/selectversion.js new file mode 100644 index 000000000..cca161fc5 --- /dev/null +++ b/doc/doxygen/common/selectversion.js @@ -0,0 +1,88 @@ +if (window.location.hostname.includes("abibuilder")) { + urlrootdir = "/archive/openms/Documentation" +} else if (window.location.hostname.includes("archive.openms.de")) { + urlrootdir = "/openms/Documentation" +} else { + urlrootdir = "/doxygen" +} + +let patharr = window.location.pathname.replace(/\/+/g, '/').split('/'); + +// the documentation of the GUI layer is in html-gui/, next to html/ of OpenMS +let htmldir = patharr.includes("html-gui") ? "html-gui" : "html"; +let htmlidx = patharr.indexOf(htmldir) +let thisvers = patharr[htmlidx - 1]; + +$('.dropbtn').html(thisvers); +console.log(thisvers) + +// https://stackoverflow.com/questions/30622369 +$.get(urlrootdir + '/release', function(html) { +//console.log(html) +let ret = parseDirectoryListing(html); +console.log("PARSED") +$('.dropdown-content').append(ret.join('')); + +// Now check which links actually exist, and remove the href for those +// that don't. +$('.dropdown-content') + .find('.verslink') + .each(function () { + var el = $(this); + var request = new XMLHttpRequest(); + request.open('HEAD', el.attr('href'), true); + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.status === 404) { + el.removeAttr('href'); + el.css({ + 'color': "gray", + 'text-decoration': 'line-through' + }); + } + } + }; + request.send(); + }); +}); + +function parseDirectoryListing(stringOfHtml) { + stringOfHtml = stringOfHtml.substring(stringOfHtml.indexOf("body") - 1); + stringOfHtml = stringOfHtml.substring(0, stringOfHtml.indexOf("/body")-1); + var html = $($.parseHTML(stringOfHtml)); + console.log(stringOfHtml) + var foo = [] + var docs = html.find(".fb-n>a").each(function() { + foo.push($(this).text()) + }) + + docs = foo.sort().reverse() + + docs = docs.filter(function (line) { + // suppress link to current version + return (line != thisvers) && + (line != "Parent Directory") && + // TODO decide how to display latest version + (line != "latest") && + // suppress hidden dirs + !/^[.]/.test(line); + }); + + docs = docs.map((x) => '' + + x + + ''); + + // This assumes that a nightly documentation folder is always present. + // A specific class does not necessarily need to be present there, + // since this will be checked later. + // TODO Style nightly differently to make sure users see it? + docs.unshift('' + + 'nightly' + + ''); + + return docs; +} diff --git a/doc/doxygen/common/style_ini.css b/doc/doxygen/common/style_ini.css new file mode 100755 index 000000000..f80e820c3 --- /dev/null +++ b/doc/doxygen/common/style_ini.css @@ -0,0 +1,144 @@ +:root { + --item-advanced-foreground-color: var(--nav-menu-foreground-color); +} + +div.ini_global { + padding-left:10px; + border: 2px solid; + font-size: 12px; + padding-top: 5px; + font-family: Lucida Grande, Verdana, Geneva, Arial, sans-serif; + border-collapse: collapse; +} + +div.legend { + width: 200px; + border: 1px dashed; +} + +div.legend div { + padding-left:10px; +} + +div.node { + overflow:hidden; + font-size: 12px; + padding-top: 20px; + border-bottom:1px solid; + display: table; +} + + +div.node span.node_name { + padding: 5px; + text-align:left; + font-weight:bold; + display: table-cell; +} + +div.node span.node_description { + padding-left: 10px; + display: table-cell; +} + +div.ini_global > div.item { + display: table-row; + border-bottom: 5px solid transparent; +} + +.item_advanced { + color: var(--item-advanced-foreground-color); +} + +div.item span.item_name { + display: table-cell; + width:300px; +} + +div.item span.item_value { + display: table-cell; + padding-left: 20px; + padding-left: 10px; + width:100px; +} + +div.item span.item_description { + display: table-cell; + padding-left:20px; + width:400px; +} +div.item span.item_tags { + display: table-cell; + padding-left:20px; + width:200px; +} +div.item span.item_restrictions { + display: table-cell; + padding-left:20px; + width:200px; +} + +.item_required { + color: #FF7F00; + font-weight:bold; +} + +/* For the version selector */ +/* (c) Axel Naumann, CERN; 2020-03-02 */ +/* Modified by Bernd Riederer to incorporate color schemes from doxygen-awesome */ +/* Adapted from https://www.w3schools.com/howto/howto_css_dropdown.asp */ +.dropbtn { + margin-left: var(--spacing-small); + border: 1px solid lightgray; + color: white; + background-color: var(--primary-color); + padding: var(--spacing-small); + font-size: 14px; + min-width: 85px; + } +.dropbtn:after{ + content: ''; + border: 4px solid transparent; + border-top: 4px solid white; + margin-left: 12px; + margin-bottom: 3px; + display: inline-block; + vertical-align: bottom; +} + +/* The container
- needed to position the dropdown content */ +.dropdown { + position: relative; + display: inline-block; +} + +/* Dropdown Content (Hidden by Default) */ +.dropdown-content { + display: none; + position: absolute; + margin-left: var(--spacing-small); + background-color: var(--page-background-color); + min-width: 75px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 103; /* search bar has 102 */ +} + +/* Links inside the dropdown */ +.dropdown-content a { + color: var(--page-foreground-color); + padding: 4px 16px; + text-decoration: none; + display: block; +} + +/* Change color of dropdown links on hover */ +.dropdown-content a:hover {background-color: var(--primary-lighter-color);} + +/* Show the dropdown menu on hover */ +.dropdown:hover .dropdown-content {display: block;} + +/* Change the background color of the dropdown button when the dropdown content is shown */ +.dropdown:hover .dropbtn { + color: black; + background-color: var(--primary-light-color); +} diff --git a/doc/doxygen/doxygen-awesome-css/LICENSE b/doc/doxygen/doxygen-awesome-css/LICENSE new file mode 100644 index 000000000..1d8b99a95 --- /dev/null +++ b/doc/doxygen/doxygen-awesome-css/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 jothepro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/doxygen/doxygen-awesome-css/README.md b/doc/doxygen/doxygen-awesome-css/README.md new file mode 100644 index 000000000..bebbd46a7 --- /dev/null +++ b/doc/doxygen/doxygen-awesome-css/README.md @@ -0,0 +1,90 @@ +# Doxygen Awesome + +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/jothepro/doxygen-awesome-css)](https://github.com/jothepro/doxygen-awesome-css/releases/latest) +[![GitHub](https://img.shields.io/github/license/jothepro/doxygen-awesome-css)](https://github.com/jothepro/doxygen-awesome-css/blob/main/LICENSE) +![GitHub Repo stars](https://img.shields.io/github/stars/jothepro/doxygen-awesome-css) + +
+ +![Screenshot of Doxygen Awesome CSS](img/screenshot.png) + +
+ +**Doxygen Awesome** is a custom **CSS theme for Doxygen HTML-documentation** with lots of customization parameters. + +## Motivation + +I really like how the Doxygen HTML-documentation is structured! But IMHO it looks a bit outdated. + +This theme is an attemt to update the visuals of Doxygen without changing it's overall layout too much. + +## Features + +- 🌈 Clean, modern design +- 🚀 Heavily customizable by adjusting CSS-variables +- 🧩 No changes to the HTML structure of Doxygen required +- 📱 Improved mobile usability +- 🌘 Dark mode support! +- 🥇 Works best with **doxygen 1.9.1** - **1.9.3** + +## Examples + +- Sidebar-Only theme: [Documentation of this repository](https://jothepro.github.io/doxygen-awesome-css/) +- Base theme: [libsl3](https://a4z.github.io/libsl3/) + +## Installation + +Copy the file `doxygen-awesome.css` from this repository into your project or add this repository as submodule and check out the latest release: + +```bash +git submodule add https://github.com/jothepro/doxygen-awesome-css.git +cd doxygen-awesome-css +git checkout v2.0.3 +``` + +Choose one of the theme variants and configure Doxygen accordingly: + + + +![Available theme variants](img/theme-variants.drawio.svg) + + + +1. **Base theme**: +``` +# Doxyfile +GENERATE_TREEVIEW = YES # optional. Also works without treeview +HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css +``` + +2. **Sidebar-only theme**: +``` +# Doxyfile +GENERATE_TREEVIEW = YES # required! +HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css \ + doxygen-awesome-css/doxygen-awesome-sidebar-only.css +``` + +Further installation instructions: + +- [How to install extensions](docs/extensions.md) +- [How to customize the theme (colors, spacing, border-radius, ...)](docs/customization.md) +- [Tips and Tricks for further configuration](docs/tricks.md) + +## Browser support + +Tested with + +- Chrome 98, Chrome 98 for Android, Chrome 87 for iOS +- Safari 15, Safari for iOS 15 +- Firefox 97, Firefox Daylight 97 for Android, Firefox Daylight 96 for iOS + +## Credits + +- This theme is inspired by the [vuepress](https://vuepress.vuejs.org/) static site generator default theme. +- Thank you for all the feedback on github! + + + +Read Next: [Extensions](docs/extensions.md) + \ No newline at end of file diff --git a/doc/doxygen/doxygen-awesome-css/custom.css b/doc/doxygen/doxygen-awesome-css/custom.css new file mode 100644 index 000000000..66c17d6bf --- /dev/null +++ b/doc/doxygen/doxygen-awesome-css/custom.css @@ -0,0 +1,4 @@ +html { + --primary-color: #29379b; + --content-maxwidth: auto; +} \ No newline at end of file diff --git a/doc/doxygen/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js b/doc/doxygen/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js new file mode 100644 index 000000000..f2c5853f7 --- /dev/null +++ b/doc/doxygen/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js @@ -0,0 +1,157 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2021 - 2022 jothepro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +class DoxygenAwesomeDarkModeToggle extends HTMLElement { + // SVG icons from https://fonts.google.com/icons + // Licensed under the Apache 2.0 license: + // https://www.apache.org/licenses/LICENSE-2.0.html + static lightModeIcon = `` + static darkModeIcon = `` + static title = "Toggle Light/Dark Mode" + + static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode" + static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode" + + static _staticConstructor = function() { + DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.userPreference) + // Update the color scheme when the browsers preference changes + // without user interaction on the website. + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { + DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged() + }) + // Update the color scheme when the tab is made visible again. + // It is possible that the appearance was changed in another tab + // while this tab was in the background. + document.addEventListener("visibilitychange", visibilityState => { + if (document.visibilityState === 'visible') { + DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged() + } + }); + }() + + static init() { + $(function() { + $(document).ready(function() { + const toggleButton = document.createElement('doxygen-awesome-dark-mode-toggle') + toggleButton.title = DoxygenAwesomeDarkModeToggle.title + toggleButton.updateIcon() + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { + toggleButton.updateIcon() + }) + document.addEventListener("visibilitychange", visibilityState => { + if (document.visibilityState === 'visible') { + toggleButton.updateIcon() + } + }); + + $(document).ready(function(){ + document.getElementById("MSearchBox").parentNode.appendChild(toggleButton) + }) + $(window).resize(function(){ + document.getElementById("MSearchBox").parentNode.appendChild(toggleButton) + }) + }) + }) + } + + constructor() { + super(); + this.onclick=this.toggleDarkMode + } + + /** + * @returns `true` for dark-mode, `false` for light-mode system preference + */ + static get systemPreference() { + return window.matchMedia('(prefers-color-scheme: dark)').matches + } + + /** + * @returns `true` for dark-mode, `false` for light-mode user preference + */ + static get userPreference() { + return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) || + (DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)) + } + + static set userPreference(userPreference) { + DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference + if(!userPreference) { + if(DoxygenAwesomeDarkModeToggle.systemPreference) { + localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true) + } else { + localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey) + } + } else { + if(!DoxygenAwesomeDarkModeToggle.systemPreference) { + localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true) + } else { + localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey) + } + } + DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged() + } + + static enableDarkMode(enable) { + if(enable) { + DoxygenAwesomeDarkModeToggle.darkModeEnabled = true + document.documentElement.classList.add("dark-mode") + document.documentElement.classList.remove("light-mode") + } else { + DoxygenAwesomeDarkModeToggle.darkModeEnabled = false + document.documentElement.classList.remove("dark-mode") + document.documentElement.classList.add("light-mode") + } + } + + static onSystemPreferenceChanged() { + DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference + DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) + } + + static onUserPreferenceChanged() { + DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) + } + + toggleDarkMode() { + DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference + this.updateIcon() + } + + updateIcon() { + if(DoxygenAwesomeDarkModeToggle.darkModeEnabled) { + this.innerHTML = DoxygenAwesomeDarkModeToggle.darkModeIcon + } else { + this.innerHTML = DoxygenAwesomeDarkModeToggle.lightModeIcon + } + } +} + +customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle); diff --git a/doc/doxygen/doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js b/doc/doxygen/doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js new file mode 100644 index 000000000..7d06b348d --- /dev/null +++ b/doc/doxygen/doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js @@ -0,0 +1,85 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2022 jothepro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +class DoxygenAwesomeFragmentCopyButton extends HTMLElement { + constructor() { + super(); + this.onclick=this.copyContent + } + static title = "Copy to clipboard" + static copyIcon = `` + static successIcon = `` + static successDuration = 980 + static init() { + $(function() { + $(document).ready(function() { + if(navigator.clipboard) { + const fragments = document.getElementsByClassName("fragment") + for(const fragment of fragments) { + const fragmentWrapper = document.createElement("div") + fragmentWrapper.className = "doxygen-awesome-fragment-wrapper" + const fragmentCopyButton = document.createElement("doxygen-awesome-fragment-copy-button") + fragmentCopyButton.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon + fragmentCopyButton.title = DoxygenAwesomeFragmentCopyButton.title + + fragment.parentNode.replaceChild(fragmentWrapper, fragment) + fragmentWrapper.appendChild(fragment) + fragmentWrapper.appendChild(fragmentCopyButton) + + } + } + }) + }) + } + + + copyContent() { + const content = this.previousSibling.cloneNode(true) + // filter out line number from file listings + content.querySelectorAll(".lineno, .ttc").forEach((node) => { + node.remove() + }) + let textContent = content.textContent + // remove trailing newlines that appear in file listings + let numberOfTrailingNewlines = 0 + while(textContent.charAt(textContent.length - (numberOfTrailingNewlines + 1)) == '\n') { + numberOfTrailingNewlines++; + } + textContent = textContent.substring(0, textContent.length - numberOfTrailingNewlines) + navigator.clipboard.writeText(textContent); + this.classList.add("success") + this.innerHTML = DoxygenAwesomeFragmentCopyButton.successIcon + window.setTimeout(() => { + this.classList.remove("success") + this.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon + }, DoxygenAwesomeFragmentCopyButton.successDuration); + } +} + +customElements.define("doxygen-awesome-fragment-copy-button", DoxygenAwesomeFragmentCopyButton) diff --git a/doc/doxygen/doxygen-awesome-css/doxygen-awesome-paragraph-link.js b/doc/doxygen/doxygen-awesome-css/doxygen-awesome-paragraph-link.js new file mode 100644 index 000000000..6424dbd42 --- /dev/null +++ b/doc/doxygen/doxygen-awesome-css/doxygen-awesome-paragraph-link.js @@ -0,0 +1,51 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2022 jothepro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +class DoxygenAwesomeParagraphLink { + // Icon from https://fonts.google.com/icons + // Licensed under the Apache 2.0 license: + // https://www.apache.org/licenses/LICENSE-2.0.html + static icon = `` + static title = "Permanent Link" + static init() { + $(function() { + $(document).ready(function() { + document.querySelectorAll(".contents a.anchor[id], .contents .groupheader > a[id]").forEach((node) => { + let anchorlink = document.createElement("a") + anchorlink.setAttribute("href", `#${node.getAttribute("id")}`) + anchorlink.setAttribute("title", DoxygenAwesomeParagraphLink.title) + anchorlink.classList.add("anchorlink") + node.classList.add("anchor") + anchorlink.innerHTML = DoxygenAwesomeParagraphLink.icon + node.parentElement.appendChild(anchorlink) + }) + }) + }) + } +} diff --git a/doc/doxygen/doxygen-awesome-css/doxygen-awesome.css b/doc/doxygen/doxygen-awesome-css/doxygen-awesome.css new file mode 100644 index 000000000..8ec8fbbce --- /dev/null +++ b/doc/doxygen/doxygen-awesome-css/doxygen-awesome.css @@ -0,0 +1,2137 @@ +/** + +Doxygen Awesome +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2021 - 2022 jothepro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +html { + /* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */ + --primary-color: #1779c4; + --primary-dark-color: #335c80; + --primary-light-color: #70b1e9; + + /* page base colors */ + --page-background-color: white; + --page-foreground-color: #2f4153; + --page-secondary-foreground-color: #637485; + + /* color for all separators on the website: hr, borders, ... */ + --separator-color: #dedede; + + /* border radius for all rounded components. Will affect many components, like dropdowns, memitems, codeblocks, ... */ + --border-radius-large: 8px; + --border-radius-small: 4px; + --border-radius-medium: 6px; + + /* default spacings. Most compontest reference these values for spacing, to provide uniform spacing on the page. */ + --spacing-small: 5px; + --spacing-medium: 10px; + --spacing-large: 16px; + + /* default box shadow used for raising an element above the normal content. Used in dropdowns, Searchresult, ... */ + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.075); + + --odd-color: rgba(0,0,0,.028); + + /* font-families. will affect all text on the website + * font-family: the normal font for text, headlines, menus + * font-family-monospace: used for preformatted text in memtitle, code, fragments + */ + --font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; + --font-family-monospace: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + + /* font sizes */ + --page-font-size: 15.6px; + --navigation-font-size: 14.4px; + --code-font-size: 14px; /* affects code, fragment */ + --title-font-size: 22px; + + /* content text properties. These only affect the page content, not the navigation or any other ui elements */ + --content-line-height: 27px; + /* The content is centered and constraint in it's width. To make the content fill the whole page, set the variable to auto.*/ + --content-maxwidth: 1000px; + + /* colors for various content boxes: @warning, @note, @deprecated @bug */ + --warning-color: #f8d1cc; + --warning-color-dark: #b61825; + --warning-color-darker: #75070f; + --note-color: #faf3d8; + --note-color-dark: #f3a600; + --note-color-darker: #5f4204; + --todo-color: #e4f3ff; + --todo-color-dark: #1879C4; + --todo-color-darker: #274a5c; + --deprecated-color: #ecf0f3; + --deprecated-color-dark: #5b6269; + --deprecated-color-darker: #43454a; + --bug-color: #e4dafd; + --bug-color-dark: #5b2bdd; + --bug-color-darker: #2a0d72; + --invariant-color: #d8f1e3; + --invariant-color-dark: #44b86f; + --invariant-color-darker: #265532; + + /* blockquote colors */ + --blockquote-background: #f8f9fa; + --blockquote-foreground: #636568; + + /* table colors */ + --tablehead-background: #f1f1f1; + --tablehead-foreground: var(--page-foreground-color); + + /* menu-display: block | none + * Visibility of the top navigation on screens >= 768px. On smaller screen the menu is always visible. + * `GENERATE_TREEVIEW` MUST be enabled! + */ + --menu-display: block; + + --menu-focus-foreground: var(--page-background-color); + --menu-focus-background: var(--primary-color); + --menu-selected-background: rgba(0,0,0,.05); + + + --header-background: var(--page-background-color); + --header-foreground: var(--page-foreground-color); + + /* searchbar colors */ + --searchbar-background: var(--side-nav-background); + --searchbar-foreground: var(--page-foreground-color); + + /* searchbar size + * (`searchbar-width` is only applied on screens >= 768px. + * on smaller screens the searchbar will always fill the entire screen width) */ + --searchbar-height: 33px; + --searchbar-width: 210px; + --searchbar-border-radius: var(--searchbar-height); + + /* code block colors */ + --code-background: #f5f5f5; + --code-foreground: var(--page-foreground-color); + + /* fragment colors */ + --fragment-background: #F8F9FA; + --fragment-foreground: #37474F; + --fragment-keyword: #bb6bb2; + --fragment-keywordtype: #8258b3; + --fragment-keywordflow: #d67c3b; + --fragment-token: #438a59; + --fragment-comment: #969696; + --fragment-link: #5383d6; + --fragment-preprocessor: #46aaa5; + --fragment-linenumber-color: #797979; + --fragment-linenumber-background: #f4f4f5; + --fragment-linenumber-border: #e3e5e7; + --fragment-lineheight: 20px; + + /* sidebar navigation (treeview) colors */ + --side-nav-background: #fbfbfb; + --side-nav-foreground: var(--page-foreground-color); + --side-nav-arrow-opacity: 0; + --side-nav-arrow-hover-opacity: 0.9; + + --toc-background: var(--side-nav-background); + --toc-foreground: var(--side-nav-foreground); + + /* height of an item in any tree / collapsable table */ + --tree-item-height: 30px; + + --memname-font-size: var(--code-font-size); + --memtitle-font-size: 18px; + + --webkit-scrollbar-size: 7px; + --webkit-scrollbar-padding: 4px; + --webkit-scrollbar-color: var(--separator-color); +} + +@media screen and (max-width: 767px) { + html { + --page-font-size: 16px; + --navigation-font-size: 16px; + --code-font-size: 15px; /* affects code, fragment */ + --title-font-size: 22px; + } +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) { + color-scheme: dark; + + --primary-color: #1982d2; + --primary-dark-color: #86a9c4; + --primary-light-color: #4779ac; + + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.35); + + --odd-color: rgba(100,100,100,.06); + + --menu-selected-background: rgba(0,0,0,.4); + + --page-background-color: #1C1D1F; + --page-foreground-color: #d2dbde; + --page-secondary-foreground-color: #859399; + --separator-color: #38393b; + --side-nav-background: #252628; + + --code-background: #2a2c2f; + + --tablehead-background: #2a2c2f; + + --blockquote-background: #222325; + --blockquote-foreground: #7e8c92; + + --warning-color: #2e1917; + --warning-color-dark: #ad2617; + --warning-color-darker: #f5b1aa; + --note-color: #3b2e04; + --note-color-dark: #f1b602; + --note-color-darker: #ceb670; + --todo-color: #163750; + --todo-color-dark: #1982D2; + --todo-color-darker: #dcf0fa; + --deprecated-color: #2e323b; + --deprecated-color-dark: #738396; + --deprecated-color-darker: #abb0bd; + --bug-color: #2a2536; + --bug-color-dark: #7661b3; + --bug-color-darker: #ae9ed6; + --invariant-color: #303a35; + --invariant-color-dark: #76ce96; + --invariant-color-darker: #cceed5; + + --fragment-background: #282c34; + --fragment-foreground: #dbe4eb; + --fragment-keyword: #cc99cd; + --fragment-keywordtype: #ab99cd; + --fragment-keywordflow: #e08000; + --fragment-token: #7ec699; + --fragment-comment: #999999; + --fragment-link: #98c0e3; + --fragment-preprocessor: #65cabe; + --fragment-linenumber-color: #cccccc; + --fragment-linenumber-background: #35393c; + --fragment-linenumber-border: #1f1f1f; + } +} + +/* dark mode variables are defined twice, to support both the dark-mode without and with doxygen-awesome-darkmode-toggle.js */ +html.dark-mode { + color-scheme: dark; + + --primary-color: #1982d2; + --primary-dark-color: #86a9c4; + --primary-light-color: #4779ac; + + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.30); + + --odd-color: rgba(100,100,100,.06); + + --menu-selected-background: rgba(0,0,0,.4); + + --page-background-color: #1C1D1F; + --page-foreground-color: #d2dbde; + --page-secondary-foreground-color: #859399; + --separator-color: #38393b; + --side-nav-background: #252628; + + --code-background: #2a2c2f; + + --tablehead-background: #2a2c2f; + + --blockquote-background: #222325; + --blockquote-foreground: #7e8c92; + + --warning-color: #2e1917; + --warning-color-dark: #ad2617; + --warning-color-darker: #f5b1aa; + --note-color: #3b2e04; + --note-color-dark: #f1b602; + --note-color-darker: #ceb670; + --todo-color: #163750; + --todo-color-dark: #1982D2; + --todo-color-darker: #dcf0fa; + --deprecated-color: #2e323b; + --deprecated-color-dark: #738396; + --deprecated-color-darker: #abb0bd; + --bug-color: #2a2536; + --bug-color-dark: #7661b3; + --bug-color-darker: #ae9ed6; + --invariant-color: #303a35; + --invariant-color-dark: #76ce96; + --invariant-color-darker: #cceed5; + + --fragment-background: #282c34; + --fragment-foreground: #dbe4eb; + --fragment-keyword: #cc99cd; + --fragment-keywordtype: #ab99cd; + --fragment-keywordflow: #e08000; + --fragment-token: #7ec699; + --fragment-comment: #999999; + --fragment-link: #98c0e3; + --fragment-preprocessor: #65cabe; + --fragment-linenumber-color: #cccccc; + --fragment-linenumber-background: #35393c; + --fragment-linenumber-border: #1f1f1f; +} + +body { + color: var(--page-foreground-color); + background-color: var(--page-background-color); + font-size: var(--page-font-size); +} + +body, table, div, p, dl, #nav-tree .label, .title, .sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname, .SelectItem, #MSearchField, .navpath li.navelem a, .navpath li.navelem a:hover { + font-family: var(--font-family); +} + +h1, h2, h3, h4, h5 { + margin-top: .9em; + font-weight: 600; + line-height: initial; +} + +p, div, table, dl { + font-size: var(--page-font-size); +} + +a:link, a:visited, a:hover, a:focus, a:active { + color: var(--primary-color) !important; + font-weight: 500; +} + +a.anchor { + scroll-margin-top: var(--spacing-large); +} + +/* + Title and top navigation + */ + +#top { + background: var(--header-background); + border-bottom: 1px solid var(--separator-color); +} + +@media screen and (min-width: 768px) { + #top { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + } +} + +#main-nav { + flex-grow: 5; + padding: var(--spacing-small) var(--spacing-medium); +} + +#titlearea { + width: auto; + padding: var(--spacing-medium) var(--spacing-large); + background: none; + color: var(--header-foreground); + border-bottom: none; +} + +@media screen and (max-width: 767px) { + #titlearea { + padding-bottom: var(--spacing-small); + } +} + +#titlearea table tbody tr { + height: auto !important; +} + +#projectname { + font-size: var(--title-font-size); + font-weight: 600; +} + +#projectnumber { + font-family: inherit; + font-size: 60%; +} + +#projectbrief { + font-family: inherit; + font-size: 80%; +} + +#projectlogo { + vertical-align: middle; +} + +#projectlogo img { + max-height: calc(var(--title-font-size) * 2); + margin-right: var(--spacing-small); +} + +.sm-dox, .tabs, .tabs2, .tabs3 { + background: none; + padding: 0; +} + +.tabs, .tabs2, .tabs3 { + border-bottom: 1px solid var(--separator-color); + margin-bottom: -1px; +} + +@media screen and (max-width: 767px) { + .sm-dox a span.sub-arrow { + background: var(--code-background); + } + + #main-menu a.has-submenu span.sub-arrow { + color: var(--page-secondary-foreground-color); + border-radius: var(--border-radius-medium); + } + + #main-menu a.has-submenu:hover span.sub-arrow { + color: var(--page-foreground-color); + } +} + +@media screen and (min-width: 768px) { + .sm-dox li, .tablist li { + display: var(--menu-display); + } + + .sm-dox a span.sub-arrow { + border-color: var(--header-foreground) transparent transparent transparent; + } + + .sm-dox a:hover span.sub-arrow { + border-color: var(--menu-focus-foreground) transparent transparent transparent; + } + + .sm-dox ul a span.sub-arrow { + border-color: transparent transparent transparent var(--page-foreground-color); + } + + .sm-dox ul a:hover span.sub-arrow { + border-color: transparent transparent transparent var(--menu-focus-foreground); + } +} + +.sm-dox ul { + background: var(--page-background-color); + box-shadow: var(--box-shadow); + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium) !important; + padding: var(--spacing-small); + animation: ease-out 150ms slideInMenu; +} + +@keyframes slideInMenu { + from { + opacity: 0; + transform: translate(0px, -2px); + } + + to { + opacity: 1; + transform: translate(0px, 0px); + } +} + +.sm-dox ul a { + color: var(--page-foreground-color) !important; + background: var(--page-background-color); + font-size: var(--navigation-font-size); +} + +.sm-dox>li>ul:after { + border-bottom-color: var(--page-background-color) !important; +} + +.sm-dox>li>ul:before { + border-bottom-color: var(--separator-color) !important; +} + +.sm-dox ul a:hover, .sm-dox ul a:active, .sm-dox ul a:focus { + font-size: var(--navigation-font-size) !important; + color: var(--menu-focus-foreground) !important; + text-shadow: none; + background-color: var(--menu-focus-background); + border-radius: var(--border-radius-small) !important; +} + +.sm-dox a, .sm-dox a:focus, .tablist li, .tablist li a, .tablist li.current a { + text-shadow: none; + background: transparent; + background-image: none !important; + color: var(--header-foreground) !important; + font-weight: normal; + font-size: var(--navigation-font-size); + border-radius: var(--border-radius-small) !important; +} + +.sm-dox a:focus { + outline: auto; +} + +.sm-dox a:hover, .sm-dox a:active, .tablist li a:hover { + text-shadow: none; + font-weight: normal; + background: var(--menu-focus-background); + color: var(--menu-focus-foreground) !important; + border-radius: var(--border-radius-small) !important; + font-size: var(--navigation-font-size); +} + +.tablist li.current { + border-radius: var(--border-radius-small); + background: var(--menu-selected-background); +} + +.tablist li { + margin: var(--spacing-small) 0 var(--spacing-small) var(--spacing-small); +} + +.tablist a { + padding: 0 var(--spacing-large); +} + + +/* + Search box + */ + +#MSearchBox { + height: var(--searchbar-height); + background: var(--searchbar-background); + border-radius: var(--searchbar-border-radius); + border: 1px solid var(--separator-color); + overflow: hidden; + width: var(--searchbar-width); + position: relative; + box-shadow: none; + display: block; + margin-top: 0; +} + +.left #MSearchSelect { + left: 0; + user-select: none; + padding-left: 8px; +} + +.left #MSearchSelect[src$=".png"] { + padding-left: 0 +} + +.SelectionMark { + user-select: none; +} + +.tabs .left #MSearchSelect { + padding-left: 0; +} + +.tabs #MSearchBox { + position: absolute; + right: var(--spacing-medium); +} + +@media screen and (max-width: 767px) { + .tabs #MSearchBox { + position: relative; + right: 0; + margin-left: var(--spacing-medium); + margin-top: 0; + } +} + +#MSearchSelectWindow, #MSearchResultsWindow { + z-index: 9999; +} + +#MSearchBox.MSearchBoxActive { + border-color: var(--primary-color); + box-shadow: inset 0 0 0 1px var(--primary-color); +} + +#main-menu > li:last-child { + margin-right: 0; +} + +@media screen and (max-width: 767px) { + #main-menu > li:last-child { + height: 50px; + } +} + +#MSearchField { + font-size: var(--navigation-font-size); + height: calc(var(--searchbar-height) - 2px); + background: transparent; + width: calc(var(--searchbar-width) - 64px); +} + +.MSearchBoxActive #MSearchField { + color: var(--searchbar-foreground); +} + +#MSearchSelect { + top: calc(calc(var(--searchbar-height) / 2) - 11px); +} + +#MSearchBox span.left, #MSearchBox span.right { + background: none; + background-image: none; +} + +#MSearchBox span.right { + padding-top: calc(calc(var(--searchbar-height) / 2) - 12px); + position: absolute; + right: var(--spacing-small); +} + +.tabs #MSearchBox span.right { + top: calc(calc(var(--searchbar-height) / 2) - 12px); +} + +@keyframes slideInSearchResults { + from { + opacity: 0; + transform: translate(0, 15px); + } + + to { + opacity: 1; + transform: translate(0, 20px); + } +} + +#MSearchResultsWindow { + left: auto !important; + right: var(--spacing-medium); + border-radius: var(--border-radius-large); + border: 1px solid var(--separator-color); + transform: translate(0, 20px); + box-shadow: var(--box-shadow); + animation: ease-out 280ms slideInSearchResults; + background: var(--page-background-color); +} + +iframe#MSearchResults { + margin: 4px; +} + +iframe { + color-scheme: normal; +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) iframe#MSearchResults { + filter: invert() hue-rotate(180deg); + } +} + +html.dark-mode iframe#MSearchResults { + filter: invert() hue-rotate(180deg); +} + +#MSearchSelectWindow { + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); + box-shadow: var(--box-shadow); + background: var(--page-background-color); + padding-top: var(--spacing-small); + padding-bottom: var(--spacing-small); +} + +#MSearchSelectWindow a.SelectItem { + font-size: var(--navigation-font-size); + line-height: var(--content-line-height); + margin: 0 var(--spacing-small); + border-radius: var(--border-radius-small); + color: var(--page-foreground-color) !important; + font-weight: normal; +} + +#MSearchSelectWindow a.SelectItem:hover { + background: var(--menu-focus-background); + color: var(--menu-focus-foreground) !important; +} + +@media screen and (max-width: 767px) { + #MSearchBox { + margin-top: var(--spacing-medium); + margin-bottom: var(--spacing-medium); + width: calc(100vw - 30px); + } + + #main-menu > li:last-child { + float: none !important; + } + + #MSearchField { + width: calc(100vw - 110px); + } + + @keyframes slideInSearchResultsMobile { + from { + opacity: 0; + transform: translate(0, 15px); + } + + to { + opacity: 1; + transform: translate(0, 20px); + } + } + + #MSearchResultsWindow { + left: var(--spacing-medium) !important; + right: var(--spacing-medium); + overflow: auto; + transform: translate(0, 20px); + animation: ease-out 280ms slideInSearchResultsMobile; + } + + /* + * Overwrites for fixing the searchbox on mobile in doxygen 1.9.2 + */ + label.main-menu-btn ~ #searchBoxPos1 { + top: 3px !important; + right: 6px !important; + left: 45px; + display: flex; + } + + label.main-menu-btn ~ #searchBoxPos1 > #MSearchBox { + margin-top: 0; + margin-bottom: 0; + flex-grow: 2; + float: left; + } +} + +/* + Tree view + */ + +#side-nav { + padding: 0 !important; + background: var(--side-nav-background); +} + +@media screen and (max-width: 767px) { + #side-nav { + display: none; + } + + #doc-content { + margin-left: 0 !important; + } +} + +#nav-tree { + background: transparent; +} + +#nav-tree .label { + font-size: var(--navigation-font-size); +} + +#nav-tree .item { + height: var(--tree-item-height); + line-height: var(--tree-item-height); +} + +#nav-sync { + bottom: 12px; + right: 12px; + top: auto !important; + user-select: none; +} + +#nav-tree .selected { + text-shadow: none; + background-image: none; + background-color: transparent; + position: relative; +} + +#nav-tree .selected::after { + content: ""; + position: absolute; + top: 1px; + bottom: 1px; + left: 0; + width: 4px; + border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; + background: var(--primary-color); +} + + +#nav-tree a { + color: var(--side-nav-foreground) !important; + font-weight: normal; +} + +#nav-tree a:focus { + outline-style: auto; +} + +#nav-tree .arrow { + opacity: var(--side-nav-arrow-opacity); +} + +.arrow { + color: inherit; + cursor: pointer; + font-size: 45%; + vertical-align: middle; + margin-right: 2px; + font-family: serif; + height: auto; + text-align: right; +} + +#nav-tree div.item:hover .arrow, #nav-tree a:focus .arrow { + opacity: var(--side-nav-arrow-hover-opacity); +} + +#nav-tree .selected a { + color: var(--primary-color) !important; + font-weight: bolder; + font-weight: 600; +} + +.ui-resizable-e { + background: var(--separator-color); + width: 1px; +} + +/* + Contents + */ + +div.header { + border-bottom: 1px solid var(--separator-color); + background-color: var(--page-background-color); + background-image: none; +} + +div.contents, div.header .title, div.header .summary { + max-width: var(--content-maxwidth); +} + +div.contents, div.header .title { + line-height: initial; + margin: calc(var(--spacing-medium) + .2em) auto var(--spacing-medium) auto; +} + +div.header .summary { + margin: var(--spacing-medium) auto 0 auto; +} + +div.headertitle { + padding: 0; +} + +div.header .title { + font-weight: 600; + font-size: 210%; + padding: var(--spacing-medium) var(--spacing-large); + word-break: break-word; +} + +div.header .summary { + width: auto; + display: block; + float: none; + padding: 0 var(--spacing-large); +} + +td.memSeparator { + border-color: var(--separator-color); +} + +span.mlabel { + background: var(--primary-color); + border: none; + padding: 4px 9px; + border-radius: 12px; + margin-right: var(--spacing-medium); +} + +span.mlabel:last-of-type { + margin-right: 2px; +} + +div.contents { + padding: 0 var(--spacing-large); +} + +div.contents p, div.contents li { + line-height: var(--content-line-height); +} + +div.contents div.dyncontent { + margin: var(--spacing-medium) 0; +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) div.contents div.dyncontent img, + html:not(.light-mode) div.contents center img, + html:not(.light-mode) div.contents table img, + html:not(.light-mode) div.contents div.dyncontent iframe, + html:not(.light-mode) div.contents center iframe, + html:not(.light-mode) div.contents table iframe { + filter: hue-rotate(180deg) invert(); + } +} + +html.dark-mode div.contents div.dyncontent img, +html.dark-mode div.contents center img, +html.dark-mode div.contents table img, +html.dark-mode div.contents div.dyncontent iframe, +html.dark-mode div.contents center iframe, +html.dark-mode div.contents table iframe { + filter: hue-rotate(180deg) invert(); +} + +h2.groupheader { + border-bottom: 0px; + color: var(--page-foreground-color); + box-shadow: + 100px 0 var(--page-background-color), + -100px 0 var(--page-background-color), + 100px 0.75px var(--separator-color), + -100px 0.75px var(--separator-color), + 500px 0 var(--page-background-color), + -500px 0 var(--page-background-color), + 500px 0.75px var(--separator-color), + -500px 0.75px var(--separator-color), + 1500px 0 var(--page-background-color), + -1500px 0 var(--page-background-color), + 1500px 0.75px var(--separator-color), + -1500px 0.75px var(--separator-color), + 2000px 0 var(--page-background-color), + -2000px 0 var(--page-background-color), + 2000px 0.75px var(--separator-color), + -2000px 0.75px var(--separator-color); +} + +blockquote { + margin: 0 var(--spacing-medium) 0 var(--spacing-medium); + padding: var(--spacing-small) var(--spacing-large); + background: var(--blockquote-background); + color: var(--blockquote-foreground); + border-left: 0; + overflow: visible; + border-radius: var(--border-radius-medium); + overflow: visible; + position: relative; +} + +blockquote::before, blockquote::after { + font-weight: bold; + font-family: serif; + font-size: 360%; + opacity: .15; + position: absolute; +} + +blockquote::before { + content: "“"; + left: -10px; + top: 4px; +} + +blockquote::after { + content: "”"; + right: -8px; + bottom: -25px; +} + +blockquote p { + margin: var(--spacing-small) 0 var(--spacing-medium) 0; +} +.paramname { + font-weight: 600; + color: var(--primary-dark-color); +} + +.paramname > code { + border: 0; +} + +table.params .paramname { + font-weight: 600; + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); + padding-right: var(--spacing-small); +} + +.glow { + text-shadow: 0 0 15px var(--primary-light-color) !important; +} + +.alphachar a { + color: var(--page-foreground-color); +} + +/* + Table of Contents + */ + +div.toc { + z-index: 10; + position: relative; + background-color: var(--toc-background); + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); + box-shadow: var(--box-shadow); + padding: 0 var(--spacing-large); + margin: 0 0 var(--spacing-medium) var(--spacing-medium); +} + +div.toc h3 { + color: var(--toc-foreground); + font-size: var(--navigation-font-size); + margin: var(--spacing-large) 0; +} + +div.toc li { + font-size: var(--navigation-font-size); + padding: 0; + background: none; +} + +div.toc li:before { + content: '↓'; + font-weight: 800; + font-family: var(--font-family); + margin-right: var(--spacing-small); + color: var(--toc-foreground); + opacity: .4; +} + +div.toc ul li.level1 { + margin: 0; +} + +div.toc ul li.level2, div.toc ul li.level3 { + margin-top: 0; +} + + +@media screen and (max-width: 767px) { + div.toc { + float: none; + width: auto; + margin: 0 0 var(--spacing-medium) 0; + } +} + +/* + Code & Fragments + */ + +code, div.fragment, pre.fragment { + border-radius: var(--border-radius-small); + border: 1px solid var(--separator-color); + overflow: hidden; +} + +code { + display: inline; + background: var(--code-background); + color: var(--code-foreground); + padding: 2px 6px; + /* word-break: break-word; */ /* disabled to avoid install instructions looking weird */ +} + +div.fragment, pre.fragment { + margin: var(--spacing-medium) 0; + padding: calc(var(--spacing-large) - (var(--spacing-large) / 6)) var(--spacing-large); + background: var(--fragment-background); + color: var(--fragment-foreground); + overflow-x: auto; +} + +@media screen and (max-width: 767px) { + div.fragment, pre.fragment { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: 0; + } + + .contents > div.fragment, + .textblock > div.fragment, + .textblock > pre.fragment, + .contents > .doxygen-awesome-fragment-wrapper > div.fragment, + .textblock > .doxygen-awesome-fragment-wrapper > div.fragment, + .textblock > .doxygen-awesome-fragment-wrapper > pre.fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-large)); + border-radius: 0; + border-left: 0; + } + + .textblock li > .fragment, + .textblock li > .doxygen-awesome-fragment-wrapper > .fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-large)); + } + + .memdoc li > .fragment, + .memdoc li > .doxygen-awesome-fragment-wrapper > .fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); + } + + .textblock ul, .memdoc ul { + overflow: initial; + } + + .memdoc > div.fragment, + .memdoc > pre.fragment, + dl dd > div.fragment, + dl dd pre.fragment, + .memdoc > .doxygen-awesome-fragment-wrapper > div.fragment, + .memdoc > .doxygen-awesome-fragment-wrapper > pre.fragment, + dl dd > .doxygen-awesome-fragment-wrapper > div.fragment, + dl dd .doxygen-awesome-fragment-wrapper > pre.fragment { + margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); + border-radius: 0; + border-left: 0; + } +} + +code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size) !important; +} + +div.line:after { + margin-right: var(--spacing-medium); +} + +div.fragment .line, pre.fragment { + white-space: pre; + word-wrap: initial; + line-height: var(--fragment-lineheight); +} + +div.fragment span.keyword { + color: var(--fragment-keyword); +} + +div.fragment span.keywordtype { + color: var(--fragment-keywordtype); +} + +div.fragment span.keywordflow { + color: var(--fragment-keywordflow); +} + +div.fragment span.stringliteral { + color: var(--fragment-token) +} + +div.fragment span.comment { + color: var(--fragment-comment); +} + +div.fragment a.code { + color: var(--fragment-link) !important; +} + +div.fragment span.preprocessor { + color: var(--fragment-preprocessor); +} + +div.fragment span.lineno { + display: inline-block; + width: 27px; + border-right: none; + background: var(--fragment-linenumber-background); + color: var(--fragment-linenumber-color); +} + +div.fragment span.lineno a { + background: none; + color: var(--fragment-link) !important; +} + +div.fragment .line:first-child .lineno { + box-shadow: -999999px 0px 0 999999px var(--fragment-linenumber-background), -999998px 0px 0 999999px var(--fragment-linenumber-border); +} + +/* + dl warning, attention, note, deprecated, bug, ... + */ + +dl.bug dt a, dl.deprecated dt a, dl.todo dt a { + font-weight: bold !important; +} + +dl.warning, dl.attention, dl.note, dl.deprecated, dl.bug, dl.invariant, dl.pre, dl.todo, dl.remark { + padding: var(--spacing-medium); + margin: var(--spacing-medium) 0; + color: var(--page-background-color); + overflow: hidden; + margin-left: 0; + border-radius: var(--border-radius-small); +} + +dl.section dd { + margin-bottom: 2px; +} + +dl.warning, dl.attention { + background: var(--warning-color); + border-left: 8px solid var(--warning-color-dark); + color: var(--warning-color-darker); +} + +dl.warning dt, dl.attention dt { + color: var(--warning-color-dark); +} + +dl.note, dl.remark { + background: var(--note-color); + border-left: 8px solid var(--note-color-dark); + color: var(--note-color-darker); +} + +dl.note dt, dl.remark dt { + color: var(--note-color-dark); +} + +dl.todo { + background: var(--todo-color); + border-left: 8px solid var(--todo-color-dark); + color: var(--todo-color-darker); +} + +dl.todo dt { + color: var(--todo-color-dark); +} + +dl.bug dt a { + color: var(--todo-color-dark) !important; +} + +dl.bug { + background: var(--bug-color); + border-left: 8px solid var(--bug-color-dark); + color: var(--bug-color-darker); +} + +dl.bug dt a { + color: var(--bug-color-dark) !important; +} + +dl.deprecated { + background: var(--deprecated-color); + border-left: 8px solid var(--deprecated-color-dark); + color: var(--deprecated-color-darker); +} + +dl.deprecated dt a { + color: var(--deprecated-color-dark) !important; +} + +dl.section dd, dl.bug dd, dl.deprecated dd, dl.todo dd { + margin-inline-start: 0px; +} + +dl.invariant, dl.pre { + background: var(--invariant-color); + border-left: 8px solid var(--invariant-color-dark); + color: var(--invariant-color-darker); +} + +dl.invariant dt, dl.pre dt { + color: var(--invariant-color-dark); +} + +/* + memitem + */ + +div.memdoc, div.memproto, h2.memtitle { + box-shadow: none; + background-image: none; + border: none; +} + +div.memdoc { + padding: 0 var(--spacing-medium); + background: var(--page-background-color); +} + +h2.memtitle, div.memitem { + border: 1px solid var(--separator-color); + box-shadow: var(--box-shadow); +} + +h2.memtitle { + box-shadow: 0px var(--spacing-medium) 0 -1px var(--fragment-background), var(--box-shadow); +} + +div.memitem { + transition: none; +} + +div.memproto, h2.memtitle { + background: var(--fragment-background); + text-shadow: none; +} + +h2.memtitle { + font-weight: 500; + font-size: var(--memtitle-font-size); + font-family: var(--font-family-monospace); + border-bottom: none; + border-top-left-radius: var(--border-radius-medium); + border-top-right-radius: var(--border-radius-medium); + word-break: break-all; + position: relative; +} + +h2.memtitle:after { + content: ""; + display: block; + background: var(--fragment-background); + height: var(--spacing-medium); + bottom: calc(0px - var(--spacing-medium)); + left: 0; + right: -14px; + position: absolute; + border-top-right-radius: var(--border-radius-medium); +} + +h2.memtitle > span.permalink { + font-size: inherit; +} + +h2.memtitle > span.permalink > a { + text-decoration: none; + padding-left: 3px; + margin-right: -4px; + user-select: none; + display: inline-block; + margin-top: -6px; +} + +h2.memtitle > span.permalink > a:hover { + color: var(--primary-dark-color) !important; +} + +a:target + h2.memtitle, a:target + h2.memtitle + div.memitem { + border-color: var(--primary-light-color); +} + +div.memitem { + border-top-right-radius: var(--border-radius-medium); + border-bottom-right-radius: var(--border-radius-medium); + border-bottom-left-radius: var(--border-radius-medium); + overflow: hidden; + display: block !important; +} + +div.memdoc { + border-radius: 0; +} + +div.memproto { + border-radius: 0 var(--border-radius-small) 0 0; + overflow: auto; + border-bottom: 1px solid var(--separator-color); + padding: var(--spacing-medium); + margin-bottom: -1px; +} + +div.memtitle { + border-top-right-radius: var(--border-radius-medium); + border-top-left-radius: var(--border-radius-medium); +} + +div.memproto table.memname { + font-family: var(--font-family-monospace); + color: var(--page-foreground-color); + font-size: var(--memname-font-size); +} + +div.memproto div.memtemplate { + font-family: var(--font-family-monospace); + color: var(--primary-dark-color); + font-size: var(--memname-font-size); + margin-left: 2px; +} + +table.mlabels, table.mlabels > tbody { + display: block; +} + +td.mlabels-left { + width: auto; +} + +td.mlabels-right { + margin-top: 3px; + position: sticky; + left: 0; +} + +table.mlabels > tbody > tr:first-child { + display: flex; + justify-content: space-between; + flex-wrap: wrap; +} + +.memname, .memitem span.mlabels { + margin: 0 +} + +/* + reflist + */ + +dl.reflist { + box-shadow: var(--box-shadow); + border-radius: var(--border-radius-medium); + border: 1px solid var(--separator-color); + overflow: hidden; + padding: 0; +} + + +dl.reflist dt, dl.reflist dd { + box-shadow: none; + text-shadow: none; + background-image: none; + border: none; + padding: 12px; +} + + +dl.reflist dt { + font-weight: 500; + border-radius: 0; + background: var(--code-background); + border-bottom: 1px solid var(--separator-color); + color: var(--page-foreground-color) +} + + +dl.reflist dd { + background: none; +} + +/* + Table + */ + +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) { + display: inline-block; + max-width: 100%; + } + +.contents > table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname):not(.classindex) { + margin-left: calc(0px - var(--spacing-large)); + margin-right: calc(0px - var(--spacing-large)); + max-width: calc(100% + 2 * var(--spacing-large)); +} + +table.markdownTable, table.fieldtable { + border: none; + margin: var(--spacing-medium) 0; + box-shadow: 0 0 0 1px var(--separator-color); + border-radius: var(--border-radius-small); +} + +table.fieldtable { + width: 100%; +} + +th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { + background: var(--tablehead-background); + color: var(--tablehead-foreground); + font-weight: 600; + font-size: var(--page-font-size); +} + +th.markdownTableHeadLeft:first-child, th.markdownTableHeadRight:first-child, th.markdownTableHeadCenter:first-child, th.markdownTableHeadNone:first-child { + border-top-left-radius: var(--border-radius-small); +} + +th.markdownTableHeadLeft:last-child, th.markdownTableHeadRight:last-child, th.markdownTableHeadCenter:last-child, th.markdownTableHeadNone:last-child { + border-top-right-radius: var(--border-radius-small); +} + +table.markdownTable td, table.markdownTable th, table.fieldtable dt { + border: none; + border-right: 1px solid var(--separator-color); + padding: var(--spacing-small) var(--spacing-medium); +} + +table.markdownTable td:last-child, table.markdownTable th:last-child, table.fieldtable dt:last-child { + border: none; +} + +table.markdownTable tr, table.markdownTable tr { + border-bottom: 1px solid var(--separator-color); +} + +table.markdownTable tr:last-child, table.markdownTable tr:last-child { + border-bottom: none; +} + +table.fieldtable th { + font-size: var(--page-font-size); + font-weight: 600; + background-image: none; + background-color: var(--tablehead-background); + color: var(--tablehead-foreground); + border-bottom: 1px solid var(--separator-color); +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + border-bottom: 1px solid var(--separator-color); + border-right: 1px solid var(--separator-color); +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid var(--separator-color); +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: var(--primary-light-color); + box-shadow: 0 0 15px var(--primary-light-color); +} + +table.memberdecls { + display: block; +} + +table.memberdecls tr[class^='memitem'] { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); +} + +table.memberdecls tr[class^='memitem'] .memTemplParams { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); + color: var(--primary-dark-color); +} + +table.memberdecls .memItemLeft, +table.memberdecls .memItemRight, +table.memberdecls .memTemplItemLeft, +table.memberdecls .memTemplItemRight, +table.memberdecls .memTemplParams { + transition: none; + padding-top: var(--spacing-small); + padding-bottom: var(--spacing-small); + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + background-color: var(--fragment-background); +} + +table.memberdecls .memTemplItemLeft, +table.memberdecls .memTemplItemRight { + padding-top: 2px; +} + +table.memberdecls .memTemplParams { + border-bottom: 0; + border-left: 1px solid var(--separator-color); + border-right: 1px solid var(--separator-color); + border-radius: var(--border-radius-small) var(--border-radius-small) 0 0; + padding-bottom: 0; +} + +table.memberdecls .memTemplItemLeft { + border-radius: 0 0 0 var(--border-radius-small); + border-left: 1px solid var(--separator-color); + border-top: 0; +} + +table.memberdecls .memTemplItemRight { + border-radius: 0 0 var(--border-radius-small) 0; + border-right: 1px solid var(--separator-color); + border-top: 0; +} + +table.memberdecls .memItemLeft { + border-radius: var(--border-radius-small) 0 0 var(--border-radius-small); + border-left: 1px solid var(--separator-color); + padding-left: var(--spacing-medium); + padding-right: 0; +} + +table.memberdecls .memItemRight { + border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; + border-right: 1px solid var(--separator-color); + padding-right: var(--spacing-medium); + padding-left: 0; + +} + +table.memberdecls .mdescLeft, table.memberdecls .mdescRight { + background: none; + color: var(--page-foreground-color); + padding: var(--spacing-small) 0; +} + +table.memberdecls .memSeparator { + background: var(--page-background-color); + height: var(--spacing-large); + border: 0; + transition: none; +} + +table.memberdecls .groupheader { + margin-bottom: var(--spacing-large); +} + +table.memberdecls .inherit_header td { + padding: 0 0 var(--spacing-medium) 0; + text-indent: -12px; + line-height: 1.5em; + color: var(--page-secondary-foreground-color); +} + +@media screen and (max-width: 767px) { + + table.memberdecls .memItemLeft, + table.memberdecls .memItemRight, + table.memberdecls .mdescLeft, + table.memberdecls .mdescRight, + table.memberdecls .memTemplItemLeft, + table.memberdecls .memTemplItemRight, + table.memberdecls .memTemplParams { + display: block; + text-align: left; + padding-left: var(--spacing-large); + margin: 0 calc(0px - var(--spacing-large)) 0 calc(0px - var(--spacing-large)); + border-right: none; + border-left: none; + border-radius: 0; + } + + table.memberdecls .memItemLeft, + table.memberdecls .mdescLeft, + table.memberdecls .memTemplItemLeft { + border-bottom: 0; + padding-bottom: 0; + } + + table.memberdecls .memTemplItemLeft { + padding-top: 0; + } + + table.memberdecls .mdescLeft { + margin-top: calc(0px - var(--page-font-size)); + } + + table.memberdecls .memItemRight, + table.memberdecls .mdescRight, + table.memberdecls .memTemplItemRight { + border-top: 0; + padding-top: 0; + padding-right: var(--spacing-large); + overflow-x: auto; + } + + table.memberdecls tr[class^='memitem']:not(.inherit) { + display: block; + width: calc(100vw - 2 * var(--spacing-large)); + } + + table.memberdecls .mdescRight { + color: var(--page-foreground-color); + } + + table.memberdecls tr.inherit { + visibility: hidden; + } + + table.memberdecls tr[style="display: table-row;"] { + display: block !important; + visibility: visible; + width: calc(100vw - 2 * var(--spacing-large)); + animation: fade .5s; + } + + @keyframes fade { + 0% { + opacity: 0; + max-height: 0; + } + + 100% { + opacity: 1; + max-height: 200px; + } + } +} + + +/* + Horizontal Rule + */ + +hr { + margin-top: var(--spacing-large); + margin-bottom: var(--spacing-large); + height: 1px; + background-color: var(--separator-color); + border: 0; +} + +.contents hr { + box-shadow: 100px 0 0 var(--separator-color), + -100px 0 0 var(--separator-color), + 500px 0 0 var(--separator-color), + -500px 0 0 var(--separator-color), + 1500px 0 0 var(--separator-color), + -1500px 0 0 var(--separator-color), + 2000px 0 0 var(--separator-color), + -2000px 0 0 var(--separator-color); +} + +.contents img, .contents .center, .contents center, .contents div.image object { + max-width: 100%; + overflow: auto; +} + +@media screen and (max-width: 767px) { + .contents .dyncontent > .center, .contents > center { + margin-left: calc(0px - var(--spacing-large)); + margin-right: calc(0px - var(--spacing-large)); + max-width: calc(100% + 2 * var(--spacing-large)); + } +} + +/* + Directories + */ +div.directory { + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + width: auto; +} + +table.directory { + font-family: var(--font-family); + font-size: var(--page-font-size); + font-weight: normal; + width: 100%; +} + +table.directory td.entry { + padding: var(--spacing-small); +} + +table.directory td.desc { + min-width: 250px; +} + +table.directory tr.even { + background-color: var(--odd-color); +} + +.icona { + width: auto; + height: auto; + margin: 0 var(--spacing-small); +} + +.icon { + background: var(--primary-color); + width: 18px; + height: 18px; + line-height: 18px; +} + +.iconfopen, .icondoc, .iconfclosed { + background-position: center; + margin-bottom: 0; +} + +.icondoc { + filter: saturate(0.2); +} + +@media screen and (max-width: 767px) { + div.directory { + margin-left: calc(0px - var(--spacing-large)); + margin-right: calc(0px - var(--spacing-large)); + } +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) .iconfopen, html:not(.light-mode) .iconfclosed { + filter: hue-rotate(180deg) invert(); + } +} + +html.dark-mode .iconfopen, html.dark-mode .iconfclosed { + filter: hue-rotate(180deg) invert(); +} + +/* + Class list + */ + +.classindex dl.odd { + background: var(--odd-color); + border-radius: var(--border-radius-small); +} + +/* + Class Index Doxygen 1.8 +*/ + +table.classindex { + margin-left: 0; + margin-right: 0; + width: 100%; +} + +table.classindex table div.ah { + background-image: none; + background-color: initial; + border-color: var(--separator-color); + color: var(--page-foreground-color); + box-shadow: var(--box-shadow); + border-radius: var(--border-radius-large); + padding: var(--spacing-small); +} + +div.qindex { + background-color: var(--odd-color); + border-radius: var(--border-radius-small); + border: 1px solid var(--separator-color); + padding: var(--spacing-small) 0; +} + +/* + Footer and nav-path + */ + +#nav-path { + width: 100%; +} + +#nav-path ul { + background-image: none; + background: var(--page-background-color); + border: none; + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + border-bottom: 0; + box-shadow: 0 0.75px 0 var(--separator-color); + font-size: var(--navigation-font-size); +} + +img.footer { + width: 60px; +} + +.navpath li.footer { + color: var(--page-secondary-foreground-color); +} + +address.footer { + color: var(--page-secondary-foreground-color); + margin-bottom: var(--spacing-large); +} + +#nav-path li.navelem { + background-image: none; + display: flex; + align-items: center; +} + +.navpath li.navelem a { + text-shadow: none; + display: inline-block; + color: var(--primary-color) !important; +} + +.navpath li.navelem b { + color: var(--primary-dark-color); + font-weight: 500; +} + +li.navelem { + padding: 0; + margin-left: -8px; +} + +li.navelem:first-child { + margin-left: var(--spacing-large); +} + +li.navelem:first-child:before { + display: none; +} + +#nav-path li.navelem:after { + content: ''; + border: 5px solid var(--page-background-color); + border-bottom-color: transparent; + border-right-color: transparent; + border-top-color: transparent; + transform: translateY(-1px) scaleY(4.2); + z-index: 10; + margin-left: 6px; +} + +#nav-path li.navelem:before { + content: ''; + border: 5px solid var(--separator-color); + border-bottom-color: transparent; + border-right-color: transparent; + border-top-color: transparent; + transform: translateY(-1px) scaleY(3.2); + margin-right: var(--spacing-small); +} + +.navpath li.navelem a:hover { + color: var(--primary-color); +} + +/* + Scrollbars for Webkit +*/ + +#nav-tree::-webkit-scrollbar, +div.fragment::-webkit-scrollbar, +pre.fragment::-webkit-scrollbar, +div.memproto::-webkit-scrollbar, +.contents center::-webkit-scrollbar, +.contents .center::-webkit-scrollbar, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname)::-webkit-scrollbar { + width: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); + height: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); +} + +#nav-tree::-webkit-scrollbar-thumb, +div.fragment::-webkit-scrollbar-thumb, +pre.fragment::-webkit-scrollbar-thumb, +div.memproto::-webkit-scrollbar-thumb, +.contents center::-webkit-scrollbar-thumb, +.contents .center::-webkit-scrollbar-thumb, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname)::-webkit-scrollbar-thumb { + background-color: transparent; + border: var(--webkit-scrollbar-padding) solid transparent; + border-radius: calc(var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); + background-clip: padding-box; +} + +#nav-tree:hover::-webkit-scrollbar-thumb, +div.fragment:hover::-webkit-scrollbar-thumb, +pre.fragment:hover::-webkit-scrollbar-thumb, +div.memproto:hover::-webkit-scrollbar-thumb, +.contents center:hover::-webkit-scrollbar-thumb, +.contents .center:hover::-webkit-scrollbar-thumb, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname):hover::-webkit-scrollbar-thumb { + background-color: var(--webkit-scrollbar-color); +} + +#nav-tree::-webkit-scrollbar-track, +div.fragment::-webkit-scrollbar-track, +pre.fragment::-webkit-scrollbar-track, +div.memproto::-webkit-scrollbar-track, +.contents center::-webkit-scrollbar-track, +.contents .center::-webkit-scrollbar-track, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname)::-webkit-scrollbar-track { + background: transparent; +} + +#nav-tree::-webkit-scrollbar-corner { + background-color: var(--side-nav-background); +} + +#nav-tree, +div.fragment, +pre.fragment, +div.memproto, +.contents center, +.contents .center, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) { + overflow-x: auto; + overflow-x: overlay; +} + +#nav-tree { + overflow-x: auto; + overflow-y: auto; + overflow-y: overlay; +} + +/* + Scrollbars for Firefox +*/ + +#nav-tree, +div.fragment, +pre.fragment, +div.memproto, +.contents center, +.contents .center, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) { + scrollbar-width: thin; +} + +/* + Optional Dark mode toggle button +*/ + +doxygen-awesome-dark-mode-toggle { + display: inline-block; + margin: 0 0 0 var(--spacing-small); + padding: 0; + width: var(--searchbar-height); + height: var(--searchbar-height); + background: none; + border: none; + border-radius: var(--searchbar-height); + vertical-align: middle; + text-align: center; + line-height: var(--searchbar-height); + font-size: 22px; + display: flex; + align-items: center; + justify-content: center; + user-select: none; + cursor: pointer; +} + +doxygen-awesome-dark-mode-toggle > svg { + transition: transform .1s ease-in-out; +} + +doxygen-awesome-dark-mode-toggle:active > svg { + transform: scale(.5); +} + +doxygen-awesome-dark-mode-toggle:hover { + background-color: rgba(0,0,0,.03); +} + +html.dark-mode doxygen-awesome-dark-mode-toggle:hover { + background-color: rgba(0,0,0,.18); +} + +/* + Optional fragment copy button +*/ +.doxygen-awesome-fragment-wrapper { + position: relative; +} + +doxygen-awesome-fragment-copy-button { + opacity: 0; + background: var(--fragment-background); + width: 28px; + height: 28px; + position: absolute; + right: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); + top: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); + border: 1px solid var(--fragment-foreground); + cursor: pointer; + border-radius: var(--border-radius-small); + display: flex; + justify-content: center; + align-items: center; +} + +.doxygen-awesome-fragment-wrapper:hover doxygen-awesome-fragment-copy-button, doxygen-awesome-fragment-copy-button.success { + opacity: .28; +} + +doxygen-awesome-fragment-copy-button:hover, doxygen-awesome-fragment-copy-button.success { + opacity: 1 !important; +} + +doxygen-awesome-fragment-copy-button:active:not([class~=success]) svg { + transform: scale(.91); +} + +doxygen-awesome-fragment-copy-button svg { + fill: var(--fragment-foreground); + width: 18px; + height: 18px; +} + +doxygen-awesome-fragment-copy-button.success svg { + fill: rgb(14, 168, 14); +} + +doxygen-awesome-fragment-copy-button.success { + border-color: rgb(14, 168, 14); +} + +@media screen and (max-width: 767px) { + .textblock > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .textblock li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .memdoc li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .memdoc > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + dl dd > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button { + right: 0; + } +} + +/* + Optional paragraph link button +*/ + +a.anchorlink { + font-size: 90%; + margin-left: var(--spacing-small); + color: var(--page-foreground-color) !important; + text-decoration: none; + opacity: .15; + display: none; + transition: opacity .1s ease-in-out, color .1s ease-in-out; +} + +a.anchorlink svg { + fill: var(--page-foreground-color); +} + +h3 a.anchorlink svg, h4 a.anchorlink svg { + margin-bottom: -3px; + margin-top: -4px; +} + +a.anchorlink:hover { + opacity: .45; +} + +h2:hover a.anchorlink, h1:hover a.anchorlink, h3:hover a.anchorlink, h4:hover a.anchorlink { + display: inline-block; +} diff --git a/doc/doxygen/doxygen-awesome-css/logo.drawio.svg b/doc/doxygen/doxygen-awesome-css/logo.drawio.svg new file mode 100644 index 000000000..a506ee080 --- /dev/null +++ b/doc/doxygen/doxygen-awesome-css/logo.drawio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/doxygen/parameters/GUIParamDocumenter.cpp b/doc/doxygen/parameters/GUIParamDocumenter.cpp new file mode 100644 index 000000000..38577f385 --- /dev/null +++ b/doc/doxygen/parameters/GUIParamDocumenter.cpp @@ -0,0 +1,218 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: $ +// $Authors: Marc Sturm $ +// -------------------------------------------------------------------------- + +// Writes the parameter tables (output/OpenMS_.parameters) of the classes of the GUI +// library that are DefaultParamHandlers. The class documentation includes them with +// @htmlinclude. The parameters of the OpenMS core classes are documented by +// DefaultParamHandlerDocumenter of OpenMS. + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace OpenMS; + +//********************************************************************************** +//Helper method - use this method to generate the actual parameter documentation +//********************************************************************************** +void writeParameters(const std::string& class_name, const Param& param, bool table_only = false) +{ + const std::string filename = std::string("output/OpenMS_") + class_name + ".parameters"; + ofstream f(filename.c_str()); + + if (!f) + { + std::cerr << "Cannot open file '" << filename << "'. Check for invalid characters in filename and permissions.\n"; + exit(1); + } + + if (!table_only) + { + f << "Parameters of this class are:

\n"; + } + f << R"()" << endl; + f << "" << endl; + std::string type, description, restrictions; + for (Param::ParamIterator it = param.begin(); it != param.end(); ++it) + { + restrictions = ""; + if (it->value.valueType() == ParamValue::INT_VALUE || it->value.valueType() == ParamValue::INT_LIST) + { + type = "int"; + if (it->value.valueType() == ParamValue::INT_LIST) + { + type += " list"; + } + + //restrictions + bool first = true; + if (it->min_int != -(numeric_limits::max)()) + { + restrictions += "min: " + StringUtils::toStr(it->min_int); + first = false; + } + if (it->max_int != (numeric_limits::max)()) + { + if (!first) + { + restrictions += ' '; + } + restrictions += "max: " + StringUtils::toStr(it->max_int); + } + } + else if (it->value.valueType() == ParamValue::DOUBLE_VALUE || it->value.valueType() == ParamValue::DOUBLE_LIST) + { + type = "float"; + if (it->value.valueType() == ParamValue::DOUBLE_LIST) + type += " list"; + + //restrictions + bool first = true; + if (it->min_float != -(numeric_limits::max)()) + { + restrictions += "min: " + StringUtils::toStr(it->min_float); + first = false; + } + if (it->max_float != (numeric_limits::max)()) + { + if (!first) + restrictions += ' '; + restrictions += "max: " + StringUtils::toStr(it->max_float); + } + } + else if (it->value.valueType() == ParamValue::STRING_VALUE || it->value.valueType() == ParamValue::STRING_LIST) + { + type = "string"; + if (it->value.valueType() == ParamValue::STRING_LIST) + type += " list"; + + //restrictions + if (!it->valid_strings.empty()) + { + std::string valid_strings = StringUtils::concatenate(it->valid_strings, ", "); + restrictions += valid_strings; + } + } + if (restrictions == "") + { + restrictions = " "; + } + //replace #, @ and newline in description + description = param.getDescription(it.getName()); + StringUtils::substitute(description, "@", "XXnot_containedXX"); + StringUtils::substitute(description, "XXnot_containedXX", "@@"); + StringUtils::substitute(description, "#", "XXnot_containedXX"); + StringUtils::substitute(description, "XXnot_containedXX", "@#"); + StringUtils::substitute(description, "\n", "
"); + + //create tooltips for sections if they are documented + std::string name = it.getName(); + vector parts; + StringUtils::split(name, ':', parts); + std::string prefix; + for (Size i = 0; i + 1 < parts.size(); ++i) + { + if (i == 0) + { + prefix = parts[i]; + } + else + { + prefix = prefix + ":" + parts[i]; + } + std::string docu = param.getSectionDescription(prefix); + if (docu != "") + { + parts[i] = std::string("" + parts[i] + ""; + } + } + if (parts.size() != 0) + { + name = StringUtils::concatenate(parts, ":"); + } + + //replace # and @ in values + std::string value = it->value.toString(true); + StringUtils::substitute(value, "@", "XXnot_containedXX"); + StringUtils::substitute(value, "XXnot_containedXX", "@@"); + StringUtils::substitute(value, "#", "XXnot_containedXX"); + StringUtils::substitute(value, "XXnot_containedXX", "@#"); + + //make the advanced parameters cursive, the normal ones bold + std::string style = "b"; + if (it->tags.count("advanced") == 1) + style = "i"; + + //final output + f << "
\n" + << " \n" + << " \n" + << " \n" + << "\n"; + } + f << "
NameTypeDefaultRestrictionsDescription
<" << style << ">" << name << "" << type << "" << value << "" << restrictions << "" << description << "
" << "\n"; + if (!table_only) + { + f << "
" << "\n" + << "Note:" << "\n" + << "
    " << "\n" + << "
  • If a section name is documented, the documentation is displayed as tooltip." << "\n" + << "
  • Advanced parameter names are italic." << "\n" + << "
" << "\n"; + } + f.close(); +} + +//********************************************************************************** +//Helper macros that can be used for easy classes +//********************************************************************************** + +// For classes that have a default-constructor, simply use this macro with the +// class name +#define DOCME(class_name) \ + writeParameters("" # class_name, class_name().getDefaults()); + +// For class templates and classes without default constructor use this macro +// with (1.) the class name and (2.) a class instance. +#define DOCME2(class_template_name, instantiation) \ + writeParameters("" # class_template_name, (instantiation).getDefaults()); + +//********************************************************************************** +//Main method - add your class here +//********************************************************************************** +int main(int argc, char** argv) +{ + // the widgets require a QApplication (use QT_QPA_PLATFORM=offscreen without a display) + QApplication app(argc, argv); + + DOCME(TOPPASBase); + DOCME2(TOPPViewBase, TOPPViewBase(TOPPViewBase::TOOL_SCAN::SKIP_SCAN)); + DOCME2(PlotCanvas, Plot1DCanvas(Param())); // PlotCanvas is abstract, its parameters are those of the subclasses + DOCME2(Plot1DCanvas, Plot1DCanvas(Param())); + DOCME2(Plot2DCanvas, Plot2DCanvas(Param())); + DOCME2(Plot3DCanvas, Plot3DCanvas(Param())); + DOCME2(SpectraIDViewTab, SpectraIDViewTab(Param())); + + return 0; +} diff --git a/doc/doxygen/parameters/GUIToolDocumenter.cpp b/doc/doxygen/parameters/GUIToolDocumenter.cpp new file mode 100644 index 000000000..b1133eac2 --- /dev/null +++ b/doc/doxygen/parameters/GUIToolDocumenter.cpp @@ -0,0 +1,364 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: Chris Bielow $ +// $Authors: Marc Sturm, Mathias Walzer, Chris Bielow $ +// -------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +using namespace std; +using namespace OpenMS; +using namespace Internal; + +namespace +{ + /// Set an environment variable, overwriting any existing value. + void setEnvVar(const char* name, const char* value) + { +#ifdef _WIN32 + _putenv_s(name, value); +#else + setenv(name, value, 1); +#endif + } +} // namespace + +void convertINI2HTML(const Param& p, ostream& os) +{ + os << "
\n"; + os << "
\n"; + os << "Legend:
\n"; + os << "
required parameter
\n"; + os << "
advanced parameter
\n"; + os << "
\n"; + os << "
\n"; + os << "

This section lists all parameters supported by the tool. Parameters are organized into hierarchical subsections that group related settings together. Subsections may contain further subsections or individual parameters.

\n"; + os << "

Each parameter entry contains the following information:

\n"; + os << "
    \n"; + os << "
  • Name The identifier used in configuration files and on the command line.
  • \n"; + os << "
  • Default value The value used if the parameter is not explicitly specified.
  • \n"; + os << "
  • Description A short explanation describing the purpose and behavior of the parameter.
  • \n"; + os << "
  • Tags Additional metadata associated with the parameter.
  • \n"; + os << "
  • Restrictions Allowed value ranges for numeric parameters or valid options for string parameters.
  • \n"; + os << "
\n"; + os << "

Parameter tags provide additional information about how a parameter is used. "; + os << "Some tags indicate whether a parameter is required or intended for advanced configuration, "; + os << "while others may be used internally by OpenMS or workflow tools.

\n"; + os << "

Parameters highlighted as required must be specified for the tool to run successfully. Parameters marked as advanced allow fine-tuning of algorithm behavior and are typically not needed for standard workflows.

\n"; + os << "
\n"; + + Param::ParamIterator it = p.begin(); + std::string indentation = " "; + + while (it != p.end()) + { + string key = it.getName(); + + //write opened/closed nodes + const std::vector& trace = it.getTrace(); + for (std::vector::const_iterator it2 = trace.begin(); it2 != trace.end(); ++it2) + { + if (it2->opened) //opened node + { + std::string d = it2->description; + StringUtils::substitute(d, "\n", "
"); + os << indentation + << R"(
)" + // TODO replace/remove weird "(TOPPAS) instance 1" nodes that only confuse people. + << (StringUtils::fillLeft(std::string(), '+', (UInt) indentation.size() / 2) + it2->name) + << "" + << (d) + << "
" + << "\n"; + indentation += " "; + } + else //closed node + { + indentation.resize(indentation.size() - 2); + //os << indentation << "
" << "\n"; + } + } + + //write item + std::string s_attr; + std::string s_req; + if (it->tags.find("advanced") != it->tags.end()) + s_attr += " item_advanced"; // optionally add advanced class + if (it->tags.find("required") != it->tags.end()) + s_req += " item_required"; // optionally add required class + ParamValue::ValueType value_type = it->value.valueType(); + //write opening tag + os << indentation + << "
" + << (it->name) + << "" + << it->value.toString() + << "" + << "\n"; + + //replace all critical characters in description + std::string d = it->description; + StringUtils::substitute(d, "\n", "
"); + os << "" << (d) << ""; + + //tags + std::string list; + for (auto tag_it = it->tags.begin(); tag_it != it->tags.end(); ++tag_it) + { + if (*tag_it == "advanced") + continue; // do not list "advanced" or "required" (this is done by color coding) + if (*tag_it == "required") + continue; + if (!list.empty()) + list += ", "; + list += *tag_it; + } + os << "" << (list) << ""; + + //restrictions + std::string restrictions; + switch (value_type) + { + case ParamValue::INT_VALUE: + case ParamValue::INT_LIST: + { + // TODO think about doing the same infinity replacement + // for default values. A single ":" looks weird. + bool min_set = (it->min_int != -numeric_limits::max()); + bool max_set = (it->max_int != numeric_limits::max()); + if (max_set || min_set) + { + if (min_set) + restrictions += StringUtils::toStr(it->min_int); + else + restrictions += "-∞"; // infinity symbol + restrictions += ':'; + if (max_set) + restrictions += StringUtils::toStr(it->max_int); + else + restrictions += "∞"; + } + } + break; + + case ParamValue::DOUBLE_VALUE: + case ParamValue::DOUBLE_LIST: + { + bool min_set = (it->min_float != -numeric_limits::max()); + bool max_set = (it->max_float != numeric_limits::max()); + if (max_set || min_set) + { + if (min_set) + restrictions += StringUtils::toStr(it->min_float); + else + restrictions += "-∞"; // infinity symbol + restrictions += ':'; + if (max_set) + restrictions += StringUtils::toStr(it->max_float); + else + restrictions += "∞"; + } + } + break; + + case ParamValue::STRING_VALUE: + case ParamValue::STRING_LIST: + if (!it->valid_strings.empty()) + { + // make sure browsers can word wrap with additional whitespace + // TODO: If param name is *modification* just add a link to + // a page with all modifications otherwise you get a HUGE list. + // Also think about a different separator, in case the restrictions have commas. + restrictions = StringUtils::concatenate(it->valid_strings, ", "); + } + else if (value_type == ParamValue::STRING_VALUE) + { + // Issue #8475: Flag parameters are written as type="bool" in INI, which loads + // as STRING_VALUE with no valid_strings. Detect these by checking if value is boolean. + std::string val = it->value.toString(); + if (val == "true" || val == "false") + { + restrictions = "(flag)"; + } + } + break; + + default: + break; + } + if (restrictions.empty()) + restrictions = " "; // create content, such that the cell gets an underline + + os << "" << restrictions << ""; + + os << "
"; // end div item + + ++it; + } + + os << "
\n"; // end global div +} + +/// Writes output/.cli (the --help output of each tool) and, for tools that support +/// -write_ini, output/.html (their INI parameters). +bool generate(const ToolListType& tools, const std::string& prefix, const std::string& binary_directory) +{ + // Add an environment variable (used by each TOPP tool to determine width of help text (see TOPPBase)) + setEnvVar("COLUMNS", "110"); + // Add Global environment variable to suppress stty errors + setEnvVar("TERM", "dumb"); + setEnvVar("STTY", "/bin/true"); + + bool errors_occured = false; + for (ToolListType::const_iterator it = tools.begin(); it != tools.end(); ++it) + { + std::string command = binary_directory + it->first; +#if defined(__APPLE__) + if (it->first == "TOPPView" || it->first == "TOPPAS") + { + command = binary_directory + it->first + ".app/Contents/MacOS/" + it->first; + } +#endif +#ifdef OPENMS_WINDOWSPLATFORM + command += ".exe"; // otherwise File::exists() will fail +#endif + + ofstream f((std::string("output/") + prefix + it->first + ".cli").c_str()); + if (!File::exists(command)) + { + stringstream ss; + ss << "Errors occurred while generating the command line documentation for " << it->first << "!" << endl; + ss << "Tool could not be found at '" << command << "'\n " << command << endl; + f << ss.str(); + cerr << ss.str(); + errors_occured = true; + f.close(); + continue; + } + else + { + ExternalProcess ep([&](const std::string& s) { f << s; }, + [&](const std::string& s) { f << s; }); + std::string error_msg; + if (ep.run(command, std::vector{"--help"}, "", false, error_msg, ExternalProcess::IO_MODE::READ_WRITE) + != ExternalProcess::RETURNSTATE::SUCCESS) + { // error while generation cli docu + stringstream ss; + ss << "Errors occurred while generating the command line documentation for " << it->first << "!" << endl; + ss << "Output was: \n"; + ep.setCallbacks([&](const std::string& s) { ss << s; }, [&](const std::string& s) { ss << s; }); + ep.run(command, std::vector{"--help"}, "", false, error_msg, ExternalProcess::IO_MODE::READ_WRITE); + ss << "\nCommand line was: \n " << command << endl; + f << ss.str(); + cerr << ss.str(); + errors_occured = true; + f.close(); + continue; + } + } + f.close(); + + ////// + // get the INI file and convert it into HTML + ////// + if (it->first != "TOPPView" && // do not support -write_ini + it->first != "TOPPAS") + { + std::string tmp_file = SystemSettings::getTempDirectory() + "/" + File::getUniqueName() + "_" + it->first + ".ini"; + const std::vector ini_command_args = {"-write_ini", tmp_file}; + + // the output of the tool is only reported if writing the INI file fails + std::string ini_output; + ExternalProcess ep([&](const std::string& s) { ini_output += s; }, [&](const std::string& s) { ini_output += s; }); + std::string error_msg; + if (ep.run(command, ini_command_args, "", false, error_msg, + ExternalProcess::IO_MODE::READ_WRITE) + != ExternalProcess::RETURNSTATE::SUCCESS + || ! File::exists(tmp_file)) + { // error while generation cli docu + std::cerr << "Errors occurred while writing ini file for " << it->first << "!" << std::endl; + std::cerr << "Output was: \n" << ini_output << error_msg << std::endl; + std::string args_str; + for (const auto& a : ini_command_args) args_str += " " + a; + std::cerr << "Command line was: \n " << command << args_str << std::endl; + if (File::exists(tmp_file)) File::remove(tmp_file); + errors_occured = true; + continue; + } + + // load content of written ini file + Param p; + ParamXMLFile pf; + try + { + pf.load(tmp_file, p); + } + catch (const Exception::BaseException& e) + { + std::cerr << "Errors occurred while reading the ini file of " << it->first << ": " << e.what() << std::endl; + File::remove(tmp_file); + errors_occured = true; + continue; + } + File::remove(tmp_file); + ofstream f_html((std::string("output/") + prefix + it->first + ".html").c_str()); + convertINI2HTML(p, f_html); + f_html.close(); + } + } + return errors_occured; +} + +int main(int argc, char** argv) +{ + if (argc != 2) + { + cerr << "Please specify the path where the GUI tools are located." << endl; + return EXIT_FAILURE; + } + + std::string binary_directory = StringUtils::ensureLastChar(argv[1], '/'); + + if (!File::exists(binary_directory)) + { + cerr << "The given binary directory does not exist. Aborting." << endl; + return EXIT_FAILURE; + } + + // The tools of this repository whose pages include generated documentation (see the @page + // blocks in src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS). INIFileEditor has none. + // ExecutePipeline and ImageCreator are TOPP tools (OpenMS-GUI.tsv); TOPPView and TOPPAS do + // not support -write_ini, generate() skips the INI part for them. + ToolListType gui_tools; + for (const std::string& name : {"ExecutePipeline", "ImageCreator", "TOPPAS", "TOPPView"}) + { + gui_tools[name] = Internal::ToolDescription(); + } + + if (generate(gui_tools, "TOPP_", binary_directory)) + { + cerr << "Errors occurred while generating the command line documentation for some of the GUI tools." << endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/doc/doxygen/parameters/executables.cmake b/doc/doxygen/parameters/executables.cmake new file mode 100644 index 000000000..2eb5d975d --- /dev/null +++ b/doc/doxygen/parameters/executables.cmake @@ -0,0 +1,18 @@ +### the directory name +set(directory doc/doxygen/parameters) + +### the documenters of the GUI layer (see doc/CMakeLists.txt) +set(sources_list +GUIParamDocumenter +GUIToolDocumenter +) + +### pass source file list to the upper instance +set(OpenMS_doc_executables ${sources_list}) + +### source group definition +set(sources) +foreach(i ${sources_list}) + list(APPEND sources ${i}.cpp) +endforeach() +source_group("Source Files\\doc\\doxygen\\parameters" FILES ${sources}) diff --git a/doc/doxygen/public/GUI.doxygen b/doc/doxygen/public/GUI.doxygen new file mode 100644 index 000000000..81dabf69c --- /dev/null +++ b/doc/doxygen/public/GUI.doxygen @@ -0,0 +1,86 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: $ +// $Authors: $ +// -------------------------------------------------------------------------- + +/** + @mainpage %OpenMS GUI documentation + + The graphical tools of %OpenMS and the library they are built on, OpenMS_GUI. + They are developed in the repository + OpenMS-TOPP-GUI and are built and + installed on top of an %OpenMS installation of the same version (the %OpenMS library, the + TOPP tools and the shared data come from there). + + All aspects of using %OpenMS (installation, workflows, user tutorials, including those for + TOPPView) are described on the + %OpenMS ReadTheDocs website. + The documentation of the %OpenMS library and of all other TOPP tools is the + %OpenMS API reference + (nightly). + +

Graphical tools

+
    +
  • @subpage TOPP_TOPPView - visualization and analysis of MS data +
  • @subpage TOPP_TOPPAS - design and execution of TOPP workflows +
  • @subpage TOPP_INIFileEditor - editor for the parameter (INI) files of TOPP tools +
+ +

TOPP tools that need the GUI library

+
    +
  • @subpage TOPP_ExecutePipeline - runs TOPPAS workflows on the command line +
  • @subpage TOPP_ImageCreator - writes an LC-MS map as an image +
+ +

Tutorials

+
    +
  • @subpage TOPPAS_tutorial +
  • @subpage GUI_developer_notes +
+ +

The GUI library

+ The classes of the GUI library are grouped in the module @ref Visual (see also the class + list). The code examples in doc/code_examples/Tutorial_GUI_*.cpp show how to use + some of the widgets in an application of your own. +*/ + +/** + @page GUI_developer_notes Developing the GUI layer + + @section GUI_dev_build Building + + OpenMS-TOPP-GUI is built against an installed %OpenMS (or the %OpenMS SDK) of the same version + and Qt 6: + @code + cmake -S OpenMS-TOPP-GUI -B build -DCMAKE_PREFIX_PATH=";" + cmake --build build + ctest --test-dir build + @endcode + The repository's README.md lists all options. + + @section GUI_dev_widgets Using the widgets in your own application + + Link the target OpenMS::OpenMS_GUI of the CMake package OpenMSGUI + (or ask for find_package(OpenMS COMPONENTS GUI), which finds it when the GUI layer + is installed into the %OpenMS prefix). The examples: +
    +
  • Tutorial_GUI_ListEditor.cpp: @ref OpenMS::ListEditor +
  • Tutorial_GUI_ParamEditor.cpp: @ref OpenMS::ParamEditor, editing an INI file +
  • Tutorial_GUI_Plot1D.cpp: @ref OpenMS::Plot1DWidget, showing a spectrum +
+ They are built by the target Tutorials_build (with -DENABLE_TUTORIALS=ON). + + @section GUI_dev_tests How to add a new GUI test for a class + + Suppose you want to create a GUI class for the class called MyClass. + To add a new GUI test: +
    +
  1. Create the MyClass_test.cpp in src/tests/class_tests/openms_gui/source.
  2. +
  3. Add it to src/tests/class_tests/openms_gui/CMakeLists.txt in the GUI section.
  4. +
  5. Have a look at existing GUI tests, as they use the QT TestLib framework and not the %OpenMS macros.
  6. +
+ Without a display, run the tests with QT_QPA_PLATFORM=offscreen. +*/ diff --git a/doc/doxygen/public/Modules.doxygen b/doc/doxygen/public/Modules.doxygen new file mode 100644 index 000000000..e415b31c4 --- /dev/null +++ b/doc/doxygen/public/Modules.doxygen @@ -0,0 +1,70 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: $ +// $Authors: Marc Sturm $ +// -------------------------------------------------------------------------- + +// The module groups of the GUI library (the groups of the OpenMS core are +// defined in the OpenMS repository, doc/doxygen/public/Modules.doxygen). + + +// ----------------------------- Visual ------------------------------- + +/** + @defgroup Visual Visual + + @brief Visualization classes +*/ + +/** + @defgroup PlotWidgets Spectrum visualization widgets + + @brief Spectrum visualization widgets + + The spectrum widgets allow spectrum data to be visualized + in different manners. There are predefined widgets for + 1D, 2D and 3D visualization. For each type of view, at least + two new classes must be defined: + + - a "canvas" class derived from OpenMS::PlotCanvas, + used for painting the data. + - a "widget" class derived from OpenMS::PlotWidget, + which holds the instance of the canvas class, and some + additional widgets, such as OpenMS::AxisWidget. This + class is also responsible for holding the data and for + providing it to the canvas class instance. + + @ingroup Visual +*/ + +/** + @defgroup TOPPView_elements TOPPView + + @brief GUI elements for TOPPView + + Some GUI classes were designed for TOPPView only and are probably not usable in any other application. + These classes are listed in this module. + + @ingroup Visual +*/ + +/** + @defgroup TOPPAS_elements TOPPAS + + @brief GUI elements for TOPPAS + + Some GUI classes were designed for TOPPAS only and are probably not usable in any other application. + These classes are listed in this module. + + @ingroup Visual +*/ + +/** + @defgroup Dialogs Dialogs + + @brief Dialogs for user interaction. + + @ingroup Visual +*/ diff --git a/share/OpenMS/DESKTOP/icons/128x128/INIFileEditor.png b/share/OpenMS/DESKTOP/icons/128x128/INIFileEditor.png new file mode 100644 index 000000000..458745aa5 Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/128x128/INIFileEditor.png differ diff --git a/share/OpenMS/DESKTOP/icons/128x128/TOPPAS.png b/share/OpenMS/DESKTOP/icons/128x128/TOPPAS.png new file mode 100644 index 000000000..30a3542ac Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/128x128/TOPPAS.png differ diff --git a/share/OpenMS/DESKTOP/icons/128x128/TOPPView.png b/share/OpenMS/DESKTOP/icons/128x128/TOPPView.png new file mode 100644 index 000000000..88c6897b2 Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/128x128/TOPPView.png differ diff --git a/share/OpenMS/DESKTOP/icons/16x16/INIFileEditor.png b/share/OpenMS/DESKTOP/icons/16x16/INIFileEditor.png new file mode 100644 index 000000000..059e4582e Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/16x16/INIFileEditor.png differ diff --git a/share/OpenMS/DESKTOP/icons/16x16/TOPPAS.png b/share/OpenMS/DESKTOP/icons/16x16/TOPPAS.png new file mode 100644 index 000000000..1bc484b71 Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/16x16/TOPPAS.png differ diff --git a/share/OpenMS/DESKTOP/icons/16x16/TOPPView.png b/share/OpenMS/DESKTOP/icons/16x16/TOPPView.png new file mode 100644 index 000000000..d1dc13371 Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/16x16/TOPPView.png differ diff --git a/share/OpenMS/DESKTOP/icons/256x256/INIFileEditor.png b/share/OpenMS/DESKTOP/icons/256x256/INIFileEditor.png new file mode 100644 index 000000000..0a490e2a5 Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/256x256/INIFileEditor.png differ diff --git a/share/OpenMS/DESKTOP/icons/256x256/TOPPAS.png b/share/OpenMS/DESKTOP/icons/256x256/TOPPAS.png new file mode 100644 index 000000000..df0e865bc Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/256x256/TOPPAS.png differ diff --git a/share/OpenMS/DESKTOP/icons/256x256/TOPPView.png b/share/OpenMS/DESKTOP/icons/256x256/TOPPView.png new file mode 100644 index 000000000..a02a9bfee Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/256x256/TOPPView.png differ diff --git a/share/OpenMS/DESKTOP/icons/32x32/INIFileEditor.png b/share/OpenMS/DESKTOP/icons/32x32/INIFileEditor.png new file mode 100644 index 000000000..82d4750fc Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/32x32/INIFileEditor.png differ diff --git a/share/OpenMS/DESKTOP/icons/32x32/TOPPAS.png b/share/OpenMS/DESKTOP/icons/32x32/TOPPAS.png new file mode 100644 index 000000000..c5acef97b Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/32x32/TOPPAS.png differ diff --git a/share/OpenMS/DESKTOP/icons/32x32/TOPPView.png b/share/OpenMS/DESKTOP/icons/32x32/TOPPView.png new file mode 100644 index 000000000..e9fda65d8 Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/32x32/TOPPView.png differ diff --git a/share/OpenMS/DESKTOP/icons/48x48/INIFileEditor.png b/share/OpenMS/DESKTOP/icons/48x48/INIFileEditor.png new file mode 100644 index 000000000..6533f411a Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/48x48/INIFileEditor.png differ diff --git a/share/OpenMS/DESKTOP/icons/48x48/TOPPAS.png b/share/OpenMS/DESKTOP/icons/48x48/TOPPAS.png new file mode 100644 index 000000000..621593871 Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/48x48/TOPPAS.png differ diff --git a/share/OpenMS/DESKTOP/icons/48x48/TOPPView.png b/share/OpenMS/DESKTOP/icons/48x48/TOPPView.png new file mode 100644 index 000000000..ff4398563 Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/48x48/TOPPView.png differ diff --git a/share/OpenMS/DESKTOP/icons/64x64/INIFileEditor.png b/share/OpenMS/DESKTOP/icons/64x64/INIFileEditor.png new file mode 100644 index 000000000..d7d35868f Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/64x64/INIFileEditor.png differ diff --git a/share/OpenMS/DESKTOP/icons/64x64/TOPPAS.png b/share/OpenMS/DESKTOP/icons/64x64/TOPPAS.png new file mode 100644 index 000000000..fd36894c8 Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/64x64/TOPPAS.png differ diff --git a/share/OpenMS/DESKTOP/icons/64x64/TOPPView.png b/share/OpenMS/DESKTOP/icons/64x64/TOPPView.png new file mode 100644 index 000000000..dfd1de6f1 Binary files /dev/null and b/share/OpenMS/DESKTOP/icons/64x64/TOPPView.png differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 000000000..e4300c430 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Stephan Aiche, Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +project("OpenMS_TOPP_GUI_src") + +#------------------------------------------------------------------------------ +# The GUI library, the GUI applications and the GUI TOPP tools +#------------------------------------------------------------------------------ +# Declarations accumulate in the cache, so drop what an earlier configure of this build +# directory left behind before any tool is declared. +openms_reset_topp_tools() +add_subdirectory(openms_gui) + +#------------------------------------------------------------------------------ +# The tool registry of the GUI layer, OpenMS-GUI.tsv, generated from the +# openms_topp_tool() declarations of src/openms_gui. It is installed into +# share/OpenMS/TOOLS next to the registry of the OpenMS core (OpenMS.tsv); +# ToolHandler reads every *.tsv it finds there. That directory holds registry +# files and nothing else. +#------------------------------------------------------------------------------ +openms_write_tool_registry("${OPENMS_TOOL_REGISTRY_BUILD_FILE}") +install(FILES "${OPENMS_TOOL_REGISTRY_BUILD_FILE}" + DESTINATION ${INSTALL_SHARE_DIR}/TOOLS + COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT} + PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ +) + +#------------------------------------------------------------------------------ +# Desktop integration (Linux). The GUI layer installs no other shared data: the style +# sheet of the applications is compiled into libOpenMS_GUI (source/VISUAL/STYLE). +#------------------------------------------------------------------------------ +include(${OPENMS_HOST_DIRECTORY}/cmake/install_desktop.cmake) + +#------------------------------------------------------------------------------ +# Tests (class tests of the GUI library, tests of the GUI TOPP tools, TOPPAS +# pipeline tests, installed-consumer test) +#------------------------------------------------------------------------------ +if(ENABLE_CLASS_TESTING OR ENABLE_TOPP_TESTING OR ENABLE_PIPELINE_TESTING OR OPENMS_TEST_INSTALLED_CONSUMER) + add_subdirectory(tests) +endif() diff --git a/src/openms/source/TEST/VISUAL/parameters.ini b/src/openms/source/TEST/VISUAL/parameters.ini deleted file mode 100644 index 730dc2726..000000000 --- a/src/openms/source/TEST/VISUAL/parameters.ini +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/openms_gui/CMakeLists.txt b/src/openms_gui/CMakeLists.txt index e717aff98..dbd91ab2a 100644 --- a/src/openms_gui/CMakeLists.txt +++ b/src/openms_gui/CMakeLists.txt @@ -24,13 +24,13 @@ include (${PROJECT_SOURCE_DIR}/includes.cmake) #------------------------------------------------------------------------------ # add the library: the GUI layer of the installed package (export set # OpenMSGUITargets, install components library_gui and cmake_gui, headers in -# OpenMS_GUI_headers); it links the CLI layer, which links the core layer. +# OpenMS_GUI_headers); it links the CLI layer of the installed OpenMS core, +# which links the core layer. openms_add_library(TARGET_NAME OpenMS_GUI SOURCE_FILES ${OpenMSVisual_sources} HEADER_FILES ${OpenMSVisual_sources_h} BASE_DIRS ${PROJECT_SOURCE_DIR}/include ${PROJECT_BINARY_DIR}/include EXTERNAL_INCLUDES - PRIVATE_LINK_LIBRARIES Boost::boost LINK_LIBRARIES ${OpenMS_GUI_DEP_LIBRARIES} # Qt is a PUBLIC dependency of the GUI layer only: , ALLOWED_PUBLIC_INCLUDES "Q[A-Za-z0-9_]*" "Qt[A-Za-z]+/.*" @@ -95,51 +95,75 @@ foreach(i ${GUI_executables}) target_link_libraries(${i} OpenMS_GUI) endforeach(i) -# GUI TOPP tools also need direct OpenMS link for TOPPBase etc. -## these two are TOPP tools (TOPPBase-derived) with a Qt front end -foreach(i ExecutePipeline ImageCreator) - target_link_libraries(${i} OpenMS_CLI) -endforeach() - -# The TOPP tools that need the GUI library, declared the same way as the tools of -# src/topp/executables.cmake so they reach the tool registry and the CWL/INI generation -# exactly in a WITH_GUI build. One list drives both the declarations and the dependency -# below, so a tool cannot be built here without being registered. These are a subset of -# GUI_executables, which also holds the GUI applications that are not TOPP tools -# (TOPPView, TOPPAS, INIFileEditor). +# The TOPP tools that need the GUI library (TOPPBase-derived tools with a Qt front end). +# They are declared like the TOPP tools of the OpenMS core, so they reach the tool registry +# of the GUI layer (OpenMS-GUI.tsv, see src/CMakeLists.txt). One list drives both the +# declarations and the link below, so a tool cannot be built here without being registered. +# These are a subset of GUI_executables, which also holds the GUI applications that are not +# TOPP tools (TOPPView, TOPPAS, INIFileEditor). set(GUI_TOPP_TOOLS ExecutePipeline ImageCreator) foreach(_gui_topp_tool ${GUI_TOPP_TOOLS}) + target_link_libraries(${_gui_topp_tool} OpenMS::OpenMS_CLI) openms_topp_tool(${_gui_topp_tool} "Misc") - openms_link_mimalloc(${_gui_topp_tool}) # WITH_MIMALLOC on Windows x64, otherwise a no-op endforeach() -# Make the TOPP collection target depend on GUI TOPP tools -if(TARGET TOPP) - add_dependencies(TOPP ${GUI_TOPP_TOOLS}) -endif() - - ## install platform plugins, and a qt.conf in bin/ that points to them: ExecutePipeline creates - ## a QApplication, so it needs the platform plugin even without a window. +#------------------------------------------------------------------------------ +# Qt plugins and qt.conf. Where Qt is bundled (Windows, the macOS package), the plugins the +# GUI needs at run time go to ${INSTALL_PLUGIN_DIR} (component Dependencies, with the +# libraries they need in the runtime dependency set), and bin/qt.conf points there: +# ExecutePipeline creates a QApplication, so it needs the platform plugin even without a +# window. The app bundles have a qt.conf of their own (add_mac_bundle.cmake). +# platforms the platform integration (required) +# styles the native widget style +# imageformats JPEG and GIF for "Save image", SVG for the icons of the GUI resources +# iconengines SVG icons +if(WIN32 OR (APPLE AND "${PACKAGE_TYPE}" STREQUAL "pkg")) if(WIN32) - install(IMPORTED_RUNTIME_ARTIFACTS "Qt6::QWindowsIntegrationPlugin" - DESTINATION "${INSTALL_PLUGIN_DIR}/platforms" - RUNTIME_DEPENDENCY_SET OPENMS_DEPS - COMPONENT Dependencies) - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/qt.conf" - "[Paths]\nPlugins = ${QT_PLUGIN_PATH_REL_TO_BIN}\n") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/qt.conf" - DESTINATION ${INSTALL_BIN_DIR} - COMPONENT QtPlatformPlugin) - elseif(APPLE AND "${PACKAGE_TYPE}" STREQUAL "pkg") - ## add_mac_bundle.cmake installs the cocoa plugin to ${INSTALL_PLUGIN_DIR} in component Dependencies. - ## The qt.conf of each app bundle points there; bin/ gets its own. (A dmg keeps the plugins inside - ## each bundle, so bin/ has nothing to point to there.) - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/qt.conf" - "[Paths]\nPlugins = ${QT_PLUGIN_PATH_REL_TO_BIN}\n") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/qt.conf" - DESTINATION ${INSTALL_BIN_DIR} - COMPONENT Dependencies) + set(_qt_platform_plugin Qt6::QWindowsIntegrationPlugin) + ## Qt 6.7 replaced the "windowsvista" style by "modernwindows" + set(_qt_optional_plugins Qt6::QModernWindowsStylePlugin Qt6::QWindowsVistaStylePlugin) + else() + set(_qt_platform_plugin Qt6::QCocoaIntegrationPlugin) + set(_qt_optional_plugins Qt6::QMacStylePlugin) + endif() + list(APPEND _qt_optional_plugins Qt6::QJpegPlugin Qt6::QGifPlugin Qt6::QSvgPlugin Qt6::QSvgIconPlugin) + if(NOT TARGET ${_qt_platform_plugin}) + message(FATAL_ERROR "The Qt platform plugin ${_qt_platform_plugin} was not found; the GUI applications cannot start without it.") endif() + foreach(_qt_plugin IN ITEMS ${_qt_platform_plugin} ${_qt_optional_plugins}) + if(NOT TARGET ${_qt_plugin}) + if(NOT _qt_plugin MATCHES "StylePlugin$") + message(WARNING "The Qt plugin ${_qt_plugin} was not found, so it is not installed. Install the Qt module that provides it (qtsvg, qtimageformats).") + endif() + continue() + endif() + get_target_property(_qt_plugin_type ${_qt_plugin} QT_PLUGIN_TYPE) + install(IMPORTED_RUNTIME_ARTIFACTS ${_qt_plugin} + RUNTIME_DEPENDENCY_SET ${OPENMS_GUI_DEPS} + DESTINATION "${INSTALL_PLUGIN_DIR}/${_qt_plugin_type}" + COMPONENT Dependencies) + if(APPLE) + ## Homebrew links the plugins of its Qt formulae (qtbase, qtsvg) into a shared plugin + ## folder, where they are symbolic links into the kegs; install() copies a link as a + ## link. Replace it by the file it points to. + install(CODE " + set(_qt_plugin_file \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${INSTALL_PLUGIN_DIR}/${_qt_plugin_type}/$\") + if(IS_SYMLINK \"\${_qt_plugin_file}\") + file(REMOVE \"\${_qt_plugin_file}\") + file(COPY_FILE \"$\" \"\${_qt_plugin_file}\") + file(CHMOD \"\${_qt_plugin_file}\" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + endif()" + COMPONENT Dependencies) + endif() + endforeach() + ## relative to the folder of the executable (lib == bin on Windows: "plugins") + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/qt.conf" + "[Paths]\nPlugins = ${QT_PLUGIN_PATH_REL_TO_BIN}\n") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/qt.conf" + DESTINATION ${INSTALL_BIN_DIR} + COMPONENT Dependencies) +endif() add_custom_target(GUI) add_dependencies(GUI ${GUI_executables}) diff --git a/src/openms_gui/add_mac_bundle.cmake b/src/openms_gui/add_mac_bundle.cmake index d7f661598..06215bb38 100644 --- a/src/openms_gui/add_mac_bundle.cmake +++ b/src/openms_gui/add_mac_bundle.cmake @@ -1,19 +1,20 @@ # Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin # SPDX-License-Identifier: BSD-3-Clause -# +# # -------------------------------------------------------------------------- # $Maintainer: Julianus Pfeuffer $ # $Authors: Stephan Aiche, Julianus Pfeuffer $ # -------------------------------------------------------------------------- - -# custom code to add a mac bundle application - # -------------------------------------------------------------------------- -# adds a bundle with the given name -# @param _name Name of the bundle to add -# Note that this macro will also take care of installing the bundle in the -# case that a dmg package should be build +# add_mac_app_bundle() +# Adds the GUI application as a macOS app bundle and installs it. +# +# In the macOS package (PACKAGE_TYPE pkg) the bundle goes into the root of the OpenMS +# folder, next to its lib/ and bin/. It is not self-contained: it loads the OpenMS +# libraries and Qt from lib/ and the Qt plugins from lib/plugins (its qt.conf points +# there; the plugins are installed by src/openms_gui/CMakeLists.txt). A plain installation +# puts the bundle into ${INSTALL_BIN_DIR}. macro(add_mac_app_bundle _name) # the icon file set(ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/source/VISUAL/APPLICATIONS/GUITOOLS/${_name}-resources/${_name}.icns") @@ -29,22 +30,19 @@ macro(add_mac_app_bundle _name) set(OPENMS_BUNDLE_MINIMUM_SYSTEM_VERSION "12.0") endif() - ## TODO do we need a different RPATH for apps? Doesnt CMAKE do that automatically - # we also need the icns in the app add_executable( ${_name} MACOSX_BUNDLE ${GUI_DIR}/${_name}.cpp ${ICON_FILE_PATH}) - - ## packaged bundles live in the root of the package (next to lib/), plain installs put them into ${INSTALL_BIN_DIR} - if("${PACKAGE_TYPE}" STREQUAL "dmg" OR "${PACKAGE_TYPE}" STREQUAL "pkg") - set(_bundle_lib_rpath "@executable_path/../../../lib") + + ## /Contents/MacOS/: three levels up is the folder that holds the bundle + if("${PACKAGE_TYPE}" STREQUAL "pkg") + set(_bundle_lib_rpath "@executable_path/../../../${INSTALL_LIB_DIR}") else() set(_bundle_lib_rpath "@executable_path/../../../${INSTALL_LIB_PATH_REL_TO_BIN}") endif() - set_target_properties(${_name} - PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks;${_bundle_lib_rpath}") + set_target_properties(${_name} PROPERTIES INSTALL_RPATH "${_bundle_lib_rpath}") string(TIMESTAMP MY_YEAR "%Y") @@ -63,114 +61,51 @@ macro(add_mac_app_bundle _name) set_source_files_properties(${ICON_FILE_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - ## If you are packaging: Create ".app" bundles. - ## If you are not packaging, libraries are linked via hardcoded paths specific to your machine. - if("${PACKAGE_TYPE}" STREQUAL "dmg" OR "${PACKAGE_TYPE}" STREQUAL "pkg") - - # For dmg we need to fixup BEFORE installing (inside the build dir), because installing with CMake - # (to the temp folder used for packaging) will remove - # absolute load paths, required for finding dependencies by fixup_bundle. Maybe it can be done with the - # correct CMake RPATH settings (see root CMakeLists.txt) but it is very tricky. - - # TODO Use CMake's RUNTIME_DEPENDENCY_SET option in install commands (see PKG if-cases) to collect required - # libs, install them into the bundle and only fix their loading - if("${PACKAGE_TYPE}" STREQUAL "dmg") - set (APP_FOLDER "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${_name}.app") - ## Install Qt6 plugins needed on mac and save them in a var for fixing their dependencies later - set (PLUGIN_VAR_NAME QT_PLUGINS_APPS_${_name}) - install_qt6_plugin_builddir("Qt6::QCocoaIntegrationPlugin" ${PLUGIN_VAR_NAME} "${APP_FOLDER}/Contents/PlugIns" ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - install_qt6_plugin_builddir("Qt6::QMacStylePlugin" ${PLUGIN_VAR_NAME} "${APP_FOLDER}/Contents/PlugIns" ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - - set (QT_PLUGINS_TO_FIX ${${PLUGIN_VAR_NAME}}) - ## Find Qt library folder - get_target_property(QT_LIBRARY_DIR Qt6::Core LOCATION) - get_filename_component(QT_LIBRARY_DIR ${QT_LIBRARY_DIR} PATH) - get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/.." ABSOLUTE) - ## Fix up the dependencies in the bundle and make them rel. to their location in the bundle - ## Give additional plugins to fix and extra dirs where dependencies should be searched - install(CODE " - set(BU_CHMOD_BUNDLE_ITEMS On) - include(BundleUtilities) - fixup_bundle(${APP_FOLDER} \"${QT_PLUGINS_TO_FIX}\" \"${QT_LIBRARY_DIR}\") - " - COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - endif() - - - ## Copy bundle to the target install destination. Not to bin folder but root of package/dmg. + if("${PACKAGE_TYPE}" STREQUAL "pkg") + ## into the root of the OpenMS folder install(TARGETS ${_name} BUNDLE DESTINATION . COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) ## Listed in the component plist of the pkg (cmake/generate_applications_component_plist.cmake) set_property(GLOBAL APPEND PROPERTY OPENMS_APP_BUNDLES ${_name}) - - if("${PACKAGE_TYPE}" STREQUAL "pkg") - ## Write a qt.conf file with a ref to the plugin dir outside of the bundle (to share) - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/macappqt.conf" - "[Paths]\nPlugins = ../../${INSTALL_PLUGIN_DIR}\n") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/macappqt.conf" - DESTINATION "${_name}.app/Contents/Resources/" - RENAME "qt.conf" - COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - # Get the actual plugin files, resolving symlinks - get_target_property(_cocoa_plugin_path "Qt6::QCocoaIntegrationPlugin" LOCATION) - get_filename_component(_cocoa_plugin_path "${_cocoa_plugin_path}" REALPATH) - install(FILES "${_cocoa_plugin_path}" - DESTINATION "${INSTALL_PLUGIN_DIR}/platforms" - COMPONENT Dependencies) - - get_target_property(_macstyle_plugin_path "Qt6::QMacStylePlugin" LOCATION) - get_filename_component(_macstyle_plugin_path "${_macstyle_plugin_path}" REALPATH) - install(FILES "${_macstyle_plugin_path}" - DESTINATION "${INSTALL_PLUGIN_DIR}/styles" - COMPONENT Dependencies) - # Instead of softlinking, it is recommended by Apple to use RPATHs - #install(CODE "execute_process(COMMAND ln -fs ../../${INSTALL_LIB_DIR} \${CMAKE_INSTALL_PREFIX}/${_name}.app/Contents/Frameworks)" - # COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - #install(CODE "execute_process(COMMAND ln -fs ../../${INSTALL_PLUGIN_DIR} \${CMAKE_INSTALL_PREFIX}/${_name}.app/Contents/PlugIns)" - # COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - install(CODE "execute_process(COMMAND ${OPENMS_HOST_DIRECTORY}/cmake/MacOSX/fix_dependencies.rb -b \${CMAKE_INSTALL_PREFIX}/${_name}.app/Contents/MacOS/ -e @rpath/ -n -c)" - COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - else() # dmg - ## Write a qt.conf file with a ref to the plugin dir in app bundles = PlugIns - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/macappqt.conf" - "[Paths]\nPlugins = PlugIns\n") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/macappqt.conf" - DESTINATION "${_name}.app/Contents/Resources/" - RENAME "qt.conf" - COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - endif() - - ## Notarization is only possible with Xcode/Appleclang 10, otherwise we just skip - if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10 AND ("${PACKAGE_TYPE}" STREQUAL "dmg" OR "${PACKAGE_TYPE}" STREQUAL "pkg")) - ## We also need an identity to sign with - if(DEFINED CPACK_BUNDLE_APPLE_CERT_APP) - ## TODO try to find codesign to make sure the right exec is used (currently needs to be in path) - ## TODO allow choosing keychain - ## Note: Signing identity has to be unique, and present in any of the keychains in search list - ## which needs to be unlocked. Play around with keychain argument otherwise. - install(CODE " -execute_process(COMMAND codesign --deep --force --options runtime --sign \"${CPACK_BUNDLE_APPLE_CERT_APP}\" -i de.openms.${_name} \${CMAKE_INSTALL_PREFIX}/${_name}.app OUTPUT_VARIABLE sign_out ERROR_VARIABLE sign_out) -message('\${sign_out}')" COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - - install(CODE " -execute_process(COMMAND codesign -dv \${CMAKE_INSTALL_PREFIX}/${_name}.app OUTPUT_VARIABLE sign_check_out ERROR_VARIABLE sign_check_out) -message('\${sign_check_out}')" COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - - if("${PACKAGE_TYPE}" STREQUAL "dmg") - install(CODE " -execute_process(COMMAND ${OPENMS_HOST_DIRECTORY}/cmake/MacOSX/notarize.sh \${CMAKE_INSTALL_PREFIX}/${_name}.app de.openms.${_name} ${SIGNING_EMAIL} APPLE_APP_SPECIFIC_NOTARIZATION_PASSWORD ${OPENMS_HOST_BINARY_DIRECTORY} OUTPUT_VARIABLE notarize_out ERROR_VARIABLE notarize_out) -message('\${notarize_out}')" COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) + ## The Qt plugins in lib/plugins, relative to /Contents + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/macappqt.conf" + "[Paths]\nPlugins = ../../${INSTALL_PLUGIN_DIR}\n") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/macappqt.conf" + DESTINATION "${_name}.app/Contents/Resources/" + RENAME "qt.conf" + COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - install(CODE " -execute_process(COMMAND spctl -a -v \${CMAKE_INSTALL_PREFIX}/${_name}.app OUTPUT_VARIABLE verify_out ERROR_VARIABLE verify_out) -message('\${verify_out}')" COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) - endif() - endif(DEFINED CPACK_BUNDLE_APPLE_CERT_APP) + ## Load Qt through @rpath, i.e. from lib/ (the runtime dependency set of + ## cmake/package_general.cmake has put it there); then sign the bundle, which the fix-up + ## would invalidate afterwards. + install(CODE " +execute_process(COMMAND ${OPENMS_HOST_DIRECTORY}/cmake/MacOSX/fix_dependencies.rb -b \${CMAKE_INSTALL_PREFIX}/${_name}.app/Contents/MacOS/ -e @rpath/ -n -c RESULT_VARIABLE fix_result) +if(NOT fix_result EQUAL 0) + message(FATAL_ERROR \"fix_dependencies.rb failed for ${_name}.app (\${fix_result})\") +endif()" COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) + ## The identity is CPACK_BUNDLE_APPLE_CERT_APP (the top-level CMakeLists.txt; '-' signs ad hoc). + ## It has to be unique and present in an unlocked keychain of the search list. A secure + ## timestamp is only requested for a real identity (ad hoc signatures cannot have one). + if("${CPACK_BUNDLE_APPLE_CERT_APP}" STREQUAL "-") + set(_codesign_timestamp "") + else() + set(_codesign_timestamp "--timestamp") endif() + install(CODE " +execute_process(COMMAND codesign --deep --force --options runtime ${_codesign_timestamp} --sign \"${CPACK_BUNDLE_APPLE_CERT_APP}\" -i de.openms.${_name} \${CMAKE_INSTALL_PREFIX}/${_name}.app RESULT_VARIABLE sign_result OUTPUT_VARIABLE sign_out ERROR_VARIABLE sign_out) +message(STATUS \"\${sign_out}\") +if(NOT sign_result EQUAL 0) + message(FATAL_ERROR \"codesign failed for ${_name}.app (\${sign_result})\") +endif() +execute_process(COMMAND codesign -dv \${CMAKE_INSTALL_PREFIX}/${_name}.app OUTPUT_VARIABLE sign_check_out ERROR_VARIABLE sign_check_out) +message(STATUS \"\${sign_check_out}\")" COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT}) + unset(_codesign_timestamp) else() - ## Just install to the usual bin dir without fixing it up + ## A plain installation: the bundle finds the libraries through its install RPATH. A + ## runtime dependency set takes at most one bundle executable, hence one per bundle + ## (nothing installs them). install(TARGETS ${_name} RUNTIME_DEPENDENCY_SET ${_name}_DEPS RUNTIME DESTINATION ${INSTALL_BIN_DIR} COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT} BUNDLE DESTINATION ${INSTALL_BIN_DIR} COMPONENT ${OPENMS_GUI_APPLICATIONS_COMPONENT} diff --git a/src/openms_gui/source/VISUAL/ANNOTATION/Annotation1DPeakItem.cpp b/src/openms_gui/source/VISUAL/ANNOTATION/Annotation1DPeakItem.cpp index 792d47ae2..b9eb7066b 100644 --- a/src/openms_gui/source/VISUAL/ANNOTATION/Annotation1DPeakItem.cpp +++ b/src/openms_gui/source/VISUAL/ANNOTATION/Annotation1DPeakItem.cpp @@ -15,7 +15,7 @@ namespace OpenMS { namespace { - Annotation1DPeakItem p(Peak1D(0, 0), "test", QColor()); + Annotation1DPeakItem compile_check_peak_item(Peak1D(0, 0), "test", QColor()); } } diff --git a/src/openms_gui/source/VISUAL/ANNOTATION/Annotation1DTextItem.cpp b/src/openms_gui/source/VISUAL/ANNOTATION/Annotation1DTextItem.cpp index d3f275386..bf1cd02de 100644 --- a/src/openms_gui/source/VISUAL/ANNOTATION/Annotation1DTextItem.cpp +++ b/src/openms_gui/source/VISUAL/ANNOTATION/Annotation1DTextItem.cpp @@ -13,7 +13,7 @@ namespace OpenMS { namespace { - Annotation1DTextItem p({0, 0}, "test"); + Annotation1DTextItem compile_check_text_item({0, 0}, "test"); } void Annotation1DTextItem::ensureWithinDataRange(Plot1DCanvas* const canvas, const int layer_index) diff --git a/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/ExecutePipeline.cpp b/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/ExecutePipeline.cpp index a41ae45a5..a9df157fa 100644 --- a/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/ExecutePipeline.cpp +++ b/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/ExecutePipeline.cpp @@ -61,12 +61,10 @@ It can be exported from TOPPAS (File -> Save TOPPAS resource file). For \endcode -@if WITH_GUI The command line parameters of this tool are: @verbinclude TOPP_ExecutePipeline.cli INI file documentation of this tool: @htmlinclude TOPP_ExecutePipeline.html -@endif */ // We do not want this class to show up in the docu: diff --git a/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/ImageCreator.cpp b/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/ImageCreator.cpp index bb9e5c1b2..e390fd0a2 100644 --- a/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/ImageCreator.cpp +++ b/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/ImageCreator.cpp @@ -40,12 +40,10 @@ The input is first resampled into a matrix using bilinear forward resampling. Then the content of the matrix is written to an image file. The output has a uniform spacing in both dimensions regardless of the input. -@if WITH_GUI The command line parameters of this tool are: @verbinclude TOPP_ImageCreator.cli INI file documentation of this tool: @htmlinclude TOPP_ImageCreator.html -@endif */ // We do not want this class to show up in the docu: diff --git a/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/TOPPAS.cpp b/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/TOPPAS.cpp index b30e6514b..0393c87ed 100644 --- a/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/TOPPAS.cpp +++ b/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/TOPPAS.cpp @@ -27,10 +27,8 @@ More information about TOPPAS can be found in the @ref TOPPAS_tutorial. - @if WITH_GUI The command line parameters of this tool are: @verbinclude TOPP_TOPPAS.cli - @endif */ //QT diff --git a/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/TOPPView.cpp b/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/TOPPView.cpp index 04f37da18..e48d6a4aa 100644 --- a/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/TOPPView.cpp +++ b/src/openms_gui/source/VISUAL/APPLICATIONS/GUITOOLS/TOPPView.cpp @@ -16,12 +16,10 @@ @image html TOPPView.png More information about TOPPView can be found on the OpenMS ReadTheDocs - page: https://openms.readthedocs.io/en/latest/openms-applications-and-tools/visualize-with-openms.html + page: https://openms.readthedocs.io/en/latest/getting-started/visualize-with-openms.html - @if WITH_GUI The command line parameters of this tool are: @verbinclude TOPP_TOPPView.cli - @endif Note: By default, TOPPView scans for novel TOPP tools if there has been a version update. To force a rescan you can pass the --force flag. To skip the scan for tools, you can pass the --skip_tool_scan flag. diff --git a/src/openms_gui/source/VISUAL/APPLICATIONS/MISC/QApplicationTOPP.cpp b/src/openms_gui/source/VISUAL/APPLICATIONS/MISC/QApplicationTOPP.cpp index e4e43a073..88c4fe7cd 100644 --- a/src/openms_gui/source/VISUAL/APPLICATIONS/MISC/QApplicationTOPP.cpp +++ b/src/openms_gui/source/VISUAL/APPLICATIONS/MISC/QApplicationTOPP.cpp @@ -69,8 +69,9 @@ namespace OpenMS // we keep the user's stylesheet instead of overriding it with the OpenMS default. if (this->styleSheet().isEmpty()) { - std::string filename = File::find("GUISTYLE/qtStyleSheet.qss"); - QFile fh(toQString(filename)); + // compiled into the library (source/VISUAL/STYLE/style.qrc): the OpenMS core's + // share/OpenMS, which File::find() searches, does not hold the GUI's data + QFile fh(":/GUISTYLE/qtStyleSheet.qss"); fh.open(QFile::ReadOnly); QString style_string = QLatin1String(fh.readAll()); //std::cerr << "Stylesheet content: " << style_string.toStdString() << "\n\n\n"; diff --git a/src/openms_gui/source/VISUAL/APPLICATIONS/TOPPASBase.cpp b/src/openms_gui/source/VISUAL/APPLICATIONS/TOPPASBase.cpp index d33c5ebd9..27c71f6d5 100644 --- a/src/openms_gui/source/VISUAL/APPLICATIONS/TOPPASBase.cpp +++ b/src/openms_gui/source/VISUAL/APPLICATIONS/TOPPASBase.cpp @@ -164,10 +164,10 @@ namespace OpenMS QMenu* help = new QMenu("&Help", this); menuBar()->addMenu(help); QAction* action = help->addAction("OpenMS website", this, &TOPPASBase::showURL); - action->setData("http://www.OpenMS.de"); + action->setData("https://www.openms.de"); action = help->addAction("TOPPAS tutorial", this, &TOPPASBase::showURL); action->setShortcut(Qt::Key_F1); - action->setData(toQString(std::string("html/TOPPAS_tutorial.html"))); + action->setData(QString("html-gui/TOPPAS_tutorial.html")); // the documentation of the GUI layer help->addSeparator(); help->addAction("&About", this, &TOPPASBase::showAboutDialog); diff --git a/src/openms_gui/source/VISUAL/APPLICATIONS/TOPPViewBase.cpp b/src/openms_gui/source/VISUAL/APPLICATIONS/TOPPViewBase.cpp index d36e7ebf4..880c7cb6b 100644 --- a/src/openms_gui/source/VISUAL/APPLICATIONS/TOPPViewBase.cpp +++ b/src/openms_gui/source/VISUAL/APPLICATIONS/TOPPViewBase.cpp @@ -69,7 +69,6 @@ #include #include -#include using namespace std; diff --git a/src/openms_gui/source/VISUAL/EnhancedTabBar.cpp b/src/openms_gui/source/VISUAL/EnhancedTabBar.cpp index 3007c2afa..01bcc1744 100644 --- a/src/openms_gui/source/VISUAL/EnhancedTabBar.cpp +++ b/src/openms_gui/source/VISUAL/EnhancedTabBar.cpp @@ -111,7 +111,7 @@ namespace OpenMS return; } } - throw Exception::Precondition(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION,std::string("Tab with ID ") + id + " is already gone!"); + throw Exception::Precondition(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION,"Tab with ID " + std::to_string(id) + " is already gone!"); } void EnhancedTabBar::show(int id) diff --git a/src/openms_gui/source/VISUAL/LayerDataChrom.cpp b/src/openms_gui/source/VISUAL/LayerDataChrom.cpp index 5344e5960..70acae754 100644 --- a/src/openms_gui/source/VISUAL/LayerDataChrom.cpp +++ b/src/openms_gui/source/VISUAL/LayerDataChrom.cpp @@ -20,7 +20,6 @@ #include #include -#include using namespace std; diff --git a/src/openms_gui/source/VISUAL/MISC/GUIHelpers.cpp b/src/openms_gui/source/VISUAL/MISC/GUIHelpers.cpp index 96e87e153..9b5720f88 100644 --- a/src/openms_gui/source/VISUAL/MISC/GUIHelpers.cpp +++ b/src/openms_gui/source/VISUAL/MISC/GUIHelpers.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -126,13 +127,22 @@ namespace OpenMS // we expect all unqualified urls to be file urls try { - std::string local_url = File::findDoc(fromQString(target)); + // The GUI layer installs its documentation (html-gui/) into share/doc of the OpenMS + // installation, where File::findDoc() finds it. In a build tree it is in /doc. + const std::string build_doc = fromQString(QCoreApplication::applicationDirPath()) + "/../doc/" + fromQString(target); + const std::string local_url = File::readable(build_doc) ? File::absolutePath(build_doc) : File::findDoc(fromQString(target)); url_target = QUrl::fromLocalFile(toQString(local_url)); } catch (Exception::FileNotFound&) { - // we fall back to the web url - url_target = QUrl(QString("http://www.openms.de/current_doxygen/%1").arg(target), QUrl::TolerantMode); + // Not installed locally: fall back to the online documentation. The archive has the same + // layout as an installation (html/ of OpenMS, html-gui/ of the GUI layer), per release + // version; development builds use the latest nightly documentation. + const VersionInfo::VersionDetails version = VersionInfo::getVersionStruct(); + const QString doc_version = version.pre_release_identifier.empty() + ? QString("release/%1.%2.%3").arg(version.version_major).arg(version.version_minor).arg(version.version_patch) + : QString("nightly/latest"); + url_target = QUrl(QString("https://archive.openms.de/openms/Documentation/%1/%2").arg(doc_version, target), QUrl::TolerantMode); } } else diff --git a/src/openms_gui/source/VISUAL/Plot1DCanvas.cpp b/src/openms_gui/source/VISUAL/Plot1DCanvas.cpp index 818c453c0..ae51c87a3 100644 --- a/src/openms_gui/source/VISUAL/Plot1DCanvas.cpp +++ b/src/openms_gui/source/VISUAL/Plot1DCanvas.cpp @@ -43,7 +43,6 @@ #include #include -#include using namespace std; diff --git a/share/OpenMS/GUISTYLE/qtStyleSheet.qss b/src/openms_gui/source/VISUAL/STYLE/qtStyleSheet.qss similarity index 100% rename from share/OpenMS/GUISTYLE/qtStyleSheet.qss rename to src/openms_gui/source/VISUAL/STYLE/qtStyleSheet.qss diff --git a/src/openms_gui/source/VISUAL/STYLE/style.qrc b/src/openms_gui/source/VISUAL/STYLE/style.qrc new file mode 100644 index 000000000..1a7cb411f --- /dev/null +++ b/src/openms_gui/source/VISUAL/STYLE/style.qrc @@ -0,0 +1,5 @@ + + + qtStyleSheet.qss + + diff --git a/src/openms_gui/source/VISUAL/TOPPViewMenu.cpp b/src/openms_gui/source/VISUAL/TOPPViewMenu.cpp index 752a0a93d..54fcc2240 100644 --- a/src/openms_gui/source/VISUAL/TOPPViewMenu.cpp +++ b/src/openms_gui/source/VISUAL/TOPPViewMenu.cpp @@ -172,8 +172,8 @@ namespace OpenMS parent->menuBar()->addMenu(m_help); m_help->addAction(QWhatsThis::createAction(m_help)); m_help->addSeparator(); - m_help->addAction("OpenMS website", []() { GUIHelpers::openURL("http://www.OpenMS.de"); }); - m_help->addAction("Tutorials and documentation", []() { GUIHelpers::openURL("html/index.html"); })->setShortcut(Qt::Key_F1); + m_help->addAction("OpenMS website", []() { GUIHelpers::openURL("https://www.openms.de"); }); + m_help->addAction("Tutorials and documentation", []() { GUIHelpers::openURL("html-gui/index.html"); })->setShortcut(Qt::Key_F1); m_help->addSeparator(); diff --git a/src/openms_gui/source/VISUAL/TVIdentificationViewController.cpp b/src/openms_gui/source/VISUAL/TVIdentificationViewController.cpp index e9a3d8f86..153321790 100644 --- a/src/openms_gui/source/VISUAL/TVIdentificationViewController.cpp +++ b/src/openms_gui/source/VISUAL/TVIdentificationViewController.cpp @@ -32,8 +32,7 @@ #include #include -#include -#include +#include #include #include @@ -748,7 +747,7 @@ namespace OpenMS } Size row_index = 1; // ion annotations above sequence - reverse order to have first ion closest to sequence: - for (const std::string& ion : boost::adaptors::reverse(top_ions)) + for (const std::string& ion : std::views::reverse(top_ions)) { table[row_index][0] = "" + ion + ""; for (Size pos : ion_pos[ion]) @@ -784,7 +783,7 @@ namespace OpenMS generateSequenceRow_(seq, table[row_index]); // ion annotations below sequence - iterate over the bottom ions in reverse order (bottom-most first): row_index = table.size() - 2; - for (const std::string& ion : boost::adaptors::reverse(bottom_ions)) + for (const std::string& ion : std::views::reverse(bottom_ions)) { table[row_index][n_cols - 1] = "" + ion + ""; for (Size pos : ion_pos[ion]) diff --git a/src/openms_gui/source/VISUAL/sources.cmake b/src/openms_gui/source/VISUAL/sources.cmake index f5fc6678d..abb7a4468 100644 --- a/src/openms_gui/source/VISUAL/sources.cmake +++ b/src/openms_gui/source/VISUAL/sources.cmake @@ -99,6 +99,8 @@ source_group("Source Files\\VISUAL" FILES ${sources}) ### icons # add : icons are added to source/VISUAL/ICONS/resources.qrc # remove: after removing an icon, you have to rerun 'cmake' to fix the dependencies -qt_add_resources(qt_resource_file source/VISUAL/ICONS/resources.qrc) +## the default style sheet of the GUI applications (QApplicationTOPP), compiled into the +## library so that it does not depend on where the GUI layer is installed or run from +qt_add_resources(qt_resource_file source/VISUAL/ICONS/resources.qrc source/VISUAL/STYLE/style.qrc) set(OpenMSVisual_sources ${OpenMSVisual_sources} ${qt_resource_file}) set_property(SOURCE ${qt_resource_file} PROPERTY SKIP_AUTOGEN ON) diff --git a/src/testframework/CMakeLists.txt b/src/testframework/CMakeLists.txt new file mode 100644 index 000000000..2e357a50d --- /dev/null +++ b/src/testframework/CMakeLists.txt @@ -0,0 +1,47 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +project("OpenMSTestFramework") +cmake_minimum_required(VERSION 3.24 FATAL_ERROR) + +#------------------------------------------------------------------------------ +# The class-test framework of OpenMS (ClassTest.h macros, FuzzyStringComparator, +# MacrosTest.h), a copy of OpenMS' src/testframework: the OpenMS installation does +# not ship it. A STATIC library of the standard library only, used by the class tests +# of this repository and never installed (so no install rules). The libOpenMS part of +# the test setup is the OpenMSTestSupport target of src/tests/class_tests. +# +# The headers keep their OpenMS location (OpenMS/CONCEPT/ClassTest.h), so the tests +# copied from OpenMS compile unchanged. An installed OpenMS does not carry these +# headers, so they cannot be shadowed by those of the core. + +set(OpenMSTestFramework_sources + source/CONCEPT/ClassTest.cpp + source/CONCEPT/FuzzyStringComparator.cpp +) + +set(OpenMSTestFramework_sources_h + include/OpenMS/CONCEPT/ClassTest.h + include/OpenMS/CONCEPT/ClassTestUtils.h + include/OpenMS/CONCEPT/FuzzyStringComparator.h + include/OpenMS/CONCEPT/MacrosTest.h +) + +source_group("Source Files\\CONCEPT" FILES ${OpenMSTestFramework_sources}) +source_group("Header Files\\OpenMS\\CONCEPT" FILES ${OpenMSTestFramework_sources_h}) + +add_library(OpenMSTestFramework STATIC ${OpenMSTestFramework_sources}) +target_sources(OpenMSTestFramework PUBLIC FILE_SET HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/include" + FILES ${OpenMSTestFramework_sources_h}) +target_compile_features(OpenMSTestFramework PUBLIC cxx_std_23) + +## the warning and sanitizer flags of this build (cmake/compiler_flags.cmake), when there is one +if(COMMAND openms_add_library_compiler_flags) + openms_add_library_compiler_flags(OpenMSTestFramework) +endif() diff --git a/src/testframework/include/OpenMS/CONCEPT/ClassTest.h b/src/testframework/include/OpenMS/CONCEPT/ClassTest.h new file mode 100644 index 000000000..f6f46340b --- /dev/null +++ b/src/testframework/include/OpenMS/CONCEPT/ClassTest.h @@ -0,0 +1,1096 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: Timo Sachsenberg $ +// $Authors: Marc Sturm, Clemens Groepl, Chris Bielow, Timo Sachsenberg $ +// -------------------------------------------------------------------------- + +#pragma once + +// Std-only: no libOpenMS or third-party headers, so tests of every OpenMS library +// (incl. ones that don't link libOpenMS, like OpenSwathAlgo) can use it and +// recompiles stay cheap. Utilities are its own (ClassTestUtils.h); OpenMS behavior +// is registered by the OpenMSTestSupport target (src/tests/class_tests), which +// every class test of a libOpenMS-linking project links. +#include +#include + +#include +#include +#include +#include +#include + +// Empty declaration to avoid problems in case the namespace is not +// yet defined (e.g. TEST/ClassTest_test.cpp) + +/// Provide a point of redirection for testing the test macros, see ClassTest_test.cpp +#ifndef stdcout +#define stdcout std::cout +#endif + +namespace OpenMS +{ + namespace Internal + { + /// Namespace for class tests + namespace ClassTest + { + + /// Creates a temporary file name from the test name and the line with the specified extension + std::string + createTmpFileName(const std::string& file, int line, const std::string& extension = ""); + + // isRealType() and writtenDigits() -- used by #TEST_REAL_SIMILAR -- are provided + // by ClassTestUtils.h. They classify by convertibility to double, so the framework + // does not need to know library value types (DataValue, ParamValue, ...) by name. + + /** @brief Compare floating point numbers using @em absdiff_max_allowed and + @em ratio_max_allowed. + + Side effects: Updates #fuzzy_message. + */ + void + testRealSimilar(const char* file, int line, long double number_1, + const char* number_1_stringified, + bool number_1_is_realtype, int number_1_written_digits, + long double number_2, const char* number_2_stringified, + bool /* number_2_is_realtype */, int number_2_written_digits); + + /// used by testRealSimilar() + bool + isRealSimilar(long double number_1, long double number_2); + + /**@brief Compare strings using @em absdiff_max_allowed and @em ratio_max_allowed. + + This is called by the #TEST_STRING_SIMILAR macro. + + Side effects: Updates #absdiff, #ratio, #fuzzy_message, #line_num_1_max + and #line_num_2_max. + */ + void + testStringSimilar(const char* file, int line, + const std::string& string_1, + const char* string_1_stringified, + const std::string& string_2, + const char* string_2_stringified); + + /// used by TEST_STRING_EQUAL + void + testStringEqual(const char* file, int line, + const std::string& string_1, + const char* string_1_stringified, + const std::string& string_2, + const char* string_2_stringified); + + /**@brief Compare files using @em absdiff_max_allowed and @em ratio_max_allowed. + + Side effects: Updates #absdiff, #ratio, #fuzzy_message, #line_num_1_max + and #line_num_2_max. + */ + bool + isFileSimilar(const std::string& filename_1, + const std::string& filename_2); + + /// make sure we have a newline before results from first subtest + void + initialNewline(); + + /// print the text, each line gets a prefix, the marked line number gets a special prefix + void + printWithPrefix(const std::string& text, const int marked = -1); + + /** + @brief Set up some classtest variables as obtained from the 'START_TEST' macro + and check that no additional arguments were passed to the test executable. + + @param[in] version A version string, obtained from 'START_TEST(FuzzyStringComparator, "")' + @param[in] class_name The class under test (used for error messages etc), obtained from 'START_TEST(FuzzyStringComparator, "")' + @param[in] argc The number of arguments to the main() function of the class test (must be 1; test will quit otherwise) + @param[in] argv0 Name of the executable (for debug output) + */ + void mainInit(const char* version, const char* class_name, int argc, const char* argv0); + + /** + @brief Test if two files are exactly equal (used in TEST_FILE_EQUAL macro) + + @param[in] line The line where the macro was called (for reporting) + @param[in] filename The temp file + @param[in] templatename The ground truth file + @param[in] filename_stringified The expression used as the first macro argument + @param[in] templatename_stringified The expression used as the second macro argument + */ + void filesEqual(int line, const char* filename, const char* templatename, const char* filename_stringified, const char* templatename_stringified); + + /// removed all temporary files created with the NEW_TMP_FILE macro + void removeTempFiles(); + + /// set the whitelist_ + void + setWhitelist(const char* const /* file */, const int line, + const std::string& whitelist); + + /// Maximum ratio of numbers allowed, see #TOLERANCE_RELATIVE. + extern double ratio_max_allowed; + + /// Maximum ratio of numbers observed so far, see #TOLERANCE_RELATIVE. + extern double ratio_max; + + /// Recent ratio of numbers, see #TOLERANCE_RELATIVE. + extern double ratio; + + /// Maximum absolute difference of numbers allowed, see #TOLERANCE_ABSOLUTE. + extern double absdiff_max_allowed; + + /// Maximum difference of numbers observed so far, see #TOLERANCE_ABSOLUTE. + extern double absdiff_max; + + /// Recent absolute difference of numbers, see #TOLERANCE_ABSOLUTE. + extern double absdiff; + + extern int line_num_1_max; + extern int line_num_2_max; + + /// Verbosity level ( "-v" is 1 and "-V" is 2 ) + extern int verbose; + + /// Status of the whole test + extern bool all_tests; + + /// Status of the current subsection + extern bool test; + + /// Status of last elementary test + extern bool this_test; + + /// (Used by various macros. Indicates a rough category of the exception being caught.) + extern int exception; + + /// (Used by various macros. Stores the "name" of the exception, if applicable.) + extern std::string exception_name; + + /// (Used by various macros. Stores the "message" of the exception, if applicable.) + extern std::string exception_message; + + /// Name of current subsection + extern std::string test_name; + + /// Line where current subsection started + extern int start_section_line; + + /// Line of current elementary test + extern int test_line; + + /// Version string supplied with #START_TEST + extern const char* version_string; + + /// List of tmp file names (these will be cleaned up, see #NEW_TMP_FILE) + extern std::vector tmp_file_list; + + /// List of all failed lines for summary at the end of the test + extern std::vector failed_lines_list; + + /// Questionable file tested by #TEST_FILE_EQUAL + extern std::ifstream infile; + + /// Template (correct) file used by #TEST_FILE_EQUAL + extern std::ifstream templatefile; + + /// (A variable used by #TEST_FILE_EQUAL) + extern bool equal_files; + + /// (A buffer for one line from a file. Used by #TEST_FILE_EQUAL.) + extern char line_buffer[65536]; + + /// Counter for the number of elementary tests within the current subsection. + extern int test_count; + + /// See #ADD_MESSAGE. + extern std::string add_message; + + /**@brief Last message from a fuzzy comparison. Written by + #isRealSimilar(), #testStringSimilar(), #isFileSimilar(). Read by + #TEST_REAL_SIMILAR, #TEST_STRING_SIMILAR, #TEST_FILE_SIMILAR; + */ + extern std::string fuzzy_message; + + /// (Flags whether a new line is in place, depending on context and verbosity setting. Used by initialNewline() and some macros.) + extern bool newline; + + template + void + testEqual(const char* /*file*/, int line, const T1& expression_1, + const char* expression_1_stringified, + const T2& expression_2, + const char* expression_2_stringified) + { + ++test_count; + test_line = line; + // For std::string targets, stringify the comparison value rather than + // constructing std::string(expression_2): the old OpenMS::String had + // numeric/char converting constructors (e.g. String(114) == "114") that + // std::string lacks. detail::toString reproduces that behavior (and must + // format numbers exactly like StringUtils::toStr, see ClassTestUtils.h). + if constexpr (std::is_same_v) + { + this_test = bool(expression_1 == detail::toString(expression_2)); + } + else + { + this_test = bool(expression_1 == T1(expression_2)); + } + test &= this_test; + { + initialNewline(); + if (!this_test || verbose > 1) + { + stdcout << ' ' << (this_test ? '+' : '-') << " line " << line << " : TEST_EQUAL(" << expression_1_stringified << ',' + << expression_2_stringified << "): got '"; + + // we can't print wide chars directly using operator<< so we need to test for it + if constexpr (std::is_same_v, char16_t*> || std::is_same_v, char16_t*>) + { + stdcout << (expression_1 == nullptr ? "(null)" : "(char16_t*)") << "', expected '" + << (expression_2 == nullptr ? "(null)" : "(char16_t*)") << "'\n"; + } + else if constexpr (std::is_enum_v && std::is_enum_v) + { + stdcout << static_cast(expression_1) << "', expected '" << static_cast(expression_2) << "'\n"; + } + else + { + detail::printValue(stdcout, expression_1); + stdcout << "', expected '"; + detail::printValue(stdcout, expression_2); + stdcout << "'\n"; + } + } + if (!this_test) + { + failed_lines_list.push_back(line); + } + } + } + + inline void testTrue(const char* /*file*/, int line, const bool expression_1, const char* expression_1_stringified) + { + ++test_count; + test_line = line; + this_test = expression_1; + test &= this_test; + { + initialNewline(); + if (this_test) + { + if (verbose > 1) + { + stdcout << " + line " << line << ": TEST_TRUE(" << expression_1_stringified << "): ok\n"; + } + } + else + { + stdcout << " - line " << line << ": TEST_TRUE(" << expression_1_stringified << "): failed\n"; + failed_lines_list.push_back(line); + } + } + } + + inline void testFalse(const char* /*file*/, int line, const bool expression_1, const char* expression_1_stringified) + { + ++test_count; + test_line = line; + this_test = !expression_1; + test &= this_test; + { + initialNewline(); + if (this_test) + { + if (verbose > 1) + { + stdcout << " + line " << line << ": TEST_FALSE(" << expression_1_stringified << "): ok\n"; + } + } + else + { + stdcout << " - line " << line << ": TEST_FALSE(" << expression_1_stringified << "): failed\n"; + failed_lines_list.push_back(line); + } + } + } + + template + void + testNotEqual(const char* /*file*/, int line, const T1& expression_1, + const char* expression_1_stringified, + const T2& expression_2, + const char* expression_2_stringified) + { + ++test_count; + test_line = line; + this_test = !(expression_1 == T1(expression_2)); + test &= this_test; + { + initialNewline(); + if (!this_test || verbose > 1) + { + stdcout << ' ' << (this_test ? '+' : '-') << " line " << line << " : TEST_NOT_EQUAL(" << expression_1_stringified << ',' + << expression_2_stringified << "): got '"; + if constexpr (std::is_enum_v && std::is_enum_v) + { + stdcout << static_cast(expression_1) << "', forbidden is '" << static_cast(expression_2) << "'\n"; + } + else + { + detail::printValue(stdcout, expression_1); + stdcout << "', forbidden is '"; + detail::printValue(stdcout, expression_2); + stdcout << "'\n"; + } + } + if (!this_test) + { + failed_lines_list.push_back(line); + } + } + } + + + /** + @brief Translator producing a description of the currently handled exception. + + Called with an exception being handled (rethrow it with @c throw; internally). + If the translator recognizes the type, it writes a one-line description + (e.g. name, origin, message) into @p description and returns true; + otherwise it returns false and must leave @p description alone. + + The framework itself knows only @c std::exception. Test projects register + translators for their library's exception hierarchy -- see + src/tests/class_tests/source/OpenMSTestSupport.cpp -- which is how + failure reports show OpenMS exception names without the framework + depending on libOpenMS. (Same design as Catch2's exception translators.) + */ + using ExceptionTranslator = bool (*)(std::string& description); + + /// Register an exception translator (idempotent; bounded, first-come first-served). + void registerExceptionTranslator(ExceptionTranslator translator); + + /// Whether TEST_PRECONDITION/POSTCONDITION_VIOLATED actually run their command. + /// Off by default; the test-support TU of the library under test enables it when + /// the library was built with its precondition checks active (OPENMS_ASSERTIONS), + /// so the expectation always matches the library's real behavior. + bool preconditionTestsEnabled(); + + /// See preconditionTestsEnabled(). + void setPreconditionTestsEnabled(bool enabled); + + /// Describe the exception currently being handled: registered translators first, + /// then std::exception's what(), then a generic fallback. + std::string describeCaughtException(); + + void printLastException(std::ostream& out); + + int endTestPostProcess(std::ostream& out); + + void endSectionPostProcess(std::ostream& out, const int line); + } + } +} + +// A namespace alias - apparently these cannot be documented using doxygen (?) +namespace TEST = OpenMS::Internal::ClassTest; + +/** + @defgroup ClassTest Class test macros + + @brief The macros of OpenMS' class-test framework (library + OpenMSTestFramework), used by the test programs under + src/tests/class_tests/. + + The framework lives in its own static library, OpenMSTestFramework + (src/testframework/), and depends on the C++ standard library + ONLY — it does not link or include libOpenMS. That is what lets the tests of + every OpenMS library (including ones that do not link libOpenMS, such as + OpenSwathAlgo) use the same macros, and it keeps the framework working + unchanged when libOpenMS is split into smaller libraries. + + A test program is one translation unit: #START_TEST / #END_TEST wrap the whole + program, #START_SECTION / #END_SECTION wrap each subtest, and the elementary + macros (#TEST_EQUAL, #TEST_REAL_SIMILAR, #TEST_STRING_SIMILAR, + #TEST_FILE_EQUAL, #TEST_FILE_SIMILAR, #TEST_EXCEPTION, ...) record results. + On success the program prints "PASSED", otherwise "FAILED" plus the failing + lines. With -v it prints information about subsections, with + -V (or the environment variable + OPENMS_TEST_VERBOSE=True) about every elementary test. + + Because the framework knows nothing about the library under test, + library-specific behavior is registered by the test project + (see src/tests/class_tests/source/OpenMSTestSupport.cpp, built as + the OpenMSTestSupport target, which every class test of a project + that links libOpenMS links in turn): + + - OpenMS::Internal::ClassTest::registerExceptionTranslator() — so failure + reports show the name and origin of unexpected OpenMS exceptions (the + framework itself only knows std::exception); + - OpenMS::Internal::ClassTest::setPreconditionTestsEnabled() — so + #TEST_PRECONDITION_VIOLATED / #TEST_POSTCONDITION_VIOLATED expect a throw + exactly when the library's #OPENMS_PRECONDITION checks are compiled in; + - OpenMS::UniqueIdGenerator seeding, so unique IDs written into test output + files are reproducible against the reference files. + + Values in failure reports print via operator<< found at the + macro-expansion site (so any type your test can see prints as usual); + #TEST_REAL_SIMILAR accepts floating point values and class types implicitly + convertible to double (e.g. DataValue, ParamValue) — no + registration needed. + + Schema validation of temporary files is explicit: tests that write XML call + #VALIDATE_TMP_FILES (or #VALIDATE_FILE) from + OpenMS/TestFileValidation.h in the openms class-test project — + it needs the FORMAT layer and therefore deliberately does not live in the + framework. + + To create a test, follow the guidelines in @ref developer_faq (section "How to add a new class test"). + Look at existing test files in src/tests/class_tests/ for examples. + + @ingroup Concept + + */ +/** @{ */ + +/** @brief Begin of the test program for a given class. @sa #END_TEST. + + The #START_TEST macro defines the start of the test program for a given + classname. The classname is printed together with some information when + calling the test program with any arguments (except for -v or + -V). + + The second argument version should take the form "$Id:$" but is currently + deprecated. Originally, the SVN revision was annotated by the revision + control system. + + The #START_TEST macro should be the first one to call in a test program. It + opens a global try block to catch any unwanted exceptions. If + any of these exceptions occurs, all tests fail. The report describes the + caught exception via the registered exception translators (see + OpenMS::Internal::ClassTest::registerExceptionTranslator), so e.g. %OpenMS + exceptions are reported with their name and origin. The #END_TEST macro + also closes the try block. This + try block should never catch an exception! All exceptions that + are thrown due to some malfunction in one of the member functions should be + caught by the try block created by #START_SECTION and + #END_SECTION . + + @hideinitializer + */ +#define START_TEST(class_name, version) \ + int main(int argc, char** argv) \ + { \ + TEST::mainInit(version, #class_name, argc, argv[0]); \ + try { + +/** @brief End of the test program for a class. @sa #START_TEST. + + The #END_TEST macro implements the correct termination of the test program + and should therefore be the last macro to call. It determines the exit code + based on all previously run subtests and prints out the message "PASSED" or + "FAILED". This macro also closes the global try block opened + by #START_TEST and contains the related catch clauses. If an + exception is caught here, the test program fails. + + @hideinitializer + */ +#define END_TEST \ + /* global try block */ \ + } \ + catch (...) \ + { \ + TEST::printLastException(stdcout); \ + } \ + return TEST::endTestPostProcess(stdcout); \ + } + +/** @brief Begin of a subtest with a given name. @sa #END_SECTION. + + The #START_SECTION macro is used to declare the name of a subtest. Use this + to examine a member function of the class which was specified in + #START_TEST. If you want to check e.g. the memFunc() method of a class, + insert a line #START_SECTION(memFunc()) in your test program. If the test + program is called in verbose mode, this leads to the name of the subtest + being printed on execution. + If you are testing a non-public method you can use the [EXTRA] statement, + e.g. #START_SECTION([EXTRA]memFunc()) + to mark a subtest that has no matching method declaration. + + This macro also opens a try block to catch any unexpected + exceptions thrown in the course of a subtest. To catch wanted + exceptions (i.e. to check for exceptions that are the expected result of + some command) use the #TEST_EXCEPTION macro. The try block + opened by #START_SECTION is closed in #END_SECTION, so these two macros have to be + balanced. + + @hideinitializer + */ +#define START_SECTION(name_of_test) \ + TEST::test = true; \ + TEST::newline = false; \ + TEST::test_name = # name_of_test; \ + TEST::test_count = 0; \ + TEST::start_section_line = __LINE__; \ + stdcout << "checking " << TEST::test_name << " ... " << std::flush; \ + try \ + { \ + while (true) \ + { + +/** @brief End of a subtest. @sa #START_SECTION. + + The #END_SECTION macro defines the end of a subtest. + + Each elementary test macro updates an internal variable (TEST::test) that + holds the state of the current subtest. #END_SECTION prints whether the + subtest has passed or failed (in verbose mode) and updates the internal + variables TEST::all_tests that describes the state of the whole class + test. TEST::all_tests is initialized to be true. If any + elementary test fails, TEST::test becomes false. At the time + of the next call to #END_SECTION, TEST::all_tests will be set to + false, if TEST::test is false. One failed elementary test leads + therefore to a failed subtest, which leads to a failed class test. + + This macro closes the try block opened by #START_SECTION, so + #START_SECTION and #END_SECTION have to be balanced, or some ugly + compile-time errors will occur. #END_SECTION catches any exception and + reports it via the registered exception translators (see + OpenMS::Internal::ClassTest::registerExceptionTranslator). + After the exception is caught, the execution will continue with + the next subtest, but the current subtest is marked as failed (as is the + whole test program). + + @hideinitializer + */ +#define END_SECTION \ + break; \ + } \ + } \ + catch (...) \ + { \ + TEST::printLastException(stdcout);\ + } \ + TEST::endSectionPostProcess(stdcout, __LINE__); + + +/** @brief Generic equality macro. + + This macro uses the operator == to check its two arguments for equality. + Besides handling some internal stuff, it basically evaluates ((a) == (b)). + + Remember that operator == has to be defined somehow for the two argument + types. Additionally the << operator needs to be defined. + If only == is available you will get a compilation error. As workaround + use TEST_EQUAL(a==b, true) thereby making bug tracing harder, + as you won't see the values of a and b. + + + @note This macro evaluates its arguments once or twice, depending on verbosity settings. + + @param[in] a value/object to test + @param[in] b expected value + + @hideinitializer + */ +#define TEST_EQUAL(a, b) TEST::testEqual(__FILE__, __LINE__, (a), (# a), (b), (# b)); + +/** @brief Boolean test macro. + + This macro tests if its argument evaluates to 'true'. + If possible use TEST_EQUAL(a, b) instead of TEST_TRUE(a==b), because the latter makes bug tracing harder. + + @param[in] a value/object convertible to bool + + @hideinitializer +*/ +#define TEST_TRUE(a) TEST::testTrue(__FILE__, __LINE__, (a), (#a)); + +/** @brief Boolean test macro. + + This macro tests if its argument evaluates to 'false'. + If possible use TEST_NOT_EQUAL(a, b) instead of TEST_FALSE(a!=b), because the latter makes bug tracing harder. + + @param[in] a value/object convertible to bool + + @hideinitializer +*/ +#define TEST_FALSE(a) TEST::testFalse(__FILE__, __LINE__, (a), (#a)); + + +/** @brief Generic inequality macro. + + This macro checks for inequality just like #TEST_EQUAL tests for equality. + The only difference between the two macros is that #TEST_NOT_EQUAL evaluates + !((a) == (b)). + + @param[in] a value/object to test + @param[in] b forbidden value + + @hideinitializer + */ +#define TEST_NOT_EQUAL(a, b) TEST::testNotEqual(__FILE__, __LINE__, (a), (# a), (b), (# b)); + +/** @brief std::string equality macro. + + Both arguments are converted to std::string and tested for equality. (That + is, we check whether (std::string(a) == std::string(b)) holds.) + + @note This macro evaluates its arguments once or twice, depending on verbosity settings. + + @param[in] a value to test + @param[in] b expected value + + @hideinitializer + */ +#define TEST_STRING_EQUAL(a, b) TEST::testStringEqual(__FILE__, __LINE__, (a), (# a), (b), (# b)); + +/** + @brief File comparison macro. + + This macro is used to test file operations. It + compares the file with name filename against a template file + templatename. Corresponding lines of the two files have to be + identical. + + @note line length is limited to 64k characters + + @note This macro evaluates its arguments once or twice, depending on verbosity settings. + + @hideinitializer + */ +#define TEST_FILE_EQUAL(filename, templatename) \ + { \ + TEST::filesEqual(__LINE__, filename, templatename, #filename, #templatename); \ + } + +/** @brief Floating point similarity macro. + + Checks whether the two numbers are sufficiently close based upon the + settings of #TOLERANCE_ABSOLUTE and #TOLERANCE_RELATIVE. + + @note This macro evaluates its arguments once or twice, depending on verbosity settings. + + @note Both arguments are converted to @c double. The actual comparison is done + by isRealSimilar(). + + @param[in] a value to test + @param[in] b expected value + + @hideinitializer + */ +#define TEST_REAL_SIMILAR(a, b) TEST::testRealSimilar(__FILE__, __LINE__, (a), (# a), TEST::isRealType(a), TEST::writtenDigits(a), (b), (# b), TEST::isRealType(b), TEST::writtenDigits(b)); + +/** @brief std::string similarity macro. + + Compares the two strings using @em FuzzyStringComparator with the settings of + #TOLERANCE_ABSOLUTE and #TOLERANCE_RELATIVE. + + @note This macro evaluates its arguments once or twice, depending on verbosity settings. + + @note Both arguments are converted to @c std::string. The actual comparison + is done by testStringSimilar(). + + @param[in] a value to test + @param[in] b expected value + + @hideinitializer + */ +#define TEST_STRING_SIMILAR(a, b) TEST::testStringSimilar(__FILE__, __LINE__, (a), (# a), (b), (# b)); + +/** @brief File similarity macro. + + Compares the two files using @em FuzzyStringComparator with the settings of + #TOLERANCE_ABSOLUTE and #TOLERANCE_RELATIVE. + + @note This macro evaluates its arguments once or twice, depending on verbosity settings. + + @note The actual comparison is done by isFileSimilar(). + + @param[in] a value to test + @param[in] b expected value + + @hideinitializer + */ +#define TEST_FILE_SIMILAR(a, b) \ + { \ + ++TEST::test_count; \ + TEST::test_line = __LINE__; \ + TEST::this_test = TEST::isFileSimilar((a), (b)); \ + TEST::test = TEST::test && TEST::this_test; \ + { \ + TEST::initialNewline(); \ + if (TEST::this_test) \ + { \ + if (TEST::verbose > 1) \ + { \ + stdcout << " + line " << __LINE__ \ + << ": TEST_FILE_SIMILAR(" # a "," # b "): absolute: " \ + << TEST::precisionWrapper(TEST::absdiff) \ + << " (" \ + << TEST::precisionWrapper(TEST::absdiff_max_allowed) \ + << "), relative: " \ + << TEST::precisionWrapper(TEST::ratio) \ + << " (" \ + << TEST::precisionWrapper(TEST::ratio_max_allowed) \ + << ")\n"; \ + stdcout << "message: \n"; \ + stdcout << TEST::fuzzy_message; \ + } \ + } \ + else \ + { \ + stdcout << " - line " << TEST::test_line << \ + ": TEST_FILE_SIMILAR(" # a "," # b ") ... -\n"; \ + stdcout << "message: \n"; \ + stdcout << TEST::fuzzy_message; \ + TEST::failed_lines_list.push_back(TEST::test_line); \ + } \ + } \ + } + +/** @brief Define the relative tolerance for floating point comparisons. + + @sa #TEST_REAL_SIMILAR, #TEST_STRING_SIMILAR, #TEST_FILE_SIMILAR + + Several macros consider two numbers sufficiently "close" if the ratio of + the larger and the smaller is bounded by the value supplied by + #TOLERANCE_RELATIVE. The default value is @f$ 1 + 10^{-5} @f$. It is + possible to redefine the relative tolerance by calling #TOLERANCE_RELATIVE + with the new value. + + @hideinitializer + */ +#define TOLERANCE_RELATIVE(a) \ + TEST::ratio_max_allowed = (a); \ + { \ + TEST::initialNewline(); \ + if (TEST::verbose > 1) \ + { \ + stdcout << " + line " << __LINE__ << \ + ": TOLERANCE_RELATIVE(" << TEST::ratio_max_allowed << \ + ") (\"" # a "\")\n"; \ + } \ + } + +/** @brief Define the absolute tolerance for floating point comparisons. + + @sa #TEST_REAL_SIMILAR, #TEST_STRING_SIMILAR, #TEST_FILE_SIMILAR + + Several macros consider two numbers sufficiently "close" if the absolute + difference is bounded by the value supplied by #TOLERANCE_ABSOLUTE. The + default value is @f$ 10^{-5} @f$. It is possible to redefine the absolute + tolerance by calling #TOLERANCE_ABSOLUTE with the new value. + + @hideinitializer + */ +#define TOLERANCE_ABSOLUTE(a) \ + TEST::absdiff_max_allowed = (a); \ + { \ + TEST::initialNewline(); \ + if (TEST::verbose > 1) \ + { \ + stdcout << " + line " << __LINE__ << \ + ": TOLERANCE_ABSOLUTE(" << TEST::absdiff_max_allowed << \ + ") (\"" # a "\")\n"; \ + } \ + } + +/** @brief Define the whitelist_ used by #TEST_STRING_SIMILAR and #TEST_FILE_SIMILAR. + + If both lines contain the same element from this list, they are skipped + over. (See @em FuzzyStringComparator.) + */ +#define WHITELIST(a) TEST::setWhitelist(__FILE__, __LINE__, (a)); + +/** @brief Exception test macro. + + This macro checks if a given type of exception occurred while executing the + given command. Example: #TEST_EXCEPTION(Exception::IndexOverflow, + vector[-1]). If no or a wrong exception occurred, false is returned, + otherwise true. + + @param[in] exception_type the exception-class + @param[in] command any general C++ or OpenMS-specific command + + @hideinitializer + */ +#define TEST_EXCEPTION(exception_type, command) \ + { \ + ++TEST::test_count; \ + TEST::test_line = __LINE__; \ + TEST::exception = 0; \ + try \ + { \ + command; \ + } \ + catch (exception_type&) \ + { \ + TEST::exception = 1; \ + } \ + catch (...) \ + { \ + /* the framework knows only std::exception; registered translators (see */ \ + /* OpenMSTestSupport.cpp) describe library exception types by name */ \ + TEST::exception = 2; \ + TEST::exception_name = TEST::describeCaughtException(); \ + } \ + TEST::this_test = (TEST::exception == 1); \ + TEST::test = TEST::test && TEST::this_test; \ + { \ + TEST::initialNewline(); \ + switch (TEST::exception) \ + { \ + case 0: \ + stdcout << " - line " << TEST::test_line << \ + ": TEST_EXCEPTION(" # exception_type "," # command \ + "): no exception thrown!\n"; \ + TEST::failed_lines_list.push_back(TEST::test_line); \ + break; \ + case 1: \ + if (TEST::verbose > 1) \ + { \ + stdcout << " + line " << TEST::test_line << \ + ": TEST_EXCEPTION(" # exception_type "," # command \ + "): OK\n"; \ + } \ + break; \ + case 2: \ + stdcout << " - line " << TEST::test_line << \ + ": TEST_EXCEPTION(" # exception_type "," # command \ + "): wrong exception thrown! \"" \ + << TEST::exception_name << "\"\n"; \ + TEST::failed_lines_list.push_back(TEST::test_line); \ + break; \ + } \ + } \ + } + +/** @brief Precondition test macro + + This macro checks if a precondition violation is detected while executing the command, + similar to TEST_EXCEPTION(Exception::Precondition,command). + However the test is executed only when the library's #OPENMS_PRECONDITION macros + are active; the test project registers that fact via setPreconditionTestsEnabled() + (see OpenMSTestSupport.cpp), keyed on the same OPENMS_ASSERTIONS the library + uses (see Macros.h). + + @param[in] command any general C++ or OpenMS-specific command + + @hideinitializer + */ +#define TEST_PRECONDITION_VIOLATED(command) \ + { \ + if (TEST::preconditionTestsEnabled()) \ + { \ + TEST_EXCEPTION(Exception::Precondition, command); \ + } \ + else \ + { \ + STATUS("TEST_PRECONDITION_VIOLATED(" # command ") - skipped"); \ + } \ + } + +/** @brief Postcondition test macro + + This macro checks if a postcondition violation is detected while executing the command, + similar to TEST_EXCEPTION(Exception::Postcondition,command). + However the test is executed only when the library's #OPENMS_POSTCONDITION macros + are active; see #TEST_PRECONDITION_VIOLATED for how that is determined. + + @param[in] command any general C++ or OpenMS-specific command + + @hideinitializer + */ +#define TEST_POSTCONDITION_VIOLATED(command) \ + { \ + if (TEST::preconditionTestsEnabled()) \ + { \ + TEST_EXCEPTION(Exception::Postcondition, command); \ + } \ + else \ + { \ + STATUS("TEST_POSTCONDITION_VIOLATED(" # command ") - skipped"); \ + } \ + } + + +/** @brief Exception test macro (with test for exception message). + + This macro checks if a given type of exception occurred while executing the + given command and additionally tests for the message of the exception. + + Example: #TEST_EXCEPTION_WITH_MESSAGE(Exception::IndexOverflow, vector[-1], "a null pointer was specified") + + If no, a wrong exception occurred or a wrong message is returned, false is + returned, otherwise true. + + @param[in] exception_type the exception-class + @param[in] command any general C++ or OpenMS-specific command + @param[in] message the message the exception should give + + @hideinitializer + */ +#define TEST_EXCEPTION_WITH_MESSAGE(exception_type, command, message) \ + { \ + ++TEST::test_count; \ + TEST::test_line = __LINE__; \ + TEST::exception = 0; \ + try \ + { \ + command; \ + } \ + catch (exception_type& et) \ + { \ + if (std::string(et.what()) != std::string(message)) \ + { \ + TEST::exception = 4; \ + TEST::exception_message = et.what(); \ + } \ + else TEST::exception = 1; \ + } \ + catch (...) \ + { \ + TEST::exception = 2; \ + TEST::exception_name = TEST::describeCaughtException(); \ + } \ + TEST::this_test = (TEST::exception == 1); \ + TEST::test = TEST::test && TEST::this_test; \ + \ + { \ + TEST::initialNewline(); \ + switch (TEST::exception) \ + { \ + case 0: \ + stdcout << " - line " << TEST::test_line << \ + ": TEST_EXCEPTION_WITH_MESSAGE(" # exception_type "," # command ", " # message \ + "): no exception thrown!\n"; \ + TEST::failed_lines_list.push_back(TEST::test_line); \ + break; \ + case 1: \ + if (TEST::verbose > 1) \ + { \ + /* this is actually what we want to get: */ \ + stdcout << " + line " << TEST::test_line << \ + ": TEST_EXCEPTION_WITH_MESSAGE(" # exception_type "," # command ", " # message \ + "): OK\n"; \ + } \ + break; \ + case 2: \ + stdcout << " - line " << TEST::test_line << \ + ": TEST_EXCEPTION_WITH_MESSAGE(" # exception_type "," # command ", " # message \ + "): wrong exception thrown! \"" << \ + TEST::exception_name << "\"\n"; \ + TEST::failed_lines_list.push_back(TEST::test_line); \ + break; \ + case 4: \ + stdcout << " - line " << TEST::test_line << \ + ": TEST_EXCEPTION_WITH_MESSAGE(" # exception_type "," # command ", " # message \ + "): exception has wrong message: got '" << \ + TEST::exception_message << \ + "', expected '" << \ + (message) << "'\n"; \ + TEST::failed_lines_list.push_back(TEST::test_line); \ + break; \ + } \ + } \ + } + +/** @brief Create a temporary filename. + + This macro assigns a new temporary filename to the string variable given as + its argument. The filename is created using the filename of the test and the + line number where this macro is invoked, for example 'Matrix_test.cpp' might + create a temporary file 'Matrix_test_268.tmp' if NEW_TMP_FILE is used in + line 268. All temporary files are deleted if #END_TEST is called. @p + filename string will contain the filename on completion of the macro. + + There is a version that defines the extension and one that uses tmp. + + @hideinitializer + */ +#define NEW_TMP_FILE_EXT(filename, extension) filename = TEST::createTmpFileName(__FILE__, __LINE__, extension); + + +#define NEW_TMP_FILE(filename) filename = TEST::createTmpFileName(__FILE__, __LINE__); + + +/** @brief Skip the remainder of the current subtest. + + If the condition is not fulfilled, the remainder of the current subtest is + skipped over. The TEST status is set to FAIL. + + @hideinitializer + */ +#define ABORT_IF(condition) \ + if (condition) \ + { \ + { \ + TEST::test_line = __LINE__; \ + TEST::this_test = false; \ + TEST::test = TEST::test && TEST::this_test; \ + TEST::failed_lines_list.push_back(TEST::test_line); \ + TEST::initialNewline(); \ + stdcout << " - line " << TEST::test_line << \ + ": ABORT_IF(" # condition "): TEST ABORTED\n"; \ + } \ + break; \ + } + +/** + @brief Print a status message. + + If tests require longer preparations, #STATUS may be used to print some + intermediate progress messages. #STATUS uses cout to print + these messages (in verbose mode only). The given stream expression + message is prefixed by the string status: and + terminated with a newline. All valid operations on a stream may be performed + in message. + + Example: + + STATUS( "just calculated x = " << precisionWrapper(x) ) + + + @hideinitializer + */ +#define STATUS(message) \ + { \ + TEST::initialNewline(); \ + stdcout << " line " \ + << __LINE__ \ + << ": status: " \ + << message \ + << "\n"; \ + } + +/** + @brief Sets an additional text that is displayed after final result of the test. + + This can be used to provide additional information about the test to the user. + It is e.g. used to indicate that the DB test were skipped, when there are no + credentials given. + + @hideinitializer + */ +#define ADD_MESSAGE(message) \ + TEST::add_message = message; + +/** + @brief Macro that suppresses the warning issued when no subtests are performed + + Please use this macro only if the method cannot be tested at all or cannot be + tested properly on its own. In the later case, the method must however be tested + in tests of related methods. See also @em test_count. + + @hideinitializer + */ +#define NOT_TESTABLE \ + TEST::test_count = 1; + +/** @} */ // end of defgroup ClassTest diff --git a/src/testframework/include/OpenMS/CONCEPT/ClassTestUtils.h b/src/testframework/include/OpenMS/CONCEPT/ClassTestUtils.h new file mode 100644 index 000000000..c9361f70f --- /dev/null +++ b/src/testframework/include/OpenMS/CONCEPT/ClassTestUtils.h @@ -0,0 +1,352 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: Timo Sachsenberg $ +// $Authors: Timo Sachsenberg $ +// -------------------------------------------------------------------------- + +#pragma once + +// Standard-library-only utilities owned by the class-test framework (no libOpenMS +// dependency). Two mirror libOpenMS behavior and must not drift: +// * detail::appendNumeric <-> StringUtils' appendNumeric (StringUtils.cpp): so +// TEST_EQUAL(std::string, ) formats exactly like StringUtils::toStr(). +// * writtenDigits <-> OpenMS::writtenDigits (OpenMS/CONCEPT/Types.h). + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace OpenMS +{ + namespace Internal + { + namespace ClassTest + { + /** + @brief Decimal digits to print for a value in TEST_REAL_SIMILAR reports. + + Framework copy of OpenMS::writtenDigits; class types convertible to double + (DataValue, ParamValue) print like a double. Diverges from the old version in + report precision only (never pass/fail): ParamValue prints at 15 not 6, and + long/unsigned long use their own digits10 not int's. + */ + template + constexpr int writtenDigits(const T& = T()) + { + using C = std::remove_cvref_t; + if constexpr (std::is_floating_point_v) + { + return std::numeric_limits::digits10; + } + else if constexpr (std::is_integral_v) + { + return std::numeric_limits::digits10; + } + else if constexpr (std::is_class_v && std::is_convertible_v) + { + return std::numeric_limits::digits10; + } + else + { + return 6; // default precision of an ostream (27.4.4.1), like OpenMS::writtenDigits + } + } + + /** + @brief Whether TEST_REAL_SIMILAR may convert a value of this type to double. + + True for floating-point and class types implicitly convertible to double + (DataValue, ParamValue); anything else is rejected at runtime, as before. + */ + template + constexpr bool isRealType(const T&) + { + using C = std::remove_cvref_t; + return std::is_floating_point_v || (std::is_class_v && std::is_convertible_v); + } + + namespace detail + { + /// Remove leading and trailing whitespace (' ', '\\t', '\\n', '\\r'), like StringUtils::trim. + inline void trim(std::string& s) + { + const char* ws = " \t\n\r"; + const size_t b = s.find_first_not_of(ws); + if (b == std::string::npos) + { + s.clear(); + return; + } + const size_t e = s.find_last_not_of(ws); + s = s.substr(b, e - b + 1); + } + + /// Split on a single character. Mirrors ListUtils::create(s, splitter): + /// an empty input gives an empty result; otherwise empty fields are kept and + /// fields are NOT trimmed. + inline std::vector split(const std::string& s, char splitter) + { + std::vector out; + if (s.empty()) return out; + size_t begin = 0; + for (size_t i = 0; i < s.size(); ++i) + { + if (s[i] == splitter) + { + out.emplace_back(s, begin, i - begin); + begin = i + 1; + } + } + out.emplace_back(s, begin, s.size() - begin); + return out; + } + + /// Join with a separator (display purposes only). + inline std::string join(const std::vector& v, const char* sep) + { + std::string out; + for (size_t i = 0; i < v.size(); ++i) + { + if (i) out += sep; + out += v[i]; + } + return out; + } + + /// Append a double/float/long double via std::to_chars. VERBATIM copy of + /// appendNumeric in StringUtils.cpp -- must format exactly like + /// StringUtils::toStr(); keep in sync, do not "improve". NaN -> "NaN", + /// inf -> "inf"/"-inf"; trailing zeros trimmed, one digit kept after '.'. + template + inline void appendNumeric(T value, std::string& target, int precision, bool fixed_format) + { + if (std::isnan(value)) { target += "NaN"; return; } + if (std::isinf(value)) { target += (value < T(0)) ? "-inf" : "inf"; return; } + char buf[64]; + std::to_chars_result fc; + + // Determine format: use scientific for extreme values or when fixed_format is requested + // but the value is too small/large for fixed notation + T abs_val = (value < 0) ? -value : value; + bool use_scientific = fixed_format + ? (abs_val != T(0) && (abs_val >= T(1e4) || abs_val < T(1e-2))) + : (abs_val != T(0) && (abs_val >= T(1e4) || abs_val < T(1e-2))); + + if (use_scientific) + { + if (fixed_format) + { + fc = std::to_chars(buf, buf + sizeof(buf), value, std::chars_format::scientific, 3); + } + else + { + // Shortest round-trip representation (no explicit precision): the unique shortest + // decimal that round-trips back to the same double, hence platform-independent. + fc = std::to_chars(buf, buf + sizeof(buf), value, std::chars_format::scientific); + } + } + else if (fixed_format) + { + fc = std::to_chars(buf, buf + sizeof(buf), value, std::chars_format::fixed, precision); + } + else + { + fc = std::to_chars(buf, buf + sizeof(buf), value, std::chars_format::fixed, precision); + } + + if (fc.ec == std::errc{}) + { + const char* end = fc.ptr; + + // For scientific notation: post-process "1.234e+04" into the historical "1.234e04" + const char* e_pos = reinterpret_cast(std::memchr(buf, 'e', end - buf)); + if (e_pos) + { + const char* e_orig = e_pos; // save before mantissa trimming + + // Trim trailing zeros from mantissa (before 'e') + const char* dot = reinterpret_cast(std::memchr(buf, '.', e_orig - buf)); + if (dot) + { + while (e_pos > dot + 1 && *(e_pos - 1) == '0') --e_pos; + if (e_pos == dot + 1) e_pos = dot + 2; // keep at least one digit after dot + } + target.append(buf, static_cast(e_pos - buf)); + if (!dot) target += ".0"; + target += 'e'; + + // Fix exponent format: "+04" -> "04" (remove '+', keep '-' and zero-padding). + const char* exp_start = e_orig + 1; // skip 'e' + if (exp_start < end && *exp_start == '+') + ++exp_start; // skip '+' + size_t exp_len = static_cast(end - exp_start); + target.append(exp_start, exp_len); + return; + } + + // For fixed notation: trim trailing zeros after decimal point + const char* dot = reinterpret_cast(std::memchr(buf, '.', end - buf)); + if (dot) + { + while (end > dot + 1 && *(end - 1) == '0') --end; + if (end == dot + 1) end = dot + 2; // keep at least one digit after dot + } + else if constexpr (std::is_floating_point_v) + { + target.append(buf, static_cast(end - buf)); + target += ".0"; + return; + } + target.append(buf, static_cast(end - buf)); + } + else + { + target += std::to_string(static_cast(value)); + } + } + + ///@name Stringification for TEST_EQUAL(std::string, x). Mirrors the + /// StringUtils::toStr overloads, so TEST_EQUAL(some_string, 114) compares + /// against "114" and doubles keep full precision. + //@{ + inline std::string toString(const std::string& s) { return s; } + inline std::string toString(std::string_view sv) { return std::string(sv); } + inline std::string toString(const char* s) { return std::string(s); } + inline std::string toString(char c) { return std::string(1, c); } + template + inline std::string toString(const T& v) + { + static_assert(std::is_arithmetic_v, + "TEST_EQUAL(std::string, x): x must be a string, character or number. " + "For other types, stringify explicitly in the test."); + std::string r; + if constexpr (std::is_same_v) + { + r = v ? "1" : "0"; + } + else if constexpr (std::is_floating_point_v) + { +#ifdef _LIBCPP_VERSION + if constexpr (std::is_same_v) // libc++ formats long double as double, see StringUtils::appendToStr(long double) + { + appendNumeric(static_cast(v), r, std::numeric_limits::digits10, false); + } + else +#endif + { + appendNumeric(v, r, std::numeric_limits::digits10, false); // full precision, like toStr(v, true) + } + } + else + { + char buf[32]; + auto [p, ec] = std::to_chars(buf, buf + sizeof(buf), v); + if (ec == std::errc{}) r.append(buf, p); + } + return r; + } + //@} + + /// Is 'os << t' well-formed? (this header's context + ADL, so vector is + /// streamable only via an ADL operator, e.g. ListUtilsIO.h for OpenMS types.) + template + concept Streamable = requires(std::ostream& os, const T& t) { os << t; }; + + /// Print a value in a TEST_EQUAL/TEST_NOT_EQUAL failure report: directly if + /// streamable; ranges as "[e1, e2, ...]" (like ListUtilsIO.h); else a placeholder. + template + void printValue(std::ostream& os, const T& v) + { + using C = std::remove_cvref_t; + if constexpr (Streamable) + { + os << v; + } + else if constexpr (std::ranges::input_range) + { + os << '['; + bool first = true; + for (const auto& e : v) + { + if (!first) os << ", "; + first = false; + using E = std::remove_cvref_t; + if constexpr (std::is_arithmetic_v) + { + os << toString(e); // numbers like ListUtilsIO (via toStr) + } + else if constexpr (Streamable) + { + os << e; + } + else + { + os << ""; + } + } + os << ']'; + } + else + { + os << ""; + } + } + + /// UTF-8 std::string -> std::filesystem::path. Copy of OpenMS::to_path + /// (OpenMS/SYSTEM/PathUtils.h): on Windows construct from u8string (not the + /// current code page), falling back to the native page for non-UTF-8 bytes. + inline std::filesystem::path to_path(const std::string& s) + { +#ifdef _WIN32 + try + { + return std::filesystem::path(std::u8string(reinterpret_cast(s.data()), s.size())); + } + catch (const std::system_error&) + { + return std::filesystem::path(s); + } +#else + return std::filesystem::path(std::u8string(reinterpret_cast(s.data()), s.size())); +#endif + } + } // namespace detail + + /** + @brief Stream wrapper printing a floating point value with full precision. + + The framework's own version of OpenMS::precisionWrapper (OpenMS/CONCEPT/PrecisionWrapper.h): + printing goes through detail::toString, i.e. the same format as StringUtils::toStr(x, true). + Used by TEST_FILE_SIMILAR's report. + */ + template + struct PrecisionWrapped + { + FloatingPointType value; + }; + + template + inline PrecisionWrapped precisionWrapper(const FloatingPointType value) + { + return PrecisionWrapped {value}; + } + + template + inline std::ostream& operator<<(std::ostream& os, const PrecisionWrapped& p) + { + os << detail::toString(p.value); + return os; + } + } // namespace ClassTest + } // namespace Internal +} // namespace OpenMS diff --git a/src/testframework/include/OpenMS/CONCEPT/FuzzyStringComparator.h b/src/testframework/include/OpenMS/CONCEPT/FuzzyStringComparator.h new file mode 100644 index 000000000..14990eb61 --- /dev/null +++ b/src/testframework/include/OpenMS/CONCEPT/FuzzyStringComparator.h @@ -0,0 +1,353 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: Timo Sachsenberg $ +// $Authors: Clemens Groepl, Stephan Aiche $ +// -------------------------------------------------------------------------- + +#pragma once + + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace OpenMS +{ + namespace Internal + { + namespace ClassTest + { + void testStringSimilar(const char * file, + int line, + const std::string & string_1, + const char * string_1_stringified, + const std::string & string_2, + const char * string_2_stringified); + + bool isFileSimilar(const std::string &, + const std::string &); + } + } + + /** + @brief Fuzzy comparison of strings, tolerates numeric differences. + */ + class FuzzyStringComparator + { + + friend void + Internal::ClassTest::testStringSimilar( + const char * file, + int line, + const std::string & string_1, + const char * string_1_stringified, + const std::string & string_2, + const char * string_2_stringified); + + friend bool + Internal::ClassTest::isFileSimilar(const std::string &, + const std::string &); + + /// %Internal exception class. + struct AbortComparison + { + }; + +public: + + ///@name the fabulous four + //@{ + + /// Constructor + FuzzyStringComparator(); + + /// Destructor + virtual + ~FuzzyStringComparator(); + + /// Copy constructor intentionally not implemented + FuzzyStringComparator(const FuzzyStringComparator & rhs); + + /// Assignment operator intentionally not implemented + FuzzyStringComparator & operator=(const FuzzyStringComparator & rhs); + + //@} + + /// Acceptable relative error (a number >= 1.0) + const double & getAcceptableRelative() const; + + /// Acceptable relative error (a number >= 1.0) + void setAcceptableRelative(const double rhs); + + /// Acceptable absolute difference (a number >= 0.0) + const double & getAcceptableAbsolute() const; + + /// Acceptable absolute difference (a number >= 0.0) + void setAcceptableAbsolute(const double rhs); + + /// White list. If both lines contain the same element from this list, they are skipped over. + const std::vector & getWhitelist() const; + + /// White list. If both lines contain the same element from this list, they are skipped over. + std::vector & getWhitelist(); + + /// White list. If both lines contain the same element from this list, they are skipped over. + void setWhitelist(const std::vector & rhs); + + /// Matched white list. If file 1 contains element 1 and file 2 contains element 2, they are skipped over. + void setMatchedWhitelist(const std::vector< std::pair >& rhs); + + /// Matched white list. If file 1 contains element 1 and file 2 contains element 2, they are skipped over. + const std::vector< std::pair >& getMatchedWhitelist() const; + + /** + @brief verbose level + + - 0 = very quiet mode (absolutely no output) + - 1 = quiet mode (no output unless differences detected) + - 2 = default (include summary at end) + - 3 = continue after errors + */ + const int & getVerboseLevel() const; + + /** + @brief verbose level + + - 0 = very quiet mode (absolutely no output) + - 1 = quiet mode (no output unless differences detected) + - 2 = default (include summary at end) + - 3 = continue after errors + */ + void setVerboseLevel(const int rhs); + + /** + @brief get tab width (for column numbers) + */ + const int & getTabWidth() const; + + /** + @brief set tab width (for column numbers) + */ + void setTabWidth(const int rhs); + + /** + @brief get first column (for column numbers) + */ + const int & getFirstColumn() const; + + /** + @brief set first column (for column numbers) + */ + void setFirstColumn(const int rhs); + + /** + @brief Log output is written to this destination. + + The default is std::cout. Use std::ostringstream etc. to save the output + in a string. + */ + std::ostream & getLogDestination() const; + + /** + @brief Log output is written to this destination. + + The default is std::cout. Use std::ostringstream etc. to save the output + in a string. + + @internal There seems to be an issue with this under Windows, see comment + in FuzzyStringComparator_test.cpp + + */ + void setLogDestination(std::ostream & rhs); + + /** + @brief Compare two strings. + + This compares all lines of the input. + + @return true in case of no differences found + */ + bool compareStrings(std::string const & lhs, std::string const & rhs); + + /** + @brief Compare two streams of input. + + This compares all lines of the input. Intended to be used for file + streams. + + @return true in case of no differences found + */ + bool compareStreams(std::istream & input_1, std::istream & input_2); + + /** + @brief Simple diff-like application to compare two input files. + Numeric differences are tolerated up to a certain ratio or absolute + difference. + + where + @param[in] filename_1 first input file + @param[in] filename_2 second input file + @return true in case of no differences found + + @sa ratio_max_allowed_ + @sa absdiff_max_allowed_ + @sa verbose_level_ + */ + bool compareFiles(const std::string & filename_1, + const std::string & filename_2); + +protected: + + /** + @brief Compare two lines of input. + + This implements the core functionality. Intended to be used for a single + line of input. + + @return true (non-zero) in case of success + */ + bool compareLines_(std::string const & line_str_1, std::string const & line_str_2); + + /// Report good news. + void reportSuccess_() const; + + /// Report bad news. + /// @exception AbortComparison + void reportFailure_(char const * const message) const; + + /// Write info about hits in the whitelist + void writeWhitelistCases_(const std::string & prefix) const; + + /// read the next line in input stream, skipping over empty lines + /// and lines consisting of whitespace only + void readNextLine_(std::istream & input_stream, std::string & line_string, int & line_number) const; + + /// opens and checks an input file stream std::ifstream + bool openInputFileStream_(const std::string & filename, std::ifstream & input_stream) const; + + /// Log and results output goes here + std::ostream * log_dest_; + + /// Name of first input e.g., filename + std::string input_1_name_; + /// Name of second input e.g., filename + std::string input_2_name_; + + /// Stores information about the current input line (i.e., stream for the line and the current position in the stream) + struct InputLine + { + std::stringstream line_; + std::ios::pos_type line_position_; + + InputLine(); + + /// Initialize the input line to the passed string + void setToString(const std::string & s); + + /// Save current position of the stream + void updatePosition(); + + /// Resets the stream to the last saved position + void seekGToSavedPosition(); + + /** + @brief Convert to bool + + The function indicates success when none of the error flags (either failbit or badbit of the nested std::stringstream) are set. + + @return False on error, true otherwise. + */ + bool ok() const; + }; + + InputLine input_line_1_; + InputLine input_line_2_; + + int line_num_1_; + int line_num_2_; + + int line_num_1_max_; + int line_num_2_max_; + + std::string line_str_1_max_; + std::string line_str_2_max_; + + /// Maximum ratio of numbers allowed, see @em ratio_max_. + double ratio_max_allowed_; + + /// Maximum ratio of numbers observed so far, see @em ratio_max_allowed_. + double ratio_max_; + + /// Maximum absolute difference of numbers allowed, see @em absdiff_max_. + double absdiff_max_allowed_; + + /// Maximum difference of numbers observed so far, see @em absdiff_max_allowed_. + double absdiff_max_; + + /// Stores information about characters, numbers, and white spaces loaded from the InputStream + struct StreamElement_ + { + double number; + unsigned char letter; + bool is_number; + bool is_space; + + StreamElement_(); + + /// reset all elements of the element to default value + void reset(); + + /// Read the next element from an InputLine and update the InputLine accordingly + /// The @p str_line contains the same data as the stream, since it saves some forth-and-back conversion internally + /// TODO: avoid streams all together (slow, and no random access, required by boost::qi) at some point + void fillFromInputLine(InputLine& input_line, const std::string& str_line); + }; + + /// Stores information about characters, numbers, and white spaces loaded from the first input stream + StreamElement_ element_1_; + /// Stores information about characters, numbers, and white spaces loaded from the second input stream + StreamElement_ element_2_; + + /// Wrapper for the prefix information computed for the failure report + struct PrefixInfo_ + { + std::string prefix; + std::string prefix_whitespaces; + int line_column; + + PrefixInfo_(const InputLine & input_line, const int tab_width_, const int first_column_); + }; + + bool is_absdiff_small_; + + int verbose_level_; + int tab_width_; + int first_column_; + + /** + @brief Has comparison been successful so far? Note: this flag is + changed in reportFailure_(); + */ + bool is_status_success_; + + /// use a prefix when reporting + bool use_prefix_; + + /// Whitelist + std::vector whitelist_; + /// Occurrences of whitelist entries + std::map whitelist_cases_; + + /// Alternative Whitelist + std::vector< std::pair > matched_whitelist_; + }; // class FuzzyStringComparator + +} //namespace OpenMS + diff --git a/src/testframework/include/OpenMS/CONCEPT/MacrosTest.h b/src/testframework/include/OpenMS/CONCEPT/MacrosTest.h new file mode 100644 index 000000000..752d03e3e --- /dev/null +++ b/src/testframework/include/OpenMS/CONCEPT/MacrosTest.h @@ -0,0 +1,59 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: Hannes Roest $ +// $Authors: Philippe M. Groarke, Hannes Roest $ +// -------------------------------------------------------------------------- + +// See https://philippegroarke.com/posts/2018/easy_defensive_programming/ +// https://github.com/p-groarke/defensive_cpp + +#pragma once +#include + +namespace OpenMS +{ + // no constexpr lambdas in C++11, therefore we have to use functions + namespace Test + { + template + constexpr bool fulfills_rule_of_5() + { + static_assert(std::is_destructible::value, "T : must be destructible"); + static_assert(std::is_copy_constructible::value, "T : must be copy constructible"); + static_assert(std::is_move_constructible::value, "T : must be move constructible"); + static_assert(std::is_copy_assignable::value, "T : must be copy assignable"); + static_assert(std::is_move_assignable::value, "T : must be move assignable"); + + return std::is_destructible::value && + std::is_copy_constructible::value && + std::is_move_constructible::value && + std::is_copy_assignable::value && + std::is_move_assignable::value; + } + + template + constexpr bool fulfills_rule_of_6() + { + static_assert(fulfills_rule_of_5(), "T : must fulfill rule of 5"); + static_assert(std::is_default_constructible::value, "T : must be default constructible"); + + return fulfills_rule_of_5() && + std::is_default_constructible::value; + } + + template + constexpr bool fulfills_fast_vector() + { + static_assert( (std::is_trivially_copy_constructible::value && std::is_trivially_destructible::value) || + std::is_nothrow_move_constructible::value, + "T : doesn't fulfill fast vector (trivially copy constructible " \ + "and trivially destructible, or nothrow move constructible)"); + + return (std::is_trivially_copy_constructible::value && std::is_trivially_destructible::value) || + std::is_nothrow_move_constructible::value; + }; + } +} + diff --git a/src/testframework/source/CONCEPT/ClassTest.cpp b/src/testframework/source/CONCEPT/ClassTest.cpp new file mode 100644 index 000000000..7cc89b359 --- /dev/null +++ b/src/testframework/source/CONCEPT/ClassTest.cpp @@ -0,0 +1,692 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: Timo Sachsenberg $ +// $Authors: Marc Sturm, Clemens Groepl, Chris Bielow, Timo Sachsenberg $ +// -------------------------------------------------------------------------- + +#include + +#include + +// Std-only (see ClassTest.h); OpenMS behavior is registered by the OpenMSTestSupport +// target (src/tests/class_tests/source/OpenMSTestSupport.cpp). +#include // std::getenv, exit +#include // std::exception, current-exception handling +#include +#include +#include +#include +#include +#include // std::error_code + +namespace OpenMS::Internal::ClassTest +{ + bool all_tests = true; + bool equal_files; + bool newline = false; + bool test = true; + bool this_test; + char line_buffer[65536]; + const char* version_string = nullptr; + double absdiff = 0.; + double absdiff_max = 0.; + double absdiff_max_allowed = 1E-5; + double ratio = 1.; + double ratio_max = 1.; + double ratio_max_allowed = 1. + 1E-5; + int exception = 0; + int line_num_1_max = -1; + int line_num_2_max = -1; + int start_section_line = 0; + int test_count = 0; + int test_line = 0; + int verbose = 0; + std::ifstream infile; + std::ifstream templatefile; + std::string add_message; + std::string exception_message; + std::string exception_name; + std::string fuzzy_message; + std::string test_name; + std::vector tmp_file_list; + std::vector failed_lines_list; + std::vector whitelist; + + namespace + { + // Registered exception translators (see ClassTest.h). A small fixed array is + // plenty: one per library-under-test project. + constexpr int max_translators = 8; + ExceptionTranslator translators[max_translators] = {}; + int translator_count = 0; + } + + namespace + { + bool precondition_tests_enabled = false; + } + + bool preconditionTestsEnabled() + { + return precondition_tests_enabled; + } + + void setPreconditionTestsEnabled(bool enabled) + { + precondition_tests_enabled = enabled; + } + + void registerExceptionTranslator(ExceptionTranslator translator) + { + if (translator == nullptr) return; + for (int i = 0; i < translator_count; ++i) + { + if (translators[i] == translator) return; // idempotent + } + if (translator_count < max_translators) + { + translators[translator_count++] = translator; + } + } + + // Call only from inside a catch block: the translators and the fallback use a + // bare `throw;`, which calls std::terminate if no exception is active. + std::string describeCaughtException() + { + for (int i = 0; i < translator_count; ++i) + { + std::string description; + if (translators[i](description)) return description; + } + try + { + throw; // rethrow the exception currently being handled + } + catch (const std::exception& e) + { + return std::string("std::exception - Message: ") + e.what(); + } + catch (...) + { + return "unidentified exception - No message."; + } + } + + void mainInit(const char* version, const char* class_name, int argc, const char* argv0) + { + // if env var "OPENMS_TEST_VERBOSE=True" enable output of successfull line + char* pverbose = std::getenv("OPENMS_TEST_VERBOSE"); + if (pverbose != nullptr) + { + if (std::string(pverbose) == "True") TEST::verbose = 2; + } + + TEST::version_string = version; + + if (argc > 1) + { + std::cerr + << "This is " << argv0 << ", the test program for the\n" + << class_name << " class.\n" + "\n" + "On successful operation it returns PASSED,\n" + "otherwise FAILED is printed.\n"; + exit(1); + } + } + + void filesEqual(int line, const char* filename, const char* templatename, const char* filename_stringified, const char* templatename_stringified) + { + ++TEST::test_count; + TEST::test_line = line; + + TEST::equal_files = true; + TEST::infile.open(filename, std::ios::in); + TEST::templatefile.open(templatename, std::ios::in); + + if (TEST::infile.good() && TEST::templatefile.good()) + { + std::string TEST_FILE__template_line; + std::string TEST_FILE__line; + + while (TEST::infile.good() && TEST::templatefile.good()) + { + TEST::templatefile.getline(TEST::line_buffer, 65535); + TEST_FILE__template_line = TEST::line_buffer; + TEST::infile.getline(TEST::line_buffer, 65535); + TEST_FILE__line = TEST::line_buffer; + detail::trim(TEST_FILE__template_line); // remove leading and trailing whitespaces (ignore CR/LF line endings on Unix) + detail::trim(TEST_FILE__line); // remove leading and trailing whitespaces (ignore CR/LF line endings on Unix) + if (TEST_FILE__template_line != TEST_FILE__line) + { + TEST::equal_files = false; + TEST::initialNewline(); + stdcout << " TEST_FILE_EQUAL: line mismatch:\n got: '" + << TEST_FILE__line << "'\n expected: '" + << TEST_FILE__template_line << "'\n"; + } + } + } + else + { + TEST::equal_files = false; + { + TEST::initialNewline(); + stdcout << " + line " + << line + << ": TEST_FILE_EQUAL(" + << filename_stringified + << ", " + << templatename_stringified; + stdcout << ") : " + << " cannot open file: \""; + if (!TEST::infile.good()) + { + stdcout << filename << "\" (input file) "; + } + if (!TEST::templatefile.good()) + { + stdcout << templatename << "\" (template file) "; + } + stdcout << "'\n"; + } + } + TEST::infile.close(); + TEST::templatefile.close(); + TEST::infile.clear(); + TEST::templatefile.clear(); + + TEST::this_test = TEST::equal_files; + TEST::test = TEST::test && TEST::this_test; + { + TEST::initialNewline(); + if (TEST::this_test) + { + if (TEST::verbose > 1) + { + stdcout << " + line " + << line + << ": TEST_FILE_EQUAL(" + << filename_stringified + << ", " + << templatename_stringified + << "): true"; + } + } + else + { + stdcout << " - line " + << line + << ": TEST_FILE_EQUAL(" + << filename_stringified + << ", " + << templatename_stringified + << "): false (different files: " + << filename + << " " + << templatename + << " )\n"; + TEST::failed_lines_list.push_back(TEST::test_line); + } + } + } + + void removeTempFiles() + { + for (std::size_t i = 0; i < TEST::tmp_file_list.size(); ++i) + { + // NEW_TMP_FILE only registers the *name*; the test may never have created + // the file, so a missing file is fine (like OpenMS::File::remove). + const std::filesystem::path p = detail::to_path(TEST::tmp_file_list[i]); + std::error_code ec; + if (std::filesystem::exists(p, ec) && !std::filesystem::remove(p, ec)) + { + stdcout << "Warning: unable to remove temporary file '" + << TEST::tmp_file_list[i] + << "'" + << '\n'; + } + } + } + + void + setWhitelist(const char* const /* file */, const int line, + const std::string& whitelist_) + { + TEST::whitelist = detail::split(whitelist_, ','); + + if ((TEST::verbose > 1) || (!TEST::this_test && (TEST::verbose > 0))) + { + TEST::initialNewline(); + stdcout << " + line " << line << ": WHITELIST(\"" << whitelist_ + << "\"): whitelist is: " << detail::join(TEST::whitelist, ", ") << '\n'; + } + return; + } + + void + initialNewline() + { + if (!newline) + { + newline = true; + std::cout << '\n'; + } + return; + } + + void + printWithPrefix(const std::string& text, const int marked) + { + std::istringstream is(text); + std::string line; + int line_number = 0; + while (std::getline(is, line)) + { + ++line_number; + std::cout << (line_number == marked ? " # :|: " : " :|: ") << line << '\n'; + } + return; + } + + std::string + createTmpFileName(const std::string& file, int line, const std::string& extension) + { + std::filesystem::path fp(file); + std::string filename = (std::string(fp.stem().string())) + '_' + std::to_string(line) + ".tmp" + extension; + TEST::tmp_file_list.push_back(filename); + TEST::initialNewline(); + stdcout << " creating new temporary filename '" + << filename + << "' (line " + << __LINE__ + << ")\n"; + return filename; + } + + void testRealSimilar(const char* /*file*/, int line, + long double number_1, const char* number_1_stringified, bool number_1_is_realtype, int number_1_written_digits, + long double number_2, const char* number_2_stringified, bool /* number_2_is_realtype */, int number_2_written_digits + ) + { + TEST::initialNewline(); + ++TEST::test_count; + TEST::test_line = line; + TEST::this_test = true; + if (!number_1_is_realtype) + { + TEST::this_test = false; + stdcout << " - line " << line << ':' + << "TEST_REAL_SIMILAR(" << number_1_stringified << ',' + << number_2_stringified << "):" + " argument " << number_1_stringified + << " does not have a floating point type! Go fix your code!" + << '\n'; + failed_lines_list.push_back(line); + } + TEST::test = TEST::test && TEST::this_test; + if (TEST::this_test) + { + TEST::this_test = TEST::isRealSimilar(number_1, number_2); + TEST::test = TEST::test && TEST::this_test; + { + if (TEST::this_test) + { + if (TEST::verbose > 1) + { + stdcout << " + line " << line << ": TEST_REAL_SIMILAR(" + << number_1_stringified << ',' << number_2_stringified + << "): got " << std::setprecision(number_1_written_digits) + << number_1 << ", expected " + << std::setprecision(number_2_written_digits) << number_2 << '\n'; + } + } + else + { + stdcout << " - line " << TEST::test_line + << ": TEST_REAL_SIMILAR(" << number_1_stringified << ',' + << number_2_stringified << "): got " + << std::setprecision(number_1_written_digits) << number_1 + << ", expected " + << std::setprecision(number_2_written_digits) << number_2 + << " (absolute: " << TEST::absdiff << " [" + << TEST::absdiff_max_allowed << "], relative: " + << TEST::ratio << " [" << TEST::ratio_max_allowed + << "], message: \"" << TEST::fuzzy_message << "\"\n"; + failed_lines_list.push_back(line); + } + } + } + } + + bool isRealSimilar(long double number_1, long double number_2) + { + // Note: The original version of the stuff below was copied from + // FuzzyStringComparator and then heavily modified for ClassTest. + // But still the case distinctions should be similar. + + absdiff = 0.; + ratio = 0.; + fuzzy_message.clear(); + + if (std::isnan(number_1)) + { + fuzzy_message = "number_1 is nan"; + return false; + } + if (std::isnan(number_2)) + { + fuzzy_message = "number_2 is nan"; + return false; + } + + // check if absolute difference is small + absdiff = number_1 - number_2; + if (absdiff < 0) + { + absdiff = -absdiff; + } + if (absdiff > absdiff_max) + { + absdiff_max = absdiff; + } + // If absolute difference is small, large relative errors will be + // tolerated in the cases below. But a large absolute difference is + // not an error, if relative error is small. We do not jump out of + // the case distinction here because we want to record the relative + // error even in case of a successful comparison. + bool is_absdiff_small = (absdiff <= absdiff_max_allowed); + + if (!number_1) // number_1 is zero + { + if (!number_2) // both numbers are zero + { + fuzzy_message = "both numbers are zero"; + return true; + } + else + { + if (!is_absdiff_small) + { + fuzzy_message = "number_1 is zero, but number_2 is not small"; + return false; + } + else + { + fuzzy_message = "number_1 is zero, number_2 is small"; + return true; + } + } + } + else // number_1 is not zero + { + if (!number_2) + { + if (!is_absdiff_small) + { + fuzzy_message = "number_1 is not zero, but number_2 is"; + return false; + } + else + { + fuzzy_message = "number_2 is zero, but number_1 is not small"; + return true; + } + } + else // both numbers are not zero + { + ratio = number_1 / number_2; + if (ratio < 0.) + { + if (!is_absdiff_small) + { + fuzzy_message + = "numbers have different signs and difference is not small"; + return false; + } + else + { + fuzzy_message + = "numbers have different signs, but difference is small"; + return true; + } + } + else // ok, numbers have same sign, but we still need to check their ratio + { + if (ratio < 1.) // take reciprocal value + { + ratio = 1. / ratio; + } + // by now, we are sure that ratio >= 1 + if (ratio > ratio_max) // update running max + { + ratio_max = ratio; + } + if (ratio > ratio_max_allowed) + { + if (!is_absdiff_small) + { + fuzzy_message = "ratio of numbers is large"; + return false; + } + else + { + fuzzy_message + = "ratio of numbers is large, but numbers are small"; + return true; + } + } + else + { + fuzzy_message = "ratio of numbers is small"; + return true; + } + } + } + } + } + + void testStringEqual(const char* /*file*/, int line, + const std::string& string_1, + const char* string_1_stringified, + const std::string& string_2, + const char* string_2_stringified) + { + ++test_count; + test_line = line; + this_test = (string_1 == string_2); + test = test && this_test; + { + initialNewline(); + if (this_test) + { + if (TEST::verbose > 1) + { + stdcout << " + line " << line << ": TEST_STRING_EQUAL(" + << string_1_stringified << ',' << string_2_stringified + << "): got \"" << string_1 << "\", expected \"" << string_2 + << "\"\n"; + } + } + else + { + stdcout << " - line " << line << ": TEST_STRING_EQUAL(" + << string_1_stringified << ',' << string_2_stringified + << "): got \"" << string_1 << "\", expected \"" << string_2 + << "\"\n"; + failed_lines_list.push_back(line); + } + } + } + + void testStringSimilar(const char* /*file*/, int line, + const std::string& string_1, + const char* string_1_stringified, + const std::string& string_2, + const char* string_2_stringified + ) + { + ++TEST::test_count; + TEST::test_line = line; + + TEST::fuzzy_message.clear(); + FuzzyStringComparator fsc; + fsc.setAcceptableAbsolute(absdiff_max_allowed); + fsc.setAcceptableRelative(ratio_max_allowed); + fsc.setVerboseLevel(2); + fsc.setWhitelist(whitelist); + std::ostringstream os; + fsc.setLogDestination(os); + fsc.use_prefix_ = true; + + TEST::this_test = fsc.compareStrings(string_1, string_2); + + TEST::fuzzy_message = os.str(); + TEST::absdiff = fsc.absdiff_max_; + TEST::ratio = fsc.ratio_max_; + TEST::line_num_1_max = fsc.line_num_1_max_; + TEST::line_num_2_max = fsc.line_num_2_max_; + + TEST::test = TEST::test && TEST::this_test; + + TEST::initialNewline(); + if (TEST::this_test) + { + if (TEST::verbose > 1) + { + stdcout << " + line " << line << ": TEST_STRING_SIMILAR(" + << string_1_stringified << ',' << string_2_stringified << "): " + "absolute: " << TEST::absdiff << " (" << TEST::absdiff_max_allowed + << "), relative: " << TEST::ratio << " (" + << TEST::ratio_max_allowed << ") +\n"; + stdcout << "got:\n"; + TEST::printWithPrefix(string_1, TEST::line_num_1_max); + stdcout << "expected:\n"; + TEST::printWithPrefix(string_2, TEST::line_num_2_max); + } + } + else + { + stdcout << " - line " << TEST::test_line + << ": TEST_STRING_SIMILAR(" << string_1_stringified << ',' + << string_2_stringified << ") ... -\n" + "got:\n"; + TEST::printWithPrefix(string_1, TEST::line_num_1_max); + stdcout << "expected:\n"; + TEST::printWithPrefix(string_2, TEST::line_num_2_max); + stdcout << "message: \n"; + stdcout << TEST::fuzzy_message; + failed_lines_list.push_back(line); + } + } + + bool + isFileSimilar(const std::string& filename_1, + const std::string& filename_2) + { + fuzzy_message.clear(); + FuzzyStringComparator fsc; + fsc.setAcceptableAbsolute(absdiff_max_allowed); + fsc.setAcceptableRelative(ratio_max_allowed); + fsc.setVerboseLevel(2); + fsc.setWhitelist(whitelist); + std::ostringstream os; + fsc.setLogDestination(os); + fsc.use_prefix_ = true; + + bool result = fsc.compareFiles(filename_1, filename_2); + + fuzzy_message = os.str(); + absdiff = fsc.absdiff_max_; + ratio = fsc.ratio_max_; + line_num_1_max = fsc.line_num_1_max_; + line_num_2_max = fsc.line_num_2_max_; + + return result; + } + + + void printLastException(std::ostream& out) + { + TEST::this_test = false; + TEST::test = false; + TEST::all_tests = false; + TEST::initialNewline(); + // A registered translator (e.g. for OpenMS exceptions) yields the complete + // one-line description; the fallbacks below reproduce the framework's + // historical report format byte for byte. + for (int i = 0; i < translator_count; ++i) + { + std::string description; + if (translators[i](description)) + { + out << "Error: Caught unexpected " << description << '\n'; + return; + } + } + try + { + throw; // rethrow the exception currently being handled + } + catch (const std::exception& e) + { + out << "Error: Caught unexpected std::exception\n"; + out << " - Message: " << e.what() << '\n'; + } + catch (...) + { + out << "Error: Caught unidentified and unexpected exception - No message.\n"; + } + } + + int endTestPostProcess(std::ostream& out) + { + if (TEST::verbose == 0) + { + out << "Output of successful tests were suppressed. Set the environment variable 'OPENMS_TEST_VERBOSE=True' to enable them.\n"; + } /* check for exit code */ + if (!TEST::all_tests) + { + out << "FAILED\n"; + if (TEST::add_message != "") + out << "Message: " << TEST::add_message << '\n'; + out << "Failed lines: "; + for (std::size_t i = 0; i < TEST::failed_lines_list.size(); ++i) + { + out << TEST::failed_lines_list[i] << " "; + } + out << '\n'; + return 1; + } + else + { /* remove temporary files*/ + TEST::removeTempFiles(); + out << "PASSED"; + if (TEST::add_message != "") + out << " (" << TEST::add_message << ")"; + out << '\n'; + return 0; + } + } + + void endSectionPostProcess(std::ostream& out, const int line) + { + TEST::all_tests = TEST::all_tests && TEST::test; + if (TEST::test) + { + out << ": passed\n"; + } + else + { + out << ": failed\n"; + } + if (TEST::test_count == 0) + { + if (std::string(TEST::test_name).find('~') != std::string::npos) + out << "Warning: no subtests performed in '" << TEST::test_name << "' (line " << line << ")!\n"; + } + stdcout << '\n'; + } + } diff --git a/src/testframework/source/CONCEPT/FuzzyStringComparator.cpp b/src/testframework/source/CONCEPT/FuzzyStringComparator.cpp new file mode 100644 index 000000000..ae824f4a4 --- /dev/null +++ b/src/testframework/source/CONCEPT/FuzzyStringComparator.cpp @@ -0,0 +1,1031 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: Timo Sachsenberg $ +// $Authors: Clemens Groepl, Stephan Aiche $ +// -------------------------------------------------------------------------- + +#include +// Std-only (see ClassTest.h): the libOpenMS utilities this file used are copied +// into the unnamed namespace below, each pointing at its source of truth. Include +// every std header used -- transitive availability differs (libc++ vs libstdc++). +#include // isspace +#include // errno in the strtod fallback +#include // std::from_chars +#include +#include // std::strtod/strtof in the libc++ fallback +#include // std::memcpy +#include +#include +#include +#include +#include +#include // std::numeric_limits +#include // std::unique_ptr in the libc++ fallback +#include // std::streambuf in getLine +#include +#include // std::is_same_v in the libc++ from_chars fallback + +// #define DEBUG_FUZZY + +namespace +{ + // Convert a UTF-8 std::string to std::filesystem::path safely on all platforms. + // Copy of to_path() from OpenMS/SYSTEM/PathUtils.h; see there for the rationale + // (Windows code pages vs. UTF-8). + std::filesystem::path to_path(const std::string& s) + { +#ifdef _WIN32 + try + { + return std::filesystem::path(std::u8string(reinterpret_cast(s.data()), s.size())); + } + catch (const std::system_error&) + { + return std::filesystem::path(s); + } +#else + return std::filesystem::path(std::u8string(reinterpret_cast(s.data()), s.size())); +#endif + } + + // Local copy of OpenMS::TextFile::getLine() (FORMAT layer): reads a line and strips any + // line ending (\n, \r\n, \r), so otherwise equal lines compare quickly across platforms. + // Kept here so the test framework library does not depend on the FORMAT layer. + std::istream& getLine(std::istream& is, std::string& t) + { + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + std::istream::sentry se(is, true); + if (!se) + { // the stream has an error + return is; + } + + std::streambuf* sb = is.rdbuf(); + + for (;;) + { + int c = sb->sbumpc(); // get and advance to next char + switch (c) { + case '\n': + return is; + case '\r': // consume next '\n' (if any) and return + if (sb->sgetc() == '\n') // peek current char + { + sb->sbumpc(); // consume it + } + return is; + case std::streambuf::traits_type::eof(): + is.setstate(std::ios::eofbit); // still allows: while(is == true) + if (t.empty()) + { // only if we just started a new line, we set the is.fail() == true, ie. is == false + is.setstate(std::ios::badbit); + } + return is; + default: + t += (char)c; + } + } + } + + // Mirrors OpenMS::File::absolutePath() (SYSTEM layer), duplicated here so the test + // framework does not depend on it; only used to print readable paths in the failure report. + std::string absolutePath(const std::string& file) + { + if (file.empty()) return std::filesystem::current_path().generic_string(); +#ifdef _WIN32 + // On Windows, paths starting with '/' are treated as absolute, + // but fs::absolute() prepends the current drive letter. Preserve this behavior. + if (file[0] == '/') return file; +#endif + return std::filesystem::absolute(to_path(file)).generic_string(); + } + + // Return the last 'length' characters, like StringUtils::suffix() (without the + // exception on overlong requests -- the call sites below always pass a valid length). + std::string suffix(const std::string& s, std::size_t length) + { + if (length > s.size()) return s; + return s.substr(s.size() - length, length); + } + + // Return the first 'length' characters, like StringUtils::prefix() (without the + // exception on overlong requests -- the call site below always passes a valid length). + std::string prefixOf(const std::string& s, std::size_t length) + { + if (length > s.size()) return s; + return s.substr(0, length); + } + + // --------------------------------------------------------------------------- + // Number parsing: VERBATIM copies of the std::from_chars-based helpers in + // src/openms/source/DATASTRUCTURES/StringUtils.cpp (tryParseNaN, the libc++ + // fallback, parseFloat, StringUtilsHelper::extractDouble). The fuzzy comparison + // must accept exactly the same number tokens as the library writes/reads. + // Keep the two in sync; do not "improve" this copy. + // --------------------------------------------------------------------------- + + template + bool tryParseNaN(const char*& p, const char* end, T& target) + { + if (p == end) return false; + if (*p != 'n' && *p != 'N') return false; + + const char* start = p; + if ((end - start) >= 3 && + (start[0] == 'n' || start[0] == 'N') && + (start[1] == 'a' || start[1] == 'A') && + (start[2] == 'n' || start[2] == 'N')) + { + p += 3; + if (p != end && *p == '(') + { + ++p; + while (p != end && *p != ')') ++p; + if (p == end) { p = start; return false; } + ++p; + } + target = std::numeric_limits::quiet_NaN(); + return true; + } + return false; + } + + // libc++ (e.g. Apple Clang) does not implement the floating-point overloads of + // std::from_chars -- only the integer ones. Provide a strtod/strtof based + // fallback with matching semantics (general format, no leading whitespace/'+'). +#if defined(_LIBCPP_VERSION) + #define OPENMS_TESTFRAMEWORK_NO_FLOAT_FROM_CHARS 1 +#endif + +#ifdef OPENMS_TESTFRAMEWORK_NO_FLOAT_FROM_CHARS + template + std::from_chars_result fromCharsFloat(const char* first, const char* last, T& value) + { + std::from_chars_result res{first, std::errc::invalid_argument}; + // std::from_chars(general) rejects leading whitespace and a leading '+'; mimic that + // so behaviour is identical to the std::from_chars path on other platforms. + if (first == last || *first == '+' || *first == ' ' || *first == '\t' || + *first == '\n' || *first == '\r' || *first == '\f' || *first == '\v') + { + return res; + } + + // strtod/strtof need a null-terminated string and may read past 'last', so copy the + // range into a bounded buffer first. + const std::size_t n = static_cast(last - first); + char stackbuf[64]; + std::unique_ptr heapbuf; + char* buf = stackbuf; + if (n + 1 > sizeof(stackbuf)) + { + heapbuf = std::make_unique(n + 1); + buf = heapbuf.get(); + } + std::memcpy(buf, first, n); + buf[n] = '\0'; + + errno = 0; + char* parse_end = nullptr; + if constexpr (std::is_same_v) + value = std::strtof(buf, &parse_end); + else + value = std::strtod(buf, &parse_end); + + if (parse_end == buf) // nothing consumed + { + return res; // invalid_argument, ptr == first + } + res.ptr = first + (parse_end - buf); + // strtod/strtof set errno==ERANGE on both overflow (returns +/-HUGE_VAL) and + // underflow (returns a representable subnormal or 0). std::from_chars only reports + // result_out_of_range on overflow, so match that: accept underflow as success. + res.ec = (errno == ERANGE && std::isinf(value)) ? std::errc::result_out_of_range : std::errc{}; + return res; + } +#endif + + /// Thin wrapper dispatching to std::from_chars or the libc++ fallback above. + template + inline std::from_chars_result parseFloat(const char* first, const char* last, T& value) + { +#ifdef OPENMS_TESTFRAMEWORK_NO_FLOAT_FROM_CHARS + return fromCharsFloat(first, last, value); +#else + return std::from_chars(first, last, value); +#endif + } + + bool extractDouble(const char*& begin, const char* end, double& target) + { + if (tryParseNaN(begin, end, target)) + return true; + + const char* start = begin; + if (start != end && *start == '+') ++start; + + auto fc = parseFloat(start, end, target); + if (fc.ec == std::errc{}) + { + begin = fc.ptr; + return true; + } + return false; + } + + /// Overload for std::string iterators (converts to const char* internally), + /// like StringUtils::extractDouble. The end pointer is computed from the range + /// length so the past-the-end iterator is never dereferenced (which debug + /// iterators assert on), and an empty range parses to false without touching + /// either iterator. + bool extractDouble(std::string::const_iterator& begin, const std::string::const_iterator& end, double& target) + { + if (begin == end) return false; + const char* const p_start = &(*begin); + const char* p = p_start; + const char* const e = p_start + (end - begin); + bool ok = extractDouble(p, e, target); + begin += (p - p_start); // advance iterator by number of consumed chars (MSVC iterators cannot be built from a raw pointer) + return ok; + } +} + +namespace OpenMS +{ + + FuzzyStringComparator::FuzzyStringComparator() : + log_dest_(&std::cout), + input_1_name_("input_1"), + input_2_name_("input_2"), + input_line_1_(), + input_line_2_(), + line_num_1_(0), + line_num_2_(0), + line_num_1_max_(-1), + line_num_2_max_(-1), + line_str_1_max_(), + line_str_2_max_(), + ratio_max_allowed_(1.0), + ratio_max_(1.0), + absdiff_max_allowed_(0.0), + absdiff_max_(0.0), + element_1_(), + element_2_(), + is_absdiff_small_(false), + verbose_level_(2), + tab_width_(8), + first_column_(1), + is_status_success_(true), + use_prefix_(false), + whitelist_(), + whitelist_cases_() + { + } + + FuzzyStringComparator::~FuzzyStringComparator() = default; + + const double& FuzzyStringComparator::getAcceptableRelative() const + { + return ratio_max_allowed_; + } + + void FuzzyStringComparator::setAcceptableRelative(const double rhs) + { + this->ratio_max_allowed_ = rhs; + if (ratio_max_allowed_ < 1.0) + { + ratio_max_allowed_ = 1 / ratio_max_allowed_; + } + + } + + const double& FuzzyStringComparator::getAcceptableAbsolute() const + { + return absdiff_max_allowed_; + } + + void FuzzyStringComparator::setAcceptableAbsolute(const double rhs) + { + this->absdiff_max_allowed_ = rhs; + if (absdiff_max_allowed_ < 0.0) + { + absdiff_max_allowed_ = -absdiff_max_allowed_; + } + } + + const std::vector& FuzzyStringComparator::getWhitelist() const + { + return whitelist_; + } + + std::vector& FuzzyStringComparator::getWhitelist() + { + return whitelist_; + } + + void FuzzyStringComparator::setWhitelist(const std::vector& rhs) + { + whitelist_ = rhs; + } + + void FuzzyStringComparator::setMatchedWhitelist(const std::vector< std::pair >& rhs) + { + matched_whitelist_ = rhs; + } + + const std::vector< std::pair >& FuzzyStringComparator::getMatchedWhitelist() const + { + return matched_whitelist_; + } + + const int& FuzzyStringComparator::getVerboseLevel() const + { + return verbose_level_; + } + + void FuzzyStringComparator::setVerboseLevel(const int rhs) + { + this->verbose_level_ = rhs; + } + + const int& FuzzyStringComparator::getTabWidth() const + { + return tab_width_; + } + + void FuzzyStringComparator::setTabWidth(const int rhs) + { + this->tab_width_ = rhs; + } + + const int& FuzzyStringComparator::getFirstColumn() const + { + return first_column_; + } + + void FuzzyStringComparator::setFirstColumn(const int rhs) + { + this->first_column_ = rhs; + } + + std::ostream& FuzzyStringComparator::getLogDestination() const + { + return *log_dest_; + } + + void FuzzyStringComparator::setLogDestination(std::ostream& rhs) + { + this->log_dest_ = &rhs; + } + + void FuzzyStringComparator::reportFailure_(char const* const message) const + { + // We neither want this entire method be non-const nor make + // is_status_success_ a mutable. So lets hack around it. (Documented in + // class.) + const_cast(is_status_success_) = false; + + if (verbose_level_ >= 1) + { + PrefixInfo_ prefix1(input_line_1_, tab_width_, first_column_); + PrefixInfo_ prefix2(input_line_2_, tab_width_, first_column_); + + std::string prefix; + if (use_prefix_) + { + prefix = " :|: "; + } + + *log_dest_ << std::boolalpha << + prefix << "FAILED: '" << message << "'\n" << + prefix << "\n" << + prefix << " input:\tin1\tin2\n" << + prefix << " line:\t" << line_num_1_ << '\t' << line_num_2_ << "\n" << + prefix << " pos/col:\t" << input_line_1_.line_position_ << '/' << prefix1.line_column << '\t' << input_line_2_.line_position_ << '/' << prefix2.line_column << "\n" << + prefix << " --------------------------------\n" << + prefix << " is_number:\t" << element_1_.is_number << '\t' << element_2_.is_number << "\n" << + prefix << " numbers:\t" << element_1_.number << '\t' << element_2_.number << "\n" << + prefix << " is_space:\t" << element_1_.is_space << '\t' << element_2_.is_space << "\n" << + prefix << " is_letter:\t" << (!element_1_.is_number && !element_1_.is_space) << '\t' << (!element_2_.is_number && !element_2_.is_space) << "\n" << + prefix << " letters:\t\"" << element_1_.letter << "\"\t\"" << element_2_.letter << "\"\n" << + prefix << " char_codes:\t" << static_cast(element_1_.letter) << "\t" << static_cast(element_2_.letter) << "\n" << + prefix << " --------------------------------\n" << + prefix << " relative_max: " << ratio_max_ << "\n" << + prefix << " relative_acceptable: " << ratio_max_allowed_ << "\n" << + prefix << " --------------------------------\n" << + prefix << " absolute_max: " << absdiff_max_ << "\n" << + prefix << " absolute_acceptable: " << absdiff_max_allowed_ << '\n'; + + writeWhitelistCases_(prefix); + + *log_dest_ + << prefix << "\n" + << prefix << "Offending lines:\t\t\t(tab_width = " << tab_width_ << ", first_column = " << first_column_ << ")\n" + << prefix << "\n" + << prefix << "in1: " << to_path(absolutePath(input_1_name_)).make_preferred().string() << " (line: " << line_num_1_ << ", position/column: " << input_line_1_.line_position_ << '/' << prefix1.line_column << ")\n" + << prefix << prefix1.prefix << "!\n" + << prefix << prefix1.prefix_whitespaces << suffix(std::string(input_line_1_.line_.str()), input_line_1_.line_.str().size() - prefix1.prefix.size()) << "\n" + << prefix << "\n" + << prefix << "in2: " << to_path(absolutePath(input_2_name_)).make_preferred().string() << " (line: " << line_num_2_ << ", position/column: " << input_line_2_.line_position_ << '/' << prefix2.line_column << ")\n" + << prefix << prefix2.prefix << "!\n" + << prefix << prefix2.prefix_whitespaces << suffix(std::string(input_line_2_.line_.str()), input_line_2_.line_.str().size() - prefix2.prefix.size()) << "\n" + << prefix << "\n\n" + << "Easy Access:" << "\n" + << to_path(absolutePath(input_1_name_)).make_preferred().string() << ':' << line_num_1_ << ":" << prefix1.line_column << ":\n" + << to_path(absolutePath(input_2_name_)).make_preferred().string() << ':' << line_num_2_ << ":" << prefix2.line_column << ":\n" + << "\n" + #ifdef WIN32 + << "TortoiseGitMerge" + << " /base:\"" << to_path(absolutePath(input_1_name_)).make_preferred().string() << "\"" + << " /mine:\"" << to_path(absolutePath(input_2_name_)).make_preferred().string() << "\"" + #else + << "diff" + << " " << to_path(absolutePath(input_1_name_)).make_preferred().string() + << " " << to_path(absolutePath(input_2_name_)).make_preferred().string() + #endif + << '\n'; + } + + // If verbose level is low, report only the first error. + if (verbose_level_ < 3) + { + throw FuzzyStringComparator::AbortComparison(); + } + + return; + } // reportFailure_() + + void FuzzyStringComparator::reportSuccess_() const + { + if (is_status_success_ && verbose_level_ >= 2) + { + std::string prefix; + if (use_prefix_) + { + prefix = " :|: "; + } + + *log_dest_ << + prefix << "PASSED.\n" << + prefix << '\n' << + prefix << " relative_max: " << ratio_max_ << '\n' << + prefix << " relative_acceptable: " << ratio_max_allowed_ << '\n' << + prefix << '\n' << + prefix << " absolute_max: " << absdiff_max_ << '\n' << + prefix << " absolute_acceptable: " << absdiff_max_allowed_ << '\n'; + + writeWhitelistCases_(prefix); + + *log_dest_ << prefix << '\n'; + + if (line_num_1_max_ == -1 && line_num_2_max_ == -1) + { + *log_dest_ << + prefix << "No numeric differences were found.\n" << + prefix << '\n'; + } + else + { + *log_dest_ << + prefix << "Maximum relative error was attained at these lines, enclosed in \"\":\n" << + prefix << '\n' << + to_path(input_1_name_).make_preferred().string() << ':' << line_num_1_max_ << ":\n" << + "\"" << line_str_1_max_ << "\"\n" << + '\n' << + to_path(input_2_name_).make_preferred().string() << ':' << line_num_2_max_ << ":\n" << + "\"" << line_str_2_max_ << "\"\n" << + std::endl; + } + } + return; + } + + bool FuzzyStringComparator::compareLines_(std::string const& line_str_1, std::string const& line_str_2) + { + // in most cases, results will be identical. If not, do the expensive fuzzy compare + if (line_str_1 == line_str_2) + { + return true; + } + for (std::vector::const_iterator slit = whitelist_.begin(); + slit != whitelist_.end(); ++slit) + { + if (line_str_1.contains(*slit) && + line_str_2.contains(*slit)) + { + ++whitelist_cases_[*slit]; + // *log_dest_ << "whitelist_ case: " << *slit << '\n'; + return is_status_success_; + } + } + + // check matched whitelist + // If file 1 contains element 1 and file 2 contains element 2, they are skipped over. + for (std::vector< std::pair >::const_iterator pair_it = matched_whitelist_.begin(); + pair_it != matched_whitelist_.end(); ++pair_it) + { + if ((line_str_1.contains(pair_it->first) && + line_str_2.contains(pair_it->second) + ) || + (line_str_1.contains(pair_it->second) && + line_str_2.contains(pair_it->first) + ) + ) + { + // ++whitelist_cases_[*slit]; + // *log_dest_ << "whitelist_ case: " << *slit << '\n'; + return is_status_success_; + } + } + + input_line_1_.setToString(line_str_1); + input_line_2_.setToString(line_str_2); + + try + { + while (input_line_1_.ok() && input_line_2_.ok()) + { + element_1_.fillFromInputLine(input_line_1_, line_str_1); + element_2_.fillFromInputLine(input_line_2_, line_str_2); + + if (element_1_.is_number) + { + if (element_2_.is_number) // we are comparing numbers + { +#ifdef DEBUG_FUZZY + std::cout << "cmp number: " << element_1_.number << " : " << element_2_.number << '\n'; +#endif + if (element_1_.number == element_2_.number) + { + continue; + } + + // Decide the non-finite cases here, because the tests below cannot: every comparison + // against NaN is false, so 'absdiff > absdiff_max_', 'ratio < 0', 'ratio < 1' and + // finally 'ratio > ratio_max_' - the sole gate to reportFailure_ - are all false, and + // the pair falls through to the end of the branch and is accepted. A value that + // regressed to NaN was therefore invisible to every file comparison in the test suite, + // whatever the other file held. + // Only the cases the code below gets WRONG are intercepted. An infinity against a + // finite number is ordered and is already reported correctly (by the ratio test for + // +inf, by the sign test for -inf), so it keeps its existing path and message. + if (std::isnan(element_1_.number) || std::isnan(element_2_.number)) + { + // A reference may legitimately contain 'nan'; two of them are equal. (They do not + // reach the equality test above, since NaN != NaN.) + if (std::isnan(element_1_.number) && std::isnan(element_2_.number)) + { + continue; + } + reportFailure_("one value is NaN and the other is not"); + continue; + } + if (std::isinf(element_1_.number) || std::isinf(element_2_.number)) + { + if (std::isinf(element_1_.number) && std::isinf(element_2_.number)) + { + reportFailure_("infinities have different signs"); + } + else + { + reportFailure_("one value is infinite and the other is not"); + } + continue; + } + + // check if absolute difference is small + double absdiff = element_1_.number - element_2_.number; + if (absdiff < 0) + { + absdiff = -absdiff; + } + if (absdiff > absdiff_max_) + { + absdiff_max_ = absdiff; + } + // If absolute difference is small, large relative errors will be + // tolerated in the cases below. But a large absolute difference is + // not an error, if relative error is small. We do not jump out of + // the case distinction here because we want to record the relative + // error even in case of a successful comparison. + is_absdiff_small_ = (absdiff <= absdiff_max_allowed_); + + if (!element_1_.number) // element_1_.number_ is zero + { + if (!element_2_.number) // both numbers are zero + { + continue; + } + else + { + if (!is_absdiff_small_) + { + reportFailure_("element_1_.number_ is zero, but element_2_.number_ is not"); + continue; + } + } + } + else // element_1_.number_ is not zero + { + if (!element_2_.number) + { + if (!is_absdiff_small_) + { + reportFailure_("element_1_.number_ is not zero, but element_2_.number_ is"); + continue; + } + } + else // both numbers are not zero + { + double ratio = element_1_.number / element_2_.number; + if (ratio < 0) + { + if (!is_absdiff_small_) + { + reportFailure_("numbers have different signs"); + continue; + } + } + else // ok, numbers have same sign, but we still need to check their ratio + { + if (ratio < 1) // take reciprocal value + { + ratio = 1.0 / ratio; + } +#ifdef DEBUG_FUZZY + std::cout << " check ratio: " << ratio << " vs " << ratio_max_ << '\n'; +#endif + + // by now, we are sure that ratio >= 1 + if (ratio > ratio_max_) // update running max + { + line_num_1_max_ = line_num_1_; + line_num_2_max_ = line_num_2_; + line_str_1_max_ = line_str_1; + line_str_2_max_ = line_str_2; + if (ratio > ratio_max_allowed_) + { +#ifdef DEBUG_FUZZY + std::cout << "Ratio test failed: is larger than ratio_max \n"; +#endif + if (!is_absdiff_small_) + { + ratio_max_ = ratio; + reportFailure_("ratio of numbers is too large"); + continue; + } + } + } + } + // okay + continue; + } + } + } + else + { + reportFailure_("input_1 is a number, but input_2 is not"); + continue; + } + } + else // input_1 is not a number + { + if (element_2_.is_number) + { + reportFailure_("input_1 is not a number, but input_2 is"); + continue; + } + else // ok, both inputs are not numbers, let us compare them as characters or whitespace + { + if (element_1_.is_space) + { + if (element_2_.is_space) // ok, both inputs are whitespace + { + continue; + } + else + { + if (element_1_.letter == '\r') // should be 13 == ascii carriage return char + { + // we skip over '\r' + input_line_2_.line_.clear(); // reset status + input_line_2_.line_.seekg(input_line_2_.line_position_); // rewind to saved position + continue; + //reportFailure_("input_1 is carriage return, but input_2_ is not whitespace"); + } + else + { + reportFailure_("input_1 is whitespace, but input_2 is not"); + } + continue; + } + } + else // input_1 is not whitespace + { + if (element_2_.is_space) + { + if (element_2_.letter == '\r') // should be 13 == ascii carriage return char + { + // we skip over '\r' + input_line_1_.line_.clear(); // reset status + input_line_1_.line_.seekg(input_line_1_.line_position_); // rewind to saved position + continue; + //reportFailure_("input_1 is not whitespace, but input_2 is carriage return"); + } + else + { + reportFailure_("input_1 is not whitespace, but input_2 is"); + } + continue; + } + else // both inputs are neither numbers nor whitespace, let us compare them as characters + { + if (element_1_.letter == element_2_.letter) // ok, same characters + { + continue; + } + else + { + reportFailure_("different letters"); + continue; + } + } + } + } + } + + if (is_absdiff_small_) + { + is_absdiff_small_ = false; + continue; + } + + verbose_level_ = 10000; + reportFailure_ + ("This cannot happen. You should never get here ... " + "please report this bug along with the data that produced it." + ); + + } // while ( input_line_1_ || input_line_2_ ) + + if (input_line_1_.ok() && !input_line_2_.ok()) + { + reportFailure_("line from input_2 is shorter than line from input_1"); + } + if (!input_line_1_.ok() && input_line_2_.ok()) + { + reportFailure_("line from input_1 is shorter than line from input_2"); + } + } + catch (FuzzyStringComparator::AbortComparison const&) + { + // *log_dest_ << "compareLines_(): Caught FuzzyStringComparator::AbortComparison\n"; + } + + return is_status_success_; + } // compareLines_() + + bool FuzzyStringComparator::compareStrings(std::string const& lhs, std::string const& rhs) + { + std::istringstream input_1(lhs); + std::istringstream input_2(rhs); + + return compareStreams(input_1, input_2); + + } // compareStrings() + + bool FuzzyStringComparator::compareStreams(std::istream& input_1, std::istream& input_2) + { + // reset 'success' state to true, in case its currently false due to a prior call (reporting depends on it) + const_cast(is_status_success_) = true; + + std::string line_str_1; + std::string line_str_2; + + while (input_1 || input_2) + { + + readNextLine_(input_1, line_str_1, line_num_1_); +#ifdef DEBUG_FUZZY + std::cout << "eof: " << input_1.eof() << " failbit: " << input_1.fail() << " badbit: " << input_1.bad() << " reading " << input_1.tellg () << "chars\n"; +#endif + + readNextLine_(input_2, line_str_2, line_num_2_); +#ifdef DEBUG_FUZZY + std::cout << "eof: " << input_2.eof() << " failbit: " << input_2.fail() << " badbit: " << input_2.bad() << " reading " << input_2.tellg () << "chars\n"; + std::cout << line_str_1 << "\n" << line_str_2 << '\n'; +#endif + // compare the two lines of input + if (!compareLines_(line_str_1, line_str_2) && verbose_level_ < 3) + { + break; + } + + } // while ( input_1 || input_2 ) + + reportSuccess_(); + + return is_status_success_; + + } // compareStreams() + + void FuzzyStringComparator::readNextLine_(std::istream& input_stream, std::string& line_string, int& line_number) const + { + // use getLine (local copy of TextFile::getLine) for reading, since it will remove \r automatically on all platforms without much overhead + // This allows to compare otherwise equal lines between files quickly (see compareLines_(...)) + for (line_string.clear(); static_cast(++line_number), getLine(input_stream, line_string); ) + { + if (line_string.empty()) + { + continue; // read next line + } + std::string::const_iterator iter = line_string.begin(); // loop initialization + for (; iter != line_string.end() && isspace((unsigned char)*iter); ++iter) + { + } + // skip over whitespace + if (iter != line_string.end()) + { + return; // line is not empty or whitespace only + } + } + } + + bool FuzzyStringComparator::compareFiles(const std::string& filename_1, const std::string& filename_2) + { + input_1_name_ = filename_1; + input_2_name_ = filename_2; + + if (input_1_name_ == input_2_name_) + { + *log_dest_ << "Error: first and second input file have the same name. That's cheating!\n"; + return false; + } + + std::ifstream input_1_f; + if (!openInputFileStream_(input_1_name_, input_1_f)) + { + return false; + } + + std::ifstream input_2_f; + if (!openInputFileStream_(input_2_name_, input_2_f)) + { + return false; + } + + //------------------------------------------------------------ + // main loop + + compareStreams(input_1_f, input_2_f); + + return is_status_success_; + + } // compareFiles() + + bool FuzzyStringComparator::openInputFileStream_(const std::string& filename, std::ifstream& input_stream) const + { + input_stream.open(filename.c_str(), std::ios::in | std::ios::binary); + if (!input_stream) + { + *log_dest_ << "Error opening first input file '" << filename << "'.\n"; + return false; + } + input_stream.unsetf(std::ios::skipws); + return true; + } + + void FuzzyStringComparator::writeWhitelistCases_(const std::string& prefix) const + { + if (!whitelist_cases_.empty()) + { + *log_dest_ << + prefix << '\n' << + prefix << " whitelist cases:\n"; + std::size_t length = 0; + for (std::map::const_iterator wlcit = whitelist_cases_.begin(); + wlcit != whitelist_cases_.end(); ++wlcit) + { + if (wlcit->first.size() > length) + { + length = wlcit->first.size(); + } + } + for (std::map::const_iterator wlcit = whitelist_cases_.begin(); + wlcit != whitelist_cases_.end(); ++wlcit) + { + *log_dest_ << + prefix << " " << std::setw(length + 3) << std::left << + ("\"" + wlcit->first + "\"") << std::setw(3) << std::right << + wlcit->second << "x\n"; + } + } + } + + + void FuzzyStringComparator::StreamElement_::fillFromInputLine(InputLine& input_line, const std::string& str_line) + { + // first reset all internal variables so we do not mess with + // old values + reset(); + + input_line.updatePosition(); + input_line.line_ >> letter; // read letter + if ((is_space = (isspace(letter) != 0))) // is whitespace? + { + input_line.line_ >> std::ws; // skip over further whitespace + } + else + { + // go back to initial position and try to read as double + input_line.seekGToSavedPosition(); + auto it_start = str_line.begin() + (int)input_line.line_position_; + auto it_start_fixed = it_start; + // extracting the double does NOT modify the stream (since we work on the string) + is_number = extractDouble(it_start, str_line.end(), number); + if (is_number) + { // forward the stream + input_line.line_.seekg(long(input_line.line_.tellg()) + long(std::distance(it_start_fixed, it_start))); + } + else + { // no double/float/int either ... so read as letter + input_line.line_ >> letter; + } + } + } + + void FuzzyStringComparator::StreamElement_::reset() + { + is_number = false; + is_space = false; + letter = '\0'; + number = std::numeric_limits::quiet_NaN(); + } + + FuzzyStringComparator::StreamElement_::StreamElement_() : + number(0), + letter(0), + is_number(false), + is_space(false) + { + } + + FuzzyStringComparator::InputLine::InputLine() : + line_() + { + } + + void FuzzyStringComparator::InputLine::setToString(const std::string& s) + { + line_.str(s); + line_.seekp(0); + line_.clear(); + line_.unsetf(std::ios::skipws); + + line_position_ = line_.tellg(); + } + + void FuzzyStringComparator::InputLine::updatePosition() + { + line_position_ = (int(line_.tellg()) != -1 ? line_.tellg() : std::ios::pos_type(line_.str().length())); // save current reading position + } + + void FuzzyStringComparator::InputLine::seekGToSavedPosition() + { + line_.clear(); // reset status + line_.seekg(line_position_); // rewind to saved position + } + + bool FuzzyStringComparator::InputLine::ok() const + { + return !line_.fail(); // failbit AND badbit are both NOT set; using fail() seems the only portable solution for both C++98 and C++11 + // operator bool() (C++11 only) and operator void*() (C++98 only) are both not very sexy since they are not "safe bool idiomatic" and would require + // a macro here... So we use a real function name (both internally and externally) + } + + FuzzyStringComparator::PrefixInfo_::PrefixInfo_(const InputLine& input_line, const int this_tab_width_, const int this_first_column_) : + prefix(input_line.line_.str()), line_column(0) + { + prefix = prefixOf(prefix, size_t(input_line.line_position_)); + prefix_whitespaces = prefix; + for (auto iter = prefix_whitespaces.begin(); iter != prefix_whitespaces.end(); ++iter) + { + if (*iter != '\t') + { + *iter = ' '; + ++line_column; + } + else + { + line_column = (line_column / this_tab_width_ + 1) * this_tab_width_; + } + } + line_column += this_first_column_; + } + +} //namespace OpenMS diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt new file mode 100644 index 000000000..4e61c2920 --- /dev/null +++ b/src/tests/CMakeLists.txt @@ -0,0 +1,145 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Stephan Aiche, Chris Bielow, Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +project("OpenMS_GUI_tests") + +#------------------------------------------------------------------------------ +# The tests of the GUI layer (options in the top-level CMakeLists.txt): +# class_tests class tests of the GUI library (ENABLE_CLASS_TESTING) +# topp tests of the GUI TOPP tools (ENABLE_TOPP_TESTING) +# toppas TOPPAS pipelines via ExecutePipeline (ENABLE_PIPELINE_TESTING) +# external installed-consumer test (OPENMS_TEST_INSTALLED_CONSUMER) +# +# FuzzyDiff and the TOPP tools the pipelines run come from the OpenMS core +# (OPENMS_TOPP_BIN_DIR); tests that need what is not there are left out with a +# message. The example pipelines and data are those of the installed core +# (share/OpenMS/examples, install component 'examples'). +#------------------------------------------------------------------------------ + +if(NOT "${PACKAGE_TYPE}" STREQUAL "none") + message(WARNING "PACKAGE_TYPE specified along with enabled testing. Tests are only enabled when PACKAGE_TYPE is none. Ignoring tests.") + return() +endif() + +#------------------------------------------------------------------------------ +# The run-time environment of every test that runs a GUI application or GUI TOPP +# tool of this build tree against the OpenMS core: +# - OPENMS_TOOL_REGISTRY_PATH: the tool registry of the core does not list the GUI +# TOPP tools, and TOPPBase refuses to start an unregistered tool. Not set when the +# core's share/OpenMS/TOOLS already holds an OpenMS-GUI.tsv (a GUI layer installed +# into that prefix): the tools would then be registered twice, which ToolHandler +# rejects. This is decided when configuring: reconfigure after installing the GUI +# layer into (or removing it from) the core's prefix. +# - QT_QPA_PLATFORM: no display is needed (ExecutePipeline creates a QApplication, +# the GUI tests open windows); see OPENMS_TEST_QT_QPA_PLATFORM below. +# - OPENMS_DATA_PATH: the last place libOpenMS looks for share/OpenMS, so it only +# matters where the core is not at its compiled-in prefix (Windows, a relocated SDK). +# - PATH: OPENMS_TOPP_BIN_DIR, where ExecutePipeline finds the TOPP tools of the +# pipelines; on Windows also the DLLs of the core and Qt, and the Qt plugins. +#------------------------------------------------------------------------------ +if(NOT "$ENV{QT_QPA_PLATFORM}" STREQUAL "") + set(_qt_qpa_platform_default "") +else() + set(_qt_qpa_platform_default "offscreen") +endif() +set(OPENMS_TEST_QT_QPA_PLATFORM "${_qt_qpa_platform_default}" CACHE STRING + "Qt platform plugin the GUI tests run with (QT_QPA_PLATFORM). Empty: use the environment of ctest. Defaults to 'offscreen' unless QT_QPA_PLATFORM is set when configuring.") +unset(_qt_qpa_platform_default) + +if(EXISTS "${OPENMS_DATA_DIR}/TOOLS/OpenMS-GUI.tsv") + message(STATUS "The OpenMS installation already holds a GUI tool registry (${OPENMS_DATA_DIR}/TOOLS/OpenMS-GUI.tsv): " + "the tests run the GUI TOPP tools with that registry instead of the one of this build.") + set(_openms_gui_test_registry "") +else() + set(_openms_gui_test_registry "${OPENMS_TOOL_REGISTRY_BUILD_DIR}") +endif() +set(OPENMS_GUI_TEST_REGISTRY_PATH "${_openms_gui_test_registry}" CACHE INTERNAL "OPENMS_TOOL_REGISTRY_PATH of the tests") +unset(_openms_gui_test_registry) + +# openms_gui_test_environment([NO_REGISTRY] ...) +# Sets the environment above on the given tests. The tests must be added with +# add_test(NAME ...) in the calling directory (the generator expressions are evaluated +# for those only). NO_REGISTRY leaves out OPENMS_TOOL_REGISTRY_PATH, for tests that run +# only TOPP tools of the core (FuzzyDiff): the registry of this build would make the GUI +# TOPP tools appear twice to the tools of an OpenMS built with its own GUI. +function(openms_gui_test_environment) + cmake_parse_arguments(PARSE_ARGV 0 _arg "NO_REGISTRY" "" "") + set(_env "OPENMS_DATA_PATH=${OPENMS_DATA_DIR}") + if(OPENMS_GUI_TEST_REGISTRY_PATH AND NOT _arg_NO_REGISTRY) + list(APPEND _env "OPENMS_TOOL_REGISTRY_PATH=${OPENMS_GUI_TEST_REGISTRY_PATH}") + endif() + if(OPENMS_TEST_QT_QPA_PLATFORM) + list(APPEND _env "QT_QPA_PLATFORM=${OPENMS_TEST_QT_QPA_PLATFORM}") + endif() + set(_path "PATH=path_list_prepend:${OPENMS_TOPP_BIN_DIR}") + if(WIN32) + ## no RPATH: the DLLs of the core and of Qt are found through PATH + list(APPEND _path + "PATH=path_list_prepend:${OPENMS_BIN_DIR}" + "PATH=path_list_prepend:${OPENMS_LIB_DIR}" + "PATH=path_list_prepend:$") + ## the build tree has no qt.conf: point Qt to its plugins (/platforms/qwindows.dll) + if(TARGET Qt6::QWindowsIntegrationPlugin) + list(APPEND _env "QT_PLUGIN_PATH=$>") + endif() + endif() + set_property(TEST ${_arg_UNPARSED_ARGUMENTS} APPEND PROPERTY ENVIRONMENT ${_env}) + set_property(TEST ${_arg_UNPARSED_ARGUMENTS} APPEND PROPERTY ENVIRONMENT_MODIFICATION ${_path}) +endfunction() + +## FuzzyDiff of the core, used by the tests that compare output files +set(OPENMS_FUZZYDIFF "${OPENMS_TOPP_BIN_DIR}/FuzzyDiff${CMAKE_EXECUTABLE_SUFFIX}") +if(NOT EXISTS "${OPENMS_FUZZYDIFF}") + set(OPENMS_FUZZYDIFF "") + message(STATUS "FuzzyDiff not found in OPENMS_TOPP_BIN_DIR='${OPENMS_TOPP_BIN_DIR}': " + "the tests that compare output files are left out.") +endif() + +## The TOPP tools run by the tests (FuzzyDiff, the tools of the pipelines) read the tool +## registry of their own installation plus OPENMS_TOOL_REGISTRY_PATH. If that installation +## is not the core this build uses and was built with its own GUI, it registers the GUI +## TOPP tools as well, and every tool started with the registry of this build fails on the +## duplicate names (cmake/find_openms.cmake checks the same for the core itself). +file(GLOB _topp_bin_registries "${OPENMS_TOPP_BIN_DIR}/../share/OpenMS/TOOLS/*.tsv") +foreach(_registry IN LISTS _topp_bin_registries) + file(STRINGS "${_registry}" _gui_tools_in_registry REGEX "^(ExecutePipeline|ImageCreator)\t") + if(_gui_tools_in_registry AND OPENMS_GUI_TEST_REGISTRY_PATH) + message(WARNING "The tool registry of the TOPP tools in OPENMS_TOPP_BIN_DIR, ${_registry}, lists the GUI " + "TOPP tools (an OpenMS built with its own GUI): the pipeline tests will fail. Point " + "OPENMS_TOPP_BIN_DIR to the TOPP tools of an OpenMS built without the GUI.") + endif() +endforeach() +unset(_topp_bin_registries) +unset(_gui_tools_in_registry) + +#------------------------------------------------------------------------------ +# Class tests +if(ENABLE_CLASS_TESTING) + ## the class-test framework (not shipped by the OpenMS installation) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../testframework ${CMAKE_CURRENT_BINARY_DIR}/testframework) + add_subdirectory(class_tests) +endif() + +#------------------------------------------------------------------------------ +# Tests of the GUI TOPP tools +if(ENABLE_TOPP_TESTING) + add_subdirectory(topp) +endif() + +#------------------------------------------------------------------------------ +# TOPPAS pipeline tests +if(ENABLE_PIPELINE_TESTING) + add_subdirectory(toppas) +endif() + +#------------------------------------------------------------------------------ +# Installed-consumer tests (install this build into a scratch prefix, then build an +# external project against it) +if(OPENMS_TEST_INSTALLED_CONSUMER) + include(${CMAKE_CURRENT_SOURCE_DIR}/external/installed_consumer.cmake) +endif() diff --git a/src/tests/class_tests/CMakeLists.txt b/src/tests/class_tests/CMakeLists.txt new file mode 100644 index 000000000..709f24aff --- /dev/null +++ b/src/tests/class_tests/CMakeLists.txt @@ -0,0 +1,38 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Stephan Aiche $ +# $Authors: Stephan Aiche $ +# -------------------------------------------------------------------------- + +project("OpenMS_GUI_class_tests") + +#------------------------------------------------------------------------------ +# The libOpenMS half of the class-test setup (as in OpenMS): a fixed +# UniqueIdGenerator seed and an OpenMS exception translator, registered at static +# init (the source says what breaks without it). Every class test links this. +# +# An OBJECT library, not STATIC, and that is the point: this translation unit exports no +# name that anyone references, so as a static archive member the linker would drop it and +# the seed would silently never be set. An object library puts its objects into the +# consumer unconditionally. +add_library(OpenMSTestSupport OBJECT source/OpenMSTestSupport.cpp) +target_link_libraries(OpenMSTestSupport PUBLIC OpenMSTestFramework OpenMS::OpenMS) +openms_add_library_compiler_flags(OpenMSTestSupport) +# The class tests are built at -O0, which also drops the NDEBUG that +# CMAKE_CXX_FLAGS_RELEASE carries. This translation unit ends up in the same +# executables and includes the same headers, so it has to agree with them: otherwise +# an NDEBUG-guarded inline would differ between the two and be an ODR mismatch with +# no diagnostic. On MSVC, OPENMS_ASSERTIONS (config.h) is keyed on NDEBUG, and this +# file's OPENMS_ASSERTIONS branch decides whether precondition tests run at all. +# One quoted generator expression per flag: an unquoted one containing a space is split +# into two arguments. +if(CMAKE_COMPILER_IS_INTELCXX OR CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) + target_compile_options(OpenMSTestSupport PRIVATE + "$<$:-O0>" "$<$:-UNDEBUG>") +endif() + +#------------------------------------------------------------------------------ +# the class tests of the GUI library +add_subdirectory(openms_gui) diff --git a/src/tests/class_tests/openms_gui/CMakeLists.txt b/src/tests/class_tests/openms_gui/CMakeLists.txt index 528d829bb..acfebb69a 100644 --- a/src/tests/class_tests/openms_gui/CMakeLists.txt +++ b/src/tests/class_tests/openms_gui/CMakeLists.txt @@ -1,13 +1,12 @@ # Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin # SPDX-License-Identifier: BSD-3-Clause -# +# # -------------------------------------------------------------------------- # $Maintainer: Stephan Aiche $ # $Authors: Stephan Aiche $ # -------------------------------------------------------------------------- - -project("OpenMS_class_tests_openms_gui") +project("OpenMS_GUI_class_tests_openms_gui") set(visual_executables_list Annotation1DPeakItem_test @@ -21,16 +20,11 @@ set(visual_executables_list set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) -# TODO should not be required anymore #------------------------------------------------------------------------------ -# required to build GUI tests (e.g., TOPPView_test) -include_directories(SYSTEM ${OpenMS_GUI_INCLUDE_DIRECTORIES}) - -# add include to find moced files -include_directories(${PROJECT_BINARY_DIR}) - -# required for GUI tests to find the ui_foo.h files (e.g. TSGDialog_test) -include_directories(${CMAKE_BINARY_DIR}/src/openms_gui) +# disable optimization for tests for gcc like compilers (as OpenMSTestSupport does) +if(CMAKE_COMPILER_IS_INTELCXX OR CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) + set(CMAKE_CXX_FLAGS_RELEASE "-O0") +endif() #------------------------------------------------------------------------------ # VISUAL @@ -39,49 +33,54 @@ add_dependencies(VISUAL_TEST ${visual_executables_list}) foreach(_class_test ${visual_executables_list}) add_executable(${_class_test} source/${_class_test}.cpp) - target_link_libraries(${_class_test} OpenMSTestSupport ${OpenMS_GUI_LIBRARIES}) + target_link_libraries(${_class_test} PRIVATE OpenMSTestSupport OpenMS_GUI) openms_add_executable_compiler_flags(${_class_test}) - add_test(${_class_test} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${_class_test}) -endforeach(_class_test) + add_test(NAME ${_class_test} COMMAND ${_class_test}) +endforeach() +openms_gui_test_environment(${visual_executables_list}) #------------------------------------------------------------------------------ -# GUI tests special treatment - Apply MOC compiler to GUI +# GUI tests (QtTest): they open windows, so they need a display or a display-less +# platform plugin (QT_QPA_PLATFORM=offscreen, see src/tests/CMakeLists.txt). +# The ui_*.h headers of the dialogs (TSGDialog_test) come with the build-tree +# interface of OpenMS_GUI. #------------------------------------------------------------------------------ - -# we only want to run those tests if we have a running XServer if(HAS_XSERVER) + find_package(Qt6 REQUIRED COMPONENTS Test) + set(GUI_executables_list TOPPView_test TSGDialog_test ) - #------------------------------------------------------------------------------ - # Use QtTest lib - - find_package(Qt6 REQUIRED - Test - ) - if (NOT Qt6Test_FOUND) - message(STATUS "QtTest module not found!") - message(FATAL_ERROR "To find a custom Qt installation use: cmake <..more options..> -D QT_QMAKE_EXECUTABLE='") - endif() - - #------------------------------------------------------------------------------ - # Add GUI tests foreach(_gui_test ${GUI_executables_list}) - # add the executable (Note: The header needs to be added as source file, too. Otherwise automoc will not find it. + # the header is listed as a source file too, so automoc sees its Q_OBJECT add_executable(${_gui_test} source/GUI/${_gui_test}.cpp ${CMAKE_CURRENT_SOURCE_DIR}/include/GUI/${_gui_test}.h) - # add headers target_include_directories(${_gui_test} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/GUI) - # link against openms_gui and qt - target_link_libraries(${_gui_test} OpenMSTestSupport ${OpenMS_GUI_LIBRARIES} ${QT_LIBRARIES} Qt6::Test) - # add the test - add_test(${_gui_test} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${_gui_test}) - endforeach(_gui_test) + target_link_libraries(${_gui_test} PRIVATE OpenMSTestSupport OpenMS_GUI Qt6::Test) + openms_add_executable_compiler_flags(${_gui_test}) + endforeach() + + ## a class test that needs a display: QApplicationTOPP is a QApplication + add_executable(QApplicationTOPP_test source/QApplicationTOPP_test.cpp) + target_link_libraries(QApplicationTOPP_test PRIVATE OpenMSTestSupport OpenMS_GUI) + openms_add_executable_compiler_flags(QApplicationTOPP_test) + list(APPEND GUI_executables_list QApplicationTOPP_test) - #------------------------------------------------------------------------------ - # GUI tests add_custom_target(GUI_TEST) add_dependencies(GUI_TEST ${GUI_executables_list}) + ## TOPPView_test opens examples/peakpicker_tutorial_1.mzML from the shared data of + ## the OpenMS core (install component 'examples', not part of the OpenMS SDK) + set(_toppview_example "${OPENMS_DATA_DIR}/examples/peakpicker_tutorial_1.mzML") + set(_gui_tests TSGDialog_test QApplicationTOPP_test) + if(EXISTS "${_toppview_example}") + list(APPEND _gui_tests TOPPView_test) + else() + message(STATUS "TOPPView_test is left out: ${_toppview_example} does not exist (the OpenMS installation has no examples).") + endif() + foreach(_gui_test ${_gui_tests}) + add_test(NAME ${_gui_test} COMMAND ${_gui_test}) + endforeach() + openms_gui_test_environment(${_gui_tests}) endif() diff --git a/src/tests/class_tests/openms_gui/source/QApplicationTOPP_test.cpp b/src/tests/class_tests/openms_gui/source/QApplicationTOPP_test.cpp new file mode 100644 index 000000000..dd271e59d --- /dev/null +++ b/src/tests/class_tests/openms_gui/source/QApplicationTOPP_test.cpp @@ -0,0 +1,40 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: Timo Sachsenberg $ +// $Authors: Timo Sachsenberg $ +// -------------------------------------------------------------------------- + +#include + +/////////////////////////// + +#include + +/////////////////////////// + +using namespace OpenMS; +using namespace std; + +START_TEST(QApplicationTOPP, "$Id$") + +///////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////// + +START_SECTION((QApplicationTOPP(int& argc, char** argv))) +{ + // The default style sheet is compiled into libOpenMS_GUI. Before, it was looked up in the + // shared data of the OpenMS core, which does not hold it: the constructor threw + // Exception::FileNotFound when the GUI applications ran from the build tree. + char app_name[] = "QApplicationTOPP_test"; + char* argv[] = {app_name, nullptr}; + int argc = 1; + QApplicationTOPP app(argc, argv); + TEST_EQUAL(app.styleSheet().contains("::item:selected:!active"), true) +} +END_SECTION + +///////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////// +END_TEST diff --git a/src/tests/class_tests/source/OpenMSTestSupport.cpp b/src/tests/class_tests/source/OpenMSTestSupport.cpp new file mode 100644 index 000000000..01223e239 --- /dev/null +++ b/src/tests/class_tests/source/OpenMSTestSupport.cpp @@ -0,0 +1,72 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: Timo Sachsenberg $ +// $Authors: Timo Sachsenberg $ +// -------------------------------------------------------------------------- + +// libOpenMS-specific setup for the std-only class-test framework. Built as the +// OpenMSTestSupport target and linked by every class test of a project that links +// libOpenMS -- the openms, openms_cli and openms_gui test projects and the +// tool-local test/ folders -- but not by tests that do not, e.g. OpenSwathAlgo. +// Registers, at static init: +// 1. a fixed UniqueIdGenerator seed, so IDs in output files match the reference +// files; without it they are drawn from a time-derived seed and ID-bearing +// comparisons (e.g. featureXML) become nondeterministic; +// 2. an OpenMS exception translator, so failure reports name unexpected OpenMS +// exceptions (without it: std::exception::what() -- degraded, not wrong). + +#include +#include +#include + +#include +#include +#include + +namespace +{ + bool describeOpenMSException(std::string& description) + { + try + { + throw; // rethrow the exception currently being handled + } + catch (const OpenMS::Exception::BaseException& e) + { + std::ostringstream os; + os << "OpenMS exception of type '" << e.getName() << "'"; + if ((e.getLine() > 0) && std::strcmp(e.getFile(), "") != 0) + { + os << " thrown in line " << e.getLine() << " of file '" << e.getFile() + << "' in function '" << e.getFunction() << "'"; + } + os << " - Message: " << e.what(); + description = os.str(); + return true; + } + catch (...) + { + return false; // not ours; let the framework (or another translator) describe it + } + } + + struct OpenMSTestSupport + { + OpenMSTestSupport() + { + // Fixed seed so unique ids stored in test output are reproducible. + OpenMS::UniqueIdGenerator::setSeed(2453440375); + OpenMS::Internal::ClassTest::registerExceptionTranslator(&describeOpenMSException); + // Key TEST_PRECONDITION/POSTCONDITION_VIOLATED on the same OPENMS_ASSERTIONS + // (from ) that Macros.h keys the library's checks on, so the + // two cannot diverge across build type or generator. +#ifdef OPENMS_ASSERTIONS + OpenMS::Internal::ClassTest::setPreconditionTestsEnabled(true); +#endif + } + }; + + const OpenMSTestSupport openms_test_support_init; +} diff --git a/src/tests/external/CMakeLists.txt b/src/tests/external/CMakeLists.txt new file mode 100644 index 000000000..9c28e6d0c --- /dev/null +++ b/src/tests/external/CMakeLists.txt @@ -0,0 +1,103 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +cmake_minimum_required(VERSION 3.24 FATAL_ERROR) + +### Example CMakeLists.txt of a program using the OpenMS GUI library. It doubles as +### the installed-consumer test of OpenMS-TOPP-GUI (OPENMS_TEST_INSTALLED_CONSUMER, +### src/tests/external/installed_consumer.cmake), so it also checks what the package +### promises; a plain consumer project needs only find_package() and +### target_link_libraries(). +project("OpenMS_GUI_ExternalCodeTest" LANGUAGES CXX) + +include(CTest) + +## OFF: find_package(OpenMSGUI), the package of the GUI layer itself. +## ON: find_package(OpenMS COMPONENTS GUI), which the core's package delegates to OpenMSGUI +## when the GUI layer is installed into the core's prefix. +option(OPENMS_GUI_VIA_CORE_PACKAGE "Find the GUI layer through find_package(OpenMS COMPONENTS GUI)." OFF) +## the directory the GUI package has to be found in (empty: not checked) +set(EXPECTED_OPENMSGUI_PREFIX "" CACHE PATH "Installation prefix the GUI layer must be found in.") + +message(STATUS "Received CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}") + +if(OPENMS_GUI_VIA_CORE_PACKAGE) + find_package(OpenMS CONFIG REQUIRED COMPONENTS GUI) + if(NOT OpenMS_GUI_FOUND OR NOT OpenMS_WITH_GUI) + message(FATAL_ERROR "find_package(OpenMS COMPONENTS GUI) succeeded without setting OpenMS_GUI_FOUND and OpenMS_WITH_GUI") + endif() + if(NOT OpenMSGUI_FOUND) + message(FATAL_ERROR "find_package(OpenMS COMPONENTS GUI) did not find the GUI layer through its package OpenMSGUI") + endif() +else() + find_package(OpenMSGUI CONFIG REQUIRED) +endif() +message(STATUS "Found OpenMSGUI ${OpenMSGUI_VERSION} at ${OpenMSGUI_DIR} (OpenMS ${OpenMS_VERSION} at ${OpenMS_DIR})") + +#------------------------------------------------------------------------------ +## Package contract + +## the GUI layer belongs to the core of the same version +if(NOT OpenMSGUI_VERSION VERSION_EQUAL OpenMS_VERSION) + message(FATAL_ERROR "OpenMSGUI ${OpenMSGUI_VERSION} was found with OpenMS ${OpenMS_VERSION}") +endif() +## ... it provides the namespaced imported target and the compatibility alias, and brings +## the core and CLI layers and the Qt modules of its link interface along +foreach(_target IN ITEMS OpenMS::OpenMS_GUI OpenMS_GUI OpenMS::OpenMS OpenMS::OpenMS_CLI Qt6::Core Qt6::Widgets) + if(NOT TARGET ${_target}) + message(FATAL_ERROR "find_package(OpenMSGUI) did not provide the target ${_target}") + endif() +endforeach() +get_target_property(_imported OpenMS::OpenMS_GUI IMPORTED) +if(NOT _imported) + message(FATAL_ERROR "OpenMS::OpenMS_GUI is not an imported target") +endif() +if(NOT OpenMSGUI_QT_COMPONENTS) + message(FATAL_ERROR "OpenMSGUI_QT_COMPONENTS is empty") +endif() +foreach(_module IN LISTS OpenMSGUI_QT_COMPONENTS) + if(NOT TARGET Qt6::${_module}) + message(FATAL_ERROR "OpenMSGUI_QT_COMPONENTS lists ${_module}, but Qt6::${_module} was not found") + endif() +endforeach() + +## ... and the library is where the package says, in the expected installation +if(NOT IS_DIRECTORY "${OPENMS_GUI_LIB_DIR}") + message(FATAL_ERROR "OPENMS_GUI_LIB_DIR='${OPENMS_GUI_LIB_DIR}' is not a directory") +endif() +get_filename_component(_gui_lib_dir "${OPENMS_GUI_LIB_DIR}" ABSOLUTE) +get_target_property(_configurations OpenMS::OpenMS_GUI IMPORTED_CONFIGURATIONS) +foreach(_configuration IN LISTS _configurations) + get_target_property(_location OpenMS::OpenMS_GUI IMPORTED_LOCATION_${_configuration}) + get_filename_component(_location_dir "${_location}" DIRECTORY) + if(NOT _location_dir STREQUAL _gui_lib_dir) + message(FATAL_ERROR "OPENMS_GUI_LIB_DIR='${OPENMS_GUI_LIB_DIR}' but the ${_configuration} OpenMS_GUI library is ${_location}") + endif() +endforeach() +if(EXPECTED_OPENMSGUI_PREFIX) + get_filename_component(_expected "${EXPECTED_OPENMSGUI_PREFIX}" ABSOLUTE) + string(FIND "${OpenMSGUI_DIR}/" "${_expected}/" _pos) + if(NOT _pos EQUAL 0) + message(FATAL_ERROR "OpenMSGUI was found at ${OpenMSGUI_DIR}, not in ${EXPECTED_OPENMSGUI_PREFIX}") + endif() +endif() + +#------------------------------------------------------------------------------ +## a program using the GUI library (and through it the core and Qt) +add_executable(TestExternalCodeGUI TestExternalCodeGUI.cpp) +target_link_libraries(TestExternalCodeGUI PRIVATE OpenMS::OpenMS_GUI) +add_test(NAME TestExternalCodeGUI COMMAND TestExternalCodeGUI) +set_tests_properties(TestExternalCodeGUI PROPERTIES PASS_REGULAR_EXPRESSION "MultiGradient red-blue at 50") + +## Windows has no RPATH: the DLLs of the GUI layer, the core and Qt are found through PATH +if(WIN32) + set_property(TEST TestExternalCodeGUI APPEND PROPERTY ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:${OPENMS_GUI_LIB_DIR}" + "PATH=path_list_prepend:${OPENMS_LIB_DIR}" + "PATH=path_list_prepend:$") +endif() diff --git a/src/tests/external/TestExternalCodeGUI.cpp b/src/tests/external/TestExternalCodeGUI.cpp new file mode 100644 index 000000000..495d81290 --- /dev/null +++ b/src/tests/external/TestExternalCodeGUI.cpp @@ -0,0 +1,46 @@ +// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +// SPDX-License-Identifier: BSD-3-Clause +// +// -------------------------------------------------------------------------- +// $Maintainer: Timo Sachsenberg $ +// $Authors: Timo Sachsenberg $ +// -------------------------------------------------------------------------- + +// Installed-consumer check of the GUI layer: compiled against the installed headers +// of OpenMS_GUI (which include Qt headers and those of the OpenMS core and CLI +// layers) and linked against OpenMS::OpenMS_GUI only, so the build succeeds only +// when the include directories and the link interface of the installed package +// resolve: Qt from OpenMSGUIConfig.cmake, the core through the GUI library's +// PUBLIC dependencies. Uses no QApplication, so it runs without a display. + +#include +#include + +#include + +#include + +int main() +{ + // the core library, carried by OpenMS_GUI as a transitive dependency + OpenMS::MSExperiment exp; + exp.resize(3); + if (exp.size() != 3) + { + std::cerr << "MSExperiment::resize() failed\n"; + return 1; + } + + // the GUI library and Qt + OpenMS::MultiGradient gradient; + gradient.insert(0, Qt::red); + gradient.insert(100, Qt::blue); + const QColor middle = gradient.interpolatedColorAt(50); + if (middle.red() <= 0 || middle.red() >= 255 || middle.green() != 0 || middle.blue() <= 0 || middle.blue() >= 255) + { + std::cerr << "MultiGradient::interpolatedColorAt(50) returned " << middle.name().toStdString() << '\n'; + return 1; + } + std::cout << "MultiGradient red-blue at 50: " << middle.name().toStdString() << '\n'; + return 0; +} diff --git a/src/tests/external/installed_consumer.cmake b/src/tests/external/installed_consumer.cmake new file mode 100644 index 000000000..5336f47f9 --- /dev/null +++ b/src/tests/external/installed_consumer.cmake @@ -0,0 +1,155 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +#------------------------------------------------------------------------------ +# Installed-consumer tests (OPENMS_TEST_INSTALLED_CONSUMER), included by +# src/tests/CMakeLists.txt. Each step is one plain cmake/ctest invocation, chained +# through CTest fixtures: +# +# 1. GUIConsumer: install this build (components library_gui, OpenMS_GUI_headers, +# cmake_gui, GUIApplications) into a scratch prefix below the build tree, then +# configure, build and run src/tests/external against it with +# find_package(OpenMSGUI), CMAKE_PREFIX_PATH=;;. +# 2. GUIConsumerViaCore, only when the core's OpenMSConfig.cmake delegates its +# component GUI to the OpenMSGUI package: the installation of 1 merged with the +# core's prefix (links, the core's prefix is not written to), and the same project +# with find_package(OpenMS COMPONENTS GUI) against that prefix alone. +#------------------------------------------------------------------------------ + +# Only the toolchain of this build is forwarded to the consumer, as a consumer building +# with the same compiler and dependency provider would pass it; everything about OpenMS +# comes from the installed packages. OpenMP_ROOT is part of it: an OpenMS built with +# Homebrew's keg-only libomp needs it in every consumer (find_dependency(OpenMP)). +set(_configure_args) +foreach(_var IN ITEMS + CMAKE_TOOLCHAIN_FILE VCPKG_INSTALLED_DIR VCPKG_TARGET_TRIPLET VCPKG_HOST_TRIPLET + CMAKE_CXX_COMPILER CMAKE_MAKE_PROGRAM CMAKE_CXX_COMPILER_LAUNCHER + CMAKE_MSVC_RUNTIME_LIBRARY CMAKE_OSX_ARCHITECTURES CMAKE_OSX_DEPLOYMENT_TARGET + CMAKE_OSX_SYSROOT OpenMP_ROOT) + if(DEFINED ${_var} AND NOT "${${_var}}" STREQUAL "") + string(REPLACE ";" "$" _value "${${_var}}") + list(APPEND _configure_args "-D${_var}=${_value}") + endif() +endforeach() +list(APPEND _configure_args -DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON) +if(CMAKE_GENERATOR_PLATFORM) + list(APPEND _configure_args -A "${CMAKE_GENERATOR_PLATFORM}") +endif() +if(CMAKE_GENERATOR_TOOLSET) + list(APPEND _configure_args -T "${CMAKE_GENERATOR_TOOLSET}") +endif() + +## the prefixes of the core (OpenMS_DIR is /lib/cmake/OpenMS, /CMake on +## Windows) and of Qt (Qt6_DIR is /lib/cmake/Qt6, /lib//cmake/Qt6 +## on Debian, /share/Qt6 with vcpkg) +if(WIN32) + get_filename_component(_core_prefix "${OpenMS_DIR}/.." ABSOLUTE) +else() + get_filename_component(_core_prefix "${OpenMS_DIR}/../../.." ABSOLUTE) +endif() +get_filename_component(_qt_prefix "${Qt6_DIR}" ABSOLUTE) +if(_qt_prefix MATCHES "^(.+)/(lib(64)?(/[^/]+)?/cmake|share)/Qt6$") + set(_qt_prefix "${CMAKE_MATCH_1}") +else() + get_filename_component(_qt_prefix "${Qt6_DIR}/../../.." ABSOLUTE) +endif() + +set(_consumer_root "${CMAKE_BINARY_DIR}/installed-consumer") +set(_gui_prefix "${_consumer_root}/prefix") +set(_gui_components library_gui OpenMS_GUI_headers cmake_gui GUIApplications) + +# _openms_gui_consumer_chain( +# [CONFIGURE_ARGS ...]) +# _configure, _build and _run of src/tests/external. +function(_openms_gui_consumer_chain name prefix_path consumer fixture) + cmake_parse_arguments(PARSE_ARGV 4 _chain "" "" "CONFIGURE_ARGS") + string(REPLACE ";" "$" _prefix_path "${prefix_path}") + add_test(NAME ${name}_configure + COMMAND ${CMAKE_COMMAND} -S "${CMAKE_CURRENT_SOURCE_DIR}/external" -B "${consumer}" + -G "${CMAKE_GENERATOR}" "-DCMAKE_BUILD_TYPE=$" + "-DCMAKE_PREFIX_PATH=${_prefix_path}" + ${_chain_CONFIGURE_ARGS} ${_configure_args}) + set_tests_properties(${name}_configure PROPERTIES + FIXTURES_REQUIRED ${fixture} + FIXTURES_SETUP ${name}_configured) + add_test(NAME ${name}_build + COMMAND ${CMAKE_COMMAND} --build "${consumer}" --config $) + set_tests_properties(${name}_build PROPERTIES + FIXTURES_REQUIRED ${name}_configured + FIXTURES_SETUP ${name}_built) + add_test(NAME ${name}_run + COMMAND ${CMAKE_CTEST_COMMAND} --test-dir "${consumer}" -C $ + --output-on-failure --no-tests=error) + set_tests_properties(${name}_run PROPERTIES FIXTURES_REQUIRED ${name}_built) + set_tests_properties(${name}_configure ${name}_build ${name}_run PROPERTIES LABELS installed_consumer) +endfunction() + +#------------------------------------------------------------------------------ +# 1. The GUI layer in a prefix of its own, next to the core's +# +# The prefix is emptied first because `cmake --install --component` only ever adds +# files: files of an earlier run would otherwise stay behind. +add_test(NAME GUIConsumer_clean + COMMAND ${CMAKE_COMMAND} -E rm -rf "${_gui_prefix}" "${_consumer_root}/prefix-via-core" + "${_consumer_root}/build" "${_consumer_root}/build-via-core") +set_tests_properties(GUIConsumer_clean PROPERTIES FIXTURES_SETUP GUIConsumer_cleaned) +set(_install_tests) +foreach(_component IN LISTS _gui_components) + add_test(NAME GUIConsumer_install_${_component} + COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --config $ + --prefix "${_gui_prefix}" --component ${_component}) + list(APPEND _install_tests GUIConsumer_install_${_component}) +endforeach() +# each `cmake --install --component ` writes install_manifest_.txt of this build tree +set_tests_properties(${_install_tests} PROPERTIES + FIXTURES_REQUIRED GUIConsumer_cleaned + FIXTURES_SETUP GUIConsumer_installed + RESOURCE_LOCK openms_gui_installed_consumer_install) +set_tests_properties(GUIConsumer_clean ${_install_tests} PROPERTIES LABELS installed_consumer) + +_openms_gui_consumer_chain(GUIConsumer + "${_gui_prefix};${_core_prefix};${_qt_prefix};${CMAKE_PREFIX_PATH}" + "${_consumer_root}/build" + GUIConsumer_installed + CONFIGURE_ARGS "-DEXPECTED_OPENMSGUI_PREFIX=${_gui_prefix}") + +#------------------------------------------------------------------------------ +# 2. The GUI layer installed into the core's prefix, found through the core's package +file(STRINGS "${OpenMS_DIR}/OpenMSConfig.cmake" _core_delegates_gui + REGEX "(find_package|find_dependency)[ \t]*\\([ \t]*OpenMSGUI[ \t)]") +if(_core_delegates_gui) + set(_merged_prefix "${_consumer_root}/prefix-via-core") + ## only what a CMake consumer of the libraries reads from the core's prefix + add_test(NAME GUIConsumerViaCore_merge + COMMAND ${CMAKE_COMMAND} "-DDEST=${_merged_prefix}" + "-DSOURCES=${_gui_prefix}$${_core_prefix}" + "-DENTRIES=bin$lib$include$CMake" + -P "${CMAKE_CURRENT_SOURCE_DIR}/external/merge_prefixes.cmake") + set_tests_properties(GUIConsumerViaCore_merge PROPERTIES + FIXTURES_REQUIRED GUIConsumer_installed + FIXTURES_SETUP GUIConsumerViaCore_merged + LABELS installed_consumer) + _openms_gui_consumer_chain(GUIConsumerViaCore + "${_merged_prefix};${_qt_prefix};${CMAKE_PREFIX_PATH}" + "${_consumer_root}/build-via-core" + GUIConsumerViaCore_merged + CONFIGURE_ARGS "-DOPENMS_GUI_VIA_CORE_PACKAGE=ON" "-DEXPECTED_OPENMSGUI_PREFIX=${_merged_prefix}") +else() + message(STATUS "GUIConsumerViaCore is left out: ${OpenMS_DIR}/OpenMSConfig.cmake does not delegate " + "the component GUI to the OpenMSGUI package (an OpenMS from before the split).") +endif() + +unset(_configure_args) +unset(_core_prefix) +unset(_qt_prefix) +unset(_consumer_root) +unset(_gui_prefix) +unset(_gui_components) +unset(_install_tests) +unset(_merged_prefix) +unset(_core_delegates_gui) diff --git a/src/tests/external/merge_prefixes.cmake b/src/tests/external/merge_prefixes.cmake new file mode 100644 index 000000000..8d16aac95 --- /dev/null +++ b/src/tests/external/merge_prefixes.cmake @@ -0,0 +1,86 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +cmake_minimum_required(VERSION 3.24 FATAL_ERROR) + +### Builds one installation prefix out of several, the way installing the GUI layer into +### the prefix of the OpenMS core does, without writing into the core's prefix: +### +### cmake -DDEST= "-DSOURCES=;..." [-DENTRIES=;...] -P merge_prefixes.cmake +### +### A directory present in more than one source becomes a real directory and is merged +### recursively; every other entry is linked. Where symbolic links cannot be created (Windows +### without the privilege) a directory is recreated and its files are hard-linked, or +### copied where that fails too. A file present in more than one +### source is an error: the GUI layer must not install anything the core ships. +### ENTRIES restricts the top level to the given names (default: everything). + +foreach(_required DEST SOURCES) + if(NOT DEFINED ${_required}) + message(FATAL_ERROR "merge_prefixes.cmake: -D${_required}= is required") + endif() +endforeach() + +function(_link_entry source dest) + file(CREATE_LINK "${source}" "${dest}" RESULT _result SYMBOLIC) + if(NOT _result EQUAL 0) + if(IS_DIRECTORY "${source}") + file(GLOB _children RELATIVE "${source}" "${source}/*") + _merge("${dest}" "${source}" "${_children}") + else() + file(CREATE_LINK "${source}" "${dest}" COPY_ON_ERROR) + endif() + endif() +endfunction() + +function(_merge dest sources names) + file(MAKE_DIRECTORY "${dest}") + foreach(_name IN LISTS names) + set(_having) + foreach(_source IN LISTS sources) + if(EXISTS "${_source}/${_name}" OR IS_SYMLINK "${_source}/${_name}") + list(APPEND _having "${_source}/${_name}") + endif() + endforeach() + list(LENGTH _having _count) + if(_count EQUAL 1) + _link_entry("${_having}" "${dest}/${_name}") + elseif(_count GREATER 1) + set(_subsources) + set(_subnames) + foreach(_entry IN LISTS _having) + if(NOT IS_DIRECTORY "${_entry}") + string(REPLACE ";" ", " _list "${_having}") + message(FATAL_ERROR "'${_name}' is a file in more than one prefix: ${_list}") + endif() + list(APPEND _subsources "${_entry}") + file(GLOB _children RELATIVE "${_entry}" "${_entry}/*") + list(APPEND _subnames ${_children}) + endforeach() + list(REMOVE_DUPLICATES _subnames) + _merge("${dest}/${_name}" "${_subsources}" "${_subnames}") + endif() + endforeach() +endfunction() + +if(EXISTS "${DEST}") + message(FATAL_ERROR "merge_prefixes.cmake: '${DEST}' exists already") +endif() + +if(DEFINED ENTRIES) + set(_names ${ENTRIES}) +else() + set(_names) + foreach(_source IN LISTS SOURCES) + file(GLOB _children RELATIVE "${_source}" "${_source}/*") + list(APPEND _names ${_children}) + endforeach() + list(REMOVE_DUPLICATES _names) +endif() +_merge("${DEST}" "${SOURCES}" "${_names}") +message(STATUS "Merged ${SOURCES} into ${DEST}") diff --git a/src/tests/topp/CMakeLists.txt b/src/tests/topp/CMakeLists.txt new file mode 100644 index 000000000..9d1eb2c36 --- /dev/null +++ b/src/tests/topp/CMakeLists.txt @@ -0,0 +1,68 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Timo Sachsenberg $ +# $Authors: Andreas Bertsch, Chris Bielow, Timo Sachsenberg $ +# -------------------------------------------------------------------------- + +project("OpenMS_GUI_TOPP_tests") + +#------------------------------------------------------------------------------ +# Tests of the GUI TOPP tools (${TOPP_TOOLS}: ExecutePipeline, ImageCreator), declared +# like those of the TOPP tools in OpenMS' src/tests/topp/CMakeLists.txt. +# +# Naming scheme: TOPP__[_out]; a test that checks the output of +# another one with FuzzyDiff DEPENDS on it. +#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ +# General settings +#------------------------------------------------------------------------------ +set(DATA_DIR_TOPP ${PROJECT_SOURCE_DIR}) +set(TESTS_TEMP_DIR "${PROJECT_BINARY_DIR}/tmp_path") +file(MAKE_DIRECTORY "${TESTS_TEMP_DIR}") +## FuzzyDiff of the OpenMS core (src/tests/CMakeLists.txt); empty if there is none +set(DIFF ${OPENMS_FUZZYDIFF} -test -ini ${DATA_DIR_TOPP}/FuzzyDiff.ini) + +#------------------------------------------------------------------------------ +# The generated tool registry of the GUI layer (OpenMS-GUI.tsv) has to be something +# ToolHandler can read, and carry every declared tool. +#------------------------------------------------------------------------------ +add_test(NAME TOPP_tool_registry_is_readable + COMMAND ${CMAKE_COMMAND} + -DREGISTRY_FILE=${OPENMS_TOOL_REGISTRY_BUILD_FILE} + "-DTOOLS=${TOPP_TOOLS}" + -P "${DATA_DIR_TOPP}/check_tool_registry.cmake") + +#------------------------------------------------------------------------------ +# -write_ini and -write_ctd of each GUI TOPP tool +#------------------------------------------------------------------------------ +set(_gui_tool_tests) +foreach(i ${TOPP_TOOLS}) + add_test(NAME "TOPPWRITEINI_${i}" COMMAND ${i} -test -write_ini "${TESTS_TEMP_DIR}/${i}.tmp.ini") + add_test(NAME "TOPPWRITEINI_${i}_SectionName" COMMAND ${CMAKE_COMMAND} -test -DNAME=${i} -DFILE=${TESTS_TEMP_DIR}/${i}.tmp.ini -P "${DATA_DIR_TOPP}/check_ini.cmake") + set_tests_properties("TOPPWRITEINI_${i}_SectionName" PROPERTIES DEPENDS "TOPPWRITEINI_${i}") + add_test(NAME "TOPPWRITECTD_${i}" COMMAND ${i} -test -write_ctd "${TESTS_TEMP_DIR}/") + list(APPEND _gui_tool_tests "TOPPWRITEINI_${i}" "TOPPWRITECTD_${i}") +endforeach() + +#------------------------------------------------------------------------------ +# ImageCreator (QImage/QPainter only, no window) +#------------------------------------------------------------------------------ +add_test(NAME "TOPP_ImageCreator_1" COMMAND ImageCreator -test -in ${DATA_DIR_TOPP}/ImageCreator_1_input.mzML -out ${TESTS_TEMP_DIR}/ImageCreator1.bmp -width 20 -height 15) +# testing forced out_type +add_test(NAME "TOPP_ImageCreator_2" COMMAND ImageCreator -test -in ${DATA_DIR_TOPP}/ImageCreator_2_input.mzML -out ${TESTS_TEMP_DIR}/ImageCreator2.png -out_type bmp -width 20 -height 15 -precursors -precursor_size 3 -precursor_color green -log_intensity) +list(APPEND _gui_tool_tests "TOPP_ImageCreator_1" "TOPP_ImageCreator_2") +if(OPENMS_FUZZYDIFF) + add_test(NAME "TOPP_ImageCreator_1_out1" COMMAND ${DIFF} -in1 ${TESTS_TEMP_DIR}/ImageCreator1.bmp -in2 ${DATA_DIR_TOPP}/ImageCreator_1_output.bmp) + set_tests_properties("TOPP_ImageCreator_1_out1" PROPERTIES DEPENDS "TOPP_ImageCreator_1") + add_test(NAME "TOPP_ImageCreator_2_out1" COMMAND ${DIFF} -in1 ${TESTS_TEMP_DIR}/ImageCreator2.png -in2 ${DATA_DIR_TOPP}/ImageCreator_2_output.bmp) + set_tests_properties("TOPP_ImageCreator_2_out1" PROPERTIES DEPENDS "TOPP_ImageCreator_2") + ## FuzzyDiff is a tool of the core: no tool registry of this build + openms_gui_test_environment(NO_REGISTRY "TOPP_ImageCreator_1_out1" "TOPP_ImageCreator_2_out1") +else() + message(STATUS "TOPP_ImageCreator_1_out1 and TOPP_ImageCreator_2_out1 are left out (no FuzzyDiff).") +endif() + +openms_gui_test_environment(${_gui_tool_tests}) diff --git a/src/tests/topp/FuzzyDiff.ini b/src/tests/topp/FuzzyDiff.ini new file mode 100644 index 000000000..e868ce17e --- /dev/null +++ b/src/tests/topp/FuzzyDiff.ini @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tests/topp/check_ini.cmake b/src/tests/topp/check_ini.cmake new file mode 100644 index 000000000..366449c0a --- /dev/null +++ b/src/tests/topp/check_ini.cmake @@ -0,0 +1,7 @@ +file(STRINGS ${FILE} DATA REGEX "^ ', optionally followed by a TAB and the tool's '-type' +## sub-modes. ToolHandler reads it with CsvFile, which drops '#' comment lines; a line that is +## neither a comment nor a well-formed entry is one ToolHandler would log and skip, so the +## tool it was meant to register would silently not exist. +set(registered) +set(_line_number 0) +foreach(_line IN LISTS _lines) + math(EXPR _line_number "${_line_number} + 1") + if(_line MATCHES "^#" OR _line STREQUAL "") + continue() + endif() + ## CMake cannot match a literal tab portably inside a bracket expression, so split on it. + string(REPLACE "\t" ";" _fields "${_line}") + list(LENGTH _fields _field_count) + if(_field_count LESS 2) + message(FATAL_ERROR "'${REGISTRY_FILE}' line ${_line_number} is not a registry entry: '${_line}'. " + "Expected ''; ToolHandler skips such a line, so the tool " + "would not be registered at all.") + endif() + list(GET _fields 0 _name) + list(GET _fields 1 _category) + if(_name STREQUAL "" OR _category STREQUAL "") + message(FATAL_ERROR "'${REGISTRY_FILE}' line ${_line_number} has an empty name or category: '${_line}'.") + endif() + if(_name IN_LIST registered) + message(FATAL_ERROR "'${_name}' is registered twice in '${REGISTRY_FILE}'. ToolHandler throws on a duplicate " + "name, which stops every TOPP tool from starting.") + endif() + list(APPEND registered "${_name}") +endforeach() + +## The generated file has to carry the declarations across intact. This cannot fail through a +## forgotten declaration (the same list builds the executables), but it does catch the +## generator dropping or truncating entries. +set(built ${TOOLS}) +list(REMOVE_DUPLICATES built) + +set(missing ${built}) +list(REMOVE_ITEM missing ${registered}) +if(missing) + string(REPLACE ";" ", " _list "${missing}") + message(FATAL_ERROR "The generated registry '${REGISTRY_FILE}' is missing tools this build declares: ${_list}. " + "openms_write_tool_registry() lost them.") +endif() + +set(unexpected ${registered}) +list(REMOVE_ITEM unexpected ${built}) +if(unexpected) + string(REPLACE ";" ", " _list "${unexpected}") + message(FATAL_ERROR "The generated registry '${REGISTRY_FILE}' lists tools this build does not declare: ${_list}. " + "A registered tool without a binary is offered by TOPPAS and cannot run.") +endif() + +list(LENGTH registered _count) +message(STATUS "Generated TOPP tool registry is readable and carries all ${_count} declared tools.") diff --git a/src/tests/toppas/CMakeLists.txt b/src/tests/toppas/CMakeLists.txt index 7c7850475..01e5a2f0e 100644 --- a/src/tests/toppas/CMakeLists.txt +++ b/src/tests/toppas/CMakeLists.txt @@ -1,116 +1,100 @@ +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# -------------------------------------------------------------------------- +# $Maintainer: Chris Bielow $ +# $Authors: Chris Bielow, Stephan Aiche $ +# -------------------------------------------------------------------------- + project("OpenMS_TOPPAS_tests") ################################## README ###################################### # -# This file contains the declarations of the pipeline tests executed by CTest -# -# It is included when -D ENABLE_PIPELINE_TESTING is 'On'. -# -# In order to add a test, do the following: -# -# - Add a subfolder named 'WorkflowName' to source/TEST/TOPPAS -# -# - Put your workflow there, together with the input data -# (please use the same directory structure as in the existing workflow tests) -# -# - Add the corresponding test declarations below. -# -# Please note: -# -# Test names (the first argument of 'add_test') must be distinct. -# If not, the first definition of the test is overwritten! -# -# Therefore, we use the following naming scheme for TOPPAS pipeline tests: -# -# -# TOPPAS_[_out] -# -# -# 'TOPPAS_' This prefix is used to distinguish TOPPAS, TOPP, and OpenMS tests -# in the nightly builds. -# -# The workflow name used to identify failed TOPP tool tests during -# the nightly builds. -# -# '_out' When a result file of a workflow is checked with FuzzyDiff, this -# suffix is appended. -# -# If n output files of a workflow need to be checked, the checks are -# numbered from 1 to n. +# Pipeline tests: ExecutePipeline (TOPPAS without the window) runs TOPPAS workflows. # -# When the result file of a test is checked with FuzzyDiff, we need to set the -# dependency to the test that generates the output file by +# The TOPP tools of a workflow are those of the OpenMS core, which ExecutePipeline +# finds next to itself, in the bin/ of the OpenMS installation it was linked against, +# or on PATH (OPENMS_TOPP_BIN_DIR, see src/tests/CMakeLists.txt). A workflow whose +# tools are not all in OPENMS_TOPP_BIN_DIR is left out with a message. # -# set_tests_properties("TOPPAS__out" PROPERTIES DEPENDS "TOPPAS_") +# Naming scheme: TOPP_ExecutePipeline_[_out]. When a result file of a +# workflow is checked with FuzzyDiff, the check DEPENDS on the workflow test. # ################################################################################ -if(WITH_GUI) # ExecutePipeline requires Qt/GUI internally - - ### General settings - set(TOPP_BIN_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - set(DATA_DIR_TOPPAS ${PROJECT_SOURCE_DIR}) - set(DATA_DIR_TOPPAS_BIN ${PROJECT_BINARY_DIR}) - set(DIFF ${TOPP_BIN_PATH}/FuzzyDiff -test -whitelist "IdentificationRun " "SearchParameters " "xml-stylesheet") +set(DATA_DIR_TOPPAS ${PROJECT_SOURCE_DIR}) +set(DATA_DIR_TOPPAS_BIN ${PROJECT_BINARY_DIR}) + +## The example workflows of the OpenMS core (share/OpenMS/examples/TOPPAS, install +## component 'examples'). They refer to the data next to them and in the other examples, +## so the whole examples directory of an installation is needed. +set(OPENMS_EXAMPLES_DIR "${OPENMS_DATA_DIR}/examples" CACHE PATH + "The examples directory of the OpenMS core (share/OpenMS/examples), whose TOPPAS workflows the pipeline tests run.") + +# _openms_gui_missing_tools( ) +# The TOPP tools of the workflow that are neither in OPENMS_TOPP_BIN_DIR nor built here. +function(_openms_gui_missing_tools out_var workflow) + file(STRINGS "${workflow}" _tool_lines REGEX "/share/OpenMS/examples, to make sure they are up to date - file(GLOB TOPPAS_EXAMPLE_PIPELINES ${CF_OPENMS_DATA_PATH}/examples/TOPPAS/*.toppas) - foreach(I_PIPELINE_PATH ${TOPPAS_EXAMPLE_PIPELINES}) - get_filename_component(I_FILE "${I_PIPELINE_PATH}" NAME) - message(STATUS "Adding TOPPAS example pipeline for testing: ${I_FILE} [WITH_GUI + ENABLE_PIPELINE_TESTING]") - add_test("TOPP_ExecutePipeline_${I_FILE}" ${TOPP_BIN_PATH}/ExecutePipeline -test -in "${I_PIPELINE_PATH}" -out_dir . -num_jobs 4) - ## allow failure if search engines (comet.exe etc) are not found - set_tests_properties("TOPP_ExecutePipeline_${I_FILE}" PROPERTIES SKIP_RETURN_CODE 14) ## EXTERNAL_PROGRAM_NOTFOUND - endforeach() - - - - ################### Labelfree quantification with IDMapping #################### - - ## Example usage (no actual test or data currently available) - - ## Test workflow - # set(WF_NAME "LF_Quant") - # add_test("TOPPAS_${WF_NAME}" ${TOPP_BIN_PATH}/ExecutePipeline -in ${DATA_DIR_TOPPAS}/${WF_NAME}/labelfree_quant_only.toppas -out_dir ${DATA_DIR_TOPPAS}/${WF_NAME}) - # set_tests_properties("TOPPAS_${WF_NAME}" PROPERTIES TIMEOUT 1800) - - ## Check output - # set(OF_PATH "003-FeatureFinderCentroided/labelfree_1.featureXML") - # add_test("TOPPAS_${WF_NAME}_out1" ${DIFF} -in1 ${DATA_DIR_TOPPAS}/${WF_NAME}/TOPPAS_out/${OF_PATH} -in2 ${DATA_DIR_TOPPAS}/${WF_NAME}/desired_output/${OF_PATH}) - # set_tests_properties("TOPPAS_${WF_NAME}_out1" PROPERTIES DEPENDS "TOPPAS_${WF_NAME}") - - # set(OF_PATH "003-FeatureFinderCentroided/labelfree_2.featureXML") - # add_test("TOPPAS_${WF_NAME}_out2" ${DIFF} -in1 ${DATA_DIR_TOPPAS}/${WF_NAME}/TOPPAS_out/${OF_PATH} -in2 ${DATA_DIR_TOPPAS}/${WF_NAME}/desired_output/${OF_PATH}) - # set_tests_properties("TOPPAS_${WF_NAME}_out2" PROPERTIES DEPENDS "TOPPAS_${WF_NAME}") - - # set(OF_PATH "003-FeatureFinderCentroided/labelfree_3.featureXML") - # add_test("TOPPAS_${WF_NAME}_out3" ${DIFF} -in1 ${DATA_DIR_TOPPAS}/${WF_NAME}/TOPPAS_out/${OF_PATH} -in2 ${DATA_DIR_TOPPAS}/${WF_NAME}/desired_output/${OF_PATH}) - # set_tests_properties("TOPPAS_${WF_NAME}_out3" PROPERTIES DEPENDS "TOPPAS_${WF_NAME}") - - # set(OF_PATH "010-ConsensusMapNormalizer/labelfree_1.consensusXML") - # add_test("TOPPAS_${WF_NAME}_out4" ${DIFF} -in1 ${DATA_DIR_TOPPAS}/${WF_NAME}/TOPPAS_out/${OF_PATH} -in2 ${DATA_DIR_TOPPAS}/${WF_NAME}/desired_output/${OF_PATH}) - # set_tests_properties("TOPPAS_${WF_NAME}_out4" PROPERTIES DEPENDS "TOPPAS_${WF_NAME}") - - # set(OF_PATH "011-FeatureLinkerUnlabeledQT/labelfree_1.consensusXML") - # add_test("TOPPAS_${WF_NAME}_out5" ${DIFF} -in1 ${DATA_DIR_TOPPAS}/${WF_NAME}/TOPPAS_out/${OF_PATH} -in2 ${DATA_DIR_TOPPAS}/${WF_NAME}/desired_output/${OF_PATH}) - # set_tests_properties("TOPPAS_${WF_NAME}_out5" PROPERTIES DEPENDS "TOPPAS_${WF_NAME}") + list(APPEND _pipeline_tests "TOPP_ExecutePipeline_1") +endif() +#------------------------------------------------------------------------------ +## Run the example workflows of the OpenMS core, to make sure they are up to date +file(GLOB TOPPAS_EXAMPLE_PIPELINES "${OPENMS_EXAMPLES_DIR}/TOPPAS/*.toppas") +if(NOT TOPPAS_EXAMPLE_PIPELINES) + message(STATUS "The TOPPAS example pipeline tests are left out: no workflows in '${OPENMS_EXAMPLES_DIR}/TOPPAS' (the OpenMS installation has no examples; set OPENMS_EXAMPLES_DIR).") +endif() +foreach(I_PIPELINE_PATH ${TOPPAS_EXAMPLE_PIPELINES}) + get_filename_component(I_FILE "${I_PIPELINE_PATH}" NAME) + _openms_gui_missing_tools(_missing "${I_PIPELINE_PATH}") + if(_missing) + list(JOIN _missing ", " _missing) + message(STATUS "TOPP_ExecutePipeline_${I_FILE} is left out: ${_missing} not found in OPENMS_TOPP_BIN_DIR='${OPENMS_TOPP_BIN_DIR}'.") + continue() endif() - - -################################################################################ - - + ## each workflow writes its results (TOPPAS_out/--...) into a directory + ## of its own, so workflows run in parallel cannot overwrite each other's files + set(_workflow_dir "${DATA_DIR_TOPPAS_BIN}/${I_FILE}.out") + file(MAKE_DIRECTORY "${_workflow_dir}") + add_test(NAME "TOPP_ExecutePipeline_${I_FILE}" COMMAND ExecutePipeline -test -in "${I_PIPELINE_PATH}" -out_dir . -num_jobs 4 + WORKING_DIRECTORY "${_workflow_dir}") + ## allow failure if search engines (comet.exe etc) are not found + set_tests_properties("TOPP_ExecutePipeline_${I_FILE}" PROPERTIES SKIP_RETURN_CODE 14) ## EXTERNAL_PROGRAM_NOTFOUND + list(APPEND _pipeline_tests "TOPP_ExecutePipeline_${I_FILE}") +endforeach() + +if(_pipeline_tests) + openms_gui_test_environment(${_pipeline_tests}) endif() diff --git a/tools/MSWindowsOS_OpenMS_file_associations_modify.reg b/tools/MSWindowsOS_OpenMS_file_associations_modify.reg new file mode 100644 index 000000000..87d97cf4a Binary files /dev/null and b/tools/MSWindowsOS_OpenMS_file_associations_modify.reg differ diff --git a/tools/ci/deps-macos.sh b/tools/ci/deps-macos.sh new file mode 100755 index 000000000..1e66d47db --- /dev/null +++ b/tools/ci/deps-macos.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# The dependencies of OpenMS-TOPP-GUI on macOS (Homebrew). The OpenMS core is +# not installed here: build it from source or use the OpenMS SDK (see README.md). + +set -eu +set -o pipefail + +SKIP_DOC_DEPS=false + +while [[ $# -gt 0 ]]; do + case $1 in + --skip-doc-deps) + SKIP_DOC_DEPS=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--skip-doc-deps]" + exit 1 + ;; + esac +done + +# GitHub's macOS runners come with packages (e.g. Python) whose links brew would +# refuse to overwrite. This wrapper adds --overwrite to every brew install below. +function brew() { + local action=$1 + shift + + # macOS' bash 3.2 rejects empty arrays under 'set -u', so the action is the + # first element of the flags + local -a flags=("$action") + + if [ "$action" = "install" ]; then + flags+=("--overwrite") + fi + + command brew "${flags[@]}" "$@" +} + +# Code between the following doxygen markers are included in the +# public-facing installation instructions. + +# [installation_documentation] +# Update the package index: +brew update + +# Build tools and Qt: +brew install \ + ninja \ + qtbase \ + qtsvg + +# libomp: the OpenMP runtime of an OpenMS built from source with Homebrew's libomp +# (the macOS presets point OpenMP_ROOT at /opt/homebrew/opt/libomp). The OpenMS SDK +# bundles its own and does not need it. +brew install libomp + +# Optional documentation dependencies: +if [ "$SKIP_DOC_DEPS" = false ]; then + brew install \ + doxygen +fi +# [installation_documentation] + +# These are only needed in CI: +brew install \ + ccache diff --git a/tools/ci/deps-ubuntu.sh b/tools/ci/deps-ubuntu.sh new file mode 100755 index 000000000..3d8725509 --- /dev/null +++ b/tools/ci/deps-ubuntu.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# The dependencies of OpenMS-TOPP-GUI on Ubuntu (24.04: Qt 6.4). +# The OpenMS core is not installed here: build it from source or use the OpenMS +# SDK (see README.md). + +set -eu +set -o pipefail + +SKIP_DOC_DEPS=false + +while [[ $# -gt 0 ]]; do + case $1 in + --skip-doc-deps) + SKIP_DOC_DEPS=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--skip-doc-deps]" + exit 1 + ;; + esac +done + +# Code between the following doxygen markers are included in the +# public-facing installation instructions. + +# [installation_documentation] +# Add "universe" and update: +sudo add-apt-repository -y universe +sudo apt-get -qq update + +# Build tools: +sudo apt-get -qq install -y \ + build-essential \ + cmake \ + git \ + ninja-build \ + pkg-config + +# Qt (libqt6test6: QtTest, for the tests of the GUI): +sudo apt-get -qq install -y \ + qt6-base-dev \ + libqt6svg6-dev \ + libqt6opengl6-dev \ + libqt6openglwidgets6 \ + libqt6test6 + +# Optional documentation dependencies: +if [ "$SKIP_DOC_DEPS" = false ]; then + sudo apt-get -qq install -y \ + doxygen +fi +# [installation_documentation] + +# These are only needed in CI: +sudo apt-get -qq install -y \ + ccache diff --git a/tools/ci/deps-windows.sh b/tools/ci/deps-windows.sh new file mode 100755 index 000000000..1fedbcdd9 --- /dev/null +++ b/tools/ci/deps-windows.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# The build tools of OpenMS-TOPP-GUI on Windows (Chocolatey). Qt comes from the +# Qt online installer or aqtinstall (CI: jurplel/install-qt-action), the OpenMS +# core from a source build or the OpenMS SDK (see README.md). + +set -eu +set -o pipefail + +SKIP_DOC_DEPS=false + +while [[ $# -gt 0 ]]; do + case $1 in + --skip-doc-deps) + SKIP_DOC_DEPS=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--skip-doc-deps]" + exit 1 + ;; + esac +done + +# Code between the following doxygen markers are included in the +# public-facing installation instructions. + +# [installation_documentation] +choco install -y --no-progress \ + cmake \ + ninja +# [installation_documentation] + +# doxygen and ccache come from the packages of OpenMS/chocolatey-packages +# (SHA256-checked) rather than from the Chocolatey community feed, which fails +# too often. +work_dir=$(mktemp -d) +git clone https://github.com/OpenMS/chocolatey-packages.git "$work_dir/chocolatey-packages" +(cd "$work_dir/chocolatey-packages" && make) +packages=(ccache) +if [ "$SKIP_DOC_DEPS" = false ]; then + packages+=(doxygen) +fi +(cd "$work_dir/chocolatey-packages/build" && + choco install -y --no-progress -s . "${packages[@]}") + +# A custom NSIS, which provides 8-k string support and has UltraModernUI +# integrated already (for the installer, PACKAGE_TYPE=nsis): +curl --no-progress-meter --fail --retry 3 --retry-all-errors -L -o "$work_dir/NSIS.tar.gz" https://raw.githubusercontent.com/OpenMS/NSIS/main/NSIS.tar.gz +7z x -so "$work_dir/NSIS.tar.gz" | 7z x -si -ttar -aoa -o"C:/Program Files (x86)/NSIS/" +rm -rf "$work_dir" diff --git a/tools/update_version_numbers.sh b/tools/update_version_numbers.sh new file mode 100755 index 000000000..901de0f5c --- /dev/null +++ b/tools/update_version_numbers.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin +# SPDX-License-Identifier: BSD-3-Clause +# +# Set the version of OpenMS-TOPP-GUI. It is always the version of the OpenMS core +# the GUI layer is built against (find_package(OpenMS EXACT)), so it is +# changed together with the version of OpenMS. + +################################################################################ +set -eu +set -o pipefail + +################################################################################ +usage() { + cat <&2 "ERROR: please provide three numbers (use -h for more info)" + exit 1 +fi + +for number in "$@"; do + if ! [[ "$number" =~ ^[0-9]+$ ]]; then + echo >&2 "ERROR: '$number' is not a number" + exit 1 + fi +done + +if [ ! -f CMakeLists.txt ] || ! grep -q '^set(OPENMS_GUI_VERSION ' CMakeLists.txt; then + echo >&2 "ERROR: run this script from the top-level directory of OpenMS-TOPP-GUI" + exit 1 +fi + +################################################################################ +package_version="$1.$2.$3" + +echo "Setting version $package_version" + +################################################################################ +# the version of the project (and of the OpenMS core it requires) +sed -i -e "s#^set(OPENMS_GUI_VERSION .*)#set(OPENMS_GUI_VERSION $package_version)#" CMakeLists.txt + +# the INI files of the tests, which carry the version of the tool that wrote them +find ./src/tests/topp/ \ + -type f \ + -name '*.ini' \ + -exec grep -q "/dev/null; then + sed -i -e "s#^version:.*#version: \"$package_version\"#" CITATION.cff +fi + +################################################################################ +# A new section at the top of the CHANGELOG (above its first line of dashes), +# unless it has one for this version already. sed wants a backslash at the end of +# every line of the inserted text but the last. +if grep -qE "^----[[:space:]]+OpenMS-TOPP-GUI ${package_version//./\\.}[[:space:]]" CHANGELOG; then + echo "CHANGELOG already has a section for $package_version" +else + section_header=$( + sed -Ee '$!s/$/\\/' <<<" +------------------------------------------------------------------------------------------ +---- OpenMS-TOPP-GUI ${package_version} (under development) ---- +---- requires OpenMS ${package_version} ---- +------------------------------------------------------------------------------------------ + + " # NOTE: this line should start with a single blank space! + ) + + # the line number of the first line starting with more than 10 "-" + line_num=$( + grep \ + --extended-regexp \ + --line-number \ + --max-count=1 "^-{10,}" CHANGELOG | + cut -d: -f1 + ) + + sed -i \ + -e "${line_num}i $section_header" \ + CHANGELOG +fi + +################################################################################ +echo "done."