diff --git a/conf/make.docker.Makefile b/conf/make.docker.Makefile index 5effd0d..d772876 100644 --- a/conf/make.docker.Makefile +++ b/conf/make.docker.Makefile @@ -28,6 +28,10 @@ docker_show_databases: docker_show_logs_live: docker compose logs -f +.PHONY: docker_show_absolute_path +docker_show_absolute_path: + docker inspect $(docker ps -q) --format '{{.Name}}{{range .Mounts}} | {{.Source}} → {{.Destination}}{{end}}' + .PHONY: docker_show_process docker_show_process: docker compose ps diff --git a/conf/make.installation.Makefile b/conf/make.installation.Makefile index ca3e4a4..7365faf 100644 --- a/conf/make.installation.Makefile +++ b/conf/make.installation.Makefile @@ -113,6 +113,38 @@ switch_odoo_12_update: ./script/version/update_env_version.py --erplibre_version odoo12.0_python3.7.17 --switch --switch_update ./script/make.sh config_gen_all +#################### +# INSTALL WITH EXTRA (CybroOdoo) +#################### + +.PHONY: install_odoo_18_with_extra +install_odoo_18_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo18.0_python3.12.10 --install_dev --with_extra + +.PHONY: install_odoo_17_with_extra +install_odoo_17_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo17.0_python3.10.18 --install_dev --with_extra + +.PHONY: install_odoo_16_with_extra +install_odoo_16_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo16.0_python3.10.18 --install_dev --with_extra + +.PHONY: install_odoo_15_with_extra +install_odoo_15_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo15.0_python3.8.20 --install_dev --with_extra + +.PHONY: install_odoo_14_with_extra +install_odoo_14_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo14.0_python3.8.20 --install_dev --with_extra + +.PHONY: install_odoo_13_with_extra +install_odoo_13_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo13.0_python3.7.17 --install_dev --with_extra + +.PHONY: install_odoo_12_with_extra +install_odoo_12_with_extra: + ./script/version/update_env_version.py --erplibre_version odoo12.0_python3.7.17 --install_dev --with_extra + .PHONY: install_odoo_all_version install_odoo_all_version: ./script/make.sh install_odoo_18 diff --git a/manifest/git_manifest_erplibre_odoo_dev.xml b/manifest/git_manifest_erplibre_odoo_dev.xml index ac648c3..bfdb291 100644 --- a/manifest/git_manifest_erplibre_odoo_dev.xml +++ b/manifest/git_manifest_erplibre_odoo_dev.xml @@ -1,5 +1,9 @@ + + + + + + + + + diff --git a/manifest/git_manifest_extra_odoo13.0.xml b/manifest/git_manifest_extra_odoo13.0.xml new file mode 100644 index 0000000..c7a74a4 --- /dev/null +++ b/manifest/git_manifest_extra_odoo13.0.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/manifest/git_manifest_extra_odoo14.0.xml b/manifest/git_manifest_extra_odoo14.0.xml new file mode 100644 index 0000000..94cd0fb --- /dev/null +++ b/manifest/git_manifest_extra_odoo14.0.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/manifest/git_manifest_extra_odoo15.0.xml b/manifest/git_manifest_extra_odoo15.0.xml new file mode 100644 index 0000000..c096a17 --- /dev/null +++ b/manifest/git_manifest_extra_odoo15.0.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/manifest/git_manifest_extra_odoo16.0.xml b/manifest/git_manifest_extra_odoo16.0.xml new file mode 100644 index 0000000..6be7a49 --- /dev/null +++ b/manifest/git_manifest_extra_odoo16.0.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/manifest/git_manifest_extra_odoo17.0.xml b/manifest/git_manifest_extra_odoo17.0.xml new file mode 100644 index 0000000..d18e6fb --- /dev/null +++ b/manifest/git_manifest_extra_odoo17.0.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/manifest/git_manifest_extra_odoo18.0.xml b/manifest/git_manifest_extra_odoo18.0.xml new file mode 100644 index 0000000..a69a128 --- /dev/null +++ b/manifest/git_manifest_extra_odoo18.0.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/manifest/git_manifest_mobile_home.xml b/manifest/git_manifest_mobile_home.xml index 549bbf3..54d42ec 100644 --- a/manifest/git_manifest_mobile_home.xml +++ b/manifest/git_manifest_mobile_home.xml @@ -1,6 +1,8 @@ + + + + + + + + diff --git a/manifest/git_manifest_odoo12.0.xml b/manifest/git_manifest_odoo12.0.xml index 4af28fa..08c03df 100644 --- a/manifest/git_manifest_odoo12.0.xml +++ b/manifest/git_manifest_odoo12.0.xml @@ -26,13 +26,6 @@ - - - - - - - - - - - - diff --git a/requirement/erplibre_require-ments.txt b/requirement/erplibre_require-ments.txt index c385ec8..16203b1 100644 --- a/requirement/erplibre_require-ments.txt +++ b/requirement/erplibre_require-ments.txt @@ -19,6 +19,7 @@ isort pykeepass click aioshutil +python-magic openai humanize requests diff --git a/script/generate_config.sh b/script/generate_config.sh index 834100f..574a857 100755 --- a/script/generate_config.sh +++ b/script/generate_config.sh @@ -27,8 +27,6 @@ printf "longpolling_port = ${EL_LONGPOLLING_PORT}\n" >> "${EL_CONFIG_FILE}" printf "addons_path = ${EL_HOME_ODOO}/addons,${EL_HOME_ODOO}/odoo/addons,${EL_HOME}/odoo${EL_ODOO_VERSION}/addons/addons," >> "${EL_CONFIG_FILE}" if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then - printf "${EL_HOME_ODOO_PROJECT}/addons/CybroOdoo_CybroAddons," >> "${EL_CONFIG_FILE}" - printf "${EL_HOME_ODOO_PROJECT}/addons/CybroOdoo_OpenHRMS," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/ERPLibre_erplibre_addons," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/MathBenTech_development," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_account-analytic," >> "${EL_CONFIG_FILE}" @@ -155,7 +153,6 @@ if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then printf "${EL_HOME_ODOO_PROJECT}/addons/dhongu_deltatech," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/muk-it_odoo-modules," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/novacode-nl_odoo-formio," >> "${EL_CONFIG_FILE}" - printf "${EL_HOME_ODOO_PROJECT}/addons/odoo_design-themes," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/odoomates_odooapps," >> "${EL_CONFIG_FILE}" printf "${EL_HOME_ODOO_PROJECT}/addons/openeducat_openeducat_erp," >> "${EL_CONFIG_FILE}" fi diff --git a/script/git/git_merge_repo_manifest.py b/script/git/git_merge_repo_manifest.py index 7e1a115..890126c 100755 --- a/script/git/git_merge_repo_manifest.py +++ b/script/git/git_merge_repo_manifest.py @@ -15,6 +15,7 @@ new_path = os.path.normpath( sys.path.append(new_path) from script.git.git_tool import GitTool +from script.version.erplibre_state import get_version_extra _logger = logging.getLogger(__name__) @@ -30,6 +31,8 @@ DEFAULT_PATH_MANIFEST_PRIVATE_CONF = os.path.join( DEFAULT_PATH_INSTALLED_ODOO_VERSION = os.path.join( ".repo", "installed_odoo_version.txt" ) +MOBILE_PATH = os.path.join("mobile", "erplibre_home_mobile") +ODOO_VERSION_PATH = ".odoo-version" def get_config(): @@ -67,6 +70,19 @@ def get_config(): action="store_true", help="Add mobile project manifest", ) + parser.add_argument( + "--with_extra", + action="store_true", + help=( + "Add extra modules manifest for current Odoo version" + " (e.g. CybroOdoo). Version read from .odoo-version." + ), + ) + parser.add_argument( + "--with_new_manifest", + action="store_true", + help="Will overwrite local manifest", + ) args = parser.parse_args() return args @@ -75,6 +91,20 @@ def main(): config = get_config() git_tool = GitTool() + odoo_version = None + if os.path.isfile(ODOO_VERSION_PATH): + with open(ODOO_VERSION_PATH, "r") as f: + odoo_version = f.readline() + + # Add local manifest + if not config.with_new_manifest: + if odoo_version: + config.with_OCA = True + if os.path.isdir(MOBILE_PATH): + config.with_mobile = True + if odoo_version and get_version_extra(odoo_version.strip()): + config.with_extra = True + input_paths = config.input if not input_paths: input_paths = [] @@ -83,8 +113,6 @@ def main(): input_paths, DEFAULT_PATH_MANIFEST_ODOO_CONF ) if os.path.exists(".odoo-version"): - with open(".odoo-version", "r") as f: - odoo_version = f.readline() if odoo_version: path_manifest_odoo_version = os.path.join( "manifest", f"git_manifest_odoo{odoo_version}.xml" @@ -123,11 +151,20 @@ def main(): if os.path.exists(path_manifest_odoo_version): input_paths.append(path_manifest_odoo_version) - elif config.with_mobile: + if config.with_mobile: append_file_path_manifest( input_paths, DEFAULT_PATH_MANIFEST_MOBILE_CONF ) - else: + if config.with_extra and odoo_version: + path_extra = os.path.join( + "manifest", + f"git_manifest_extra_odoo{odoo_version.strip()}.xml", + ) + if os.path.exists(path_extra): + input_paths.append(path_extra) + else: + print(f"WARNING: {path_extra} does not exist, skipping extra") + if not config.with_mobile or not config.with_OCA: append_file_path_manifest(input_paths, DEFAULT_PATH_MANIFEST_CONF) append_file_path_manifest( input_paths, DEFAULT_PATH_MANIFEST_PRIVATE_CONF diff --git a/script/install/install_debian_dependency.sh b/script/install/install_debian_dependency.sh index 8df7730..adbc2d5 100755 --- a/script/install/install_debian_dependency.sh +++ b/script/install/install_debian_dependency.sh @@ -127,7 +127,8 @@ if [ "18.04" == "${UBUNTU_VERSION}" ]; then sudo apt remove nodeJS npm NODE_MAJOR=16 else - NODE_MAJOR=20 + # Node 22+ required by @capacitor/cli v8.x (mobile app dependency) + NODE_MAJOR=22 fi echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list diff --git a/script/install/install_locally.sh b/script/install/install_locally.sh index d709e3d..8148e50 100755 --- a/script/install/install_locally.sh +++ b/script/install/install_locally.sh @@ -1,5 +1,3 @@ -#!/usr/bin/env bash - . ./env_var.sh EL_USER=${USER} EL_HOME=$PWD @@ -17,14 +15,6 @@ FILE_INSTALLATION_VERSION=".repo/installed_odoo_version.txt" Red='\033[0;31m' # Red Color_Off='\033[0m' # Text Reset -./script/generate_config.sh - -# Generate empty addons if missing -path_addons_addons="./odoo${EL_ODOO_VERSION}/addons/addons" -if [[ ! -d "${path_addons_addons}" ]]; then - mkdir -p "${path_addons_addons}" -fi - # example, 3.7.8 will be 3.7 into PYTHON_VERSION_MAJOR PYTHON_VERSION_MAJOR=$(echo "$EL_PYTHON_ODOO_VERSION" | sed 's/\.[^\.]*$//') VENV_ERPLIBRE_PATH=$(cat "conf/python-erplibre-venv" | xargs) @@ -32,71 +22,92 @@ VENV_ODOO_PATH=".venv.${EL_ERPLIBRE_VERSION}" POETRY_ODOO_PATH=${VENV_ERPLIBRE_PATH}/bin/poetry export WITH_POETRY_INSTALLATION=1 -if [[ ! -n "${DOCKER_BUILD}" ]]; then - # Install ERPLibre venv - echo -e "Install ${VENV_ERPLIBRE_PATH} with ${EL_PYTHON_ERPLIBRE_VERSION}" - ./script/install/install_venv.sh "ERPLibre" "${VENV_ERPLIBRE_PATH}" "${EL_PYTHON_ERPLIBRE_VERSION}" - # Install Odoo venv - echo -e "Install ${VENV_ODOO_PATH} with ${EL_PYTHON_ODOO_VERSION}" - ./script/install/install_venv.sh "Odoo" "${VENV_ODOO_PATH}" "${EL_PYTHON_ODOO_VERSION}" -else - mkdir .venv -fi +# EL_PHASE controls which steps to execute. Used by install_locally_dev.sh +# for parallel installation — do not set manually unless you know what you do. +# all (default) – full install: setup + poetry phases +# setup – prereqs only: venvs, pip-erplibre, git-repo +# poetry – python packages: poetry install + post-install +EL_PHASE=${EL_PHASE:-all} -source ./${VENV_ERPLIBRE_PATH}/bin/activate -echo -e "Upgrade pip to ${VENV_ERPLIBRE_PATH}" -pip install --upgrade pip -pip install -r requirement/erplibre_require-ments.txt +# ── Setup phase (venvs + pip-erplibre + git-repo) ──────────────────────────── +if [[ "${EL_PHASE}" != "poetry" ]]; then + ./script/generate_config.sh -./script/install/install_git_repo.sh - -source ${VENV_ODOO_PATH}/bin/activate -echo -e "Upgrade pip to ${VENV_ODOO_PATH}" -pip install --upgrade pip - -echo -e "\n---- Installing poetry dependency ----" - -if [[ -z "${EL_POETRY_VERSION}" ]]; then - echo -e "${Red}Error${Color_Off} missing poetry version, please check file .poetry-version" - cat .poetry-version - ls -la - exit 1 -fi - -# Delete artifacts created by pip, cause error in next "poetry install" -if [[ ! -f "${POETRY_ODOO_PATH}" ]]; then - echo -e "Install Poetry ${POETRY_ODOO_PATH}" - pip install poetry==${EL_POETRY_VERSION} - poetry --version - # Fix broken poetry by installing ignored dependence - # poetry lock --no-update - # To fix keyring problem when installation is blocked, use - export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring - if [[ ${WITH_POETRY_INSTALLATION} -ne 0 ]]; then - poetry install --no-root -vvv + # Generate empty addons if missing + path_addons_addons="./odoo${EL_ODOO_VERSION}/addons/addons" + if [[ ! -d "${path_addons_addons}" ]]; then + mkdir -p "${path_addons_addons}" fi - retVal=$? - if [[ $retVal -ne 0 ]]; then - echo "Poetry installation error with status ${retVal}" + + if [[ ! -n "${DOCKER_BUILD}" ]]; then + # Install ERPLibre venv + echo -e "Install ${VENV_ERPLIBRE_PATH} with ${EL_PYTHON_ERPLIBRE_VERSION}" + ./script/install/install_venv.sh "ERPLibre" "${VENV_ERPLIBRE_PATH}" "${EL_PYTHON_ERPLIBRE_VERSION}" + # Install Odoo venv + echo -e "Install ${VENV_ODOO_PATH} with ${EL_PYTHON_ODOO_VERSION}" + ./script/install/install_venv.sh "Odoo" "${VENV_ODOO_PATH}" "${EL_PYTHON_ODOO_VERSION}" + else + mkdir .venv + fi + + source ./${VENV_ERPLIBRE_PATH}/bin/activate + echo -e "Upgrade pip to ${VENV_ERPLIBRE_PATH}" + pip install --upgrade pip + pip install -r requirement/erplibre_require-ments.txt + + ./script/install/install_git_repo.sh +fi + +# ── Poetry phase (install python packages + post-install) ──────────────────── +if [[ "${EL_PHASE}" != "setup" ]]; then + source ${VENV_ODOO_PATH}/bin/activate + echo -e "Upgrade pip to ${VENV_ODOO_PATH}" + pip install --upgrade pip + + echo -e "\n---- Installing poetry dependency ----" + + if [[ -z "${EL_POETRY_VERSION}" ]]; then + echo -e "${Red}Error${Color_Off} missing poetry version, please check file .poetry-version" + cat .poetry-version + ls -la exit 1 fi -fi - -# Delete artifacts created by pip, cause error in next "poetry install" -rm -rf artifacts - -# Link for dev tools into Odoo -echo -e "\n---- Add link dependency in site-packages of Python ----" -# TODO this link can break, the symbolic link is maybe not created -ln -fs "${EL_HOME_ODOO}/odoo" "${EL_HOME}/${VENV_ODOO_PATH}/lib/python${PYTHON_VERSION_MAJOR}/site-packages/" - -# Force to return to erplibre source -source ./${VENV_ERPLIBRE_PATH}/bin/activate - -# Add trace of installation -LINE_TO_ADD="odoo${EL_ODOO_VERSION}" -mkdir -p "$(dirname "$FILE_INSTALLATION_VERSION")" -touch "$FILE_INSTALLATION_VERSION" -if ! grep -qxF "$LINE_TO_ADD" "$FILE_INSTALLATION_VERSION"; then - echo "$LINE_TO_ADD" >> "$FILE_INSTALLATION_VERSION" + + # Delete artifacts created by pip, cause error in next "poetry install" + if [[ ! -f "${POETRY_ODOO_PATH}" ]]; then + echo -e "Install Poetry ${POETRY_ODOO_PATH}" + pip install poetry==${EL_POETRY_VERSION} + poetry --version + # Fix broken poetry by installing ignored dependence + # poetry lock --no-update + # To fix keyring problem when installation is blocked, use + export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring + if [[ ${WITH_POETRY_INSTALLATION} -ne 0 ]]; then + poetry install --no-root -vvv + fi + retVal=$? + if [[ $retVal -ne 0 ]]; then + echo "Poetry installation error with status ${retVal}" + exit 1 + fi + fi + + # Delete artifacts created by pip, cause error in next "poetry install" + rm -rf artifacts + + # Link for dev tools into Odoo + echo -e "\n---- Add link dependency in site-packages of Python ----" + # TODO this link can break, the symbolic link is maybe not created + ln -fs "${EL_HOME_ODOO}/odoo" "${EL_HOME}/${VENV_ODOO_PATH}/lib/python${PYTHON_VERSION_MAJOR}/site-packages/" + + # Force to return to erplibre source + source ./${VENV_ERPLIBRE_PATH}/bin/activate + + # Add trace of installation + LINE_TO_ADD="odoo${EL_ODOO_VERSION}" + mkdir -p "$(dirname "$FILE_INSTALLATION_VERSION")" + touch "$FILE_INSTALLATION_VERSION" + if ! grep -qxF "$LINE_TO_ADD" "$FILE_INSTALLATION_VERSION"; then + echo "$LINE_TO_ADD" >> "$FILE_INSTALLATION_VERSION" + fi fi diff --git a/script/install/install_locally_dev.sh b/script/install/install_locally_dev.sh index e0e9e8d..023c33d 100755 --- a/script/install/install_locally_dev.sh +++ b/script/install/install_locally_dev.sh @@ -3,27 +3,90 @@ . ./env_var.sh Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +Yellow='\033[0;33m' # Yellow Color_Off='\033[0m' # Text Reset -echo "" -echo "RUN ./script/install/install_locally.sh" -echo "" -./script/install/install_locally.sh -retVal=$? -if [[ $retVal -ne 0 ]]; then - echo -e "${Red}Error${Color_Off} ./script/install/install_locally.sh" - exit 1 -fi +# EL_PARALLEL_INSTALL=1 (default): run repo sync in parallel with poetry install +# EL_PARALLEL_INSTALL=0: legacy sequential behavior (set to debug or force order) +EL_PARALLEL_INSTALL=${EL_PARALLEL_INSTALL:-1} + +if [[ ${EL_PARALLEL_INSTALL} -eq 1 ]]; then + + # ── Phase 1 : common prereqs (~1-3 min) ────────────────────────────────── + # Create venvs, pip-erplibre, install git-repo. Must finish before both + # repo sync (.venv.erplibre/bin/repo) and poetry (.venv.odooXX) can start. + echo "" + echo "RUN ./script/install/install_locally.sh [phase: setup]" + echo "" + EL_PHASE=setup ./script/install/install_locally.sh + retVal=$? + if [[ $retVal -ne 0 ]]; then + echo -e "${Red}Error${Color_Off} ./script/install/install_locally.sh [setup]" + exit 1 + fi + + # ── Phase 2 : parallel — repo sync (bg) + poetry (fg) ─────────────────── + REPO_SYNC_LOG="/tmp/el_repo_sync_$$.log" + echo "" + echo -e "${Yellow}Starting repo sync in background → ${REPO_SYNC_LOG}${Color_Off}" + echo -e "${Yellow} Follow live: tail -f ${REPO_SYNC_LOG}${Color_Off}" + echo "" + ./script/manifest/update_manifest_local_dev.sh >"${REPO_SYNC_LOG}" 2>&1 & + PID_REPO=$! + # Safety: kill background job if this script exits unexpectedly + trap "kill ${PID_REPO} 2>/dev/null; wait ${PID_REPO} 2>/dev/null; rm -f ${REPO_SYNC_LOG}" EXIT + + echo "" + echo "RUN ./script/install/install_locally.sh [phase: poetry]" + echo "" + EL_PHASE=poetry ./script/install/install_locally.sh + retVal=$? + if [[ $retVal -ne 0 ]]; then + echo -e "${Red}Error${Color_Off} ./script/install/install_locally.sh [poetry]" + exit 1 + fi + + # ── Wait for repo sync ──────────────────────────────────────────────────── + trap - EXIT + echo "" + echo "Waiting for repo sync to complete (pid ${PID_REPO})..." + wait ${PID_REPO} + REPO_STATUS=$? + if [[ ${REPO_STATUS} -ne 0 ]]; then + echo -e "${Red}Error${Color_Off} manifest update (repo sync) failed." + echo -e "Last 50 lines of ${REPO_SYNC_LOG}:" + tail -n 50 "${REPO_SYNC_LOG}" + rm -f "${REPO_SYNC_LOG}" + exit 1 + fi + echo -e "${Green}Repo sync completed successfully.${Color_Off}" + rm -f "${REPO_SYNC_LOG}" + +else + + # ── Sequential mode (legacy) ────────────────────────────────────────────── + echo "" + echo "RUN ./script/install/install_locally.sh" + echo "" + ./script/install/install_locally.sh + retVal=$? + if [[ $retVal -ne 0 ]]; then + echo -e "${Red}Error${Color_Off} ./script/install/install_locally.sh" + exit 1 + fi + + # Update git-repo + echo "" + echo "RUN ./script/manifest/update_manifest_local_dev.sh" + echo "" + ./script/manifest/update_manifest_local_dev.sh + retVal=$? + if [[ $retVal -ne 0 ]]; then + echo -e "${Red}Error${Color_Off} manifest update, check git-repo." + exit 1 + fi -# Update git-repo -echo "" -echo "RUN ./script/manifest/update_manifest_local_dev.sh" -echo "" -./script/manifest/update_manifest_local_dev.sh -retVal=$? -if [[ $retVal -ne 0 ]]; then - echo -e "${Red}Error${Color_Off} manifest update, check git-repo." - exit 1 fi npm install diff --git a/script/manifest/update_manifest_dev.sh b/script/manifest/update_manifest_dev.sh index 3558c24..f36cd22 100755 --- a/script/manifest/update_manifest_dev.sh +++ b/script/manifest/update_manifest_dev.sh @@ -5,6 +5,9 @@ #EL_MANIFEST_PROD="./default.xml" #EL_MANIFEST_DEV="./manifest/default.dev.xml" +# Generate local manifest +.venv.erplibre/bin/python ./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_OCA + # Update git-repo .venv.erplibre/bin/repo init -u https://github.com/ERPLibre/ERPLibre -b $(git rev-parse --verify HEAD) -m ${EL_MANIFEST_DEV} .venv.erplibre/bin/repo sync -v diff --git a/script/manifest/update_manifest_local_dev_code_generator.sh b/script/manifest/update_manifest_local_dev_code_generator.sh index ec52a82..e9635fd 100755 --- a/script/manifest/update_manifest_local_dev_code_generator.sh +++ b/script/manifest/update_manifest_local_dev_code_generator.sh @@ -21,6 +21,9 @@ else JOBS="$(sysctl -n hw.ncpu)" fi +# Generate local manifest +.venv.erplibre/bin/python ./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_OCA + .venv.erplibre/bin/repo init -u git://127.0.0.1:9418/ -b $(git rev-parse --verify HEAD) -m ${MANIFEST_TARGET} -g base,code_generator .venv.erplibre/bin/repo sync -c -j "$JOBS" -v -m ${MANIFEST_TARGET} diff --git a/script/manifest/update_manifest_local_prod.sh b/script/manifest/update_manifest_local_prod.sh index 66c029a..09c7d57 100755 --- a/script/manifest/update_manifest_local_prod.sh +++ b/script/manifest/update_manifest_local_prod.sh @@ -21,6 +21,9 @@ else JOBS="$(sysctl -n hw.ncpu)" fi +# Generate local manifest +.venv.erplibre/bin/python ./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_OCA + .venv.erplibre/bin/repo init -u git://127.0.0.1:9418/ -b $(git rev-parse --verify HEAD) -m ${MANIFEST_TARGET} .venv.erplibre/bin/repo sync -c -j "$JOBS" -v -m ${MANIFEST_TARGET} diff --git a/script/manifest/update_manifest_prod.sh b/script/manifest/update_manifest_prod.sh index 4a8e8e2..fa3beb0 100755 --- a/script/manifest/update_manifest_prod.sh +++ b/script/manifest/update_manifest_prod.sh @@ -11,6 +11,9 @@ else JOBS="$(sysctl -n hw.ncpu)" fi +# Generate local manifest +.venv.erplibre/bin/python ./script/git/git_merge_repo_manifest.py --output .repo/local_manifests/erplibre_manifest.xml --with_OCA + # Update git-repo .venv.erplibre/bin/repo init -u https://github.com/ERPLibre/ERPLibre -b $(git rev-parse --verify HEAD) .venv.erplibre/bin/repo sync -v -j "$JOBS" diff --git a/script/todo/database_manager.py b/script/todo/database_manager.py index 8011cb4..617a8e6 100644 --- a/script/todo/database_manager.py +++ b/script/todo/database_manager.py @@ -8,15 +8,16 @@ import logging import os import zipfile -import click - from script.todo.todo_i18n import t _logger = logging.getLogger(__name__) try: + import click + from script.todo import todo_file_browser except Exception: + click = None todo_file_browser = None diff --git a/script/todo/todo.py b/script/todo/todo.py index c947631..53cccd2 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -409,6 +409,37 @@ class TODO: return cmd_intern = install_commands.get(odoo_version_input)[2] + + # For numbered version selections, offer extra modules sub-menu + if odoo_version_input.isdigit(): + extra_choices = { + "1": ( + "1", + f"1: {t('Standard install (without extra modules)')}", + ), + "2": ( + "2", + f"2: {t('Install with extra modules (CybroOdoo - large, slow)')}", + ), + "0": ("0", f"0: {t('Back')}"), + } + extra_input = "" + while extra_input not in extra_choices: + if extra_input: + print( + f"{t('Error, cannot understand value')} '{extra_input}'" + ) + str_extra = ( + f"💬 {t('Install type:')}\n\t" + + "\n\t".join([a[1] for a in extra_choices.values()]) + + f"\n{t('Select: ')}" + ) + extra_input = input(str_extra).strip() + if extra_input == "0": + return + if extra_input == "2": + cmd_intern = cmd_intern + " --with_extra" + print(f"{t('Will execute:')}\n{cmd_intern}") # TODO use external script to detect terminal to use on system diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index 5922b4c..6867282 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -367,6 +367,18 @@ TRANSLATIONS = { "fr": "Choisir une version :", "en": "Choose a version:", }, + "Install type:": { + "fr": "Type d'installation :", + "en": "Install type:", + }, + "Standard install (without extra modules)": { + "fr": "Installation standard (sans modules extra)", + "en": "Standard install (without extra modules)", + }, + "Install with extra modules (CybroOdoo - large, slow)": { + "fr": "Installation avec modules extra (CybroOdoo - gros, lent)", + "en": "Install with extra modules (CybroOdoo - large, slow)", + }, # todo.json translatable prompt_descriptions "Test - Minimal base instance": { "fr": "Test - Instance de base minimale", diff --git a/script/todo/todo_upgrade.py b/script/todo/todo_upgrade.py index a83f199..d77b6dc 100755 --- a/script/todo/todo_upgrade.py +++ b/script/todo/todo_upgrade.py @@ -511,7 +511,7 @@ class TodoUpgrade: file_browser.run_main_frame() elif self.file_path == "remote": status, self.file_path, default_database_name = ( - self.todo.download_database_backup_cli() + self.todo.db_manager.download_database_backup_cli() ) if status: _logger.error( @@ -2038,7 +2038,9 @@ class TodoUpgrade: if not has_existing_target_branch: cmd_git_clone = ( f"cd {target_addons_path} " - f"&& git checkout -b {branch_target} && cd ~-" + f"&& if git rev-parse --verify --quiet refs/heads/{branch_target}; then " + f"git checkout {branch_target}; else git checkout -b {branch_target}; fi " + f"&& cd ~-" ) status, cmd_executed, lst_output = self.todo_upgrade_execute( cmd_git_clone, diff --git a/script/version/erplibre_state.py b/script/version/erplibre_state.py new file mode 100644 index 0000000..790434c --- /dev/null +++ b/script/version/erplibre_state.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# © 2026 TechnoLibre (http://www.technolibre.ca) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +""" +Utility module for reading and writing .erplibre-state.json. + +This file tracks what is installed in the current ERPLibre workspace: + - Which Odoo versions are installed, and with which options (extra, etc.) + - Whether the mobile project is active + - The currently active Odoo version +""" + +import json +import logging +import os +from datetime import date + +_logger = logging.getLogger(__name__) + +STATE_FILE = ".erplibre-state.json" + +_EMPTY_VERSION_ENTRY = { + "installed": False, + "extra": False, + "python": None, + "poetry": None, + "installed_at": None, + "switched_at": None, +} + +_EMPTY_STATE = { + "current_odoo_version": None, + "mobile": { + "active": False, + "installed_at": None, + }, + "odoo_versions": {}, +} + + +def read_state() -> dict: + """Return the current state, or an empty state if the file does not exist.""" + if not os.path.isfile(STATE_FILE): + return _deep_copy(_EMPTY_STATE) + try: + with open(STATE_FILE, "r", encoding="utf-8") as f: + return json.load(f) + except (json.JSONDecodeError, OSError) as e: + _logger.warning(f"Cannot read {STATE_FILE}: {e}. Using empty state.") + return _deep_copy(_EMPTY_STATE) + + +def write_state(state: dict) -> None: + """Persist state to .erplibre-state.json.""" + with open(STATE_FILE, "w", encoding="utf-8") as f: + json.dump(state, f, indent=4, ensure_ascii=False) + f.write("\n") + + +def set_version_installed( + odoo_version: str, + extra: bool = False, + python: str = None, + poetry: str = None, +) -> None: + """Record that an Odoo version has been installed (or reinstalled).""" + state = read_state() + entry = state["odoo_versions"].get(odoo_version, _deep_copy(_EMPTY_VERSION_ENTRY)) + entry["installed"] = True + entry["extra"] = extra + if python: + entry["python"] = python + if poetry: + entry["poetry"] = poetry + entry["installed_at"] = str(date.today()) + state["odoo_versions"][odoo_version] = entry + state["current_odoo_version"] = odoo_version + write_state(state) + _logger.info( + f"State updated: odoo {odoo_version} installed" + f" (extra={extra}, python={python}, poetry={poetry})" + ) + + +def set_version_switched(odoo_version: str) -> None: + """Record that the workspace was switched to an Odoo version.""" + state = read_state() + entry = state["odoo_versions"].get(odoo_version, _deep_copy(_EMPTY_VERSION_ENTRY)) + entry["switched_at"] = str(date.today()) + state["odoo_versions"][odoo_version] = entry + state["current_odoo_version"] = odoo_version + write_state(state) + + +def set_mobile_active(active: bool) -> None: + """Record whether the mobile project is currently synced.""" + state = read_state() + state["mobile"]["active"] = active + if active: + state["mobile"]["installed_at"] = str(date.today()) + write_state(state) + _logger.info(f"State updated: mobile active={active}") + + +def get_version_extra(odoo_version: str) -> bool: + """Return True if the given Odoo version was installed with extra modules.""" + state = read_state() + entry = state["odoo_versions"].get(odoo_version) + if entry is None: + return False + return bool(entry.get("extra", False)) + + +def get_version_installed(odoo_version: str) -> bool: + """Return True if the given Odoo version has an installation record.""" + state = read_state() + entry = state["odoo_versions"].get(odoo_version) + if entry is None: + return False + return bool(entry.get("installed", False)) + + +def get_current_version() -> str | None: + """Return the currently active Odoo version, or None.""" + return read_state().get("current_odoo_version") + + +def get_mobile_active() -> bool: + """Return True if mobile is recorded as active.""" + return bool(read_state().get("mobile", {}).get("active", False)) + + +def print_state() -> None: + """Log a human-readable summary of the current state.""" + state = read_state() + current = state.get("current_odoo_version") or "unknown" + mobile = state.get("mobile", {}) + mobile_status = "active" if mobile.get("active") else "inactive" + + _logger.info(f"Current Odoo version : {current}") + _logger.info(f"Mobile context : {mobile_status}") + + versions = state.get("odoo_versions", {}) + if versions: + installed = [ + f"{v}{' +extra' if d.get('extra') else ''}" + for v, d in sorted(versions.items()) + if d.get("installed") + ] + if installed: + _logger.info(f"Installed versions : {', '.join(installed)}") + else: + _logger.info("Installed versions : none recorded") + else: + _logger.info("Installed versions : none recorded") + + +def _deep_copy(d: dict) -> dict: + """Simple deep copy for plain dicts/lists (no external deps).""" + return json.loads(json.dumps(d)) diff --git a/script/version/update_env_version.py b/script/version/update_env_version.py index 9831847..154913b 100755 --- a/script/version/update_env_version.py +++ b/script/version/update_env_version.py @@ -12,6 +12,21 @@ import subprocess import sys import time +# 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 + logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO")) _logger = logging.getLogger(__name__) @@ -24,6 +39,7 @@ 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") +MOBILE_PATH = os.path.join("mobile", "erplibre_home_mobile") VENV_TEMPLATE_FILE = ".venv.%s" MANIFEST_FILE = "default.dev.xml" MANIFEST_TEMPLATE_FILE = "default.dev.odoo%s.xml" @@ -123,6 +139,14 @@ def get_config(): action="store_true", help="Force all, include force_install, force_repo.", ) + 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." + ), + ) args = parser.parse_args() if args.force: @@ -144,6 +168,7 @@ class Update: self.data_version = None self.odoo_version = None self.python_version = None + self.mobile_active = False self.detected_version_erplibre = None self.new_version_erplibre = None self.new_version_odoo = None @@ -188,10 +213,21 @@ class Update: with open(VERSION_POETRY_FILE) as txt: poetry_version = txt.read().strip() + # Detect mobile context + self.mobile_active = os.path.isdir(MOBILE_PATH) + # Show actual version _logger.info(f"Python version: {self.python_version}") _logger.info(f"Odoo version: {self.odoo_version}") _logger.info(f"Poetry version: {poetry_version}") + _logger.info( + f"Mobile context: {'active (' + MOBILE_PATH + ')' if self.mobile_active else 'inactive'}" + ) + if _STATE_AVAILABLE: + extra = get_version_extra(self.odoo_version) + _logger.info( + f"Extra modules (CybroOdoo): {'active' if extra else 'inactive'}" + ) erplibre_version_to_search = ERPLIBRE_TEMPLATE_VERSION % ( self.odoo_version, self.python_version, @@ -405,15 +441,38 @@ class Update: if self.config.install_dev: _logger.info("Installation.") status = self.install_erplibre() + 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, + ) elif ( self.config.is_in_switch and self.config.is_in_switch_force_update ): _logger.info("Switch") self.execute_log.append(f"System update") - status = os.system( - "./script/manifest/update_manifest_local_dev.sh" - ) + # 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) # To support multiple addons directory, remove TEMP # for addons_path in os.listdir("."):