Skip to content

Commit

Permalink
Add pre-commit hook to generate external dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
sbidoul committed Oct 22, 2023
1 parent 969238e commit d86317d
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,10 @@
pass_filenames: false
language: python
files: (__manifest__\.py|__openerp__\.py|__terp__\.py)$

- id: oca-gen-external-dependencies
name: Generate requirements.txt for an addons directory
entry: oca-gen-external-dependencies
language: python
pass_filenames: false
files: (__manifest__\.py|__openerp__\.py|__terp__\.py|setup\.py|pyproject\.toml)$
4 changes: 4 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
"selenium",
"twine",
"wheel",
"pyproject_dependencies ; python_version>='3.7'",
"setuptools-odoo", # for oca-gen-external-dependencies
"whool", # for oca-gen-external-dependencies
],
python_requires=">=3.6",
classifiers=[
Expand All @@ -64,6 +67,7 @@
"oca-publish-modules = tools.publish_modules:main",
"oca-gen-addon-readme = tools.gen_addon_readme:gen_addon_readme",
"oca-gen-addon-icon = tools.gen_addon_icon:gen_addon_icon",
"oca-gen-external-dependencies = tools.gen_external_dependencies:main",
"oca-towncrier = tools.oca_towncrier:oca_towncrier",
"oca-create-migration-issue = tools.create_migration_issue:main",
"oca-update-pre-commit-excluded-addons = "
Expand Down
Empty file added tests/__init__.py
Empty file.
65 changes: 65 additions & 0 deletions tests/test_gen_external_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import subprocess
import sys
import textwrap

import pytest

from tools.gen_external_dependencies import main as gen_external_dependencies

from .utils import dir_changer


def _make_addon(
addons_dir, addon_name, depends, external_dependencies, installable=True
):
addon_dir = addons_dir / addon_name
addon_dir.mkdir()
manifest = {
"name": addon_name,
"version": "16.0.1.0.0",
"depends": depends,
"external_dependencies": external_dependencies,
"installable": installable,
}
addon_dir.joinpath("__manifest__.py").write_text(repr(manifest))
addon_dir.joinpath("__init__.py").touch()


@pytest.mark.skipif("sys.version_info < (3,7)")
def test_gen_external_dependencies(tmp_path):
...
_make_addon(
tmp_path,
addon_name="addon1",
depends=["mis_builder"],
external_dependencies={"python": ["requests", "xlrd"]},
)
_make_addon(
tmp_path,
addon_name="addon2",
depends=[],
external_dependencies={"python": ["requests", "pydantic>=2"]},
)
_make_addon(
tmp_path,
addon_name="addon3",
depends=[],
external_dependencies={},
installable=False,
)
with dir_changer(tmp_path):
assert gen_external_dependencies() != 0 # no pyproject.toml
subprocess.run([sys.executable, "-m", "whool", "init"], check=True)
assert tmp_path.joinpath("addon1").joinpath("pyproject.toml").is_file()
assert tmp_path.joinpath("addon2").joinpath("pyproject.toml").is_file()
assert gen_external_dependencies() == 0
requirements_txt_path = tmp_path.joinpath("requirements.txt")
assert requirements_txt_path.is_file()
assert requirements_txt_path.read_text() == textwrap.dedent(
"""\
# generated from manifests external_dependencies
pydantic>=2
requests
xlrd
"""
)
15 changes: 15 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os
from contextlib import contextmanager
from pathlib import Path
from typing import Iterator


@contextmanager
def dir_changer(path: Path) -> Iterator[None]:
"""A context manager that changes the current working directory"""
old_cwd = Path.cwd()
os.chdir(path)
try:
yield
finally:
os.chdir(old_cwd)
66 changes: 66 additions & 0 deletions tools/gen_external_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""Generate requirements.txt with external dependencies of Odoo addons."""

import os
import subprocess
import sys
from pathlib import Path


def main() -> int:
if sys.version_info < (3, 7):
raise SystemExit("Python 3.7+ is required.")

projects = [
*Path.glob(Path.cwd(), "*/pyproject.toml"),
*Path.glob(Path.cwd(), "setup/*/setup.py"),
]

if not projects:
return 1

env = os.environ.copy()
env.update(
{
# for better performance, since we are not interested in precise versions
"WHOOL_POST_VERSION_STRATEGY_OVERRIDE": "none",
"SETUPTOOLS_ODOO_POST_VERSION_STRATEGY_OVERRIDE": "none",
}
)

result = subprocess.run(
[
sys.executable,
"-m",
"pyproject_dependencies",
"--no-isolation", # whool and setuptools Odoo must be preinstalled
"--ignore-build-errors",
"--name-filter",
r"^(odoo$|odoo\d*-addon-)", # filter out odoo and odoo addons
*projects,
],
env=env,
check=False,
stdout=subprocess.PIPE,
text=True,
)

if result.returncode != 0:
return result.returncode

requirements = result.stdout

requirements_path = Path("requirements.txt")
if requirements:
with requirements_path.open("w") as f:
f.write("# generated from manifests external_dependencies\n")
f.write(requirements)
else:
if requirements_path.exists():
requirements_path.unlink()

return 0


if __name__ == "__main__":
sys.exit(main())

0 comments on commit d86317d

Please sign in to comment.