From d8713ceffa1207884e776342a80e74695a5c12aa Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Thu, 17 Apr 2025 16:41:55 -0500 Subject: [PATCH 1/4] reckless: fix installation from local directories with subpaths This could previously copy the parent directory of a plugin into the installed reckless directory, which was unnecessary. --- tools/reckless | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/reckless b/tools/reckless index 7b692d391e0d..a76424e1a87f 100755 --- a/tools/reckless +++ b/tools/reckless @@ -1175,10 +1175,11 @@ def _install_plugin(src: InstInfo) -> Union[InstInfo, None]: log.debug(f'{clone_path} already exists - deleting') shutil.rmtree(clone_path) if src.srctype == Source.DIRECTORY: + full_source_path = Path(src.source_loc) / src.subdir log.debug(("copying local directory contents from" - f" {src.source_loc}")) + f" {full_source_path}")) create_dir(clone_path) - shutil.copytree(src.source_loc, plugin_path) + shutil.copytree(full_source_path, plugin_path) elif src.srctype in [Source.LOCAL_REPO, Source.GITHUB_REPO, Source.OTHER_URL, Source.GIT_LOCAL_CLONE]: # clone git repository to /tmp/reckless-... From 307baf7611e87b85c8e5835d6a0a47ba5e569285 Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Thu, 17 Apr 2025 17:13:49 -0500 Subject: [PATCH 2/4] reckless: accept a full local path as source+name This allows installing a local plugin directly without having to modify reckless sources. Changelog-changed: Reckless can be passed a local file or directory for installation. --- tools/reckless | 89 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/tools/reckless b/tools/reckless index a76424e1a87f..492d7ffdd6bc 100755 --- a/tools/reckless +++ b/tools/reckless @@ -1298,6 +1298,30 @@ def _install_plugin(src: InstInfo) -> Union[InstInfo, None]: return staged_src +def location_from_name(plugin_name: str) -> (str, str): + """Maybe the location was passed in place of the plugin name. Check + if this looks like a filepath or URL and return that as well as the + plugin name.""" + if not Path(plugin_name).exists(): + # No path included, return the name only. + return (None, plugin_name) + + # Directory containing the plugin? The plugin name should match the dir. + if os.path.isdir(plugin_name): + return (Path(plugin_name).parent, Path(plugin_name).name) + + # Possibly the entrypoint itself was passed? + elif os.path.isfile(plugin_name): + if Path(plugin_name).with_suffix('').name != Path(plugin_name).parent.name or \ + not Path(plugin_name).parent.parent.exists(): + # If the directory is not named for the plugin, we can't infer what + # should be done. + # FIXME: return InstInfo with entrypoint rather than source str. + return (None, plugin_name) + # We have to make inferences as to the naming here. + return (Path(plugin_name).parent.parent, Path(plugin_name).with_suffix('').name) + + def install(plugin_name: str) -> Union[str, None]: """Downloads plugin from source repos, installs and activates plugin. Returns the location of the installed plugin or "None" in the case of @@ -1310,33 +1334,48 @@ def install(plugin_name: str) -> Union[str, None]: else: name = plugin_name commit = None - log.debug(f"Searching for {name}") - if search(name): - global LAST_FOUND - src = LAST_FOUND - src.commit = commit - log.debug(f'Retrieving {src.name} from {src.source_loc}') - try: - installed = _install_plugin(src) - except FileExistsError as err: - log.error(f'File exists: {err.filename}') - return None - LAST_FOUND = None - if not installed: - log.warning(f'{plugin_name}: installation aborted') + # Is the install request specifying a path to the plugin? + direct_location, name = location_from_name(name) + if direct_location: + logging.debug(f"install of {name} requested from {direct_location}") + src = InstInfo(name, direct_location, None) + if not src.get_inst_details(): + src = None + # Treating a local git repo as a directory allows testing + # uncommitted changes. + if src and src.srctype == Source.LOCAL_REPO: + src.srctype = Source.DIRECTORY + if not direct_location or not src: + log.debug(f"direct_location {direct_location}, src: {src}") + log.debug(f"Searching for {name}") + if search(name): + global LAST_FOUND + src = LAST_FOUND + src.commit = commit + log.debug(f'Retrieving {src.name} from {src.source_loc}') + else: return None - # Match case of the containing directory - for dirname in os.listdir(RECKLESS_CONFIG.reckless_dir): - if dirname.lower() == installed.name.lower(): - inst_path = Path(RECKLESS_CONFIG.reckless_dir) - inst_path = inst_path / dirname / installed.entry - RECKLESS_CONFIG.enable_plugin(inst_path) - enable(installed.name) - return f"{installed.source_loc}" - log.error(('dynamic activation failed: ' - f'{installed.name} not found in reckless directory')) + try: + installed = _install_plugin(src) + except FileExistsError as err: + log.error(f'File exists: {err.filename}') return None + LAST_FOUND = None + if not installed: + log.warning(f'{plugin_name}: installation aborted') + return None + + # Match case of the containing directory + for dirname in os.listdir(RECKLESS_CONFIG.reckless_dir): + if dirname.lower() == installed.name.lower(): + inst_path = Path(RECKLESS_CONFIG.reckless_dir) + inst_path = inst_path / dirname / installed.entry + RECKLESS_CONFIG.enable_plugin(inst_path) + enable(installed.name) + return f"{installed.source_loc}" + log.error(('dynamic activation failed: ' + f'{installed.name} not found in reckless directory')) return None @@ -1386,6 +1425,8 @@ def search(plugin_name: str) -> Union[InstInfo, None]: if srctype in [Source.DIRECTORY, Source.LOCAL_REPO, Source.GITHUB_REPO, Source.OTHER_URL]: found = _source_search(plugin_name, source) + if found: + log.debug(f"{found}, {found.srctype}") if not found: continue log.info(f"found {found.name} in source: {found.source_loc}") From 2155a0538ad62b13a10875678b02071d39e7967c Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Tue, 22 Apr 2025 15:28:58 -0500 Subject: [PATCH 3/4] reckless: handle a direct source in the form of a git repo url --- tools/reckless | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/reckless b/tools/reckless index 492d7ffdd6bc..109155c37fe6 100755 --- a/tools/reckless +++ b/tools/reckless @@ -1303,6 +1303,12 @@ def location_from_name(plugin_name: str) -> (str, str): if this looks like a filepath or URL and return that as well as the plugin name.""" if not Path(plugin_name).exists(): + try: + parsed = urlparse(plugin_name) + if parsed.scheme in ['http', 'https']: + return (plugin_name, Path(plugin_name).with_suffix('').name) + except ValueError: + pass # No path included, return the name only. return (None, plugin_name) From 4aedd4962667754c35f36c798b5841953959b95b Mon Sep 17 00:00:00 2001 From: Alex Myers Date: Sun, 4 May 2025 16:51:13 -0500 Subject: [PATCH 4/4] reckless: store absolute paths in metadata --- tools/reckless | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/reckless b/tools/reckless index 109155c37fe6..c1ffee47ca7c 100755 --- a/tools/reckless +++ b/tools/reckless @@ -1099,12 +1099,13 @@ def add_installation_metadata(installed: InstInfo, updating the plugin.""" install_dir = Path(installed.source_loc) assert install_dir.is_dir() + abs_source_path = Path(original_request.source_loc).resolve() data = ('installation date\n' f'{datetime.date.today().isoformat()}\n' 'installation time\n' f'{int(time.time())}\n' 'original source\n' - f'{original_request.source_loc}\n' + f'{abs_source_path}\n' 'requested commit\n' f'{original_request.commit}\n' 'installed commit\n'