mirror of
https://github.com/frappe/bench.git
synced 2024-11-13 16:56:33 +00:00
8db23dd230
glob on env/**/bin is slowest operation in terms of overhead on CLI.
773 lines
21 KiB
Python
773 lines
21 KiB
Python
# imports - standard imports
|
|
import contextlib
|
|
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from functools import lru_cache
|
|
from glob import glob
|
|
from json.decoder import JSONDecodeError
|
|
from pathlib import Path
|
|
|
|
# imports - third party imports
|
|
import click
|
|
|
|
# imports - module imports
|
|
import bench
|
|
from bench.exceptions import PatchError, ValidationError
|
|
from bench.utils import (
|
|
exec_cmd,
|
|
get_bench_cache_path,
|
|
get_bench_name,
|
|
get_cmd_output,
|
|
log,
|
|
which,
|
|
)
|
|
|
|
logger = logging.getLogger(bench.PROJECT_NAME)
|
|
|
|
|
|
@lru_cache(maxsize=None)
|
|
def get_env_cmd(cmd: str, bench_path: str = ".") -> str:
|
|
exact_location = os.path.abspath(
|
|
os.path.join(bench_path, "env", "bin", cmd.strip("*"))
|
|
)
|
|
if os.path.exists(exact_location):
|
|
return exact_location
|
|
|
|
# this supports envs' generated by patched virtualenv or venv (which may cause an extra 'local' folder to be created)
|
|
existing_python_bins = glob(
|
|
os.path.join(bench_path, "env", "**", "bin", cmd), recursive=True
|
|
)
|
|
|
|
if existing_python_bins:
|
|
return os.path.abspath(existing_python_bins[0])
|
|
|
|
return exact_location
|
|
|
|
|
|
def get_venv_path(verbose=False, python="python3"):
|
|
with open(os.devnull, "wb") as devnull:
|
|
is_venv_installed = not subprocess.call(
|
|
[python, "-m", "venv", "--help"], stdout=devnull
|
|
)
|
|
if is_venv_installed:
|
|
return f"{python} -m venv"
|
|
else:
|
|
log("venv cannot be found", level=2)
|
|
|
|
|
|
def update_node_packages(bench_path=".", apps=None, verbose=None):
|
|
print("Updating node packages...")
|
|
from distutils.version import LooseVersion
|
|
|
|
from bench.utils.app import get_develop_version
|
|
|
|
v = LooseVersion(get_develop_version("frappe", bench_path=bench_path))
|
|
|
|
# After rollup was merged, frappe_version = 10.1
|
|
# if develop_verion is 11 and up, only then install yarn
|
|
if v < LooseVersion("11.x.x-develop"):
|
|
update_npm_packages(bench_path, apps=apps, verbose=verbose)
|
|
else:
|
|
update_yarn_packages(bench_path, apps=apps, verbose=verbose)
|
|
|
|
|
|
def install_python_dev_dependencies(bench_path=".", apps=None, verbose=False):
|
|
import bench.cli
|
|
from bench.bench import Bench
|
|
|
|
verbose = bench.cli.verbose or verbose
|
|
quiet_flag = "" if verbose else "--quiet"
|
|
|
|
bench = Bench(bench_path)
|
|
|
|
if isinstance(apps, str):
|
|
apps = [apps]
|
|
elif not apps:
|
|
apps = bench.get_installed_apps()
|
|
|
|
for app in apps:
|
|
pyproject_deps = None
|
|
app_path = os.path.join(bench_path, "apps", app)
|
|
pyproject_path = os.path.join(app_path, "pyproject.toml")
|
|
dev_requirements_path = os.path.join(app_path, "dev-requirements.txt")
|
|
|
|
if os.path.exists(pyproject_path):
|
|
pyproject_deps = _generate_dev_deps_pattern(pyproject_path)
|
|
if pyproject_deps:
|
|
bench.run(f"{bench.python} -m pip install {quiet_flag} --upgrade {pyproject_deps}")
|
|
|
|
if not pyproject_deps and os.path.exists(dev_requirements_path):
|
|
bench.run(
|
|
f"{bench.python} -m pip install {quiet_flag} --upgrade -r {dev_requirements_path}"
|
|
)
|
|
|
|
|
|
def _generate_dev_deps_pattern(pyproject_path):
|
|
try:
|
|
from tomli import loads
|
|
except ImportError:
|
|
from tomllib import loads
|
|
|
|
requirements_pattern = ""
|
|
pyroject_config = loads(open(pyproject_path).read())
|
|
|
|
with contextlib.suppress(KeyError):
|
|
for pkg, version in pyroject_config["tool"]["bench"]["dev-dependencies"].items():
|
|
op = "==" if "=" not in version else ""
|
|
requirements_pattern += f"{pkg}{op}{version} "
|
|
return requirements_pattern
|
|
|
|
|
|
def update_yarn_packages(bench_path=".", apps=None, verbose=None):
|
|
import bench.cli as bench_cli
|
|
from bench.bench import Bench
|
|
|
|
verbose = bench_cli.verbose or verbose
|
|
bench = Bench(bench_path)
|
|
apps = apps or bench.apps
|
|
apps_dir = os.path.join(bench.name, "apps")
|
|
|
|
# TODO: Check for stuff like this early on only??
|
|
if not which("yarn"):
|
|
print("Please install yarn using below command and try again.")
|
|
print("`npm install -g yarn`")
|
|
return
|
|
|
|
for app in apps:
|
|
app_path = os.path.join(apps_dir, app)
|
|
if os.path.exists(os.path.join(app_path, "package.json")):
|
|
click.secho(f"\nInstalling node dependencies for {app}", fg="yellow")
|
|
yarn_install = "yarn install --check-files"
|
|
if verbose:
|
|
yarn_install += " --verbose"
|
|
bench.run(yarn_install, cwd=app_path)
|
|
|
|
|
|
def update_npm_packages(bench_path=".", apps=None, verbose=None):
|
|
verbose = bench.cli.verbose or verbose
|
|
npm_install = "npm install --verbose" if verbose else "npm install"
|
|
apps_dir = os.path.join(bench_path, "apps")
|
|
package_json = {}
|
|
|
|
if not apps:
|
|
apps = os.listdir(apps_dir)
|
|
|
|
for app in apps:
|
|
package_json_path = os.path.join(apps_dir, app, "package.json")
|
|
|
|
if os.path.exists(package_json_path):
|
|
with open(package_json_path) as f:
|
|
app_package_json = json.loads(f.read())
|
|
# package.json is usually a dict in a dict
|
|
for key, value in app_package_json.items():
|
|
if key not in package_json:
|
|
package_json[key] = value
|
|
else:
|
|
if isinstance(value, dict):
|
|
package_json[key].update(value)
|
|
elif isinstance(value, list):
|
|
package_json[key].extend(value)
|
|
else:
|
|
package_json[key] = value
|
|
|
|
if package_json == {}:
|
|
with open(os.path.join(os.path.dirname(__file__), "package.json")) as f:
|
|
package_json = json.loads(f.read())
|
|
|
|
with open(os.path.join(bench_path, "package.json"), "w") as f:
|
|
f.write(json.dumps(package_json, indent=1, sort_keys=True))
|
|
|
|
exec_cmd(npm_install, cwd=bench_path)
|
|
|
|
|
|
def migrate_env(python, backup=False):
|
|
import shutil
|
|
from urllib.parse import urlparse
|
|
|
|
from bench.bench import Bench
|
|
|
|
bench = Bench(".")
|
|
nvenv = "env"
|
|
path = os.getcwd()
|
|
python = which(python)
|
|
pvenv = os.path.join(path, nvenv)
|
|
|
|
if python.startswith(pvenv):
|
|
# The supplied python version is in active virtualenv which we are about to nuke.
|
|
click.secho(
|
|
"Python version supplied is present in currently sourced virtual environment.\n"
|
|
"`deactiviate` the current virtual environment before migrating environments.",
|
|
fg="yellow",
|
|
)
|
|
sys.exit(1)
|
|
|
|
# Clear Cache before Bench Dies.
|
|
try:
|
|
config = bench.conf
|
|
rredis = urlparse(config["redis_cache"])
|
|
redis = f"{which('redis-cli')} -p {rredis.port}"
|
|
|
|
logger.log("Clearing Redis Cache...")
|
|
exec_cmd(f"{redis} FLUSHALL")
|
|
logger.log("Clearing Redis DataBase...")
|
|
exec_cmd(f"{redis} FLUSHDB")
|
|
except Exception:
|
|
logger.warning("Please ensure Redis Connections are running or Daemonized.")
|
|
|
|
# Backup venv: restore using `virtualenv --relocatable` if needed
|
|
if backup:
|
|
from datetime import datetime
|
|
|
|
parch = os.path.join(path, "archived", "envs")
|
|
os.makedirs(parch, exist_ok=True)
|
|
|
|
source = os.path.join(path, "env")
|
|
target = parch
|
|
|
|
logger.log("Backing up Virtual Environment")
|
|
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
dest = os.path.join(path, str(stamp))
|
|
|
|
os.rename(source, dest)
|
|
shutil.move(dest, target)
|
|
|
|
# Create virtualenv using specified python
|
|
def _install_app(app):
|
|
app_path = f"-e {os.path.join('apps', app)}"
|
|
exec_cmd(f"{pvenv}/bin/python -m pip install --upgrade {app_path}")
|
|
|
|
try:
|
|
logger.log(f"Setting up a New Virtual {python} Environment")
|
|
exec_cmd(f"{python} -m venv {pvenv}")
|
|
|
|
# Install frappe first
|
|
_install_app("frappe")
|
|
for app in bench.apps:
|
|
if str(app) != "frappe":
|
|
_install_app(app)
|
|
|
|
logger.log(f"Migration Successful to {python}")
|
|
except Exception:
|
|
logger.warning("Python env migration Error", exc_info=True)
|
|
raise
|
|
|
|
|
|
def validate_upgrade(from_ver, to_ver, bench_path="."):
|
|
if to_ver >= 6 and not which("npm") and not which("node") and not which("nodejs"):
|
|
raise Exception("Please install nodejs and npm")
|
|
|
|
|
|
def post_upgrade(from_ver, to_ver, bench_path="."):
|
|
from bench.bench import Bench
|
|
from bench.config import redis
|
|
from bench.config.nginx import make_nginx_conf
|
|
from bench.config.supervisor import generate_supervisor_config
|
|
|
|
conf = Bench(bench_path).conf
|
|
print("-" * 80 + f"Your bench was upgraded to version {to_ver}")
|
|
|
|
if conf.get("restart_supervisor_on_update"):
|
|
redis.generate_config(bench_path=bench_path)
|
|
generate_supervisor_config(bench_path=bench_path)
|
|
make_nginx_conf(bench_path=bench_path)
|
|
print(
|
|
"As you have setup your bench for production, you will have to reload"
|
|
" configuration for nginx and supervisor. To complete the migration, please"
|
|
" run the following commands:\nsudo service nginx restart\nsudo"
|
|
" supervisorctl reload"
|
|
)
|
|
|
|
|
|
def patch_sites(bench_path="."):
|
|
from bench.bench import Bench
|
|
from bench.utils.system import migrate_site
|
|
|
|
bench = Bench(bench_path)
|
|
|
|
for site in bench.sites:
|
|
try:
|
|
migrate_site(site, bench_path=bench_path)
|
|
except subprocess.CalledProcessError:
|
|
raise PatchError
|
|
|
|
|
|
def restart_supervisor_processes(bench_path=".", web_workers=False, _raise=False):
|
|
from bench.bench import Bench
|
|
|
|
bench = Bench(bench_path)
|
|
conf = bench.conf
|
|
cmd = conf.get("supervisor_restart_cmd")
|
|
bench_name = get_bench_name(bench_path)
|
|
|
|
if cmd:
|
|
bench.run(cmd, _raise=_raise)
|
|
|
|
else:
|
|
sudo = ""
|
|
try:
|
|
supervisor_status = get_cmd_output("supervisorctl status", cwd=bench_path)
|
|
except subprocess.CalledProcessError as e:
|
|
if e.returncode == 127:
|
|
log("restart failed: Couldn't find supervisorctl in PATH", level=3)
|
|
return
|
|
sudo = "sudo "
|
|
supervisor_status = get_cmd_output("sudo supervisorctl status", cwd=bench_path)
|
|
|
|
if not sudo and (
|
|
"error: <class 'PermissionError'>, [Errno 13] Permission denied" in supervisor_status
|
|
):
|
|
sudo = "sudo "
|
|
supervisor_status = get_cmd_output("sudo supervisorctl status", cwd=bench_path)
|
|
|
|
if web_workers and f"{bench_name}-web:" in supervisor_status:
|
|
groups = [f"{bench_name}-web:\t"]
|
|
|
|
elif f"{bench_name}-workers:" in supervisor_status:
|
|
groups = [f"{bench_name}-web:", f"{bench_name}-workers:"]
|
|
|
|
# backward compatibility
|
|
elif f"{bench_name}-processes:" in supervisor_status:
|
|
groups = [f"{bench_name}-processes:"]
|
|
|
|
# backward compatibility
|
|
else:
|
|
groups = ["frappe:"]
|
|
|
|
for group in groups:
|
|
failure = bench.run(f"{sudo}supervisorctl restart {group}", _raise=_raise)
|
|
if failure:
|
|
log(
|
|
f"restarting supervisor group `{group}` failed. Use `bench restart` to retry.",
|
|
level=3,
|
|
)
|
|
|
|
|
|
def restart_systemd_processes(bench_path=".", web_workers=False, _raise=True):
|
|
bench_name = get_bench_name(bench_path)
|
|
exec_cmd(
|
|
f"sudo systemctl stop -- $(systemctl show -p Requires {bench_name}.target | cut"
|
|
" -d= -f2)",
|
|
_raise=_raise,
|
|
)
|
|
exec_cmd(
|
|
f"sudo systemctl start -- $(systemctl show -p Requires {bench_name}.target |"
|
|
" cut -d= -f2)",
|
|
_raise=_raise,
|
|
)
|
|
|
|
|
|
def restart_process_manager(bench_path=".", web_workers=False):
|
|
# only overmind has the restart feature, not sure other supported procmans do
|
|
if which("overmind") and os.path.exists(os.path.join(bench_path, ".overmind.sock")):
|
|
worker = "web" if web_workers else ""
|
|
exec_cmd(f"overmind restart {worker}", cwd=bench_path)
|
|
|
|
|
|
def build_assets(bench_path=".", app=None, using_cached=False):
|
|
command = "bench build"
|
|
if app:
|
|
command += f" --app {app}"
|
|
|
|
env = {"BENCH_DEVELOPER": "1"}
|
|
if using_cached:
|
|
env["USING_CACHED"] = "1"
|
|
|
|
exec_cmd(command, cwd=bench_path, env=env)
|
|
|
|
|
|
def handle_version_upgrade(version_upgrade, bench_path, force, reset, conf):
|
|
from bench.utils import log, pause_exec
|
|
|
|
if version_upgrade[0]:
|
|
if force:
|
|
log(
|
|
"""Force flag has been used for a major version change in Frappe and it's apps.
|
|
This will take significant time to migrate and might break custom apps.""",
|
|
level=3,
|
|
)
|
|
else:
|
|
print(
|
|
f"""This update will cause a major version change in Frappe/ERPNext from {version_upgrade[1]} to {version_upgrade[2]}.
|
|
This would take significant time to migrate and might break custom apps."""
|
|
)
|
|
click.confirm("Do you want to continue?", abort=True)
|
|
|
|
if not reset and conf.get("shallow_clone"):
|
|
log(
|
|
"""shallow_clone is set in your bench config.
|
|
However without passing the --reset flag, your repositories will be unshallowed.
|
|
To avoid this, cancel this operation and run `bench update --reset`.
|
|
|
|
Consider the consequences of `git reset --hard` on your apps before you run that.
|
|
To avoid seeing this warning, set shallow_clone to false in your common_site_config.json
|
|
""",
|
|
level=3,
|
|
)
|
|
pause_exec(seconds=10)
|
|
|
|
if version_upgrade[0] or (not version_upgrade[0] and force):
|
|
validate_upgrade(version_upgrade[1], version_upgrade[2], bench_path=bench_path)
|
|
|
|
|
|
def update(
|
|
pull: bool = False,
|
|
apps: str = None,
|
|
patch: bool = False,
|
|
build: bool = False,
|
|
requirements: bool = False,
|
|
backup: bool = True,
|
|
compile: bool = True,
|
|
force: bool = False,
|
|
reset: bool = False,
|
|
restart_supervisor: bool = False,
|
|
restart_systemd: bool = False,
|
|
):
|
|
"""command: bench update"""
|
|
import re
|
|
|
|
from bench import patches
|
|
from bench.app import pull_apps
|
|
from bench.bench import Bench
|
|
from bench.config.common_site_config import update_config
|
|
from bench.exceptions import CannotUpdateReleaseBench
|
|
from bench.utils.app import is_version_upgrade
|
|
from bench.utils.system import backup_all_sites
|
|
|
|
bench_path = os.path.abspath(".")
|
|
bench = Bench(bench_path)
|
|
patches.run(bench_path=bench_path)
|
|
conf = bench.conf
|
|
|
|
if conf.get("release_bench"):
|
|
raise CannotUpdateReleaseBench("Release bench detected, cannot update!")
|
|
|
|
if not (pull or patch or build or requirements):
|
|
pull, patch, build, requirements = True, True, True, True
|
|
|
|
if apps and pull:
|
|
apps = [app.strip() for app in re.split(",| ", apps) if app]
|
|
else:
|
|
apps = []
|
|
|
|
validate_branch()
|
|
|
|
version_upgrade = is_version_upgrade()
|
|
handle_version_upgrade(version_upgrade, bench_path, force, reset, conf)
|
|
|
|
conf.update({"maintenance_mode": 1, "pause_scheduler": 1})
|
|
update_config(conf, bench_path=bench_path)
|
|
|
|
if backup:
|
|
print("Backing up sites...")
|
|
backup_all_sites(bench_path=bench_path)
|
|
|
|
if pull:
|
|
print("Updating apps source...")
|
|
pull_apps(apps=apps, bench_path=bench_path, reset=reset)
|
|
|
|
if requirements:
|
|
print("Setting up requirements...")
|
|
bench.setup.requirements()
|
|
|
|
if patch:
|
|
print("Patching sites...")
|
|
patch_sites(bench_path=bench_path)
|
|
|
|
if build:
|
|
print("Building assets...")
|
|
bench.build()
|
|
|
|
if version_upgrade[0] or (not version_upgrade[0] and force):
|
|
post_upgrade(version_upgrade[1], version_upgrade[2], bench_path=bench_path)
|
|
|
|
bench.reload(web=False, supervisor=restart_supervisor, systemd=restart_systemd)
|
|
|
|
conf.update({"maintenance_mode": 0, "pause_scheduler": 0})
|
|
update_config(conf, bench_path=bench_path)
|
|
|
|
print(
|
|
"_" * 80 + "\nBench: Deployment tool for Frappe and Frappe Applications"
|
|
" (https://frappe.io/bench).\nOpen source depends on your contributions, so do"
|
|
" give back by submitting bug reports, patches and fixes and be a part of the"
|
|
" community :)"
|
|
)
|
|
|
|
|
|
def clone_apps_from(bench_path, clone_from, update_app=True):
|
|
from bench.app import install_app
|
|
|
|
print(f"Copying apps from {clone_from}...")
|
|
subprocess.check_output(["cp", "-R", os.path.join(clone_from, "apps"), bench_path])
|
|
|
|
node_modules_path = os.path.join(clone_from, "node_modules")
|
|
if os.path.exists(node_modules_path):
|
|
print(f"Copying node_modules from {clone_from}...")
|
|
subprocess.check_output(["cp", "-R", node_modules_path, bench_path])
|
|
|
|
def setup_app(app):
|
|
# run git reset --hard in each branch, pull latest updates and install_app
|
|
app_path = os.path.join(bench_path, "apps", app)
|
|
|
|
# remove .egg-ino
|
|
subprocess.check_output(["rm", "-rf", app + ".egg-info"], cwd=app_path)
|
|
|
|
if update_app and os.path.exists(os.path.join(app_path, ".git")):
|
|
remotes = subprocess.check_output(["git", "remote"], cwd=app_path).strip().split()
|
|
if "upstream" in remotes:
|
|
remote = "upstream"
|
|
else:
|
|
remote = remotes[0]
|
|
print(f"Cleaning up {app}")
|
|
branch = subprocess.check_output(
|
|
["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=app_path
|
|
).strip()
|
|
subprocess.check_output(["git", "reset", "--hard"], cwd=app_path)
|
|
subprocess.check_output(["git", "pull", "--rebase", remote, branch], cwd=app_path)
|
|
|
|
install_app(app, bench_path, restart_bench=False)
|
|
|
|
with open(os.path.join(clone_from, "sites", "apps.txt")) as f:
|
|
apps = f.read().splitlines()
|
|
|
|
for app in apps:
|
|
setup_app(app)
|
|
|
|
|
|
def remove_backups_crontab(bench_path="."):
|
|
from crontab import CronTab
|
|
|
|
from bench.bench import Bench
|
|
|
|
logger.log("removing backup cronjob")
|
|
|
|
bench_dir = os.path.abspath(bench_path)
|
|
user = Bench(bench_dir).conf.get("frappe_user")
|
|
logfile = os.path.join(bench_dir, "logs", "backup.log")
|
|
system_crontab = CronTab(user=user)
|
|
backup_command = f"cd {bench_dir} && {sys.argv[0]} --verbose --site all backup"
|
|
job_command = f"{backup_command} >> {logfile} 2>&1"
|
|
|
|
system_crontab.remove_all(command=job_command)
|
|
|
|
|
|
def set_mariadb_host(host, bench_path="."):
|
|
update_common_site_config({"db_host": host}, bench_path=bench_path)
|
|
|
|
|
|
def set_redis_cache_host(host, bench_path="."):
|
|
update_common_site_config({"redis_cache": f"redis://{host}"}, bench_path=bench_path)
|
|
|
|
|
|
def set_redis_queue_host(host, bench_path="."):
|
|
update_common_site_config({"redis_queue": f"redis://{host}"}, bench_path=bench_path)
|
|
|
|
|
|
def set_redis_socketio_host(host, bench_path="."):
|
|
update_common_site_config({"redis_socketio": f"redis://{host}"}, bench_path=bench_path)
|
|
|
|
|
|
def update_common_site_config(ddict, bench_path="."):
|
|
filename = os.path.join(bench_path, "sites", "common_site_config.json")
|
|
|
|
if os.path.exists(filename):
|
|
with open(filename) as f:
|
|
content = json.load(f)
|
|
|
|
else:
|
|
content = {}
|
|
|
|
content.update(ddict)
|
|
with open(filename, "w") as f:
|
|
json.dump(content, f, indent=1, sort_keys=True)
|
|
|
|
|
|
def validate_app_installed_on_sites(app, bench_path="."):
|
|
print("Checking if app installed on active sites...")
|
|
ret = check_app_installed(app, bench_path=bench_path)
|
|
|
|
if ret is None:
|
|
check_app_installed_legacy(app, bench_path=bench_path)
|
|
else:
|
|
return ret
|
|
|
|
|
|
def check_app_installed(app, bench_path="."):
|
|
try:
|
|
out = subprocess.check_output(
|
|
["bench", "--site", "all", "list-apps", "--format", "json"],
|
|
stderr=open(os.devnull, "wb"),
|
|
cwd=bench_path,
|
|
).decode("utf-8")
|
|
except subprocess.CalledProcessError:
|
|
return None
|
|
|
|
try:
|
|
apps_sites_dict = json.loads(out)
|
|
except JSONDecodeError:
|
|
return None
|
|
|
|
for site, apps in apps_sites_dict.items():
|
|
if app in apps:
|
|
raise ValidationError(f"Cannot remove, app is installed on site: {site}")
|
|
|
|
|
|
def check_app_installed_legacy(app, bench_path="."):
|
|
site_path = os.path.join(bench_path, "sites")
|
|
|
|
for site in os.listdir(site_path):
|
|
req_file = os.path.join(site_path, site, "site_config.json")
|
|
if os.path.exists(req_file):
|
|
out = subprocess.check_output(
|
|
["bench", "--site", site, "list-apps"], cwd=bench_path
|
|
).decode("utf-8")
|
|
if re.search(r"\b" + app + r"\b", out):
|
|
print(f"Cannot remove, app is installed on site: {site}")
|
|
sys.exit(1)
|
|
|
|
|
|
def validate_branch():
|
|
from bench.bench import Bench
|
|
from bench.utils.app import get_current_branch
|
|
|
|
apps = Bench(".").apps
|
|
|
|
installed_apps = set(apps)
|
|
check_apps = {"frappe", "erpnext"}
|
|
intersection_apps = installed_apps.intersection(check_apps)
|
|
|
|
for app in intersection_apps:
|
|
branch = get_current_branch(app)
|
|
|
|
if branch == "master":
|
|
print(
|
|
"""'master' branch is renamed to 'version-11' since 'version-12' release.
|
|
As of January 2020, the following branches are
|
|
version Frappe ERPNext
|
|
11 version-11 version-11
|
|
12 version-12 version-12
|
|
13 version-13 version-13
|
|
14 develop develop
|
|
|
|
Please switch to new branches to get future updates.
|
|
To switch to your required branch, run the following commands: bench switch-to-branch [branch-name]"""
|
|
)
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
def cache_helper(clear=False, remove_app="", remove_key="") -> None:
|
|
can_remove = bool(remove_key or remove_app)
|
|
if not clear and not can_remove:
|
|
cache_list()
|
|
elif can_remove:
|
|
cache_remove(remove_app, remove_key)
|
|
elif clear:
|
|
cache_clear()
|
|
else:
|
|
pass # unreachable
|
|
|
|
|
|
def cache_list() -> None:
|
|
from datetime import datetime
|
|
|
|
tot_size = 0
|
|
tot_items = 0
|
|
|
|
printed_header = False
|
|
for item in get_bench_cache_path("apps").iterdir():
|
|
if item.suffix not in [".tar", ".tgz"]:
|
|
continue
|
|
|
|
stat = item.stat()
|
|
size_mb = stat.st_size / 1_000_000
|
|
created = datetime.fromtimestamp(stat.st_ctime)
|
|
accessed = datetime.fromtimestamp(stat.st_atime)
|
|
|
|
app = item.name.split("-")[0]
|
|
tot_items += 1
|
|
tot_size += stat.st_size
|
|
compressed = item.suffix == ".tgz"
|
|
|
|
if not printed_header:
|
|
click.echo(
|
|
f"{'APP':15} "
|
|
f"{'FILE':25} "
|
|
f"{'SIZE':>13} "
|
|
f"{'COMPRESSED'} "
|
|
f"{'CREATED':19} "
|
|
f"{'ACCESSED':19} "
|
|
)
|
|
printed_header = True
|
|
|
|
click.echo(
|
|
f"{app:15} "
|
|
f"{item.name:25} "
|
|
f"{size_mb:10.3f} MB "
|
|
f"{str(compressed):10} "
|
|
f"{created:%Y-%m-%d %H:%M:%S} "
|
|
f"{accessed:%Y-%m-%d %H:%M:%S} "
|
|
)
|
|
|
|
if tot_items:
|
|
click.echo(f"Total size {tot_size / 1_000_000:.3f} MB belonging to {tot_items} items")
|
|
else:
|
|
click.echo("No cached items")
|
|
|
|
|
|
def cache_remove(app: str = "", key: str = "") -> None:
|
|
rem_items = 0
|
|
rem_size = 0
|
|
for item in get_bench_cache_path("apps").iterdir():
|
|
if not should_remove_item(item, app, key):
|
|
continue
|
|
|
|
rem_items += 1
|
|
rem_size += item.stat().st_size
|
|
item.unlink(True)
|
|
click.echo(f"Removed {item.name}")
|
|
|
|
if rem_items:
|
|
click.echo(f"Cleared {rem_size / 1_000_000:.3f} MB belonging to {rem_items} items")
|
|
else:
|
|
click.echo("No items removed")
|
|
|
|
|
|
def should_remove_item(item: Path, app: str, key: str) -> bool:
|
|
if item.suffix not in [".tar", ".tgz"]:
|
|
return False
|
|
|
|
name = item.name
|
|
if app and key and name.startswith(f"{app}-{key[:10]}."):
|
|
return True
|
|
|
|
if app and name.startswith(f"{app}-"):
|
|
return True
|
|
|
|
if key and f"-{key[:10]}." in name:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def cache_clear() -> None:
|
|
cache_path = get_bench_cache_path("apps")
|
|
tot_items = len(os.listdir(cache_path))
|
|
if not tot_items:
|
|
click.echo("No cached items")
|
|
return
|
|
|
|
tot_size = get_dir_size(cache_path)
|
|
shutil.rmtree(cache_path)
|
|
|
|
if tot_items:
|
|
click.echo(f"Cleared {tot_size / 1_000_000:.3f} MB belonging to {tot_items} items")
|
|
|
|
|
|
def get_dir_size(p: Path) -> int:
|
|
return sum(i.stat(follow_symlinks=False).st_size for i in p.iterdir())
|