2024-08-25 15:54:24 -04:00
|
|
|
#!/usr/bin/env python3
|
2026-03-11 23:15:07 -04:00
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
2024-08-25 15:54:24 -04:00
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
2025-05-05 04:43:13 -04:00
|
|
|
# This script need only basic importation, it needs to be supported by python of your system
|
2024-08-25 15:54:24 -04:00
|
|
|
import argparse
|
|
|
|
|
import json
|
2024-12-01 23:47:17 -05:00
|
|
|
import logging
|
2024-08-25 15:54:24 -04:00
|
|
|
import os
|
|
|
|
|
import shutil
|
2024-12-01 23:47:17 -05:00
|
|
|
import subprocess
|
2024-08-25 15:54:24 -04:00
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
|
2026-03-30 14:46:56 -04:00
|
|
|
# erplibre_state is in the same directory; import lazily to avoid hard failure
|
|
|
|
|
# when the script runs outside the project root (e.g. during bare install).
|
|
|
|
|
try:
|
|
|
|
|
from script.version.erplibre_state import (
|
|
|
|
|
get_version_extra,
|
|
|
|
|
get_version_installed,
|
|
|
|
|
print_state,
|
|
|
|
|
set_version_installed,
|
|
|
|
|
set_version_switched,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_STATE_AVAILABLE = True
|
|
|
|
|
except ImportError:
|
|
|
|
|
_STATE_AVAILABLE = False
|
|
|
|
|
|
2024-08-25 15:54:24 -04:00
|
|
|
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
|
|
|
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
PROJECT_NAME = os.path.basename(os.getcwd())
|
|
|
|
|
VERSION_DATA_FILE = os.path.join("conf", "supported_version_erplibre.json")
|
2025-05-05 04:43:13 -04:00
|
|
|
VERSION_PYTHON_FILE = os.path.join(".python-odoo-version")
|
|
|
|
|
INSTALLED_ODOO_VERSION_FILE = ".repo/installed_odoo_version.txt"
|
2024-08-25 15:54:24 -04:00
|
|
|
VERSION_ERPLIBRE_FILE = os.path.join(".erplibre-version")
|
|
|
|
|
VERSION_ODOO_FILE = os.path.join(".odoo-version")
|
|
|
|
|
VERSION_POETRY_FILE = os.path.join(".poetry-version")
|
|
|
|
|
ADDONS_PATH = os.path.join("addons")
|
2026-03-29 23:19:48 -04:00
|
|
|
MOBILE_PATH = os.path.join("mobile", "erplibre_home_mobile")
|
2024-08-25 15:54:24 -04:00
|
|
|
VENV_TEMPLATE_FILE = ".venv.%s"
|
|
|
|
|
MANIFEST_FILE = "default.dev.xml"
|
|
|
|
|
MANIFEST_TEMPLATE_FILE = "default.dev.odoo%s.xml"
|
|
|
|
|
MANIFEST_FILE_PATH = os.path.join(".", "manifest", MANIFEST_FILE)
|
|
|
|
|
PYPROJECT_FILE = os.path.join("pyproject.toml")
|
|
|
|
|
PYPROJECT_TEMPLATE_FILE = "pyproject.%s.toml"
|
|
|
|
|
POETRY_LOCK_FILE = os.path.join("poetry.lock")
|
|
|
|
|
POETRY_LOCK_TEMPLATE_FILE = "poetry.%s.lock"
|
2025-07-14 03:44:48 -04:00
|
|
|
# PIP_REQUIREMENT_FILE = os.path.join("requirements.txt")
|
2024-08-25 15:54:24 -04:00
|
|
|
PIP_REQUIREMENT_TEMPLATE_FILE = "requirements.%s.txt"
|
|
|
|
|
PIP_IGNORE_REQUIREMENT_TEMPLATE_FILE = "ignore_requirements.%s.txt"
|
2025-08-07 06:28:34 -04:00
|
|
|
ADDONS_TEMPLATE_FILE = "odoo%s/addons"
|
2025-05-04 23:10:34 -04:00
|
|
|
ODOO_TEMPLATE_FILE = "odoo%s"
|
2024-08-25 15:54:24 -04:00
|
|
|
ERPLIBRE_TEMPLATE_VERSION = "odoo%s_python%s"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_config():
|
|
|
|
|
"""Parse command line arguments, extracting the config file name,
|
|
|
|
|
returning the union of config file and command line arguments
|
|
|
|
|
|
|
|
|
|
:return: dict of config file settings and command line arguments
|
|
|
|
|
"""
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
|
|
description=f"""\
|
|
|
|
|
Change environnement from supported version, check file {VERSION_DATA_FILE}
|
|
|
|
|
""",
|
|
|
|
|
epilog="""\
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-l",
|
|
|
|
|
"--list",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="List all supported version.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--python_version",
|
|
|
|
|
help="Select the python version.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--poetry_version",
|
|
|
|
|
help="Select the poetry version.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--odoo_version",
|
|
|
|
|
help="Select the odoo version.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--erplibre_version",
|
|
|
|
|
help="Select the erplibre version.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--erplibre_package",
|
|
|
|
|
help=(
|
|
|
|
|
"Select the erplibre package to configure environnement only for"
|
|
|
|
|
" this package."
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--install",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Install environnement.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--switch",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Will only update the system by switch environnement.",
|
|
|
|
|
)
|
2025-08-07 06:28:34 -04:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--switch_update",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Work with --switch, will do repo update",
|
|
|
|
|
)
|
2024-08-25 15:54:24 -04:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--install_dev",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Install developer environment.",
|
|
|
|
|
)
|
2024-12-03 23:34:54 -05:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--partial_install",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Preparation environment file, without installation. Docker need this",
|
|
|
|
|
)
|
2024-08-25 15:54:24 -04:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--force_install",
|
|
|
|
|
action="store_true",
|
2025-05-05 04:43:13 -04:00
|
|
|
help="Will erase .venv.odooVersion and create symbolic link after installation.",
|
2024-08-25 15:54:24 -04:00
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--force_repo",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Will erase all repo before install it.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--force",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Force all, include force_install, force_repo.",
|
|
|
|
|
)
|
2026-03-30 14:46:56 -04:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--with_extra",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help=(
|
|
|
|
|
"Install or switch with extra modules (e.g. CybroOdoo)."
|
|
|
|
|
" State is saved in .erplibre-state.json."
|
|
|
|
|
),
|
|
|
|
|
)
|
2024-08-25 15:54:24 -04:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
if args.force:
|
|
|
|
|
args.force_install = True
|
|
|
|
|
args.force_repo = True
|
|
|
|
|
args.is_in_installation = args.install or args.install_dev
|
|
|
|
|
args.is_in_switch = args.switch
|
2025-08-07 06:28:34 -04:00
|
|
|
args.is_in_switch_force_update = args.switch and args.switch_update
|
2024-08-25 15:54:24 -04:00
|
|
|
if not (args.install or args.install_dev) and args.force_install:
|
|
|
|
|
args.force_install = False
|
|
|
|
|
|
|
|
|
|
return args
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Update:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.execute_log = []
|
|
|
|
|
self.config = get_config()
|
|
|
|
|
self.data_version = None
|
|
|
|
|
self.odoo_version = None
|
|
|
|
|
self.python_version = None
|
2026-03-29 23:19:48 -04:00
|
|
|
self.mobile_active = False
|
2024-08-25 15:54:24 -04:00
|
|
|
self.detected_version_erplibre = None
|
|
|
|
|
self.new_version_erplibre = None
|
|
|
|
|
self.new_version_odoo = None
|
|
|
|
|
self.new_version_python = None
|
|
|
|
|
self.new_version_poetry = None
|
2025-05-04 23:10:34 -04:00
|
|
|
self.expected_odoo_name = None
|
|
|
|
|
self.expected_odoo_path = None
|
|
|
|
|
self.expected_addons_name = None
|
2024-08-25 15:54:24 -04:00
|
|
|
self.expected_venv_name = None
|
|
|
|
|
self.expected_manifest_name = None
|
|
|
|
|
self.expected_manifest_path = None
|
|
|
|
|
self.expected_pyproject_name = None
|
|
|
|
|
self.expected_pyproject_path = None
|
|
|
|
|
self.expected_poetry_lock_name = None
|
|
|
|
|
self.expected_poetry_lock_path = None
|
|
|
|
|
|
|
|
|
|
def check_version_data(self):
|
|
|
|
|
die(
|
|
|
|
|
not os.path.isfile(VERSION_DATA_FILE),
|
|
|
|
|
f"Missing {VERSION_DATA_FILE} path, are you sure you run this"
|
|
|
|
|
" script at root of the project?",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with open(VERSION_DATA_FILE) as txt:
|
|
|
|
|
self.data_version = json.load(txt)
|
|
|
|
|
|
|
|
|
|
if self.config.list:
|
|
|
|
|
for key, value in self.data_version.items():
|
|
|
|
|
print(f"{key}: {value}")
|
|
|
|
|
|
|
|
|
|
def detect_version(self):
|
|
|
|
|
# Detect actual version
|
|
|
|
|
if not os.path.exists(VERSION_PYTHON_FILE) or not os.path.exists(
|
|
|
|
|
VERSION_ODOO_FILE
|
|
|
|
|
):
|
|
|
|
|
_logger.info("New installation, cannot detect actual version.")
|
|
|
|
|
return False
|
|
|
|
|
with open(VERSION_PYTHON_FILE) as txt:
|
|
|
|
|
self.python_version = txt.read().strip()
|
|
|
|
|
with open(VERSION_ODOO_FILE) as txt:
|
|
|
|
|
self.odoo_version = txt.read().strip()
|
2025-05-05 04:43:13 -04:00
|
|
|
with open(VERSION_POETRY_FILE) as txt:
|
|
|
|
|
poetry_version = txt.read().strip()
|
2024-08-25 15:54:24 -04:00
|
|
|
|
2026-03-29 23:19:48 -04:00
|
|
|
# Detect mobile context
|
|
|
|
|
self.mobile_active = os.path.isdir(MOBILE_PATH)
|
|
|
|
|
|
2024-08-25 15:54:24 -04:00
|
|
|
# Show actual version
|
|
|
|
|
_logger.info(f"Python version: {self.python_version}")
|
|
|
|
|
_logger.info(f"Odoo version: {self.odoo_version}")
|
2025-05-05 04:43:13 -04:00
|
|
|
_logger.info(f"Poetry version: {poetry_version}")
|
2026-03-29 23:19:48 -04:00
|
|
|
_logger.info(
|
|
|
|
|
f"Mobile context: {'active (' + MOBILE_PATH + ')' if self.mobile_active else 'inactive'}"
|
|
|
|
|
)
|
2026-03-30 14:46:56 -04:00
|
|
|
if _STATE_AVAILABLE:
|
|
|
|
|
extra = get_version_extra(self.odoo_version)
|
|
|
|
|
_logger.info(
|
|
|
|
|
f"Extra modules (CybroOdoo): {'active' if extra else 'inactive'}"
|
|
|
|
|
)
|
2024-08-25 15:54:24 -04:00
|
|
|
erplibre_version_to_search = ERPLIBRE_TEMPLATE_VERSION % (
|
|
|
|
|
self.odoo_version,
|
|
|
|
|
self.python_version,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Detect key actual version
|
|
|
|
|
for key, value in self.data_version.items():
|
|
|
|
|
if (
|
|
|
|
|
value.get("odoo_version") == self.odoo_version
|
|
|
|
|
and value.get("python_version") == self.python_version
|
|
|
|
|
):
|
|
|
|
|
self.detected_version_erplibre = key
|
|
|
|
|
_logger.info(
|
|
|
|
|
f"Detected erplibre version {erplibre_version_to_search}"
|
|
|
|
|
)
|
|
|
|
|
break
|
|
|
|
|
if not self.detected_version_erplibre:
|
|
|
|
|
_logger.error(
|
|
|
|
|
f"The actual version '{erplibre_version_to_search}' is not"
|
|
|
|
|
f" configured into '{VERSION_DATA_FILE}'. Please update this"
|
|
|
|
|
" file before continue."
|
|
|
|
|
)
|
|
|
|
|
return False
|
|
|
|
|
|
2025-05-05 04:43:13 -04:00
|
|
|
if os.path.exists(INSTALLED_ODOO_VERSION_FILE):
|
|
|
|
|
with open(INSTALLED_ODOO_VERSION_FILE) as txt:
|
|
|
|
|
lst_version_installed = sorted(txt.read().splitlines())
|
|
|
|
|
str_installed_version = "Installed version: " + ", ".join(
|
|
|
|
|
lst_version_installed
|
|
|
|
|
)
|
|
|
|
|
_logger.info(str_installed_version)
|
2024-08-25 15:54:24 -04:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def validate_version(self):
|
|
|
|
|
dct_data_version = {}
|
|
|
|
|
if (
|
|
|
|
|
not self.config.python_version
|
|
|
|
|
and not self.config.poetry_version
|
|
|
|
|
and not self.config.odoo_version
|
|
|
|
|
and not self.config.erplibre_version
|
|
|
|
|
and not self.config.erplibre_package
|
|
|
|
|
):
|
|
|
|
|
dct_data_version = {}
|
|
|
|
|
if self.detected_version_erplibre:
|
|
|
|
|
dct_data_version = self.data_version.get(
|
|
|
|
|
self.detected_version_erplibre
|
|
|
|
|
)
|
|
|
|
|
if not dct_data_version:
|
|
|
|
|
# Take default version
|
|
|
|
|
default_data = [
|
|
|
|
|
key
|
|
|
|
|
for key, value in self.data_version.items()
|
|
|
|
|
if value.get("default")
|
|
|
|
|
]
|
|
|
|
|
if not default_data:
|
|
|
|
|
_logger.error(
|
|
|
|
|
"Cannot find default version into file"
|
|
|
|
|
f" {VERSION_DATA_FILE}"
|
|
|
|
|
)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
dct_data_version = self.data_version.get(default_data[0])
|
|
|
|
|
|
|
|
|
|
has_new_version = False
|
|
|
|
|
if self.config.erplibre_version:
|
|
|
|
|
data = self.data_version.get(self.config.erplibre_version)
|
|
|
|
|
if not data:
|
|
|
|
|
_logger.error(
|
|
|
|
|
"Missing data for erplibre_version"
|
|
|
|
|
f" {self.config.erplibre_version}"
|
|
|
|
|
)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
self.new_version_odoo = data.get("odoo_version")
|
|
|
|
|
self.new_version_python = data.get("python_version")
|
|
|
|
|
self.new_version_poetry = data.get("poetry_version")
|
|
|
|
|
has_new_version = True
|
|
|
|
|
if self.config.python_version:
|
|
|
|
|
self.new_version_python = self.config.python_version
|
|
|
|
|
has_new_version = True
|
|
|
|
|
if self.config.poetry_version:
|
|
|
|
|
self.new_version_poetry = self.config.poetry_version
|
|
|
|
|
has_new_version = True
|
|
|
|
|
if self.config.odoo_version:
|
|
|
|
|
self.new_version_odoo = self.config.odoo_version
|
|
|
|
|
has_new_version = True
|
|
|
|
|
if has_new_version:
|
|
|
|
|
self.new_version_erplibre = ERPLIBRE_TEMPLATE_VERSION % (
|
|
|
|
|
self.new_version_odoo,
|
|
|
|
|
self.new_version_python,
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
_logger.info(
|
|
|
|
|
"No difference between detected version and new version:"
|
|
|
|
|
f" {self.detected_version_erplibre}"
|
|
|
|
|
)
|
|
|
|
|
if not dct_data_version:
|
|
|
|
|
_logger.error("Cannot find dct_data_version")
|
|
|
|
|
else:
|
|
|
|
|
self.new_version_erplibre = ERPLIBRE_TEMPLATE_VERSION % (
|
|
|
|
|
dct_data_version.get("odoo_version"),
|
|
|
|
|
dct_data_version.get("python_version"),
|
|
|
|
|
)
|
|
|
|
|
self.new_version_odoo = dct_data_version.get("odoo_version")
|
|
|
|
|
self.new_version_python = dct_data_version.get(
|
|
|
|
|
"python_version"
|
|
|
|
|
)
|
|
|
|
|
self.new_version_poetry = dct_data_version.get(
|
|
|
|
|
"poetry_version"
|
|
|
|
|
)
|
|
|
|
|
# self.new_version_erplibre = self.detected_version_erplibre
|
|
|
|
|
# self.new_version_odoo = self.odoo_version
|
|
|
|
|
# self.new_version_python = self.python_version
|
|
|
|
|
# TODO needs to detect actual poetry? No?
|
|
|
|
|
|
|
|
|
|
self.expected_venv_name = (
|
|
|
|
|
VENV_TEMPLATE_FILE % self.new_version_erplibre
|
|
|
|
|
)
|
|
|
|
|
self.expected_manifest_name = (
|
|
|
|
|
MANIFEST_TEMPLATE_FILE % self.new_version_odoo
|
|
|
|
|
)
|
|
|
|
|
self.expected_manifest_path = os.path.join(
|
|
|
|
|
".", "manifest", self.expected_manifest_name
|
|
|
|
|
)
|
|
|
|
|
self.expected_pyproject_name = (
|
|
|
|
|
PYPROJECT_TEMPLATE_FILE % self.new_version_erplibre
|
|
|
|
|
)
|
|
|
|
|
self.expected_pyproject_path = os.path.join(
|
|
|
|
|
".", "requirement", self.expected_pyproject_name
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.expected_poetry_lock_name = (
|
|
|
|
|
POETRY_LOCK_TEMPLATE_FILE % self.new_version_erplibre
|
|
|
|
|
)
|
|
|
|
|
self.expected_poetry_lock_path = os.path.join(
|
|
|
|
|
".", "requirement", self.expected_poetry_lock_name
|
|
|
|
|
)
|
|
|
|
|
self.expected_pip_requirement_name = (
|
|
|
|
|
PIP_REQUIREMENT_TEMPLATE_FILE % self.new_version_erplibre
|
|
|
|
|
)
|
|
|
|
|
self.expected_addons_name = (
|
|
|
|
|
ADDONS_TEMPLATE_FILE % self.new_version_odoo
|
|
|
|
|
)
|
2025-05-04 23:10:34 -04:00
|
|
|
self.expected_odoo_name = ODOO_TEMPLATE_FILE % self.new_version_odoo
|
2025-05-05 04:43:13 -04:00
|
|
|
self.expected_odoo_path = os.path.join(
|
|
|
|
|
".", self.expected_odoo_name, "odoo", "."
|
|
|
|
|
)
|
2024-08-25 15:54:24 -04:00
|
|
|
self.expected_pip_requirement_path = os.path.join(
|
|
|
|
|
".", "requirement", self.expected_pip_requirement_name
|
|
|
|
|
)
|
|
|
|
|
self.expected_pip_ignore_requirement_name = (
|
|
|
|
|
PIP_IGNORE_REQUIREMENT_TEMPLATE_FILE % self.new_version_erplibre
|
|
|
|
|
)
|
|
|
|
|
self.expected_pip_ignore_requirement_path = os.path.join(
|
|
|
|
|
".", "requirement", self.expected_pip_ignore_requirement_name
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if self.config.erplibre_package:
|
|
|
|
|
_logger.warning("Not supported erplibre_package configuration")
|
|
|
|
|
|
|
|
|
|
def validate_environment(self):
|
|
|
|
|
status = True
|
2025-05-05 04:43:13 -04:00
|
|
|
venv_exist = os.path.exists(self.expected_venv_name)
|
2024-08-25 15:54:24 -04:00
|
|
|
if not venv_exist and not self.config.install_dev:
|
|
|
|
|
_logger.info("Relaunch this script with --install_dev argument.")
|
2025-05-04 23:10:34 -04:00
|
|
|
# Validate Odoo repo
|
2025-08-07 06:28:34 -04:00
|
|
|
# TODO need to remove this from architecture
|
2024-08-25 15:54:24 -04:00
|
|
|
# Validate Poetry and pip
|
|
|
|
|
status &= self.update_link_file(
|
|
|
|
|
"Poetry project toml",
|
|
|
|
|
PYPROJECT_FILE,
|
|
|
|
|
self.expected_pyproject_path,
|
|
|
|
|
do_delete_source=True,
|
|
|
|
|
)
|
|
|
|
|
status &= self.update_link_file(
|
|
|
|
|
"Poetry lock",
|
|
|
|
|
POETRY_LOCK_FILE,
|
|
|
|
|
self.expected_poetry_lock_path,
|
|
|
|
|
do_delete_source=True,
|
|
|
|
|
)
|
|
|
|
|
if not os.path.isdir(self.expected_addons_name):
|
|
|
|
|
status = False
|
|
|
|
|
return status
|
|
|
|
|
|
|
|
|
|
def update_environment(self):
|
|
|
|
|
status = True
|
|
|
|
|
if self.config.force_repo:
|
|
|
|
|
# TODO add script to check difference before erase all
|
|
|
|
|
os.system("./script/git/clean_repo_manifest.sh")
|
|
|
|
|
self.execute_log.append(
|
|
|
|
|
f"Clear all repo from manifest, everything is deleted"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Always overwrite version
|
2025-05-05 04:43:13 -04:00
|
|
|
_logger.info(
|
|
|
|
|
f"Update local file, "
|
|
|
|
|
f"python version '{self.new_version_python}', "
|
|
|
|
|
f"erplibre version '{self.new_version_erplibre}', "
|
|
|
|
|
f"odoo version '{self.new_version_odoo}', "
|
|
|
|
|
f"poetry version '{self.new_version_poetry}'"
|
|
|
|
|
)
|
2024-08-25 15:54:24 -04:00
|
|
|
with open(VERSION_PYTHON_FILE, "w") as txt:
|
|
|
|
|
txt.write(self.new_version_python)
|
|
|
|
|
with open(VERSION_ERPLIBRE_FILE, "w") as txt:
|
|
|
|
|
txt.write(self.new_version_erplibre)
|
|
|
|
|
with open(VERSION_ODOO_FILE, "w") as txt:
|
|
|
|
|
txt.write(self.new_version_odoo)
|
|
|
|
|
with open(VERSION_POETRY_FILE, "w") as txt:
|
|
|
|
|
txt.write(self.new_version_poetry)
|
|
|
|
|
|
|
|
|
|
if self.config.is_in_installation or self.config.is_in_switch:
|
|
|
|
|
# TODO need to be force if installation path is all good, return True
|
2024-11-23 23:32:11 -05:00
|
|
|
if self.config.install_dev:
|
2024-08-25 15:54:24 -04:00
|
|
|
_logger.info("Installation.")
|
2024-11-23 23:32:11 -05:00
|
|
|
status = self.install_erplibre()
|
2026-03-30 14:46:56 -04:00
|
|
|
if _STATE_AVAILABLE and not status:
|
|
|
|
|
set_version_installed(
|
|
|
|
|
self.new_version_odoo,
|
|
|
|
|
extra=bool(self.config.with_extra),
|
|
|
|
|
python=self.new_version_python,
|
|
|
|
|
poetry=self.new_version_poetry,
|
|
|
|
|
)
|
2025-08-07 06:28:34 -04:00
|
|
|
elif (
|
|
|
|
|
self.config.is_in_switch
|
|
|
|
|
and self.config.is_in_switch_force_update
|
|
|
|
|
):
|
2024-08-25 15:54:24 -04:00
|
|
|
_logger.info("Switch")
|
|
|
|
|
self.execute_log.append(f"System update")
|
2026-03-30 14:46:56 -04:00
|
|
|
# Auto-detect extra from state, warn if no state recorded
|
|
|
|
|
with_extra = bool(self.config.with_extra)
|
|
|
|
|
if _STATE_AVAILABLE and not with_extra:
|
|
|
|
|
if not get_version_installed(self.new_version_odoo):
|
|
|
|
|
_logger.warning(
|
|
|
|
|
f"No installation state found for odoo"
|
|
|
|
|
f" {self.new_version_odoo}. Proceeding without"
|
|
|
|
|
f" extra modules (CybroOdoo). To install with"
|
|
|
|
|
f" extra: make"
|
|
|
|
|
f" install_odoo_{self.new_version_odoo.replace('.', '')}_with_extra"
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
with_extra = get_version_extra(self.new_version_odoo)
|
|
|
|
|
manifest_script = "./script/manifest/update_manifest_local_dev.sh"
|
|
|
|
|
if with_extra:
|
|
|
|
|
manifest_script += " --with_extra"
|
|
|
|
|
status = os.system(manifest_script)
|
|
|
|
|
if _STATE_AVAILABLE and not status:
|
|
|
|
|
set_version_switched(self.new_version_odoo)
|
2024-08-25 15:54:24 -04:00
|
|
|
|
|
|
|
|
# To support multiple addons directory, remove TEMP
|
2025-05-05 04:43:13 -04:00
|
|
|
# for addons_path in os.listdir("."):
|
|
|
|
|
# if (
|
|
|
|
|
# addons_path.startswith("addons")
|
|
|
|
|
# and addons_path != addons_path_with_version
|
|
|
|
|
# and addons_path.endswith("TEMP")
|
|
|
|
|
# ):
|
|
|
|
|
# os.rename(addons_path, addons_path[:-4])
|
2024-08-25 15:54:24 -04:00
|
|
|
for addons_path in os.listdir("."):
|
|
|
|
|
if addons_path.startswith("addons"):
|
|
|
|
|
# In same time, force to create addons if not existing
|
|
|
|
|
addons_dir_path = os.path.join(addons_path, "addons")
|
|
|
|
|
if not os.path.isdir(addons_dir_path):
|
|
|
|
|
os.makedirs(addons_dir_path)
|
|
|
|
|
|
|
|
|
|
# Force create addons link
|
2025-05-05 04:43:13 -04:00
|
|
|
# if os.path.isdir(ADDONS_PATH):
|
|
|
|
|
# if os.path.islink(ADDONS_PATH):
|
|
|
|
|
# os.remove(ADDONS_PATH)
|
|
|
|
|
# else:
|
|
|
|
|
# os.rename(
|
|
|
|
|
# ADDONS_PATH,
|
|
|
|
|
# ADDONS_PATH
|
|
|
|
|
# + "_"
|
|
|
|
|
# + time.strftime("%Yy%mm%dd-%Hh%Mm%Ss"),
|
|
|
|
|
# )
|
|
|
|
|
# os.symlink(addons_path_with_version, ADDONS_PATH)
|
2024-08-25 15:54:24 -04:00
|
|
|
return status
|
|
|
|
|
|
|
|
|
|
def print_log(self):
|
|
|
|
|
if not self.execute_log:
|
|
|
|
|
_logger.info("Nothing to do")
|
|
|
|
|
return
|
|
|
|
|
_logger.info("List of execution log :")
|
|
|
|
|
for log_info in self.execute_log:
|
|
|
|
|
_logger.info("\t" + log_info)
|
|
|
|
|
|
|
|
|
|
def pycharm_update(self):
|
|
|
|
|
pycharm_is_installed = os.path.exists(".idea")
|
|
|
|
|
if not pycharm_is_installed or not self.execute_log:
|
|
|
|
|
return
|
2024-11-24 22:15:19 -05:00
|
|
|
os.system(
|
2025-05-05 04:43:13 -04:00
|
|
|
"./.venv.erplibre/bin/python ./script/ide/pycharm_configuration.py --init"
|
2024-11-24 22:15:19 -05:00
|
|
|
)
|
2024-08-25 15:54:24 -04:00
|
|
|
|
2024-11-23 23:32:11 -05:00
|
|
|
def install_erplibre(self):
|
|
|
|
|
self.execute_log.append(f"Dev installation")
|
|
|
|
|
status = os.system("./script/install/install_locally_dev.sh")
|
|
|
|
|
return status
|
2024-08-25 15:54:24 -04:00
|
|
|
|
2024-11-23 23:32:11 -05:00
|
|
|
def install_system(self):
|
|
|
|
|
self.execute_log.append(f"System installation")
|
|
|
|
|
status = os.system("./script/install/install_dev.sh")
|
2024-08-25 15:54:24 -04:00
|
|
|
return status
|
|
|
|
|
|
|
|
|
|
def update_link_file(
|
|
|
|
|
self,
|
|
|
|
|
component_name,
|
|
|
|
|
source_file,
|
|
|
|
|
target_file,
|
|
|
|
|
is_directory=False,
|
|
|
|
|
do_delete_source=False,
|
|
|
|
|
):
|
|
|
|
|
"""Call to create a symbolic link
|
|
|
|
|
0. check if file is symlink or file/directory
|
|
|
|
|
1.
|
|
|
|
|
Case 1 : origin file exist, need to be switched by a symlink and rename it;
|
|
|
|
|
Case 2 : no file, check if new version exist and symlink it;
|
|
|
|
|
Case 3 : source file is symlink, and wrong direction, erase it. If new version exist, symlink it.
|
|
|
|
|
Case 4: origin file exist, but it's a wrong symlink
|
|
|
|
|
Good case : source file is symlink, and good redirection.
|
|
|
|
|
"""
|
|
|
|
|
# TODO support case symlink is invalid
|
|
|
|
|
status = False
|
|
|
|
|
if not source_file or not target_file:
|
|
|
|
|
_logger.error(
|
|
|
|
|
f"{component_name}:Source file or target file is empty."
|
|
|
|
|
)
|
|
|
|
|
source_file_exist = os.path.exists(source_file)
|
|
|
|
|
source_file_is_symlink = os.path.islink(source_file)
|
|
|
|
|
target_file_exist = os.path.exists(target_file)
|
|
|
|
|
if not target_file_exist:
|
2025-05-05 04:43:13 -04:00
|
|
|
_logger.error(f"'{target_file}' not exist.")
|
2024-08-25 15:54:24 -04:00
|
|
|
do_symlink = False
|
|
|
|
|
# Case 4
|
|
|
|
|
if source_file_is_symlink and not source_file_exist:
|
|
|
|
|
_logger.error(
|
|
|
|
|
f"{component_name}:File '{source_file}' is a wrong symlink,"
|
|
|
|
|
" delete it."
|
|
|
|
|
)
|
|
|
|
|
os.system(f"rm {source_file}")
|
|
|
|
|
self.execute_log.append(
|
|
|
|
|
f"{component_name}:Delete file {source_file}"
|
|
|
|
|
)
|
|
|
|
|
source_file_is_symlink = False
|
|
|
|
|
|
|
|
|
|
# Case 1
|
|
|
|
|
if source_file_exist:
|
|
|
|
|
if not source_file_is_symlink:
|
|
|
|
|
# Check if source type
|
|
|
|
|
if is_directory:
|
|
|
|
|
source_file_is_directory = os.path.isdir(source_file)
|
|
|
|
|
if not source_file_is_directory:
|
|
|
|
|
_logger.error(
|
|
|
|
|
f"{component_name} - Source '{source_file}' is"
|
|
|
|
|
" expected to be a directory and it's not."
|
|
|
|
|
)
|
|
|
|
|
os.system(f"ls -lha {source_file}")
|
|
|
|
|
sys.exit(1)
|
2025-05-05 04:43:13 -04:00
|
|
|
# else:
|
|
|
|
|
# do_switch_origin_sim = True
|
2024-08-25 15:54:24 -04:00
|
|
|
else:
|
|
|
|
|
source_file_is_file = os.path.isfile(source_file)
|
|
|
|
|
if not source_file_is_file:
|
|
|
|
|
_logger.error(
|
|
|
|
|
f"{component_name} - Source '{source_file}' is"
|
|
|
|
|
" expected to be a file and it's not."
|
|
|
|
|
)
|
|
|
|
|
os.system(f"ls -lha {source_file}")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
else:
|
|
|
|
|
# Case 3
|
|
|
|
|
# Is symlink
|
|
|
|
|
lst_path_source = [
|
|
|
|
|
a for a in os.path.split(remove_dot_path(source_file)) if a
|
|
|
|
|
]
|
|
|
|
|
ref_symlink_source = os.readlink(source_file).strip("/")
|
|
|
|
|
lst_path_symlink_source = [
|
|
|
|
|
a
|
|
|
|
|
for a in os.path.split(remove_dot_path(ref_symlink_source))
|
|
|
|
|
if a
|
|
|
|
|
]
|
|
|
|
|
if (
|
|
|
|
|
len(lst_path_source) != len(lst_path_symlink_source)
|
|
|
|
|
and len(lst_path_symlink_source) == 1
|
|
|
|
|
):
|
|
|
|
|
ref_symlink_source_from_root = os.path.join(
|
|
|
|
|
".",
|
|
|
|
|
os.sep.join(lst_path_source[:-1]),
|
|
|
|
|
ref_symlink_source,
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
ref_symlink_source_from_root = ref_symlink_source
|
|
|
|
|
do_delete_source = do_delete_source or os.path.exists(
|
|
|
|
|
target_file
|
|
|
|
|
)
|
2025-05-04 23:10:34 -04:00
|
|
|
if os.path.normpath(
|
|
|
|
|
ref_symlink_source_from_root
|
|
|
|
|
) == os.path.normpath(target_file):
|
2024-08-25 15:54:24 -04:00
|
|
|
_logger.info(
|
|
|
|
|
f"{component_name}:The system configuration is good."
|
|
|
|
|
)
|
|
|
|
|
status = True
|
|
|
|
|
elif do_delete_source:
|
|
|
|
|
_logger.warning(
|
|
|
|
|
f"{component_name}:The file '{source_file}' link"
|
|
|
|
|
f" '{ref_symlink_source_from_root}' will be delete and"
|
|
|
|
|
f" link to '{target_file}'."
|
|
|
|
|
)
|
|
|
|
|
# symlink, delete it if wrong
|
|
|
|
|
os.system(f"rm {source_file}")
|
|
|
|
|
self.execute_log.append(
|
|
|
|
|
f"{component_name}:Delete file {source_file}"
|
|
|
|
|
)
|
|
|
|
|
if target_file_exist:
|
|
|
|
|
do_symlink = True
|
|
|
|
|
else:
|
|
|
|
|
_logger.warning(
|
|
|
|
|
f"{component_name}:The actual link is"
|
|
|
|
|
f" '{ref_symlink_source_from_root}', but expect to be"
|
|
|
|
|
f" '{target_file}'."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
elif target_file_exist:
|
|
|
|
|
# Case 2
|
|
|
|
|
do_symlink = True
|
|
|
|
|
if do_symlink:
|
|
|
|
|
lst_path_target = [
|
|
|
|
|
a for a in os.path.split(remove_dot_path(target_file)) if a
|
|
|
|
|
]
|
|
|
|
|
lst_path_source = [
|
|
|
|
|
a for a in os.path.split(remove_dot_path(source_file)) if a
|
|
|
|
|
]
|
|
|
|
|
if len(lst_path_source) == 1:
|
|
|
|
|
os.symlink(target_file, source_file)
|
|
|
|
|
msg = (
|
|
|
|
|
f"{component_name}:Create symbolic link {source_file} to"
|
|
|
|
|
f" {target_file}"
|
|
|
|
|
)
|
|
|
|
|
_logger.info(msg)
|
|
|
|
|
self.execute_log.append(msg)
|
|
|
|
|
else:
|
|
|
|
|
target_file_name = lst_path_target[-1]
|
|
|
|
|
source_file_name = lst_path_source[-1]
|
|
|
|
|
# TODO compare lst_path_source avec lst_path_target, si différent, faire le path
|
|
|
|
|
cd_path_target = (
|
|
|
|
|
"." + os.sep + os.sep.join(lst_path_target[:-1])
|
|
|
|
|
)
|
|
|
|
|
cmd_symlink_manifest = (
|
|
|
|
|
f"cd {cd_path_target};ln -s"
|
|
|
|
|
f" {target_file_name} {source_file_name};cd -"
|
|
|
|
|
)
|
|
|
|
|
os.system(cmd_symlink_manifest)
|
|
|
|
|
msg = (
|
|
|
|
|
f"{component_name}:Create symbolic link"
|
|
|
|
|
f" {source_file_name} to {target_file_name} from path"
|
|
|
|
|
f" {cd_path_target}"
|
|
|
|
|
)
|
|
|
|
|
_logger.info(msg)
|
|
|
|
|
self.execute_log.append(msg)
|
|
|
|
|
return status
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def remove_dot_path(path):
|
|
|
|
|
"""
|
|
|
|
|
if path is ./path/2, will return path/2
|
|
|
|
|
"""
|
|
|
|
|
if path.startswith("./"):
|
|
|
|
|
return path[2:]
|
|
|
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
update = Update()
|
|
|
|
|
_logger.info(f"Work on directory {os.getcwd()}")
|
|
|
|
|
_logger.info("Get data version")
|
|
|
|
|
update.check_version_data()
|
|
|
|
|
|
|
|
|
|
_logger.info("Detect version")
|
|
|
|
|
update.detect_version()
|
|
|
|
|
|
|
|
|
|
_logger.info("Validate version")
|
|
|
|
|
update.validate_version()
|
|
|
|
|
|
|
|
|
|
_logger.info("Validate environment")
|
2024-11-23 23:32:11 -05:00
|
|
|
status = 0
|
2025-04-13 12:58:46 -04:00
|
|
|
if (
|
|
|
|
|
update.config.install_dev
|
|
|
|
|
or update.config.partial_install
|
|
|
|
|
or update.config.is_in_switch
|
|
|
|
|
):
|
2024-11-23 23:32:11 -05:00
|
|
|
status = update.validate_environment()
|
|
|
|
|
if update.config.install:
|
|
|
|
|
status = update.install_system()
|
|
|
|
|
if (
|
|
|
|
|
update.config.force_install
|
|
|
|
|
or update.config.install_dev
|
2024-12-03 23:34:54 -05:00
|
|
|
or update.config.partial_install
|
2024-11-23 23:32:11 -05:00
|
|
|
or update.config.is_in_switch
|
|
|
|
|
) and not status:
|
2024-08-25 15:54:24 -04:00
|
|
|
update.update_environment()
|
|
|
|
|
update.print_log()
|
|
|
|
|
|
2024-11-23 23:32:11 -05:00
|
|
|
if update.config.install_dev:
|
2024-08-25 15:54:24 -04:00
|
|
|
# Update pycharm configuration
|
|
|
|
|
update.pycharm_update()
|
|
|
|
|
|
|
|
|
|
# Update OCB configuration
|
2025-05-05 04:43:13 -04:00
|
|
|
os.system(
|
|
|
|
|
"./.venv.erplibre/bin/python ./script/git/git_repo_update_group.py"
|
|
|
|
|
)
|
2024-08-25 15:54:24 -04:00
|
|
|
os.system("./script/generate_config.sh")
|
|
|
|
|
# TODO ignore this if installation fail
|
|
|
|
|
|
2025-05-05 04:43:13 -04:00
|
|
|
# TODO this cause an error at first execution, need to source ./.venv.erplibre/bin/activate and rerun
|
|
|
|
|
# subprocess.run(['source', './.venv.erplibre/bin/activate'], shell=True)
|
2024-08-25 15:54:24 -04:00
|
|
|
# subprocess.run(['make', 'config_gen_all'])
|
|
|
|
|
# status = os.system(f"make config_gen_all")
|
|
|
|
|
#
|
|
|
|
|
# if not status:
|
|
|
|
|
# print("Please run:")
|
2025-05-05 04:43:13 -04:00
|
|
|
# print("source ./.venv.erplibre/bin/activate")
|
2024-08-25 15:54:24 -04:00
|
|
|
# print("make config_gen_all")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def die(cond, message, code=1):
|
|
|
|
|
if cond:
|
|
|
|
|
print(message, file=sys.stderr)
|
|
|
|
|
sys.exit(code)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|