Skip to content

sysconfig.get_path("purelib") returns site-packages folder instead of app_packages #2435

@timrid

Description

@timrid

Describe the bug

sysconfig.get_path("purelib") returns site-packages folder instead of app_packages

Some libraries access sysconfig.get_path("purelib") to get the path of the folder, where the 3rd party libraries are installed. E.g. pydevd (see here) or debugpy (see here)

Currently sysconfig.get_paths() returns on Windows

{
    'stdlib': '...\build\helloworld\windows\app\src\Lib',
    'platstdlib': '...\build\helloworld\windows\app\src\Lib',
    'purelib': '...\build\helloworld\windows\app\src\Lib\site-packages',
    'platlib': '...\build\helloworld\windows\app\src\Lib\site-packages',
    'include': '...\build\helloworld\windows\app\src\Include',
    'platinclude': '...\build\helloworld\windows\app\src\Include',
    'scripts': '...\build\helloworld\windows\app\src\Scripts',
    'data': '...\build\helloworld\windows\app\src'
}

But ...\build\helloworld\windows\app\src\Lib\site-packages does not exists, because all libraries are inside ...\build\helloworld\windows\app\src\app_packages.

Steps to reproduce

  1. Create an example briefcase app
  2. Add
import sysconfig
print(sysconfig.get_path("purelib"))
  1. Run briefcase run

Expected behavior

sysconfig.get_path("purelib") should return the path to the app_packages folder instead of the non existing site-packages folder.

Screenshots

No response

Environment

  • Operating System: Windows 11
  • Python version: 3.13
  • Software versions:
    • Briefcase: 0.3.24

Logs


Additional context

There has already been discussion about whether it makes sense to use the standard site-packages folder instead of a custom app_packages folder, and why it was rejected: #2195

I created a working sysconfig patch function that can may be used:

Details
def _patch_sysconfig():
    import sysconfig

    BRIEFCASE_SCHEME = "briefcase"

    sysconfig._INSTALL_SCHEMES[BRIEFCASE_SCHEME] = {
        "stdlib": "{installed_base}/Lib",
        "platstdlib": "{base}/Lib",
        "purelib": "{base}/app_packages",
        "platlib": "{base}/app_packages",
        "include": "{installed_base}/Include",
        "platinclude": "{installed_base}/Include",
        "scripts": "{base}/Scripts",
        "data": "{base}",
    }

    __orig_get_paths = sysconfig.get_paths
    __orig_get_path = sysconfig.get_path

    def get_default_scheme():
        return BRIEFCASE_SCHEME

    def get_paths(*args, **kwargs):
        if len(args) >= 1:
            new_args = list(args)
            if new_args[0] is None:
                new_args[0] = BRIEFCASE_SCHEME
            args = tuple(new_args)
        elif "scheme" not in kwargs or kwargs.get("scheme") is None:
            kwargs["scheme"] = BRIEFCASE_SCHEME
        return __orig_get_paths(*args, **kwargs)

    def get_path(*args, **kwargs):
        if len(args) >= 2:
            new_args = list(args)
            if new_args[1] is None:
                new_args[1] = BRIEFCASE_SCHEME
            args = tuple(new_args)
        elif "scheme" not in kwargs or kwargs.get("scheme") is None:
            kwargs["scheme"] = BRIEFCASE_SCHEME
        return __orig_get_path(*args, **kwargs)

    sysconfig.get_default_scheme = get_default_scheme
    sysconfig.get_paths = get_paths
    sysconfig.get_path = get_path

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA crash or error in behavior.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions