Coverage for apio/__main__.py: 90%
43 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:55 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:55 +0000
1#!venv/bin/python
2"""Apio starting point."""
4import sys
5import os
6import atexit
8# -- Since this module is used also as the entry point for the scons
9# -- subprocess, we don't add here dependency on util.py and use a light
10# -- weight debug env var detection.
11# -- Set APIO_DEBUG=1 to enable.
14def _is_debug_enabled() -> bool:
15 """Lightweight detection of the APIO_DEBUG env var. Malformed values
16 are treated here as 'disabled'; util.debug_level() reports them to the
17 user with a proper error message, on the commands that reach it."""
18 value = os.environ.get("APIO_DEBUG", "0")
20 # -- For windows benefit, remove optional quotes, same as
21 # -- env_options.get() does.
22 if value.startswith('"') and value.endswith('"'):
23 value = value[1:-1]
25 try:
26 return int(value) > 0
27 except ValueError:
28 return False
31debug_enabled = _is_debug_enabled()
34def on_exit(msg):
35 """Prints a debug message on process exit. The msg string is passed
36 from the handler registrations below.
37 """
38 if debug_enabled:
39 print(msg)
42def main():
43 """Apio starting point."""
45 if debug_enabled:
46 print(f"Apio main(): original argv: {sys.argv}")
48 # pylint: disable=import-outside-toplevel
50 # -- Handle the case of the scons subprocess. Because we also use
51 # -- pyinstaller, without standard pip packages, we can't simply invoke
52 # -- the 'scons' binary so we invoke the SCons module from the apio main.
53 # -- See more details at:
54 # -- See https://github.com/orgs/pyinstaller/discussions/9023.
55 # --
56 # -- In this case argv goes through these stages
57 # -- Original: <binary> -m apio --scons ...
58 # -- Under python: <binary> --scons ...
59 # -- Under pyinstaller: <binary> -m apio --scons ...
60 # -- After fixing for scons: <binary> ...
61 # --
62 # -- Notice that the -m apio args are automatically removed by the
63 # -- python interpreter but are preserved by the pyinstaller, hence
64 # -- the two cases.
65 python_scons = sys.argv[1:2] == ["--scons"]
66 pyinstaller_scons = sys.argv[1:4] == ["-m", "apio", "--scons"]
68 if debug_enabled:
69 print(f"Apio main(): {python_scons=}")
70 print(f"Apio main(): {pyinstaller_scons=}")
72 if python_scons or pyinstaller_scons:
74 if debug_enabled: 74 ↛ 75line 74 didn't jump to line 75 because the condition on line 74 was never true
75 print("Apio main(): this is an scons process")
77 # -- Since scons_main() doesn't return, we use this handler to print
78 # -- an exit message for debugging.
79 atexit.register(on_exit, "Apio main(): scons process exit")
81 # -- Import and initialize scons only when running the scons
82 # -- subprocess.
83 from SCons.Script.Main import main as scons_main
84 from apio.common.common_util import maybe_wait_for_remote_debugger
86 # -- If system env var APIO_SCONS_DEBUGGER is defined, regardless of
87 # -- its value, we wait on a remote debugger to be attached, e.g.
88 # -- from Visual Studio Code.
89 # --
90 # -- You can place a breakpoint for example at SconsHandler.start().
91 maybe_wait_for_remote_debugger("APIO_SCONS_DEBUGGER")
93 # -- Drop the scons trigger args
94 if python_scons: 94 ↛ 101line 94 didn't jump to line 101 because the condition on line 94 was always true
95 # -- Case 1: Using a python interpreter which already dropped
96 # -- the ["-m", "apio"]. Dropping just ["--scons"]
97 sys.argv[1:] = sys.argv[2:]
98 else:
99 # -- Case 2: Using pyinstaller which preserved the ["-m", "apio"].
100 # -- Dropping ["-m", "apio", "--scons"]
101 sys.argv[1:] = sys.argv[4:]
103 if debug_enabled: 103 ↛ 104line 103 didn't jump to line 104 because the condition on line 103 was never true
104 print(f"Apio main(): scons fixed argv: {sys.argv}")
106 # -- Invoke the scons main function. It gets the modified argv from sys
107 # -- and doesn't return.
108 scons_main()
110 # -- Handle the case of a normal apio invocation.
111 else:
112 if debug_enabled:
113 print("Apio main(): this is an apio process")
115 # -- Since apio_top_cli() doesn't return, we use this handler to print
116 # -- an exit message for debugging.
117 atexit.register(on_exit, "Apio main(): apio process exit")
119 # -- Import the apio CLI only when running the apio process
120 # -- (as opposed to the scons sub process).
121 from apio.commands.apio import apio_top_cli
123 # -- Due to the Click decorations of apio_top_cli() and the Click
124 # -- magic, this function is not really invoked but Click dispatches
125 # -- it to its subcommands that was selected by the user command line.
126 # -- This function call doesn't return.
127 apio_top_cli()
130if __name__ == "__main__":
131 main()