#     Copyright 2026, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file


"""Pack and copy files for standalone mode.

This is expected to work for macOS, Windows, and Linux. Other things like
FreeBSD are also very welcome, but might break with time and need your
help.
"""

import os

from nuitka.containers.OrderedSets import OrderedSet
from nuitka.Errors import NuitkaForbiddenDLLEncounter, NuitkaNotYetSupported
from nuitka.importing.Importing import (
    getPythonUnpackedSearchPath,
    locateModule,
)
from nuitka.importing.StandardLibrary import isStandardLibraryPath
from nuitka.options.Options import (
    isShowProgress,
    shallCreateAppBundle,
    shallNotStoreDependsExeCachedResults,
    shallNotUseDependsExeCachedResults,
)
from nuitka.plugins.Hooks import (
    decideAllowOutsideDependencies,
    onCopiedDLLs,
    removeDllDependencies,
)
from nuitka.Progress import (
    closeProgressBar,
    reportProgressBar,
    setupProgressBar,
)
from nuitka.PythonFlavors import (
    getHomebrewInstallPath,
    getMacPortsInstallPath,
    isAnacondaPython,
    isCPythonOfficialPackage,
    isHomebrewPython,
    isMacPortsPython,
    isMonolithPy,
    isMSYS2MingwPython,
    isPyenvHomebrewPython,
    isPythonBuildStandalonePython,
)
from nuitka.PythonVersions import getSystemPrefixPath
from nuitka.Tracing import general, inclusion_logger
from nuitka.utils.Execution import executeToolChecked
from nuitka.utils.FileOperations import (
    areInSamePaths,
    areSamePaths,
    deleteFile,
    getFileList,
    getNormalizedPathJoin,
    getSubDirectories,
    isFilenameBelowPath,
    isLink,
    listDir,
    makePath,
    relpath,
    removeDirectory,
    withMadeWritableFileMode,
)
from nuitka.utils.SharedLibraries import (
    copyDllFile,
    getStandaloneEntryPointRPATHs,
    setSharedLibraryRPATH,
)
from nuitka.utils.Signing import addMacOSCodeSignature
from nuitka.utils.Timing import TimerReport
from nuitka.utils.Utils import (
    getOS,
    isDebianBasedLinux,
    isElfUsingPlatform,
    isMacOS,
    isPosixWindows,
    isRPathUsingPlatform,
    isWin32Windows,
)

from .DllDependenciesMacOS import (
    detectBinaryPathDLLsMacOS,
    fixupBinaryDLLPathsMacOS,
)
from .DllDependenciesPosix import detectBinaryPathDLLsPosix
from .DllDependenciesWin32 import (
    detectBinaryPathDLLsWin32,
    shallIncludeVCRedistDLL,
)
from .IncludedDataFiles import getIncludedFrameworkDistPathFromSourcePath
from .IncludedEntryPoints import (
    addIncludedEntryPoint,
    getIncludedExtensionModule,
    getStandaloneEntryPointForSourceFile,
    makeDllEntryPoint,
)
from .MacOSApp import createEntitlementsInfoFile


def checkFreezingModuleSet():
    """Check the module set for troubles.

    Typically Linux OS specific packages must be avoided, e.g. Debian packaging
    does make sure the packages will not run on other OSes.
    """
    # Cyclic dependency
    from nuitka import ModuleRegistry

    problem_modules = OrderedSet()

    if isDebianBasedLinux():
        message = "Standalone with Python package from Debian installation may not be working."
        mnemonic = "debian-dist-packages"

        def checkModulePath(module):
            module_filename = module.getCompileTimeFilename()
            module_filename_parts = module_filename.split("/")

            if (
                "dist-packages" in module_filename_parts
                and "local" not in module_filename_parts
            ):
                module_name = module.getFullName()

                package_name = module_name.getTopLevelPackageName()

                if package_name is not None:
                    problem_modules.add(package_name)
                else:
                    problem_modules.add(module_name)

    else:
        checkModulePath = None
        message = None
        mnemonic = None

    # We intend for other platforms to join, e.g. Fedora, etc. but currently
    # only Debian is done.
    if checkModulePath is not None:
        for module in ModuleRegistry.getDoneModules():
            if not module.getFullName().isFakeModuleName():
                checkModulePath(module)

    if problem_modules:
        general.info("Using Debian packages for '%s'." % ",".join(problem_modules))
        general.warning(message=message, mnemonic=mnemonic)


def _detectBinaryDLLs(
    is_main_executable,
    source_dir,
    original_filename,
    binary_filename,
    package_name,
    use_path,
    use_cache,
    update_cache,
):
    """Detect the DLLs used by a binary.

    Using "ldd" (Linux), "depends.exe" (Windows), or
    "otool" (macOS) the list of used DLLs is retrieved.
    """

    if is_main_executable and isMonolithPy():
        return OrderedSet()
    elif (
        getOS() in ("Linux", "NetBSD", "FreeBSD", "OpenBSD", "AIX") or isPosixWindows()
    ):
        return detectBinaryPathDLLsPosix(
            dll_filename=original_filename,
            package_name=package_name,
            original_dir=os.path.dirname(original_filename),
        )
    elif isWin32Windows():
        with TimerReport(
            logger=inclusion_logger,
            message="Detecting dependencies for %s took %%.2f seconds"
            % binary_filename,
            decider=isShowProgress,
        ):
            return detectBinaryPathDLLsWin32(
                is_main_executable=is_main_executable,
                source_dir=source_dir,
                original_dir=os.path.dirname(original_filename),
                binary_filename=binary_filename,
                package_name=package_name,
                use_path=use_path,
                use_cache=use_cache,
                update_cache=update_cache,
            )
    elif isMacOS():
        return detectBinaryPathDLLsMacOS(
            original_dir=os.path.dirname(original_filename),
            binary_filename=original_filename,
            package_name=package_name,
            keep_unresolved=False,
            recursive_dlls=OrderedSet(),
            parent_rpaths=OrderedSet(),
        )
    else:
        # Support your platform above, for many platforms the POSIX branch will
        # be working and its parser for "ldd" just need fixes for specialties
        # maybe.
        raise NuitkaNotYetSupported(
            "DLL dependency detection for '%s', please help adding it." % getOS()
        )


def copyDllsUsed(dist_dir, standalone_entry_points):
    # Only do ones not ignored
    copy_standalone_entry_points = [
        standalone_entry_point
        for standalone_entry_point in standalone_entry_points[1:]
        if not standalone_entry_point.kind.endswith("_ignored")
        if "copy" in standalone_entry_point.tags
    ]
    main_standalone_entry_point = standalone_entry_points[0]

    if isMacOS():
        fixupBinaryDLLPathsMacOS(
            binary_filename=getNormalizedPathJoin(
                dist_dir, main_standalone_entry_point.dest_path
            ),
            package_name=main_standalone_entry_point.package_name,
            original_location=main_standalone_entry_point.source_path,
            standalone_entry_points=standalone_entry_points,
            removed_dll_paths=getRemovedUsedDllPaths(main_standalone_entry_point),
        )

    # After dependency detection, we can change the RPATH for main binary.
    if isRPathUsingPlatform():
        main_binary_rpath = "$ORIGIN"

        if isElfUsingPlatform():
            # Allow basename based "dlopen()" calls from the program to find
            # copied shared libraries below the distribution root.
            main_binary_rpath = ":".join(
                getStandaloneEntryPointRPATHs(
                    dest_path=standalone_entry_points[0].dest_path,
                    other_entry_points=copy_standalone_entry_points,
                    rpath_mode="dirs",
                )
            )

        setSharedLibraryRPATH(
            getNormalizedPathJoin(dist_dir, standalone_entry_points[0].dest_path),
            main_binary_rpath,
        )

    setupProgressBar(
        stage="Copying used DLLs",
        unit="DLL",
        total=len(copy_standalone_entry_points),
    )

    for standalone_entry_point in copy_standalone_entry_points:
        reportProgressBar(standalone_entry_point.dest_path)

        copyDllFile(
            source_path=standalone_entry_point.source_path,
            dist_dir=dist_dir,
            dest_path=standalone_entry_point.dest_path,
            executable=standalone_entry_point.executable,
            other_entry_points=copy_standalone_entry_points,
        )

        if isMacOS():
            fixupBinaryDLLPathsMacOS(
                binary_filename=getNormalizedPathJoin(
                    dist_dir, standalone_entry_point.dest_path
                ),
                package_name=standalone_entry_point.package_name,
                original_location=standalone_entry_point.source_path,
                standalone_entry_points=standalone_entry_points,
                removed_dll_paths=getRemovedUsedDllPaths(standalone_entry_point),
            )

    closeProgressBar()

    if isMacOS():
        # Some dependencies, e.g. copied libpython variants, are only fully
        # resolvable once their standalone copies exist already.
        fixupBinaryDLLPathsMacOS(
            binary_filename=getNormalizedPathJoin(
                dist_dir, main_standalone_entry_point.dest_path
            ),
            package_name=main_standalone_entry_point.package_name,
            original_location=main_standalone_entry_point.source_path,
            standalone_entry_points=standalone_entry_points,
            removed_dll_paths=getRemovedUsedDllPaths(main_standalone_entry_point),
        )

    onCopiedDLLs(
        dist_dir=dist_dir,
        standalone_entry_points=copy_standalone_entry_points,
    )

    return main_standalone_entry_point, copy_standalone_entry_points


def signDistributionMacOS(
    dist_dir, data_file_paths, main_standalone_entry_point, copy_standalone_entry_points
):
    # Make all top level directories symlinks for signing issues with MacOS
    # bundles. Also strip all extended attributes. This is complex, because we
    # also need to handle the need to symlink and track information.

    filenames_to_make_writable = getFileList(dist_dir) + getSubDirectories(dist_dir)
    translations = ()
    framework_paths = ()

    with withMadeWritableFileMode(filenames_to_make_writable):
        # spell-checker: ignore xattr
        executeToolChecked(
            logger=inclusion_logger,
            command=("/usr/bin/xattr", "-cr", dist_dir),
            absence_message="needs 'xattr' to remove extended attributes",
        )

        if shallCreateAppBundle():
            translations, framework_paths = _relocateMacOSAppBundleDirectories(
                dist_dir=dist_dir, data_file_paths=data_file_paths
            )

    addMacOSCodeSignature(
        filenames=_getMacOSCodeSigningPaths(
            dist_dir=dist_dir,
            data_file_paths=data_file_paths,
            translations=translations,
            framework_paths=framework_paths,
            main_standalone_entry_point=main_standalone_entry_point,
            copy_standalone_entry_points=copy_standalone_entry_points,
        ),
        entitlements_filename=createEntitlementsInfoFile(),
    )


def _translateMacOSCodeSigningPath(path, translations):
    for old_path, new_path in translations:
        if path == old_path:
            return new_path

        path = path.replace(old_path + "/", new_path + "/", 1)

    return path


def _getMacOSNotSignableDirectoryPart(filename):
    result = []

    for part in os.path.dirname(filename).split("/"):
        result.append(part)
        if "." in part:
            return "/".join(result)

    return None


def _relocateMacOSAppBundleDirectories(dist_dir, data_file_paths):
    app_path = getNormalizedPathJoin(dist_dir, "..", "..")
    frameworks_dir = getNormalizedPathJoin(dist_dir, "..", "Frameworks")
    resources_dir = getNormalizedPathJoin(dist_dir, "..", "Resources")

    translations = OrderedSet()
    symlinks = OrderedSet()

    for data_file_path in sorted(data_file_paths, key=len):
        data_file_path = _translateMacOSCodeSigningPath(data_file_path, translations)

        inside_path = relpath(data_file_path, start=app_path)

        if not inside_path.startswith("Contents/MacOS"):
            continue

        not_signable_part = _getMacOSNotSignableDirectoryPart(inside_path)
        if not_signable_part is None:
            continue

        filename = not_signable_part[len("Contents/MacOS/") :]
        not_signable_path = getNormalizedPathJoin(app_path, not_signable_part)
        if filename.endswith(".framework"):
            relocated_path = getNormalizedPathJoin(frameworks_dir, filename)
            symlink_target = getNormalizedPathJoin("..", "Frameworks", filename)
        else:
            relocated_path = getNormalizedPathJoin(resources_dir, filename)
            symlink_target = getNormalizedPathJoin("..", "Resources", filename)

        if relocated_path in symlinks:
            continue

        makePath(os.path.dirname(relocated_path))
        os.rename(not_signable_path, relocated_path)

        for _i in range(filename.count("/")):
            symlink_target = getNormalizedPathJoin("..", symlink_target)

        os.symlink(symlink_target, not_signable_path)

        symlinks.add(relocated_path)
        translations.add((not_signable_path, relocated_path))

    return translations, _normalizeMacOSFrameworkBundleLayouts(
        frameworks_dir=frameworks_dir
    )


def _getMacOSCodeSigningPaths(
    dist_dir,
    data_file_paths,
    translations,
    framework_paths,
    main_standalone_entry_point,
    copy_standalone_entry_points,
):
    filenames_to_sign = OrderedSet()

    for filename in [
        getNormalizedPathJoin(dist_dir, standalone_entry_point.dest_path)
        for standalone_entry_point in [main_standalone_entry_point]
        + copy_standalone_entry_points
    ] + data_file_paths:
        filename = _translateMacOSCodeSigningPath(filename, translations)

        if not os.path.exists(filename):
            continue

        if framework_paths and isFilenameBelowPath(
            path=framework_paths, filename=filename
        ):
            continue

        if "/_CodeSignature/" in filename:
            continue

        filenames_to_sign.add(filename)

    for framework_path in sorted(framework_paths, key=len, reverse=True):
        if os.path.exists(framework_path):
            filenames_to_sign.add(framework_path)

    return filenames_to_sign


def _removeMacOSFrameworkBundlePath(path):
    if isLink(path) or os.path.isfile(path):
        deleteFile(path, must_exist=False)
    elif os.path.isdir(path):
        removeDirectory(
            path=path,
            logger=inclusion_logger,
            ignore_errors=False,
            extra_recommendation=None,
        )


def _makeMacOSFrameworkBundleSymlink(link_path, link_target):
    if isLink(link_path) and os.readlink(link_path) == link_target:
        return

    if os.path.exists(link_path) or isLink(link_path):
        _removeMacOSFrameworkBundlePath(link_path)

    os.symlink(link_target, link_path)


def _detectMacOSFrameworkCurrentVersion(framework_path):
    framework_name = os.path.basename(framework_path)[: -len(".framework")]
    versions_dir = getNormalizedPathJoin(framework_path, "Versions")

    if not os.path.isdir(versions_dir):
        return None, None

    version_candidates = []

    for version_path, version_name in listDir(versions_dir):
        if version_name == "Current":
            continue

        if not os.path.isdir(version_path):
            continue

        if os.path.exists(getNormalizedPathJoin(version_path, framework_name)):
            version_candidates.append((version_name, version_path))

    if not version_candidates:
        return None, None

    current_path = getNormalizedPathJoin(versions_dir, "Current")

    if isLink(current_path):
        current_target_path = getNormalizedPathJoin(
            versions_dir, os.readlink(current_path)
        )

        for version_name, version_path in version_candidates:
            if areSamePaths(version_path, current_target_path):
                return version_name, version_path

    return version_candidates[0]


def _isMacOSFrameworkVersionPath(path, framework_name):
    return os.path.isdir(path) and os.path.exists(
        getNormalizedPathJoin(path, framework_name)
    )


def _normalizeMacOSFrameworkBundleLayout(framework_path):
    framework_name = os.path.basename(framework_path)[: -len(".framework")]

    version_name, version_path = _detectMacOSFrameworkCurrentVersion(
        framework_path=framework_path
    )

    if version_name is None:
        return

    top_level_code_signature_path = getNormalizedPathJoin(
        framework_path, "_CodeSignature"
    )

    if os.path.isdir(top_level_code_signature_path):
        _removeMacOSFrameworkBundlePath(top_level_code_signature_path)

    versions_dir = getNormalizedPathJoin(framework_path, "Versions")

    for version_entry_path, version_entry_name in listDir(versions_dir):
        if version_entry_name == "Current":
            continue

        if _isMacOSFrameworkVersionPath(version_entry_path, framework_name):
            continue

        _removeMacOSFrameworkBundlePath(version_entry_path)

    _makeMacOSFrameworkBundleSymlink(
        link_path=getNormalizedPathJoin(versions_dir, "Current"),
        link_target=version_name,
    )

    versioned_binary_path = getNormalizedPathJoin(version_path, framework_name)

    if os.path.exists(versioned_binary_path):
        _makeMacOSFrameworkBundleSymlink(
            link_path=getNormalizedPathJoin(framework_path, framework_name),
            link_target=getNormalizedPathJoin("Versions", "Current", framework_name),
        )

    for entry_path, entry_name in listDir(version_path):
        if entry_name == "_CodeSignature":
            continue

        if os.path.isdir(entry_path):
            _makeMacOSFrameworkBundleSymlink(
                link_path=getNormalizedPathJoin(framework_path, entry_name),
                link_target=getNormalizedPathJoin("Versions", "Current", entry_name),
            )


def _normalizeMacOSFrameworkBundleLayouts(frameworks_dir):
    if not os.path.isdir(frameworks_dir):
        return ()

    framework_paths = [
        sub_directory
        for sub_directory in getSubDirectories(frameworks_dir)
        if sub_directory.endswith(".framework")
    ]

    for framework_path in framework_paths:
        _normalizeMacOSFrameworkBundleLayout(framework_path=framework_path)

    return tuple(framework_paths)


_excluded_system_dlls = set()


def _reduceToPythonPath(used_dll_paths):
    """Remove DLLs outside of python path unless they are found in the MSVC Redist folder."""
    inside_paths = getPythonUnpackedSearchPath()

    if isAnacondaPython():
        inside_paths.insert(0, getSystemPrefixPath())

    if isMacOS() and (isCPythonOfficialPackage() or isPythonBuildStandalonePython()):
        inside_paths.insert(0, getSystemPrefixPath())

    if isMacPortsPython():
        inside_paths.insert(0, getMacPortsInstallPath())

    if isHomebrewPython() or isPyenvHomebrewPython():
        inside_paths.insert(0, getHomebrewInstallPath())

    if isMSYS2MingwPython():
        inside_paths.insert(0, getNormalizedPathJoin(getSystemPrefixPath(), "bin"))

    def decideInside(dll_filename):
        return any(
            isFilenameBelowPath(path=inside_path, filename=dll_filename)
            for inside_path in inside_paths
        )

    def decideMsvcRedistDll(dll_filename):
        return isWin32Windows() and shallIncludeVCRedistDLL(dll_filename)

    kept_used_dll_paths = OrderedSet()
    removed_dll_paths = OrderedSet()

    for dll_filename in used_dll_paths:
        if decideInside(dll_filename) or decideMsvcRedistDll(dll_filename):
            kept_used_dll_paths.add(dll_filename)
        else:
            if dll_filename not in _excluded_system_dlls:
                _excluded_system_dlls.add(dll_filename)
                inclusion_logger.debug("Not including system DLL '%s'" % dll_filename)

            removed_dll_paths.add(dll_filename)

    return kept_used_dll_paths, removed_dll_paths


_removed_dll_usages = {}


def getRemovedUsedDllsInfo():
    return _removed_dll_usages.items()


def getRemovedUsedDllPaths(standalone_entry_point):
    removed_dll_info = _removed_dll_usages.get(standalone_entry_point)

    if removed_dll_info is None:
        return ()
    else:
        _reason, removed_dll_paths = removed_dll_info
        return removed_dll_paths


def _detectUsedDLLs(standalone_entry_point, source_dir):
    # TODO: We are handling a bunch of cases here, but also some special cases,
    # that should live elsewhere.
    # pylint: disable=too-many-branches,too-many-locals

    if standalone_entry_point.module_name is not None:
        # For Linux Pythons, there can be DLLs to pick up from the system.
        if (
            not isWin32Windows()
            and not isMacOS()
            and isStandardLibraryPath(standalone_entry_point.source_path)
        ):
            allow_outside_dependencies = True
        else:
            allow_outside_dependencies = decideAllowOutsideDependencies(
                standalone_entry_point.module_name
            )
    else:
        allow_outside_dependencies = False

    binary_filename = standalone_entry_point.source_path
    try:
        used_dll_paths = _detectBinaryDLLs(
            is_main_executable=standalone_entry_point.kind == "executable",
            source_dir=source_dir,
            original_filename=standalone_entry_point.source_path,
            binary_filename=binary_filename,
            package_name=standalone_entry_point.package_name,
            use_path=allow_outside_dependencies,
            use_cache=not shallNotUseDependsExeCachedResults(),
            update_cache=not shallNotStoreDependsExeCachedResults(),
        )
    except NuitkaForbiddenDLLEncounter:
        inclusion_logger.info(
            "Not including due to forbidden DLL '%s'." % binary_filename
        )
    except (RuntimeError, Exception) as e:
        inclusion_logger.warning(
            """\
Error, cannot detect used DLLs for DLL '%s' in package '%s' due to: %s"""
            % (binary_filename, standalone_entry_point.package_name, str(e))
        )

        raise
    else:
        # Plugins generally decide if they allow dependencies from the outside
        # based on the package name.

        if standalone_entry_point.module_name is not None and used_dll_paths:
            module_name, _module_filename, _kind, finding = locateModule(
                standalone_entry_point.module_name, parent_package=None, level=0
            )

            # Make sure we are not surprised here.
            assert module_name == standalone_entry_point.module_name, (
                module_name,
                "!=",
                standalone_entry_point.module_name,
            )
            assert finding == "absolute", standalone_entry_point.module_name

            if not allow_outside_dependencies:
                used_dll_paths, removed_dll_paths = _reduceToPythonPath(
                    used_dll_paths=used_dll_paths
                )

                if removed_dll_paths:
                    _removed_dll_usages[standalone_entry_point] = (
                        "no outside dependencies allowed for '%s'"
                        % standalone_entry_point.module_name,
                        removed_dll_paths,
                    )

        # Lets be sure of this
        assert type(used_dll_paths) is OrderedSet, type(used_dll_paths)

        # Allow plugins can prevent inclusion, this may discard things from
        # used_dlls through its return value.
        removed_dlls = removeDllDependencies(
            dll_filename=binary_filename, dll_filenames=OrderedSet(used_dll_paths)
        )
        used_dll_paths = used_dll_paths - removed_dlls

        for used_dll_path in used_dll_paths:
            # Ignore frameworks dependencies on macOS, we don't add those automatically.
            if isMacOS() and getIncludedFrameworkDistPathFromSourcePath(used_dll_path):
                continue

            extension_standalone_entry_point = getIncludedExtensionModule(used_dll_path)
            if extension_standalone_entry_point is not None:
                # Sometimes an extension module is used like a DLL, make sure to
                # remove it as a DLL then, there is no value in keeping those. Need
                # to keep it's destination path from that extension module then.
                dest_path = extension_standalone_entry_point.dest_path
            elif (
                standalone_entry_point.package_name is not None
                and standalone_entry_point.package_name.hasOneOfNamespaces(
                    "openvino",
                    "av",
                )
                and areInSamePaths(binary_filename, used_dll_path)
            ):
                # TODO: If used by a DLL from the same folder, put it there,
                # otherwise top level, but for now this is limited to a few cases
                # where required that way (openvino) or known to be good only (av),
                # because it broke other things. spell-checker: ignore openvino

                dest_path = getNormalizedPathJoin(
                    os.path.dirname(standalone_entry_point.dest_path),
                    os.path.basename(used_dll_path),
                )
            else:
                existing_entry_point = getStandaloneEntryPointForSourceFile(
                    source_path=used_dll_path,
                    package_name=standalone_entry_point.package_name,
                )

                if existing_entry_point is None:
                    dest_path = os.path.basename(used_dll_path)
                else:
                    dest_path = existing_entry_point.dest_path

            dll_entry_point = makeDllEntryPoint(
                logger=inclusion_logger,
                source_path=used_dll_path,
                dest_path=dest_path,
                module_name=standalone_entry_point.module_name,
                package_name=standalone_entry_point.package_name,
                reason="Used by '%s'" % standalone_entry_point.dest_path,
            )

            addIncludedEntryPoint(dll_entry_point)


def detectUsedDLLs(standalone_entry_points, source_dir):
    setupProgressBar(
        stage="Detecting used DLLs",
        unit="DLL",
        total=len(standalone_entry_points),
    )

    for standalone_entry_point in standalone_entry_points:
        reportProgressBar(standalone_entry_point.dest_path)

        _detectUsedDLLs(
            standalone_entry_point=standalone_entry_point, source_dir=source_dir
        )

    closeProgressBar()


#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the GNU Affero General Public License, Version 3 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#        https://www.gnu.org/licenses/agpl-3.0.txt
#
#     See also: "Nuitka Runtime Library Exception, Version 1.0" in file
#     "LICENSE-RUNTIME.txt" for additional permissions granted under Section 7.
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
