Coverage for apio/commands/apio_format.py: 74%

61 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-24 03:51 +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 format' command""" 

9 

10import sys 

11import os 

12from pathlib import Path 

13from glob import glob 

14from typing import Tuple, List, Optional 

15import click 

16from apio.common.apio_console import cout, cerror, cstyle 

17from apio.common.apio_styles import EMPH3, SUCCESS, INFO, ERROR 

18from apio.common.common_util import PROJECT_BUILD_PATH, sort_files 

19from apio.apio_context import ( 

20 ApioContext, 

21 PackagesPolicy, 

22 ProjectPolicy, 

23 RemoteConfigPolicy, 

24) 

25from apio.commands import options 

26from apio.utils import util, cmd_util 

27 

28 

29# -------------- apio format 

30 

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

32APIO_FORMAT_HELP = """ 

33The command 'apio format' formats the project's source files to ensure \ 

34consistency and style without altering their semantics. The command accepts \ 

35the names of specific source files to format or formats all project source \ 

36files by default. 

37 

38Examples:[code] 

39 apio format # Format all source files. 

40 apio format -v # Same but with verbose output. 

41 apio format main.v main_tb.v # Format the two files.[/code] 

42 

43[NOTE] The file arguments are relative to the project directory, even if \ 

44the --project-dir option is used. 

45 

46The format command utilizes the format tool from the Verible project, which \ 

47can be configured by setting its flags in the apio.ini project file \ 

48For example: 

49 

50 

51[code]format-verible-options = 

52 --column_limit=80 

53 --indentation_spaces=4[/code] 

54 

55If needed, sections of source code can be protected from formatting using \ 

56Verible formatter directives: 

57 

58[code]// verilog_format: off 

59... untouched code ... 

60// verilog_format: on[/code] 

61 

62For a full list of Verible formatter flags, refer to the documentation page \ 

63online or use the command 'apio raw -- verible-verilog-format --helpfull'. 

64""" 

65 

66# -- File types that the format support. 'sv' indicates System Verilog 

67# -- and 'h' indicates an includes file. 

68_FILE_TYPES = [".v", ".sv", ".vh", ".svh"] 

69 

70 

71@click.command( 

72 name="format", 

73 cls=cmd_util.ApioCommand, 

74 short_help="Format verilog source files.", 

75 help=APIO_FORMAT_HELP, 

76) 

77@click.argument("files", nargs=-1, required=False) 

78@options.env_option_gen() 

79@options.project_dir_option 

80@options.verbose_option 

81def cli( 

82 *, 

83 # Arguments 

84 files: Tuple[str], 

85 env: Optional[str], 

86 project_dir: Optional[Path], 

87 verbose: bool, 

88): 

89 """Implements the format command which formats given or all source 

90 files to format. 

91 """ 

92 

93 # -- Create an apio context with a project object. 

94 apio_ctx = ApioContext( 

95 project_policy=ProjectPolicy.PROJECT_REQUIRED, 

96 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

97 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

98 project_dir_arg=project_dir, 

99 env_arg=env, 

100 ) 

101 

102 # -- Get the optional formatter options from apio.ini 

103 cmd_options = apio_ctx.project.get_list_option( 

104 "format-verible-options", default=[] 

105 ) 

106 

107 # -- Add verbose option if needed. 

108 if verbose and "--verbose" not in cmd_options: 108 ↛ 109line 108 didn't jump to line 109 because the condition on line 108 was never true

109 cmd_options.append("--verbose") 

110 

111 # -- Prepare the packages for use. 

112 apio_ctx.set_env_for_packages(quiet=not verbose) 

113 

114 # -- Convert the tuple with file names into a list. 

115 _files: List[str] = list(files) 

116 

117 # -- Change to the project's folder. 

118 os.chdir(apio_ctx.project_dir) 

119 

120 # -- If user didn't specify files to format, all all source files to 

121 # -- the list. 

122 if not _files: 

123 for ext in _FILE_TYPES: 

124 _files.extend(glob("**/*" + ext, recursive=True)) 

125 

126 # -- Filter out files that are under the _build directory. 

127 _files = [ 

128 f for f in _files if PROJECT_BUILD_PATH not in Path(f).parents 

129 ] 

130 

131 # -- Error if no file to format. 

132 if not _files: 132 ↛ 133line 132 didn't jump to line 133 because the condition on line 132 was never true

133 cerror(f"No files of types {_FILE_TYPES}") 

134 sys.exit(1) 

135 

136 # -- Sort files, case insensitive. 

137 _files = sort_files(_files) 

138 

139 # -- Iterate the files and format one at a time. We could format 

140 # -- all of them at once but this way we can make the output more 

141 # -- user friendly. 

142 failures = 0 

143 for f in _files: 

144 # -- Convert to a Path object. 

145 path = Path(f) 

146 

147 # -- Check the file extension. 

148 _, ext = os.path.splitext(path) 

149 if ext not in _FILE_TYPES: 149 ↛ 150line 149 didn't jump to line 150 because the condition on line 149 was never true

150 cerror(f"'{f}' has an unexpected extension.") 

151 cout(f"Should be one of {_FILE_TYPES}", style=INFO) 

152 sys.exit(1) 

153 

154 # -- Check that the file exists and is a file. 

155 if not path.is_file(): 155 ↛ 156line 155 didn't jump to line 156 because the condition on line 155 was never true

156 cerror(f"'{f}' is not a file.") 

157 sys.exit(1) 

158 

159 # -- Print file name. 

160 styled_f = cstyle(f, style=EMPH3) 

161 cout(f"Formatting {styled_f}") 

162 

163 # -- Construct the formatter command line. 

164 command = ( 

165 "verible-verilog-format --nofailsafe_success --inplace " 

166 f' {" ".join(cmd_options)} "{f}"' 

167 ) 

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

169 cout(command) 

170 

171 # -- Execute the formatter command line. 

172 exit_code = os.system(command) 

173 if exit_code != 0: 173 ↛ 174line 173 didn't jump to line 174 because the condition on line 173 was never true

174 cerror(f"Formatting of '{f}' failed") 

175 failures += 1 

176 

177 # -- Report failures, if eny. 

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

179 cout() 

180 cout( 

181 f"Encountered {util.plurality(failures, 'failure')}.", 

182 style=ERROR, 

183 ) 

184 sys.exit(1) 

185 

186 # -- All done ok. 

187 cout(f"Processed {util.plurality(_files, 'file')}.", style=SUCCESS) 

188 sys.exit(0)