Directory structure
November 17, 2025 · View on GitHub
This file describes the development process for Shroud. This includes setting up a development environment, running tests and creating a release.
Directory structure
After cloning the repository, the directories will be as follows.
Except for the build directory which is created by the Makefile.
build # created by Makefile
sphix # Generated documentation
$tempdir
regression # Output of regression tests
run # Output from compiled tests
venv # Virtual environment
compiler
# Directories of code to test compiler features
docs # RST files for documentation.
shroud # Python source
regression
input
prob.yaml
reference
prob
# Files generated by Shroud
run
prob
# The library wrapped for the problem.
tests
# unittest python code
Development
All development is driven by the top level Makefile.
Create development environment
A virtual environment is created and the YAML package is installed. Defaults to Python 3.
make virtualenv
make develop
make develop allows changes to be seen without installed the module again.
The tempdir can be verified with make print-tempdir.
Similar to temp.linux-x86_64-3.9.
For python2, soon to be dropped, edit the top level Makefile to
comment out the line PYTHONEXE := python3. It will then use the
python2 on your path.
make virtualenv2
make develop-setup
Note that due to dictionaries not being ordered in Python 2.7, the do-test tests will produce different output in the JSON files.
Documentation
Sphinx is used the create the documentation.
The files are in docs directory.
make docs
Creates build/sphinx/html/index.html
make pdf
Creates build/sphinx/latex/shroud.pdf
Testing
Run unittest
make test
The unittests are in the tests directory.
Run a single unittest:
cd tests
../build/$tempdir/venv/bin/python test_declast.py CheckParse.test_inheritance
Running a single Python unit test.
# Use the Python in the virtual environment
setenv WORK .../build/$tempdir
setenv PYTHONEXE $WORK/venv/bin/python
cd tests
$PYTHONEXE -m unittest test_ast.CheckAst.test_d_generate1
# Run a regression test
setenv PYTHONPATH $WORK/run/struct-class-c/python
cd regression/run/struct-class-c/python
$PYTHONEXE -m unittest test.Struct.test_Arrays1
Parse test
Parse some strings in tests/check_decl.py and compare to reference
in tests/check-decl.output.
The parse trees are compared.
make test-decl
make test-decl-diff
make test-decl-replace
Regression tests
make do-test
Run the script regression/do-test.py over some regression/input yaml files.
The output is saved in build/$tempdir/regression
A script is added to the output directory to help compare regression files.
cd tempdir/regression/classes ./cmp-shroud
Update fiducials
make do-test-replace [ do-test-args=tutorial ]
Running a single test
make do-test do-test-args=tutorial
Running a test manually after adding lines like "import pdb;pdb.set_trace()".
First, run the test via do-test. Extract run line from test.log.
It will contain paths and flags used to run do-test.
cd $WORK/build/$tempdir/regression/classes
$WORK/build/$tempdir/venv/bin/shroud ... $WORK/regression/input/classes.yaml
Test generated code
Compile the files in regression/reference.
The Library code which is wrapped is in the regression/run directory.
make test-clean
make test-all -k
Compile the generated code (after do-test-replace) and run some unit
tests. These tests are in regression/run and contains a small library
which is wrapped by the corresponding yaml file
(i.e. regression/input/tutorial.yaml wraps regression/run/tutorial)
The make -k flag will keep-going if one of the tests fails.
regression/run/Makfile is included by the top level makefile.
regression/run/default.mk has compiler flags.
The default is to use gcc. Other compilers can be specified as:
make compiler=oneapi test-all
make compiler=intel test-all
make compiler=cray test-all
Targets to compile a single test:
fortran-XXX compile wrappers for XXX.yaml
test-fortran-XXX run test for XXX.yaml
test-fortran All Fortran tests
test-cfi Fortran tests with C-Fortran-Interface
test-c-XXX
py-XXX compile module for XXX.yaml
test-python-XXX run Python test for XXX.yaml
test-python All Python tests
The Fortran tests use fruit, C tests assert, and Python tests use unittest.
test-commit
Runs test-clean, test, test-decl, and do-test. To be used before a commit.
make test-commit
Adding a regression test
-
Add a file
regression/input/newtest.yaml. -
Create directory
mkdir regression/reference/newtest. -
Run the test
make do-test do-test-args=newtest -
Look for errors in
$(tempdir)/regression/log.test -
Add a call to TestDesc in
regression/do-test.pyto run newtest. -
mkdir regression/run/newtest -
Create files
Makefile,newtest.cppandnewtest.hpp -
Add to variable fortran-test-list-std in
regression/run/Makefileto run as part oftest-alltarget. Trymake -n test-fortran-newtestto see commands which will be run.
Several tests are run with language=c and c++. To accomplish this,
the run/pkg files have the .c suffix. The sync-cxx target copies the
files to another directory with a .cpp suffix. This directory is added
to the VPATH. For the Python tests, there is an explicit run to force
the use of CXX. The VPATH finds both the C and C++ files since they
have the same stem.
Sample Fortran Makefile to allow TEST_FFLAGS to be in every compile
for a specific test.
testdir = xxx
TEST_FFLAGS = -DTEST_C_WRAPPER
C_OBJS = \
xxx.o \
wrapxxx.o
F_OBJS = \
main.o \
fruit.o \
wrapfxxx.o
include $(top)/regression/run/defaults.mk
## dependencies
Sample Python makefile
testdir = xxx
pymodule = xxx.so
AS_SHARED := TRUE
include $(top)/regression/run/defaults.mk
PY_OBJS = \
xxx.o
pyxxxmodule.o
# dependencies
Debugging
Wrappers
There are a couple of Shroud YAML options that provide useful debugging information.
options:
debug: True
debug_index: True
The debug option writes additional information into the generated files. This includes which statement group was used for each argument. This makes it easier to understand how the wrapper is being generated.
The debug_index requires the *debug option to also be True. It
dump out more information such as the _function_index and
signature fields. _function_index is an integer index used to
uniquely identify each funtion to map between the generated code and
the internal state dump into the JSON output file. signature is a
collection of statement group indexes Shroud uses to help identify if
two possible wrappers are the same.
debug is used in the testsuite to help identify when something
changes. Using debug_index will produce excessive change when a
function is added to a test or a statement group is added to
fc-statement.json. The additions can cause indexes to change
resulting in excessive changes. debug_index is designed for
debugging by the developer, not the user.
memory debugging
Valgrind can be used by setting valgrind=valgrind on the Make command line.
Actually, valgrind=path-to-valgrind.
make -k valgrind=valgrind test-all
Recompile code with address sanitizer.
make test-clean ; make -k use_asan=1 test-all
reference counting
Python-3.9.12/Misc/valgrind-python.supp
valgrind --tool=memcheck --suppressions=valgrind-python.supp \
python -E -tt ./my_python_script.py
Build Python with --with-address-sanitizer --with-pydebug
Development
file dependency
main.py
wrapX.py generate.py
fcfmt.py metaattrs.py
ast.py
declast.py declstr.py fcmem.py
typemap.py statements.py todict.py
whelpers.py
util.py visitor.py
error.py # no dependencies
metadata.py
Error with template: 'call SHROUD_copy_array_{cxx_T}({c_var_context}, {f_var}, size({f_var},kind=C_SIZE_T))'
can be debugged by changing util.wformat to remove comment to provide a backtrace.
adding a type
typemap.py
ast.py LibraryNode.create_std_names() add to namespace
declast.py get_canonical_typemap() Convert 'long int' into 'long'
formatting generated code
Always use {nullptr} instead of NULL. Helps C++.
error checking
Write a #error line instead of generating bad code so the
compiler will point out the error.
debugging
import yaml
print(yaml.dump(cf_tree, default_flow_style=False))
import pprint
pp = pprint.PrettyPrinter(indent=4)
pp.pprint( dict or tuple )
import pdb; pdb.set_trace()
% yes c | python ... >&! out
Answer 'c' to all pdb prompts.
To start pdb on an exception, uncomment line in main.py
#sys.excepthook = info
pythondevmode
If this environment variable is set to a non-empty string, enable Python Development Mode, introducing additional runtime checks that are too expensive to be enabled by default. New in version 3.7.
setenv PYTHONDEVMODE 1
Release Process
Typically, Shroud releases are done when it makes sense to make new features or other changes available to users.
Shroud follows the semantic versioning scheme for assigning release numbers. Semantic versioning conveys specific meaning about the code and modifications from version to version by the way version numbers are constructed.
The master branch records the official release history of the project. Specifically, whenever, the master branch is changed, it is tagged with a new version number. We use a git ‘lightweight tag’ for this purpose. Such a tag is essentially a pointer to a specific commit on the master branch.
When all pull requests intended to be included in a release have been merged into the develop branch, we create a release candidate branch from the develop branch. The release candidate branch is used to finalize preparations for the release. At this point, the next release cycle begins and work may continue on the develop branch.
When a release candidate branch is ready, it will be merged into the master branch and a release tag will be made. Then, the master branch is merged into the develop branch so that changes made to finalize the release are included there.
Update docs/releases.rst with changes.
It is best to continually update this file as user visible changes are made.
Start Release Candidate Branch
Create a release candidate branch off the develop branch to initiate a release. The name of a release branch should contain the associated release version name. Typically, we use a name like v0.14.0-rc
Finalize the Release in the Release Candidate Branch
All changes to Shroud related to finalizing the release documentation, as opposed to source code changes, are done in the release candidate branch. Typical changes include:
shroud/metadata.pysetup.pyDownload pathpyproject.tomlREADME.mddocs/conf.pydocs/releases.rst- Update reference files with new version.
make do-test-replace do-test-args=none
Build a Python Distribution
Build and test python distribution files. Both a tar file and a wheel. Do this before finishing the release to make sure it works.
make install-twine
make sdist
make twine-check
Test it in a virtual environment where SHROUD_DEV is where the
git clone is located.
cd $SHROUD_TEST
python3 -m venv venv
source venv/bin/activate or source venv/bin/activate.csh
pip install $SHROUD_DEV/dist/*.tar.gz
$SHROUD_TEST/venv/bin/shroud --version
cd $SHROUD_DEV
make do-test-install install-shroud=$SHROUD_TEST/venv/bin/shroud
Create a Pull Request for the Release
Create a pull request to merge the release candidate branch into master after all release preparation changes have been made. When the release candidate branch is complete, reviewed and approved, it will be merged into master.
Merge Release Candidate
Merge the release candidate branch into the master branch after it has been approved and all CI checks have passed. Do not "squash merge" as it will make the histories of master and develop branches disagree, and we want to preserve the history. After merging, the release candidate branch can be deleted.
Create Shiv file
shiv is a command line utility for building fully self-contained
Python zipapps as outlined in PEP 441, but with all their dependencies
included!
shiv's primary goal is making distributing Python applications fast & easy.
https://pypi.org/project/shiv/
git checkout master
make install-shiv
rm dist-shiv/*
make shiv-file
make do-test-shiv
Draft a GitHub Release
Draft a new Release on GitHub. Describe in https://help.github.com/articles/creating-releases/
-
Enter a Release title. We typically use titles of the following form Shroud-v0.14.0
-
Select master as the target branch to tag a release.
-
Enter the release tag name, such as v0.14.0, and specify to create the tag when the release is published.
-
Summary the information in
releases.rstinto the release description (omit any sections that are empty). -
Add shiv file into the binaries box.
-
Publish the release. This will create a tag at the tip of the master branch and add corresponding entry in the Releases section
Merge Master to Develop
Create a pull request to merge master into develop so that changes in the release candidate branch are integrated into subsequent Shroud development.
Upload to PyPi
The Pypi website is used by the pip utility to install Python modules.
You should be able to use the dist files from
Build a Python distribution since nothing has changed.
You need an account on pypi.org and test.pypi.org.
Pypi uses two factor authentication.
You should also create API tokens used in a .pypirc file.
First, updated to https://test.pypi.org.
make testpypi
Verify the test install.
cd ~/tmp
python3 -m venv testpypi
cd testpypi
bin/pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ llnl-shroud
bin/shroud --version
cd $SHROUD_DEV
make do-test-install install-shroud=$SHROUD_TEST/venv/bin/shroud
If the test install works, then do the real install. Note that pypi only allows a file to be uploaded once. You can delete then reupload within 24 hours. Otherwise, a new version number will be required.
make pypi
Then verify the install.
cd ~/tmp
python3 -m venv pypi
cd pypi
bin/pip install llnl-shroud
bin/shroud --version
cd $SHROUD_DEV
make do-test-install install-shroud=$SHROUD_TEST/venv/bin/shroud
Spack
After a commit hash is created, update
scripts/spack/packages/py-shroud/package.py.
Find the hash with git rev-list -n 1 v0.14.0
In the spack-packages repository update
repos/spack_repo/builtin/packages/py_shroud
Annual Changes
Update copyright in LICENSE.
docs/conf.py copyright date.