NCSU GeoForAll Lab
at the
Center for Geospatial Analytics
North Carolina State University
FOSS4G Firenze, August 25, 2022
Research software engineering as a service, Tangible Landscape support, open source development, skill-transfer at geospatial.ncsu.edu
Large contributions by: Nicklas Larsson, Anna Petrasova, Carmen Tawalika, Loïc Bartoletti, Vaclav Petras
utils
directory (to be used locally).
Wanted: CSpell (possibly through MegaLinter)
7.8.0 | main branch | |
---|---|---|
PEP 8 formatting | 25518 | 0* |
Flake8 | 10590 | 3018** |
GCC -Wall -Wextra | 1200 | 959*** |
*Assuming Black compliance equals PEP 8 compliance.
** Flake8 issues remaining: comments, start imports, unused variables, invalid escape sequences.
*** GCC critical issues fixed: dangling-else, discarded-qualifiers, format, logical-op, parentheses, pointers, restrict, tautological-compare, uninitialized.
git diff
" in mindApply formatting to all code:
black .
Check in CI:
black --check .
In a GRASS session:
valgrind --leak-check=yes r.stats.quantile base=zipcodes cover=elevation quantiles=3 -p
valgrind --track-origins=yes --redzone-size=2048 d.legend soil_loss
Using --exec
:
grass --tmp-mapset ~/data/nc_spm/ --exec valgrind r.stats.quantile base=zipcodes cover=elevation quantiles=3 -p
gs.fatal(_("File does not exist: {name}".format(name=filename)))
Pylint reports:
Formatting a regular string which could be a f-string (consider-using-f-string)
Analysis:
_
(underscore) is used.
gs.fatal(_("File does not exist: {name}").format(name=filename))
It is much easier start right at the beginning, than later on.
Even for small projects!
It was hard to get started in GRASS GIS, but it is hard even for projects which are couple years old.
Now!
… but better late than never and certainly possible.
Example for GCC and Clang:
gcc -Wall -Werror ...
Flake8 can ignore warnings per file or directory:
per-file-ignores =
lib/init/grass.py: E501, E722, F821, F841, W605
gui/wxpython/vnet/*: F841, E501
Warnings can be easily enabled again later from a single place.
Yes — only one line:
apple._refresh_core() # pylint: disable=protected-access
No — everything after the line:
# pylint: disable=protected-access
apple._refresh_core()
Also: Line ignores and in-file ignores are best for ignores which are there to stay, i.e., the code just has to be that way or the warning does not apply
(e.g., access to private or protected methods in tests).
Code:
gs.fatal(_("File does not exist: {name}").format(name=filename))
Original error from Pylint:
Undefined variable '_' (undefined-variable)
Quick fix for Pylint by disabling the reported message:
disable=undefined-variable
Full fix for Pylint by telling it about our code:
additional-builtins=_
For permanent ignores, include explanation.
Example: Pylint disable in a theoretical GUI application:
# Always show an error message window even when the code is broken.
# This avoids tracebacks appearing without context.
except Exception as error: # pylint: disable=broad-except
show_error_window(error)
$
Contribute: github.com/OSGeo/grass
Get support from NC State University: vpetras@ncsu.edu
Why to Change Code which Works?
With languages, dependencies, operating systems changing fasts, even stable code needs modifications. So, you may as well update the code to latest standard because:
Even if you don't care about best practices:
When you can apply automatic linters, maintaining and changing the code is simpler because there is more checks in place.
Should I changing surrounding code only because it is ugly?
Don't add bad code because the surrounding code is bad.
Leave the code in a better condition than when you found it.
So, the code will change (and that's fine).
The day when all manual testing will be obsolete because of some tool is very far away.
Linters work together with, not instead of manual testing and written tests.
__init__.py
)Check all code:
flake8
Check all code:
pydocstyle
Apply formatting to all code:
isort .
Check in CI:
isort --check-only .
General use:
pylint scripts/v.db.univar/v.db.univar.py
In GRASS GIS with environment variables:
PYTHONPATH=$(grass --config python_path) \
LD_LIBRARY_PATH=$(grass --config path)/lib \
pylint scripts/v.db.univar/v.db.univar.py
Or using --exec
:
grass --tmp-location XY --exec pylint scripts/v.db.univar/v.db.univar.py
General use:
gcc -std=c99 -Wall -Wextra ...
In GRASS GIS configuration (e.g., your script):
export CFLAGS="-std=gnu99 -Wall"
export CXXFLAGS="-std=c++11 -Wall"
./configure \
--enable-largefile \
...
Usage:
cppcheck lib/
Usage:
clang-tidy -header-filter=project/*.hpp -checks=* -warnings-as-errors=* main.cpp
ClangFormat runs on files (not directories):
clang-format vector/v.kernel/main.c
ClangFormat check (only since version 10):
clang-format --dry-run -Werror vector/v.kernel/main.c
GitHub Actions use ClangFormat with
DoozyX/clang-format-lint-action
.git
)build
, dist.*
)
[tool.black]
exclude = '''
(
/(
\.git
| \.venv
| bin\..*
| dist\..*
)/
| python/libgrass_interface_generator/
)
'''
To get started, fix what you can, exclude (ignore) the rest.
In command line:
black --check --diff scripts
black --check --diff utils
Separate jobs in GitHub Actions:
strategy:
matrix:
directory:
- scripts
- utils
fail-fast: false
steps:
# ...
- name: Run Black
run: |
cd ${{ matrix.directory }}
black --check --diff .
Works well as a temporary solution, but hard to navigate and maintain long term.
.git-blame-ignore-revs
file
with a list of Git commit hashes to ignore when doing git blame
.
→ Minimizes influence of formatting changes on code history.
Use locally:
git blame vector/v.patch/main.c --ignore-revs-file .git-blame-ignore-revs
GitHub applies it automatically.
actions/setup-python in GitHub Actions:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.10"
Pylint minimal supported Python version:
pylint --py-version=3.7 ...
Black target Python versions:
[tool.black]
target-version = ['py37', 'py38', 'py39', 'py310']
Use simpler Language?...if you can.
Python is easier to get right and has easy to use tooling than C and C++.
But: C compilation-time checks. Strong C++ type checking.