Coverage for apio/__main__.py: 90%

43 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-09 01:55 +0000

1#!venv/bin/python 

2"""Apio starting point.""" 

3 

4import sys 

5import os 

6import atexit 

7 

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. 

12 

13 

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") 

19 

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] 

24 

25 try: 

26 return int(value) > 0 

27 except ValueError: 

28 return False 

29 

30 

31debug_enabled = _is_debug_enabled() 

32 

33 

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) 

40 

41 

42def main(): 

43 """Apio starting point.""" 

44 

45 if debug_enabled: 

46 print(f"Apio main(): original argv: {sys.argv}") 

47 

48 # pylint: disable=import-outside-toplevel 

49 

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"] 

67 

68 if debug_enabled: 

69 print(f"Apio main(): {python_scons=}") 

70 print(f"Apio main(): {pyinstaller_scons=}") 

71 

72 if python_scons or pyinstaller_scons: 

73 

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") 

76 

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") 

80 

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 

85 

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") 

92 

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:] 

102 

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}") 

105 

106 # -- Invoke the scons main function. It gets the modified argv from sys 

107 # -- and doesn't return. 

108 scons_main() 

109 

110 # -- Handle the case of a normal apio invocation. 

111 else: 

112 if debug_enabled: 

113 print("Apio main(): this is an apio process") 

114 

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") 

118 

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 

122 

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() 

128 

129 

130if __name__ == "__main__": 

131 main()