Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ The config file will be created at `{TLC_CODE_DIR}/config.toml`.

See [Configuration Schema](./docs/project-overview-pdr.md#configuration-schema) for all options and validation rules.

## System Packages

When `install-tools` installs system packages, it uses a curated list that goes beyond what Odoo itself requires. The goal is to pre-install all system-level dependencies needed to compile and run any OCA module out of the box — things like `libcups2-dev` (for `pycups`), `libgeos-dev` (for `shapely`), `libxmlsec1-dev` (for `pysaml2`), `libzbar-dev` (for `pyzbar`), and more. This avoids compilation errors when installing OCA module requirements, without needing to know in advance which modules will be used.

## System Requirements

- Python 3.10+
Expand Down
207 changes: 207 additions & 0 deletions assets/oca_contributor.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Exhaustive config to contribute to OCA
# Place this file at ~/code/config.toml (or set TLC_CODE_DIR)

versions = ["18.0", "19.0"]

[tools]
uv = [
"copier",
"oca-port",
"odooly",
"odoo-venv",
"odoo-addons-path"
]
system_packages = ["postgresql"]

[repos]
odoo = ["odoo"]
oca = [
# addons repositories
"account-analytic",
"account-budgeting",
"account-closing",
"account-consolidation",
"account-financial-reporting",
"account-financial-tools",
"account-fiscal-rule",
"account-invoice-reporting",
"account-invoicing",
"account-payment",
"account-reconcile",
"agreement",
"ai",
"apps-store",
"automation",
"bank-payment",
"bank-payment-alternative",
"bank-statement-import",
"brand",
"business-requirement",
"calendar",
"cim",
"commission",
"community-data-files",
"connector",
"connector-accountedge",
"connector-cmis",
"connector-ecommerce",
"connector-infor",
"connector-interfaces",
"connector-jira",
"connector-lengow",
"connector-lims",
"connector-magento",
"connector-odoo2odoo",
"connector-prestashop",
"connector-redmine",
"connector-sage",
"connector-salesforce",
"connector-spscommerce",
"connector-telephony",
"connector-woocommerce",
"contract",
"cooperative",
"credit-control",
"crm",
"crowdfunding",
"currency",
"data-protection",
"ddmrp",
"delivery-carrier",
"department",
"dms",
"donation",
"dotnet",
"e-commerce",
"e-learning",
"edi",
"edi-ediversa",
"edi-framework",
"edi-voxel",
"event",
"field-service",
"fleet",
"geospatial",
"helpdesk",
"hr",
"hr-attendance",
"hr-expense",
"hr-holidays",
"infrastructure",
"interface-github",
"intrastat-extrastat",
"iot",
"knowledge",
"l10n-brazil",
"l10n-france",
"l10n-usa",
"mail",
"maintenance",
"management-system",
"manufacture",
"manufacture-reporting",
"margin-analysis",
"mass-mailing",
"mis-builder",
"mis-builder-contrib",
"multi-company",
"odoo-pim",
"operating-unit",
"partner-contact",
"payroll",
"pms",
"pos",
"product-attribute",
"product-configurator",
"product-kitting",
"product-pack",
"product-variant",
"program",
"project",
"project-agile",
"project-reporting",
"purchase-reporting",
"purchase-workflow",
"pwa-builder",
"queue",
"repair",
"report-print-send",
"reporting-engine",
"resource",
"rest-api",
"rest-framework",
"rma",
"role-policy",
"sale-blanket",
"sale-channel",
"sale-financial",
"sale-prebook",
"sale-promotion",
"sale-reporting",
"sale-workflow",
"search-engine",
"server-auth",
"server-backend",
"server-brand",
"server-env",
"server-tools",
"server-ux",
"shift-planning",
"shopfloor-app",
"sign",
"social",
"spreadsheet",
"stock-logistics-availability",
"stock-logistics-barcode",
"stock-logistics-interfaces",
"stock-logistics-orderpoint",
"stock-logistics-putaway",
"stock-logistics-release-channel",
"stock-logistics-reporting",
"stock-logistics-request",
"stock-logistics-reservation",
"stock-logistics-shopfloor",
"stock-logistics-tracking",
"stock-logistics-transport",
"stock-logistics-warehouse",
"stock-logistics-workflow",
"stock-weighing",
"storage",
"survey",
"timesheet",
"vertical-abbey",
"vertical-agriculture",
"vertical-association",
"vertical-community",
"vertical-construction",
"vertical-cooperative-supermarket",
"vertical-edition",
"vertical-education",
"vertical-hotel",
"vertical-isp",
"vertical-medical",
"vertical-ngo",
"vertical-realestate",
"vertical-rental",
"vertical-travel",
"wallet",
"web",
"web-api",
"web-api-contrib",
"webhook",
"webkit-tools",
"website",
"website-cms",
"website-themes",
"wms",
# exceptions, new repositories
["oca-custom", ["18.0"]],
["tier-validation", ["19.0"]],
# tooling
["oca-ci", ["master"]],
["oca-github-bot", ["master"]],
["oca-port", ["main"]],
["odoo-module-migrator", ["master"]],
]
camptocamp = ["odoo-cloud-platform"]
forgeflow = ["stock-rma"]
32 changes: 30 additions & 2 deletions tests/test_pull_repos.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def test_get_tasks_generates_correct_list(mock_config, tmp_path):
{
"repo_name": "server-tools",
"repo_path": code_root / "oca" / "16.0" / "server-tools",
"repo_url": "git@github.com:OCA/server-tools.git",
"repo_url": "git@github.com:oca/server-tools.git",
"version": "16.0",
},
{
Expand All @@ -173,7 +173,7 @@ def test_get_tasks_generates_correct_list(mock_config, tmp_path):
{
"repo_name": "server-tools",
"repo_path": code_root / "oca" / "17.0" / "server-tools",
"repo_url": "git@github.com:OCA/server-tools.git",
"repo_url": "git@github.com:oca/server-tools.git",
"version": "17.0",
},
]
Expand Down Expand Up @@ -209,3 +209,31 @@ def test_get_tasks_with_filter(mock_config, tmp_path):
task["repo_path"] = str(task["repo_path"])

assert tasks == expected_tasks


def test_get_tasks_inline_branch_override(mock_config, tmp_path):
odoo_versions = ["17.0", "18.0"]
repos_config = {
"oca": [
"server-tools",
["oca-port", ["main"]],
["oca-custom", ["17.0", "18.0"]],
]
}
code_root = tmp_path / "code"

tasks = _get_tasks(odoo_versions, repos_config, code_root, None)

paths = {(t["repo_name"], t["version"]): t["repo_path"] for t in tasks}

# plain string → one task per configured version
assert (code_root / "oca" / "17.0" / "server-tools") == paths[("server-tools", "17.0")]
assert (code_root / "oca" / "18.0" / "server-tools") == paths[("server-tools", "18.0")]
# inline branch override → only the specified branches, no version loop
assert ("oca-port", "17.0") not in paths
assert ("oca-port", "18.0") not in paths
assert (code_root / "oca" / "main" / "oca-port") == paths[("oca-port", "main")]
# multiple explicit branches
assert (code_root / "oca" / "17.0" / "oca-custom") == paths[("oca-custom", "17.0")]
assert (code_root / "oca" / "18.0" / "oca-custom") == paths[("oca-custom", "18.0")]
assert len(tasks) == 5
28 changes: 21 additions & 7 deletions trobz_local/concurrency.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,31 @@ class TaskResult:

def run_tasks(tasks, max_workers: int = 4):
results = []
total = len(tasks)
completed_count = 0

with Progress(
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
) as progress:
overall = progress.add_task(f"[cyan]0/{total} done", total=total)

future_to_task = {}
with ThreadPoolExecutor(max_workers=max_workers) as executor:
try:
# submit tasks
# Submit tasks with hidden progress rows; reveal them on start
for task_info in tasks:
name = task_info["name"]
func = task_info["func"]
kwargs = task_info.get("args", {})
task_id = progress.add_task(
name,
total=100,
)
future = executor.submit(func, progress, task_id, **kwargs)
task_id = progress.add_task(name, total=100, visible=False)

def _run(f=func, tid=task_id, kw=kwargs):
progress.update(tid, visible=True)
return f(progress, tid, **kw)

future = executor.submit(_run)
future_to_task[future] = {"name": name, "task_id": task_id}

# Wait for all tasks to complete
Expand All @@ -42,7 +49,14 @@ def run_tasks(tasks, max_workers: int = 4):

try:
future.result()
progress.update(task_id, completed=100)
task = next(t for t in progress.tasks if t.id == task_id)
label = task.description or name
progress.update(task_id, completed=100, visible=False)
completed_count += 1
progress.update(
overall, completed=completed_count, description=f"[cyan]{completed_count}/{total} done"
)
progress.console.print(label)
results.append(TaskResult(name=name, success=True, message="Completed"))

except Exception as e:
Expand Down
52 changes: 34 additions & 18 deletions trobz_local/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,27 +193,43 @@ def pull_repos( # noqa: C901
typer.secho("\nAll repositories updated successfully.", fg=typer.colors.GREEN)


def _iter_org_entries(org_repos, odoo_versions):
"""Yield (repo_name, branch) pairs for an org's repo list.

Plain strings use all configured versions; [name, [branch, ...]] entries
use their explicit branch list.
"""
for entry in org_repos:
if isinstance(entry, str):
for version in odoo_versions:
yield entry, str(version)
else:
for branch in entry[1]:
yield entry[0], str(branch)


def _get_tasks(odoo_versions, repos_config, code_root, repo_filter):
tasks = []
for version in odoo_versions:
if "odoo" in repos_config:
for repo_name in repos_config["odoo"]:
if repo_name in ODOO_URLS and (not repo_filter or repo_name in repo_filter):
tasks.append({
"repo_name": repo_name,
"repo_path": code_root / "odoo" / repo_name / version,
"repo_url": ODOO_URLS[repo_name],
"version": str(version),
})
if "oca" in repos_config:
for repo_name in repos_config["oca"]:
if not repo_filter or repo_name in repo_filter:
tasks.append({
"repo_name": repo_name,
"repo_path": code_root / "oca" / str(version) / repo_name,
"repo_url": f"git@github.com:OCA/{repo_name}.git",
"version": str(version),
})
for repo_name in repos_config.get("odoo", []):
if repo_name in ODOO_URLS and (not repo_filter or repo_name in repo_filter):
tasks.append({
"repo_name": repo_name,
"repo_path": code_root / "odoo" / repo_name / version,
"repo_url": ODOO_URLS[repo_name],
"version": str(version),
})
for org, org_repos in repos_config.items():
if org == "odoo":
continue
for repo_name, branch in _iter_org_entries(org_repos, odoo_versions):
if not repo_filter or repo_name in repo_filter:
tasks.append({
"repo_name": repo_name,
"repo_path": code_root / org / branch / repo_name,
"repo_url": f"git@github.com:{org}/{repo_name}.git",
"version": branch,
})
return tasks


Expand Down
Loading