aboutsummaryrefslogtreecommitdiff
path: root/CODECONVENTIONS.md
blob: d6b0a5926bf4b6b0f681cc26777aa36a2c745778 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
Git commit conventions
======================

Each commit message should start with a directory or full file path
prefix, so it was clear which part of codebase a commit affects. If
a change affects one file, it's better to use path to a file. If it
affects few files in a subdirectory, using subdirectory as a prefix
is ok. For longish paths, it's acceptable to drop intermediate
components, which still should provide good context of a change.
It's also ok to drop file extensions.

Besides prefix, first line of a commit message should describe a
change clearly and to the point, and be a grammatical sentence with
final full stop. First line must fit within 72 characters. Examples
of good first line of commit messages:

    py/objstr: Add splitlines() method.
    py: Rename FOO to BAR.
    docs/machine: Fix typo in reset() description.
    ports: Switch to use lib/foo instead of duplicated code.

After the first line add an empty line and in the following lines describe
the change in a detail, if needed, with lines fitting within 75 characters
(with an exception for long items like URLs which cannot be broken). Any
change beyond 5 lines would likely require such detailed description.

To get good practical examples of good commits and their messages, browse
the `git log` of the project.

When committing you must sign-off your commit by adding "Signed-off-by:"
line(s) at the end of the commit message, e.g. using `git commit -s`.  You
are then certifying and signing off against the following:

* That you wrote the change yourself, or took it from a project with
  a compatible license (in the latter case the commit message, and possibly
  source code should provide reference where the implementation was taken
  from and give credit to the original author, as required by the license).
* That you are allowed to release these changes to an open-source project
  (for example, changes done during paid work for a third party may require
  explicit approval from that third party).
* That you (or your employer) agree to release the changes under
  MicroPython's license, which is the MIT license. Note that you retain
  copyright for your changes (for smaller changes, the commit message
  conveys your copyright; if you make significant changes to a particular
  source module, you're welcome to add your name to the file header).
* Your contribution including commit message will be publicly and
  indefinitely available for anyone to access, including redistribution
  under the terms of the project's license.
* Your signature for all of the above, which is the "Signed-off-by" line,
  includes your full real name and a valid and active email address by
  which you can be contacted in the foreseeable future.

Code auto-formatting
====================

Both C and Python code formatting are controlled for consistency across the
MicroPython codebase.  C code is formatted using the `tools/codeformat.py`
script which uses [uncrustify](https://github.com/uncrustify/uncrustify).
Python code is linted and formatted using
[ruff & ruff format](https://github.com/astral-sh/ruff).
After making changes, and before committing, run  `tools/codeformat.py` to
reformat your C code and `ruff format` for any Python code.  Without
arguments this tool will reformat all source code (and may take some time
to run).  Otherwise pass as arguments to the tool the files that changed,
and it will only reformat those.

uncrustify
==========

Only [uncrustify](https://github.com/uncrustify/uncrustify) v0.71 or v0.72 can
be used for MicroPython. Different uncrustify versions produce slightly
different formatting, and the configuration file formats are often
incompatible. v0.73 or newer *will not work*.

Depending on your operating system version, it may be possible to install a pre-compiled
uncrustify version:

Ubuntu, Debian
--------------

Ubuntu versions 21.10 or 22.04LTS, and Debian versions bullseye or bookworm all
include v0.72 so can be installed directly:

```
$ apt install uncrustify
```

Arch Linux
----------

The current Arch uncrustify version is too new. There is an [old Arch package
for v0.72](https://archive.archlinux.org/packages/u/uncrustify/) that can be
installed from the Arch Linux archive ([more
information](https://wiki.archlinux.org/title/Downgrading_packages#Arch_Linux_Archive)). Use
the [IgnorePkg feature](https://wiki.archlinux.org/title/Pacman#Skip_package_from_being_upgraded)
to prevent it re-updating.

Brew
----

This command may work, please raise a new Issue if it doesn't:

```
curl -L https://github.com/Homebrew/homebrew-core/raw/2b07d8192623365078a8b855a164ebcdf81494a6/Formula/uncrustify.rb > uncrustify.rb && brew install uncrustify.rb && rm uncrustify.rb
```

Automatic Pre-Commit Hooks
==========================

To have code formatting and commit message conventions automatically checked,
a configuration file is provided for the [pre-commit](https://pre-commit.com/)
tool.

First install `pre-commit`, either from your system package manager or via
`pip`. When installing `pre-commit` via pip, it is recommended to use a
virtual environment. Other sources, such as Brew are also available, see
[the docs](https://pre-commit.com/index.html#install) for details.

```
$ apt install pre-commit       # Ubuntu, Debian
$ pacman -Sy python-precommit  # Arch Linux
$ brew install pre-commit      # Brew
$ pip install pre-commit       # PyPI
```

Next, install [uncrustify (see above)](#uncrustify). Other dependencies are managed by
pre-commit automatically, but uncrustify needs to be installed and available on
the PATH.

Then, inside the MicroPython repository, register the git hooks for pre-commit
by running:

```
$ pre-commit install --hook-type pre-commit --hook-type commit-msg
```

pre-commit will now automatically run during `git commit` for both code and
commit message formatting.

The same formatting checks will be run by CI for any Pull Request submitted to
MicroPython. Pre-commit allows you to see any failure more quickly, and in many
cases will automatically correct it in your local working copy.

To unregister `pre-commit` from your MicroPython repository, run:

```
$ pre-commit uninstall --hook-type pre-commit --hook-type commit-msg
```

Tips:

* To skip pre-commit checks on a single commit, use `git commit -n` (for
  `--no-verify`).
* To ignore the pre-commit message format check temporarily, start the commit
  message subject line with "WIP" (for "Work In Progress").

Running pre-commit manually
===========================

Once pre-commit is installed as per the previous section it can be manually
run against the MicroPython python codebase to update file formatting on
demand, with either:
* `pre-commit run --all-files` to fix all files in the MicroPython codebase
* `pre-commit run --file ./path/to/my/file` to fix just one file
* `pre-commit run --file ./path/to/my/folder/*` to fix just one folder

Python code conventions
=======================

Python code follows [PEP 8](https://legacy.python.org/dev/peps/pep-0008/) and
is auto-formatted using [ruff format](https://docs.astral.sh/ruff/formatter)
with a line-length of 99 characters.

Naming conventions:
- Module names are short and all lowercase; eg pyb, stm.
- Class names are CamelCase, with abbreviations all uppercase; eg I2C, not
  I2c.
- Function and method names are all lowercase with words separated by
  a single underscore as necessary to improve readability; eg mem_read.
- Constants are all uppercase with words separated by a single underscore;
  eg GPIO_IDR.

C code conventions
==================

C code is auto-formatted using [uncrustify](https://github.com/uncrustify/uncrustify)
and the corresponding configuration file `tools/uncrustify.cfg`, with a few
minor fix-ups applied by `tools/codeformat.py`.  When writing new C code please
adhere to the existing style and use `tools/codeformat.py` to check any changes.
The main conventions, and things not enforceable via the auto-formatter, are
described below.

White space:
- Expand tabs to 4 spaces.
- Don't leave trailing whitespace at the end of a line.
- For control blocks (if, for, while), put 1 space between the
  keyword and the opening parenthesis.
- Put 1 space after a comma, and 1 space around operators.

Braces:
- Use braces for all blocks, even no-line and single-line pieces of
  code.
- Put opening braces on the end of the line it belongs to, not on
  a new line.
- For else-statements, put the else on the same line as the previous
  closing brace.

Header files:
- Header files should be protected from multiple inclusion with #if
  directives. See an existing header for naming convention.

Names:
- Use underscore_case, not camelCase for all names.
- Use CAPS_WITH_UNDERSCORE for enums and macros.
- When defining a type use underscore_case and put '_t' after it.

Integer types: MicroPython runs on 16, 32, and 64 bit machines, so it's
important to use the correctly-sized (and signed) integer types.  The
general guidelines are:
- For most cases use mp_int_t for signed and mp_uint_t for unsigned
  integer values.  These are guaranteed to be machine-word sized and
  therefore big enough to hold the value from a MicroPython small-int
  object.
- Use size_t for things that count bytes / sizes of objects.
- You can use int/uint, but remember that they may be 16-bits wide.
- If in doubt, use mp_int_t/mp_uint_t.

Comments:
- Be concise and only write comments for things that are not obvious.
- Use `// ` prefix, NOT `/* ... */`. No extra fluff.

Memory allocation:
- Use m_new, m_renew, m_del (and friends) to allocate and free heap memory.
  These macros are defined in py/misc.h.

Examples
--------

Braces, spaces, names and comments:

    #define TO_ADD (123)

    // This function will always recurse indefinitely and is only used to show
    // coding style
    int foo_function(int x, int some_value) {
        if (x < some_value) {
            foo(some_value, x);
        } else {
            foo(x + TO_ADD, some_value - 1);
        }

        for (int my_counter = 0; my_counter < x; ++my_counter) {
        }
    }

Type declarations:

    typedef struct _my_struct_t {
        int member;
        void *data;
    } my_struct_t;

Documentation conventions
=========================

MicroPython generally follows CPython in documentation process and
conventions. reStructuredText syntax is used for the documentation.

Specific conventions/suggestions:

* Use `*` markup to refer to arguments of a function, e.g.:

```
.. method:: poll.unregister(obj)

   Unregister *obj* from polling.
```

* Use following syntax for cross-references/cross-links:

```
:func:`foo` - function foo in current module
:func:`module1.foo` - function foo in module "module1"
    (similarly for other referent types)
:class:`Foo` - class Foo
:meth:`Class.method1` - method1 in Class
:meth:`~Class.method1` - method1 in Class, but rendered just as "method1()",
    not "Class.method1()"
:meth:`title <method1>` - reference method1, but render as "title" (use only
    if really needed)
:mod:`module1` - module module1

`symbol` - generic xref syntax which can replace any of the above in case
    the xref is unambiguous. If there's ambiguity, there will be a warning
    during docs generation, which need to be fixed using one of the syntaxes
    above
```

* Cross-referencing arbitrary locations
~~~
.. _xref_target:

Normal non-indented text.

This is :ref:`reference <xref_target>`.

(If xref target is followed by section title, can be just
:ref:`xref_target`).
~~~

* Linking to external URL:
```
`link text <http://foo.com/...>`_
```

* Referencing builtin singleton objects:
```
``None``, ``True``, ``False``
```

* Use following syntax to create common description for more than one element:
~~~
.. function:: foo(x)
              bar(y)

   Description common to foo() and bar().
~~~


More detailed guides and quickrefs:

* http://www.sphinx-doc.org/en/stable/rest.html
* http://www.sphinx-doc.org/en/stable/markup/inline.html
* http://docutils.sourceforge.net/docs/user/rst/quickref.html