Coverage for apio/commands/apio_raw.py: 81%

51 statements  

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

1# -*- coding: utf-8 -*- 

2# -- This file is part of the Apio project 

3# -- (C) 2016-2024 FPGAwars 

4# -- Authors 

5# -- * Jesús Arroyo (2016-2019) 

6# -- * Juan Gonzalez (obijuan) (2019-2024) 

7# -- License GPLv2 

8"""Implementation of 'apio raw' command""" 

9 

10import sys 

11import shlex 

12import subprocess 

13from typing import Tuple, List 

14import click 

15from apio.common.apio_console import cout, cerror 

16from apio.common.apio_styles import SUCCESS, ERROR, INFO 

17from apio.apio_context import ( 

18 ApioContext, 

19 PackagesPolicy, 

20 ProjectPolicy, 

21 RemoteConfigPolicy, 

22) 

23from apio.commands import options 

24from apio.utils import cmd_util 

25from apio.utils.cmd_util import ApioCommand 

26 

27# ----------- apio raw 

28 

29 

30def _run_raw_command( 

31 arg_list: List[str], is_windows: bool, verbose: bool 

32) -> int: 

33 """ 

34 Runs a command and returns its exit code. 

35 

36 This functionality has a few special requeiements so we placed it in a 

37 method of its own despite its simplicity. If you change it for any reason, 

38 make sure that your new code doesn't break anything. 

39 

40 Requirements: 

41 

42 1. Users should be able to copy commands such as yosys and nextpnr 

43 that are printed by apio scons and paste them with no change after 

44 `apio raw --` for proper execution. This applies to the native shell 

45 of each platform (e.g. on windows cmd or powershell rather than gitbash) 

46 

47 2. On window, commands such as 'apio raw -- zadig' will handle privilege 

48 elevation properly and will ask the user for approval. 

49 

50 3. On windows, commands such as `apio raw -- xyz` should match also the 

51 files xyz.exe, xyz.cmd and xyz.bat. 

52 """ 

53 if not arg_list: 53 ↛ 54line 53 didn't jump to line 54 because the condition on line 53 was never true

54 return 0 # nothing to run 

55 

56 # -- Join the args with proper quotes for the native shell. 

57 if is_windows: 57 ↛ 58line 57 didn't jump to line 58 because the condition on line 57 was never true

58 cmd_str = subprocess.list2cmdline(arg_list) 

59 else: 

60 cmd_str = shlex.join(arg_list) 

61 

62 # -- Run the command and return the exit code. 

63 if verbose: 63 ↛ 64line 63 didn't jump to line 64 because the condition on line 63 was never true

64 cout(f"\n---- Executing: [{cmd_str}]") 

65 

66 return subprocess.call(cmd_str, shell=True) 

67 

68 

69# -- Text in the rich-text format of the python rich library. 

70APIO_RAW_HELP = """ 

71The command 'apio raw' allows you to bypass Apio and run underlying tools \ 

72directly. This is an advanced command that requires familiarity with the \ 

73underlying tools. 

74 

75Before running the command, Apio temporarily modifies system environment \ 

76variables such as '$PATH' to provide access to its packages. To view these \ 

77environment changes, run the command with the '-v' option. 

78 

79Examples:[code] 

80 apio raw -- yosys --version # Yosys version 

81 apio raw -v -- yosys --version # Verbose apio info. 

82 apio raw -- yosys # Yosys interactive mode. 

83 apio raw -- icepll -i 12 -o 30 # Calc ICE PLL. 

84 apio raw -- which yosys # Lookup a command. 

85 apio raw -- bash # Open a shell with Apio's env. 

86 apio raw -- zadig # Run Zadig (on Windows). 

87 apio raw -v # Show apio env setting. 

88 apio raw -h # Show this help info.[/code] 

89 

90The marker '--' must separate between the arguments of the apio \ 

91command itself and those of the executed command. 

92""" 

93 

94 

95@click.command( 

96 name="raw", 

97 cls=ApioCommand, 

98 short_help="Execute commands directly from the Apio packages.", 

99 help=APIO_RAW_HELP, 

100 context_settings={"ignore_unknown_options": True}, 

101) 

102@click.pass_context 

103@click.argument("cmd", metavar="COMMAND", nargs=-1, type=click.UNPROCESSED) 

104@options.verbose_option 

105def cli( 

106 cmd_ctx: click.Context, 

107 *, 

108 # Arguments 

109 cmd: Tuple[str], 

110 # Options 

111 verbose: bool, 

112): 

113 """Implements the apio raw command which executes user 

114 specified commands from apio installed tools. 

115 """ 

116 

117 # -- If the user specifies a raw command, verify that the '--' separator 

118 # -- exists and that all the command tokens were specified after it. 

119 # -- Ideally Click should be able to validate it but it doesn't (?). 

120 if cmd: 

121 

122 # -- Locate the first '--' in argv. None if not found. 

123 dd_index = next((i for i, x in enumerate(sys.argv) if x == "--"), None) 

124 

125 # -- If the '--' separator was not specified this is an error. 

126 if dd_index is None: 

127 cerror("The raw command separator '--' was not found.") 

128 cout( 

129 "The raw command should be specified after a '--' separator.", 

130 "Type 'apio raw -h' for details.", 

131 style=INFO, 

132 ) 

133 sys.exit(1) 

134 

135 # -- Number of command tokens after the "--" 

136 n_after = len(sys.argv) - dd_index - 1 

137 

138 # -- Command tokens that where specified before the '--' 

139 tokens_before = list(cmd)[: len(cmd) - n_after] 

140 

141 # -- Should have no command tokens before the "--" 

142 if tokens_before: 

143 cerror(f"Invalid arguments: {tokens_before}.") 

144 cout( 

145 "Did you mean to have them after the '--' separator?", 

146 "See 'apio raw -h' for details.", 

147 style=INFO, 

148 ) 

149 sys.exit(1) 

150 

151 # -- At lease one of -v and cmd should be specified. 

152 cmd_util.check_at_least_one_param(cmd_ctx, ["verbose", "cmd"]) 

153 

154 # -- Create an apio context. We don't care about an apio project. 

155 # -- Using config and packages because we want the binaries in the apio 

156 # -- packages to be available for the 'apio raw' command. 

157 apio_ctx = ApioContext( 

158 project_policy=ProjectPolicy.NO_PROJECT, 

159 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

160 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

161 ) 

162 

163 # -- Set the env for packages. If verbose, also dumping the env changes 

164 # -- in a user friendly way. 

165 apio_ctx.set_env_for_packages(quiet=not verbose, verbose=verbose) 

166 

167 # -- If no command, we are done. 

168 if not cmd: 

169 sys.exit(0) 

170 

171 # -- Convert the tuple of strings to a list of strings. 

172 arg_list: List[str] = list(cmd) 

173 

174 # -- Invoke the command. 

175 exit_code = _run_raw_command(arg_list, apio_ctx.is_windows, verbose) 

176 

177 if verbose: 177 ↛ 178line 177 didn't jump to line 178 because the condition on line 177 was never true

178 cout("----\n") 

179 if exit_code == 0: 

180 cout("Exit status [0] OK", style=SUCCESS) 

181 

182 else: 

183 cout(f"Exist status [{exit_code}] ERROR", style=ERROR) 

184 

185 # -- Return the command's status code. 

186 sys.exit(exit_code)