2020-03-09 08:23:13 +00:00
|
|
|
# imports - standard imports
|
2014-07-21 06:10:03 +00:00
|
|
|
import json
|
2021-04-01 19:42:19 +00:00
|
|
|
from json.decoder import JSONDecodeError
|
2020-03-09 08:23:13 +00:00
|
|
|
import logging
|
|
|
|
import os
|
2015-02-24 07:46:22 +00:00
|
|
|
import re
|
|
|
|
import subprocess
|
2016-07-18 05:57:05 +00:00
|
|
|
import sys
|
2015-02-24 07:46:22 +00:00
|
|
|
|
2020-03-09 08:23:13 +00:00
|
|
|
# imports - third party imports
|
2019-12-15 06:36:12 +00:00
|
|
|
import click
|
2020-06-23 08:54:14 +00:00
|
|
|
from setuptools.config import read_configuration
|
2015-02-24 07:46:22 +00:00
|
|
|
|
2020-03-09 08:23:13 +00:00
|
|
|
# imports - module imports
|
|
|
|
import bench
|
2021-10-19 19:03:34 +00:00
|
|
|
from bench.utils import color, CommandFailedError, build_assets, check_git_for_shallow_clone, exec_cmd, get_cmd_output, get_frappe, is_bench_directory, restart_supervisor_processes, restart_systemd_processes, run_frappe_cmd
|
2020-03-09 08:23:13 +00:00
|
|
|
|
|
|
|
|
2020-05-19 07:41:57 +00:00
|
|
|
logger = logging.getLogger(bench.PROJECT_NAME)
|
2014-07-10 17:21:34 +00:00
|
|
|
|
2020-03-09 08:23:13 +00:00
|
|
|
|
2016-06-08 06:41:47 +00:00
|
|
|
class InvalidBranchException(Exception): pass
|
|
|
|
class InvalidRemoteException(Exception): pass
|
|
|
|
|
2021-10-19 19:03:34 +00:00
|
|
|
|
|
|
|
def find_org(org_repo):
|
|
|
|
import requests
|
|
|
|
|
|
|
|
org_repo = org_repo[0]
|
|
|
|
|
|
|
|
for org in ["frappe", "erpnext"]:
|
|
|
|
res = requests.head(f'https://api.github.com/repos/{org}/{org_repo}')
|
|
|
|
if res.ok:
|
|
|
|
return org, org_repo
|
|
|
|
|
|
|
|
raise InvalidRemoteException
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_details_from_tag(_tag):
|
|
|
|
if not _tag:
|
|
|
|
raise Exception("Tag is not provided")
|
|
|
|
|
|
|
|
app_tag = _tag.split("@")
|
|
|
|
org_repo = app_tag[0].split("/")
|
|
|
|
|
|
|
|
try:
|
|
|
|
repo, tag = app_tag
|
|
|
|
except ValueError:
|
|
|
|
repo, tag = app_tag + [None]
|
|
|
|
|
|
|
|
try:
|
|
|
|
org, repo = org_repo
|
|
|
|
except ValueError:
|
|
|
|
org, repo = find_org(org_repo)
|
|
|
|
|
|
|
|
return org, repo, tag
|
|
|
|
|
|
|
|
|
|
|
|
class App:
|
|
|
|
def __init__(self, name: str, branch : str = None):
|
|
|
|
"""
|
|
|
|
name (str): This could look something like
|
|
|
|
1. https://github.com/frappe/healthcare.git
|
|
|
|
2. git@github.com:frappe/healthcare.git
|
|
|
|
3. frappe/healthcare@develop
|
|
|
|
4. healthcare
|
|
|
|
5. healthcare@develop, healthcare@v13.12.1
|
|
|
|
|
|
|
|
References for Version Identifiers:
|
|
|
|
* https://www.python.org/dev/peps/pep-0440/#version-specifiers
|
|
|
|
* https://docs.npmjs.com/about-semantic-versioning
|
|
|
|
|
|
|
|
class Healthcare(AppConfig):
|
|
|
|
dependencies = [{"frappe/erpnext": "~13.17.0"}]
|
|
|
|
"""
|
|
|
|
self.name = name
|
|
|
|
self.remote_server = "github.com"
|
2021-10-20 14:03:16 +00:00
|
|
|
self.on_disk = False
|
2021-10-19 19:03:34 +00:00
|
|
|
self.use_ssh = False
|
|
|
|
self.branch = branch
|
|
|
|
self.setup_details()
|
|
|
|
|
|
|
|
def setup_details(self):
|
|
|
|
# fetch meta for repo on mounted disk
|
|
|
|
if os.path.exists(self.name):
|
2021-10-20 14:03:16 +00:00
|
|
|
self.on_disk = True
|
2021-10-19 19:03:34 +00:00
|
|
|
self._setup_details_from_mounted_disk()
|
|
|
|
|
|
|
|
# fetch meta for repo from remote git server - traditional get-app url
|
|
|
|
elif is_git_url(self.name):
|
|
|
|
self._setup_details_from_git_url()
|
|
|
|
|
|
|
|
# fetch meta from new styled name tags & first party apps on github
|
|
|
|
else:
|
|
|
|
self._setup_details_from_name_tag()
|
|
|
|
|
|
|
|
def _setup_details_from_name_tag(self):
|
|
|
|
self.org, self.repo, self.tag = fetch_details_from_tag(self.name)
|
|
|
|
|
|
|
|
def _setup_details_from_mounted_disk(self):
|
2021-10-20 14:03:16 +00:00
|
|
|
self.org, self.repo, self.tag = os.path.split(self.name)[-2:] + [self.branch]
|
2021-10-19 19:03:34 +00:00
|
|
|
|
|
|
|
def _setup_details_from_git_url(self):
|
2021-10-20 14:03:16 +00:00
|
|
|
return self.__setup_details_from_git()
|
2021-10-19 19:03:34 +00:00
|
|
|
|
2021-10-20 14:03:16 +00:00
|
|
|
def __setup_details_from_git(self):
|
2021-10-19 19:03:34 +00:00
|
|
|
if self.use_ssh:
|
|
|
|
self.org, _repo = self.name.split(":")[1].split("/")
|
|
|
|
else:
|
|
|
|
self.org, _repo = self.name.split("/")[-2:]
|
|
|
|
|
|
|
|
self.tag = self.branch
|
|
|
|
self.repo = _repo.split(".")[0]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def url(self):
|
2021-10-20 14:03:16 +00:00
|
|
|
if self.on_disk:
|
|
|
|
return os.path.abspath(self.name)
|
|
|
|
|
2021-10-19 19:03:34 +00:00
|
|
|
if self.use_ssh:
|
|
|
|
return self.get_ssh_url()
|
|
|
|
|
|
|
|
return self.get_http_url()
|
|
|
|
|
|
|
|
def get_http_url(self):
|
|
|
|
return f"https://{self.remote_server}/{self.org}/{self.repo}.git"
|
|
|
|
|
|
|
|
def get_ssh_url(self):
|
|
|
|
return f"git@{self.remote_server}:{self.org}/{self.repo}.git"
|
|
|
|
|
|
|
|
|
2015-02-24 07:46:22 +00:00
|
|
|
class MajorVersionUpgradeException(Exception):
|
|
|
|
def __init__(self, message, upstream_version, local_version):
|
|
|
|
super(MajorVersionUpgradeException, self).__init__(message)
|
|
|
|
self.upstream_version = upstream_version
|
|
|
|
self.local_version = local_version
|
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def get_apps(bench_path='.'):
|
2014-07-10 17:21:34 +00:00
|
|
|
try:
|
2016-06-16 05:48:51 +00:00
|
|
|
with open(os.path.join(bench_path, 'sites', 'apps.txt')) as f:
|
2014-07-10 17:21:34 +00:00
|
|
|
return f.read().strip().split('\n')
|
|
|
|
except IOError:
|
|
|
|
return []
|
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def add_to_appstxt(app, bench_path='.'):
|
|
|
|
apps = get_apps(bench_path=bench_path)
|
2014-07-10 17:21:34 +00:00
|
|
|
if app not in apps:
|
|
|
|
apps.append(app)
|
2016-06-16 05:48:51 +00:00
|
|
|
return write_appstxt(apps, bench_path=bench_path)
|
2014-11-20 07:30:57 +00:00
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def remove_from_appstxt(app, bench_path='.'):
|
|
|
|
apps = get_apps(bench_path=bench_path)
|
2014-11-20 07:30:57 +00:00
|
|
|
if app in apps:
|
|
|
|
apps.remove(app)
|
2016-06-16 05:48:51 +00:00
|
|
|
return write_appstxt(apps, bench_path=bench_path)
|
2014-11-20 07:30:57 +00:00
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def write_appstxt(apps, bench_path='.'):
|
|
|
|
with open(os.path.join(bench_path, 'sites', 'apps.txt'), 'w') as f:
|
2014-11-20 07:30:57 +00:00
|
|
|
return f.write('\n'.join(apps))
|
2014-07-10 17:21:34 +00:00
|
|
|
|
2020-05-14 12:15:28 +00:00
|
|
|
def is_git_url(url):
|
|
|
|
# modified to allow without the tailing .git from https://github.com/jonschlinkert/is-git-url.git
|
2021-09-17 09:59:14 +00:00
|
|
|
pattern = r"(?:git|ssh|https?|\w*@[-\w.]+):(\/\/)?(.*?)(\.git)?(\/?|\#[-\d\w._]+?)$"
|
2020-05-14 14:42:26 +00:00
|
|
|
return bool(re.match(pattern, url))
|
2018-02-22 07:08:26 +00:00
|
|
|
|
2017-12-12 11:16:31 +00:00
|
|
|
def get_excluded_apps(bench_path='.'):
|
|
|
|
try:
|
|
|
|
with open(os.path.join(bench_path, 'sites', 'excluded_apps.txt')) as f:
|
|
|
|
return f.read().strip().split('\n')
|
|
|
|
except IOError:
|
|
|
|
return []
|
|
|
|
|
2018-01-20 10:51:16 +00:00
|
|
|
def add_to_excluded_apps_txt(app, bench_path='.'):
|
2018-04-13 20:38:05 +00:00
|
|
|
if app == 'frappe':
|
|
|
|
raise ValueError('Frappe app cannot be excludeed from update')
|
|
|
|
if app not in os.listdir('apps'):
|
2021-05-11 05:58:26 +00:00
|
|
|
raise ValueError(f'The app {app} does not exist')
|
2017-12-12 11:16:31 +00:00
|
|
|
apps = get_excluded_apps(bench_path=bench_path)
|
|
|
|
if app not in apps:
|
|
|
|
apps.append(app)
|
2018-01-20 10:51:16 +00:00
|
|
|
return write_excluded_apps_txt(apps, bench_path=bench_path)
|
2017-12-12 11:16:31 +00:00
|
|
|
|
2018-01-20 10:51:16 +00:00
|
|
|
def write_excluded_apps_txt(apps, bench_path='.'):
|
2017-12-12 11:16:31 +00:00
|
|
|
with open(os.path.join(bench_path, 'sites', 'excluded_apps.txt'), 'w') as f:
|
|
|
|
return f.write('\n'.join(apps))
|
|
|
|
|
2018-01-20 10:51:16 +00:00
|
|
|
def remove_from_excluded_apps_txt(app, bench_path='.'):
|
|
|
|
apps = get_excluded_apps(bench_path=bench_path)
|
2017-12-12 12:45:41 +00:00
|
|
|
if app in apps:
|
|
|
|
apps.remove(app)
|
2018-01-20 10:51:16 +00:00
|
|
|
return write_excluded_apps_txt(apps, bench_path=bench_path)
|
2017-12-12 12:45:41 +00:00
|
|
|
|
2021-10-19 19:13:46 +00:00
|
|
|
def drop_bench(bench_path):
|
|
|
|
if not os.path.exists(bench_path):
|
|
|
|
print(f"Bench {bench_path} does not exist")
|
|
|
|
return
|
|
|
|
|
|
|
|
import shutil
|
|
|
|
from bench.utils import remove_backups_crontab
|
|
|
|
|
|
|
|
sites_exist = [
|
|
|
|
x for x in os.listdir(os.path.join(bench_path, 'sites')) if x not in ('assets', 'apps.txt', 'common_site_config.json')
|
|
|
|
]
|
|
|
|
if sites_exist:
|
|
|
|
raise Exception("Cannot remove non-empty bench directory")
|
|
|
|
remove_backups_crontab(bench_path)
|
|
|
|
shutil.rmtree(bench_path)
|
|
|
|
print('Bench dropped')
|
|
|
|
|
|
|
|
def get_bench_name(git_url, bench_path):
|
2021-10-19 19:16:25 +00:00
|
|
|
if os.path.exists(git_url):
|
|
|
|
guessed_app_name = os.path.basename(git_url)
|
|
|
|
else:
|
|
|
|
app = App(git_url)
|
|
|
|
guessed_app_name = f"{app.org}_{app.repo}"
|
|
|
|
|
|
|
|
return os.path.join(bench_path, f"{guessed_app_name}-bench")
|
2021-10-19 19:13:46 +00:00
|
|
|
|
2020-05-19 12:53:59 +00:00
|
|
|
def get_app(git_url, branch=None, bench_path='.', skip_assets=False, verbose=False, restart_bench=True, overwrite=False):
|
2021-02-21 05:42:00 +00:00
|
|
|
import shutil
|
2021-02-20 07:44:56 +00:00
|
|
|
|
2021-10-19 19:16:25 +00:00
|
|
|
if not is_bench_directory(bench_path):
|
|
|
|
bench_name = get_bench_name(git_url, bench_path)
|
|
|
|
from bench.commands.make import init
|
|
|
|
|
|
|
|
click.get_current_context().invoke(init, path=bench_name)
|
|
|
|
bench_path = bench_name
|
|
|
|
|
2020-03-09 12:17:43 +00:00
|
|
|
if not os.path.exists(git_url):
|
2021-10-19 19:16:25 +00:00
|
|
|
app = App(git_url)
|
|
|
|
|
|
|
|
git_url = app.url
|
|
|
|
repo_name = app.repo
|
2020-03-09 12:17:43 +00:00
|
|
|
shallow_clone = '--depth 1' if check_git_for_shallow_clone() else ''
|
2021-10-19 19:16:25 +00:00
|
|
|
branch = f'--branch {app.tag}' if app.tag else ''
|
|
|
|
|
2020-03-09 12:17:43 +00:00
|
|
|
else:
|
2021-03-15 20:52:34 +00:00
|
|
|
git_url = os.path.abspath(git_url)
|
|
|
|
_, repo_name = os.path.split(git_url)
|
2020-03-09 12:17:43 +00:00
|
|
|
shallow_clone = ''
|
2021-05-11 05:58:26 +00:00
|
|
|
branch = f'--branch {branch}' if branch else ''
|
2016-04-27 13:51:46 +00:00
|
|
|
|
2021-10-19 19:16:25 +00:00
|
|
|
cloned_path = os.path.join(bench_path, 'apps', repo_name)
|
|
|
|
|
|
|
|
if os.path.isdir(cloned_path):
|
2019-12-15 06:36:12 +00:00
|
|
|
# application directory already exists
|
|
|
|
# prompt user to overwrite it
|
2021-05-11 05:58:26 +00:00
|
|
|
if overwrite or click.confirm(f'''A directory for the application "{repo_name}" already exists.
|
|
|
|
Do you want to continue and overwrite it?'''):
|
2021-10-19 19:16:25 +00:00
|
|
|
shutil.rmtree(cloned_path)
|
2019-12-15 06:36:12 +00:00
|
|
|
elif click.confirm('''Do you want to reinstall the existing application?''', abort=True):
|
|
|
|
app_name = get_app_name(bench_path, repo_name)
|
2021-10-19 19:16:25 +00:00
|
|
|
install_app(
|
|
|
|
app=app_name, bench_path=bench_path, verbose=verbose, skip_assets=skip_assets
|
|
|
|
)
|
2020-02-25 06:15:03 +00:00
|
|
|
sys.exit()
|
2019-12-15 06:36:12 +00:00
|
|
|
|
2021-05-11 05:58:26 +00:00
|
|
|
print(f'\n{color.yellow}Getting {repo_name}{color.nc}')
|
|
|
|
logger.log(f'Getting app {repo_name}')
|
2021-10-19 19:16:25 +00:00
|
|
|
|
|
|
|
exec_cmd(
|
|
|
|
f"git clone {git_url} {branch} {shallow_clone} --origin upstream",
|
|
|
|
cwd=os.path.join(bench_path, 'apps')
|
|
|
|
)
|
2016-05-04 09:07:10 +00:00
|
|
|
|
2019-12-15 06:36:12 +00:00
|
|
|
app_name = get_app_name(bench_path, repo_name)
|
2021-10-19 19:16:25 +00:00
|
|
|
install_app(
|
|
|
|
app=app_name, bench_path=bench_path, verbose=verbose, skip_assets=skip_assets
|
|
|
|
)
|
2019-12-15 06:36:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_app_name(bench_path, repo_name):
|
2020-06-23 08:54:14 +00:00
|
|
|
app_name = None
|
|
|
|
apps_path = os.path.join(os.path.abspath(bench_path), 'apps')
|
|
|
|
config_path = os.path.join(apps_path, repo_name, 'setup.cfg')
|
|
|
|
if os.path.exists(config_path):
|
|
|
|
config = read_configuration(config_path)
|
|
|
|
app_name = config.get('metadata', {}).get('name')
|
|
|
|
|
|
|
|
if not app_name:
|
|
|
|
# retrieve app name from setup.py as fallback
|
|
|
|
app_path = os.path.join(apps_path, repo_name, 'setup.py')
|
|
|
|
with open(app_path, 'rb') as f:
|
|
|
|
app_name = re.search(r'name\s*=\s*[\'"](.*)[\'"]', f.read().decode('utf-8')).group(1)
|
|
|
|
|
|
|
|
if app_name and repo_name != app_name:
|
|
|
|
os.rename(os.path.join(apps_path, repo_name), os.path.join(apps_path, app_name))
|
2020-09-10 06:14:45 +00:00
|
|
|
return app_name
|
2020-06-23 08:54:14 +00:00
|
|
|
|
2020-09-10 06:14:45 +00:00
|
|
|
return repo_name
|
2016-05-04 09:07:10 +00:00
|
|
|
|
2014-07-10 17:21:34 +00:00
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def new_app(app, bench_path='.'):
|
2016-04-29 10:06:46 +00:00
|
|
|
# For backwards compatibility
|
2016-05-18 11:50:47 +00:00
|
|
|
app = app.lower().replace(" ", "_").replace("-", "_")
|
2021-05-11 05:58:26 +00:00
|
|
|
logger.log(f'creating new app {app}')
|
2016-06-16 05:48:51 +00:00
|
|
|
apps = os.path.abspath(os.path.join(bench_path, 'apps'))
|
2021-04-13 07:36:08 +00:00
|
|
|
run_frappe_cmd('make-app', apps, app, bench_path=bench_path)
|
2016-06-16 05:48:51 +00:00
|
|
|
install_app(app, bench_path=bench_path)
|
2014-07-10 17:21:34 +00:00
|
|
|
|
2019-12-15 06:36:12 +00:00
|
|
|
|
2020-05-19 12:53:59 +00:00
|
|
|
def install_app(app, bench_path=".", verbose=False, no_cache=False, restart_bench=True, skip_assets=False):
|
2021-02-21 04:36:44 +00:00
|
|
|
from bench.config.common_site_config import get_config
|
|
|
|
|
2021-05-11 05:58:26 +00:00
|
|
|
print(f'\n{color.yellow}Installing {app}{color.nc}')
|
|
|
|
logger.log(f"installing {app}")
|
2019-11-17 10:36:24 +00:00
|
|
|
|
2021-04-23 13:07:03 +00:00
|
|
|
python_path = os.path.join(bench_path, "env", "bin", "python")
|
2019-11-17 10:36:24 +00:00
|
|
|
quiet_flag = "-q" if not verbose else ""
|
|
|
|
app_path = os.path.join(bench_path, "apps", app)
|
|
|
|
cache_flag = "--no-cache-dir" if no_cache else ""
|
|
|
|
|
2021-05-11 05:58:26 +00:00
|
|
|
exec_cmd(f"{python_path} -m pip install {quiet_flag} -U -e {app_path} {cache_flag}")
|
2020-05-06 06:24:34 +00:00
|
|
|
|
|
|
|
if os.path.exists(os.path.join(app_path, 'package.json')):
|
|
|
|
exec_cmd("yarn install", cwd=app_path)
|
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
add_to_appstxt(app, bench_path=bench_path)
|
2014-07-10 17:21:34 +00:00
|
|
|
|
2021-09-13 15:25:21 +00:00
|
|
|
conf = get_config(bench_path=bench_path)
|
|
|
|
|
|
|
|
if conf.get("developer_mode"):
|
|
|
|
from bench.utils import install_python_dev_dependencies
|
|
|
|
install_python_dev_dependencies(apps=app)
|
|
|
|
|
2020-05-19 12:47:38 +00:00
|
|
|
if not skip_assets:
|
|
|
|
build_assets(bench_path=bench_path, app=app)
|
|
|
|
|
2020-05-19 12:53:59 +00:00
|
|
|
if restart_bench:
|
2019-12-15 06:36:12 +00:00
|
|
|
if conf.get('restart_supervisor_on_update'):
|
|
|
|
restart_supervisor_processes(bench_path=bench_path)
|
|
|
|
if conf.get('restart_systemd_on_update'):
|
|
|
|
restart_systemd_processes(bench_path=bench_path)
|
|
|
|
|
|
|
|
|
2016-07-18 05:57:05 +00:00
|
|
|
def remove_app(app, bench_path='.'):
|
2021-02-21 05:42:00 +00:00
|
|
|
import shutil
|
2021-02-21 04:36:44 +00:00
|
|
|
from bench.config.common_site_config import get_config
|
|
|
|
|
2021-04-01 19:42:19 +00:00
|
|
|
app_path = os.path.join(bench_path, 'apps', app)
|
|
|
|
py = os.path.join(bench_path, 'env', 'bin', 'python')
|
|
|
|
|
|
|
|
# validate app removal
|
2019-12-15 06:36:12 +00:00
|
|
|
if app not in get_apps(bench_path):
|
2021-05-11 05:58:26 +00:00
|
|
|
print(f"No app named {app}")
|
2016-07-18 05:57:05 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
2021-04-01 19:42:19 +00:00
|
|
|
validate_app_installed_on_sites(app, bench_path=bench_path)
|
|
|
|
|
|
|
|
# remove app from bench
|
|
|
|
exec_cmd("{0} -m pip uninstall -y {1}".format(py, app), cwd=bench_path)
|
|
|
|
remove_from_appstxt(app, bench_path)
|
|
|
|
shutil.rmtree(app_path)
|
|
|
|
|
|
|
|
# re-build assets and restart processes
|
|
|
|
run_frappe_cmd("build", bench_path=bench_path)
|
|
|
|
if get_config(bench_path).get('restart_supervisor_on_update'):
|
|
|
|
restart_supervisor_processes(bench_path=bench_path)
|
|
|
|
if get_config(bench_path).get('restart_systemd_on_update'):
|
|
|
|
restart_systemd_processes(bench_path=bench_path)
|
|
|
|
|
|
|
|
|
|
|
|
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="."):
|
2021-05-10 13:40:06 +00:00
|
|
|
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
|
2021-04-01 19:42:19 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
apps_sites_dict = json.loads(out)
|
|
|
|
except JSONDecodeError:
|
|
|
|
return None
|
|
|
|
|
|
|
|
for site, apps in apps_sites_dict.items():
|
|
|
|
if app in apps:
|
|
|
|
print("Cannot remove, app is installed on site: {0}".format(site))
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
def check_app_installed_legacy(app, bench_path="."):
|
2016-07-18 05:57:05 +00:00
|
|
|
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):
|
2018-11-12 08:21:11 +00:00
|
|
|
out = subprocess.check_output(["bench", "--site", site, "list-apps"], cwd=bench_path).decode('utf-8')
|
2016-07-18 05:57:05 +00:00
|
|
|
if re.search(r'\b' + app + r'\b', out):
|
2021-05-11 05:58:26 +00:00
|
|
|
print(f"Cannot remove, app is installed on site: {site}")
|
2016-07-18 05:57:05 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
2020-02-10 09:31:12 +00:00
|
|
|
def pull_apps(apps=None, bench_path='.', reset=False):
|
2017-04-03 07:25:42 +00:00
|
|
|
'''Check all apps if there no local changes, pull'''
|
2021-02-21 04:36:44 +00:00
|
|
|
from bench.config.common_site_config import get_config
|
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
rebase = '--rebase' if get_config(bench_path).get('rebase_on_pull') else ''
|
2018-02-22 07:08:26 +00:00
|
|
|
|
2020-02-10 09:31:12 +00:00
|
|
|
apps = apps or get_apps(bench_path=bench_path)
|
2021-03-10 18:45:47 +00:00
|
|
|
# check for local changes
|
2017-04-07 08:03:25 +00:00
|
|
|
if not reset:
|
2020-02-10 09:31:12 +00:00
|
|
|
for app in apps:
|
2017-12-12 11:16:31 +00:00
|
|
|
excluded_apps = get_excluded_apps()
|
2018-04-13 20:38:05 +00:00
|
|
|
if app in excluded_apps:
|
2021-05-11 05:58:26 +00:00
|
|
|
print(f"Skipping reset for app {app}")
|
2017-12-12 09:10:23 +00:00
|
|
|
continue
|
2017-04-07 08:03:25 +00:00
|
|
|
app_dir = get_repo_dir(app, bench_path=bench_path)
|
|
|
|
if os.path.exists(os.path.join(app_dir, '.git')):
|
2021-03-10 19:47:48 +00:00
|
|
|
out = subprocess.check_output('git status', shell=True, cwd=app_dir)
|
2018-03-12 12:55:00 +00:00
|
|
|
out = out.decode('utf-8')
|
2017-04-07 08:03:25 +00:00
|
|
|
if not re.search(r'nothing to commit, working (directory|tree) clean', out):
|
2021-05-11 05:58:26 +00:00
|
|
|
print(f'''
|
2017-04-03 07:41:51 +00:00
|
|
|
|
2021-05-11 05:58:26 +00:00
|
|
|
Cannot proceed with update: You have local changes in app "{app}" that are not committed.
|
2017-04-03 07:41:51 +00:00
|
|
|
|
|
|
|
Here are your choices:
|
2017-04-03 07:25:42 +00:00
|
|
|
|
2021-05-11 05:58:26 +00:00
|
|
|
1. Merge the {app} app manually with "git pull" / "git pull --rebase" and fix conflicts.
|
2017-04-03 07:41:51 +00:00
|
|
|
1. Temporarily remove your changes with "git stash" or discard them completely
|
2017-04-07 08:03:25 +00:00
|
|
|
with "bench update --reset" or for individual repositries "git reset --hard"
|
2017-04-03 07:41:51 +00:00
|
|
|
2. If your changes are helpful for others, send in a pull request via GitHub and
|
2021-05-11 05:58:26 +00:00
|
|
|
wait for them to be merged in the core.''')
|
2017-04-07 11:48:21 +00:00
|
|
|
sys.exit(1)
|
2017-04-03 07:25:42 +00:00
|
|
|
|
2018-05-15 10:48:02 +00:00
|
|
|
excluded_apps = get_excluded_apps()
|
2020-02-10 09:31:12 +00:00
|
|
|
for app in apps:
|
2018-04-13 20:38:05 +00:00
|
|
|
if app in excluded_apps:
|
2021-05-11 05:58:26 +00:00
|
|
|
print(f"Skipping pull for app {app}")
|
2017-12-12 11:16:31 +00:00
|
|
|
continue
|
2016-06-16 05:48:51 +00:00
|
|
|
app_dir = get_repo_dir(app, bench_path=bench_path)
|
2014-07-10 17:21:34 +00:00
|
|
|
if os.path.exists(os.path.join(app_dir, '.git')):
|
2016-08-28 12:50:48 +00:00
|
|
|
remote = get_remote(app)
|
2018-05-15 10:48:02 +00:00
|
|
|
if not remote:
|
|
|
|
# remote is False, i.e. remote doesn't exist, add the app to excluded_apps.txt
|
|
|
|
add_to_excluded_apps_txt(app, bench_path=bench_path)
|
2021-05-11 05:58:26 +00:00
|
|
|
print(f"Skipping pull for app {app}, since remote doesn't exist, and adding it to excluded apps")
|
2018-05-15 10:48:02 +00:00
|
|
|
continue
|
2021-05-10 09:15:46 +00:00
|
|
|
|
|
|
|
if not get_config(bench_path).get('shallow_clone') or not reset:
|
|
|
|
is_shallow = os.path.exists(os.path.join(app_dir, ".git", "shallow"))
|
|
|
|
if is_shallow:
|
|
|
|
s = " to safely pull remote changes." if not reset else ""
|
|
|
|
print(f"Unshallowing {app}{s}")
|
|
|
|
exec_cmd(f"git fetch {remote} --unshallow", cwd=app_dir)
|
|
|
|
|
2021-03-10 19:47:48 +00:00
|
|
|
branch = get_current_branch(app, bench_path=bench_path)
|
2021-05-11 05:58:26 +00:00
|
|
|
logger.log(f'pulling {app}')
|
2016-11-07 11:53:25 +00:00
|
|
|
if reset:
|
2021-05-11 05:58:26 +00:00
|
|
|
reset_cmd = f"git reset --hard {remote}/{branch}"
|
2021-03-04 23:18:42 +00:00
|
|
|
if get_config(bench_path).get('shallow_clone'):
|
2021-05-11 05:58:26 +00:00
|
|
|
exec_cmd(f"git fetch --depth=1 --no-tags {remote} {branch}",
|
2021-03-10 19:47:48 +00:00
|
|
|
cwd=app_dir)
|
2021-03-04 23:18:42 +00:00
|
|
|
exec_cmd(reset_cmd, cwd=app_dir)
|
2021-03-10 18:45:47 +00:00
|
|
|
exec_cmd("git reflog expire --all", cwd=app_dir)
|
|
|
|
exec_cmd("git gc --prune=all", cwd=app_dir)
|
2021-03-04 23:18:42 +00:00
|
|
|
else:
|
|
|
|
exec_cmd("git fetch --all", cwd=app_dir)
|
|
|
|
exec_cmd(reset_cmd, cwd=app_dir)
|
2016-11-07 11:53:25 +00:00
|
|
|
else:
|
2021-05-11 05:58:26 +00:00
|
|
|
exec_cmd(f"git pull {rebase} {remote} {branch}", cwd=app_dir)
|
2016-05-18 11:50:47 +00:00
|
|
|
exec_cmd('find . -name "*.pyc" -delete', cwd=app_dir)
|
|
|
|
|
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def is_version_upgrade(app='frappe', bench_path='.', branch=None):
|
|
|
|
upstream_version = get_upstream_version(app=app, branch=branch, bench_path=bench_path)
|
2015-02-24 07:46:22 +00:00
|
|
|
|
|
|
|
if not upstream_version:
|
2021-05-11 05:58:26 +00:00
|
|
|
raise InvalidBranchException(f'Specified branch of app {app} is not in upstream remote')
|
2015-02-24 07:46:22 +00:00
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
local_version = get_major_version(get_current_version(app, bench_path=bench_path))
|
2015-02-24 07:46:22 +00:00
|
|
|
upstream_version = get_major_version(upstream_version)
|
2017-12-12 11:16:31 +00:00
|
|
|
|
2021-03-10 18:45:47 +00:00
|
|
|
if upstream_version > local_version:
|
2015-08-17 10:28:01 +00:00
|
|
|
return (True, local_version, upstream_version)
|
|
|
|
|
|
|
|
return (False, local_version, upstream_version)
|
2015-02-24 07:46:22 +00:00
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def get_current_frappe_version(bench_path='.'):
|
2015-03-03 11:09:07 +00:00
|
|
|
try:
|
2016-06-16 05:48:51 +00:00
|
|
|
return get_major_version(get_current_version('frappe', bench_path=bench_path))
|
2015-03-03 11:09:07 +00:00
|
|
|
except IOError:
|
2016-04-27 13:51:46 +00:00
|
|
|
return 0
|
2015-02-24 09:40:30 +00:00
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def get_current_branch(app, bench_path='.'):
|
|
|
|
repo_dir = get_repo_dir(app, bench_path=bench_path)
|
2014-10-30 07:08:02 +00:00
|
|
|
return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir)
|
2014-07-21 06:10:03 +00:00
|
|
|
|
2016-08-28 12:50:48 +00:00
|
|
|
def get_remote(app, bench_path='.'):
|
|
|
|
repo_dir = get_repo_dir(app, bench_path=bench_path)
|
2019-12-15 06:36:12 +00:00
|
|
|
contents = subprocess.check_output(['git', 'remote', '-v'], cwd=repo_dir, stderr=subprocess.STDOUT)
|
2018-03-12 12:55:00 +00:00
|
|
|
contents = contents.decode('utf-8')
|
2016-08-28 12:50:48 +00:00
|
|
|
if re.findall('upstream[\s]+', contents):
|
2018-05-15 10:48:02 +00:00
|
|
|
return 'upstream'
|
|
|
|
elif not contents:
|
|
|
|
# if contents is an empty string => remote doesn't exist
|
|
|
|
return False
|
2016-08-28 12:50:48 +00:00
|
|
|
else:
|
|
|
|
# get the first remote
|
2018-05-15 10:48:02 +00:00
|
|
|
return contents.splitlines()[0].split()[0]
|
2016-08-28 12:50:48 +00:00
|
|
|
|
2016-05-05 10:53:00 +00:00
|
|
|
def use_rq(bench_path):
|
|
|
|
bench_path = os.path.abspath(bench_path)
|
|
|
|
celery_app = os.path.join(bench_path, 'apps', 'frappe', 'frappe', 'celery_app.py')
|
|
|
|
return not os.path.exists(celery_app)
|
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def get_current_version(app, bench_path='.'):
|
2020-06-23 08:54:14 +00:00
|
|
|
current_version = None
|
2016-06-16 05:48:51 +00:00
|
|
|
repo_dir = get_repo_dir(app, bench_path=bench_path)
|
2020-09-10 06:19:03 +00:00
|
|
|
config_path = os.path.join(repo_dir, "setup.cfg")
|
|
|
|
init_path = os.path.join(repo_dir, os.path.basename(repo_dir), '__init__.py')
|
|
|
|
setup_path = os.path.join(repo_dir, 'setup.py')
|
|
|
|
|
2016-06-09 12:56:46 +00:00
|
|
|
try:
|
2020-06-23 08:54:14 +00:00
|
|
|
if os.path.exists(config_path):
|
|
|
|
config = read_configuration(config_path)
|
|
|
|
current_version = config.get("metadata", {}).get("version")
|
|
|
|
if not current_version:
|
2020-09-10 06:19:03 +00:00
|
|
|
with open(init_path) as f:
|
2020-06-23 08:54:14 +00:00
|
|
|
current_version = get_version_from_string(f.read())
|
2016-06-09 12:56:46 +00:00
|
|
|
|
|
|
|
except AttributeError:
|
|
|
|
# backward compatibility
|
2020-09-10 06:19:03 +00:00
|
|
|
with open(setup_path) as f:
|
2020-06-23 08:54:14 +00:00
|
|
|
current_version = get_version_from_string(f.read(), field='version')
|
|
|
|
|
|
|
|
return current_version
|
2015-02-24 07:46:22 +00:00
|
|
|
|
2018-02-28 13:18:11 +00:00
|
|
|
def get_develop_version(app, bench_path='.'):
|
|
|
|
repo_dir = get_repo_dir(app, bench_path=bench_path)
|
|
|
|
with open(os.path.join(repo_dir, os.path.basename(repo_dir), 'hooks.py')) as f:
|
|
|
|
return get_version_from_string(f.read(), field='develop_version')
|
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def get_upstream_version(app, branch=None, bench_path='.'):
|
|
|
|
repo_dir = get_repo_dir(app, bench_path=bench_path)
|
2015-05-04 04:12:25 +00:00
|
|
|
if not branch:
|
2016-06-16 05:48:51 +00:00
|
|
|
branch = get_current_branch(app, bench_path=bench_path)
|
2021-03-10 19:58:55 +00:00
|
|
|
|
2021-03-10 18:45:47 +00:00
|
|
|
try:
|
2021-05-11 05:58:26 +00:00
|
|
|
subprocess.call(f'git fetch --depth=1 --no-tags upstream {branch}', shell=True, cwd=repo_dir)
|
2021-03-10 18:45:47 +00:00
|
|
|
except CommandFailedError:
|
2021-05-11 05:58:26 +00:00
|
|
|
raise InvalidRemoteException(f'Failed to fetch from remote named upstream for {app}')
|
2021-03-10 18:45:47 +00:00
|
|
|
|
2015-02-24 07:46:22 +00:00
|
|
|
try:
|
2021-05-11 05:58:26 +00:00
|
|
|
contents = subprocess.check_output(f'git show upstream/{branch}:{app}/__init__.py',
|
2021-03-10 18:45:47 +00:00
|
|
|
shell=True, cwd=repo_dir, stderr=subprocess.STDOUT)
|
2018-03-12 12:55:00 +00:00
|
|
|
contents = contents.decode('utf-8')
|
2017-04-17 11:58:18 +00:00
|
|
|
except subprocess.CalledProcessError as e:
|
2018-02-15 07:15:05 +00:00
|
|
|
if b"Invalid object" in e.output:
|
2015-02-24 07:46:22 +00:00
|
|
|
return None
|
|
|
|
else:
|
|
|
|
raise
|
|
|
|
return get_version_from_string(contents)
|
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def get_repo_dir(app, bench_path='.'):
|
|
|
|
return os.path.join(bench_path, 'apps', app)
|
2015-07-04 08:36:53 +00:00
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def switch_branch(branch, apps=None, bench_path='.', upgrade=False, check_upgrade=True):
|
2021-02-20 07:52:56 +00:00
|
|
|
import git
|
2021-05-11 06:25:46 +00:00
|
|
|
import importlib
|
2020-03-09 08:23:13 +00:00
|
|
|
from bench.utils import update_requirements, update_node_packages, backup_all_sites, patch_sites, build_assets, post_upgrade
|
2021-05-11 06:25:46 +00:00
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
apps_dir = os.path.join(bench_path, 'apps')
|
2016-04-28 11:56:28 +00:00
|
|
|
version_upgrade = (False,)
|
|
|
|
switched_apps = []
|
2015-05-04 04:12:25 +00:00
|
|
|
|
2015-03-17 03:28:49 +00:00
|
|
|
if not apps:
|
2016-04-28 11:56:28 +00:00
|
|
|
apps = [name for name in os.listdir(apps_dir)
|
|
|
|
if os.path.isdir(os.path.join(apps_dir, name))]
|
2015-09-03 05:58:34 +00:00
|
|
|
if branch=="v4.x.x":
|
|
|
|
apps.append('shopping_cart')
|
2016-06-08 06:41:47 +00:00
|
|
|
|
2015-03-17 03:28:49 +00:00
|
|
|
for app in apps:
|
|
|
|
app_dir = os.path.join(apps_dir, app)
|
2020-03-09 08:23:13 +00:00
|
|
|
|
|
|
|
if not os.path.exists(app_dir):
|
2021-05-11 05:58:26 +00:00
|
|
|
bench.utils.log(f"{app} does not exist!", level=2)
|
2020-03-09 08:23:13 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
repo = git.Repo(app_dir)
|
|
|
|
unshallow_flag = os.path.exists(os.path.join(app_dir, ".git", "shallow"))
|
2021-05-11 05:58:26 +00:00
|
|
|
bench.utils.log(f"Fetching upstream {'unshallow ' if unshallow_flag else ''}for {app}")
|
2020-03-09 08:23:13 +00:00
|
|
|
|
|
|
|
bench.utils.exec_cmd("git remote set-branches upstream '*'", cwd=app_dir)
|
2021-05-11 05:58:26 +00:00
|
|
|
bench.utils.exec_cmd(f"git fetch --all{' --unshallow' if unshallow_flag else ''} --quiet", cwd=app_dir)
|
2020-03-09 08:23:13 +00:00
|
|
|
|
|
|
|
if check_upgrade:
|
|
|
|
version_upgrade = is_version_upgrade(app=app, bench_path=bench_path, branch=branch)
|
|
|
|
if version_upgrade[0] and not upgrade:
|
2021-05-11 05:58:26 +00:00
|
|
|
bench.utils.log(f"Switching to {branch} will cause upgrade from {version_upgrade[1]} to {version_upgrade[2]}. Pass --upgrade to confirm", level=2)
|
2020-03-09 08:23:13 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
print("Switching for "+app)
|
2021-05-11 05:58:26 +00:00
|
|
|
bench.utils.exec_cmd(f"git checkout -f {branch}", cwd=app_dir)
|
2020-03-09 08:23:13 +00:00
|
|
|
|
|
|
|
if str(repo.active_branch) == branch:
|
|
|
|
switched_apps.append(app)
|
|
|
|
else:
|
2021-05-11 05:58:26 +00:00
|
|
|
bench.utils.log(f"Switching branches failed for: {app}", level=2)
|
2016-04-28 11:56:28 +00:00
|
|
|
|
|
|
|
if switched_apps:
|
2020-03-09 08:23:13 +00:00
|
|
|
bench.utils.log("Successfully switched branches for: " + ", ".join(switched_apps), level=1)
|
|
|
|
print('Please run `bench update --patch` to be safe from any differences in database schema')
|
2015-03-17 03:28:49 +00:00
|
|
|
|
2015-08-17 10:28:01 +00:00
|
|
|
if version_upgrade[0] and upgrade:
|
2015-05-04 04:12:25 +00:00
|
|
|
update_requirements()
|
2018-02-22 07:08:26 +00:00
|
|
|
update_node_packages()
|
2021-05-11 06:25:46 +00:00
|
|
|
importlib.reload(bench.utils)
|
2015-05-04 04:12:25 +00:00
|
|
|
backup_all_sites()
|
|
|
|
patch_sites()
|
|
|
|
build_assets()
|
2015-08-17 10:28:01 +00:00
|
|
|
post_upgrade(version_upgrade[1], version_upgrade[2])
|
2015-05-04 04:12:25 +00:00
|
|
|
|
2020-03-09 08:23:13 +00:00
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def switch_to_branch(branch=None, apps=None, bench_path='.', upgrade=False):
|
|
|
|
switch_branch(branch, apps=apps, bench_path=bench_path, upgrade=upgrade)
|
2016-04-28 11:56:28 +00:00
|
|
|
|
2017-09-27 08:12:43 +00:00
|
|
|
def switch_to_develop(apps=None, bench_path='.', upgrade=True):
|
2016-06-16 05:48:51 +00:00
|
|
|
switch_branch('develop', apps=apps, bench_path=bench_path, upgrade=upgrade)
|
2015-03-17 11:20:43 +00:00
|
|
|
|
2016-06-09 13:24:41 +00:00
|
|
|
def get_version_from_string(contents, field='__version__'):
|
2019-12-15 06:36:12 +00:00
|
|
|
match = re.search(r"^(\s*%s\s*=\s*['\\\"])(.+?)(['\"])(?sm)" % field, contents)
|
2015-02-24 07:46:22 +00:00
|
|
|
return match.group(2)
|
|
|
|
|
|
|
|
def get_major_version(version):
|
2021-02-21 05:42:40 +00:00
|
|
|
import semantic_version
|
|
|
|
|
2015-02-24 07:46:22 +00:00
|
|
|
return semantic_version.Version(version).major
|
|
|
|
|
2016-06-16 05:48:51 +00:00
|
|
|
def install_apps_from_path(path, bench_path='.'):
|
2014-08-01 11:11:05 +00:00
|
|
|
apps = get_apps_json(path)
|
|
|
|
for app in apps:
|
2019-12-20 11:50:28 +00:00
|
|
|
get_app(app['url'], branch=app.get('branch'), bench_path=bench_path, skip_assets=True)
|
2014-07-21 06:10:03 +00:00
|
|
|
|
2014-08-01 11:11:05 +00:00
|
|
|
def get_apps_json(path):
|
2021-02-20 07:44:56 +00:00
|
|
|
import requests
|
|
|
|
|
2014-07-21 06:10:03 +00:00
|
|
|
if path.startswith('http'):
|
|
|
|
r = requests.get(path)
|
|
|
|
return r.json()
|
2019-10-21 10:18:38 +00:00
|
|
|
|
|
|
|
with open(path) as f:
|
|
|
|
return json.load(f)
|
2019-07-22 10:40:28 +00:00
|
|
|
|
|
|
|
def validate_branch():
|
2020-01-17 14:12:05 +00:00
|
|
|
installed_apps = set(get_apps())
|
|
|
|
check_apps = set(['frappe', 'erpnext'])
|
2020-01-21 08:57:00 +00:00
|
|
|
intersection_apps = installed_apps.intersection(check_apps)
|
2020-01-17 14:12:05 +00:00
|
|
|
|
2020-01-17 02:33:12 +00:00
|
|
|
for app in intersection_apps:
|
2019-07-22 10:40:28 +00:00
|
|
|
branch = get_current_branch(app)
|
|
|
|
|
|
|
|
if branch == "master":
|
2020-01-17 14:12:05 +00:00
|
|
|
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
|
2021-08-30 06:59:09 +00:00
|
|
|
13 version-13 version-13
|
|
|
|
14 develop develop
|
2020-01-17 14:12:05 +00:00
|
|
|
|
|
|
|
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]""")
|
2019-07-22 10:40:28 +00:00
|
|
|
|
2020-01-17 02:33:12 +00:00
|
|
|
sys.exit(1)
|