From d41e05e24ffff934ef71a2aa10d91f975123cccd Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Wed, 19 Jan 2022 13:43:58 +0530 Subject: [PATCH 001/108] feat: introduced simple resolver --- bench/app.py | 37 ++++++++++++++++++++++++++++++++++++- bench/utils/app.py | 6 ++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index 0dc2fba7..fd9b16dc 100755 --- a/bench/app.py +++ b/bench/app.py @@ -1,4 +1,5 @@ # imports - standard imports +from collections import OrderedDict import functools import json import logging @@ -46,6 +47,7 @@ class AppMeta: 3. frappe/healthcare@develop 4. healthcare 5. healthcare@develop, healthcare@v13.12.1 + 6. erpnext References for Version Identifiers: * https://www.python.org/dev/peps/pep-0440/#version-specifiers @@ -91,6 +93,7 @@ class AppMeta: self._setup_details_from_name_tag() def _setup_details_from_mounted_disk(self): + self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split(self.name)[-2:] + (self.branch,) def _setup_details_from_name_tag(self): @@ -98,6 +101,7 @@ class AppMeta: self.tag = self.tag or self.branch def _setup_details_from_installed_apps(self): + self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split( os.path.join(self.bench.name, "apps", self.name) )[-2:] + (self.branch,) @@ -197,6 +201,35 @@ class App(AppMeta): def uninstall(self): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") + def _get_dependencies(self): + from bench.utils.app import get_required_deps_url + import toml + + info_file = None + required_url = get_required_deps_url(self.url, self.tag) + print(required_url) + try: + info_file = toml.loads(requests.get(required_url).text) + except Exception: + click.echo("\nNo toml file found \n", err=True) + + return info_file["required_apps"] if info_file else {} + +def make_resolution_plan(app: App, bench): + """ + decide what apps and versions to install and in what order + """ + resolution = OrderedDict() + resolution[app.repo] = app + for app_name, branch in app._get_dependencies().items(): + dep_app = App(app_name, branch=branch, bench=bench) + resolution[dep_app.repo] = dep_app + resolution.update(make_resolution_plan(dep_app, bench)) + if app_name in resolution: + print("Resolve this conflict!") + return resolution + + def add_to_appstxt(app, bench_path="."): from bench.bench import Bench @@ -310,6 +343,7 @@ def get_app( repo_name = app.repo branch = app.tag bench_setup = False + apps_to_install = make_resolution_plan(app, bench) if not is_bench_directory(bench_path): if not init_bench: @@ -320,8 +354,9 @@ def get_app( from bench.utils.system import init + frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init(path=bench_path, frappe_branch=branch) + init(path=bench_path, frappe_branch=frappe_app.tag) os.chdir(bench_path) bench_setup = True diff --git a/bench/utils/app.py b/bench/utils/app.py index ae980743..f7a6d5cc 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -164,6 +164,12 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) +def get_required_deps_url(git_url, branch="master", deps="info.toml"): + git_url = ( + git_url.replace(".git", "").replace("github.com", "raw.github.com") + ) + git_url += f"/{branch}/{deps}" if branch else f"/{deps}" + return git_url def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) From 7a311a242d574396df82a06cec1dc5b491a26bd8 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 19 Jan 2022 23:50:35 +0530 Subject: [PATCH 002/108] feat: Added bench command for resolve and install --- bench/commands/__init__.py | 2 ++ bench/commands/make.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/bench/commands/__init__.py b/bench/commands/__init__.py index 08a493b9..4cdc8882 100755 --- a/bench/commands/__init__.py +++ b/bench/commands/__init__.py @@ -39,6 +39,7 @@ from bench.commands.make import ( new_app, pip, remove_app, + resolve_and_install, ) bench_command.add_command(init) @@ -48,6 +49,7 @@ bench_command.add_command(new_app) bench_command.add_command(remove_app) bench_command.add_command(exclude_app_for_update) bench_command.add_command(include_app_for_update) +bench_command.add_command(resolve_and_install) bench_command.add_command(pip) diff --git a/bench/commands/make.py b/bench/commands/make.py index 88b37c61..c6b7748b 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -147,6 +147,25 @@ def get_app( init_bench=init_bench, ) +@click.command("resolve-and-install", help="Resolve dependencies and install apps") +@click.argument("git-url") +@click.option("--branch", default=None) +@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") +@click.option( + "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" +) +@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") +@click.option("--verbose", is_flag=True, default=False, help="Verbosity") +def resolve_and_install(git_url, branch, skip_assets, verbose, init_bench): + from bench.app import resolve_and_install + + resolve_and_install( + git_url=git_url, + branch=branch, + skip_assets=skip_assets, + init_bench=init_bench, + verbose=verbose, + ) @click.command("new-app", help="Create a new Frappe application under apps folder") @click.option( From 549e8e2a1d6fd59fb87fc30dbdfab1322f8f4a19 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 20 Jan 2022 15:04:43 +0530 Subject: [PATCH 003/108] feat: Added app states --- bench/app.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ bench/bench.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/bench/app.py b/bench/app.py index fd9b16dc..f4ea84ec 100755 --- a/bench/app.py +++ b/bench/app.py @@ -13,6 +13,7 @@ from datetime import date # imports - third party imports import click +import requests # imports - module imports import bench @@ -197,6 +198,12 @@ class App(AppMeta): app=app_name, bench_path=self.bench.name, verbose=verbose, skip_assets=skip_assets, ) + @step(title="Cloning and installing {repo}", success="App {repo} Installed") + def install_resolved_apps(self, *args, **kwargs): + self.get() + self.install(*args, **kwargs, resolved=True) + self.bench.apps.update_apps_states(self.repo, self.tag) + @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") @@ -395,6 +402,49 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) +def resolve_and_install( + git_url, + branch=None, + bench_path=".", + skip_assets=False, + verbose=False, + init_bench=False, +): + from bench.cli import Bench + from bench.utils.system import init + from bench.utils.app import check_existing_dir + + bench = Bench(bench_path) + app = App(git_url, branch=branch, bench=bench) + + resolution = make_resolution_plan(app, bench) + if "frappe" in resolution: + # Terminal dependency + del resolution["frappe"] + + if init_bench: + bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) + init( + path=bench_path, + frappe_branch=branch, + skip_assets=skip_assets, + verbose=verbose, + ) + os.chdir(bench_path) + + for repo_name, app in reversed(resolution.items()): + existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) + if existing_dir: + if click.confirm( + f"A directory for the application '{repo_name}' already exists. " + "Do you want to continue and overwrite it?" + ): + click.secho(f"Removing {repo_name}", fg="yellow") + shutil.rmtree(cloned_path) + app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) + + continue + app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) def new_app(app, no_git=None, bench_path="."): if bench.FRAPPE_VERSION in (0, None): diff --git a/bench/bench.py b/bench/bench.py index db04fd34..6a0081c3 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -2,6 +2,7 @@ import functools import os import shutil +import json import sys import logging from typing import List, MutableSequence, TYPE_CHECKING @@ -30,6 +31,7 @@ from bench.utils.bench import ( get_env_cmd, ) from bench.utils.render import job, step +from bench.utils.app import get_current_version if TYPE_CHECKING: @@ -55,6 +57,7 @@ class Bench(Base, Validator): def __init__(self, path): self.name = path self.cwd = os.path.abspath(path) + self.apps_states = os.path.join(self.name, "sites", "apps_states.json") self.exists = is_bench_directory(self.name) self.setup = BenchSetup(self) @@ -122,6 +125,7 @@ class Bench(Base, Validator): self.validate_app_uninstall(app) self.apps.remove(App(app, bench=self, to_clone=False)) self.apps.sync() + self.apps.update_apps_states() # self.build() - removed because it seems unnecessary self.reload() @@ -148,6 +152,38 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.initialize_apps() + self.initialize_states() + + def initialize_states(self): + try: + with open(self.bench.apps_states, "r") as f: + self.states = json.loads(f.read() or "{}") + except FileNotFoundError: + with open(self.bench.apps_states, "w") as f: + self.states = json.loads(f.read() or "{}") + + def update_apps_states(self, app_name: str = None, resolution: str = None): + # Only tracking for app state for apps installed via `bench resolve-and-install`, + # Not all states can be maintained as some apps may not be install via bench resolve-and-install + # or may not be compatible with versioning + self.initialize_apps() + apps_to_remove = [] + for app in self.states: + if app not in self.apps: + apps_to_remove.append(app) + + for app in apps_to_remove: + del self.states[app] + + if app_name and resolution: + version = get_current_version(app_name) + self.states[app_name] = { + "resolution": resolution, + "version": version, + } + + with open(self.bench.apps_states, "w") as f: + f.write(json.dumps(self.states, indent=4)) def sync(self): self.initialize_apps() From 2363fe38d5a8e83d266ba92d2de6e1bc700a4ccb Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 28 Jan 2022 21:20:14 +0530 Subject: [PATCH 004/108] refactor: moved resolve and install to get-app --- bench/app.py | 36 +++++++++++++++--------------------- bench/commands/__init__.py | 2 -- bench/commands/make.py | 35 ++++++++++++++--------------------- bench/utils/system.py | 14 ++++++++++++-- 4 files changed, 41 insertions(+), 46 deletions(-) diff --git a/bench/app.py b/bench/app.py index f4ea84ec..ab22eea2 100755 --- a/bench/app.py +++ b/bench/app.py @@ -222,7 +222,7 @@ class App(AppMeta): return info_file["required_apps"] if info_file else {} -def make_resolution_plan(app: App, bench): +def make_resolution_plan(app: App, bench: "Bench"): """ decide what apps and versions to install and in what order """ @@ -332,6 +332,7 @@ def get_app( verbose=False, overwrite=False, init_bench=False, + resolve=False, ): """bench get-app clones a Frappe App from remote (GitHub or any other git server), and installs it on the current bench. This also resolves dependencies based on the @@ -343,6 +344,7 @@ def get_app( from bench.bench import Bench import bench as _bench import bench.cli as bench_cli + from bench.utils.app import check_existing_dir bench = Bench(bench_path) app = App(git_url, branch=branch, bench=bench) @@ -375,9 +377,17 @@ def get_app( "color": None, }) + if resolve: + resolve_and_install( + app=app, + bench=bench, + bench_path=bench_path, + skip_assets=skip_assets, + verbose=verbose, + ) + return - cloned_path = os.path.join(bench_path, "apps", repo_name) - dir_already_exists = os.path.isdir(cloned_path) + dir_already_exists, cloned_path = check_existing_dir(bench_path, repo_name) to_clone = not dir_already_exists # application directory already exists @@ -403,35 +413,20 @@ def get_app( app.install(verbose=verbose, skip_assets=skip_assets) def resolve_and_install( - git_url, - branch=None, + app: App, + bench: "Bench", bench_path=".", skip_assets=False, verbose=False, - init_bench=False, ): - from bench.cli import Bench - from bench.utils.system import init from bench.utils.app import check_existing_dir - bench = Bench(bench_path) - app = App(git_url, branch=branch, bench=bench) resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Terminal dependency del resolution["frappe"] - if init_bench: - bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init( - path=bench_path, - frappe_branch=branch, - skip_assets=skip_assets, - verbose=verbose, - ) - os.chdir(bench_path) - for repo_name, app in reversed(resolution.items()): existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) if existing_dir: @@ -510,7 +505,6 @@ def install_app( if restart_bench: bench.reload() - def pull_apps(apps=None, bench_path=".", reset=False): """Check all apps if there no local changes, pull""" from bench.bench import Bench diff --git a/bench/commands/__init__.py b/bench/commands/__init__.py index 4cdc8882..08a493b9 100755 --- a/bench/commands/__init__.py +++ b/bench/commands/__init__.py @@ -39,7 +39,6 @@ from bench.commands.make import ( new_app, pip, remove_app, - resolve_and_install, ) bench_command.add_command(init) @@ -49,7 +48,6 @@ bench_command.add_command(new_app) bench_command.add_command(remove_app) bench_command.add_command(exclude_app_for_update) bench_command.add_command(include_app_for_update) -bench_command.add_command(resolve_and_install) bench_command.add_command(pip) diff --git a/bench/commands/make.py b/bench/commands/make.py index c6b7748b..c19e71cd 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -133,8 +133,20 @@ def drop(path): @click.option( "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" ) +@click.option( + "--resolve/--no-resolve", + is_flag=True, + default=False, + help="Resolve dependencies before installing app", +) def get_app( - git_url, branch, name=None, overwrite=False, skip_assets=False, init_bench=False + git_url, + branch, + name=None, + overwrite=False, + skip_assets=False, + init_bench=False, + resolve=False, ): "clone an app from the internet and set it up in your bench" from bench.app import get_app @@ -145,26 +157,7 @@ def get_app( skip_assets=skip_assets, overwrite=overwrite, init_bench=init_bench, - ) - -@click.command("resolve-and-install", help="Resolve dependencies and install apps") -@click.argument("git-url") -@click.option("--branch", default=None) -@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") -@click.option( - "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" -) -@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") -@click.option("--verbose", is_flag=True, default=False, help="Verbosity") -def resolve_and_install(git_url, branch, skip_assets, verbose, init_bench): - from bench.app import resolve_and_install - - resolve_and_install( - git_url=git_url, - branch=branch, - skip_assets=skip_assets, - init_bench=init_bench, - verbose=verbose, + resolve=resolve, ) @click.command("new-app", help="Create a new Frappe application under apps folder") diff --git a/bench/utils/system.py b/bench/utils/system.py index 50c8b1d8..a72ec2ba 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -76,7 +76,12 @@ def init( frappe_path = frappe_path or "https://github.com/frappe/frappe.git" get_app( - frappe_path, branch=frappe_branch, bench_path=path, skip_assets=True, verbose=verbose + frappe_path, + branch=frappe_branch, + bench_path=path, + skip_assets=True, + verbose=verbose, + resolve=False, ) # fetch remote apps using config file - deprecate this! @@ -86,7 +91,12 @@ def init( # getting app on bench init using --install-app if install_app: get_app( - install_app, branch=frappe_branch, bench_path=path, skip_assets=True, verbose=verbose + install_app, + branch=frappe_branch, + bench_path=path, + skip_assets=True, + verbose=verbose, + resolve=False, ) if not skip_assets: From e3bd34c12ca6f1e86150e811c6d23f3ab1196504 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 4 Feb 2022 20:50:35 +0530 Subject: [PATCH 005/108] fix: fixed init in get-app and frappe versions --- .vscode/settings.json | 3 +++ bench/app.py | 22 +++++++++++++--------- bench/utils/__init__.py | 7 +++++++ bench/utils/system.py | 7 ++++++- 4 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..de288e1e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.formatting.provider": "black" +} \ No newline at end of file diff --git a/bench/app.py b/bench/app.py index ab22eea2..70370c17 100755 --- a/bench/app.py +++ b/bench/app.py @@ -354,6 +354,10 @@ def get_app( bench_setup = False apps_to_install = make_resolution_plan(app, bench) + if resolve: + resolution = make_resolution_plan(app, bench) + frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag + if not is_bench_directory(bench_path): if not init_bench: raise NotInBenchDirectoryError( @@ -365,7 +369,11 @@ def get_app( frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init(path=bench_path, frappe_branch=frappe_app.tag) + init( + path=bench_path, + frappe_path=frappe_path if resolve else None, + frappe_branch=frappe_branch if resolve else branch, + ) os.chdir(bench_path) bench_setup = True @@ -378,9 +386,8 @@ def get_app( }) if resolve: - resolve_and_install( - app=app, - bench=bench, + install_resolved_deps( + resolution, bench_path=bench_path, skip_assets=skip_assets, verbose=verbose, @@ -412,17 +419,14 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) -def resolve_and_install( - app: App, - bench: "Bench", +def install_resolved_deps( + resolution, bench_path=".", skip_assets=False, verbose=False, ): from bench.utils.app import check_existing_dir - - resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Terminal dependency del resolution["frappe"] diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index f75fe263..87998d69 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -11,6 +11,7 @@ from typing import List, Tuple # imports - third party imports import click +import requests # imports - module imports from bench import PROJECT_NAME, VERSION @@ -48,6 +49,12 @@ def is_frappe_app(directory: str) -> bool: return bool(is_frappe_app) +def is_valid_frappe_branch(frappe_path: str, frappe_branch: str): + url = frappe_path + url += "/tree/" + frappe_branch if frappe_branch else "" + return requests.get(url).status_code != 404 + + def log(message, level=0, no_log=False): import bench import bench.cli diff --git a/bench/utils/system.py b/bench/utils/system.py index a72ec2ba..2ad8cd16 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -14,6 +14,7 @@ from bench.utils import ( run_frappe_cmd, sudoers_file, which, + is_valid_frappe_branch, ) from bench.utils.bench import build_assets, clone_apps_from from bench.utils.render import job @@ -74,7 +75,11 @@ def init( # remote apps else: frappe_path = frappe_path or "https://github.com/frappe/frappe.git" - + frappe_branch = ( + frappe_branch + if is_valid_frappe_branch(frappe_path, frappe_branch) + else None + ) get_app( frappe_path, branch=frappe_branch, From 42f4d74beb5418de2cfb418ab103dc6e568871d1 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 10:13:15 +0530 Subject: [PATCH 006/108] fix: fixed app states and checking/updating frappe version on install fix: fixed valid branch check in init --- bench/app.py | 11 ++++++----- bench/bench.py | 5 +---- bench/utils/__init__.py | 8 +++++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bench/app.py b/bench/app.py index 70370c17..4299b032 100755 --- a/bench/app.py +++ b/bench/app.py @@ -352,11 +352,13 @@ def get_app( repo_name = app.repo branch = app.tag bench_setup = False - apps_to_install = make_resolution_plan(app, bench) + frappe_path, frappe_branch = None, None if resolve: resolution = make_resolution_plan(app, bench) - frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag + if "frappe" in resolution: + # Todo: Make frappe a terminal dependency for all frappe apps. + frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag if not is_bench_directory(bench_path): if not init_bench: @@ -367,12 +369,11 @@ def get_app( from bench.utils.system import init - frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) init( path=bench_path, - frappe_path=frappe_path if resolve else None, - frappe_branch=frappe_branch if resolve else branch, + frappe_path=frappe_path, + frappe_branch=frappe_branch if frappe_branch else branch, ) os.chdir(bench_path) bench_setup = True diff --git a/bench/bench.py b/bench/bench.py index 6a0081c3..804abf8f 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -152,7 +152,6 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.initialize_apps() - self.initialize_states() def initialize_states(self): try: @@ -163,9 +162,7 @@ class BenchApps(MutableSequence): self.states = json.loads(f.read() or "{}") def update_apps_states(self, app_name: str = None, resolution: str = None): - # Only tracking for app state for apps installed via `bench resolve-and-install`, - # Not all states can be maintained as some apps may not be install via bench resolve-and-install - # or may not be compatible with versioning + self.initialize_states() self.initialize_apps() apps_to_remove = [] for app in self.states: diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 87998d69..b0402bee 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -50,9 +50,11 @@ def is_frappe_app(directory: str) -> bool: def is_valid_frappe_branch(frappe_path: str, frappe_branch: str): - url = frappe_path - url += "/tree/" + frappe_branch if frappe_branch else "" - return requests.get(url).status_code != 404 + if "http" in frappe_path: + url = frappe_path + url += "/tree/" + frappe_branch if frappe_branch else "" + return requests.get(url).status_code != 404 + return True def log(message, level=0, no_log=False): From b7e46aab6fce2aa5d2ec22fe3138870be11a8858 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 10:18:14 +0530 Subject: [PATCH 007/108] fix: removed erroneous file push --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index de288e1e..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.formatting.provider": "black" -} \ No newline at end of file From c8dfe39c05469a67560b25bb0e5fca5584dd6bab Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:22:48 +0530 Subject: [PATCH 008/108] refactor: using hooks.py instead of toml files to read dependencies --- bench/app.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/bench/app.py b/bench/app.py index 4299b032..3e7c5243 100755 --- a/bench/app.py +++ b/bench/app.py @@ -1,5 +1,4 @@ # imports - standard imports -from collections import OrderedDict import functools import json import logging @@ -9,6 +8,7 @@ import shutil import subprocess import sys import typing +from collections import OrderedDict from datetime import date # imports - third party imports @@ -210,15 +210,15 @@ class App(AppMeta): def _get_dependencies(self): from bench.utils.app import get_required_deps_url - import toml - info_file = None - required_url = get_required_deps_url(self.url, self.tag) - print(required_url) + required_url = get_required_deps_url(git_url=self.url, repo_name=self.repo, branch=self.tag) try: - info_file = toml.loads(requests.get(required_url).text) - except Exception: - click.echo("\nNo toml file found \n", err=True) + f = requests.get(required_url).text + lines = [x for x in f.split("\n") if x.strip().startswith("required_apps")] + required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) + return required_apps + except Exception as e: + return [] return info_file["required_apps"] if info_file else {} @@ -228,16 +228,17 @@ def make_resolution_plan(app: App, bench: "Bench"): """ resolution = OrderedDict() resolution[app.repo] = app - for app_name, branch in app._get_dependencies().items(): - dep_app = App(app_name, branch=branch, bench=bench) + + for app_name in app._get_dependencies(): + dep_app = App(app_name, bench=bench) + if dep_app.repo in resolution: + click.secho(f"{dep_app.repo} is already resolved skipping", fg="yellow") + continue resolution[dep_app.repo] = dep_app resolution.update(make_resolution_plan(dep_app, bench)) - if app_name in resolution: - print("Resolve this conflict!") return resolution - def add_to_appstxt(app, bench_path="."): from bench.bench import Bench @@ -332,7 +333,7 @@ def get_app( verbose=False, overwrite=False, init_bench=False, - resolve=False, + resolve_deps=False, ): """bench get-app clones a Frappe App from remote (GitHub or any other git server), and installs it on the current bench. This also resolves dependencies based on the @@ -341,9 +342,9 @@ def get_app( If the bench_path is not a bench directory, a new bench is created named using the git_url parameter. """ - from bench.bench import Bench import bench as _bench import bench.cli as bench_cli + from bench.bench import Bench from bench.utils.app import check_existing_dir bench = Bench(bench_path) @@ -354,7 +355,7 @@ def get_app( bench_setup = False frappe_path, frappe_branch = None, None - if resolve: + if resolve_deps: resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. @@ -386,7 +387,7 @@ def get_app( "color": None, }) - if resolve: + if resolve_deps: install_resolved_deps( resolution, bench_path=bench_path, From 80cfb9dfb4d065040b705b0615d2229f05544c3e Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:24:41 +0530 Subject: [PATCH 009/108] refactor: changed resolve to resolve-deps --- bench/commands/make.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bench/commands/make.py b/bench/commands/make.py index c19e71cd..ac48f410 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -134,7 +134,7 @@ def drop(path): "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" ) @click.option( - "--resolve/--no-resolve", + "--resolve-deps", is_flag=True, default=False, help="Resolve dependencies before installing app", @@ -146,7 +146,7 @@ def get_app( overwrite=False, skip_assets=False, init_bench=False, - resolve=False, + resolve_deps=False, ): "clone an app from the internet and set it up in your bench" from bench.app import get_app @@ -157,7 +157,7 @@ def get_app( skip_assets=skip_assets, overwrite=overwrite, init_bench=init_bench, - resolve=resolve, + resolve_deps=resolve_deps, ) @click.command("new-app", help="Create a new Frappe application under apps folder") From 7f678a304713c33905156242f275e7521983f41c Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:25:22 +0530 Subject: [PATCH 010/108] feat: added support for fetching dependencies from hooks.py --- bench/utils/app.py | 5 +++-- bench/utils/system.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index f7a6d5cc..13241290 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -164,11 +164,12 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) -def get_required_deps_url(git_url, branch="master", deps="info.toml"): +def get_required_deps_url(git_url, repo_name, branch, deps="hooks.py"): git_url = ( git_url.replace(".git", "").replace("github.com", "raw.github.com") ) - git_url += f"/{branch}/{deps}" if branch else f"/{deps}" + branch = branch if branch else "develop" + git_url += f"/{branch}/{repo_name}/{deps}" return git_url def get_remote(app, bench_path="."): diff --git a/bench/utils/system.py b/bench/utils/system.py index 2ad8cd16..5d6aaea9 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -86,7 +86,7 @@ def init( bench_path=path, skip_assets=True, verbose=verbose, - resolve=False, + resolve_deps=False, ) # fetch remote apps using config file - deprecate this! From 7e28a3dd9e8a5670083912f30d6ff06cc216b0f0 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 12:24:50 +0530 Subject: [PATCH 011/108] fix: added code removed via erroneous commit --- bench/app.py | 31 ++++++++++++++----------------- bench/bench.py | 16 ++++++++-------- bench/utils/app.py | 4 ++++ 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/bench/app.py b/bench/app.py index 3e7c5243..ef73f040 100755 --- a/bench/app.py +++ b/bench/app.py @@ -94,7 +94,6 @@ class AppMeta: self._setup_details_from_name_tag() def _setup_details_from_mounted_disk(self): - self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split(self.name)[-2:] + (self.branch,) def _setup_details_from_name_tag(self): @@ -102,7 +101,6 @@ class AppMeta: self.tag = self.tag or self.branch def _setup_details_from_installed_apps(self): - self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split( os.path.join(self.bench.name, "apps", self.name) )[-2:] + (self.branch,) @@ -178,31 +176,30 @@ class App(AppMeta): shutil.move(active_app_path, archived_app_path) @step(title="Installing App {repo}", success="App {repo} Installed") - def install(self, skip_assets=False, verbose=False): + def install(self, skip_assets=False, verbose=False, resolved=False): import bench.cli from bench.utils.app import get_app_name verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) - - # TODO: this should go inside install_app only tho - issue: default/resolved branch - setup_app_dependencies( - repo_name=self.repo, - bench_path=self.bench.name, - branch=self.tag, - verbose=verbose, - skip_assets=skip_assets, - ) + if not resolved: + # TODO: this should go inside install_app only tho - issue: default/resolved branch + setup_app_dependencies( + repo_name=self.repo, + bench_path=self.bench.name, + branch=self.tag, + verbose=verbose, + skip_assets=skip_assets, + ) install_app( - app=app_name, bench_path=self.bench.name, verbose=verbose, skip_assets=skip_assets, + app=app_name, tag=self.tag, bench_path=self.bench.name, verbose=verbose, skip_assets=skip_assets, ) @step(title="Cloning and installing {repo}", success="App {repo} Installed") def install_resolved_apps(self, *args, **kwargs): self.get() self.install(*args, **kwargs, resolved=True) - self.bench.apps.update_apps_states(self.repo, self.tag) @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): @@ -217,11 +214,9 @@ class App(AppMeta): lines = [x for x in f.split("\n") if x.strip().startswith("required_apps")] required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) return required_apps - except Exception as e: + except Exception: return [] - return info_file["required_apps"] if info_file else {} - def make_resolution_plan(app: App, bench: "Bench"): """ decide what apps and versions to install and in what order @@ -473,6 +468,7 @@ def new_app(app, no_git=None, bench_path="."): def install_app( app, + tag, bench_path=".", verbose=False, no_cache=False, @@ -504,6 +500,7 @@ def install_app( bench.run("yarn install", cwd=app_path) bench.apps.sync() + bench.apps.update_apps_states(app, tag) if not skip_assets: build_assets(bench_path=bench_path, app=app) diff --git a/bench/bench.py b/bench/bench.py index 804abf8f..87dcc3c6 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -57,7 +57,6 @@ class Bench(Base, Validator): def __init__(self, path): self.name = path self.cwd = os.path.abspath(path) - self.apps_states = os.path.join(self.name, "sites", "apps_states.json") self.exists = is_bench_directory(self.name) self.setup = BenchSetup(self) @@ -151,19 +150,20 @@ class Bench(Base, Validator): class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench + self.states_path = os.path.join(self.bench.name, "sites", "apps_states.json") self.initialize_apps() - def initialize_states(self): + def set_states(self): try: - with open(self.bench.apps_states, "r") as f: + with open(self.states_path, "r") as f: self.states = json.loads(f.read() or "{}") except FileNotFoundError: - with open(self.bench.apps_states, "w") as f: + with open(self.states_path, "w+") as f: self.states = json.loads(f.read() or "{}") def update_apps_states(self, app_name: str = None, resolution: str = None): - self.initialize_states() self.initialize_apps() + self.set_states() apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -172,14 +172,14 @@ class BenchApps(MutableSequence): for app in apps_to_remove: del self.states[app] - if app_name and resolution: - version = get_current_version(app_name) + if app_name: + version = get_current_version(app_name, self.bench.name) self.states[app_name] = { "resolution": resolution, "version": version, } - with open(self.bench.apps_states, "w") as f: + with open(self.states_path, "w") as f: f.write(json.dumps(self.states, indent=4)) def sync(self): diff --git a/bench/utils/app.py b/bench/utils/app.py index 13241290..e86a0304 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -208,6 +208,10 @@ def get_app_name(bench_path, repo_name): return repo_name +def check_existing_dir(bench_path, repo_name): + cloned_path = os.path.join(bench_path, "apps", repo_name) + dir_already_exists = os.path.isdir(cloned_path) + return dir_already_exists, cloned_path def get_current_version(app, bench_path="."): current_version = None From 129e2fd922d6d459e9cd653d577273563a122164 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Feb 2022 16:51:34 +0530 Subject: [PATCH 012/108] test: get-app resolve_deps --- bench/tests/test_init.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bench/tests/test_init.py b/bench/tests/test_init.py index 52c23282..f34a777e 100755 --- a/bench/tests/test_init.py +++ b/bench/tests/test_init.py @@ -105,6 +105,20 @@ class TestBenchInit(TestBenchBase): app_installed_in_env = TEST_FRAPPE_APP in subprocess.check_output(["bench", "pip", "freeze"], cwd=bench_path).decode('utf8') self.assertTrue(app_installed_in_env) + def test_get_app_resolve_deps(self): + FRAPPE_APP = "healthcare" + self.init_bench("test-bench") + bench_path = os.path.join(self.benches_path, "test-bench") + exec_cmd(f"bench get-app {FRAPPE_APP} --resolve-deps", cwd=bench_path) + self.assertTrue(os.path.exists(os.path.join(bench_path, "apps", FRAPPE_APP))) + + states_path = os.path.join(bench_path, "sites", "apps_states.json") + self.assert_(os.path.exists(states_path)) + + with open(states_path, "r") as f: + states = json.load(f) + + self.assert_(FRAPPE_APP in states) def test_install_app(self): bench_name = "test-bench" From bba4019579390533b11e06cef8be9b5644fb7dc8 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 17 Feb 2022 21:38:01 +0530 Subject: [PATCH 013/108] feat: printing resolved dependencies when using resolve-deps flag wih get-app --- bench/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bench/app.py b/bench/app.py index ef73f040..f415f3f9 100755 --- a/bench/app.py +++ b/bench/app.py @@ -352,6 +352,8 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) + click.secho("Apps to be installed:", fg="yellow") + print("\n".join([app.name for app in reversed(resolution.values())])) if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag From e629ca6f8679d2144394c143602a160424660484 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 17 Feb 2022 21:48:40 +0530 Subject: [PATCH 014/108] fix: minor fixes --- bench/app.py | 2 +- bench/utils/system.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index f415f3f9..7f42889b 100755 --- a/bench/app.py +++ b/bench/app.py @@ -470,7 +470,7 @@ def new_app(app, no_git=None, bench_path="."): def install_app( app, - tag, + tag=None, bench_path=".", verbose=False, no_cache=False, diff --git a/bench/utils/system.py b/bench/utils/system.py index 5d6aaea9..ab27893c 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -101,7 +101,7 @@ def init( bench_path=path, skip_assets=True, verbose=verbose, - resolve=False, + resolve_deps=False, ) if not skip_assets: From 8fb6796250a6158c826380cf465ed4e981018062 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 23 Feb 2022 20:42:22 +0530 Subject: [PATCH 015/108] test: Added tests for app utils --- .travis.yml | 6 +++--- bench/tests/test_utils.py | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 bench/tests/test_utils.py diff --git a/.travis.yml b/.travis.yml index e2b718ae..9c57f8ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,17 +48,17 @@ matrix: - name: "Python 3.7 Tests" python: 3.7 env: TEST=bench - script: python -m unittest -v bench.tests.test_init + script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils - name: "Python 3.8 Tests" python: 3.8 env: TEST=bench - script: python -m unittest -v bench.tests.test_init + script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils - name: "Python 3.9 Tests" python: 3.9 env: TEST=bench - script: python -m unittest -v bench.tests.test_init + script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils - name: "Python 3.7 Easy Install" python: 3.7 diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py new file mode 100644 index 00000000..91ff66ec --- /dev/null +++ b/bench/tests/test_utils.py @@ -0,0 +1,25 @@ +import unittest + +from bench.app import App +from bench.bench import Bench +from bench.utils import exec_cmd + + +class TestUtils(unittest.TestCase): + def test_app_utils(self): + git_url = "https://github.com/frappe/frappe" + branch = "develop" + app = App(name=git_url, branch=branch, bench=Bench(".")) + self.assertTrue( + all( + [ + app.name == git_url, + app.branch == branch, + app.tag == branch, + app.is_url == True, + app.on_disk == False, + app.org == "frappe", + app.url == git_url, + ] + ) + ) \ No newline at end of file From df76415e6b8046d348f1dc0d3c16c42ef6c0b662 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 24 Feb 2022 12:40:00 +0530 Subject: [PATCH 016/108] refactor: running utils tests before bench tests --- .travis.yml | 6 +++--- bench/tests/test_utils.py | 34 +++++++++++++++++----------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9c57f8ca..97296634 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,17 +48,17 @@ matrix: - name: "Python 3.7 Tests" python: 3.7 env: TEST=bench - script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils + script: python -m unittest -v bench.tests.test_utils && python -m unittest -v bench.tests.test_init - name: "Python 3.8 Tests" python: 3.8 env: TEST=bench - script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils + script: python -m unittest -v bench.tests.test_utils && python -m unittest -v bench.tests.test_init - name: "Python 3.9 Tests" python: 3.9 env: TEST=bench - script: python -m unittest -v bench.tests.test_init && python -m unittest -v bench.tests.test_utils + script: python -m unittest -v bench.tests.test_utils && python -m unittest -v bench.tests.test_init - name: "Python 3.7 Easy Install" python: 3.7 diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 91ff66ec..602dbb38 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -6,20 +6,20 @@ from bench.utils import exec_cmd class TestUtils(unittest.TestCase): - def test_app_utils(self): - git_url = "https://github.com/frappe/frappe" - branch = "develop" - app = App(name=git_url, branch=branch, bench=Bench(".")) - self.assertTrue( - all( - [ - app.name == git_url, - app.branch == branch, - app.tag == branch, - app.is_url == True, - app.on_disk == False, - app.org == "frappe", - app.url == git_url, - ] - ) - ) \ No newline at end of file + def test_app_utils(self): + git_url = "https://github.com/frappe/frappe" + branch = "develop" + app = App(name=git_url, branch=branch, bench=Bench(".")) + self.assertTrue( + all( + [ + app.name == git_url, + app.branch == branch, + app.tag == branch, + app.is_url == True, + app.on_disk == False, + app.org == "frappe", + app.url == git_url, + ] + ) + ) From bb911b5e5a4a06d32c98e5f7951a9afe31f96081 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 25 Feb 2022 16:51:06 +0530 Subject: [PATCH 017/108] refactor: ignoring app deps when install app without --resolve-deps --- bench/app.py | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/bench/app.py b/bench/app.py index db1e34cb..aaf32b9b 100755 --- a/bench/app.py +++ b/bench/app.py @@ -191,13 +191,9 @@ class App(AppMeta): verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) if not resolved: - # TODO: this should go inside install_app only tho - issue: default/resolved branch - setup_app_dependencies( - repo_name=self.repo, - bench_path=self.bench.name, - branch=self.tag, - verbose=verbose, - skip_assets=skip_assets, + click.secho( + f"Ignoring dependencies of {self.name} to install dependencies use --resolve-deps", + fg="yellow", ) install_app( @@ -303,35 +299,6 @@ def remove_from_excluded_apps_txt(app, bench_path="."): return write_excluded_apps_txt(apps, bench_path=bench_path) -def setup_app_dependencies( - repo_name, bench_path=".", branch=None, skip_assets=False, verbose=False -): - # branch kwarg is somewhat of a hack here; since we're assuming the same branches for all apps - # for eg: if you're installing erpnext@develop, you'll want frappe@develop and healthcare@develop too - import glob - import bench.cli - from bench.bench import Bench - - verbose = bench.cli.verbose or verbose - apps_path = os.path.join(os.path.abspath(bench_path), "apps") - files = glob.glob(os.path.join(apps_path, repo_name, "**", "hooks.py")) - - if files: - with open(files[0]) as f: - lines = [x for x in f.read().split("\n") if x.strip().startswith("required_apps")] - if lines: - required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) - # TODO: when the time comes, add version check here - for app in required_apps: - if app not in Bench(bench_path).apps: - get_app( - app, - bench_path=bench_path, - branch=branch, - skip_assets=skip_assets, - verbose=verbose, - ) - def get_app( git_url, @@ -366,7 +333,9 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) click.secho("Apps to be installed:", fg="yellow") - print("\n".join([app.name for app in reversed(resolution.values())])) + for idx, app in enumerate(reversed(resolution.values()), start=1): + print(f"{idx}. {app.name}") + if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag From de315f27ad31b88ccf557e2257d455a3bed762b9 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 28 Feb 2022 14:29:05 +0530 Subject: [PATCH 018/108] fix: logging when in init-bench --- bench/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index aaf32b9b..37ba560a 100755 --- a/bench/app.py +++ b/bench/app.py @@ -190,7 +190,7 @@ class App(AppMeta): verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) - if not resolved: + if not resolved and self.repo != "frappe": click.secho( f"Ignoring dependencies of {self.name} to install dependencies use --resolve-deps", fg="yellow", From bfd69c37ebabdb7165484b997cb8c9b7ae7f76e8 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Wed, 19 Jan 2022 13:43:58 +0530 Subject: [PATCH 019/108] feat: introduced simple resolver --- bench/app.py | 36 +++++++++++++++++++++++++++++++++++- bench/utils/app.py | 6 ++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index 5ce0f8fc..59d9b799 100755 --- a/bench/app.py +++ b/bench/app.py @@ -1,4 +1,5 @@ # imports - standard imports +from collections import OrderedDict import functools import json import logging @@ -47,6 +48,7 @@ class AppMeta: 3. frappe/healthcare@develop 4. healthcare 5. healthcare@develop, healthcare@v13.12.1 + 6. erpnext References for Version Identifiers: * https://www.python.org/dev/peps/pep-0440/#version-specifiers @@ -104,6 +106,7 @@ class AppMeta: self.tag = self.tag or self.branch def _setup_details_from_installed_apps(self): + self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split( os.path.join(self.bench.name, "apps", self.name) )[-2:] + (self.branch,) @@ -207,6 +210,35 @@ class App(AppMeta): def uninstall(self): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") + def _get_dependencies(self): + from bench.utils.app import get_required_deps_url + import toml + + info_file = None + required_url = get_required_deps_url(self.url, self.tag) + print(required_url) + try: + info_file = toml.loads(requests.get(required_url).text) + except Exception: + click.echo("\nNo toml file found \n", err=True) + + return info_file["required_apps"] if info_file else {} + +def make_resolution_plan(app: App, bench): + """ + decide what apps and versions to install and in what order + """ + resolution = OrderedDict() + resolution[app.repo] = app + for app_name, branch in app._get_dependencies().items(): + dep_app = App(app_name, branch=branch, bench=bench) + resolution[dep_app.repo] = dep_app + resolution.update(make_resolution_plan(dep_app, bench)) + if app_name in resolution: + print("Resolve this conflict!") + return resolution + + def add_to_appstxt(app, bench_path="."): from bench.bench import Bench @@ -320,6 +352,7 @@ def get_app( repo_name = app.repo branch = app.tag bench_setup = False + apps_to_install = make_resolution_plan(app, bench) if not is_bench_directory(bench_path): if not init_bench: @@ -330,8 +363,9 @@ def get_app( from bench.utils.system import init + frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init(path=bench_path, frappe_branch=branch) + init(path=bench_path, frappe_branch=frappe_app.tag) os.chdir(bench_path) bench_setup = True diff --git a/bench/utils/app.py b/bench/utils/app.py index ae980743..f7a6d5cc 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -164,6 +164,12 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) +def get_required_deps_url(git_url, branch="master", deps="info.toml"): + git_url = ( + git_url.replace(".git", "").replace("github.com", "raw.github.com") + ) + git_url += f"/{branch}/{deps}" if branch else f"/{deps}" + return git_url def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) From cdaeea1d4a332d3c28cfb39191f46b5081a090ee Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 19 Jan 2022 23:50:35 +0530 Subject: [PATCH 020/108] feat: Added bench command for resolve and install --- bench/commands/__init__.py | 2 ++ bench/commands/make.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/bench/commands/__init__.py b/bench/commands/__init__.py index 08a493b9..4cdc8882 100755 --- a/bench/commands/__init__.py +++ b/bench/commands/__init__.py @@ -39,6 +39,7 @@ from bench.commands.make import ( new_app, pip, remove_app, + resolve_and_install, ) bench_command.add_command(init) @@ -48,6 +49,7 @@ bench_command.add_command(new_app) bench_command.add_command(remove_app) bench_command.add_command(exclude_app_for_update) bench_command.add_command(include_app_for_update) +bench_command.add_command(resolve_and_install) bench_command.add_command(pip) diff --git a/bench/commands/make.py b/bench/commands/make.py index 88b37c61..c6b7748b 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -147,6 +147,25 @@ def get_app( init_bench=init_bench, ) +@click.command("resolve-and-install", help="Resolve dependencies and install apps") +@click.argument("git-url") +@click.option("--branch", default=None) +@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") +@click.option( + "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" +) +@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") +@click.option("--verbose", is_flag=True, default=False, help="Verbosity") +def resolve_and_install(git_url, branch, skip_assets, verbose, init_bench): + from bench.app import resolve_and_install + + resolve_and_install( + git_url=git_url, + branch=branch, + skip_assets=skip_assets, + init_bench=init_bench, + verbose=verbose, + ) @click.command("new-app", help="Create a new Frappe application under apps folder") @click.option( From fdab757f9b4972e3a782fed75850595dbe9892d1 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 20 Jan 2022 15:04:43 +0530 Subject: [PATCH 021/108] feat: Added app states --- bench/app.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ bench/bench.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/bench/app.py b/bench/app.py index 59d9b799..c667c155 100755 --- a/bench/app.py +++ b/bench/app.py @@ -14,6 +14,7 @@ from urllib.parse import urlparse # imports - third party imports import click +import requests # imports - module imports import bench @@ -206,6 +207,12 @@ class App(AppMeta): restart_bench=restart_bench ) + @step(title="Cloning and installing {repo}", success="App {repo} Installed") + def install_resolved_apps(self, *args, **kwargs): + self.get() + self.install(*args, **kwargs, resolved=True) + self.bench.apps.update_apps_states(self.repo, self.tag) + @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") @@ -404,6 +411,49 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) +def resolve_and_install( + git_url, + branch=None, + bench_path=".", + skip_assets=False, + verbose=False, + init_bench=False, +): + from bench.cli import Bench + from bench.utils.system import init + from bench.utils.app import check_existing_dir + + bench = Bench(bench_path) + app = App(git_url, branch=branch, bench=bench) + + resolution = make_resolution_plan(app, bench) + if "frappe" in resolution: + # Terminal dependency + del resolution["frappe"] + + if init_bench: + bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) + init( + path=bench_path, + frappe_branch=branch, + skip_assets=skip_assets, + verbose=verbose, + ) + os.chdir(bench_path) + + for repo_name, app in reversed(resolution.items()): + existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) + if existing_dir: + if click.confirm( + f"A directory for the application '{repo_name}' already exists. " + "Do you want to continue and overwrite it?" + ): + click.secho(f"Removing {repo_name}", fg="yellow") + shutil.rmtree(cloned_path) + app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) + + continue + app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) def new_app(app, no_git=None, bench_path="."): if bench.FRAPPE_VERSION in (0, None): diff --git a/bench/bench.py b/bench/bench.py index f359be7c..4e82f402 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -2,6 +2,7 @@ import functools import os import shutil +import json import sys import logging from typing import List, MutableSequence, TYPE_CHECKING @@ -30,6 +31,7 @@ from bench.utils.bench import ( get_env_cmd, ) from bench.utils.render import job, step +from bench.utils.app import get_current_version if TYPE_CHECKING: @@ -55,6 +57,7 @@ class Bench(Base, Validator): def __init__(self, path): self.name = path self.cwd = os.path.abspath(path) + self.apps_states = os.path.join(self.name, "sites", "apps_states.json") self.exists = is_bench_directory(self.name) self.setup = BenchSetup(self) @@ -122,6 +125,7 @@ class Bench(Base, Validator): self.validate_app_uninstall(app) self.apps.remove(App(app, bench=self, to_clone=False)) self.apps.sync() + self.apps.update_apps_states() # self.build() - removed because it seems unnecessary self.reload() @@ -156,6 +160,38 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.initialize_apps() + self.initialize_states() + + def initialize_states(self): + try: + with open(self.bench.apps_states, "r") as f: + self.states = json.loads(f.read() or "{}") + except FileNotFoundError: + with open(self.bench.apps_states, "w") as f: + self.states = json.loads(f.read() or "{}") + + def update_apps_states(self, app_name: str = None, resolution: str = None): + # Only tracking for app state for apps installed via `bench resolve-and-install`, + # Not all states can be maintained as some apps may not be install via bench resolve-and-install + # or may not be compatible with versioning + self.initialize_apps() + apps_to_remove = [] + for app in self.states: + if app not in self.apps: + apps_to_remove.append(app) + + for app in apps_to_remove: + del self.states[app] + + if app_name and resolution: + version = get_current_version(app_name) + self.states[app_name] = { + "resolution": resolution, + "version": version, + } + + with open(self.bench.apps_states, "w") as f: + f.write(json.dumps(self.states, indent=4)) def sync(self): self.initialize_apps() From 210146d2227ae2e6a51d5d82998b81232ceb6ce1 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 28 Jan 2022 21:20:14 +0530 Subject: [PATCH 022/108] refactor: moved resolve and install to get-app --- bench/app.py | 36 +++++++++++++++--------------------- bench/commands/__init__.py | 2 -- bench/commands/make.py | 35 ++++++++++++++--------------------- bench/utils/system.py | 14 ++++++++++++-- 4 files changed, 41 insertions(+), 46 deletions(-) diff --git a/bench/app.py b/bench/app.py index c667c155..fca841f7 100755 --- a/bench/app.py +++ b/bench/app.py @@ -231,7 +231,7 @@ class App(AppMeta): return info_file["required_apps"] if info_file else {} -def make_resolution_plan(app: App, bench): +def make_resolution_plan(app: App, bench: "Bench"): """ decide what apps and versions to install and in what order """ @@ -341,6 +341,7 @@ def get_app( verbose=False, overwrite=False, init_bench=False, + resolve=False, ): """bench get-app clones a Frappe App from remote (GitHub or any other git server), and installs it on the current bench. This also resolves dependencies based on the @@ -352,6 +353,7 @@ def get_app( from bench.bench import Bench import bench as _bench import bench.cli as bench_cli + from bench.utils.app import check_existing_dir bench = Bench(bench_path) app = App(git_url, branch=branch, bench=bench) @@ -384,9 +386,17 @@ def get_app( "color": None, }) + if resolve: + resolve_and_install( + app=app, + bench=bench, + bench_path=bench_path, + skip_assets=skip_assets, + verbose=verbose, + ) + return - cloned_path = os.path.join(bench_path, "apps", repo_name) - dir_already_exists = os.path.isdir(cloned_path) + dir_already_exists, cloned_path = check_existing_dir(bench_path, repo_name) to_clone = not dir_already_exists # application directory already exists @@ -412,35 +422,20 @@ def get_app( app.install(verbose=verbose, skip_assets=skip_assets) def resolve_and_install( - git_url, - branch=None, + app: App, + bench: "Bench", bench_path=".", skip_assets=False, verbose=False, - init_bench=False, ): - from bench.cli import Bench - from bench.utils.system import init from bench.utils.app import check_existing_dir - bench = Bench(bench_path) - app = App(git_url, branch=branch, bench=bench) resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Terminal dependency del resolution["frappe"] - if init_bench: - bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init( - path=bench_path, - frappe_branch=branch, - skip_assets=skip_assets, - verbose=verbose, - ) - os.chdir(bench_path) - for repo_name, app in reversed(resolution.items()): existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) if existing_dir: @@ -519,7 +514,6 @@ def install_app( if restart_bench: bench.reload() - def pull_apps(apps=None, bench_path=".", reset=False): """Check all apps if there no local changes, pull""" from bench.bench import Bench diff --git a/bench/commands/__init__.py b/bench/commands/__init__.py index 4cdc8882..08a493b9 100755 --- a/bench/commands/__init__.py +++ b/bench/commands/__init__.py @@ -39,7 +39,6 @@ from bench.commands.make import ( new_app, pip, remove_app, - resolve_and_install, ) bench_command.add_command(init) @@ -49,7 +48,6 @@ bench_command.add_command(new_app) bench_command.add_command(remove_app) bench_command.add_command(exclude_app_for_update) bench_command.add_command(include_app_for_update) -bench_command.add_command(resolve_and_install) bench_command.add_command(pip) diff --git a/bench/commands/make.py b/bench/commands/make.py index c6b7748b..c19e71cd 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -133,8 +133,20 @@ def drop(path): @click.option( "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" ) +@click.option( + "--resolve/--no-resolve", + is_flag=True, + default=False, + help="Resolve dependencies before installing app", +) def get_app( - git_url, branch, name=None, overwrite=False, skip_assets=False, init_bench=False + git_url, + branch, + name=None, + overwrite=False, + skip_assets=False, + init_bench=False, + resolve=False, ): "clone an app from the internet and set it up in your bench" from bench.app import get_app @@ -145,26 +157,7 @@ def get_app( skip_assets=skip_assets, overwrite=overwrite, init_bench=init_bench, - ) - -@click.command("resolve-and-install", help="Resolve dependencies and install apps") -@click.argument("git-url") -@click.option("--branch", default=None) -@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") -@click.option( - "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" -) -@click.option("--skip-assets", is_flag=True, default=False, help="Do not build assets") -@click.option("--verbose", is_flag=True, default=False, help="Verbosity") -def resolve_and_install(git_url, branch, skip_assets, verbose, init_bench): - from bench.app import resolve_and_install - - resolve_and_install( - git_url=git_url, - branch=branch, - skip_assets=skip_assets, - init_bench=init_bench, - verbose=verbose, + resolve=resolve, ) @click.command("new-app", help="Create a new Frappe application under apps folder") diff --git a/bench/utils/system.py b/bench/utils/system.py index 50c8b1d8..a72ec2ba 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -76,7 +76,12 @@ def init( frappe_path = frappe_path or "https://github.com/frappe/frappe.git" get_app( - frappe_path, branch=frappe_branch, bench_path=path, skip_assets=True, verbose=verbose + frappe_path, + branch=frappe_branch, + bench_path=path, + skip_assets=True, + verbose=verbose, + resolve=False, ) # fetch remote apps using config file - deprecate this! @@ -86,7 +91,12 @@ def init( # getting app on bench init using --install-app if install_app: get_app( - install_app, branch=frappe_branch, bench_path=path, skip_assets=True, verbose=verbose + install_app, + branch=frappe_branch, + bench_path=path, + skip_assets=True, + verbose=verbose, + resolve=False, ) if not skip_assets: From 88c23673d6dc52d580444775dceab6783377829f Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 4 Feb 2022 20:50:35 +0530 Subject: [PATCH 023/108] fix: fixed init in get-app and frappe versions --- .vscode/settings.json | 3 +++ bench/app.py | 22 +++++++++++++--------- bench/utils/__init__.py | 7 +++++++ bench/utils/system.py | 7 ++++++- 4 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..de288e1e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.formatting.provider": "black" +} \ No newline at end of file diff --git a/bench/app.py b/bench/app.py index fca841f7..7ac7aa2c 100755 --- a/bench/app.py +++ b/bench/app.py @@ -363,6 +363,10 @@ def get_app( bench_setup = False apps_to_install = make_resolution_plan(app, bench) + if resolve: + resolution = make_resolution_plan(app, bench) + frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag + if not is_bench_directory(bench_path): if not init_bench: raise NotInBenchDirectoryError( @@ -374,7 +378,11 @@ def get_app( frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) - init(path=bench_path, frappe_branch=frappe_app.tag) + init( + path=bench_path, + frappe_path=frappe_path if resolve else None, + frappe_branch=frappe_branch if resolve else branch, + ) os.chdir(bench_path) bench_setup = True @@ -387,9 +395,8 @@ def get_app( }) if resolve: - resolve_and_install( - app=app, - bench=bench, + install_resolved_deps( + resolution, bench_path=bench_path, skip_assets=skip_assets, verbose=verbose, @@ -421,17 +428,14 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) -def resolve_and_install( - app: App, - bench: "Bench", +def install_resolved_deps( + resolution, bench_path=".", skip_assets=False, verbose=False, ): from bench.utils.app import check_existing_dir - - resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Terminal dependency del resolution["frappe"] diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index f75fe263..87998d69 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -11,6 +11,7 @@ from typing import List, Tuple # imports - third party imports import click +import requests # imports - module imports from bench import PROJECT_NAME, VERSION @@ -48,6 +49,12 @@ def is_frappe_app(directory: str) -> bool: return bool(is_frappe_app) +def is_valid_frappe_branch(frappe_path: str, frappe_branch: str): + url = frappe_path + url += "/tree/" + frappe_branch if frappe_branch else "" + return requests.get(url).status_code != 404 + + def log(message, level=0, no_log=False): import bench import bench.cli diff --git a/bench/utils/system.py b/bench/utils/system.py index a72ec2ba..2ad8cd16 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -14,6 +14,7 @@ from bench.utils import ( run_frappe_cmd, sudoers_file, which, + is_valid_frappe_branch, ) from bench.utils.bench import build_assets, clone_apps_from from bench.utils.render import job @@ -74,7 +75,11 @@ def init( # remote apps else: frappe_path = frappe_path or "https://github.com/frappe/frappe.git" - + frappe_branch = ( + frappe_branch + if is_valid_frappe_branch(frappe_path, frappe_branch) + else None + ) get_app( frappe_path, branch=frappe_branch, From 30a4ec3b42db8d7e953521ef311c9a80515b4948 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 10:13:15 +0530 Subject: [PATCH 024/108] fix: fixed app states and checking/updating frappe version on install fix: fixed valid branch check in init --- bench/app.py | 11 ++++++----- bench/bench.py | 5 +---- bench/utils/__init__.py | 8 +++++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bench/app.py b/bench/app.py index 7ac7aa2c..7aa9b2e8 100755 --- a/bench/app.py +++ b/bench/app.py @@ -361,11 +361,13 @@ def get_app( repo_name = app.repo branch = app.tag bench_setup = False - apps_to_install = make_resolution_plan(app, bench) + frappe_path, frappe_branch = None, None if resolve: resolution = make_resolution_plan(app, bench) - frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag + if "frappe" in resolution: + # Todo: Make frappe a terminal dependency for all frappe apps. + frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag if not is_bench_directory(bench_path): if not init_bench: @@ -376,12 +378,11 @@ def get_app( from bench.utils.system import init - frappe_app = apps_to_install["frappe"] bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) init( path=bench_path, - frappe_path=frappe_path if resolve else None, - frappe_branch=frappe_branch if resolve else branch, + frappe_path=frappe_path, + frappe_branch=frappe_branch if frappe_branch else branch, ) os.chdir(bench_path) bench_setup = True diff --git a/bench/bench.py b/bench/bench.py index 4e82f402..25b26c1f 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -160,7 +160,6 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.initialize_apps() - self.initialize_states() def initialize_states(self): try: @@ -171,9 +170,7 @@ class BenchApps(MutableSequence): self.states = json.loads(f.read() or "{}") def update_apps_states(self, app_name: str = None, resolution: str = None): - # Only tracking for app state for apps installed via `bench resolve-and-install`, - # Not all states can be maintained as some apps may not be install via bench resolve-and-install - # or may not be compatible with versioning + self.initialize_states() self.initialize_apps() apps_to_remove = [] for app in self.states: diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 87998d69..b0402bee 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -50,9 +50,11 @@ def is_frappe_app(directory: str) -> bool: def is_valid_frappe_branch(frappe_path: str, frappe_branch: str): - url = frappe_path - url += "/tree/" + frappe_branch if frappe_branch else "" - return requests.get(url).status_code != 404 + if "http" in frappe_path: + url = frappe_path + url += "/tree/" + frappe_branch if frappe_branch else "" + return requests.get(url).status_code != 404 + return True def log(message, level=0, no_log=False): From f2fe56962fd93953b80a7b304c00c797afb1bb57 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 10:18:14 +0530 Subject: [PATCH 025/108] fix: removed erroneous file push --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index de288e1e..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.formatting.provider": "black" -} \ No newline at end of file From 4feb07684a2552a2da8403fc2615888248390366 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:22:48 +0530 Subject: [PATCH 026/108] refactor: using hooks.py instead of toml files to read dependencies --- bench/app.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/bench/app.py b/bench/app.py index 7aa9b2e8..242ebf36 100755 --- a/bench/app.py +++ b/bench/app.py @@ -1,5 +1,4 @@ # imports - standard imports -from collections import OrderedDict import functools import json import logging @@ -9,6 +8,7 @@ import shutil import subprocess import sys import typing +from collections import OrderedDict from datetime import date from urllib.parse import urlparse @@ -219,15 +219,15 @@ class App(AppMeta): def _get_dependencies(self): from bench.utils.app import get_required_deps_url - import toml - info_file = None - required_url = get_required_deps_url(self.url, self.tag) - print(required_url) + required_url = get_required_deps_url(git_url=self.url, repo_name=self.repo, branch=self.tag) try: - info_file = toml.loads(requests.get(required_url).text) - except Exception: - click.echo("\nNo toml file found \n", err=True) + f = requests.get(required_url).text + lines = [x for x in f.split("\n") if x.strip().startswith("required_apps")] + required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) + return required_apps + except Exception as e: + return [] return info_file["required_apps"] if info_file else {} @@ -237,16 +237,17 @@ def make_resolution_plan(app: App, bench: "Bench"): """ resolution = OrderedDict() resolution[app.repo] = app - for app_name, branch in app._get_dependencies().items(): - dep_app = App(app_name, branch=branch, bench=bench) + + for app_name in app._get_dependencies(): + dep_app = App(app_name, bench=bench) + if dep_app.repo in resolution: + click.secho(f"{dep_app.repo} is already resolved skipping", fg="yellow") + continue resolution[dep_app.repo] = dep_app resolution.update(make_resolution_plan(dep_app, bench)) - if app_name in resolution: - print("Resolve this conflict!") return resolution - def add_to_appstxt(app, bench_path="."): from bench.bench import Bench @@ -341,7 +342,7 @@ def get_app( verbose=False, overwrite=False, init_bench=False, - resolve=False, + resolve_deps=False, ): """bench get-app clones a Frappe App from remote (GitHub or any other git server), and installs it on the current bench. This also resolves dependencies based on the @@ -350,9 +351,9 @@ def get_app( If the bench_path is not a bench directory, a new bench is created named using the git_url parameter. """ - from bench.bench import Bench import bench as _bench import bench.cli as bench_cli + from bench.bench import Bench from bench.utils.app import check_existing_dir bench = Bench(bench_path) @@ -363,7 +364,7 @@ def get_app( bench_setup = False frappe_path, frappe_branch = None, None - if resolve: + if resolve_deps: resolution = make_resolution_plan(app, bench) if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. @@ -395,7 +396,7 @@ def get_app( "color": None, }) - if resolve: + if resolve_deps: install_resolved_deps( resolution, bench_path=bench_path, From 1cd914933f27ab36121bd5c995e8de74c1f0aa1c Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:24:41 +0530 Subject: [PATCH 027/108] refactor: changed resolve to resolve-deps --- bench/commands/make.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bench/commands/make.py b/bench/commands/make.py index c19e71cd..ac48f410 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -134,7 +134,7 @@ def drop(path): "--init-bench", is_flag=True, default=False, help="Initialize Bench if not in one" ) @click.option( - "--resolve/--no-resolve", + "--resolve-deps", is_flag=True, default=False, help="Resolve dependencies before installing app", @@ -146,7 +146,7 @@ def get_app( overwrite=False, skip_assets=False, init_bench=False, - resolve=False, + resolve_deps=False, ): "clone an app from the internet and set it up in your bench" from bench.app import get_app @@ -157,7 +157,7 @@ def get_app( skip_assets=skip_assets, overwrite=overwrite, init_bench=init_bench, - resolve=resolve, + resolve_deps=resolve_deps, ) @click.command("new-app", help="Create a new Frappe application under apps folder") From 58319a21b7b40ddc76704e7505ce8ca90a2ae882 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 6 Feb 2022 01:25:22 +0530 Subject: [PATCH 028/108] feat: added support for fetching dependencies from hooks.py --- bench/utils/app.py | 5 +++-- bench/utils/system.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index f7a6d5cc..13241290 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -164,11 +164,12 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) -def get_required_deps_url(git_url, branch="master", deps="info.toml"): +def get_required_deps_url(git_url, repo_name, branch, deps="hooks.py"): git_url = ( git_url.replace(".git", "").replace("github.com", "raw.github.com") ) - git_url += f"/{branch}/{deps}" if branch else f"/{deps}" + branch = branch if branch else "develop" + git_url += f"/{branch}/{repo_name}/{deps}" return git_url def get_remote(app, bench_path="."): diff --git a/bench/utils/system.py b/bench/utils/system.py index 2ad8cd16..5d6aaea9 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -86,7 +86,7 @@ def init( bench_path=path, skip_assets=True, verbose=verbose, - resolve=False, + resolve_deps=False, ) # fetch remote apps using config file - deprecate this! From 5b641758cf64dd82362905cf79c192bf9ce0f502 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Feb 2022 12:24:50 +0530 Subject: [PATCH 029/108] fix: added code removed via erroneous commit --- bench/app.py | 29 ++++++++++++++--------------- bench/bench.py | 16 ++++++++-------- bench/utils/app.py | 4 ++++ 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/bench/app.py b/bench/app.py index 242ebf36..9c467a8a 100755 --- a/bench/app.py +++ b/bench/app.py @@ -107,7 +107,6 @@ class AppMeta: self.tag = self.tag or self.branch def _setup_details_from_installed_apps(self): - self.branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) self.org, self.repo, self.tag = os.path.split( os.path.join(self.bench.name, "apps", self.name) )[-2:] + (self.branch,) @@ -183,24 +182,25 @@ class App(AppMeta): shutil.move(active_app_path, archived_app_path) @step(title="Installing App {repo}", success="App {repo} Installed") - def install(self, skip_assets=False, verbose=False, restart_bench=True): + def install(self, skip_assets=False, verbose=False, restart_bench=True, resolved=False): import bench.cli from bench.utils.app import get_app_name verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) - - # TODO: this should go inside install_app only tho - issue: default/resolved branch - setup_app_dependencies( - repo_name=self.repo, - bench_path=self.bench.name, - branch=self.tag, - verbose=verbose, - skip_assets=skip_assets, - ) + if not resolved: + # TODO: this should go inside install_app only tho - issue: default/resolved branch + setup_app_dependencies( + repo_name=self.repo, + bench_path=self.bench.name, + branch=self.tag, + verbose=verbose, + skip_assets=skip_assets, + ) install_app( app=app_name, + tag=self.tag, bench_path=self.bench.name, verbose=verbose, skip_assets=skip_assets, @@ -211,7 +211,6 @@ class App(AppMeta): def install_resolved_apps(self, *args, **kwargs): self.get() self.install(*args, **kwargs, resolved=True) - self.bench.apps.update_apps_states(self.repo, self.tag) @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): @@ -226,11 +225,9 @@ class App(AppMeta): lines = [x for x in f.split("\n") if x.strip().startswith("required_apps")] required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) return required_apps - except Exception as e: + except Exception: return [] - return info_file["required_apps"] if info_file else {} - def make_resolution_plan(app: App, bench: "Bench"): """ decide what apps and versions to install and in what order @@ -482,6 +479,7 @@ def new_app(app, no_git=None, bench_path="."): def install_app( app, + tag, bench_path=".", verbose=False, no_cache=False, @@ -513,6 +511,7 @@ def install_app( bench.run("yarn install", cwd=app_path) bench.apps.sync() + bench.apps.update_apps_states(app, tag) if not skip_assets: build_assets(bench_path=bench_path, app=app) diff --git a/bench/bench.py b/bench/bench.py index 25b26c1f..21c40c3d 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -57,7 +57,6 @@ class Bench(Base, Validator): def __init__(self, path): self.name = path self.cwd = os.path.abspath(path) - self.apps_states = os.path.join(self.name, "sites", "apps_states.json") self.exists = is_bench_directory(self.name) self.setup = BenchSetup(self) @@ -159,19 +158,20 @@ class Bench(Base, Validator): class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench + self.states_path = os.path.join(self.bench.name, "sites", "apps_states.json") self.initialize_apps() - def initialize_states(self): + def set_states(self): try: - with open(self.bench.apps_states, "r") as f: + with open(self.states_path, "r") as f: self.states = json.loads(f.read() or "{}") except FileNotFoundError: - with open(self.bench.apps_states, "w") as f: + with open(self.states_path, "w+") as f: self.states = json.loads(f.read() or "{}") def update_apps_states(self, app_name: str = None, resolution: str = None): - self.initialize_states() self.initialize_apps() + self.set_states() apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -180,14 +180,14 @@ class BenchApps(MutableSequence): for app in apps_to_remove: del self.states[app] - if app_name and resolution: - version = get_current_version(app_name) + if app_name: + version = get_current_version(app_name, self.bench.name) self.states[app_name] = { "resolution": resolution, "version": version, } - with open(self.bench.apps_states, "w") as f: + with open(self.states_path, "w") as f: f.write(json.dumps(self.states, indent=4)) def sync(self): diff --git a/bench/utils/app.py b/bench/utils/app.py index 13241290..e86a0304 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -208,6 +208,10 @@ def get_app_name(bench_path, repo_name): return repo_name +def check_existing_dir(bench_path, repo_name): + cloned_path = os.path.join(bench_path, "apps", repo_name) + dir_already_exists = os.path.isdir(cloned_path) + return dir_already_exists, cloned_path def get_current_version(app, bench_path="."): current_version = None From 95b0834932145008ba13259781938e16d04b7e85 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 17 Feb 2022 21:38:01 +0530 Subject: [PATCH 030/108] feat: printing resolved dependencies when using resolve-deps flag wih get-app --- bench/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bench/app.py b/bench/app.py index 9c467a8a..1f8df84e 100755 --- a/bench/app.py +++ b/bench/app.py @@ -363,6 +363,8 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) + click.secho("Apps to be installed:", fg="yellow") + print("\n".join([app.name for app in reversed(resolution.values())])) if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag From cccd76729f271d08179473ea18755b7062270d17 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Feb 2022 16:51:34 +0530 Subject: [PATCH 031/108] test: get-app resolve_deps --- bench/tests/test_init.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bench/tests/test_init.py b/bench/tests/test_init.py index 80018b59..69221b14 100755 --- a/bench/tests/test_init.py +++ b/bench/tests/test_init.py @@ -107,6 +107,20 @@ class TestBenchInit(TestBenchBase): app_installed_in_env = TEST_FRAPPE_APP in subprocess.check_output(["bench", "pip", "freeze"], cwd=bench_path).decode('utf8') self.assertTrue(app_installed_in_env) + def test_get_app_resolve_deps(self): + FRAPPE_APP = "healthcare" + self.init_bench("test-bench") + bench_path = os.path.join(self.benches_path, "test-bench") + exec_cmd(f"bench get-app {FRAPPE_APP} --resolve-deps", cwd=bench_path) + self.assertTrue(os.path.exists(os.path.join(bench_path, "apps", FRAPPE_APP))) + + states_path = os.path.join(bench_path, "sites", "apps_states.json") + self.assert_(os.path.exists(states_path)) + + with open(states_path, "r") as f: + states = json.load(f) + + self.assert_(FRAPPE_APP in states) def test_install_app(self): bench_name = "test-bench" From 94105c0aa8e565a25d6243fe9484098629de258d Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 17 Feb 2022 21:48:40 +0530 Subject: [PATCH 032/108] fix: minor fixes --- bench/app.py | 2 +- bench/utils/system.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index 1f8df84e..951e0a54 100755 --- a/bench/app.py +++ b/bench/app.py @@ -481,7 +481,7 @@ def new_app(app, no_git=None, bench_path="."): def install_app( app, - tag, + tag=None, bench_path=".", verbose=False, no_cache=False, diff --git a/bench/utils/system.py b/bench/utils/system.py index 5d6aaea9..ab27893c 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -101,7 +101,7 @@ def init( bench_path=path, skip_assets=True, verbose=verbose, - resolve=False, + resolve_deps=False, ) if not skip_assets: From 0d51206241881c268065d60169f012386d173ead Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 25 Feb 2022 16:51:06 +0530 Subject: [PATCH 033/108] refactor: ignoring app deps when install app without --resolve-deps --- bench/app.py | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/bench/app.py b/bench/app.py index 951e0a54..7700ab0b 100755 --- a/bench/app.py +++ b/bench/app.py @@ -189,13 +189,9 @@ class App(AppMeta): verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) if not resolved: - # TODO: this should go inside install_app only tho - issue: default/resolved branch - setup_app_dependencies( - repo_name=self.repo, - bench_path=self.bench.name, - branch=self.tag, - verbose=verbose, - skip_assets=skip_assets, + click.secho( + f"Ignoring dependencies of {self.name} to install dependencies use --resolve-deps", + fg="yellow", ) install_app( @@ -301,35 +297,6 @@ def remove_from_excluded_apps_txt(app, bench_path="."): return write_excluded_apps_txt(apps, bench_path=bench_path) -def setup_app_dependencies( - repo_name, bench_path=".", branch=None, skip_assets=False, verbose=False -): - # branch kwarg is somewhat of a hack here; since we're assuming the same branches for all apps - # for eg: if you're installing erpnext@develop, you'll want frappe@develop and healthcare@develop too - import glob - import bench.cli - from bench.bench import Bench - - verbose = bench.cli.verbose or verbose - apps_path = os.path.join(os.path.abspath(bench_path), "apps") - files = glob.glob(os.path.join(apps_path, repo_name, "**", "hooks.py")) - - if files: - with open(files[0]) as f: - lines = [x for x in f.read().split("\n") if x.strip().startswith("required_apps")] - if lines: - required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) - # TODO: when the time comes, add version check here - for app in required_apps: - if app not in Bench(bench_path).apps: - get_app( - app, - bench_path=bench_path, - branch=branch, - skip_assets=skip_assets, - verbose=verbose, - ) - def get_app( git_url, @@ -364,7 +331,9 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) click.secho("Apps to be installed:", fg="yellow") - print("\n".join([app.name for app in reversed(resolution.values())])) + for idx, app in enumerate(reversed(resolution.values()), start=1): + print(f"{idx}. {app.name}") + if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. frappe_path, frappe_branch = resolution["frappe"].url, resolution["frappe"].tag From a26f3ce564035e601e154ebadaa3af2c4deab4b9 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 28 Feb 2022 14:29:05 +0530 Subject: [PATCH 034/108] fix: logging when in init-bench --- bench/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index 7700ab0b..f7b114d7 100755 --- a/bench/app.py +++ b/bench/app.py @@ -188,7 +188,7 @@ class App(AppMeta): verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) - if not resolved: + if not resolved and self.repo != "frappe": click.secho( f"Ignoring dependencies of {self.name} to install dependencies use --resolve-deps", fg="yellow", From a1e1572937a32609f5d9cc89bf0384de4493dbfd Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Tue, 1 Mar 2022 18:11:13 +0530 Subject: [PATCH 035/108] test: get_required_deps_url() --- bench/tests/test_utils.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 602dbb38..9cae1924 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -23,3 +23,15 @@ class TestUtils(unittest.TestCase): ] ) ) + + def test_get_required_deps_url(self): + self.assertEqual( + get_required_deps_url(git_url="https://github.com/frappe/frappe.git", branch=None, repo_name="frappe"), + "https://raw.github.com/frappe/frappe/develop/frappe/hooks.py", + ) + self.assertEqual( + get_required_deps_url( + git_url="https://github.com/frappe/frappe.git", branch="version-13", repo_name="frappe" + ), + "https://raw.github.com/frappe/frappe/version-13/frappe/hooks.py", + ) From 6609ad20c4747424c9e039ab72151310993026bc Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Tue, 1 Mar 2022 18:11:46 +0530 Subject: [PATCH 036/108] test: is_valid_frappe_branch() --- bench/tests/test_utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 9cae1924..772f6aaf 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -35,3 +35,10 @@ class TestUtils(unittest.TestCase): ), "https://raw.github.com/frappe/frappe/version-13/frappe/hooks.py", ) + + def test_is_valid_frappe_branch(self): + self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="")) + self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="develop")) + self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-13")) + self.assertFalse(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-1")) + From 03a435330b18ce57af00e2afc21fff5da2850da1 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Tue, 1 Mar 2022 18:15:34 +0530 Subject: [PATCH 037/108] test: app states creation & update --- bench/tests/test_utils.py | 38 +++++++++++++++++++++++++++++++++++++- bench/utils/app.py | 10 ++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 772f6aaf..bcf58f6c 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -1,8 +1,11 @@ +import os +import shutil import unittest from bench.app import App from bench.bench import Bench -from bench.utils import exec_cmd +from bench.utils import exec_cmd, is_valid_frappe_branch +from bench.utils.app import get_required_deps_url class TestUtils(unittest.TestCase): @@ -42,3 +45,36 @@ class TestUtils(unittest.TestCase): self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-13")) self.assertFalse(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-1")) + def test_app_states(self): + bench_dir = "./sandbox" + sites_dir = os.path.join(bench_dir, "sites") + + if not os.path.exists(sites_dir): + os.makedirs(sites_dir) + + fake_bench = Bench(bench_dir) + + fake_bench.apps.set_states() + + self.assertTrue(hasattr(fake_bench.apps, "states")) + self.assertTrue(os.path.exists(os.path.join(sites_dir, "apps_states.json"))) + + fake_bench.apps.states = {"frappe": {"resolution": None, "version": "14.0.0-dev"}} + fake_bench.apps.update_apps_states() + + self.assertEqual(fake_bench.apps.states, {}) + + frappe_path = os.path.join(bench_dir, "apps", "frappe", "frappe") + + os.makedirs(frappe_path) + + with open(os.path.join(frappe_path, "__init__.py"), "w+") as f: + f.write("__version__ = '11.0'") + + fake_bench.apps.update_apps_states("frappe") + + self.assertIn("frappe", fake_bench.apps.states) + self.assertIn("version", fake_bench.apps.states["frappe"]) + self.assertEqual("11.0", fake_bench.apps.states["frappe"]["version"]) + + shutil.rmtree(bench_dir) diff --git a/bench/utils/app.py b/bench/utils/app.py index e86a0304..caa4bbe0 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -107,7 +107,9 @@ def switch_to_develop(apps=None, bench_path=".", upgrade=True): def get_version_from_string(contents, field="__version__"): - match = re.search(r"^(\s*%s\s*=\s*['\\\"])(.+?)(['\"])(?sm)" % field, contents) + match = re.search(r"^(\s*%s\s*=\s*['\\\"])(.+?)(['\"])" % field, contents, flags=(re.S | re.M)) + if not match: + raise Exception("No match was found") return match.group(2) @@ -164,14 +166,16 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) + def get_required_deps_url(git_url, repo_name, branch, deps="hooks.py"): git_url = ( git_url.replace(".git", "").replace("github.com", "raw.github.com") ) branch = branch if branch else "develop" - git_url += f"/{branch}/{repo_name}/{deps}" + git_url += f"/{branch}/{repo_name}/{deps}" return git_url + def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) contents = subprocess.check_output( @@ -208,11 +212,13 @@ def get_app_name(bench_path, repo_name): return repo_name + def check_existing_dir(bench_path, repo_name): cloned_path = os.path.join(bench_path, "apps", repo_name) dir_already_exists = os.path.isdir(cloned_path) return dir_already_exists, cloned_path + def get_current_version(app, bench_path="."): current_version = None repo_dir = get_repo_dir(app, bench_path=bench_path) From 7ea09ce7c903910907d55ebfd6b979e75426fa1a Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Wed, 2 Mar 2022 15:11:18 +0530 Subject: [PATCH 038/108] test: get_dependencies() --- bench/tests/test_utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index bcf58f6c..63147df2 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -78,3 +78,9 @@ class TestUtils(unittest.TestCase): self.assertEqual("11.0", fake_bench.apps.states["frappe"]["version"]) shutil.rmtree(bench_dir) + + def test_get_dependencies(self): + fake_app = App("frappe/healthcare@develop") + self.assertIn("erpnext", fake_app._get_dependencies()) + fake_app = App("frappe/not_healthcare@not-a-branch") + self.assertTrue(len(fake_app._get_dependencies()) == 0) From b0ae3ae359c691fd9587562aa3912c6e9b60447b Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 2 Mar 2022 22:44:12 +0530 Subject: [PATCH 039/108] perf: Using github API --- bench/app.py | 21 +++++++++++++++------ bench/utils/app.py | 16 +++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/bench/app.py b/bench/app.py index 37ba560a..c750b18f 100755 --- a/bench/app.py +++ b/bench/app.py @@ -215,17 +215,26 @@ class App(AppMeta): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") def _get_dependencies(self): - from bench.utils.app import get_required_deps_url + from bench.utils.app import get_required_deps - required_url = get_required_deps_url(git_url=self.url, repo_name=self.repo, branch=self.tag) try: - f = requests.get(required_url).text - lines = [x for x in f.split("\n") if x.strip().startswith("required_apps")] - required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) - return required_apps + required_deps = get_required_deps( + self.org, self.repo, self.tag or self.branch + ) + lines = [ + x + for x in required_deps.split("\n") + if x.strip().startswith("required_apps") + ] + required_apps = eval( + lines[0].strip("required_apps").strip().lstrip("=").strip() + ) except Exception: return [] + return required_apps + + def make_resolution_plan(app: App, bench: "Bench"): """ decide what apps and versions to install and in what order diff --git a/bench/utils/app.py b/bench/utils/app.py index e86a0304..6d5f0f32 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -164,13 +164,15 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) -def get_required_deps_url(git_url, repo_name, branch, deps="hooks.py"): - git_url = ( - git_url.replace(".git", "").replace("github.com", "raw.github.com") - ) - branch = branch if branch else "develop" - git_url += f"/{branch}/{repo_name}/{deps}" - return git_url +def get_required_deps(org, name, branch, deps="hooks.py"): + import requests + import base64 + + url = f"https://api.github.com/repos/{org}/{name}/contents/{name}/{deps}" + params = {"branch": branch or "develop"} + res = requests.get(url=url, params=params).json() + return base64.decodebytes(res["content"].encode()).decode() + def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) From fbee987490dbd937439d3dc2c67ffbc148b41082 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 2 Mar 2022 23:08:31 +0530 Subject: [PATCH 040/108] test: removed required urls tests as it's covered in test_get_dependencies --- bench/tests/test_utils.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 63147df2..f3708f95 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -4,8 +4,7 @@ import unittest from bench.app import App from bench.bench import Bench -from bench.utils import exec_cmd, is_valid_frappe_branch -from bench.utils.app import get_required_deps_url +from bench.utils import is_valid_frappe_branch class TestUtils(unittest.TestCase): @@ -27,18 +26,6 @@ class TestUtils(unittest.TestCase): ) ) - def test_get_required_deps_url(self): - self.assertEqual( - get_required_deps_url(git_url="https://github.com/frappe/frappe.git", branch=None, repo_name="frappe"), - "https://raw.github.com/frappe/frappe/develop/frappe/hooks.py", - ) - self.assertEqual( - get_required_deps_url( - git_url="https://github.com/frappe/frappe.git", branch="version-13", repo_name="frappe" - ), - "https://raw.github.com/frappe/frappe/version-13/frappe/hooks.py", - ) - def test_is_valid_frappe_branch(self): self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="")) self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="develop")) From c71fd096ccd916cf41b3144d07bc8ac7f29fb5b4 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 2 Mar 2022 23:09:20 +0530 Subject: [PATCH 041/108] refactor: removed code added through merge commit --- bench/utils/app.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index a04d60ac..eedd2400 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -166,6 +166,7 @@ def get_current_branch(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) + def get_required_deps(org, name, branch, deps="hooks.py"): import requests import base64 @@ -176,15 +177,6 @@ def get_required_deps(org, name, branch, deps="hooks.py"): return base64.decodebytes(res["content"].encode()).decode() -def get_required_deps_url(git_url, repo_name, branch, deps="hooks.py"): - git_url = ( - git_url.replace(".git", "").replace("github.com", "raw.github.com") - ) - branch = branch if branch else "develop" - git_url += f"/{branch}/{repo_name}/{deps}" - return git_url - - def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) contents = subprocess.check_output( @@ -226,11 +218,6 @@ def check_existing_dir(bench_path, repo_name): dir_already_exists = os.path.isdir(cloned_path) return dir_already_exists, cloned_path -def check_existing_dir(bench_path, repo_name): - cloned_path = os.path.join(bench_path, "apps", repo_name) - dir_already_exists = os.path.isdir(cloned_path) - return dir_already_exists, cloned_path - def get_current_version(app, bench_path="."): current_version = None From 6c15327348789cefe474fa183c2d39cca6d45d78 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Mar 2022 18:06:03 +0530 Subject: [PATCH 042/108] fix: fixed frappe branch check --- bench/utils/__init__.py | 15 ++++++++++----- bench/utils/system.py | 6 +----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index b0402bee..160387d9 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -49,12 +49,17 @@ def is_frappe_app(directory: str) -> bool: return bool(is_frappe_app) -def is_valid_frappe_branch(frappe_path: str, frappe_branch: str): +def is_valid_frappe_branch(frappe_path, frappe_branch): if "http" in frappe_path: - url = frappe_path - url += "/tree/" + frappe_branch if frappe_branch else "" - return requests.get(url).status_code != 404 - return True + frappe_path = frappe_path.replace(".git", "") + try: + owner, repo = frappe_path.split("/")[3], frappe_path.split("/")[4] + except IndexError: + raise InvalidRemoteException + git_api = f"https://api.github.com/repos/{owner}/{repo}/branches" + res = requests.get(git_api).json() + if "message" in res or frappe_branch not in [x["name"] for x in res]: + raise InvalidRemoteException def log(message, level=0, no_log=False): diff --git a/bench/utils/system.py b/bench/utils/system.py index ab27893c..152759d0 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -75,11 +75,7 @@ def init( # remote apps else: frappe_path = frappe_path or "https://github.com/frappe/frappe.git" - frappe_branch = ( - frappe_branch - if is_valid_frappe_branch(frappe_path, frappe_branch) - else None - ) + is_valid_frappe_branch(frappe_path=frappe_path, frappe_branch=frappe_branch) get_app( frappe_path, branch=frappe_branch, From 17888a5ce5193e7c4b1fedcd2a4ea86c91cefed6 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 7 Mar 2022 19:16:29 +0530 Subject: [PATCH 043/108] test: Updated tests for is_valid_frappe_branch --- bench/tests/test_utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index f3708f95..da52a0cf 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -5,7 +5,7 @@ import unittest from bench.app import App from bench.bench import Bench from bench.utils import is_valid_frappe_branch - +from bench.exceptions import InvalidRemoteException class TestUtils(unittest.TestCase): def test_app_utils(self): @@ -27,10 +27,11 @@ class TestUtils(unittest.TestCase): ) def test_is_valid_frappe_branch(self): - self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="")) - self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/frappe", frappe_branch="develop")) - self.assertTrue(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-13")) - self.assertFalse(is_valid_frappe_branch("https://github.com/frappe/erpnext", frappe_branch="version-1")) + with self.assertRaises(InvalidRemoteException): + is_valid_frappe_branch("https://github.com/frappe/frappe.git", frappe_branch="random-branch") + is_valid_frappe_branch("https://github.com/random/random.git", frappe_branch="random-branch") + + is_valid_frappe_branch("https://github.com/frappe/frappe.git", frappe_branch="develop") def test_app_states(self): bench_dir = "./sandbox" From 79765e6b25a09eb93e6ca67c6ff8e5ea1f565fba Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Thu, 10 Mar 2022 12:59:14 +0530 Subject: [PATCH 044/108] fix: check for valid branch in resolve --- bench/app.py | 2 ++ bench/utils/__init__.py | 10 +++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/bench/app.py b/bench/app.py index c750b18f..b2cddf75 100755 --- a/bench/app.py +++ b/bench/app.py @@ -24,6 +24,7 @@ from bench.utils import ( get_available_folder_name, is_bench_directory, is_git_url, + is_valid_frappe_branch, log, run_frappe_cmd, ) @@ -244,6 +245,7 @@ def make_resolution_plan(app: App, bench: "Bench"): for app_name in app._get_dependencies(): dep_app = App(app_name, bench=bench) + is_valid_frappe_branch(dep_app.url, dep_app.branch) if dep_app.repo in resolution: click.secho(f"{dep_app.repo} is already resolved skipping", fg="yellow") continue diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 160387d9..ada125a8 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -58,7 +58,7 @@ def is_valid_frappe_branch(frappe_path, frappe_branch): raise InvalidRemoteException git_api = f"https://api.github.com/repos/{owner}/{repo}/branches" res = requests.get(git_api).json() - if "message" in res or frappe_branch not in [x["name"] for x in res]: + if "message" in res or (frappe_branch and frappe_branch not in [x["name"] for x in res]): raise InvalidRemoteException @@ -76,9 +76,7 @@ def log(message, level=0, no_log=False): color, prefix = levels.get(level, levels[0]) if bench.cli.from_command_line and bench.cli.dynamic_feed: - bench.LOG_BUFFER.append( - {"prefix": prefix, "message": message, "color": color} - ) + bench.LOG_BUFFER.append({"prefix": prefix, "message": message, "color": color}) if no_log: click.secho(message, fg=color) @@ -196,9 +194,7 @@ def get_git_version() -> float: def get_cmd_output(cmd, cwd=".", _raise=True): output = "" try: - output = subprocess.check_output( - cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE, encoding="utf-8" - ).strip() + output = subprocess.check_output(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE, encoding="utf-8").strip() except subprocess.CalledProcessError as e: if e.output: output = e.output From d178b08abbfbc287b6f92424401cd72c941f9ff3 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 10 Mar 2022 18:33:49 +0530 Subject: [PATCH 045/108] feat: verbose env setup & checking for compatible versions --- bench/app.py | 23 ++++++++++++++--------- bench/bench.py | 9 +++++---- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/bench/app.py b/bench/app.py index b2cddf75..4f74fc32 100755 --- a/bench/app.py +++ b/bench/app.py @@ -343,7 +343,7 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) - click.secho("Apps to be installed:", fg="yellow") + click.secho("Following apps will be installed", fg="yellow") for idx, app in enumerate(reversed(resolution.values()), start=1): print(f"{idx}. {app.name}") @@ -379,6 +379,7 @@ def get_app( if resolve_deps: install_resolved_deps( + bench, resolution, bench_path=bench_path, skip_assets=skip_assets, @@ -412,6 +413,7 @@ def get_app( app.install(verbose=verbose, skip_assets=skip_assets) def install_resolved_deps( + bench, resolution, bench_path=".", skip_assets=False, @@ -424,16 +426,19 @@ def install_resolved_deps( del resolution["frappe"] for repo_name, app in reversed(resolution.items()): - existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) + existing_dir, _ = check_existing_dir(bench_path, repo_name) if existing_dir: - if click.confirm( - f"A directory for the application '{repo_name}' already exists. " - "Do you want to continue and overwrite it?" - ): - click.secho(f"Removing {repo_name}", fg="yellow") - shutil.rmtree(cloned_path) + if bench.apps.states[repo_name]["resolution"] != app.tag: + click.secho( + f"Incompatible version of {repo_name} is already installed", + fg="yellow", + ) app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) - + else: + click.secho( + f"Compatible version of {repo_name} is already installed", + fg="yellow", + ) continue app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) diff --git a/bench/bench.py b/bench/bench.py index 21c40c3d..93a83956 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -160,18 +160,16 @@ class BenchApps(MutableSequence): self.bench = bench self.states_path = os.path.join(self.bench.name, "sites", "apps_states.json") self.initialize_apps() + self.set_states() def set_states(self): try: with open(self.states_path, "r") as f: self.states = json.loads(f.read() or "{}") except FileNotFoundError: - with open(self.states_path, "w+") as f: - self.states = json.loads(f.read() or "{}") + self.states = {} def update_apps_states(self, app_name: str = None, resolution: str = None): - self.initialize_apps() - self.set_states() apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -281,6 +279,9 @@ class BenchSetup(Base): - install frappe python dependencies """ import bench.cli + import click + + click.secho("Setting Up Environment", fg="yellow") frappe = os.path.join(self.bench.name, "apps", "frappe") virtualenv = get_venv_path() From 9004e1dd6a473a40bfb43fe663867e117e076f95 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Thu, 10 Mar 2022 23:06:47 +0530 Subject: [PATCH 046/108] feat: add commit hash and branch in `states.json` --- bench/app.py | 4 ++-- bench/bench.py | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/bench/app.py b/bench/app.py index 4f74fc32..7ec14047 100755 --- a/bench/app.py +++ b/bench/app.py @@ -428,7 +428,7 @@ def install_resolved_deps( for repo_name, app in reversed(resolution.items()): existing_dir, _ = check_existing_dir(bench_path, repo_name) if existing_dir: - if bench.apps.states[repo_name]["resolution"] != app.tag: + if bench.apps.states[repo_name]["resolution"]["branch"] != app.tag: click.secho( f"Incompatible version of {repo_name} is already installed", fg="yellow", @@ -500,7 +500,7 @@ def install_app( bench.run("yarn install", cwd=app_path) bench.apps.sync() - bench.apps.update_apps_states(app, tag) + bench.apps.update_apps_states(app, branch=tag) if not skip_assets: build_assets(bench_path=bench_path, app=app) diff --git a/bench/bench.py b/bench/bench.py index 93a83956..3c757349 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -1,11 +1,12 @@ # imports - standard imports +import subprocess import functools import os import shutil import json import sys import logging -from typing import List, MutableSequence, TYPE_CHECKING +from typing import List, MutableSequence, TYPE_CHECKING, Union # imports - module imports import bench @@ -159,6 +160,7 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.states_path = os.path.join(self.bench.name, "sites", "apps_states.json") + self.apps_path = os.path.join(self.bench.name, "apps") self.initialize_apps() self.set_states() @@ -169,7 +171,7 @@ class BenchApps(MutableSequence): except FileNotFoundError: self.states = {} - def update_apps_states(self, app_name: str = None, resolution: str = None): + def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None): apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -180,8 +182,23 @@ class BenchApps(MutableSequence): if app_name: version = get_current_version(app_name, self.bench.name) + + app_dir = os.path.join(self.apps_path, app_name) + if not branch: + branch = ( + subprocess + .check_output("git rev-parse --abbrev-ref HEAD", shell=True, cwd=app_dir) + .decode("utf-8") + .rstrip() + ) + + commit_hash = subprocess.check_output(f"git rev-parse {branch}", shell=True, cwd=app_dir).decode("utf-8").rstrip() + self.states[app_name] = { - "resolution": resolution, + "resolution": { + "commit_hash":commit_hash, + "branch": branch + }, "version": version, } From 5bb3b52f23bf01ca138a359eaf22e020007375f8 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Thu, 10 Mar 2022 23:07:15 +0530 Subject: [PATCH 047/108] fix: print required by in output --- bench/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index 7ec14047..7a873b78 100755 --- a/bench/app.py +++ b/bench/app.py @@ -155,6 +155,7 @@ class App(AppMeta): self, name: str, branch: str = None, bench: "Bench" = None, *args, **kwargs ): self.bench = bench + self.required_by = None super().__init__(name, branch, *args, **kwargs) @step(title="Fetching App {repo}", success="App {repo} Fetched") @@ -246,6 +247,7 @@ def make_resolution_plan(app: App, bench: "Bench"): for app_name in app._get_dependencies(): dep_app = App(app_name, bench=bench) is_valid_frappe_branch(dep_app.url, dep_app.branch) + dep_app.required_by = app.name if dep_app.repo in resolution: click.secho(f"{dep_app.repo} is already resolved skipping", fg="yellow") continue @@ -345,7 +347,7 @@ def get_app( resolution = make_resolution_plan(app, bench) click.secho("Following apps will be installed", fg="yellow") for idx, app in enumerate(reversed(resolution.values()), start=1): - print(f"{idx}. {app.name}") + print(f"{idx}. {app.name} {f'required by {app.required_by}' if app.required_by else ''}") if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. From 56f7016e6926a04d0f5c6538f2e51d8601c24e07 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Thu, 10 Mar 2022 23:07:44 +0530 Subject: [PATCH 048/108] docs: rename states.json file --- bench/bench.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/bench.py b/bench/bench.py index 3c757349..2f92b67e 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -159,7 +159,7 @@ class Bench(Base, Validator): class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench - self.states_path = os.path.join(self.bench.name, "sites", "apps_states.json") + self.states_path = os.path.join(self.bench.name, "sites", "apps.json") self.apps_path = os.path.join(self.bench.name, "apps") self.initialize_apps() self.set_states() From c71432de3ce569d5646dfffcaa213e7fe7f6d854 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Thu, 10 Mar 2022 23:09:53 +0530 Subject: [PATCH 049/108] test: update remove file creation check --- bench/tests/test_utils.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index da52a0cf..69e959f5 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -1,11 +1,14 @@ import os import shutil +import subprocess import unittest +from tabnanny import check from bench.app import App from bench.bench import Bench -from bench.utils import is_valid_frappe_branch from bench.exceptions import InvalidRemoteException +from bench.utils import is_valid_frappe_branch + class TestUtils(unittest.TestCase): def test_app_utils(self): @@ -42,23 +45,27 @@ class TestUtils(unittest.TestCase): fake_bench = Bench(bench_dir) - fake_bench.apps.set_states() - self.assertTrue(hasattr(fake_bench.apps, "states")) - self.assertTrue(os.path.exists(os.path.join(sites_dir, "apps_states.json"))) - fake_bench.apps.states = {"frappe": {"resolution": None, "version": "14.0.0-dev"}} + fake_bench.apps.states = { + "frappe": {"resolution": {"branch": "develop", "commit_hash": "234rwefd"}, "version": "14.0.0-dev"} + } fake_bench.apps.update_apps_states() self.assertEqual(fake_bench.apps.states, {}) - frappe_path = os.path.join(bench_dir, "apps", "frappe", "frappe") + frappe_path = os.path.join(bench_dir, "apps", "frappe") - os.makedirs(frappe_path) + os.makedirs(os.path.join(frappe_path, "frappe")) - with open(os.path.join(frappe_path, "__init__.py"), "w+") as f: + subprocess.run(["git", "init"], cwd=frappe_path, capture_output=True, check=True) + + with open(os.path.join(frappe_path, "frappe", "__init__.py"), "w+") as f: f.write("__version__ = '11.0'") + subprocess.run(["git", "add", "."], cwd=frappe_path, capture_output=True, check=True) + subprocess.run(["git", "commit", "-m", "temp"], cwd=frappe_path, capture_output=True, check=True) + fake_bench.apps.update_apps_states("frappe") self.assertIn("frappe", fake_bench.apps.states) From 2287f3f7ec04cbf872049029809b65da829f6efd Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 00:59:26 +0530 Subject: [PATCH 050/108] fix: check current branch --- bench/app.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/bench/app.py b/bench/app.py index 7a873b78..fcfa6abe 100755 --- a/bench/app.py +++ b/bench/app.py @@ -428,19 +428,34 @@ def install_resolved_deps( del resolution["frappe"] for repo_name, app in reversed(resolution.items()): - existing_dir, _ = check_existing_dir(bench_path, repo_name) + existing_dir, path_to_app = check_existing_dir(bench_path, repo_name) if existing_dir: - if bench.apps.states[repo_name]["resolution"]["branch"] != app.tag: - click.secho( - f"Incompatible version of {repo_name} is already installed", - fg="yellow", + is_compatible = False + + installed_branch = bench.apps.states[repo_name]["resolution"]["branch"].strip() + if app.tag is None: + current_remote = ( + subprocess.check_output(f"git config branch.{installed_branch}.remote", shell=True, cwd=path_to_app) + .decode("utf-8") + .rstrip() ) - app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) + + default_branch = ( + subprocess.check_output( + f"git symbolic-ref refs/remotes/{current_remote}/HEAD", shell=True, cwd=path_to_app + ) + .decode("utf-8") + .rsplit("/")[-1] + .strip() + ) + is_compatible = default_branch == installed_branch else: - click.secho( - f"Compatible version of {repo_name} is already installed", - fg="yellow", - ) + is_compatible = bench.apps.states[repo_name]["resolution"]["branch"] == app.tag + + click.secho( + f"{'C' if is_compatible else 'Inc'}ompatible version of {repo_name} is already installed", + fg="yellow", + ) continue app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) From 7de1fa7c4def60f10826694a8647a4910c0b0711 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 01:00:09 +0530 Subject: [PATCH 051/108] style: black-ish --- bench/app.py | 69 ++++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 43 deletions(-) diff --git a/bench/app.py b/bench/app.py index fcfa6abe..8045766d 100755 --- a/bench/app.py +++ b/bench/app.py @@ -50,7 +50,6 @@ class AppMeta: 3. frappe/healthcare@develop 4. healthcare 5. healthcare@develop, healthcare@v13.12.1 - 6. erpnext References for Version Identifiers: * https://www.python.org/dev/peps/pep-0440/#version-specifiers @@ -59,7 +58,7 @@ class AppMeta: class Healthcare(AppConfig): dependencies = [{"frappe/erpnext": "~13.17.0"}] """ - self.name = name.rstrip('/') + self.name = name.rstrip("/") self.remote_server = "github.com" self.to_clone = to_clone self.on_disk = False @@ -67,9 +66,7 @@ class AppMeta: self.from_apps = False self.is_url = False self.branch = branch - self.mount_path = os.path.abspath( - os.path.join(urlparse(self.name).netloc, urlparse(self.name).path) - ) + self.mount_path = os.path.abspath(os.path.join(urlparse(self.name).netloc, urlparse(self.name).path)) self.setup_details() def setup_details(self): @@ -99,9 +96,7 @@ class AppMeta: self._setup_details_from_name_tag() def _setup_details_from_mounted_disk(self): - self.org, self.repo, self.tag = os.path.split(self.mount_path)[-2:] + ( - self.branch, - ) + self.org, self.repo, self.tag = os.path.split(self.mount_path)[-2:] + (self.branch,) def _setup_details_from_name_tag(self): self.org, self.repo, self.tag = fetch_details_from_tag(self.name) @@ -151,9 +146,7 @@ class AppMeta: @functools.lru_cache(maxsize=None) class App(AppMeta): - def __init__( - self, name: str, branch: str = None, bench: "Bench" = None, *args, **kwargs - ): + def __init__(self, name: str, branch: str = None, bench: "Bench" = None, *args, **kwargs): self.bench = bench self.required_by = None super().__init__(name, branch, *args, **kwargs) @@ -176,17 +169,13 @@ class App(AppMeta): def remove(self): active_app_path = os.path.join("apps", self.repo) archived_path = os.path.join("archived", "apps") - archived_name = get_available_folder_name( - f"{self.repo}-{date.today()}", archived_path - ) + archived_name = get_available_folder_name(f"{self.repo}-{date.today()}", archived_path) archived_app_path = os.path.join(archived_path, archived_name) log(f"App moved from {active_app_path} to {archived_app_path}") shutil.move(active_app_path, archived_app_path) @step(title="Installing App {repo}", success="App {repo} Installed") - def install( - self, skip_assets=False, verbose=False, resolved=False, restart_bench=True - ): + def install(self, skip_assets=False, verbose=False, resolved=False, restart_bench=True): import bench.cli from bench.utils.app import get_app_name @@ -220,17 +209,9 @@ class App(AppMeta): from bench.utils.app import get_required_deps try: - required_deps = get_required_deps( - self.org, self.repo, self.tag or self.branch - ) - lines = [ - x - for x in required_deps.split("\n") - if x.strip().startswith("required_apps") - ] - required_apps = eval( - lines[0].strip("required_apps").strip().lstrip("=").strip() - ) + required_deps = get_required_deps(self.org, self.repo, self.tag or self.branch) + lines = [x for x in required_deps.split("\n") if x.strip().startswith("required_apps")] + required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) except Exception: return [] @@ -312,7 +293,6 @@ def remove_from_excluded_apps_txt(app, bench_path="."): return write_excluded_apps_txt(apps, bench_path=bench_path) - def get_app( git_url, branch=None, @@ -372,12 +352,14 @@ def get_app( bench_setup = True if bench_setup and bench_cli.from_command_line and bench_cli.dynamic_feed: - _bench.LOG_BUFFER.append({ - "message": f"Fetching App {repo_name}", - "prefix": click.style('⏼', fg='bright_yellow'), - "is_parent": True, - "color": None, - }) + _bench.LOG_BUFFER.append( + { + "message": f"Fetching App {repo_name}", + "prefix": click.style("⏼", fg="bright_yellow"), + "is_parent": True, + "color": None, + } + ) if resolve_deps: install_resolved_deps( @@ -414,6 +396,7 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) + def install_resolved_deps( bench, resolution, @@ -459,11 +442,10 @@ def install_resolved_deps( continue app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) + def new_app(app, no_git=None, bench_path="."): if bench.FRAPPE_VERSION in (0, None): - raise NotInBenchDirectoryError( - f"{os.path.realpath(bench_path)} is not a valid bench directory." - ) + raise NotInBenchDirectoryError(f"{os.path.realpath(bench_path)} is not a valid bench directory.") # For backwards compatibility app = app.lower().replace(" ", "_").replace("-", "_") @@ -471,10 +453,7 @@ def new_app(app, no_git=None, bench_path="."): args = ["make-app", apps, app] if no_git: if bench.FRAPPE_VERSION < 14: - click.secho( - "Frappe v14 or greater is needed for '--no-git' flag", - fg="red" - ) + click.secho("Frappe v14 or greater is needed for '--no-git' flag", fg="red") return args.append(no_git) @@ -525,6 +504,7 @@ def install_app( if restart_bench: bench.reload() + def pull_apps(apps=None, bench_path=".", reset=False): """Check all apps if there no local changes, pull""" from bench.bench import Bench @@ -615,7 +595,10 @@ def install_apps_from_path(path, bench_path="."): apps = get_apps_json(path) for app in apps: get_app( - app["url"], branch=app.get("branch"), bench_path=bench_path, skip_assets=True, + app["url"], + branch=app.get("branch"), + bench_path=bench_path, + skip_assets=True, ) From 67df07b732389e04f1847d978c47c5f21e9f028a Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 13:22:49 +0530 Subject: [PATCH 052/108] fix: empty states json condition --- bench/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index 8045766d..b9e5e80a 100755 --- a/bench/app.py +++ b/bench/app.py @@ -415,7 +415,11 @@ def install_resolved_deps( if existing_dir: is_compatible = False - installed_branch = bench.apps.states[repo_name]["resolution"]["branch"].strip() + try: + installed_branch = bench.apps.states[repo_name]["resolution"]["branch"].strip() + except: + installed_branch = None + if app.tag is None: current_remote = ( subprocess.check_output(f"git config branch.{installed_branch}.remote", shell=True, cwd=path_to_app) @@ -433,7 +437,7 @@ def install_resolved_deps( ) is_compatible = default_branch == installed_branch else: - is_compatible = bench.apps.states[repo_name]["resolution"]["branch"] == app.tag + is_compatible = installed_branch == app.tag click.secho( f"{'C' if is_compatible else 'Inc'}ompatible version of {repo_name} is already installed", From af3c871632843ffb8c8d0e78de097e15f0b8fd1e Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 13:34:54 +0530 Subject: [PATCH 053/108] fix: missing installed branch --- bench/app.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index b9e5e80a..80d75410 100755 --- a/bench/app.py +++ b/bench/app.py @@ -418,8 +418,14 @@ def install_resolved_deps( try: installed_branch = bench.apps.states[repo_name]["resolution"]["branch"].strip() except: - installed_branch = None - + installed_branch = ( + subprocess. + check_output("git rev-parse --abbrev-ref HEAD", shell=True, cwd=path_to_app) + .decode("utf-8") + .rstrip() + ) + + if app.tag is None: current_remote = ( subprocess.check_output(f"git config branch.{installed_branch}.remote", shell=True, cwd=path_to_app) From 9ac091b4d9caac1567fe5cd4f16ba5096cbc8e22 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Fri, 11 Mar 2022 14:12:31 +0530 Subject: [PATCH 054/108] feat: add required and order of install --- bench/app.py | 52 ++++++++++++++++++++++++++++++-------------------- bench/bench.py | 10 ++++++---- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/bench/app.py b/bench/app.py index 80d75410..6053e50c 100755 --- a/bench/app.py +++ b/bench/app.py @@ -149,6 +149,7 @@ class App(AppMeta): def __init__(self, name: str, branch: str = None, bench: "Bench" = None, *args, **kwargs): self.bench = bench self.required_by = None + self.local_resolution = [] super().__init__(name, branch, *args, **kwargs) @step(title="Fetching App {repo}", success="App {repo} Fetched") @@ -194,6 +195,7 @@ class App(AppMeta): verbose=verbose, skip_assets=skip_assets, restart_bench=restart_bench, + resolution = self.local_resolution ) @step(title="Cloning and installing {repo}", success="App {repo} Installed") @@ -217,6 +219,11 @@ class App(AppMeta): return required_apps + def update_app_state(self): + from bench.bench import Bench + bench = Bench(self.bench.name) + bench.apps.sync(self.name, self.tag, self.local_resolution) + def make_resolution_plan(app: App, bench: "Bench"): """ @@ -234,6 +241,7 @@ def make_resolution_plan(app: App, bench: "Bench"): continue resolution[dep_app.repo] = dep_app resolution.update(make_resolution_plan(dep_app, bench)) + app.local_resolution = [repo_name for repo_name, _ in reversed(resolution.items())] return resolution @@ -424,31 +432,33 @@ def install_resolved_deps( .decode("utf-8") .rstrip() ) - - - if app.tag is None: - current_remote = ( - subprocess.check_output(f"git config branch.{installed_branch}.remote", shell=True, cwd=path_to_app) - .decode("utf-8") - .rstrip() - ) - - default_branch = ( - subprocess.check_output( - f"git symbolic-ref refs/remotes/{current_remote}/HEAD", shell=True, cwd=path_to_app + try: + if app.tag is None: + current_remote = ( + subprocess.check_output(f"git config branch.{installed_branch}.remote", shell=True, cwd=path_to_app) + .decode("utf-8") + .rstrip() ) - .decode("utf-8") - .rsplit("/")[-1] - .strip() - ) - is_compatible = default_branch == installed_branch - else: - is_compatible = installed_branch == app.tag + + default_branch = ( + subprocess.check_output( + f"git symbolic-ref refs/remotes/{current_remote}/HEAD", shell=True, cwd=path_to_app + ) + .decode("utf-8") + .rsplit("/")[-1] + .strip() + ) + is_compatible = default_branch == installed_branch + else: + is_compatible = installed_branch == app.tag + except: + is_compatible = False click.secho( f"{'C' if is_compatible else 'Inc'}ompatible version of {repo_name} is already installed", fg="yellow", ) + app.update_app_state() continue app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) @@ -480,6 +490,7 @@ def install_app( no_cache=False, restart_bench=True, skip_assets=False, + resolution=[] ): import bench.cli as bench_cli from bench.bench import Bench @@ -505,8 +516,7 @@ def install_app( if os.path.exists(os.path.join(app_path, "package.json")): bench.run("yarn install", cwd=app_path) - bench.apps.sync() - bench.apps.update_apps_states(app, branch=tag) + bench.apps.sync(app, required=resolution, branch=tag) if not skip_assets: build_assets(bench_path=bench_path, app=app) diff --git a/bench/bench.py b/bench/bench.py index 2f92b67e..0baa47a4 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -125,7 +125,6 @@ class Bench(Base, Validator): self.validate_app_uninstall(app) self.apps.remove(App(app, bench=self, to_clone=False)) self.apps.sync() - self.apps.update_apps_states() # self.build() - removed because it seems unnecessary self.reload() @@ -171,7 +170,7 @@ class BenchApps(MutableSequence): except FileNotFoundError: self.states = {} - def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None): + def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -199,16 +198,19 @@ class BenchApps(MutableSequence): "commit_hash":commit_hash, "branch": branch }, + "required":required, + "order of install":len(self.states) + 1, "version": version, } with open(self.states_path, "w") as f: f.write(json.dumps(self.states, indent=4)) - def sync(self): + def sync(self,app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): self.initialize_apps() with open(self.bench.apps_txt, "w") as f: - return f.write("\n".join(self.apps)) + f.write("\n".join(self.apps)) + self.update_apps_states(app_name, branch, required) def initialize_apps(self): is_installed = lambda app: app in installed_packages From bb16b733587fe03719266c70445cf42e3ad9c57f Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 11 Mar 2022 18:38:21 +0530 Subject: [PATCH 055/108] test: Updated tests --- bench/tests/test_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/tests/test_init.py b/bench/tests/test_init.py index 69221b14..e3002307 100755 --- a/bench/tests/test_init.py +++ b/bench/tests/test_init.py @@ -114,7 +114,7 @@ class TestBenchInit(TestBenchBase): exec_cmd(f"bench get-app {FRAPPE_APP} --resolve-deps", cwd=bench_path) self.assertTrue(os.path.exists(os.path.join(bench_path, "apps", FRAPPE_APP))) - states_path = os.path.join(bench_path, "sites", "apps_states.json") + states_path = os.path.join(bench_path, "sites", "apps.json") self.assert_(os.path.exists(states_path)) with open(states_path, "r") as f: From 8dd92c32b1b9dc0f51e7fe1f1bfc7f9738dedbb8 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Mon, 14 Mar 2022 05:17:08 +0530 Subject: [PATCH 056/108] feat: alternative request to validate branch --- bench/utils/__init__.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index ada125a8..84401203 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -50,15 +50,22 @@ def is_frappe_app(directory: str) -> bool: def is_valid_frappe_branch(frappe_path, frappe_branch): - if "http" in frappe_path: + if "http" in frappe_path and frappe_branch: frappe_path = frappe_path.replace(".git", "") try: owner, repo = frappe_path.split("/")[3], frappe_path.split("/")[4] except IndexError: raise InvalidRemoteException - git_api = f"https://api.github.com/repos/{owner}/{repo}/branches" - res = requests.get(git_api).json() - if "message" in res or (frappe_branch and frappe_branch not in [x["name"] for x in res]): + git_api_req = f"https://api.github.com/repos/{owner}/{repo}/branches" + res = requests.get(git_api_req).json() + + if "message" in res: + # slower alternative with no rate limit + github_req = f'https://github.com/{owner}/{repo}/tree/{frappe_branch}' + if requests.get(github_req).status_code != 200: + raise InvalidRemoteException + + elif frappe_branch not in [x["name"] for x in res]: raise InvalidRemoteException From adbe19ac1b55fdf6d86d9b331bad535c5bf16918 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Mon, 14 Mar 2022 05:17:29 +0530 Subject: [PATCH 057/108] docs: is_valid_frappe_branch() docstring --- bench/utils/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 84401203..4427cac7 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -49,7 +49,18 @@ def is_frappe_app(directory: str) -> bool: return bool(is_frappe_app) -def is_valid_frappe_branch(frappe_path, frappe_branch): +def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): + """ Check if a branch exists in a repo. Throws InvalidRemoteException if branch is not found + + Uses github's api without auth to query branch. + If rate limited by gitapi, requests are sent to github.com + + :param frappe_path: git url + :type frappe_path: str + :param frappe_branch: branch to check + :type frappe_branch: str + :raises InvalidRemoteException: branch for this repo doesn't exist + """ if "http" in frappe_path and frappe_branch: frappe_path = frappe_path.replace(".git", "") try: From d0bd294dc2aa1a7b5c08616bc1c802346aaf60d4 Mon Sep 17 00:00:00 2001 From: saxenabhishek Date: Mon, 14 Mar 2022 05:33:44 +0530 Subject: [PATCH 058/108] feat: slower alternative for get_required_deps --- bench/utils/app.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index eedd2400..434af655 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -171,9 +171,14 @@ def get_required_deps(org, name, branch, deps="hooks.py"): import requests import base64 - url = f"https://api.github.com/repos/{org}/{name}/contents/{name}/{deps}" + git_api_url = f"https://api.github.com/repos/{org}/{name}/contents/{name}/{deps}" params = {"branch": branch or "develop"} - res = requests.get(url=url, params=params).json() + res = requests.get(url=git_api_url, params=params).json() + + if "message" in res: + git_url = f"https://raw.githubusercontent.com/{org}/{name}/{params['branch']}/{deps}" + return requests.get(git_url).text + return base64.decodebytes(res["content"].encode()).decode() From d33d987b2c6286401d7cf3f9902e348d2fad356d Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 17 Mar 2022 11:41:34 +0530 Subject: [PATCH 059/108] fix: User messages on get-app actions --- bench/app.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bench/app.py b/bench/app.py index 6053e50c..cb7b0786 100755 --- a/bench/app.py +++ b/bench/app.py @@ -184,7 +184,7 @@ class App(AppMeta): app_name = get_app_name(self.bench.name, self.repo) if not resolved and self.repo != "frappe": click.secho( - f"Ignoring dependencies of {self.name} to install dependencies use --resolve-deps", + f"Ignoring dependencies of {self.name}. To install dependencies use --resolve-deps", fg="yellow", ) @@ -333,9 +333,9 @@ def get_app( if resolve_deps: resolution = make_resolution_plan(app, bench) - click.secho("Following apps will be installed", fg="yellow") + click.secho("Following apps will be installed", fg="bright_blue") for idx, app in enumerate(reversed(resolution.values()), start=1): - print(f"{idx}. {app.name} {f'required by {app.required_by}' if app.required_by else ''}") + print(f"{idx}. {app.name} {f'(required by {app.required_by})' if app.required_by else ''}") if "frappe" in resolution: # Todo: Make frappe a terminal dependency for all frappe apps. @@ -454,9 +454,10 @@ def install_resolved_deps( except: is_compatible = False + prefix = 'C' if is_compatible else 'Inc' click.secho( - f"{'C' if is_compatible else 'Inc'}ompatible version of {repo_name} is already installed", - fg="yellow", + f"{prefix}ompatible version of {repo_name} is already installed", + fg="green" if is_compatible else "red", ) app.update_app_state() continue From ad000f1be7c8a74746d276e2633651513d5297ff Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 17 Mar 2022 11:41:57 +0530 Subject: [PATCH 060/108] fix: Don't handle `BaseException`s --- bench/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index cb7b0786..d7889a69 100755 --- a/bench/app.py +++ b/bench/app.py @@ -425,7 +425,7 @@ def install_resolved_deps( try: installed_branch = bench.apps.states[repo_name]["resolution"]["branch"].strip() - except: + except Exception: installed_branch = ( subprocess. check_output("git rev-parse --abbrev-ref HEAD", shell=True, cwd=path_to_app) @@ -451,7 +451,7 @@ def install_resolved_deps( is_compatible = default_branch == installed_branch else: is_compatible = installed_branch == app.tag - except: + except Exception: is_compatible = False prefix = 'C' if is_compatible else 'Inc' From bbca3e9fab11491f071d7e5b9ef27d9d9b32c75c Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 20 Mar 2022 22:15:41 +0530 Subject: [PATCH 061/108] feat: mimicking get-app behaviour when app is already installed --- bench/app.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index d7889a69..78244347 100755 --- a/bench/app.py +++ b/bench/app.py @@ -460,7 +460,13 @@ def install_resolved_deps( fg="green" if is_compatible else "red", ) app.update_app_state() - continue + if click.confirm( + f"Do you wish to clone and install the already installed {prefix}ompatible app" + ): + click.secho(f"Removing installed app {app.name}", fg="yellow") + shutil.rmtree(path_to_app) + else: + continue app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) From 67638b19b70f7159679173b96dd0175ed51f3a1d Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 21 Mar 2022 14:52:04 +0530 Subject: [PATCH 062/108] refactor: added ignore resolution to ignore messages in install app --- bench/app.py | 13 ++++++++++--- bench/bench.py | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/bench/app.py b/bench/app.py index 78244347..043c7c8e 100755 --- a/bench/app.py +++ b/bench/app.py @@ -176,13 +176,20 @@ class App(AppMeta): shutil.move(active_app_path, archived_app_path) @step(title="Installing App {repo}", success="App {repo} Installed") - def install(self, skip_assets=False, verbose=False, resolved=False, restart_bench=True): + def install( + self, + skip_assets=False, + verbose=False, + resolved=False, + restart_bench=True, + ignore_resolution=False, + ): import bench.cli from bench.utils.app import get_app_name verbose = bench.cli.verbose or verbose app_name = get_app_name(self.bench.name, self.repo) - if not resolved and self.repo != "frappe": + if not resolved and self.repo != "frappe" and not ignore_resolution: click.secho( f"Ignoring dependencies of {self.name}. To install dependencies use --resolve-deps", fg="yellow", @@ -195,7 +202,7 @@ class App(AppMeta): verbose=verbose, skip_assets=skip_assets, restart_bench=restart_bench, - resolution = self.local_resolution + resolution=self.local_resolution ) @step(title="Cloning and installing {repo}", success="App {repo} Installed") diff --git a/bench/bench.py b/bench/bench.py index 0baa47a4..b0f6d50f 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -392,7 +392,9 @@ class BenchSetup(Base): print(f"Installing {len(apps)} applications...") for app in apps: - App(app, bench=self.bench, to_clone=False).install( skip_assets=True, restart_bench=False) + App(app, bench=self.bench, to_clone=False).install( + skip_assets=True, restart_bench=False, ignore_resolution=True + ) def python(self, apps=None): """Install and upgrade Python dependencies for specified / all installed apps on given Bench From b28cfab5502096e28dfbda332337b14635278fb2 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 21 Mar 2022 23:57:52 +0530 Subject: [PATCH 063/108] fix: add code removed via erroneous commit --- bench/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bench/app.py b/bench/app.py index 480d5d7e..9463c106 100755 --- a/bench/app.py +++ b/bench/app.py @@ -336,6 +336,7 @@ def get_app( repo_name = app.repo branch = app.tag bench_setup = False + restart_bench = not init_bench frappe_path, frappe_branch = None, None if resolve_deps: From 7ab469af84be283098a17a01526c0e5572a16e3a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 22 Mar 2022 12:51:29 +0530 Subject: [PATCH 064/108] fix: setup all apps is empty list is supplied --- bench/utils/bench.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/utils/bench.py b/bench/utils/bench.py index e0be48b9..8bf03aa2 100644 --- a/bench/utils/bench.py +++ b/bench/utils/bench.py @@ -74,7 +74,7 @@ def install_python_dev_dependencies(bench_path=".", apps=None, verbose=False): if isinstance(apps, str): apps = [apps] - elif apps is None: + elif not apps: apps = bench.get_installed_apps() for app in apps: From ddd498ebd03f3858705f1c6c8cbb8b4e2eac1a91 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 23 Mar 2022 18:18:37 +0530 Subject: [PATCH 065/108] feat: Added exception for invalid version --- bench/exceptions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bench/exceptions.py b/bench/exceptions.py index 0d517fe4..b3340915 100644 --- a/bench/exceptions.py +++ b/bench/exceptions.py @@ -30,3 +30,7 @@ class FeatureDoesNotExistError(CommandFailedError): class NotInBenchDirectoryError(Exception): pass + + +class VersionNotFound(Exception): + pass \ No newline at end of file From cd583f7822a5aaa29f09cc9ba016b6b23c1d9cc2 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 23 Mar 2022 18:20:49 +0530 Subject: [PATCH 066/108] refactor: Added messages on invalid remote exceptions --- bench/utils/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 8f4cc05c..b815fb3a 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -66,7 +66,7 @@ def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): try: owner, repo = frappe_path.split("/")[3], frappe_path.split("/")[4] except IndexError: - raise InvalidRemoteException + raise InvalidRemoteException("Invalid git url") git_api_req = f"https://api.github.com/repos/{owner}/{repo}/branches" res = requests.get(git_api_req).json() @@ -74,10 +74,10 @@ def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): # slower alternative with no rate limit github_req = f'https://github.com/{owner}/{repo}/tree/{frappe_branch}' if requests.get(github_req).status_code != 200: - raise InvalidRemoteException + raise InvalidRemoteException("Invalid git url") elif frappe_branch not in [x["name"] for x in res]: - raise InvalidRemoteException + raise InvalidRemoteException("Frappe branch does not exist") def log(message, level=0, no_log=False): From c8205e485decf56ab47947a950ae84581efb5749 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 23 Mar 2022 17:04:37 +0100 Subject: [PATCH 067/108] fix: support single required app --- bench/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bench/app.py b/bench/app.py index f8c1ba22..2df62aaa 100755 --- a/bench/app.py +++ b/bench/app.py @@ -282,6 +282,10 @@ def setup_app_dependencies( lines = [x for x in f.read().split("\n") if x.strip().startswith("required_apps")] if lines: required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) + + if isinstance(required_apps, str): + required_apps = [required_apps] + # TODO: when the time comes, add version check here for app in required_apps: if app not in Bench(bench_path).apps: From 3ec774946c7a581f4a0bf16de9e38ad177dfba66 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 23 Mar 2022 18:22:54 +0530 Subject: [PATCH 068/108] refactor: Appropriate message for invalid version --- bench/utils/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index 434af655..98c53514 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -7,6 +7,7 @@ from bench.exceptions import ( InvalidRemoteException, InvalidBranchException, CommandFailedError, + VersionNotFound, ) from bench.app import get_repo_dir @@ -109,7 +110,7 @@ def switch_to_develop(apps=None, bench_path=".", upgrade=True): def get_version_from_string(contents, field="__version__"): match = re.search(r"^(\s*%s\s*=\s*['\\\"])(.+?)(['\"])" % field, contents, flags=(re.S | re.M)) if not match: - raise Exception("No match was found") + raise VersionNotFound(f"{contents} is not a valid version") return match.group(2) From 0b862c07457c9914840bd0ce87a90306fec37456 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 25 Mar 2022 22:27:58 +0530 Subject: [PATCH 069/108] feat: Added support for mounted app in get_dependencies --- bench/app.py | 13 ++++++++++--- bench/utils/app.py | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/bench/app.py b/bench/app.py index 9463c106..790850b9 100755 --- a/bench/app.py +++ b/bench/app.py @@ -215,12 +215,19 @@ class App(AppMeta): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") def _get_dependencies(self): - from bench.utils.app import get_required_deps + from bench.utils.app import get_required_deps, required_apps_from_hooks + if self.on_disk: + required_deps = os.path.join(self.mount_path, self.repo,'hooks.py') + try: + print(required_apps_from_hooks(required_deps)) + except IndexError: + print(f"No dependencies for {self.repo}") + finally: + return try: required_deps = get_required_deps(self.org, self.repo, self.tag or self.branch) - lines = [x for x in required_deps.split("\n") if x.strip().startswith("required_apps")] - required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) + return required_apps_from_hooks(required_deps) except Exception: return [] diff --git a/bench/utils/app.py b/bench/utils/app.py index 98c53514..f04b0020 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -183,6 +183,14 @@ def get_required_deps(org, name, branch, deps="hooks.py"): return base64.decodebytes(res["content"].encode()).decode() +def required_apps_from_hooks(required_deps): + with open(required_deps) as f: + required_deps = f.read() + lines = [x for x in required_deps.split("\n") if x.strip().startswith("required_apps")] + required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) + return required_apps + + def get_remote(app, bench_path="."): repo_dir = get_repo_dir(app, bench_path=bench_path) contents = subprocess.check_output( From 2f17da1bf08168a21b97587b52755f84e04a3e90 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 26 Mar 2022 00:06:17 +0530 Subject: [PATCH 070/108] fix: support for local and remote apps when looking for dependencies --- bench/app.py | 4 +--- bench/utils/app.py | 7 ++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bench/app.py b/bench/app.py index 790850b9..b14c6d82 100755 --- a/bench/app.py +++ b/bench/app.py @@ -220,7 +220,7 @@ class App(AppMeta): if self.on_disk: required_deps = os.path.join(self.mount_path, self.repo,'hooks.py') try: - print(required_apps_from_hooks(required_deps)) + print(required_apps_from_hooks(required_deps, local=True)) except IndexError: print(f"No dependencies for {self.repo}") finally: @@ -231,8 +231,6 @@ class App(AppMeta): except Exception: return [] - return required_apps - def update_app_state(self): from bench.bench import Bench bench = Bench(self.bench.name) diff --git a/bench/utils/app.py b/bench/utils/app.py index f04b0020..32e958f3 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -183,9 +183,10 @@ def get_required_deps(org, name, branch, deps="hooks.py"): return base64.decodebytes(res["content"].encode()).decode() -def required_apps_from_hooks(required_deps): - with open(required_deps) as f: - required_deps = f.read() +def required_apps_from_hooks(required_deps, local=False): + if local: + with open(required_deps) as f: + required_deps = f.read() lines = [x for x in required_deps.split("\n") if x.strip().startswith("required_apps")] required_apps = eval(lines[0].strip("required_apps").strip().lstrip("=").strip()) return required_apps From fc0be250c2a7f10b9bb9941df09dba8b0f749662 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 26 Mar 2022 22:46:28 +0530 Subject: [PATCH 071/108] feat: Exiting when app does not exist --- bench/app.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bench/app.py b/bench/app.py index b14c6d82..a55de782 100755 --- a/bench/app.py +++ b/bench/app.py @@ -70,6 +70,11 @@ class AppMeta: self.setup_details() def setup_details(self): + if not urlparse(self.name).netloc and not os.path.exists( + os.path.realpath(self.name) + ): + click.secho(f"{self.name} does not exist!", fg="yellow") + return # fetch meta from installed apps if ( not self.to_clone @@ -220,11 +225,9 @@ class App(AppMeta): if self.on_disk: required_deps = os.path.join(self.mount_path, self.repo,'hooks.py') try: - print(required_apps_from_hooks(required_deps, local=True)) + return required_apps_from_hooks(required_deps, local=True) except IndexError: - print(f"No dependencies for {self.repo}") - finally: - return + return [] try: required_deps = get_required_deps(self.org, self.repo, self.tag or self.branch) return required_apps_from_hooks(required_deps) From 4252b1100ec60bdc77964c55045ae43e2c59fa65 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 27 Mar 2022 16:54:30 +0530 Subject: [PATCH 072/108] test: updated tests --- bench/tests/test_utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 69e959f5..d61e714b 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -75,7 +75,10 @@ class TestUtils(unittest.TestCase): shutil.rmtree(bench_dir) def test_get_dependencies(self): - fake_app = App("frappe/healthcare@develop") + git_url = "https://github.com/frappe/healthcare" + branch = "develop" + fake_app = App(git_url, branch=branch) self.assertIn("erpnext", fake_app._get_dependencies()) - fake_app = App("frappe/not_healthcare@not-a-branch") + git_url = git_url.replace("healthcare", "erpnext") + fake_app = App(git_url) self.assertTrue(len(fake_app._get_dependencies()) == 0) From ff98aaf198a34eac0afbcb67ddac0fb52b5df880 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 27 Mar 2022 17:21:27 +0530 Subject: [PATCH 073/108] fix: reverted erroneous commits --- bench/app.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bench/app.py b/bench/app.py index a55de782..fc2c421f 100755 --- a/bench/app.py +++ b/bench/app.py @@ -70,11 +70,6 @@ class AppMeta: self.setup_details() def setup_details(self): - if not urlparse(self.name).netloc and not os.path.exists( - os.path.realpath(self.name) - ): - click.secho(f"{self.name} does not exist!", fg="yellow") - return # fetch meta from installed apps if ( not self.to_clone From 4441528d3f0ee1dd345506ac96c2de7ebc7977e7 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 28 Mar 2022 21:00:41 +0530 Subject: [PATCH 074/108] style: black-ish code --- bench/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index fc2c421f..3321fab9 100755 --- a/bench/app.py +++ b/bench/app.py @@ -14,7 +14,6 @@ from urllib.parse import urlparse # imports - third party imports import click -import requests # imports - module imports import bench @@ -66,7 +65,9 @@ class AppMeta: self.from_apps = False self.is_url = False self.branch = branch - self.mount_path = os.path.abspath(os.path.join(urlparse(self.name).netloc, urlparse(self.name).path)) + self.mount_path = os.path.abspath( + os.path.join(urlparse(self.name).netloc, urlparse(self.name).path) + ) self.setup_details() def setup_details(self): From bd105a66095456add51e3e04e0840af93dacdb6d Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 28 Mar 2022 21:02:38 +0530 Subject: [PATCH 075/108] perf: caching deps and orgs, repos to avoid time taking checks & added slower version to check org and repo --- bench/utils/__init__.py | 4 ++++ bench/utils/app.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index b815fb3a..450d9492 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -8,6 +8,7 @@ import sys from glob import glob from shlex import split from typing import List, Tuple +from functools import lru_cache # imports - third party imports import click @@ -421,6 +422,7 @@ def clear_command_cache(bench_path="."): print("Bench command cache doesn't exist in this folder!") +@lru_cache(maxsize=5) def find_org(org_repo): import requests @@ -428,6 +430,8 @@ def find_org(org_repo): for org in ["frappe", "erpnext"]: res = requests.head(f"https://api.github.com/repos/{org}/{org_repo}") + if "message" in res.json(): + res = requests.head(f"https://github.com/{org}/{org_repo}") if res.ok: return org, org_repo diff --git a/bench/utils/app.py b/bench/utils/app.py index 32e958f3..d07422e1 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -10,6 +10,7 @@ from bench.exceptions import ( VersionNotFound, ) from bench.app import get_repo_dir +from functools import lru_cache def is_version_upgrade(app="frappe", bench_path=".", branch=None): @@ -168,6 +169,7 @@ def get_current_branch(app, bench_path="."): return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir) +@lru_cache(maxsize=5) def get_required_deps(org, name, branch, deps="hooks.py"): import requests import base64 From dbb19046df0fe72ed82fd87220fa7aa5a37d0e56 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 28 Mar 2022 21:59:17 +0530 Subject: [PATCH 076/108] fix: checking status code to determine rate limit --- bench/utils/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 450d9492..902a79c7 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -422,7 +422,6 @@ def clear_command_cache(bench_path="."): print("Bench command cache doesn't exist in this folder!") -@lru_cache(maxsize=5) def find_org(org_repo): import requests @@ -430,7 +429,7 @@ def find_org(org_repo): for org in ["frappe", "erpnext"]: res = requests.head(f"https://api.github.com/repos/{org}/{org_repo}") - if "message" in res.json(): + if res.status_code == 400: res = requests.head(f"https://github.com/{org}/{org_repo}") if res.ok: return org, org_repo From 3ac19ca4c6d3ab48065849e92878b3263ed9bdcf Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 1 Apr 2022 11:33:39 +0530 Subject: [PATCH 077/108] refactor: not sorting apps to maintain order of install --- bench/bench.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bench/bench.py b/bench/bench.py index b0f6d50f..4981f200 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -230,7 +230,6 @@ class BenchApps(MutableSequence): and is_installed(x) ) ] - self.apps.sort() except FileNotFoundError: self.apps = [] From 339875b2b14c7038fa9d2e355aba1357f5f6d10d Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 2 Apr 2022 15:05:24 +0530 Subject: [PATCH 078/108] refactor: added message while raising invalidremote exception --- bench/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 902a79c7..0c67e305 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -434,7 +434,7 @@ def find_org(org_repo): if res.ok: return org, org_repo - raise InvalidRemoteException + raise InvalidRemoteException(f"{org_repo} Not foung in frappe or erpnext") def fetch_details_from_tag(_tag: str) -> Tuple[str, str, str]: From a1a35eed0e4729d337894474bb56f2da7a32ba49 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 2 Apr 2022 15:41:21 +0530 Subject: [PATCH 079/108] feat: Added support for backward compatibility in states --- bench/bench.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/bench/bench.py b/bench/bench.py index 4981f200..35e97422 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -171,6 +171,22 @@ class BenchApps(MutableSequence): self.states = {} def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): + if self.apps and not os.path.exists(self.states_path): + # idx according to apps listed in apps.txt (backwards compatibility) + print("Found existing apps updating states...") + for idx, app in enumerate(self.apps, start=1): + self.states[app] = { + "resolution": { + "commit_hash": None, + "branch": None + }, + "required": required, + "idx": idx, + "version": get_current_version(app, self.bench.name), + } + with open(self.states_path, "w") as f: + f.write(json.dumps(self.states, indent=4)) + apps_to_remove = [] for app in self.states: if app not in self.apps: @@ -199,7 +215,7 @@ class BenchApps(MutableSequence): "branch": branch }, "required":required, - "order of install":len(self.states) + 1, + "idx":len(self.states) + 1, "version": version, } From 281d96ac160c84588f394beac43ddf4594c3bad3 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 4 Apr 2022 23:42:52 +0530 Subject: [PATCH 080/108] fix: Fixed order of install --- bench/bench.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bench/bench.py b/bench/bench.py index 35e97422..110d2859 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -173,6 +173,13 @@ class BenchApps(MutableSequence): def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): if self.apps and not os.path.exists(self.states_path): # idx according to apps listed in apps.txt (backwards compatibility) + # Keeping frappe as the first app. + if "frappe" in self.apps: + self.apps.remove("frappe") + self.apps.insert(0, "frappe") + with open(self.bench.apps_txt, "w") as f: + f.write("\n".join(self.apps)) + print("Found existing apps updating states...") for idx, app in enumerate(self.apps, start=1): self.states[app] = { @@ -184,8 +191,6 @@ class BenchApps(MutableSequence): "idx": idx, "version": get_current_version(app, self.bench.name), } - with open(self.states_path, "w") as f: - f.write(json.dumps(self.states, indent=4)) apps_to_remove = [] for app in self.states: @@ -195,7 +200,7 @@ class BenchApps(MutableSequence): for app in apps_to_remove: del self.states[app] - if app_name: + if app_name and app_name not in self.states: version = get_current_version(app_name, self.bench.name) app_dir = os.path.join(self.apps_path, app_name) From e9b0112a9cf69a9523e6bdf3a73301cd5c0f7e75 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Wed, 6 Apr 2022 12:05:58 +0530 Subject: [PATCH 081/108] fix: Check for unauthorized status code in response --- bench/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 0c67e305..169ba04a 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -429,7 +429,7 @@ def find_org(org_repo): for org in ["frappe", "erpnext"]: res = requests.head(f"https://api.github.com/repos/{org}/{org_repo}") - if res.status_code == 400: + if res.status_code in (400, 403): res = requests.head(f"https://github.com/{org}/{org_repo}") if res.ok: return org, org_repo From c8ef8224d473b6b087fce2343f87e90acbdb5a93 Mon Sep 17 00:00:00 2001 From: Aradhya Tripathi <67282231+Aradhya-Tripathi@users.noreply.github.com> Date: Tue, 19 Apr 2022 22:31:51 +0530 Subject: [PATCH 082/108] fix: fixed org, branch for apps mounted on disk (#1293) * fix: fixed org, branch for apps mounted on disk * fix: DRY-er code * fix: fixed loading apps when use_ssh is true * fix: removed flaky tests to test dependencies * fix: removed exception if app not found in frappe or erpnext org * fix: remote server * test: Updated tests fix: tests * fix: raise when org is not found * fix: setup requirements * fix: falling back to original method of mounted app initialization if git repo isn't found --- bench/app.py | 42 +++++++++++++++++++-------------------- bench/bench.py | 3 ++- bench/tests/test_init.py | 3 ++- bench/tests/test_utils.py | 9 --------- bench/utils/__init__.py | 2 +- 5 files changed, 25 insertions(+), 34 deletions(-) diff --git a/bench/app.py b/bench/app.py index 3321fab9..de5b1d92 100755 --- a/bench/app.py +++ b/bench/app.py @@ -14,6 +14,7 @@ from urllib.parse import urlparse # imports - third party imports import click +from git import Repo # imports - module imports import bench @@ -75,10 +76,10 @@ class AppMeta: if ( not self.to_clone and hasattr(self, "bench") - and os.path.exists(os.path.join(self.bench.name, "apps", self.name)) + and os.path.exists(self.mount_path) ): self.from_apps = True - self._setup_details_from_installed_apps() + self._setup_details_from_mounted_disk() # fetch meta for repo on mounted disk elif os.path.exists(self.mount_path): @@ -88,8 +89,6 @@ class AppMeta: # fetch meta for repo from remote git server - traditional get-app url elif is_git_url(self.name): self.is_url = True - if self.name.startswith("git@") or self.name.startswith("ssh://"): - self.use_ssh = True self._setup_details_from_git_url() # fetch meta from new styled name tags & first party apps on github @@ -97,39 +96,38 @@ class AppMeta: self._setup_details_from_name_tag() def _setup_details_from_mounted_disk(self): - self.org, self.repo, self.tag = os.path.split(self.mount_path)[-2:] + (self.branch,) + # If app is a git repo + self.git_repo = Repo(self.mount_path) + try: + self._setup_details_from_git_url(self.git_repo.remotes[0].url) + if not (self.branch or self.tag): + self.tag = self.branch = self.git_repo.active_branch.name + except IndexError: + self.org, self.repo, self.tag = os.path.split(self.mount_path)[-2:] + (self.branch,) def _setup_details_from_name_tag(self): self.org, self.repo, self.tag = fetch_details_from_tag(self.name) self.tag = self.tag or self.branch - def _setup_details_from_installed_apps(self): - self.org, self.repo, self.tag = os.path.split( - os.path.join(self.bench.name, "apps", self.name) - )[-2:] + (self.branch,) + def _setup_details_from_git_url(self, url=None): + return self.__setup_details_from_git(url) - def _setup_details_from_git_url(self): - return self.__setup_details_from_git() - - def __setup_details_from_git(self): - if self.use_ssh: - _first_part, _second_part = self.name.split(":") + def __setup_details_from_git(self, url=None): + name = url if url else self.name + if name.startswith("git@") or name.startswith("ssh://"): + self.use_ssh = True + _first_part, _second_part = name.split(":") self.remote_server = _first_part.split("@")[-1] self.org, _repo = _second_part.rsplit("/", 1) else: - self.remote_server, self.org, _repo = self.name.rsplit("/", 2) + protocal = "https://" if "https://" in name else "http://" + self.remote_server, self.org, _repo = name.replace(protocal, "").rsplit("/", 2) self.tag = self.branch self.repo = _repo.split(".")[0] @property def url(self): - if self.from_apps: - return os.path.abspath(os.path.join("apps", self.name)) - - if self.on_disk: - return self.mount_path - if self.is_url: return self.name diff --git a/bench/bench.py b/bench/bench.py index 110d2859..9d8d23de 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -412,7 +412,8 @@ class BenchSetup(Base): print(f"Installing {len(apps)} applications...") for app in apps: - App(app, bench=self.bench, to_clone=False).install( + path_to_app = os.path.join(self.bench.name, "apps", app) + App(path_to_app, bench=self.bench, to_clone=False).install( skip_assets=True, restart_bench=False, ignore_resolution=True ) diff --git a/bench/tests/test_init.py b/bench/tests/test_init.py index e3002307..329fedae 100755 --- a/bench/tests/test_init.py +++ b/bench/tests/test_init.py @@ -38,7 +38,8 @@ class TestBenchInit(TestBenchBase): def test_init(self, bench_name="test-bench", **kwargs): self.init_bench(bench_name, **kwargs) app = App("file:///tmp/frappe") - self.assertEqual(app.url, "/tmp/frappe") + self.assertEqual(app.mount_path, "/tmp/frappe") + self.assertEqual(app.url, "https://github.com/frappe/frappe.git") self.assert_folders(bench_name) self.assert_virtual_env(bench_name) self.assert_config(bench_name) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index d61e714b..3c2b3307 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -73,12 +73,3 @@ class TestUtils(unittest.TestCase): self.assertEqual("11.0", fake_bench.apps.states["frappe"]["version"]) shutil.rmtree(bench_dir) - - def test_get_dependencies(self): - git_url = "https://github.com/frappe/healthcare" - branch = "develop" - fake_app = App(git_url, branch=branch) - self.assertIn("erpnext", fake_app._get_dependencies()) - git_url = git_url.replace("healthcare", "erpnext") - fake_app = App(git_url) - self.assertTrue(len(fake_app._get_dependencies()) == 0) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 169ba04a..45cd1dce 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -434,7 +434,7 @@ def find_org(org_repo): if res.ok: return org, org_repo - raise InvalidRemoteException(f"{org_repo} Not foung in frappe or erpnext") + raise InvalidRemoteException(f"{org_repo} not found in frappe or erpnext") def fetch_details_from_tag(_tag: str) -> Tuple[str, str, str]: From d4ae743d10e6120f7e10d97fc3188c54ebdee0c1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 21 Apr 2022 13:05:13 +0530 Subject: [PATCH 083/108] chore: remove VM links (#1300) [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de2f4365..4d5c0f40 100755 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ The setup for each of these installations can be achieved in multiple ways: We recommend using either the Docker Installation or the Easy Install Script to setup a Production Environment. For Development, you may choose either of the three methods to setup an instance. -Otherwise, if you are looking to evaluate ERPNext, you can also download the [Virtual Machine Image](https://erpnext.com/download) or register for [a free trial on erpnext.com](https://erpnext.com/pricing). +Otherwise, if you are looking to evaluate ERPNext, you can register for [a free trial on erpnext.com](https://erpnext.com/pricing). ### Containerized Installation From eb715735bbb79e5edd39e29c1b0e5587d2d3712c Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Wed, 27 Apr 2022 20:17:04 +0530 Subject: [PATCH 084/108] fix: Misc fixes (#1294) * fix: use better api endpoint for is_valid_frappe_branch * fix: dont allow appnames starting with number and/or dot in them * fix: dont convert response to json * fix: using ls-remote to check for valid branches * refactor: changed the message displayed on invalid branch and invalid frappe path Co-authored-by: Aradhya Co-authored-by: Aradhya Tripathi <67282231+Aradhya-Tripathi@users.noreply.github.com> --- bench/app.py | 7 +++++++ bench/utils/__init__.py | 40 +++++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/bench/app.py b/bench/app.py index de5b1d92..e0da4e18 100755 --- a/bench/app.py +++ b/bench/app.py @@ -486,6 +486,13 @@ def new_app(app, no_git=None, bench_path="."): # For backwards compatibility app = app.lower().replace(" ", "_").replace("-", "_") + if app[0].isdigit() or "." in app: + click.secho( + "App names cannot start with numbers(digits) or have dot(.) in them", + fg="red" + ) + return + apps = os.path.abspath(os.path.join(bench_path, "apps")) args = ["make-app", apps, app] if no_git: diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 45cd1dce..d0bb0383 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -51,10 +51,9 @@ def is_frappe_app(directory: str) -> bool: def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): - """ Check if a branch exists in a repo. Throws InvalidRemoteException if branch is not found + """Check if a branch exists in a repo. Throws InvalidRemoteException if branch is not found - Uses github's api without auth to query branch. - If rate limited by gitapi, requests are sent to github.com + Uses native git command to check for branches on a remote. :param frappe_path: git url :type frappe_path: str @@ -62,23 +61,26 @@ def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): :type frappe_branch: str :raises InvalidRemoteException: branch for this repo doesn't exist """ - if "http" in frappe_path and frappe_branch: - frappe_path = frappe_path.replace(".git", "") + import subprocess + + if frappe_branch: try: - owner, repo = frappe_path.split("/")[3], frappe_path.split("/")[4] - except IndexError: - raise InvalidRemoteException("Invalid git url") - git_api_req = f"https://api.github.com/repos/{owner}/{repo}/branches" - res = requests.get(git_api_req).json() - - if "message" in res: - # slower alternative with no rate limit - github_req = f'https://github.com/{owner}/{repo}/tree/{frappe_branch}' - if requests.get(github_req).status_code != 200: - raise InvalidRemoteException("Invalid git url") - - elif frappe_branch not in [x["name"] for x in res]: - raise InvalidRemoteException("Frappe branch does not exist") + ret = subprocess.check_output( + ( + "git", + "ls-remote", + "--heads", + frappe_path, + frappe_branch, + ), + encoding="UTF-8", + ) + if not ret: + raise InvalidRemoteException( + f"Invalid {frappe_branch} for the remote {frappe_path}" + ) + except subprocess.CalledProcessError: + raise InvalidRemoteException(f"Invalid frappe path {frappe_path}") def log(message, level=0, no_log=False): From 31d9de4decacf62a5e8aec9d88855174dc929b51 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 Apr 2022 11:03:25 +0530 Subject: [PATCH 085/108] ci: Remove install script tests The script has been untested and unmaintained for a while. We've asked for help in maintenance but nobody seems to want to undertake it. With newer versions of distros and packages coming out each day, setups get more and more volatile. Making these tests run in the CI don't reflect how it would go down irl as no server comes with the same base config. Apart from the base sanity, these tests are just a waste of carbon. Removing them. --- .travis.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 97296634..f1d8b83f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,21 +60,6 @@ matrix: env: TEST=bench script: python -m unittest -v bench.tests.test_utils && python -m unittest -v bench.tests.test_init - - name: "Python 3.7 Easy Install" - python: 3.7 - env: TEST=easy_install - script: sudo python $TRAVIS_BUILD_DIR/install.py --user travis --run-travis --production --verbose - - - name: "Python 3.8 Easy Install" - python: 3.8 - env: TEST=easy_install - script: sudo python $TRAVIS_BUILD_DIR/install.py --user travis --run-travis --production --verbose - - - name: "Python 3.9 Easy Install" - python: 3.9 - env: TEST=easy_install - script: sudo python $TRAVIS_BUILD_DIR/install.py --user travis --run-travis --production --verbose - install: - pip3 install urllib3 pyOpenSSL ndg-httpsclient pyasn1 From 7b8f16bcb43e5390aabb06225870887cfda376fc Mon Sep 17 00:00:00 2001 From: Kelvin zawala <87924723+Zawala@users.noreply.github.com> Date: Tue, 3 May 2022 13:52:10 +0200 Subject: [PATCH 086/108] Get-app from port specific ssh git server (#1299) * Get-app from port specific ssh git server Current bench package fails to get an app from a private git server with a specific ssh port other than the normal 22. * test: Added tests for ssh ports Co-authored-by: Aradhya Tripathi <67282231+Aradhya-Tripathi@users.noreply.github.com> Co-authored-by: Aradhya --- bench/app.py | 2 +- bench/tests/test_utils.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index e0da4e18..6a9d4e97 100755 --- a/bench/app.py +++ b/bench/app.py @@ -116,7 +116,7 @@ class AppMeta: name = url if url else self.name if name.startswith("git@") or name.startswith("ssh://"): self.use_ssh = True - _first_part, _second_part = name.split(":") + _first_part, _second_part = self.name.rsplit(":", 1) self.remote_server = _first_part.split("@")[-1] self.org, _repo = _second_part.rsplit("/", 1) else: diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index 3c2b3307..f91e8785 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -73,3 +73,7 @@ class TestUtils(unittest.TestCase): self.assertEqual("11.0", fake_bench.apps.states["frappe"]["version"]) shutil.rmtree(bench_dir) + + def test_ssh_ports(self): + app = App("git@github.com:22:frappe/frappe") + self.assertEqual((app.use_ssh, app.org, app.repo), (True, "frappe", "frappe")) \ No newline at end of file From 49900ce74b6dcc109b5d5a2092fc9e35fb211a30 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 4 May 2022 18:08:13 +0530 Subject: [PATCH 087/108] fix: removed unnecessary requests while removing apps --- bench/bench.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bench/bench.py b/bench/bench.py index 9d8d23de..75d002f9 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -123,7 +123,8 @@ class Bench(Base, Validator): from bench.app import App self.validate_app_uninstall(app) - self.apps.remove(App(app, bench=self, to_clone=False)) + path_to_app = os.path.join(self.name, "apps", app) + self.apps.remove(App(path_to_app, bench=self, to_clone=False)) self.apps.sync() # self.build() - removed because it seems unnecessary self.reload() From b36c0723bec3c2a9229a3556151a5be29cc437c0 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 5 May 2022 19:57:37 +0530 Subject: [PATCH 088/108] feat: Made dynamic mount path if bench attr is passed to App class --- bench/app.py | 7 +++---- bench/bench.py | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/bench/app.py b/bench/app.py index de5b1d92..1acb8b65 100755 --- a/bench/app.py +++ b/bench/app.py @@ -73,11 +73,10 @@ class AppMeta: def setup_details(self): # fetch meta from installed apps - if ( - not self.to_clone - and hasattr(self, "bench") - and os.path.exists(self.mount_path) + if hasattr(self, "bench") and os.path.exists( + os.path.join(self.bench.name, "apps", self.name) ): + self.mount_path = os.path.join(self.bench.name, "apps", self.name) self.from_apps = True self._setup_details_from_mounted_disk() diff --git a/bench/bench.py b/bench/bench.py index 75d002f9..9d8d23de 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -123,8 +123,7 @@ class Bench(Base, Validator): from bench.app import App self.validate_app_uninstall(app) - path_to_app = os.path.join(self.name, "apps", app) - self.apps.remove(App(path_to_app, bench=self, to_clone=False)) + self.apps.remove(App(app, bench=self, to_clone=False)) self.apps.sync() # self.build() - removed because it seems unnecessary self.reload() From 66157f6c920f6d9c7781e6dea7cbd588258c8ac3 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 5 May 2022 20:21:39 +0530 Subject: [PATCH 089/108] test: updated tests for App init --- bench/app.py | 2 +- bench/tests/test_init.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index 315eec6c..47aef9a3 100755 --- a/bench/app.py +++ b/bench/app.py @@ -73,7 +73,7 @@ class AppMeta: def setup_details(self): # fetch meta from installed apps - if hasattr(self, "bench") and os.path.exists( + if self.bench and os.path.exists( os.path.join(self.bench.name, "apps", self.name) ): self.mount_path = os.path.join(self.bench.name, "apps", self.name) diff --git a/bench/tests/test_init.py b/bench/tests/test_init.py index 329fedae..0e2f1964 100755 --- a/bench/tests/test_init.py +++ b/bench/tests/test_init.py @@ -12,6 +12,7 @@ from bench.utils import exec_cmd from bench.release import get_bumped_version from bench.app import App from bench.tests.test_base import FRAPPE_BRANCH, TestBenchBase +from bench.bench import Bench # changed from frappe_theme because it wasn't maintained and incompatible, @@ -43,7 +44,9 @@ class TestBenchInit(TestBenchBase): self.assert_folders(bench_name) self.assert_virtual_env(bench_name) self.assert_config(bench_name) - + test_bench = Bench(bench_name) + app = App("frappe", bench=test_bench) + self.assertEqual(app.from_apps, True) def basic(self): try: From b8aa00898f47f9400601a5ffdbde14af0844b005 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 6 May 2022 14:10:09 +0530 Subject: [PATCH 090/108] fix: reverted changes made by erroneous commit --- bench/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index 47aef9a3..bb23f70c 100755 --- a/bench/app.py +++ b/bench/app.py @@ -115,7 +115,7 @@ class AppMeta: name = url if url else self.name if name.startswith("git@") or name.startswith("ssh://"): self.use_ssh = True - _first_part, _second_part = self.name.rsplit(":", 1) + _first_part, _second_part = name.rsplit(":", 1) self.remote_server = _first_part.split("@")[-1] self.org, _repo = _second_part.rsplit("/", 1) else: From 3f576168034c605f90c23860a82df74c79df2137 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Fri, 6 May 2022 18:01:27 +0200 Subject: [PATCH 091/108] fix: case-insensitive matching --- bench/config/templates/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/config/templates/nginx.conf b/bench/config/templates/nginx.conf index 77d865a2..2ea1e3e5 100644 --- a/bench/config/templates/nginx.conf +++ b/bench/config/templates/nginx.conf @@ -80,7 +80,7 @@ server { rewrite ^(.+)/index\.html$ $1 permanent; rewrite ^(.+)\.html$ $1 permanent; - location ~ ^/files/.*.(htm|html|svg|xml) { + location ~* ^/files/.*.(htm|html|svg|xml) { add_header Content-disposition "attachment"; try_files /{{ site_name }}/public/$uri @webserver; } From 35504f81b3340b544da87316046086a0134ae5ff Mon Sep 17 00:00:00 2001 From: Aradhya Date: Fri, 6 May 2022 22:31:42 +0530 Subject: [PATCH 092/108] feat: Added app_name attribute to App class --- bench/app.py | 13 ++++++++++++- bench/bench.py | 2 +- bench/utils/app.py | 12 ++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/bench/app.py b/bench/app.py index bb23f70c..f00f8acc 100755 --- a/bench/app.py +++ b/bench/app.py @@ -11,6 +11,7 @@ import typing from collections import OrderedDict from datetime import date from urllib.parse import urlparse +import os # imports - third party imports import click @@ -27,6 +28,7 @@ from bench.utils import ( is_valid_frappe_branch, log, run_frappe_cmd, + is_frappe_app, ) from bench.utils.bench import ( build_assets, @@ -66,6 +68,8 @@ class AppMeta: self.from_apps = False self.is_url = False self.branch = branch + self.app_name = None + self.git_repo = None self.mount_path = os.path.abspath( os.path.join(urlparse(self.name).netloc, urlparse(self.name).path) ) @@ -94,6 +98,13 @@ class AppMeta: else: self._setup_details_from_name_tag() + if self.git_repo: + self.app_name = os.path.basename( + os.path.normpath(self.git_repo.working_tree_dir) + ) + else: + self.app_name = self.repo + def _setup_details_from_mounted_disk(self): # If app is a git repo self.git_repo = Repo(self.mount_path) @@ -186,7 +197,7 @@ class App(AppMeta): from bench.utils.app import get_app_name verbose = bench.cli.verbose or verbose - app_name = get_app_name(self.bench.name, self.repo) + app_name = get_app_name(self.bench.name, self.app_name) if not resolved and self.repo != "frappe" and not ignore_resolution: click.secho( f"Ignoring dependencies of {self.name}. To install dependencies use --resolve-deps", diff --git a/bench/bench.py b/bench/bench.py index 9d8d23de..c07fd4f4 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -413,7 +413,7 @@ class BenchSetup(Base): for app in apps: path_to_app = os.path.join(self.bench.name, "apps", app) - App(path_to_app, bench=self.bench, to_clone=False).install( + app = App(path_to_app, bench=self.bench, to_clone=False).install( skip_assets=True, restart_bench=False, ignore_resolution=True ) diff --git a/bench/utils/app.py b/bench/utils/app.py index d07422e1..158fdf96 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -210,25 +210,25 @@ def get_remote(app, bench_path="."): return contents.splitlines()[0].split()[0] -def get_app_name(bench_path, repo_name): +def get_app_name(bench_path, folder_name): 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") + config_path = os.path.join(apps_path, folder_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") + app_path = os.path.join(apps_path, folder_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)) + if app_name and folder_name != app_name: + os.rename(os.path.join(apps_path, folder_name), os.path.join(apps_path, app_name)) return app_name - return repo_name + return folder_name def check_existing_dir(bench_path, repo_name): cloned_path = os.path.join(bench_path, "apps", repo_name) From 7f374887f494d5982002ca69727a6cbeef62839a Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 9 May 2022 19:42:57 +0530 Subject: [PATCH 093/108] fix: fixed states sync --- bench/app.py | 2 +- bench/bench.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bench/app.py b/bench/app.py index f00f8acc..e1ce60d1 100755 --- a/bench/app.py +++ b/bench/app.py @@ -550,7 +550,7 @@ def install_app( if os.path.exists(os.path.join(app_path, "package.json")): bench.run("yarn install", cwd=app_path) - bench.apps.sync(app, required=resolution, branch=tag) + bench.apps.sync(app_name=app, required=resolution, branch=tag, app_dir=app_path) if not skip_assets: build_assets(bench_path=bench_path, app=app) diff --git a/bench/bench.py b/bench/bench.py index c07fd4f4..6b8a5798 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -170,7 +170,7 @@ class BenchApps(MutableSequence): except FileNotFoundError: self.states = {} - def update_apps_states(self, app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): + def update_apps_states(self, app_dir, app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): if self.apps and not os.path.exists(self.states_path): # idx according to apps listed in apps.txt (backwards compatibility) # Keeping frappe as the first app. @@ -203,7 +203,7 @@ class BenchApps(MutableSequence): if app_name and app_name not in self.states: version = get_current_version(app_name, self.bench.name) - app_dir = os.path.join(self.apps_path, app_name) + app_dir = os.path.join(self.apps_path, app_dir) if not branch: branch = ( subprocess From c667be90238585cedbf36ecc61e3ba938d2076cf Mon Sep 17 00:00:00 2001 From: Aradhya Date: Mon, 9 May 2022 22:53:20 +0530 Subject: [PATCH 094/108] fix: fixed args to sync-states --- bench/app.py | 4 +++- bench/bench.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index e1ce60d1..cf33a8bc 100755 --- a/bench/app.py +++ b/bench/app.py @@ -241,7 +241,9 @@ class App(AppMeta): def update_app_state(self): from bench.bench import Bench bench = Bench(self.bench.name) - bench.apps.sync(self.name, self.tag, self.local_resolution) + bench.apps.sync(app_dir=self.app_name, app_name=self.name, + branch=self.tag, required_list=self.local_resolution) + def make_resolution_plan(app: App, bench: "Bench"): diff --git a/bench/bench.py b/bench/bench.py index 6b8a5798..19728151 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -170,7 +170,13 @@ class BenchApps(MutableSequence): except FileNotFoundError: self.states = {} - def update_apps_states(self, app_dir, app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): + def update_apps_states( + self, + app_dir: str = None, + app_name: Union[str, None] = None, + branch: Union[str, None] = None, + required: List = [], + ): if self.apps and not os.path.exists(self.states_path): # idx according to apps listed in apps.txt (backwards compatibility) # Keeping frappe as the first app. @@ -200,6 +206,9 @@ class BenchApps(MutableSequence): for app in apps_to_remove: del self.states[app] + if app_name and not app_dir: + app_dir = app_name + if app_name and app_name not in self.states: version = get_current_version(app_name, self.bench.name) From 1cc961ea55ee22099a6f6df31f4435d1e667a8f9 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Tue, 10 May 2022 02:05:25 +0530 Subject: [PATCH 095/108] style: removed unused imports --- bench/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bench/app.py b/bench/app.py index cf33a8bc..3cc35631 100755 --- a/bench/app.py +++ b/bench/app.py @@ -28,7 +28,6 @@ from bench.utils import ( is_valid_frappe_branch, log, run_frappe_cmd, - is_frappe_app, ) from bench.utils.bench import ( build_assets, From 7980709a2075c606f894454ee15673cddd639203 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Tue, 10 May 2022 02:58:30 +0530 Subject: [PATCH 096/108] fix: updated apps.sync to support new app_name parameter --- bench/bench.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/bench/bench.py b/bench/bench.py index 19728151..0f8d8f62 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -236,11 +236,24 @@ class BenchApps(MutableSequence): with open(self.states_path, "w") as f: f.write(json.dumps(self.states, indent=4)) - def sync(self,app_name: Union[str, None] = None, branch: Union[str, None] = None, required:List = []): + def sync( + self, + app_name: Union[str, None] = None, + app_dir: Union[str, None] = None, + branch: Union[str, None] = None, + required: List = [] + ): self.initialize_apps() + with open(self.bench.apps_txt, "w") as f: f.write("\n".join(self.apps)) - self.update_apps_states(app_name, branch, required) + + self.update_apps_states( + app_name=app_name, + app_dir=app_dir, + branch=branch, + required=required + ) def initialize_apps(self): is_installed = lambda app: app in installed_packages From 2c77dda3c02b3620007b52257c28a9c8573aafba Mon Sep 17 00:00:00 2001 From: Aradhya Date: Tue, 10 May 2022 03:03:15 +0530 Subject: [PATCH 097/108] test: updated tests --- bench/tests/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index f91e8785..b873131d 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -66,7 +66,7 @@ class TestUtils(unittest.TestCase): subprocess.run(["git", "add", "."], cwd=frappe_path, capture_output=True, check=True) subprocess.run(["git", "commit", "-m", "temp"], cwd=frappe_path, capture_output=True, check=True) - fake_bench.apps.update_apps_states("frappe") + fake_bench.apps.update_apps_states(app_name="frappe") self.assertIn("frappe", fake_bench.apps.states) self.assertIn("version", fake_bench.apps.states["frappe"]) From c943b30c6609b758ab903af3e4d964dee4828d25 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Sun, 15 May 2022 15:39:08 +0530 Subject: [PATCH 098/108] fix: allow bench init with git tags --- bench/utils/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index d0bb0383..613a1b2d 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -74,6 +74,15 @@ def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): frappe_branch, ), encoding="UTF-8", + ) or subprocess.check_output( + ( + "git", + "ls-remote", + "--tags", + frappe_path, + frappe_branch, + ), + encoding="UTF-8", ) if not ret: raise InvalidRemoteException( From cb2b0b0d7cd38d68973981dcf4659751ddd1cb0f Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 15 May 2022 17:26:05 +0530 Subject: [PATCH 099/108] feat: using gitpython for branch validation --- bench/utils/__init__.py | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 613a1b2d..0dbe5a08 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -61,35 +61,19 @@ def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): :type frappe_branch: str :raises InvalidRemoteException: branch for this repo doesn't exist """ - import subprocess + import git + + g = git.cmd.Git() if frappe_branch: try: - ret = subprocess.check_output( - ( - "git", - "ls-remote", - "--heads", - frappe_path, - frappe_branch, - ), - encoding="UTF-8", - ) or subprocess.check_output( - ( - "git", - "ls-remote", - "--tags", - frappe_path, - frappe_branch, - ), - encoding="UTF-8", - ) - if not ret: + res = g.ls_remote("--heads", "--tags", frappe_path, frappe_branch) + if not res: raise InvalidRemoteException( - f"Invalid {frappe_branch} for the remote {frappe_path}" + f"Invalid branch: {frappe_branch} for the remote {frappe_path}" ) - except subprocess.CalledProcessError: - raise InvalidRemoteException(f"Invalid frappe path {frappe_path}") + except git.exc.GitCommandError: + raise InvalidRemoteException(f"Invalid frappe path: {frappe_path}") def log(message, level=0, no_log=False): From 19f932513fb9bcc18ddb39a152f5b410fbf2e271 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 15 May 2022 23:20:15 +0530 Subject: [PATCH 100/108] feat: verbose error messages --- bench/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 0dbe5a08..4fd15d33 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -70,7 +70,7 @@ def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): res = g.ls_remote("--heads", "--tags", frappe_path, frappe_branch) if not res: raise InvalidRemoteException( - f"Invalid branch: {frappe_branch} for the remote {frappe_path}" + f"Invalid branch or tag: {frappe_branch} for the remote {frappe_path}" ) except git.exc.GitCommandError: raise InvalidRemoteException(f"Invalid frappe path: {frappe_path}") From e7055fc6d91e477e7743af728b5c114bf9ee5893 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sun, 15 May 2022 23:58:45 +0530 Subject: [PATCH 101/108] test: Added tests for frappe tags --- bench/tests/test_utils.py | 1 + bench/utils/__init__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/bench/tests/test_utils.py b/bench/tests/test_utils.py index b873131d..0575fa9e 100644 --- a/bench/tests/test_utils.py +++ b/bench/tests/test_utils.py @@ -35,6 +35,7 @@ class TestUtils(unittest.TestCase): is_valid_frappe_branch("https://github.com/random/random.git", frappe_branch="random-branch") is_valid_frappe_branch("https://github.com/frappe/frappe.git", frappe_branch="develop") + is_valid_frappe_branch("https://github.com/frappe/frappe.git", frappe_branch="v13.29.0") def test_app_states(self): bench_dir = "./sandbox" diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index 4fd15d33..cacee9fb 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -50,6 +50,7 @@ def is_frappe_app(directory: str) -> bool: return bool(is_frappe_app) +@lru_cache(maxsize=None) def is_valid_frappe_branch(frappe_path:str, frappe_branch:str): """Check if a branch exists in a repo. Throws InvalidRemoteException if branch is not found From 117d456b16289f8a715c608a1265dd9a59368247 Mon Sep 17 00:00:00 2001 From: gavin Date: Thu, 19 May 2022 12:57:05 +0530 Subject: [PATCH 102/108] fix: Handle tag fetching failures of disk mounted App inits --- bench/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bench/app.py b/bench/app.py index 3cc35631..815f4b1d 100755 --- a/bench/app.py +++ b/bench/app.py @@ -113,6 +113,10 @@ class AppMeta: self.tag = self.branch = self.git_repo.active_branch.name except IndexError: self.org, self.repo, self.tag = os.path.split(self.mount_path)[-2:] + (self.branch,) + except TypeError: + # faced a "a detached symbolic reference as it points" in case you're in the middle of + # some git shenanigans + self.tag = self.branch = None def _setup_details_from_name_tag(self): self.org, self.repo, self.tag = fetch_details_from_tag(self.name) From a3b859bf50eb13ef24d82db47410fb4d63988331 Mon Sep 17 00:00:00 2001 From: gavin Date: Thu, 19 May 2022 12:58:24 +0530 Subject: [PATCH 103/108] fix(remove-app): Use App.name instead of App.repo for consistent App name --- bench/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/app.py b/bench/app.py index 815f4b1d..5b3f05a7 100755 --- a/bench/app.py +++ b/bench/app.py @@ -180,7 +180,7 @@ class App(AppMeta): @step(title="Archiving App {repo}", success="App {repo} Archived") def remove(self): - active_app_path = os.path.join("apps", self.repo) + active_app_path = os.path.join("apps", self.name) archived_path = os.path.join("archived", "apps") archived_name = get_available_folder_name(f"{self.repo}-{date.today()}", archived_path) archived_app_path = os.path.join(archived_path, archived_name) @@ -224,7 +224,7 @@ class App(AppMeta): @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): - self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") + self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.name}") def _get_dependencies(self): from bench.utils.app import get_required_deps, required_apps_from_hooks From 69e14e512f39b0750219f259164a995d9cb22096 Mon Sep 17 00:00:00 2001 From: gavin Date: Thu, 19 May 2022 13:00:09 +0530 Subject: [PATCH 104/108] feat(remove-app): Options for no backup of app & force removal --- bench/bench.py | 20 +++++++++++++------- bench/commands/make.py | 6 ++++-- bench/exceptions.py | 6 ++++++ bench/utils/__init__.py | 4 ++-- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/bench/bench.py b/bench/bench.py index 0f8d8f62..4aea84b3 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -10,7 +10,7 @@ from typing import List, MutableSequence, TYPE_CHECKING, Union # imports - module imports import bench -from bench.exceptions import ValidationError +from bench.exceptions import AppNotInstalledError, InvalidRemoteException from bench.config.common_site_config import setup_config from bench.utils import ( paths_in_bench, @@ -49,7 +49,7 @@ class Base: class Validator: def validate_app_uninstall(self, app): if app not in self.apps: - raise ValidationError(f"No app named {app}") + raise AppNotInstalledError(f"No app named {app}") validate_app_installed_on_sites(app, bench_path=self.name) @@ -119,11 +119,16 @@ class Bench(Base, Validator): self.apps.append(app) self.apps.sync() - def uninstall(self, app): + def uninstall(self, app, no_backup=False, force=False): from bench.app import App - self.validate_app_uninstall(app) - self.apps.remove(App(app, bench=self, to_clone=False)) + if not force: + self.validate_app_uninstall(app) + try: + self.apps.remove(App(app, bench=self, to_clone=False), no_backup=no_backup) + except InvalidRemoteException: + if not force: + raise self.apps.sync() # self.build() - removed because it seems unnecessary self.reload() @@ -305,9 +310,10 @@ class BenchApps(MutableSequence): super().append(app.repo) self.apps.sort() - def remove(self, app: "App"): + def remove(self, app: "App", no_backup: bool = False): app.uninstall() - app.remove() + if not no_backup: + app.remove() super().remove(app.repo) def append(self, app: "App"): diff --git a/bench/commands/make.py b/bench/commands/make.py index ac48f410..8fe6382b 100755 --- a/bench/commands/make.py +++ b/bench/commands/make.py @@ -180,12 +180,14 @@ def new_app(app_name, no_git=None): "Completely remove app from bench and re-build assets if not installed on any site" ), ) +@click.option("--no-backup", is_flag=True, help="Do not backup app before removing") +@click.option("--force", is_flag=True, help="Force remove app") @click.argument("app-name") -def remove_app(app_name): +def remove_app(app_name, no_backup=False, force=False): from bench.bench import Bench bench = Bench(".") - bench.uninstall(app_name) + bench.uninstall(app_name, no_backup=no_backup, force=force) @click.command("exclude-app", help="Exclude app from updating") diff --git a/bench/exceptions.py b/bench/exceptions.py index b3340915..48cb6668 100644 --- a/bench/exceptions.py +++ b/bench/exceptions.py @@ -21,9 +21,15 @@ class BenchNotFoundError(Exception): class ValidationError(Exception): pass + +class AppNotInstalledError(ValidationError): + pass + + class CannotUpdateReleaseBench(ValidationError): pass + class FeatureDoesNotExistError(CommandFailedError): pass diff --git a/bench/utils/__init__.py b/bench/utils/__init__.py index cacee9fb..85746877 100644 --- a/bench/utils/__init__.py +++ b/bench/utils/__init__.py @@ -17,7 +17,7 @@ import requests # imports - module imports from bench import PROJECT_NAME, VERSION -from bench.exceptions import CommandFailedError, InvalidRemoteException, ValidationError +from bench.exceptions import CommandFailedError, InvalidRemoteException, AppNotInstalledError logger = logging.getLogger(PROJECT_NAME) @@ -294,7 +294,7 @@ def set_git_remote_url(git_url, bench_path="."): app = git_url.rsplit("/", 1)[1].rsplit(".", 1)[0] if app not in Bench(bench_path).apps: - raise ValidationError(f"No app named {app}") + raise AppNotInstalledError(f"No app named {app}") app_dir = get_repo_dir(app, bench_path=bench_path) From f7f7459c51ad9b73d79d04e14d252de42289b44a Mon Sep 17 00:00:00 2001 From: gavin Date: Thu, 19 May 2022 13:09:18 +0530 Subject: [PATCH 105/108] fix: Delete folder if --no-backup is passed --- bench/app.py | 18 ++++++++++++------ bench/bench.py | 3 +-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/bench/app.py b/bench/app.py index 5b3f05a7..4b527366 100755 --- a/bench/app.py +++ b/bench/app.py @@ -179,13 +179,19 @@ class App(AppMeta): ) @step(title="Archiving App {repo}", success="App {repo} Archived") - def remove(self): + def remove(self, no_backup: bool = False): active_app_path = os.path.join("apps", self.name) - archived_path = os.path.join("archived", "apps") - archived_name = get_available_folder_name(f"{self.repo}-{date.today()}", archived_path) - archived_app_path = os.path.join(archived_path, archived_name) - log(f"App moved from {active_app_path} to {archived_app_path}") - shutil.move(active_app_path, archived_app_path) + + if no_backup: + shutil.rmtree(active_app_path) + log(f"App deleted from {active_app_path}") + else: + archived_path = os.path.join("archived", "apps") + archived_name = get_available_folder_name(f"{self.repo}-{date.today()}", archived_path) + archived_app_path = os.path.join(archived_path, archived_name) + + shutil.move(active_app_path, archived_app_path) + log(f"App moved from {active_app_path} to {archived_app_path}") @step(title="Installing App {repo}", success="App {repo} Installed") def install( diff --git a/bench/bench.py b/bench/bench.py index 4aea84b3..66bcd099 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -312,8 +312,7 @@ class BenchApps(MutableSequence): def remove(self, app: "App", no_backup: bool = False): app.uninstall() - if not no_backup: - app.remove() + app.remove(no_backup=no_backup) super().remove(app.repo) def append(self, app: "App"): From a88932592eed1682fa57c4231bd71f54d52b7e67 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Mon, 23 May 2022 03:14:25 -0400 Subject: [PATCH 106/108] fix: replace certbot-auto with certbot (#1303) * fix: replace certbot-auto references with certbot and add in prerequesites ansile tasks * fix: config file flag fix * fix: certbot path using find_executable instead of hardcoded * fix: remove easy install entry for certbot * fix: replace find_executable with which * fix: no need to check and raise. * fix: provide user with cerbot install instructions" * fix: return certbot path * fix: Use get_certbot_path instead of harcoded path Co-authored-by: Abhishek Balam Co-authored-by: gavin --- bench/config/lets_encrypt.py | 24 ++++++------------------ bench/config/templates/frappe_sudoers | 3 +-- bench/utils/system.py | 3 +++ 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/bench/config/lets_encrypt.py b/bench/config/lets_encrypt.py index 45a61308..4919ef73 100755 --- a/bench/config/lets_encrypt.py +++ b/bench/config/lets_encrypt.py @@ -10,11 +10,10 @@ from bench.config.nginx import make_nginx_conf from bench.config.production_setup import service from bench.config.site_config import get_domains, remove_domain, update_site_config from bench.bench import Bench -from bench.utils import exec_cmd +from bench.utils import exec_cmd, which from bench.utils.bench import update_common_site_config from bench.exceptions import CommandFailedError - def setup_letsencrypt(site, custom_domain, bench_path, interactive): site_path = os.path.join(bench_path, "sites", site, "site_config.json") @@ -58,7 +57,6 @@ def create_config(site, custom_domain): def run_certbot_and_setup_ssl(site, custom_domain, bench_path, interactive=True): service('nginx', 'stop') - get_certbot() try: interactive = '' if interactive else '-n' @@ -88,7 +86,7 @@ def run_certbot_and_setup_ssl(site, custom_domain, bench_path, interactive=True) def setup_crontab(): from crontab import CronTab - job_command = '/opt/certbot-auto renew -a nginx --post-hook "systemctl reload nginx"' + job_command = f'{get_certbot_path()} renew -a nginx --post-hook "systemctl reload nginx"' job_comment = 'Renew lets-encrypt every month' print(f"Setting Up cron job to {job_comment}") @@ -107,20 +105,11 @@ def create_dir_if_missing(path): os.makedirs(os.path.dirname(path)) -def get_certbot(): - from urllib.request import urlretrieve - - certbot_path = get_certbot_path() - create_dir_if_missing(certbot_path) - - if not os.path.isfile(certbot_path): - urlretrieve("https://dl.eff.org/certbot-auto", certbot_path) - os.chmod(certbot_path, 0o744) - - def get_certbot_path(): - return "/opt/certbot-auto" - + try: + return which("certbot", raise_err=True) + except FileNotFoundError: + raise CommandFailedError("Certbot is not installed on your system. Please visit https://certbot.eff.org/instructions for installation instructions, then try again.") def renew_certs(): # Needs to be run with sudo @@ -156,7 +145,6 @@ def setup_wildcard_ssl(domain, email, bench_path, exclude_base_domain): print("You cannot setup SSL without DNS Multitenancy") return - get_certbot() domain_list = _get_domains(domain.strip()) email_param = '' diff --git a/bench/config/templates/frappe_sudoers b/bench/config/templates/frappe_sudoers index 567ccf06..c0857408 100644 --- a/bench/config/templates/frappe_sudoers +++ b/bench/config/templates/frappe_sudoers @@ -15,6 +15,5 @@ {{ user }} ALL = (root) NOPASSWD: {{ nginx }} {% endif %} -{{ user }} ALL = (root) NOPASSWD: /opt/certbot-auto +{{ user }} ALL = (root) NOPASSWD: {{ certbot }} Defaults:{{ user }} !requiretty - diff --git a/bench/utils/system.py b/bench/utils/system.py index 152759d0..131db2a9 100644 --- a/bench/utils/system.py +++ b/bench/utils/system.py @@ -108,6 +108,8 @@ def init( def setup_sudoers(user): + from bench.config.lets_encrypt import get_certbot_path + if not os.path.exists("/etc/sudoers.d"): os.makedirs("/etc/sudoers.d") @@ -128,6 +130,7 @@ def setup_sudoers(user): "service": which("service"), "systemctl": which("systemctl"), "nginx": which("nginx"), + "certbot": get_certbot_path(), } ) From e0f6b1735d355e03d9e7dc221231ab33b7dec4c3 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 30 May 2022 14:32:55 +0530 Subject: [PATCH 107/108] fix: Remove virtualenv dependency Use virtualenv cli only if found in PATH. Else use venv package of the invoker's Python installation. venv was added in the std lib since PY33. ref: https://docs.python.org/3/library/venv.html --- bench/bench.py | 13 ++++++++++--- bench/utils/bench.py | 28 +++++++++++++++++----------- requirements.txt | 1 - 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/bench/bench.py b/bench/bench.py index 66bcd099..3585769c 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -29,6 +29,7 @@ from bench.utils.bench import ( restart_process_manager, remove_backups_crontab, get_venv_path, + get_virtualenv_path, get_env_cmd, ) from bench.utils.render import job, step @@ -347,14 +348,20 @@ class BenchSetup(Base): import bench.cli import click + verbose = bench.cli.verbose + click.secho("Setting Up Environment", fg="yellow") frappe = os.path.join(self.bench.name, "apps", "frappe") - virtualenv = get_venv_path() - quiet_flag = "" if bench.cli.verbose else "--quiet" + virtualenv = get_virtualenv_path(verbose=verbose) + quiet_flag = "" if verbose else "--quiet" if not os.path.exists(self.bench.python): - self.run(f"{virtualenv} {quiet_flag} env -p {python}") + if virtualenv: + self.run(f"{virtualenv} {quiet_flag} env -p {python}") + else: + venv = get_venv_path(verbose=verbose) + self.run(f"{venv} env") self.pip() diff --git a/bench/utils/bench.py b/bench/utils/bench.py index 8bf03aa2..8a695b7b 100644 --- a/bench/utils/bench.py +++ b/bench/utils/bench.py @@ -33,19 +33,25 @@ def get_env_cmd(cmd, bench_path="."): return os.path.abspath(os.path.join(bench_path, "env", "bin", cmd)) -def get_venv_path(): - venv = which("virtualenv") +def get_virtualenv_path(verbose=False): + virtualenv_path = which("virtualenv") - if not venv: - current_python = sys.executable - with open(os.devnull, "wb") as devnull: - is_venv_installed = not subprocess.call( - [current_python, "-m", "venv", "--help"], stdout=devnull - ) - if is_venv_installed: - venv = f"{current_python} -m venv" + if not virtualenv_path and verbose: + log("virtualenv cannot be found", level=2) - return venv or log("virtualenv cannot be found", level=2) + return virtualenv_path + + +def get_venv_path(verbose=False): + current_python = sys.executable + with open(os.devnull, "wb") as devnull: + is_venv_installed = not subprocess.call( + [current_python, "-m", "venv", "--help"], stdout=devnull + ) + if is_venv_installed: + return f"{current_python} -m venv" + else: + log("virtualenv cannot be found", level=2) def update_node_packages(bench_path=".", apps=None): diff --git a/requirements.txt b/requirements.txt index 68e7c7d3..c29eb08e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,3 @@ python-crontab~=2.4.0 requests semantic-version~=2.8.2 setuptools -virtualenv From 593c7b737fb9ce8b9425c2674597879b006a5d5e Mon Sep 17 00:00:00 2001 From: Orsiris de Jong Date: Mon, 30 May 2022 13:35:08 +0200 Subject: [PATCH 108/108] feat(config): Add IPv6 listener to nginx site configuration (#1312) --- bench/config/templates/nginx.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bench/config/templates/nginx.conf b/bench/config/templates/nginx.conf index 2ea1e3e5..4af72ab4 100644 --- a/bench/config/templates/nginx.conf +++ b/bench/config/templates/nginx.conf @@ -14,8 +14,10 @@ map {{ from_variable }} {{ to_variable }} { server { {% if ssl_certificate and ssl_certificate_key %} listen {{ port }} ssl; + listen [::]:{{ port }} ssl; {% else %} listen {{ port }}; + listen [::]:{{ port }}; {% endif %} server_name