briefcase-debugger 0.3.26__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- briefcase_debugger-0.3.26/LICENSE +27 -0
- briefcase_debugger-0.3.26/PKG-INFO +69 -0
- briefcase_debugger-0.3.26/README.md +56 -0
- briefcase_debugger-0.3.26/pyproject.toml +55 -0
- briefcase_debugger-0.3.26/setup.cfg +4 -0
- briefcase_debugger-0.3.26/setup.py +44 -0
- briefcase_debugger-0.3.26/src/briefcase_debugger/__init__.py +44 -0
- briefcase_debugger-0.3.26/src/briefcase_debugger/config.py +21 -0
- briefcase_debugger-0.3.26/src/briefcase_debugger/debugpy.py +108 -0
- briefcase_debugger-0.3.26/src/briefcase_debugger/pdb.py +44 -0
- briefcase_debugger-0.3.26/src/briefcase_debugger.egg-info/PKG-INFO +69 -0
- briefcase_debugger-0.3.26/src/briefcase_debugger.egg-info/SOURCES.txt +17 -0
- briefcase_debugger-0.3.26/src/briefcase_debugger.egg-info/dependency_links.txt +1 -0
- briefcase_debugger-0.3.26/src/briefcase_debugger.egg-info/requires.txt +6 -0
- briefcase_debugger-0.3.26/src/briefcase_debugger.egg-info/top_level.txt +1 -0
- briefcase_debugger-0.3.26/tests/test_base.py +39 -0
- briefcase_debugger-0.3.26/tests/test_debugpy.py +197 -0
- briefcase_debugger-0.3.26/tests/test_debugpy_path_mappings.py +287 -0
- briefcase_debugger-0.3.26/tests/test_pdb.py +80 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2015 Russell Keith-Magee.
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
+
are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
documentation and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
3. Neither the name of Briefcase-Debugger nor the names of its contributors may
|
|
15
|
+
be used to endorse or promote products derived from this software without
|
|
16
|
+
specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: briefcase-debugger
|
|
3
|
+
Version: 0.3.26
|
|
4
|
+
Summary: A Briefcase plugin adding remote debugging support for PDB and Visual Studio Code.
|
|
5
|
+
License-Expression: BSD-3-Clause
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Provides-Extra: pdb
|
|
9
|
+
Requires-Dist: remote-pdb<3.0.0,>=2.1.0; extra == "pdb"
|
|
10
|
+
Provides-Extra: debugpy
|
|
11
|
+
Requires-Dist: debugpy<2.0.0,>=1.8.17; extra == "debugpy"
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# Briefcase Debugger Support
|
|
15
|
+
[](https://pypi.python.org/pypi/briefcase-debugger)
|
|
16
|
+
[](https://pypi.python.org/pypi/briefcase-debugger)
|
|
17
|
+
[](https://pypi.python.org/pypi/briefcase-debugger)
|
|
18
|
+
[](https://github.com/beeware/briefcase/blob/main/debugger/LICENSE)
|
|
19
|
+
[](https://github.com/beeware/briefcase/actions)
|
|
20
|
+
[](https://beeware.org/bee/chat/)
|
|
21
|
+
|
|
22
|
+
This package contains the debugger support package for the `pdb` and `debugpy` debuggers.
|
|
23
|
+
|
|
24
|
+
It starts the remote debugger automatically at startup through an .pth file, if a `BRIEFCASE_DEBUGGER` environment variable is set.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
As an end-user, you won't normally need to install this package. It will be installed automatically by Briefcase if you specify the `--debug=pdb` or `--debug=debugpy` option when running your application.
|
|
28
|
+
|
|
29
|
+
## Financial support
|
|
30
|
+
|
|
31
|
+
The BeeWare project would not be possible without the generous support
|
|
32
|
+
of our financial members:
|
|
33
|
+
|
|
34
|
+
[](https://anaconda.com/)
|
|
35
|
+
|
|
36
|
+
Anaconda Inc. - Advancing AI through open source.
|
|
37
|
+
|
|
38
|
+
Plus individual contributions from [users like
|
|
39
|
+
you](https://beeware.org/community/members/). If you find Briefcase, or
|
|
40
|
+
other BeeWare tools useful, please consider becoming a financial member.
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
Documentation for Briefcase can be found on [Read The
|
|
45
|
+
Docs](https://briefcase.readthedocs.io).
|
|
46
|
+
|
|
47
|
+
## Community
|
|
48
|
+
|
|
49
|
+
Briefcase is part of the [BeeWare suite](https://beeware.org). You can
|
|
50
|
+
talk to the community through:
|
|
51
|
+
|
|
52
|
+
- [@beeware@fosstodon.org on Mastodon](https://fosstodon.org/@beeware)
|
|
53
|
+
- [Discord](https://beeware.org/bee/chat/)
|
|
54
|
+
- The Briefcase [GitHub Discussions
|
|
55
|
+
forum](https://github.com/beeware/briefcase/discussions)
|
|
56
|
+
|
|
57
|
+
We foster a welcoming and respectful community as described in our
|
|
58
|
+
[BeeWare Community Code of
|
|
59
|
+
Conduct](https://beeware.org/community/behavior/).
|
|
60
|
+
|
|
61
|
+
## Contributing
|
|
62
|
+
|
|
63
|
+
If you experience problems with Briefcase, [log them on
|
|
64
|
+
GitHub](https://github.com/beeware/briefcase/issues).
|
|
65
|
+
|
|
66
|
+
If you'd like to contribute to Briefcase development, our [contribution
|
|
67
|
+
guide](https://briefcase.readthedocs.io/en/latest/how-to/contribute/index.html)
|
|
68
|
+
details how to set up a development environment, and other requirements
|
|
69
|
+
we have as part of our contribution process.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Briefcase Debugger Support
|
|
2
|
+
[](https://pypi.python.org/pypi/briefcase-debugger)
|
|
3
|
+
[](https://pypi.python.org/pypi/briefcase-debugger)
|
|
4
|
+
[](https://pypi.python.org/pypi/briefcase-debugger)
|
|
5
|
+
[](https://github.com/beeware/briefcase/blob/main/debugger/LICENSE)
|
|
6
|
+
[](https://github.com/beeware/briefcase/actions)
|
|
7
|
+
[](https://beeware.org/bee/chat/)
|
|
8
|
+
|
|
9
|
+
This package contains the debugger support package for the `pdb` and `debugpy` debuggers.
|
|
10
|
+
|
|
11
|
+
It starts the remote debugger automatically at startup through an .pth file, if a `BRIEFCASE_DEBUGGER` environment variable is set.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
As an end-user, you won't normally need to install this package. It will be installed automatically by Briefcase if you specify the `--debug=pdb` or `--debug=debugpy` option when running your application.
|
|
15
|
+
|
|
16
|
+
## Financial support
|
|
17
|
+
|
|
18
|
+
The BeeWare project would not be possible without the generous support
|
|
19
|
+
of our financial members:
|
|
20
|
+
|
|
21
|
+
[](https://anaconda.com/)
|
|
22
|
+
|
|
23
|
+
Anaconda Inc. - Advancing AI through open source.
|
|
24
|
+
|
|
25
|
+
Plus individual contributions from [users like
|
|
26
|
+
you](https://beeware.org/community/members/). If you find Briefcase, or
|
|
27
|
+
other BeeWare tools useful, please consider becoming a financial member.
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
Documentation for Briefcase can be found on [Read The
|
|
32
|
+
Docs](https://briefcase.readthedocs.io).
|
|
33
|
+
|
|
34
|
+
## Community
|
|
35
|
+
|
|
36
|
+
Briefcase is part of the [BeeWare suite](https://beeware.org). You can
|
|
37
|
+
talk to the community through:
|
|
38
|
+
|
|
39
|
+
- [@beeware@fosstodon.org on Mastodon](https://fosstodon.org/@beeware)
|
|
40
|
+
- [Discord](https://beeware.org/bee/chat/)
|
|
41
|
+
- The Briefcase [GitHub Discussions
|
|
42
|
+
forum](https://github.com/beeware/briefcase/discussions)
|
|
43
|
+
|
|
44
|
+
We foster a welcoming and respectful community as described in our
|
|
45
|
+
[BeeWare Community Code of
|
|
46
|
+
Conduct](https://beeware.org/community/behavior/).
|
|
47
|
+
|
|
48
|
+
## Contributing
|
|
49
|
+
|
|
50
|
+
If you experience problems with Briefcase, [log them on
|
|
51
|
+
GitHub](https://github.com/beeware/briefcase/issues).
|
|
52
|
+
|
|
53
|
+
If you'd like to contribute to Briefcase development, our [contribution
|
|
54
|
+
guide](https://briefcase.readthedocs.io/en/latest/how-to/contribute/index.html)
|
|
55
|
+
details how to set up a development environment, and other requirements
|
|
56
|
+
we have as part of our contribution process.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
# keep versions in sync with automation/pyproject.toml and ../pyproject.toml
|
|
4
|
+
"setuptools==80.9.0",
|
|
5
|
+
"setuptools_scm==9.2.2",
|
|
6
|
+
]
|
|
7
|
+
build-backend = "setuptools.build_meta"
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
name = "briefcase-debugger"
|
|
11
|
+
description = "A Briefcase plugin adding remote debugging support for PDB and Visual Studio Code."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "BSD-3-Clause"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
dependencies = [
|
|
16
|
+
# see "pdb" or "debugpy" optional dependencies below
|
|
17
|
+
]
|
|
18
|
+
dynamic = ["version"]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
pdb = ["remote-pdb>=2.1.0,<3.0.0"]
|
|
22
|
+
debugpy = ["debugpy>=1.8.17,<2.0.0"]
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
test = [
|
|
26
|
+
"coverage[toml] == 7.12.0",
|
|
27
|
+
"pytest == 9.0.1",
|
|
28
|
+
"pytest-xdist == 3.8.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.coverage.run]
|
|
32
|
+
parallel = true
|
|
33
|
+
branch = true
|
|
34
|
+
relative_files = true
|
|
35
|
+
source_pkgs = ["briefcase_debugger"]
|
|
36
|
+
|
|
37
|
+
[tool.coverage.paths]
|
|
38
|
+
source = [
|
|
39
|
+
"src",
|
|
40
|
+
"**/site-packages",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.coverage.report]
|
|
44
|
+
show_missing = true
|
|
45
|
+
skip_covered = true
|
|
46
|
+
skip_empty = true
|
|
47
|
+
precision = 1
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
ignore = [
|
|
51
|
+
"T20", # flake8-print (useful for briefcase, but not in the debugger)
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools_scm]
|
|
55
|
+
root = "../"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import setuptools
|
|
4
|
+
from setuptools.command.install import install
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Copied from setuptools:
|
|
8
|
+
# (https://github.com/pypa/setuptools/blob/7c859e017368360ba66c8cc591279d8964c031bc/setup.py#L40C6-L82)
|
|
9
|
+
class install_with_pth(install):
|
|
10
|
+
"""Custom install command to install a .pth file for distutils patching.
|
|
11
|
+
|
|
12
|
+
This hack is necessary because there's no standard way to install behavior
|
|
13
|
+
on startup (and it's debatable if there should be one). This hack (ab)uses
|
|
14
|
+
the `extra_path` behavior in Setuptools to install a `.pth` file with
|
|
15
|
+
implicit behavior on startup to give higher precedence to the local version
|
|
16
|
+
of `distutils` over the version from the standard library.
|
|
17
|
+
|
|
18
|
+
Please do not replicate this behavior.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
_pth_name = "briefcase_debugger"
|
|
22
|
+
_pth_contents = (
|
|
23
|
+
"import briefcase_debugger; briefcase_debugger.start_remote_debugger()"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def initialize_options(self):
|
|
27
|
+
install.initialize_options(self)
|
|
28
|
+
self.extra_path = self._pth_name, self._pth_contents
|
|
29
|
+
|
|
30
|
+
def finalize_options(self):
|
|
31
|
+
install.finalize_options(self)
|
|
32
|
+
self._restore_install_lib()
|
|
33
|
+
|
|
34
|
+
def _restore_install_lib(self):
|
|
35
|
+
"""Undo secondary effect of `extra_path` adding to `install_lib`"""
|
|
36
|
+
suffix = os.path.relpath(self.install_lib, self.install_libbase)
|
|
37
|
+
|
|
38
|
+
if suffix.strip() == self._pth_contents.strip():
|
|
39
|
+
self.install_lib = self.install_libbase
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
setuptools.setup(
|
|
43
|
+
cmdclass={"install": install_with_pth},
|
|
44
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import traceback
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def start_remote_debugger():
|
|
8
|
+
try:
|
|
9
|
+
# check verbose output
|
|
10
|
+
verbose = os.environ.get("BRIEFCASE_DEBUG", "0") == "1"
|
|
11
|
+
|
|
12
|
+
# reading config
|
|
13
|
+
config_str = os.environ.get("BRIEFCASE_DEBUGGER", None)
|
|
14
|
+
|
|
15
|
+
# skip debugger if no config is set
|
|
16
|
+
if config_str is None:
|
|
17
|
+
if verbose:
|
|
18
|
+
print(
|
|
19
|
+
"No 'BRIEFCASE_DEBUGGER' environment variable found. Debugger not starting."
|
|
20
|
+
)
|
|
21
|
+
return # If BRIEFCASE_DEBUGGER is not set, this packages does nothing...
|
|
22
|
+
|
|
23
|
+
if verbose:
|
|
24
|
+
print(f"'BRIEFCASE_DEBUGGER'={config_str}")
|
|
25
|
+
|
|
26
|
+
# Parsing config json
|
|
27
|
+
config = json.loads(config_str)
|
|
28
|
+
|
|
29
|
+
# start debugger
|
|
30
|
+
print("Starting remote debugger...")
|
|
31
|
+
if config["debugger"] == "debugpy":
|
|
32
|
+
from briefcase_debugger.debugpy import start_debugpy
|
|
33
|
+
|
|
34
|
+
start_debugpy(config, verbose)
|
|
35
|
+
elif config["debugger"] == "pdb":
|
|
36
|
+
from briefcase_debugger.pdb import start_pdb
|
|
37
|
+
|
|
38
|
+
start_pdb(config, verbose)
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError(f"Unknown debugger '{config['debugger']}'")
|
|
41
|
+
except Exception:
|
|
42
|
+
# Show exception and stop the whole application when an error occurs
|
|
43
|
+
print(traceback.format_exc())
|
|
44
|
+
sys.exit(-1)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import TypedDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AppPathMappings(TypedDict):
|
|
5
|
+
device_sys_path_regex: str
|
|
6
|
+
device_subfolders: list[str]
|
|
7
|
+
host_folders: list[str]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AppPackagesPathMappings(TypedDict):
|
|
11
|
+
sys_path_regex: str
|
|
12
|
+
host_folder: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DebuggerConfig(TypedDict):
|
|
16
|
+
debugger: str
|
|
17
|
+
host: str
|
|
18
|
+
port: int
|
|
19
|
+
host_os: str
|
|
20
|
+
app_path_mappings: AppPathMappings | None
|
|
21
|
+
app_packages_path_mappings: AppPackagesPathMappings | None
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import debugpy
|
|
6
|
+
|
|
7
|
+
from briefcase_debugger.config import DebuggerConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def find_first_matching_path(regex: str) -> str:
|
|
11
|
+
"""Returns the first element of sys.paths that matches regex, otherwise None."""
|
|
12
|
+
for path in sys.path:
|
|
13
|
+
if re.search(regex, path):
|
|
14
|
+
return path
|
|
15
|
+
raise ValueError(f"No sys.path entry matches regex '{regex}'")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_path_mappings(config: DebuggerConfig, verbose: bool) -> list[tuple[str, str]]:
|
|
19
|
+
app_path_mappings = config.get("app_path_mappings", None)
|
|
20
|
+
app_packages_path_mappings = config.get("app_packages_path_mappings", None)
|
|
21
|
+
|
|
22
|
+
mappings_list = []
|
|
23
|
+
if app_path_mappings:
|
|
24
|
+
device_app_folder = find_first_matching_path(
|
|
25
|
+
app_path_mappings["device_sys_path_regex"]
|
|
26
|
+
)
|
|
27
|
+
for app_subfolder_device, app_subfolder_host in zip(
|
|
28
|
+
app_path_mappings["device_subfolders"],
|
|
29
|
+
app_path_mappings["host_folders"],
|
|
30
|
+
strict=False,
|
|
31
|
+
):
|
|
32
|
+
mappings_list.append(
|
|
33
|
+
(
|
|
34
|
+
app_subfolder_host,
|
|
35
|
+
str(Path(device_app_folder) / app_subfolder_device),
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
if app_packages_path_mappings:
|
|
39
|
+
device_app_packages_folder = find_first_matching_path(
|
|
40
|
+
app_packages_path_mappings["sys_path_regex"]
|
|
41
|
+
)
|
|
42
|
+
mappings_list.append(
|
|
43
|
+
(
|
|
44
|
+
app_packages_path_mappings["host_folder"],
|
|
45
|
+
str(Path(device_app_packages_folder)),
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if verbose:
|
|
50
|
+
print("Extracted path mappings:")
|
|
51
|
+
for idx, mapping in enumerate(mappings_list):
|
|
52
|
+
print(f"[{idx}] host = {mapping[0]}")
|
|
53
|
+
print(f"[{idx}] device = {mapping[1]}")
|
|
54
|
+
|
|
55
|
+
return mappings_list
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def start_debugpy(config: DebuggerConfig, verbose: bool):
|
|
59
|
+
host = config["host"]
|
|
60
|
+
port = config["port"]
|
|
61
|
+
path_mappings = load_path_mappings(config, verbose)
|
|
62
|
+
|
|
63
|
+
# Starting remote debugger...
|
|
64
|
+
print(f"Starting debugpy in server mode at {host}:{port}...")
|
|
65
|
+
debugpy.listen((host, port), in_process_debug_adapter=True)
|
|
66
|
+
|
|
67
|
+
if verbose:
|
|
68
|
+
# pydevd is dynamically loaded and only available after debugpy is started
|
|
69
|
+
import pydevd
|
|
70
|
+
|
|
71
|
+
pydevd.DebugInfoHolder.DEBUG_TRACE_LEVEL = 3
|
|
72
|
+
|
|
73
|
+
if len(path_mappings) > 0:
|
|
74
|
+
if verbose:
|
|
75
|
+
print("Adding path mappings...")
|
|
76
|
+
|
|
77
|
+
# pydevd is dynamically loaded and only available after a debugger has connected
|
|
78
|
+
import pydevd_file_utils
|
|
79
|
+
|
|
80
|
+
pydevd_file_utils.setup_client_server_paths(path_mappings)
|
|
81
|
+
|
|
82
|
+
print("The debugpy server started. Waiting for debugger to attach...")
|
|
83
|
+
print(
|
|
84
|
+
f"""
|
|
85
|
+
To connect to debugpy using VS Code add the following configuration to '.vscode/launch.json':
|
|
86
|
+
{{
|
|
87
|
+
"version": "0.2.0",
|
|
88
|
+
"configurations": [
|
|
89
|
+
{{
|
|
90
|
+
"name": "Briefcase: Attach (Connect)",
|
|
91
|
+
"type": "debugpy",
|
|
92
|
+
"request": "attach",
|
|
93
|
+
"connect": {{
|
|
94
|
+
"host": "{host}",
|
|
95
|
+
"port": {port}
|
|
96
|
+
}},
|
|
97
|
+
"justMyCode": false
|
|
98
|
+
}}
|
|
99
|
+
]
|
|
100
|
+
}}
|
|
101
|
+
|
|
102
|
+
For more information see: https://briefcase.beeware.org/en/stable/how-to/debugging/vscode/#bundled-app
|
|
103
|
+
"""
|
|
104
|
+
)
|
|
105
|
+
debugpy.wait_for_client()
|
|
106
|
+
|
|
107
|
+
print("Debugger attached.")
|
|
108
|
+
print("-" * 75)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from remote_pdb import RemotePdb
|
|
4
|
+
|
|
5
|
+
from briefcase_debugger.config import DebuggerConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def start_pdb(config: DebuggerConfig, verbose: bool):
|
|
9
|
+
"""Start remote PDB server."""
|
|
10
|
+
# Parsing host/port
|
|
11
|
+
host = config["host"]
|
|
12
|
+
port = config["port"]
|
|
13
|
+
|
|
14
|
+
# Print help message
|
|
15
|
+
host_os = config["host_os"]
|
|
16
|
+
telnet_cmd = f"telnet {host} {port}"
|
|
17
|
+
nc_cmd = f"nc {host} {port}"
|
|
18
|
+
if host_os == "Windows":
|
|
19
|
+
cmds_hint = f" {telnet_cmd}"
|
|
20
|
+
elif host_os in ("Linux", "Darwin"):
|
|
21
|
+
cmds_hint = f" {nc_cmd}"
|
|
22
|
+
else:
|
|
23
|
+
cmds_hint = f"""\
|
|
24
|
+
- {telnet_cmd}
|
|
25
|
+
- {nc_cmd}
|
|
26
|
+
"""
|
|
27
|
+
print(f"""
|
|
28
|
+
Remote PDB server opened at {host}:{port}.
|
|
29
|
+
Waiting for debugger to attach...
|
|
30
|
+
To connect to remote PDB use for example:
|
|
31
|
+
|
|
32
|
+
{cmds_hint}
|
|
33
|
+
|
|
34
|
+
For more information see: https://briefcase.beeware.org/en/stable/how-to/debugging/pdb/#bundled-app
|
|
35
|
+
""")
|
|
36
|
+
|
|
37
|
+
# Create a RemotePdb instance
|
|
38
|
+
remote_pdb = RemotePdb(host, port, quiet=True)
|
|
39
|
+
|
|
40
|
+
# Connect the remote PDB with the "breakpoint()" function
|
|
41
|
+
sys.breakpointhook = remote_pdb.set_trace
|
|
42
|
+
|
|
43
|
+
print("Debugger client attached.")
|
|
44
|
+
print("-" * 75)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: briefcase-debugger
|
|
3
|
+
Version: 0.3.26
|
|
4
|
+
Summary: A Briefcase plugin adding remote debugging support for PDB and Visual Studio Code.
|
|
5
|
+
License-Expression: BSD-3-Clause
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Provides-Extra: pdb
|
|
9
|
+
Requires-Dist: remote-pdb<3.0.0,>=2.1.0; extra == "pdb"
|
|
10
|
+
Provides-Extra: debugpy
|
|
11
|
+
Requires-Dist: debugpy<2.0.0,>=1.8.17; extra == "debugpy"
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# Briefcase Debugger Support
|
|
15
|
+
[](https://pypi.python.org/pypi/briefcase-debugger)
|
|
16
|
+
[](https://pypi.python.org/pypi/briefcase-debugger)
|
|
17
|
+
[](https://pypi.python.org/pypi/briefcase-debugger)
|
|
18
|
+
[](https://github.com/beeware/briefcase/blob/main/debugger/LICENSE)
|
|
19
|
+
[](https://github.com/beeware/briefcase/actions)
|
|
20
|
+
[](https://beeware.org/bee/chat/)
|
|
21
|
+
|
|
22
|
+
This package contains the debugger support package for the `pdb` and `debugpy` debuggers.
|
|
23
|
+
|
|
24
|
+
It starts the remote debugger automatically at startup through an .pth file, if a `BRIEFCASE_DEBUGGER` environment variable is set.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
As an end-user, you won't normally need to install this package. It will be installed automatically by Briefcase if you specify the `--debug=pdb` or `--debug=debugpy` option when running your application.
|
|
28
|
+
|
|
29
|
+
## Financial support
|
|
30
|
+
|
|
31
|
+
The BeeWare project would not be possible without the generous support
|
|
32
|
+
of our financial members:
|
|
33
|
+
|
|
34
|
+
[](https://anaconda.com/)
|
|
35
|
+
|
|
36
|
+
Anaconda Inc. - Advancing AI through open source.
|
|
37
|
+
|
|
38
|
+
Plus individual contributions from [users like
|
|
39
|
+
you](https://beeware.org/community/members/). If you find Briefcase, or
|
|
40
|
+
other BeeWare tools useful, please consider becoming a financial member.
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
Documentation for Briefcase can be found on [Read The
|
|
45
|
+
Docs](https://briefcase.readthedocs.io).
|
|
46
|
+
|
|
47
|
+
## Community
|
|
48
|
+
|
|
49
|
+
Briefcase is part of the [BeeWare suite](https://beeware.org). You can
|
|
50
|
+
talk to the community through:
|
|
51
|
+
|
|
52
|
+
- [@beeware@fosstodon.org on Mastodon](https://fosstodon.org/@beeware)
|
|
53
|
+
- [Discord](https://beeware.org/bee/chat/)
|
|
54
|
+
- The Briefcase [GitHub Discussions
|
|
55
|
+
forum](https://github.com/beeware/briefcase/discussions)
|
|
56
|
+
|
|
57
|
+
We foster a welcoming and respectful community as described in our
|
|
58
|
+
[BeeWare Community Code of
|
|
59
|
+
Conduct](https://beeware.org/community/behavior/).
|
|
60
|
+
|
|
61
|
+
## Contributing
|
|
62
|
+
|
|
63
|
+
If you experience problems with Briefcase, [log them on
|
|
64
|
+
GitHub](https://github.com/beeware/briefcase/issues).
|
|
65
|
+
|
|
66
|
+
If you'd like to contribute to Briefcase development, our [contribution
|
|
67
|
+
guide](https://briefcase.readthedocs.io/en/latest/how-to/contribute/index.html)
|
|
68
|
+
details how to set up a development environment, and other requirements
|
|
69
|
+
we have as part of our contribution process.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
src/briefcase_debugger/__init__.py
|
|
6
|
+
src/briefcase_debugger/config.py
|
|
7
|
+
src/briefcase_debugger/debugpy.py
|
|
8
|
+
src/briefcase_debugger/pdb.py
|
|
9
|
+
src/briefcase_debugger.egg-info/PKG-INFO
|
|
10
|
+
src/briefcase_debugger.egg-info/SOURCES.txt
|
|
11
|
+
src/briefcase_debugger.egg-info/dependency_links.txt
|
|
12
|
+
src/briefcase_debugger.egg-info/requires.txt
|
|
13
|
+
src/briefcase_debugger.egg-info/top_level.txt
|
|
14
|
+
tests/test_base.py
|
|
15
|
+
tests/test_debugpy.py
|
|
16
|
+
tests/test_debugpy_path_mappings.py
|
|
17
|
+
tests/test_pdb.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
briefcase_debugger
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from unittest.mock import MagicMock
|
|
6
|
+
|
|
7
|
+
import briefcase_debugger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_import_for_code_coverage(monkeypatch, capsys):
|
|
11
|
+
"""Get 100% code coverage."""
|
|
12
|
+
# The module `briefcase_debugger` is already imported through the .pth
|
|
13
|
+
# file. Code executed during .pth files are not covered by coverage.py.
|
|
14
|
+
# So we need to reload the module to get a 100% code coverage.
|
|
15
|
+
importlib.reload(importlib.import_module("briefcase_debugger"))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_unknown_debugger(monkeypatch, capsys):
|
|
19
|
+
"""An unknown debugger raises an error and stops the application."""
|
|
20
|
+
os_environ = {}
|
|
21
|
+
os_environ["BRIEFCASE_DEBUGGER"] = json.dumps(
|
|
22
|
+
{
|
|
23
|
+
"debugger": "unknown",
|
|
24
|
+
"host": "somehost",
|
|
25
|
+
"port": 9999,
|
|
26
|
+
"host_os": "Windows",
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
monkeypatch.setattr(os, "environ", os_environ)
|
|
30
|
+
|
|
31
|
+
fake_sys_exit = MagicMock()
|
|
32
|
+
monkeypatch.setattr(sys, "exit", fake_sys_exit)
|
|
33
|
+
|
|
34
|
+
briefcase_debugger.start_remote_debugger()
|
|
35
|
+
|
|
36
|
+
fake_sys_exit.assert_called_once_with(-1)
|
|
37
|
+
|
|
38
|
+
captured = capsys.readouterr()
|
|
39
|
+
assert "Unknown debugger" in captured.out
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from unittest.mock import MagicMock
|
|
5
|
+
|
|
6
|
+
import briefcase_debugger
|
|
7
|
+
import debugpy
|
|
8
|
+
import pytest
|
|
9
|
+
from briefcase_debugger.config import AppPathMappings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_no_env_vars(monkeypatch, capsys):
|
|
13
|
+
"""Nothing happens, when no env vars are set."""
|
|
14
|
+
os_environ = {}
|
|
15
|
+
monkeypatch.setattr(os, "environ", os_environ)
|
|
16
|
+
|
|
17
|
+
# start test function
|
|
18
|
+
briefcase_debugger.start_remote_debugger()
|
|
19
|
+
|
|
20
|
+
captured = capsys.readouterr()
|
|
21
|
+
assert captured.out == ""
|
|
22
|
+
assert captured.err == ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_no_debugger_verbose(monkeypatch, capsys):
|
|
26
|
+
"""Nothing happens except a short message, when only verbose is requested."""
|
|
27
|
+
os_environ = {}
|
|
28
|
+
os_environ["BRIEFCASE_DEBUG"] = "1"
|
|
29
|
+
monkeypatch.setattr(os, "environ", os_environ)
|
|
30
|
+
|
|
31
|
+
# start test function
|
|
32
|
+
briefcase_debugger.start_remote_debugger()
|
|
33
|
+
|
|
34
|
+
captured = capsys.readouterr()
|
|
35
|
+
assert (
|
|
36
|
+
captured.out
|
|
37
|
+
== "No 'BRIEFCASE_DEBUGGER' environment variable found. Debugger not starting.\n"
|
|
38
|
+
)
|
|
39
|
+
assert captured.err == ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parametrize(
|
|
43
|
+
("os_name", "app_path_mappings", "sys_path", "expected_path_mappings"),
|
|
44
|
+
[
|
|
45
|
+
(
|
|
46
|
+
"nt",
|
|
47
|
+
AppPathMappings(
|
|
48
|
+
device_sys_path_regex="app$",
|
|
49
|
+
device_subfolders=["helloworld"],
|
|
50
|
+
host_folders=["C:\\PROJECT_ROOT\\src\\helloworld"],
|
|
51
|
+
),
|
|
52
|
+
["C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app"],
|
|
53
|
+
[
|
|
54
|
+
(
|
|
55
|
+
"C:\\PROJECT_ROOT\\src\\helloworld",
|
|
56
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app\\helloworld",
|
|
57
|
+
)
|
|
58
|
+
],
|
|
59
|
+
),
|
|
60
|
+
(
|
|
61
|
+
"posix",
|
|
62
|
+
AppPathMappings(
|
|
63
|
+
device_sys_path_regex="app$",
|
|
64
|
+
device_subfolders=["helloworld"],
|
|
65
|
+
host_folders=["/PROJECT_ROOT/src/helloworld"],
|
|
66
|
+
),
|
|
67
|
+
[
|
|
68
|
+
"/PROJECT_ROOT/build/helloworld/macos/app/Hello World.app/Contents/Resources/app"
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
(
|
|
72
|
+
"/PROJECT_ROOT/src/helloworld",
|
|
73
|
+
"/PROJECT_ROOT/build/helloworld/macos/app/Hello World.app/Contents/Resources/app/helloworld",
|
|
74
|
+
)
|
|
75
|
+
],
|
|
76
|
+
),
|
|
77
|
+
],
|
|
78
|
+
)
|
|
79
|
+
@pytest.mark.parametrize(
|
|
80
|
+
("verbose", "some_verbose_output", "pydevd_trace_level"),
|
|
81
|
+
[
|
|
82
|
+
(True, "Extracted path mappings:\n[0] host = ", 3),
|
|
83
|
+
(False, "", 0),
|
|
84
|
+
],
|
|
85
|
+
)
|
|
86
|
+
def test_with_debugger(
|
|
87
|
+
os_name: str,
|
|
88
|
+
app_path_mappings: AppPathMappings,
|
|
89
|
+
sys_path: list[str],
|
|
90
|
+
expected_path_mappings: list[tuple[str, str]],
|
|
91
|
+
verbose: bool,
|
|
92
|
+
some_verbose_output: str,
|
|
93
|
+
pydevd_trace_level: int,
|
|
94
|
+
monkeypatch,
|
|
95
|
+
capsys,
|
|
96
|
+
):
|
|
97
|
+
"""Normal debug session."""
|
|
98
|
+
if os.name != os_name:
|
|
99
|
+
pytest.skip(f"Test only runs on {os_name} systems")
|
|
100
|
+
|
|
101
|
+
os_environ = {}
|
|
102
|
+
os_environ["BRIEFCASE_DEBUG"] = "1" if verbose else "0"
|
|
103
|
+
os_environ["BRIEFCASE_DEBUGGER"] = json.dumps(
|
|
104
|
+
{
|
|
105
|
+
"debugger": "debugpy",
|
|
106
|
+
"host": "somehost",
|
|
107
|
+
"port": 9999,
|
|
108
|
+
"host_os": "SomeOS",
|
|
109
|
+
"app_path_mappings": app_path_mappings,
|
|
110
|
+
"app_packages_path_mappings": None,
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
monkeypatch.setattr(os, "environ", os_environ)
|
|
114
|
+
|
|
115
|
+
monkeypatch.setattr(sys, "path", sys_path)
|
|
116
|
+
|
|
117
|
+
fake_debugpy_listen = MagicMock()
|
|
118
|
+
monkeypatch.setattr(debugpy, "listen", fake_debugpy_listen)
|
|
119
|
+
|
|
120
|
+
fake_debugpy_wait_for_client = MagicMock()
|
|
121
|
+
monkeypatch.setattr(debugpy, "wait_for_client", fake_debugpy_wait_for_client)
|
|
122
|
+
|
|
123
|
+
# pydevd is dynamically loaded and only available when a real debugger is attached. So
|
|
124
|
+
# we fake the whole module, as otherwise the import in start_remote_debugger would fail
|
|
125
|
+
fake_pydevd = MagicMock()
|
|
126
|
+
monkeypatch.setitem(sys.modules, "pydevd", fake_pydevd)
|
|
127
|
+
fake_pydevd.DebugInfoHolder.DEBUG_TRACE_LEVEL = 0
|
|
128
|
+
fake_pydevd_file_utils = MagicMock()
|
|
129
|
+
fake_pydevd_file_utils.setup_client_server_paths.return_value = None
|
|
130
|
+
monkeypatch.setitem(sys.modules, "pydevd_file_utils", fake_pydevd_file_utils)
|
|
131
|
+
|
|
132
|
+
# start test function
|
|
133
|
+
briefcase_debugger.start_remote_debugger()
|
|
134
|
+
|
|
135
|
+
fake_debugpy_listen.assert_called_once_with(
|
|
136
|
+
("somehost", 9999),
|
|
137
|
+
in_process_debug_adapter=True,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
fake_debugpy_wait_for_client.assert_called_once()
|
|
141
|
+
fake_pydevd_file_utils.setup_client_server_paths.assert_called_once_with(
|
|
142
|
+
expected_path_mappings
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
captured = capsys.readouterr()
|
|
146
|
+
assert "Waiting for debugger to attach..." in captured.out
|
|
147
|
+
assert captured.err == ""
|
|
148
|
+
|
|
149
|
+
assert some_verbose_output in captured.out
|
|
150
|
+
assert pydevd_trace_level == fake_pydevd.DebugInfoHolder.DEBUG_TRACE_LEVEL
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_with_debugger_without_path_mappings(monkeypatch, capsys):
|
|
154
|
+
"""Debug session without path mappings."""
|
|
155
|
+
os_environ = {}
|
|
156
|
+
os_environ["BRIEFCASE_DEBUG"] = "0"
|
|
157
|
+
os_environ["BRIEFCASE_DEBUGGER"] = json.dumps(
|
|
158
|
+
{
|
|
159
|
+
"debugger": "debugpy",
|
|
160
|
+
"host": "somehost",
|
|
161
|
+
"port": 9999,
|
|
162
|
+
"host_os": "SomeOS",
|
|
163
|
+
"app_path_mappings": None,
|
|
164
|
+
"app_packages_path_mappings": None,
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
monkeypatch.setattr(os, "environ", os_environ)
|
|
168
|
+
|
|
169
|
+
fake_debugpy_listen = MagicMock()
|
|
170
|
+
monkeypatch.setattr(debugpy, "listen", fake_debugpy_listen)
|
|
171
|
+
|
|
172
|
+
fake_debugpy_wait_for_client = MagicMock()
|
|
173
|
+
monkeypatch.setattr(debugpy, "wait_for_client", fake_debugpy_wait_for_client)
|
|
174
|
+
|
|
175
|
+
# pydevd is dynamically loaded and only available when a real debugger is attached. So
|
|
176
|
+
# we fake the whole module, as otherwise the import in start_remote_debugger would fail
|
|
177
|
+
fake_pydevd = MagicMock()
|
|
178
|
+
monkeypatch.setitem(sys.modules, "pydevd", fake_pydevd)
|
|
179
|
+
fake_pydevd.DebugInfoHolder.DEBUG_TRACE_LEVEL = 0
|
|
180
|
+
fake_pydevd_file_utils = MagicMock()
|
|
181
|
+
fake_pydevd_file_utils.setup_client_server_paths.return_value = None
|
|
182
|
+
monkeypatch.setitem(sys.modules, "pydevd_file_utils", fake_pydevd_file_utils)
|
|
183
|
+
|
|
184
|
+
# start test function
|
|
185
|
+
briefcase_debugger.start_remote_debugger()
|
|
186
|
+
|
|
187
|
+
fake_debugpy_listen.assert_called_once_with(
|
|
188
|
+
("somehost", 9999),
|
|
189
|
+
in_process_debug_adapter=True,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
fake_debugpy_wait_for_client.assert_called_once()
|
|
193
|
+
fake_pydevd_file_utils.setup_client_server_paths.assert_not_called()
|
|
194
|
+
|
|
195
|
+
captured = capsys.readouterr()
|
|
196
|
+
assert "Waiting for debugger to attach..." in captured.out
|
|
197
|
+
assert captured.err == ""
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import briefcase_debugger.debugpy
|
|
5
|
+
import pytest
|
|
6
|
+
from briefcase_debugger.config import (
|
|
7
|
+
AppPackagesPathMappings,
|
|
8
|
+
AppPathMappings,
|
|
9
|
+
DebuggerConfig,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_mappings_not_existing():
|
|
14
|
+
"""Complete empty config."""
|
|
15
|
+
path_mappings = briefcase_debugger.debugpy.load_path_mappings({}, False)
|
|
16
|
+
assert path_mappings == []
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_mappings_none(monkeypatch):
|
|
20
|
+
"""Config with no mappings set."""
|
|
21
|
+
config = DebuggerConfig(
|
|
22
|
+
debugger="debugpy",
|
|
23
|
+
host="",
|
|
24
|
+
port=0,
|
|
25
|
+
app_path_mappings=None,
|
|
26
|
+
app_packages_path_mappings=None,
|
|
27
|
+
)
|
|
28
|
+
path_mappings = briefcase_debugger.debugpy.load_path_mappings(config, False)
|
|
29
|
+
assert path_mappings == []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.parametrize(
|
|
33
|
+
(
|
|
34
|
+
"os_name",
|
|
35
|
+
"app_path_mappings",
|
|
36
|
+
"app_packages_path_mappings",
|
|
37
|
+
"sys_path",
|
|
38
|
+
"expected_path_mappings",
|
|
39
|
+
),
|
|
40
|
+
[
|
|
41
|
+
# Windows
|
|
42
|
+
pytest.param(
|
|
43
|
+
"nt",
|
|
44
|
+
AppPathMappings(
|
|
45
|
+
device_sys_path_regex="app$",
|
|
46
|
+
device_subfolders=["helloworld"],
|
|
47
|
+
host_folders=["C:\\PROJECT_ROOT\\src\\helloworld"],
|
|
48
|
+
),
|
|
49
|
+
None,
|
|
50
|
+
[
|
|
51
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\python313.zip",
|
|
52
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src",
|
|
53
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app",
|
|
54
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app_packages",
|
|
55
|
+
],
|
|
56
|
+
[
|
|
57
|
+
(
|
|
58
|
+
"C:\\PROJECT_ROOT\\src\\helloworld",
|
|
59
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app\\helloworld",
|
|
60
|
+
),
|
|
61
|
+
],
|
|
62
|
+
id="windows",
|
|
63
|
+
),
|
|
64
|
+
# Windows with `app_packages_path_mappings` (currently not used by briefcase, but principally possible)
|
|
65
|
+
pytest.param(
|
|
66
|
+
"nt",
|
|
67
|
+
AppPathMappings(
|
|
68
|
+
device_sys_path_regex="app$",
|
|
69
|
+
device_subfolders=["helloworld"],
|
|
70
|
+
host_folders=["C:\\PROJECT_ROOT\\src\\helloworld"],
|
|
71
|
+
),
|
|
72
|
+
AppPackagesPathMappings(
|
|
73
|
+
sys_path_regex="app_packages$",
|
|
74
|
+
host_folder="C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app_packages",
|
|
75
|
+
),
|
|
76
|
+
[
|
|
77
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\python313.zip",
|
|
78
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src",
|
|
79
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app",
|
|
80
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app_packages",
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
(
|
|
84
|
+
"C:\\PROJECT_ROOT\\src\\helloworld",
|
|
85
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app\\helloworld",
|
|
86
|
+
),
|
|
87
|
+
(
|
|
88
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app_packages",
|
|
89
|
+
"C:\\PROJECT_ROOT\\build\\helloworld\\windows\\app\\src\\app_packages",
|
|
90
|
+
),
|
|
91
|
+
],
|
|
92
|
+
id="windows-with-app-packages",
|
|
93
|
+
),
|
|
94
|
+
# macOS
|
|
95
|
+
pytest.param(
|
|
96
|
+
"posix",
|
|
97
|
+
AppPathMappings(
|
|
98
|
+
device_sys_path_regex="app$",
|
|
99
|
+
device_subfolders=["helloworld"],
|
|
100
|
+
host_folders=["/PROJECT_ROOT/src/helloworld"],
|
|
101
|
+
),
|
|
102
|
+
None,
|
|
103
|
+
[
|
|
104
|
+
"/PROJECT_ROOT/build/helloworld/macos/app/Hello World.app/Contents/Frameworks/Python.framework/Versions/3.13/lib/python3.13",
|
|
105
|
+
"/PROJECT_ROOT/build/helloworld/macos/app/Hello World.app/Contents/Frameworks/Python.framework/Versions/3.13/lib/python3.13/lib-dynload",
|
|
106
|
+
"/PROJECT_ROOT/build/helloworld/macos/app/Hello World.app/Contents/Resources/app",
|
|
107
|
+
"/PROJECT_ROOT/build/helloworld/macos/app/Hello World.app/Contents/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages",
|
|
108
|
+
"/PROJECT_ROOT/build/helloworld/macos/app/Hello World.app/Contents/Resources/app_packages",
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
(
|
|
112
|
+
"/PROJECT_ROOT/src/helloworld",
|
|
113
|
+
"/PROJECT_ROOT/build/helloworld/macos/app/Hello World.app/Contents/Resources/app/helloworld",
|
|
114
|
+
)
|
|
115
|
+
],
|
|
116
|
+
id="macos",
|
|
117
|
+
),
|
|
118
|
+
# iOS
|
|
119
|
+
pytest.param(
|
|
120
|
+
"posix",
|
|
121
|
+
AppPathMappings(
|
|
122
|
+
device_sys_path_regex="app$",
|
|
123
|
+
device_subfolders=["helloworld"],
|
|
124
|
+
host_folders=["/PROJECT_ROOT/src/helloworld"],
|
|
125
|
+
),
|
|
126
|
+
AppPackagesPathMappings(
|
|
127
|
+
sys_path_regex="app_packages$",
|
|
128
|
+
host_folder="/APP_PACKAGES_PATH/app_packages.iphonesimulator",
|
|
129
|
+
),
|
|
130
|
+
[
|
|
131
|
+
"CoreSimulator/Devices/RANDOM_NUMBER/data/Containers/Bundle/Application/RANDOM_NUMBER/Hello World.app/python/lib/python3.13",
|
|
132
|
+
"CoreSimulator/Devices/RANDOM_NUMBER/data/Containers/Bundle/Application/RANDOM_NUMBER/Hello World.app/python/lib/python3.13/lib-dynload",
|
|
133
|
+
"CoreSimulator/Devices/RANDOM_NUMBER/data/Containers/Bundle/Application/RANDOM_NUMBER/Hello World.app/app",
|
|
134
|
+
"CoreSimulator/Devices/RANDOM_NUMBER/data/Containers/Bundle/Application/RANDOM_NUMBER/Hello World.app/python/lib/python3.13/site-packages",
|
|
135
|
+
"CoreSimulator/Devices/RANDOM_NUMBER/data/Containers/Bundle/Application/RANDOM_NUMBER/Hello World.app/app_packages",
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
(
|
|
139
|
+
"/PROJECT_ROOT/src/helloworld",
|
|
140
|
+
"CoreSimulator/Devices/RANDOM_NUMBER/data/Containers/Bundle/Application/RANDOM_NUMBER/Hello World.app/app/helloworld",
|
|
141
|
+
),
|
|
142
|
+
(
|
|
143
|
+
"/APP_PACKAGES_PATH/app_packages.iphonesimulator",
|
|
144
|
+
"CoreSimulator/Devices/RANDOM_NUMBER/data/Containers/Bundle/Application/RANDOM_NUMBER/Hello World.app/app_packages",
|
|
145
|
+
),
|
|
146
|
+
],
|
|
147
|
+
id="ios",
|
|
148
|
+
),
|
|
149
|
+
# Android (with VS Code running on Windows)
|
|
150
|
+
pytest.param(
|
|
151
|
+
"posix",
|
|
152
|
+
AppPathMappings(
|
|
153
|
+
device_sys_path_regex="app$",
|
|
154
|
+
device_subfolders=["helloworld"],
|
|
155
|
+
host_folders=["C:\\PROJECT_ROOT\\src\\helloworld"],
|
|
156
|
+
),
|
|
157
|
+
AppPackagesPathMappings(
|
|
158
|
+
sys_path_regex="requirements$",
|
|
159
|
+
host_folder="C:\\BUNDLE_PATH\\app\\build\\python\\pip\\debug\\common",
|
|
160
|
+
),
|
|
161
|
+
[
|
|
162
|
+
"/data/data/com.example.helloworld/files/chaquopy/AssetFinder/app",
|
|
163
|
+
"/data/data/com.example.helloworld/files/chaquopy/AssetFinder/requirements",
|
|
164
|
+
"/data/data/com.example.helloworld/files/chaquopy/AssetFinder/stdlib-x86_64",
|
|
165
|
+
"/data/user/0/com.example.helloworld/files/chaquopy/stdlib-common.imy",
|
|
166
|
+
"/data/user/0/com.example.helloworld/files/chaquopy/bootstrap.imy",
|
|
167
|
+
"/data/user/0/com.example.helloworld/files/chaquopy/bootstrap-native/x86_64",
|
|
168
|
+
],
|
|
169
|
+
[
|
|
170
|
+
(
|
|
171
|
+
"C:\\PROJECT_ROOT\\src\\helloworld",
|
|
172
|
+
"/data/data/com.example.helloworld/files/chaquopy/AssetFinder/app/helloworld",
|
|
173
|
+
),
|
|
174
|
+
(
|
|
175
|
+
"C:\\BUNDLE_PATH\\app\\build\\python\\pip\\debug\\common",
|
|
176
|
+
"/data/data/com.example.helloworld/files/chaquopy/AssetFinder/requirements",
|
|
177
|
+
),
|
|
178
|
+
],
|
|
179
|
+
id="android-on-windows-host",
|
|
180
|
+
),
|
|
181
|
+
# Android (with VS Code running on POSIX system)
|
|
182
|
+
pytest.param(
|
|
183
|
+
"posix",
|
|
184
|
+
AppPathMappings(
|
|
185
|
+
device_sys_path_regex="app$",
|
|
186
|
+
device_subfolders=["helloworld"],
|
|
187
|
+
host_folders=["/PROJECT_ROOT/src/helloworld"],
|
|
188
|
+
),
|
|
189
|
+
AppPackagesPathMappings(
|
|
190
|
+
sys_path_regex="requirements$",
|
|
191
|
+
host_folder="/BUNDLE_PATH/app/build/python/pip/debug/common",
|
|
192
|
+
),
|
|
193
|
+
[
|
|
194
|
+
"/data/data/com.example.helloworld/files/chaquopy/AssetFinder/app",
|
|
195
|
+
"/data/data/com.example.helloworld/files/chaquopy/AssetFinder/requirements",
|
|
196
|
+
"/data/data/com.example.helloworld/files/chaquopy/AssetFinder/stdlib-x86_64",
|
|
197
|
+
"/data/user/0/com.example.helloworld/files/chaquopy/stdlib-common.imy",
|
|
198
|
+
"/data/user/0/com.example.helloworld/files/chaquopy/bootstrap.imy",
|
|
199
|
+
"/data/user/0/com.example.helloworld/files/chaquopy/bootstrap-native/x86_64",
|
|
200
|
+
],
|
|
201
|
+
[
|
|
202
|
+
(
|
|
203
|
+
"/PROJECT_ROOT/src/helloworld",
|
|
204
|
+
"/data/data/com.example.helloworld/files/chaquopy/AssetFinder/app/helloworld",
|
|
205
|
+
),
|
|
206
|
+
(
|
|
207
|
+
"/BUNDLE_PATH/app/build/python/pip/debug/common",
|
|
208
|
+
"/data/data/com.example.helloworld/files/chaquopy/AssetFinder/requirements",
|
|
209
|
+
),
|
|
210
|
+
],
|
|
211
|
+
id="android-on-posix-host",
|
|
212
|
+
),
|
|
213
|
+
],
|
|
214
|
+
)
|
|
215
|
+
def test_mappings(
|
|
216
|
+
os_name: str,
|
|
217
|
+
app_path_mappings: AppPathMappings,
|
|
218
|
+
app_packages_path_mappings: AppPackagesPathMappings | None,
|
|
219
|
+
sys_path: list[str],
|
|
220
|
+
expected_path_mappings: list[tuple[str, str]],
|
|
221
|
+
monkeypatch,
|
|
222
|
+
):
|
|
223
|
+
if os.name != os_name:
|
|
224
|
+
pytest.skip(f"Test only runs on {os_name} systems")
|
|
225
|
+
|
|
226
|
+
config = DebuggerConfig(
|
|
227
|
+
debugger="debugpy",
|
|
228
|
+
host="",
|
|
229
|
+
port=0,
|
|
230
|
+
app_path_mappings=app_path_mappings,
|
|
231
|
+
app_packages_path_mappings=app_packages_path_mappings,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
monkeypatch.setattr(sys, "path", sys_path)
|
|
235
|
+
|
|
236
|
+
path_mappings = briefcase_debugger.debugpy.load_path_mappings(config, False)
|
|
237
|
+
|
|
238
|
+
assert path_mappings == expected_path_mappings
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@pytest.mark.parametrize(
|
|
242
|
+
("os_name", "app_path_mappings"),
|
|
243
|
+
[
|
|
244
|
+
# Windows
|
|
245
|
+
pytest.param(
|
|
246
|
+
"nt",
|
|
247
|
+
AppPathMappings(
|
|
248
|
+
device_sys_path_regex="app$",
|
|
249
|
+
device_subfolders=["helloworld"],
|
|
250
|
+
host_folders=["C:\\PROJECT_ROOT\\src\\helloworld"],
|
|
251
|
+
),
|
|
252
|
+
id="windows",
|
|
253
|
+
),
|
|
254
|
+
# POSIX (macOS/iOS/Android)
|
|
255
|
+
pytest.param(
|
|
256
|
+
"posix",
|
|
257
|
+
AppPathMappings(
|
|
258
|
+
device_sys_path_regex="app$",
|
|
259
|
+
device_subfolders=["helloworld"],
|
|
260
|
+
host_folders=["/PROJECT_ROOT/src/helloworld"],
|
|
261
|
+
),
|
|
262
|
+
id="posix",
|
|
263
|
+
),
|
|
264
|
+
],
|
|
265
|
+
)
|
|
266
|
+
def test_mappings_wrong_sys_path(
|
|
267
|
+
os_name: str,
|
|
268
|
+
app_path_mappings: AppPathMappings,
|
|
269
|
+
monkeypatch,
|
|
270
|
+
):
|
|
271
|
+
"""Path mappings with a wrong sys path set."""
|
|
272
|
+
if os.name != os_name:
|
|
273
|
+
pytest.skip(f"Test only runs on {os_name} systems")
|
|
274
|
+
|
|
275
|
+
config = DebuggerConfig(
|
|
276
|
+
debugger="debugpy",
|
|
277
|
+
host="",
|
|
278
|
+
port=0,
|
|
279
|
+
app_path_mappings=app_path_mappings,
|
|
280
|
+
app_packages_path_mappings=None,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
sys_path = []
|
|
284
|
+
monkeypatch.setattr(sys, "path", sys_path)
|
|
285
|
+
|
|
286
|
+
with pytest.raises(ValueError, match=r"No sys.path entry matches regex"):
|
|
287
|
+
briefcase_debugger.debugpy.load_path_mappings(config, False)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from unittest.mock import MagicMock
|
|
4
|
+
|
|
5
|
+
import briefcase_debugger
|
|
6
|
+
import briefcase_debugger.pdb
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_no_env_vars(monkeypatch, capsys):
|
|
11
|
+
"""Nothing happens, when no env vars are set."""
|
|
12
|
+
os_environ = {}
|
|
13
|
+
monkeypatch.setattr(os, "environ", os_environ)
|
|
14
|
+
|
|
15
|
+
# start test function
|
|
16
|
+
briefcase_debugger.start_remote_debugger()
|
|
17
|
+
|
|
18
|
+
captured = capsys.readouterr()
|
|
19
|
+
assert captured.out == ""
|
|
20
|
+
assert captured.err == ""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_no_debugger_verbose(monkeypatch, capsys):
|
|
24
|
+
"""Nothing happens except a short message, when only verbose is requested."""
|
|
25
|
+
os_environ = {}
|
|
26
|
+
os_environ["BRIEFCASE_DEBUG"] = "1"
|
|
27
|
+
monkeypatch.setattr(os, "environ", os_environ)
|
|
28
|
+
|
|
29
|
+
# start test function
|
|
30
|
+
briefcase_debugger.start_remote_debugger()
|
|
31
|
+
|
|
32
|
+
captured = capsys.readouterr()
|
|
33
|
+
assert (
|
|
34
|
+
captured.out
|
|
35
|
+
== "No 'BRIEFCASE_DEBUGGER' environment variable found. Debugger not starting.\n"
|
|
36
|
+
)
|
|
37
|
+
assert captured.err == ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.mark.parametrize("verbose", [True, False])
|
|
41
|
+
@pytest.mark.parametrize(
|
|
42
|
+
("host_os", "expected_host_cmds"),
|
|
43
|
+
[
|
|
44
|
+
("Windows", ["telnet somehost 9999"]),
|
|
45
|
+
("Darwin", ["nc somehost 9999"]),
|
|
46
|
+
("Linux", ["nc somehost 9999"]),
|
|
47
|
+
("UnknownOS", ["nc somehost 9999", "telnet somehost 9999"]),
|
|
48
|
+
],
|
|
49
|
+
)
|
|
50
|
+
def test_with_debugger(monkeypatch, host_os, expected_host_cmds, capsys, verbose):
|
|
51
|
+
"""Normal debug session."""
|
|
52
|
+
os_environ = {}
|
|
53
|
+
os_environ["BRIEFCASE_DEBUG"] = "1" if verbose else "0"
|
|
54
|
+
os_environ["BRIEFCASE_DEBUGGER"] = json.dumps(
|
|
55
|
+
{
|
|
56
|
+
"debugger": "pdb",
|
|
57
|
+
"host": "somehost",
|
|
58
|
+
"port": 9999,
|
|
59
|
+
"host_os": host_os,
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
monkeypatch.setattr(os, "environ", os_environ)
|
|
63
|
+
|
|
64
|
+
fake_remote_pdb = MagicMock()
|
|
65
|
+
monkeypatch.setattr(briefcase_debugger.pdb, "RemotePdb", fake_remote_pdb)
|
|
66
|
+
|
|
67
|
+
# start test function
|
|
68
|
+
briefcase_debugger.start_remote_debugger()
|
|
69
|
+
|
|
70
|
+
fake_remote_pdb.assert_called_once_with(
|
|
71
|
+
"somehost",
|
|
72
|
+
9999,
|
|
73
|
+
quiet=True,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
captured = capsys.readouterr()
|
|
77
|
+
assert "Waiting for debugger to attach..." in captured.out
|
|
78
|
+
for cmd in expected_host_cmds:
|
|
79
|
+
assert cmd in captured.out
|
|
80
|
+
assert captured.err == ""
|