167 lines
5.0 KiB
Python
Executable File
167 lines
5.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Rebuild oca/ from the module-sources/oca/* submodules based om what is needed from custom bundles.
|
|
"""
|
|
import ast
|
|
import pathlib
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
CUSTOM = ROOT / "custom"
|
|
SOURCES = ROOT / "module-sources" / "oca"
|
|
EXTRA_FILE = ROOT / "module-sources" / "extra-modules.txt"
|
|
OUTPUT = ROOT / "oca"
|
|
KNOWN_CORE_MODULES = {
|
|
"crm",
|
|
"membership",
|
|
"sale_management",
|
|
"base_setup",
|
|
"base_import_module",
|
|
"auth_oauth",
|
|
"auth_signup",
|
|
"account",
|
|
"base",
|
|
"base_iban",
|
|
"base_sparse_field",
|
|
"hr",
|
|
"l10n_nl",
|
|
"mail",
|
|
"portal",
|
|
"product",
|
|
"web",
|
|
"web_tour",
|
|
"auth_totp",
|
|
"auth_password_policy_signup",
|
|
}
|
|
|
|
|
|
def run(*args, **kwargs):
|
|
kwargs.setdefault("cwd", ROOT)
|
|
kwargs.setdefault("check", True)
|
|
return subprocess.run(args, **kwargs)
|
|
|
|
|
|
def custom_module_names():
|
|
return {
|
|
module_dir.name
|
|
for module_dir in CUSTOM.iterdir()
|
|
if (module_dir / "__manifest__.py").is_file()
|
|
}
|
|
|
|
|
|
def wanted_modules():
|
|
custom_names = custom_module_names()
|
|
wanted = set(custom_names)
|
|
for name in custom_names:
|
|
manifest = ast.literal_eval((CUSTOM / name / "__manifest__.py").read_text())
|
|
wanted.update(manifest.get("depends", []))
|
|
for line in EXTRA_FILE.read_text().splitlines():
|
|
line = line.split("#", 1)[0].strip()
|
|
if line:
|
|
wanted.add(line)
|
|
return wanted
|
|
|
|
|
|
def index_sources():
|
|
# Maps each module name to every source dir that has it.
|
|
index = {}
|
|
for source_dir in sorted(d for d in SOURCES.iterdir() if d.is_dir()):
|
|
for module_dir in source_dir.iterdir():
|
|
if (module_dir / "__manifest__.py").is_file():
|
|
index.setdefault(module_dir.name, []).append(module_dir)
|
|
return index
|
|
|
|
|
|
def resolve_transitive(wanted, index):
|
|
# A bundle's depends only names what it directly needs. But those
|
|
# depencies could also have other depends that should be included.
|
|
wanted = set(wanted)
|
|
while True:
|
|
added = set()
|
|
for name in wanted:
|
|
matches = index.get(name, [])
|
|
if len(matches) == 1:
|
|
manifest = ast.literal_eval((matches[0] / "__manifest__.py").read_text())
|
|
added.update(manifest.get("depends", []))
|
|
added -= wanted
|
|
if not added:
|
|
return wanted
|
|
wanted |= added
|
|
|
|
|
|
def rebuild_oca(wanted):
|
|
index = index_sources()
|
|
wanted = resolve_transitive(wanted, index)
|
|
|
|
if OUTPUT.exists():
|
|
shutil.rmtree(OUTPUT)
|
|
OUTPUT.mkdir()
|
|
|
|
missing, collisions = [], []
|
|
for name in sorted(wanted):
|
|
matches = index.get(name, [])
|
|
if not matches:
|
|
missing.append(name)
|
|
elif len(matches) > 1:
|
|
collisions.append((name, [f"{d.parent.name}/{d.name}" for d in matches]))
|
|
else:
|
|
shutil.copytree(matches[0], OUTPUT / name, ignore=shutil.ignore_patterns(".git"))
|
|
|
|
expected_missing = custom_module_names() | KNOWN_CORE_MODULES
|
|
unexplained_missing = [name for name in missing if name not in expected_missing]
|
|
if unexplained_missing:
|
|
print(
|
|
f"ERROR: {len(unexplained_missing)} wanted name(s) matched no source and aren't a "
|
|
f"known core module or custom module. Check for a typo or a missing submodule:",
|
|
file=sys.stderr,
|
|
)
|
|
for name in unexplained_missing:
|
|
print(f" {name}", file=sys.stderr)
|
|
|
|
if collisions:
|
|
print("ERROR: the following names exist in more than one source repo:", file=sys.stderr)
|
|
for name, dirs in collisions:
|
|
print(f" {name}: {dirs}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print(f"oca/ rebuilt: {len(wanted) - len(missing)} modules copied.")
|
|
|
|
|
|
def report_changes():
|
|
run("git", "add", "oca")
|
|
unchanged = run("git", "diff", "--cached", "--quiet", "--", "oca", check=False)
|
|
if unchanged.returncode == 0:
|
|
print("No changes to oca/ since the last commit.")
|
|
return
|
|
|
|
status = run(
|
|
"git", "diff", "--cached", "--name-status", "--", "oca",
|
|
capture_output=True, text=True,
|
|
).stdout
|
|
changed = {}
|
|
for line in status.splitlines():
|
|
code, _, path = line.partition("\t")
|
|
parts = path.split("/")
|
|
if len(parts) >= 2:
|
|
changed.setdefault(parts[1], set()).add(code[0])
|
|
|
|
added = sorted(m for m, codes in changed.items() if codes == {"A"})
|
|
removed = sorted(m for m, codes in changed.items() if codes == {"D"})
|
|
updated = sorted(set(changed) - set(added) - set(removed))
|
|
|
|
print(f"Changes staged: {len(changed)} module folder(s) in oca/.")
|
|
if added:
|
|
print(f" added ({len(added)}): {', '.join(added)}")
|
|
if removed:
|
|
print(f" removed ({len(removed)}): {', '.join(removed)}")
|
|
if updated:
|
|
print(f" updated ({len(updated)}): {', '.join(updated)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run("git", "submodule", "update", "--init")
|
|
rebuild_oca(wanted_modules())
|
|
report_changes()
|