Skip to content

Commit

Permalink
Merge branch 'topic/default/init-meson' into 'branch/default'
Browse files Browse the repository at this point in the history
2 helper commands: transonic-init-meson, transonic-clean-dir

See merge request fluiddyn/transonic!134
  • Loading branch information
paugier committed Feb 20, 2024
2 parents e9421f9 + bbd4751 commit 4e01b62
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 2 deletions.
7 changes: 6 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Release notes

See also the
[unreleased changes](https://foss.heptapod.net/fluiddyn/transonic/-/compare/0.6.2...branch%2Fdefault).
[unreleased changes](https://foss.heptapod.net/fluiddyn/transonic/-/compare/0.6.3...branch%2Fdefault).

## [0.6.3] (2024-02-20)

- New helper commands `transonic-init-meson` and `transonic-clean-dir`.

## [0.6.2] (2024-02-15)

Expand Down Expand Up @@ -262,3 +266,4 @@ See also the
[0.6.0]: https://foss.heptapod.net/fluiddyn/transonic/-/compare/0.5.3...0.6.0
[0.6.1]: https://foss.heptapod.net/fluiddyn/transonic/-/compare/0.6.0...0.6.1
[0.6.2]: https://foss.heptapod.net/fluiddyn/transonic/-/compare/0.6.1...0.6.2
[0.6.3]: https://foss.heptapod.net/fluiddyn/transonic/-/compare/0.6.2...0.6.3
15 changes: 15 additions & 0 deletions doc/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ and Scikit-image. The data necessary to build the package is staggered in
[a call to Transonic](https://foss.heptapod.net/fluiddyn/fluidsim/-/blob/branch/default/fluidsim/operators/meson.build)
to automatically create backends directories with their `meson.build` file.

````{tip}
To help developers to switch to the Meson build system, Transonic provides
a command to automatically create the different `meson.build` files. One needs
to give the path of the root directory of the package, for example from the
root directory of the
[Fluidfft repository](https://foss.heptapod.net/fluiddyn/fluidfft),
one could have used:
```sh
transonic-init-meson src/fluidfft
```
````

Note that Transonic can use for this step more that one backend with something
like

Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "pdm.backend"

[project]
name = "transonic"
version = "0.6.2"
version = "0.6.3"
description = "Make your Python code fly at transonic speeds!"
authors = [
{ name = "Pierre Augier", email = "[email protected]" },
Expand Down Expand Up @@ -34,6 +34,8 @@ mpi = ["mpi4py"]
[project.scripts]
transonic = "transonic.run:run"
transonic-get-include = "transonic_cl.get_includes:main"
transonic-init-meson = "transonic_cl.init_meson:main"
transonic-clean-dir = "transonic_cl.clean_dir:main"

[tool.pdm]
package-dir = "src"
Expand Down
43 changes: 43 additions & 0 deletions src/transonic_cl/clean_dir.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import argparse
import sys
from pathlib import Path
from shutil import rmtree

from transonic.backends import backends

cmd = "transonic-clean-dir"


names = set(f"__{backend}__" for backend in backends)


def clean_dir(path_dir):
"""Delete backend files from a directory (recursive)"""
subdirs_all = sorted(path for path in path_dir.glob("*") if path.is_dir())
subdirs = []
for subdir in subdirs_all:
if subdir.name in names:
rmtree(subdir, ignore_errors=True)
continue
subdirs.append(subdir)
for subdir in subdirs:
clean_dir(subdir)


def main():

parser = argparse.ArgumentParser(
prog="transonic-clean-dir",
description="Delete files related to Transonic in a directory",
)
parser.add_argument("path", help="Path directory.")

args = parser.parse_args()

path_dir = Path(args.path)

if not path_dir.is_dir():
print("Path given is not a directory", file=sys.stderr)
sys.exit(1)

clean_dir(path_dir)
80 changes: 80 additions & 0 deletions src/transonic_cl/init_meson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import argparse
import sys

from pathlib import Path, PurePosixPath


template = """
python_sources = [
{sources}
]
py.install_sources(
python_sources,
subdir: '{subdir}'
)
"""


def process_directory(path_dir, path_pack=None):

if path_pack is None:
path_pack = path_dir
print(f"Process {path_dir}")
subdir=path_pack.name
else:
subdir=PurePosixPath(path_dir.relative_to(path_pack.parent))
print(f"Process subdir {subdir}")

names_py = sorted(path.name for path in path_dir.glob("*.py"))
print(f"{names_py = }")
paths_subdirs = [
path
for path in path_dir.glob("*")
if path.is_dir() and not path.name.startswith("__")
]

names_subdirs = sorted(path.name for path in paths_subdirs)
print(f"{names_subdirs = }")

if names_py:
code = template.format(
sources="\n".join(f" '{name}'," for name in names_py),
subdir=subdir,
)
else:
code = ""

if names_subdirs:
code += (
"\n" + "\n".join(f"subdir('{name}')" for name in names_subdirs) + "\n"
)

if not code:
return

with open(path_dir / "meson.build", "w", encoding="utf-8") as file:
file.write(code)

for path_subdir in paths_subdirs:
process_directory(path_subdir, path_pack)


def main():

parser = argparse.ArgumentParser(
prog="transonic-init-meson",
description="Create Meson files from a Python package",
)
parser.add_argument("path", help="Path package.")

args = parser.parse_args()

path_pack = Path(args.path)

if not path_pack.is_dir():
print("Path given is not a directory", file=sys.stderr)
sys.exit(1)

process_directory(path_pack)

46 changes: 46 additions & 0 deletions tests/test_clean_dir.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from unittest.mock import patch

from transonic.backends import backends

from transonic_cl.clean_dir import main

cmd = "transonic-clean-dir"


names = [backend for backend in backends]


def test_clean_dir(tmp_path):

subpack_foo = tmp_path / "foo"
subpack_bar = subpack_foo / "bar"

subpack_bar.mkdir(parents=True)

for subpack in (subpack_foo, subpack_bar):
for name in names:
(subpack / f"__{name}__").mkdir()

with patch("sys.argv", [cmd, "-h"]):
try:
main()
except SystemExit as system_exit:
assert system_exit.code == 0
else:
raise RuntimeError("Issue with -h")

with patch("sys.argv", [cmd, str(tmp_path / "does_not_exist")]):
try:
main()
except SystemExit as system_exit:
assert system_exit.code == 1
else:
raise RuntimeError("Issue with not is_dir()")

with patch("sys.argv", [cmd, str(tmp_path)]):
main()

for subpack in (subpack_foo, subpack_bar):
assert subpack.exists()
for name in names:
assert not (subpack / f"__{name}__").exists()
96 changes: 96 additions & 0 deletions tests/test_init_meson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import sys
from pathlib import PurePosixPath
from unittest.mock import patch

from transonic_cl.init_meson import main

cmd = "transonic-init-meson"


mypack = {
"mypack": {
"a.py": None,
"b.py": None,
"c.txt": None,
"subpack0": {"d.py": None},
"subpack1": {},
"subpack2": {"subsubpack": {"e.py": None}},
}
}


meson_files = {
"mypack": """
python_sources = [
'a.py',
'b.py',
]
py.install_sources(
python_sources,
subdir: 'mypack'
)
subdir('subpack0')
subdir('subpack1')
subdir('subpack2')
""",
"mypack/subpack0": """
python_sources = [
'd.py',
]
py.install_sources(
python_sources,
subdir: 'mypack/subpack0'
)
""",
"mypack/subpack2": """
subdir('subsubpack')
""",
"mypack/subpack2/subsubpack": """
python_sources = [
'e.py',
]
py.install_sources(
python_sources,
subdir: 'mypack/subpack2/subsubpack'
)
""",
}


def create_package(package: dict, path_dir):
path_dir.mkdir(exist_ok=True)
for key, value in package.items():
if value is None:
# it should be a file
(path_dir / key).touch()
else:
assert isinstance(value, dict)
create_package(value, path_dir / key)


def test_init_meson(tmp_path):
path_mypack = tmp_path / "mypack"
print(f"create package\n{path_mypack}")
create_package(mypack, tmp_path)

with patch("sys.argv", [cmd, str(path_mypack)]):
main()

meson_paths = sorted(path_mypack.rglob("meson.build"))
assert len(meson_paths) == len(meson_files)

for meson_path in meson_paths:
code = (path_mypack / meson_path).read_text(encoding="utf-8")

meson_path = PurePosixPath(meson_path.relative_to(path_mypack.parent))
print(meson_path)
code_should_be = meson_files[str(meson_path.parent)]

if code != code_should_be:
print(code_should_be)

assert code == code_should_be

0 comments on commit 4e01b62

Please sign in to comment.