Coverage for apio/commands/apio_format.py: 83%
52 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 03:53 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 03:53 +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"""
10import os
11from pathlib import Path
12from glob import glob
13import click
14from apio.common.apio_console import cout, cstyle, fatal_error
15from apio.common.apio_styles import EMPH3, SUCCESS
16from apio.common.common_util import PROJECT_BUILD_PATH, sort_files
17from apio.apio_context import (
18 ApioContext,
19 PackagesPolicy,
20 ProjectPolicy,
21 RemoteConfigPolicy,
22)
23from apio.commands import options
24from apio.utils import util, cmd_util
26# -------------- apio format
28# -- Text in the rich-text format of the python rich library.
29APIO_FORMAT_HELP = """
30The command 'apio format' formats the project's source files to ensure \
31consistency and style without altering their semantics. The command accepts \
32the names of specific source files to format or formats all project source \
33files by default.
35Examples:[code]
36 apio format # Format all source files.
37 apio format -v # Same but with verbose output.
38 apio format main.v main_tb.v # Format the two files.[/code]
40[NOTE] The file arguments are relative to the project directory, even if \
41the --project-dir option is used.
43The format command utilizes the format tool from the Verible project, which \
44can be configured by setting its flags in the apio.ini project file \
45For example:
48[code]format-verible-options =
49 --column_limit=80
50 --indentation_spaces=4
51 --line_terminator LF[/code]
53If needed, sections of source code can be protected from formatting using \
54Verible formatter directives:
56[code]// verilog_format: off
57... untouched code ...
58// verilog_format: on[/code]
60Another useful option provides a workaround for the Verible formatter \
61error 'Some token partitions failed to complete within the search limit':
63[code]format-verible-options =
64 --max_search_states=2000000[/code]
66For a full list of Verible formatter flags, refer to the documentation page \
67online or use the command 'apio raw -- verible-verilog-format --helpfull'.
68"""
70# -- File types that the format support. 'sv' indicates System Verilog
71# -- and 'h' indicates an includes file.
72_FILE_TYPES = [".v", ".sv", ".vh", ".svh"]
75@click.command(
76 name="format",
77 cls=cmd_util.ApioCommand,
78 short_help="Format verilog source files.",
79 help=APIO_FORMAT_HELP,
80)
81@click.argument("files", nargs=-1, required=False)
82@options.env_option_gen()
83@options.project_dir_option
84@options.verbose_option
85def cli(
86 *,
87 # Arguments
88 files: tuple[str],
89 env: str | None,
90 project_dir: Path | None,
91 verbose: bool,
92):
93 """Implements the format command which formats given or all source
94 files to format.
95 """
97 # -- Create an apio context with a project object.
98 apio_ctx = ApioContext(
99 project_policy=ProjectPolicy.PROJECT_REQUIRED,
100 remote_config_policy=RemoteConfigPolicy.CACHED_OK,
101 packages_policy=PackagesPolicy.ENSURE_PACKAGES,
102 project_dir_arg=project_dir,
103 env_arg=env,
104 )
106 # -- Get the optional formatter options from apio.ini
107 cmd_options = apio_ctx.project.get_list_option(
108 "format-verible-options", default=[]
109 )
111 # -- Add verbose option if needed.
112 if verbose and "--verbose" not in cmd_options: 112 ↛ 113line 112 didn't jump to line 113 because the condition on line 112 was never true
113 cmd_options.append("--verbose")
115 # -- Prepare the packages for use.
116 apio_ctx.set_env_for_packages(quiet=not verbose)
118 # -- Convert the tuple with file names into a list.
119 _files: list[str] = list(files)
121 # -- Change to the project's folder.
122 os.chdir(apio_ctx.project_dir)
124 # -- If user didn't specify files to format, all all source files to
125 # -- the list.
126 if not _files:
127 for ext in _FILE_TYPES:
128 _files.extend(glob("**/*" + ext, recursive=True))
130 # -- Filter out files that are under the _build directory.
131 _files = [
132 f for f in _files if PROJECT_BUILD_PATH not in Path(f).parents
133 ]
135 # -- Error if no file to format.
136 if not _files: 136 ↛ 137line 136 didn't jump to line 137 because the condition on line 136 was never true
137 fatal_error(f"No files of types {_FILE_TYPES}")
139 # -- Sort files, case insensitive.
140 _files = sort_files(_files)
142 # -- Find length of longest file name. We use it to align the
143 # -- status of each file.
144 width = max((len(str(f)) for f in _files), default=0)
146 # -- Iterate the files and format one at a time. We could format
147 # -- all of them at once but this way we can make the output more
148 # -- user friendly.
149 for f in _files:
150 # -- Convert to a Path object.
151 path = Path(f)
153 # -- Check the file extension.
154 _, ext = os.path.splitext(path)
155 if ext not in _FILE_TYPES: 155 ↛ 156line 155 didn't jump to line 156 because the condition on line 155 was never true
156 fatal_error(
157 f"'{f}' has an unexpected extension.",
158 info=f"Should be one of {_FILE_TYPES}",
159 )
161 # -- Check that the file exists and is a file.
162 if not path.is_file(): 162 ↛ 163line 162 didn't jump to line 163 because the condition on line 162 was never true
163 fatal_error(f"'{f}' is not a file.")
165 # -- Construct the formatter command line.
166 command = (
167 "verible-verilog-format --nofailsafe_success --inplace "
168 f' {" ".join(cmd_options)} "{f}"'
169 )
170 if verbose: 170 ↛ 171line 170 didn't jump to line 171 because the condition on line 170 was never true
171 cout(command)
173 # -- Remember bytes before
174 bytes_before = path.read_bytes()
176 # -- Execute the formatter command line.
177 exit_code = os.system(command)
178 if exit_code != 0: 178 ↛ 179line 178 didn't jump to line 179 because the condition on line 178 was never true
179 fatal_error(f"Formatting of '{f}' failed")
181 # -- Report
182 styled_fname = cstyle(f"{f:{width}}", style=EMPH3)
183 if path.read_bytes() != bytes_before:
184 cout(f"{styled_fname} formatted.")
185 else:
186 cout(f"{styled_fname} already formatted.")
188 # -- All done ok.
189 cout(f"Processed {util.plurality(_files, 'file')}.", style=SUCCESS)