Skip to content

Commit 3d9ea88

Browse files
committed
Enhance CI formatting checks for Python files
Add Black-based formatting checks for Python files: - Update `.ci/check-format.sh` to include checks for Python files using Black. - Modify `.github/workflows/main.yml` to install Python dependencies for formatting. - Add `pyproject.toml` for configuring Black as the Python code formatter. - Enhance `CONTRIBUTING.md` with guidelines for Python formatting using Black. - Refactor Python scripts to adhere to the new formatting rules, including consistent use of quotes and spacing.
1 parent 88ddb73 commit 3d9ea88

File tree

11 files changed

+394
-225
lines changed

11 files changed

+394
-225
lines changed

.ci/check-format.sh

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,15 @@ for file in ${SH_SOURCES}; do
2121
done
2222
SH_MISMATCH_FILE_CNT=$(shfmt -l ${SH_SOURCES})
2323

24-
exit $((C_MISMATCH_LINE_CNT + SH_MISMATCH_FILE_CNT))
24+
PY_SOURCES=$(find "${REPO_ROOT}" | egrep "\.py$")
25+
for file in ${PY_SOURCES}; do
26+
echo "Checking Python file: ${file}"
27+
black --diff "${file}"
28+
done
29+
PY_MISMATCH_FILE_CNT=0
30+
if [ -n "${PY_SOURCES}" ]; then
31+
PY_MISMATCH_OUTPUT=$(black --check ${PY_SOURCES} 2>&1)
32+
PY_MISMATCH_FILE_CNT=$(echo "${PY_MISMATCH_OUTPUT}" | grep -c "^would reformat ")
33+
fi
34+
35+
exit $((C_MISMATCH_LINE_CNT + SH_MISMATCH_FILE_CNT + PY_MISMATCH_FILE_CNT))

.github/workflows/main.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,8 @@ jobs:
505505
- uses: actions/checkout@v4
506506
- name: coding convention
507507
run: |
508-
sudo apt-get install -q=2 clang-format-18 shfmt
508+
sudo apt-get install -q=2 clang-format-18 shfmt python3-pip
509+
pip3 install black
509510
.ci/check-newline.sh
510511
.ci/check-format.sh
511512
shell: bash

CONTRIBUTING.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ However, participation requires adherence to fundamental ground rules:
4848
Software requirement:
4949
* [clang-format](https://clang.llvm.org/docs/ClangFormat.html) version 18 or later.
5050
* [shfmt](https://github.com/mvdan/sh).
51+
* [black](https://github.com/psf/black) for Python code formatting.
5152

52-
This repository consistently contains an up-to-date `.clang-format` file with rules that match the explained ones and uses shell script formatting supported by `shfmt`.
53-
For maintaining a uniform coding style, execute the command `clang-format -i *.{c,h}` and `shfmt -w $(find . -type f -name "*.sh")`.
53+
This repository consistently contains an up-to-date `.clang-format` file with rules that match the explained ones, uses shell script formatting supported by `shfmt`, and Python code formatting with `black`.
54+
For maintaining a uniform coding style, execute the command `clang-format -i *.{c,h}`, `shfmt -w $(find . -type f -name "*.sh")`, and `black .` for Python files.
5455

5556
## Coding Style for Shell Script
5657

@@ -65,6 +66,51 @@ Shell scripts must be clean, consistent, and portable. The following `shfmt` rul
6566
* Add spaces around redirection operators (e.g., `>`, `>>`).
6667
* Place binary operators (e.g., `&&`, `|`) on the next line when breaking lines.
6768

69+
## Coding Style for Python
70+
71+
Python scripts must be clean, consistent, and adhere to modern Python best practices. The following formatting rules are enforced project-wide using `black`:
72+
73+
* Use 4 spaces for indentation (never tabs).
74+
* Limit lines to 88 characters maximum.
75+
* Use double quotes for strings (unless single quotes avoid escaping).
76+
* Use trailing commas in multi-line constructs.
77+
* Format imports according to PEP 8 guidelines.
78+
* Use Unix-style line endings (LF).
79+
* Remove trailing whitespace at the end of lines.
80+
* Ensure files end with a newline.
81+
82+
### Configuration
83+
84+
Python formatting is configured via the `pyproject.toml` file in the project root:
85+
86+
```toml
87+
[tool.black]
88+
line-length = 88
89+
target-version = ['py37', 'py38', 'py39', 'py310', 'py311']
90+
include = '\.pyi?$'
91+
extend-exclude = '''
92+
/(
93+
# directories
94+
\.git
95+
| build
96+
)/
97+
'''
98+
```
99+
100+
### Formatting Commands
101+
102+
To format Python code in the project:
103+
```bash
104+
# Format all Python files
105+
black .
106+
107+
# Check formatting without making changes
108+
black --check .
109+
110+
# Show what would be reformatted
111+
black --diff .
112+
```
113+
68114
## Coding Style for Modern C
69115

70116
This coding style is a variant of the [K&R style](https://en.wikipedia.org/wiki/Indentation_style#K&R).

pyproject.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[tool.black]
2+
line-length = 88
3+
target-version = ['py37', 'py38', 'py39', 'py310', 'py311']
4+
include = '\.pyi?$'
5+
extend-exclude = '''
6+
/(
7+
# directories
8+
\.git
9+
| build
10+
)/
11+
'''
12+

tests/arch-test-target/constants.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import os
22

3-
dutname = 'rv32emu'
4-
refname = 'sail_cSim'
3+
dutname = "rv32emu"
4+
refname = "sail_cSim"
55

66
root = os.path.abspath(os.path.dirname(__file__))
77
cwd = os.getcwd()
88

9-
misa_A = (1 << 0)
10-
misa_C = (1 << 2)
11-
misa_E = (1 << 4)
12-
misa_F = (1 << 5)
13-
misa_I = (1 << 8)
14-
misa_M = (1 << 12)
9+
misa_A = 1 << 0
10+
misa_C = 1 << 2
11+
misa_E = 1 << 4
12+
misa_F = 1 << 5
13+
misa_I = 1 << 8
14+
misa_M = 1 << 12
1515

16-
config_temp = '''[RISCOF]
16+
config_temp = """[RISCOF]
1717
ReferencePlugin={0}
1818
ReferencePluginPath={1}
1919
DUTPlugin={2}
@@ -29,4 +29,4 @@
2929
[{0}]
3030
pluginpath={1}
3131
path={1}
32-
'''
32+
"""

tests/arch-test-target/rv32emu/riscof_rv32emu.py

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515

1616
logger = logging.getLogger()
1717

18+
1819
class rv32emu(pluginTemplate):
1920
__model__ = "rv32emu"
2021
__version__ = "dev"
2122

2223
def __init__(self, *args, **kwargs):
2324
sclass = super().__init__(*args, **kwargs)
2425

25-
config = kwargs.get('config')
26+
config = kwargs.get("config")
2627

2728
# If the config node for this DUT is missing or empty. Raise an error. At minimum we need
2829
# the paths to the ispec and pspec files
@@ -34,25 +35,27 @@ def __init__(self, *args, **kwargs):
3435
# test-bench produced by a simulator (like verilator, vcs, incisive, etc). In case of an iss or
3536
# emulator, this variable could point to where the iss binary is located. If 'PATH variable
3637
# is missing in the config.ini we can hardcode the alternate here.
37-
self.dut_exe = os.path.join(config['PATH'] if 'PATH' in config else "","rv32emu")
38+
self.dut_exe = os.path.join(
39+
config["PATH"] if "PATH" in config else "", "rv32emu"
40+
)
3841

3942
# Number of parallel jobs that can be spawned off by RISCOF
4043
# for various actions performed in later functions, specifically to run the tests in
4144
# parallel on the DUT executable. Can also be used in the build function if required.
42-
self.num_jobs = str(config['jobs'] if 'jobs' in config else 1)
45+
self.num_jobs = str(config["jobs"] if "jobs" in config else 1)
4346

4447
# Path to the directory where this python file is located. Collect it from the config.ini
45-
self.pluginpath=os.path.abspath(config['pluginpath'])
48+
self.pluginpath = os.path.abspath(config["pluginpath"])
4649

4750
# Collect the paths to the riscv-config absed ISA and platform yaml files. One can choose
4851
# to hardcode these here itself instead of picking it from the config.ini file.
49-
self.isa_spec = os.path.abspath(config['ispec'])
50-
self.platform_spec = os.path.abspath(config['pspec'])
52+
self.isa_spec = os.path.abspath(config["ispec"])
53+
self.platform_spec = os.path.abspath(config["pspec"])
5154

5255
# We capture if the user would like the run the tests on the target or
5356
# not. If you are interested in just compiling the tests and not running
5457
# them on the target, then following variable should be set to False
55-
if 'target_run' in config and config['target_run']=='0':
58+
if "target_run" in config and config["target_run"] == "0":
5659
self.target_run = False
5760
else:
5861
self.target_run = True
@@ -72,36 +75,55 @@ def initialise(self, suite, work_dir, archtest_env):
7275
# Note the march is not hardwired here, because it will change for each
7376
# test. Similarly the output elf name and compile macros will be assigned later in the
7477
# runTests function
75-
self.compile_cmd = os.getenv("CROSS_COMPILE") + 'gcc -march={0}\
78+
self.compile_cmd = (
79+
os.getenv("CROSS_COMPILE")
80+
+ "gcc -march={0}\
7681
-static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles -g\
77-
-T '+self.pluginpath+'/env/link.ld\
78-
-I '+self.pluginpath+'/env/\
79-
-I ' + archtest_env + ' {2} -o {3} {4}'
82+
-T "
83+
+ self.pluginpath
84+
+ "/env/link.ld\
85+
-I "
86+
+ self.pluginpath
87+
+ "/env/\
88+
-I "
89+
+ archtest_env
90+
+ " {2} -o {3} {4}"
91+
)
8092

8193
def build(self, isa_yaml, platform_yaml):
8294
# load the isa yaml as a dictionary in python.
83-
ispec = utils.load_yaml(isa_yaml)['hart0']
95+
ispec = utils.load_yaml(isa_yaml)["hart0"]
8496

8597
# capture the XLEN value by picking the max value in 'supported_xlen' field of isa yaml. This
8698
# will be useful in setting integer value in the compiler string (if not already hardcoded);
87-
self.xlen = ('64' if 64 in ispec['supported_xlen'] else '32')
88-
89-
if 'E' not in ispec['ISA']:
90-
self.compile_cmd = self.compile_cmd+' -mabi='+('lp64 ' if 64 in ispec['supported_xlen'] else 'ilp32 ')
99+
self.xlen = "64" if 64 in ispec["supported_xlen"] else "32"
100+
101+
if "E" not in ispec["ISA"]:
102+
self.compile_cmd = (
103+
self.compile_cmd
104+
+ " -mabi="
105+
+ ("lp64 " if 64 in ispec["supported_xlen"] else "ilp32 ")
106+
)
91107
else:
92-
self.compile_cmd = self.compile_cmd+' -mabi='+('lp64e ' if 64 in ispec['supported_xlen'] else 'ilp32e ')
93-
self.compile_cmd += '-D RV32E '
108+
self.compile_cmd = (
109+
self.compile_cmd
110+
+ " -mabi="
111+
+ ("lp64e " if 64 in ispec["supported_xlen"] else "ilp32e ")
112+
)
113+
self.compile_cmd += "-D RV32E "
94114

95115
def runTests(self, testList):
96116
# Delete Makefile if it already exists.
97-
if os.path.exists(self.work_dir+ "/Makefile." + self.name[:-1]):
98-
os.remove(self.work_dir+ "/Makefile." + self.name[:-1])
117+
if os.path.exists(self.work_dir + "/Makefile." + self.name[:-1]):
118+
os.remove(self.work_dir + "/Makefile." + self.name[:-1])
99119
# create an instance the makeUtil class that we will use to create targets.
100-
make = utils.makeUtil(makefilePath=os.path.join(self.work_dir, "Makefile." + self.name[:-1]))
120+
make = utils.makeUtil(
121+
makefilePath=os.path.join(self.work_dir, "Makefile." + self.name[:-1])
122+
)
101123

102124
# set the make command that will be used. The num_jobs parameter was set in the __init__
103125
# function earlier
104-
make.makeCommand = 'make -j' + self.num_jobs
126+
make.makeCommand = "make -j" + self.num_jobs
105127

106128
# we will iterate over each entry in the testList. Each entry node will be refered to by the
107129
# variable testname.
@@ -110,14 +132,14 @@ def runTests(self, testList):
110132
testentry = testList[testname]
111133

112134
# we capture the path to the assembly file of this test
113-
test = testentry['test_path']
135+
test = testentry["test_path"]
114136

115137
# capture the directory where the artifacts of this test will be dumped/created. RISCOF is
116138
# going to look into this directory for the signature files
117-
test_dir = testentry['work_dir']
139+
test_dir = testentry["work_dir"]
118140

119141
# name of the elf file after compilation of the test
120-
elf = 'my.elf'
142+
elf = "my.elf"
121143

122144
# name of the signature file as per requirement of RISCOF. RISCOF expects the signature to
123145
# be named as DUT-<dut-name>.signature. The below variable creates an absolute path of
@@ -127,22 +149,24 @@ def runTests(self, testList):
127149
# for each test there are specific compile macros that need to be enabled. The macros in
128150
# the testList node only contain the macros/values. For the gcc toolchain we need to
129151
# prefix with "-D". The following does precisely that.
130-
compile_macros = ' -D' + " -D".join(testentry['macros'])
152+
compile_macros = " -D" + " -D".join(testentry["macros"])
131153

132154
# substitute all variables in the compile command that we created in the initialize function
133-
cmd = self.compile_cmd.format(testentry['isa'].lower(), self.xlen, test, elf, compile_macros)
155+
cmd = self.compile_cmd.format(
156+
testentry["isa"].lower(), self.xlen, test, elf, compile_macros
157+
)
134158

135-
# if the user wants to disable running the tests and only compile the tests, then
159+
# if the user wants to disable running the tests and only compile the tests, then
136160
# the "else" clause is executed below assigning the sim command to simple no action
137161
# echo statement.
138162
if self.target_run:
139-
# set up the simulation command. Template is for spike. Please change.
140-
simcmd = self.dut_exe + ' -a {0} {1}'.format(sig_file, elf)
163+
# set up the simulation command. Template is for spike. Please change.
164+
simcmd = self.dut_exe + " -a {0} {1}".format(sig_file, elf)
141165
else:
142166
simcmd = 'echo "NO RUN"'
143167

144168
# concatenate all commands that need to be executed within a make-target.
145-
execute = '@cd {0}; {1}; {2};'.format(testentry['work_dir'], cmd, simcmd)
169+
execute = "@cd {0}; {1}; {2};".format(testentry["work_dir"], cmd, simcmd)
146170

147171
# create a target. The makeutil will create a target with the name "TARGET<num>" where num
148172
# starts from 0 and increments automatically for each new target that is added

0 commit comments

Comments
 (0)