You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

173 lines
6.2 KiB

# -*- coding: utf-8 -*-
import logging
import os
from glob import glob
from pprint import pformat
from subprocess import check_output
import yaml
# Constants needed in scripts
CUSTOM_DIR = "/app/code/custom"
AUTO_DIR = "/app/code/auto"
ADDONS_DIR = os.path.join(AUTO_DIR, "addons")
SRC_DIR = os.path.join(CUSTOM_DIR, "src")
ADDONS_YAML = os.path.join(SRC_DIR, "addons")
if os.path.isfile("%s.yaml" % ADDONS_YAML):
ADDONS_YAML = "%s.yaml" % ADDONS_YAML
else:
ADDONS_YAML = "%s.yml" % ADDONS_YAML
REPOS_YAML = os.path.join(SRC_DIR, "repos")
if os.path.isfile("%s.yaml" % REPOS_YAML):
REPOS_YAML = "%s.yaml" % REPOS_YAML
else:
REPOS_YAML = "%s.yml" % REPOS_YAML
AUTO_REPOS_YAML = os.path.join(AUTO_DIR, "repos")
if os.path.isfile("%s.yml" % AUTO_REPOS_YAML):
AUTO_REPOS_YAML = "%s.yml" % AUTO_REPOS_YAML
else:
AUTO_REPOS_YAML = "%s.yaml" % AUTO_REPOS_YAML
CLEAN = os.environ.get("CLEAN") == "true"
LOG_LEVELS = frozenset({"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"})
FILE_APT_BUILD = os.path.join(CUSTOM_DIR, "dependencies", "apt_build.txt")
PRIVATE = "private"
CORE = "odoo/addons"
ENTERPRISE = "enterprise"
PRIVATE_DIR = os.path.join(SRC_DIR, PRIVATE)
CORE_DIR = os.path.join(SRC_DIR, CORE)
ODOO_DIR = os.path.join(SRC_DIR, "odoo")
ODOO_VERSION = os.environ["ODOO_VERSION"]
MANIFESTS = ("__manifest__.py", "__openerp__.py")
# Customize logging for build
logger = logging.getLogger("doodba")
log_handler = logging.StreamHandler()
log_formatter = logging.Formatter("%(name)s %(levelname)s: %(message)s")
log_handler.setFormatter(log_formatter)
logger.addHandler(log_handler)
_log_level = os.environ.get("LOG_LEVEL", "")
if _log_level.isdigit():
_log_level = int(_log_level)
elif _log_level in LOG_LEVELS:
_log_level = getattr(logging, _log_level)
else:
if _log_level:
logger.warning("Wrong value in $LOG_LEVEL, falling back to INFO")
_log_level = logging.INFO
logger.setLevel(_log_level)
class AddonsConfigError(Exception):
def __init__(self, message, *args):
super(AddonsConfigError, self).__init__(message, *args)
self.message = message
def addons_config(filtered=True, strict=False):
"""Yield addon name and path from ``ADDONS_YAML``.
:param bool filtered:
Use ``False`` to include all addon definitions. Use ``True`` (default)
to include only those matched by ``ONLY`` clauses, if any.
:param bool strict:
Use ``True`` to raise an exception if any declared addon is not found.
:return Iterator[str, str]:
A generator that yields ``(addon, repo)`` pairs.
"""
config = dict()
missing_glob = set()
missing_manifest = set()
all_globs = {}
try:
with open(ADDONS_YAML) as addons_file:
for doc in yaml.safe_load_all(addons_file):
# Skip sections with ONLY and that don't match
only = doc.pop("ONLY", {})
if not filtered:
doc.setdefault(CORE, ["*"])
doc.setdefault(PRIVATE, ["*"])
elif any(
os.environ.get(key) not in values for key, values in only.items()
):
logger.debug("Skipping section with ONLY %s", only)
continue
# Flatten all sections in a single dict
for repo, partial_globs in doc.items():
if repo == "ENV":
continue
logger.debug("Processing %s repo", repo)
all_globs.setdefault(repo, set())
all_globs[repo].update(partial_globs)
except IOError:
logger.debug("Could not find addons configuration yaml.")
# Add default values for special sections
for repo in (CORE, PRIVATE):
all_globs.setdefault(repo, {"*"})
logger.debug("Merged addons definition before expanding: %r", all_globs)
# Expand all globs and store config
for repo, partial_globs in all_globs.items():
for partial_glob in partial_globs:
logger.debug("Expanding in repo %s glob %s", repo, partial_glob)
full_glob = os.path.join(SRC_DIR, repo, partial_glob)
found = glob(full_glob)
if not found:
# Projects without private addons should never fail
if (repo, partial_glob) != (PRIVATE, "*"):
missing_glob.add(full_glob)
logger.debug("Skipping unexpandable glob '%s'", full_glob)
continue
for addon in found:
if not os.path.isdir(addon):
continue
manifests = (os.path.join(addon, m) for m in MANIFESTS)
if not any(os.path.isfile(m) for m in manifests):
missing_manifest.add(addon)
logger.debug(
"Skipping '%s' as it is not a valid Odoo " "module", addon
)
continue
logger.debug("Registering addon %s", addon)
addon = os.path.basename(addon)
config.setdefault(addon, set())
config[addon].add(repo)
# Fail now if running in strict mode
if strict:
error = []
if missing_glob:
error += ["Addons not found:", pformat(missing_glob)]
if missing_manifest:
error += ["Addons without manifest:", pformat(missing_manifest)]
if error:
raise AddonsConfigError("\n".join(error), missing_glob, missing_manifest)
logger.debug("Resulting configuration after expanding: %r", config)
for addon, repos in config.items():
# Private addons are most important
if PRIVATE in repos:
yield addon, PRIVATE
continue
# Odoo core addons are least important
if repos == {CORE}:
yield addon, CORE
continue
repos.discard(CORE)
# Other addons fall in between
if filtered and len(repos) != 1:
raise AddonsConfigError(
u"Addon {} defined in several repos {}".format(addon, repos)
)
for repo in repos:
yield addon, repo
try:
from shutil import which
except ImportError:
# Custom which implementation for Python 2
def which(binary):
return check_output(["which", binary]).strip()