From 1c8ae683f060f11c73394afb9dc0b0a4516ad79e Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 16 Jan 2025 16:09:02 +0100 Subject: [PATCH 01/17] added yakerize task to new grasshopper module --- src/compas_invocations2/grasshopper.py | 119 +++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/compas_invocations2/grasshopper.py diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py new file mode 100644 index 0000000..0395e6a --- /dev/null +++ b/src/compas_invocations2/grasshopper.py @@ -0,0 +1,119 @@ +import os +import shutil + +import invoke +import requests + +YAK_URL = r"https://files.mcneel.com/yak/tools/latest/yak.exe" +FILENAME = "yak.exe" + + +def _download_yak_executable(target_dir: str): + response = requests.get(YAK_URL) + if response.status_code != 200: + raise ValueError(f"Failed to download the yak.exe from url:{YAK_URL} with error : {response.status_code}") + + with open(os.path.join(target_dir, FILENAME), "wb") as f: + f.write(response.content) + + +def _set_version_in_manifest(manifest_path: str, version: str): + with open(manifest_path, "r") as f: + lines = f.readlines() + + new_lines = [] + for line in lines: + if "{{ version }}" in line: + new_lines.append(line.replace("{{ version }}", version)) + else: + new_lines.append(line) + + with open(manifest_path, "w") as f: + f.writelines(new_lines) + + +def _clear_directory(path_to_dir): + print(f"target dir: {path_to_dir} exists, clearing it.") + for f in os.listdir(path_to_dir): + file_path = os.path.join(path_to_dir, f) + try: + if os.path.isfile(file_path) or os.path.islink(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + except Exception as e: + invoke.Exit(f"Failed to delete {file_path}: {e}") + + +@invoke.task( + help={ + "gh_components_dir": "Path to the directory containing the .ghuser files.", + "target_dir": "Path to the directory where the yak package will be created.", + "manifest_path": "Path to the manifest file.", + "logo_path": "Path to the logo file.", + "readme_path": "Path to the readme file.", + "license_path": "Path to the license file.", + "version": "The version number to set in the manifest file.", + } +) +def yakerize( + ctx, + gh_components_dir: str, + target_dir: str, + manifest_path: str, + logo_path: str, + readme_path: str, + license_path: str, + version: str, +) -> bool: + # copy the manifest, logo, readme, license and ghuser files to the target dir + # update the manifest file with the version number + # download the yak executable + # build the yak package + + target_dir = os.path.abspath(target_dir) + + ##################################################################### + # Copy manifest, logo, misc folder (readme, license, etc) + ##################################################################### + # if target dit exists, make sure it's empty + if os.path.exists(target_dir) and os.path.isdir(target_dir): + _clear_directory(target_dir) + else: + os.makedirs(target_dir, exist_ok=False) + + manifest_target = shutil.copy(manifest_path, target_dir) + _set_version_in_manifest(manifest_target, version) + shutil.copy(logo_path, target_dir) + + path_miscdir: str = os.path.join(target_dir, "misc") + os.makedirs(path_miscdir, exist_ok=False) + shutil.copy(readme_path, path_miscdir) + shutil.copy(license_path, path_miscdir) + + for f in os.listdir(gh_components_dir): + if f.endswith(".ghuser"): + shutil.copy(os.path.join(gh_components_dir, f), target_dir) + + ##################################################################### + # Yak exe + ##################################################################### + + try: + _download_yak_executable(target_dir) + except ValueError: + invoke.Exit("Failed to download the yak executable: {e}") + + yak_exe_path: str = os.path.join(target_dir, "yak.exe") + yak_exe_path = os.path.abspath(yak_exe_path) + + path_current: str = os.getcwd() + os.chdir(target_dir) + os.system("cd") + try: + os.system(f"{yak_exe_path} build --platform win") + except Exception: + invoke.Exit(f"Failed to build the yak package: {e}") + if not any([f.endswith(".yak") for f in os.listdir(target_dir)]): + invoke.Exit("No .yak file was created in the build directory.") + os.chdir(path_current) From 6277a323c9d08ac80278475169b5e6fb09b32a3b Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 16 Jan 2025 16:11:53 +0100 Subject: [PATCH 02/17] moved grasshopper tasks from build to grasshopper --- src/compas_invocations2/build.py | 81 ------------------------- src/compas_invocations2/grasshopper.py | 83 ++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 81 deletions(-) diff --git a/src/compas_invocations2/build.py b/src/compas_invocations2/build.py index 283cac4..ff176f8 100644 --- a/src/compas_invocations2/build.py +++ b/src/compas_invocations2/build.py @@ -1,7 +1,6 @@ import glob import os import shutil -import tempfile import invoke @@ -97,83 +96,3 @@ def prepare_changelog(ctx): changelog.write(content.replace("## ", UNRELEASED_CHANGELOG_TEMPLATE, 1)) ctx.run('git add CHANGELOG.md && git commit -m "Prepare changelog for next release"') - - -@invoke.task( - help={ - "gh_io_folder": "Folder where GH_IO.dll is located. If not specified, it will try to download from NuGet.", - "ironpython": "Command for running the IronPython executable. Defaults to `ipy`.", - "prefix": "(Optional) Append this prefix to the names of the built components.", - } -) -def build_ghuser_components(ctx, gh_io_folder=None, ironpython=None, prefix=None): - """Builds Grasshopper components using GH Componentizer.""" - prefix = prefix or getattr(ctx.ghuser, "prefix", None) - source_dir = os.path.abspath(ctx.ghuser.source_dir) - target_dir = os.path.abspath(ctx.ghuser.target_dir) - repo_url = "https://github.com/compas-dev/compas-actions.ghpython_components.git" - - with chdir(ctx.base_folder): - shutil.rmtree(os.path.join(ctx.base_folder, target_dir), ignore_errors=True) - - # Build IronPython Grasshopper user objects from source - with chdir(ctx.base_folder): - with tempfile.TemporaryDirectory("actions.ghcomponentizer") as action_dir: - ctx.run("git clone {} {}".format(repo_url, action_dir)) - - if not gh_io_folder: - gh_io_folder = tempfile.mkdtemp("ghio") - import compas_ghpython - - compas_ghpython.fetch_ghio_lib(gh_io_folder) - - if not ironpython: - ironpython = ctx.get("ironpython") or "ipy" - - gh_io_folder = os.path.abspath(gh_io_folder) - componentizer_script = os.path.join(action_dir, "componentize_ipy.py") - - cmd = "{} {} {} {}".format(ironpython, componentizer_script, source_dir, target_dir) - cmd += ' --ghio "{}"'.format(gh_io_folder) - if prefix: - cmd += ' --prefix "{}"'.format(prefix) - - ctx.run(cmd) - - -@invoke.task( - help={ - "gh_io_folder": "Folder where GH_IO.dll is located. If not specified, it will try to download from NuGet.", - "prefix": "(Optional) Append this prefix to the names of the built components.", - } -) -def build_cpython_ghuser_components(ctx, gh_io_folder=None, prefix=None): - """Builds CPython Grasshopper components using GH Componentizer.""" - prefix = prefix or getattr(ctx.ghuser_cpython, "prefix", None) - source_dir = os.path.abspath(ctx.ghuser_cpython.source_dir) - target_dir = os.path.abspath(ctx.ghuser_cpython.target_dir) - repo_url = "https://github.com/compas-dev/compas-actions.ghpython_components.git" - - with chdir(ctx.base_folder): - shutil.rmtree(os.path.join(ctx.base_folder, target_dir), ignore_errors=True) - - # Build CPython Grasshopper user objects from source - with chdir(ctx.base_folder): - with tempfile.TemporaryDirectory("actions.ghcomponentizer") as action_dir: - ctx.run("git clone {} {}".format(repo_url, action_dir)) - - if not gh_io_folder: - gh_io_folder = tempfile.mkdtemp("ghio") - import compas_ghpython - - compas_ghpython.fetch_ghio_lib(gh_io_folder) - - gh_io_folder = os.path.abspath(gh_io_folder) - componentizer_script = os.path.join(action_dir, "componentize_cpy.py") - - cmd = "{} {} {} {}".format("python", componentizer_script, source_dir, target_dir) - cmd += ' --ghio "{}"'.format(gh_io_folder) - if prefix: - cmd += ' --prefix "{}"'.format(prefix) - - ctx.run(cmd) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index 0395e6a..f1fc548 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -1,9 +1,12 @@ import os import shutil +import tempfile import invoke import requests +from compas_invocations2.console import chdir + YAK_URL = r"https://files.mcneel.com/yak/tools/latest/yak.exe" FILENAME = "yak.exe" @@ -117,3 +120,83 @@ def yakerize( if not any([f.endswith(".yak") for f in os.listdir(target_dir)]): invoke.Exit("No .yak file was created in the build directory.") os.chdir(path_current) + + +@invoke.task( + help={ + "gh_io_folder": "Folder where GH_IO.dll is located. If not specified, it will try to download from NuGet.", + "ironpython": "Command for running the IronPython executable. Defaults to `ipy`.", + "prefix": "(Optional) Append this prefix to the names of the built components.", + } +) +def build_ghuser_components(ctx, gh_io_folder=None, ironpython=None, prefix=None): + """Builds Grasshopper components using GH Componentizer.""" + prefix = prefix or getattr(ctx.ghuser, "prefix", None) + source_dir = os.path.abspath(ctx.ghuser.source_dir) + target_dir = os.path.abspath(ctx.ghuser.target_dir) + repo_url = "https://github.com/compas-dev/compas-actions.ghpython_components.git" + + with chdir(ctx.base_folder): + shutil.rmtree(os.path.join(ctx.base_folder, target_dir), ignore_errors=True) + + # Build IronPython Grasshopper user objects from source + with chdir(ctx.base_folder): + with tempfile.TemporaryDirectory("actions.ghcomponentizer") as action_dir: + ctx.run("git clone {} {}".format(repo_url, action_dir)) + + if not gh_io_folder: + gh_io_folder = tempfile.mkdtemp("ghio") + import compas_ghpython + + compas_ghpython.fetch_ghio_lib(gh_io_folder) + + if not ironpython: + ironpython = ctx.get("ironpython") or "ipy" + + gh_io_folder = os.path.abspath(gh_io_folder) + componentizer_script = os.path.join(action_dir, "componentize_ipy.py") + + cmd = "{} {} {} {}".format(ironpython, componentizer_script, source_dir, target_dir) + cmd += ' --ghio "{}"'.format(gh_io_folder) + if prefix: + cmd += ' --prefix "{}"'.format(prefix) + + ctx.run(cmd) + + +@invoke.task( + help={ + "gh_io_folder": "Folder where GH_IO.dll is located. If not specified, it will try to download from NuGet.", + "prefix": "(Optional) Append this prefix to the names of the built components.", + } +) +def build_cpython_ghuser_components(ctx, gh_io_folder=None, prefix=None): + """Builds CPython Grasshopper components using GH Componentizer.""" + prefix = prefix or getattr(ctx.ghuser_cpython, "prefix", None) + source_dir = os.path.abspath(ctx.ghuser_cpython.source_dir) + target_dir = os.path.abspath(ctx.ghuser_cpython.target_dir) + repo_url = "https://github.com/compas-dev/compas-actions.ghpython_components.git" + + with chdir(ctx.base_folder): + shutil.rmtree(os.path.join(ctx.base_folder, target_dir), ignore_errors=True) + + # Build CPython Grasshopper user objects from source + with chdir(ctx.base_folder): + with tempfile.TemporaryDirectory("actions.ghcomponentizer") as action_dir: + ctx.run("git clone {} {}".format(repo_url, action_dir)) + + if not gh_io_folder: + gh_io_folder = tempfile.mkdtemp("ghio") + import compas_ghpython + + compas_ghpython.fetch_ghio_lib(gh_io_folder) + + gh_io_folder = os.path.abspath(gh_io_folder) + componentizer_script = os.path.join(action_dir, "componentize_cpy.py") + + cmd = "{} {} {} {}".format("python", componentizer_script, source_dir, target_dir) + cmd += ' --ghio "{}"'.format(gh_io_folder) + if prefix: + cmd += ' --prefix "{}"'.format(prefix) + + ctx.run(cmd) From d6850fe50d34c721a66947414cdac943d43bc52a Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 16 Jan 2025 16:44:43 +0100 Subject: [PATCH 03/17] added toml as dependency --- requirements-dev.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 35a45e4..ceadf0f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,4 +6,5 @@ invoke >=0.14 ruff sphinx_compas2_theme twine -wheel \ No newline at end of file +wheel +toml \ No newline at end of file From cf14520d3c355559aa28610c029f14e08eff037a Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 16 Jan 2025 16:45:01 +0100 Subject: [PATCH 04/17] allow some optional arguments --- src/compas_invocations2/grasshopper.py | 32 ++++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index f1fc548..01167fe 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -4,6 +4,7 @@ import invoke import requests +import toml from compas_invocations2.console import chdir @@ -48,15 +49,23 @@ def _clear_directory(path_to_dir): invoke.Exit(f"Failed to delete {file_path}: {e}") +def _get_version_from_toml(toml_file: str) -> str: + pyproject_data = toml.load(toml_file) + version = pyproject_data.get("tool", {}).get("bumpversion", {}).get("current_version", None) + if not version: + invoke.Exit("Failed to get version from pyproject.toml. Please provide a version number.") + return version + + @invoke.task( help={ "gh_components_dir": "Path to the directory containing the .ghuser files.", "target_dir": "Path to the directory where the yak package will be created.", "manifest_path": "Path to the manifest file.", "logo_path": "Path to the logo file.", - "readme_path": "Path to the readme file.", - "license_path": "Path to the license file.", - "version": "The version number to set in the manifest file.", + "readme_path": "(Optional) Path to the readme file.", + "license_path": "(Optional) Path to the license file.", + "version": "(Optional) The version number to set in the manifest file.", } ) def yakerize( @@ -65,16 +74,25 @@ def yakerize( target_dir: str, manifest_path: str, logo_path: str, - readme_path: str, - license_path: str, - version: str, + readme_path: str = None, + license_path: str = None, + version: str = None, ) -> bool: + """Create a Grasshopper YAK package from the current project.""" # copy the manifest, logo, readme, license and ghuser files to the target dir # update the manifest file with the version number # download the yak executable # build the yak package + readme_path = readme_path or os.path.join(ctx.base_folder, "README.md") + if not os.path.exists(readme_path): + invoke.Exit(f"Readme file not found at {readme_path}. Please provide a valid path.") + + license_path = license_path or os.path.join(ctx.base_folder, "LICENSE") + if not os.path.exists(license_path): + invoke.Exit(f"License file not found at {license_path}. Please provide a valid path.") - target_dir = os.path.abspath(target_dir) + version = version or _get_version_from_toml(os.path.join(ctx.base_folder, "pyproject.toml")) + target_dir = os.path.join(ctx.base_folder, "dist", "yak_package") ##################################################################### # Copy manifest, logo, misc folder (readme, license, etc) From 5b3b106e05cd51a1c1f727cd13c97b1fd0abc270 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 16 Jan 2025 16:50:13 +0100 Subject: [PATCH 05/17] some comments and cleanup --- src/compas_invocations2/grasshopper.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index 01167fe..d9f4bd5 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -1,3 +1,11 @@ +""" +Adapted from: https://github.com/diffCheckOrg/diffCheck/blob/main/invokes/yakerize.py + +Yakerize.py was originally developed as part of the DiffCheck plugin by +Andrea Settimi, Damien Gilliard, Eleni Skevaki, Marirena Kladeftira (IBOIS, CRCL, EPFL) in 2024. +It is distributed under the MIT License, provided this attribution is retained. +""" + import os import shutil import tempfile @@ -9,7 +17,6 @@ from compas_invocations2.console import chdir YAK_URL = r"https://files.mcneel.com/yak/tools/latest/yak.exe" -FILENAME = "yak.exe" def _download_yak_executable(target_dir: str): @@ -17,7 +24,7 @@ def _download_yak_executable(target_dir: str): if response.status_code != 200: raise ValueError(f"Failed to download the yak.exe from url:{YAK_URL} with error : {response.status_code}") - with open(os.path.join(target_dir, FILENAME), "wb") as f: + with open(os.path.join(target_dir, "yak.exe"), "wb") as f: f.write(response.content) @@ -37,7 +44,6 @@ def _set_version_in_manifest(manifest_path: str, version: str): def _clear_directory(path_to_dir): - print(f"target dir: {path_to_dir} exists, clearing it.") for f in os.listdir(path_to_dir): file_path = os.path.join(path_to_dir, f) try: @@ -79,10 +85,6 @@ def yakerize( version: str = None, ) -> bool: """Create a Grasshopper YAK package from the current project.""" - # copy the manifest, logo, readme, license and ghuser files to the target dir - # update the manifest file with the version number - # download the yak executable - # build the yak package readme_path = readme_path or os.path.join(ctx.base_folder, "README.md") if not os.path.exists(readme_path): invoke.Exit(f"Readme file not found at {readme_path}. Please provide a valid path.") From 60740dc59f7c304090cd3fd2fe8650dcdf1ef1d2 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 16 Jan 2025 17:36:31 +0100 Subject: [PATCH 06/17] make user object path optional as well --- src/compas_invocations2/grasshopper.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index d9f4bd5..67efda2 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -63,12 +63,22 @@ def _get_version_from_toml(toml_file: str) -> str: return version +def _get_user_object_path(context): + if hasattr(context, "ghuser_cpython"): + print("checking ghuser_cpython") + return os.path.join(context.base_folder, context.ghuser_cpython.target_dir) + elif hasattr(context, "ghuser"): + print("checking ghuser") + return os.path.join(context.base_folder, context.ghuser.target_dir) + else: + return None + + @invoke.task( help={ - "gh_components_dir": "Path to the directory containing the .ghuser files.", - "target_dir": "Path to the directory where the yak package will be created.", "manifest_path": "Path to the manifest file.", "logo_path": "Path to the logo file.", + "gh_components_dir": "(Optional) Path to the directory containing the .ghuser files.", "readme_path": "(Optional) Path to the readme file.", "license_path": "(Optional) Path to the license file.", "version": "(Optional) The version number to set in the manifest file.", @@ -76,15 +86,18 @@ def _get_version_from_toml(toml_file: str) -> str: ) def yakerize( ctx, - gh_components_dir: str, - target_dir: str, manifest_path: str, logo_path: str, + gh_components_dir: str = None, readme_path: str = None, license_path: str = None, version: str = None, ) -> bool: """Create a Grasshopper YAK package from the current project.""" + gh_components_dir = gh_components_dir or _get_user_object_path(ctx) + if not gh_components_dir: + invoke.Exit("Please provide the path to the directory containing the .ghuser files.") + readme_path = readme_path or os.path.join(ctx.base_folder, "README.md") if not os.path.exists(readme_path): invoke.Exit(f"Readme file not found at {readme_path}. Please provide a valid path.") From 14a81e1181e170f4818c29eeb1b4e0326d6e8b64 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 16 Jan 2025 17:38:58 +0100 Subject: [PATCH 07/17] updated CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f86f4..50d1b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Added task `yakerize` to create YAK package for Grasshopper. + ### Changed * Task `build-cpython-ghuser-components` now uses `ghuser_cpython` configuration key. +* Moved task `build-cpython-ghuser-components` from `build` to `grasshopper`. +* Moved task `build-ghuser-components` from `build` to `grasshopper`. ### Removed From 21a6cc66118609d64029f02bd56f82952c688a97 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 16 Jan 2025 18:28:53 +0100 Subject: [PATCH 08/17] linting --- src/compas_invocations2/grasshopper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index 67efda2..f065010 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -138,7 +138,7 @@ def yakerize( try: _download_yak_executable(target_dir) except ValueError: - invoke.Exit("Failed to download the yak executable: {e}") + invoke.Exit("Failed to download the yak executable") yak_exe_path: str = os.path.join(target_dir, "yak.exe") yak_exe_path = os.path.abspath(yak_exe_path) @@ -148,7 +148,7 @@ def yakerize( os.system("cd") try: os.system(f"{yak_exe_path} build --platform win") - except Exception: + except Exception as e: invoke.Exit(f"Failed to build the yak package: {e}") if not any([f.endswith(".yak") for f in os.listdir(target_dir)]): invoke.Exit("No .yak file was created in the build directory.") From 0e5d336ca902c75c3ca5ba59b63764fe3141882a Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Mon, 20 Jan 2025 11:49:09 +0100 Subject: [PATCH 09/17] use the chdir context manager --- src/compas_invocations2/grasshopper.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index f065010..1b394e3 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -143,16 +143,13 @@ def yakerize( yak_exe_path: str = os.path.join(target_dir, "yak.exe") yak_exe_path = os.path.abspath(yak_exe_path) - path_current: str = os.getcwd() - os.chdir(target_dir) - os.system("cd") - try: - os.system(f"{yak_exe_path} build --platform win") - except Exception as e: - invoke.Exit(f"Failed to build the yak package: {e}") - if not any([f.endswith(".yak") for f in os.listdir(target_dir)]): - invoke.Exit("No .yak file was created in the build directory.") - os.chdir(path_current) + with chdir(target_dir): + try: + os.system(f"{yak_exe_path} build --platform win") + except Exception as e: + invoke.Exit(f"Failed to build the yak package: {e}") + if not any([f.endswith(".yak") for f in os.listdir(target_dir)]): + invoke.Exit("No .yak file was created in the build directory.") @invoke.task( From c0a9c745edcede49c2680a72894d40a6e64df7b0 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Mon, 20 Jan 2025 11:54:53 +0100 Subject: [PATCH 10/17] added comment --- src/compas_invocations2/grasshopper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index 1b394e3..06ad8ef 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -145,6 +145,7 @@ def yakerize( with chdir(target_dir): try: + # not using `ctx.run()` here to get properly formatted output (unicode+colors) os.system(f"{yak_exe_path} build --platform win") except Exception as e: invoke.Exit(f"Failed to build the yak package: {e}") From e0320aaef199680837bebdfeaf6ebfbafdceb89e Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Mon, 20 Jan 2025 18:16:34 +0100 Subject: [PATCH 11/17] added target rhino version --- src/compas_invocations2/grasshopper.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index 06ad8ef..79bf53e 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -82,6 +82,7 @@ def _get_user_object_path(context): "readme_path": "(Optional) Path to the readme file.", "license_path": "(Optional) Path to the license file.", "version": "(Optional) The version number to set in the manifest file.", + "target_rhino": "(Optional) The target Rhino version for the package. Defaults to 'rh8'.", } ) def yakerize( @@ -92,8 +93,12 @@ def yakerize( readme_path: str = None, license_path: str = None, version: str = None, + target_rhino: str = "rh8", ) -> bool: """Create a Grasshopper YAK package from the current project.""" + if target_rhino not in ["rh6", "rh7", "rh8"]: + invoke.Exit("Invalid target Rhino version. Must be one of: rh6, rh7, rh8") + gh_components_dir = gh_components_dir or _get_user_object_path(ctx) if not gh_components_dir: invoke.Exit("Please provide the path to the directory containing the .ghuser files.") @@ -146,12 +151,17 @@ def yakerize( with chdir(target_dir): try: # not using `ctx.run()` here to get properly formatted output (unicode+colors) - os.system(f"{yak_exe_path} build --platform win") + os.system(f"{yak_exe_path} build --platform any") except Exception as e: invoke.Exit(f"Failed to build the yak package: {e}") if not any([f.endswith(".yak") for f in os.listdir(target_dir)]): invoke.Exit("No .yak file was created in the build directory.") + # filename is what tells YAK the target Rhino version..? + taget_file = next((f for f in os.listdir(target_dir) if f.endswith(".yak"))) + new_filename = taget_file.replace("any-any", f"{target_rhino}-any") + os.rename(taget_file, new_filename) + @invoke.task( help={ From a3c99c41ad46a4725c17ff433b5ddc09ae89d4c9 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Mon, 20 Jan 2025 18:46:18 +0100 Subject: [PATCH 12/17] added publish-yak to grasshopper.py --- src/compas_invocations2/grasshopper.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index 79bf53e..c5c94e2 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -26,6 +26,7 @@ def _download_yak_executable(target_dir: str): with open(os.path.join(target_dir, "yak.exe"), "wb") as f: f.write(response.content) + # TODO: return path to the downloaded yak.exe def _set_version_in_manifest(manifest_path: str, version: str): @@ -163,6 +164,30 @@ def yakerize( os.rename(taget_file, new_filename) +@invoke.task( + help={"yak_file": "Path to the .yak file to publish.", "test_server": "True to publish to the test server."} +) +def publish_yak(ctx, yak_file: str, test_server: bool = False): + """Publish a YAK package to the YAK server.""" + + if not os.path.exists(yak_file) or not os.path.isfile(yak_file): + invoke.Exit(f"Yak file not found at {yak_file}. Please provide a valid path.") + if not yak_file.endswith(".yak"): + invoke.Exit("Invalid file type. Must be a .yak file.") + + with chdir(ctx.base_folder): + with tempfile.TemporaryDirectory("actions.publish_yak") as action_dir: + try: + _download_yak_executable(action_dir) + except ValueError: + invoke.Exit("Failed to download the yak executable") + + yak_exe_path: str = os.path.join(action_dir, "yak.exe") + if test_server: + ctx.run(f"{yak_exe_path} push --source https://test.yak.rhino3d.com {yak_file}") + # TODO: non test server publishing + + @invoke.task( help={ "gh_io_folder": "Folder where GH_IO.dll is located. If not specified, it will try to download from NuGet.", From d2ed41d9fcbc315fc3c9ff9c310c9b2cc0ff1081 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Tue, 21 Jan 2025 11:12:34 +0100 Subject: [PATCH 13/17] move revert breaking changes --- src/compas_invocations2/build.py | 81 ++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/compas_invocations2/build.py b/src/compas_invocations2/build.py index ff176f8..283cac4 100644 --- a/src/compas_invocations2/build.py +++ b/src/compas_invocations2/build.py @@ -1,6 +1,7 @@ import glob import os import shutil +import tempfile import invoke @@ -96,3 +97,83 @@ def prepare_changelog(ctx): changelog.write(content.replace("## ", UNRELEASED_CHANGELOG_TEMPLATE, 1)) ctx.run('git add CHANGELOG.md && git commit -m "Prepare changelog for next release"') + + +@invoke.task( + help={ + "gh_io_folder": "Folder where GH_IO.dll is located. If not specified, it will try to download from NuGet.", + "ironpython": "Command for running the IronPython executable. Defaults to `ipy`.", + "prefix": "(Optional) Append this prefix to the names of the built components.", + } +) +def build_ghuser_components(ctx, gh_io_folder=None, ironpython=None, prefix=None): + """Builds Grasshopper components using GH Componentizer.""" + prefix = prefix or getattr(ctx.ghuser, "prefix", None) + source_dir = os.path.abspath(ctx.ghuser.source_dir) + target_dir = os.path.abspath(ctx.ghuser.target_dir) + repo_url = "https://github.com/compas-dev/compas-actions.ghpython_components.git" + + with chdir(ctx.base_folder): + shutil.rmtree(os.path.join(ctx.base_folder, target_dir), ignore_errors=True) + + # Build IronPython Grasshopper user objects from source + with chdir(ctx.base_folder): + with tempfile.TemporaryDirectory("actions.ghcomponentizer") as action_dir: + ctx.run("git clone {} {}".format(repo_url, action_dir)) + + if not gh_io_folder: + gh_io_folder = tempfile.mkdtemp("ghio") + import compas_ghpython + + compas_ghpython.fetch_ghio_lib(gh_io_folder) + + if not ironpython: + ironpython = ctx.get("ironpython") or "ipy" + + gh_io_folder = os.path.abspath(gh_io_folder) + componentizer_script = os.path.join(action_dir, "componentize_ipy.py") + + cmd = "{} {} {} {}".format(ironpython, componentizer_script, source_dir, target_dir) + cmd += ' --ghio "{}"'.format(gh_io_folder) + if prefix: + cmd += ' --prefix "{}"'.format(prefix) + + ctx.run(cmd) + + +@invoke.task( + help={ + "gh_io_folder": "Folder where GH_IO.dll is located. If not specified, it will try to download from NuGet.", + "prefix": "(Optional) Append this prefix to the names of the built components.", + } +) +def build_cpython_ghuser_components(ctx, gh_io_folder=None, prefix=None): + """Builds CPython Grasshopper components using GH Componentizer.""" + prefix = prefix or getattr(ctx.ghuser_cpython, "prefix", None) + source_dir = os.path.abspath(ctx.ghuser_cpython.source_dir) + target_dir = os.path.abspath(ctx.ghuser_cpython.target_dir) + repo_url = "https://github.com/compas-dev/compas-actions.ghpython_components.git" + + with chdir(ctx.base_folder): + shutil.rmtree(os.path.join(ctx.base_folder, target_dir), ignore_errors=True) + + # Build CPython Grasshopper user objects from source + with chdir(ctx.base_folder): + with tempfile.TemporaryDirectory("actions.ghcomponentizer") as action_dir: + ctx.run("git clone {} {}".format(repo_url, action_dir)) + + if not gh_io_folder: + gh_io_folder = tempfile.mkdtemp("ghio") + import compas_ghpython + + compas_ghpython.fetch_ghio_lib(gh_io_folder) + + gh_io_folder = os.path.abspath(gh_io_folder) + componentizer_script = os.path.join(action_dir, "componentize_cpy.py") + + cmd = "{} {} {} {}".format("python", componentizer_script, source_dir, target_dir) + cmd += ' --ghio "{}"'.format(gh_io_folder) + if prefix: + cmd += ' --prefix "{}"'.format(prefix) + + ctx.run(cmd) From 918e1d3ee8119840c18e016f92f0e86fea68df00 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Tue, 21 Jan 2025 11:13:43 +0100 Subject: [PATCH 14/17] now with the attachement --- src/compas_invocations2/grasshopper.py | 80 -------------------------- 1 file changed, 80 deletions(-) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index c5c94e2..b165d82 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -186,83 +186,3 @@ def publish_yak(ctx, yak_file: str, test_server: bool = False): if test_server: ctx.run(f"{yak_exe_path} push --source https://test.yak.rhino3d.com {yak_file}") # TODO: non test server publishing - - -@invoke.task( - help={ - "gh_io_folder": "Folder where GH_IO.dll is located. If not specified, it will try to download from NuGet.", - "ironpython": "Command for running the IronPython executable. Defaults to `ipy`.", - "prefix": "(Optional) Append this prefix to the names of the built components.", - } -) -def build_ghuser_components(ctx, gh_io_folder=None, ironpython=None, prefix=None): - """Builds Grasshopper components using GH Componentizer.""" - prefix = prefix or getattr(ctx.ghuser, "prefix", None) - source_dir = os.path.abspath(ctx.ghuser.source_dir) - target_dir = os.path.abspath(ctx.ghuser.target_dir) - repo_url = "https://github.com/compas-dev/compas-actions.ghpython_components.git" - - with chdir(ctx.base_folder): - shutil.rmtree(os.path.join(ctx.base_folder, target_dir), ignore_errors=True) - - # Build IronPython Grasshopper user objects from source - with chdir(ctx.base_folder): - with tempfile.TemporaryDirectory("actions.ghcomponentizer") as action_dir: - ctx.run("git clone {} {}".format(repo_url, action_dir)) - - if not gh_io_folder: - gh_io_folder = tempfile.mkdtemp("ghio") - import compas_ghpython - - compas_ghpython.fetch_ghio_lib(gh_io_folder) - - if not ironpython: - ironpython = ctx.get("ironpython") or "ipy" - - gh_io_folder = os.path.abspath(gh_io_folder) - componentizer_script = os.path.join(action_dir, "componentize_ipy.py") - - cmd = "{} {} {} {}".format(ironpython, componentizer_script, source_dir, target_dir) - cmd += ' --ghio "{}"'.format(gh_io_folder) - if prefix: - cmd += ' --prefix "{}"'.format(prefix) - - ctx.run(cmd) - - -@invoke.task( - help={ - "gh_io_folder": "Folder where GH_IO.dll is located. If not specified, it will try to download from NuGet.", - "prefix": "(Optional) Append this prefix to the names of the built components.", - } -) -def build_cpython_ghuser_components(ctx, gh_io_folder=None, prefix=None): - """Builds CPython Grasshopper components using GH Componentizer.""" - prefix = prefix or getattr(ctx.ghuser_cpython, "prefix", None) - source_dir = os.path.abspath(ctx.ghuser_cpython.source_dir) - target_dir = os.path.abspath(ctx.ghuser_cpython.target_dir) - repo_url = "https://github.com/compas-dev/compas-actions.ghpython_components.git" - - with chdir(ctx.base_folder): - shutil.rmtree(os.path.join(ctx.base_folder, target_dir), ignore_errors=True) - - # Build CPython Grasshopper user objects from source - with chdir(ctx.base_folder): - with tempfile.TemporaryDirectory("actions.ghcomponentizer") as action_dir: - ctx.run("git clone {} {}".format(repo_url, action_dir)) - - if not gh_io_folder: - gh_io_folder = tempfile.mkdtemp("ghio") - import compas_ghpython - - compas_ghpython.fetch_ghio_lib(gh_io_folder) - - gh_io_folder = os.path.abspath(gh_io_folder) - componentizer_script = os.path.join(action_dir, "componentize_cpy.py") - - cmd = "{} {} {} {}".format("python", componentizer_script, source_dir, target_dir) - cmd += ' --ghio "{}"'.format(gh_io_folder) - if prefix: - cmd += ' --prefix "{}"'.format(prefix) - - ctx.run(cmd) From d049c783064687e364bd49e55b057109b8aee9fb Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Mon, 27 Jan 2025 14:26:23 +0100 Subject: [PATCH 15/17] added non test call to yak.exe --- src/compas_invocations2/grasshopper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index b165d82..e3aa05a 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -185,4 +185,5 @@ def publish_yak(ctx, yak_file: str, test_server: bool = False): yak_exe_path: str = os.path.join(action_dir, "yak.exe") if test_server: ctx.run(f"{yak_exe_path} push --source https://test.yak.rhino3d.com {yak_file}") - # TODO: non test server publishing + else: + ctx.run(f"{yak_exe_path} push {yak_file}") From 0929c62718ae678d90b1de22b34f6381172f164c Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Mon, 27 Jan 2025 15:09:56 +0100 Subject: [PATCH 16/17] download yak to parent directory --- src/compas_invocations2/grasshopper.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index e3aa05a..12ac586 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -24,9 +24,10 @@ def _download_yak_executable(target_dir: str): if response.status_code != 200: raise ValueError(f"Failed to download the yak.exe from url:{YAK_URL} with error : {response.status_code}") - with open(os.path.join(target_dir, "yak.exe"), "wb") as f: + target_path = os.path.join(target_dir, "yak.exe") + with open(target_path, "wb") as f: f.write(response.content) - # TODO: return path to the downloaded yak.exe + return target_path def _set_version_in_manifest(manifest_path: str, version: str): @@ -141,13 +142,14 @@ def yakerize( # Yak exe ##################################################################### + # yak executable shouldn't be in the target directory, otherwise it will be included in the package + target_parent = os.sep.join(target_dir.split(os.sep)[:-1]) try: - _download_yak_executable(target_dir) + yak_exe_path = _download_yak_executable(target_parent) except ValueError: invoke.Exit("Failed to download the yak executable") - - yak_exe_path: str = os.path.join(target_dir, "yak.exe") - yak_exe_path = os.path.abspath(yak_exe_path) + else: + yak_exe_path = os.path.abspath(yak_exe_path) with chdir(target_dir): try: From a207a9dd6d9016d4b3004854f1325a5c44152375 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Tue, 28 Jan 2025 10:54:38 +0100 Subject: [PATCH 17/17] allow minor rhino version. invoke.Exit needs to be raised --- src/compas_invocations2/grasshopper.py | 31 ++++++++++++++------------ 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/compas_invocations2/grasshopper.py b/src/compas_invocations2/grasshopper.py index 12ac586..cb51c83 100644 --- a/src/compas_invocations2/grasshopper.py +++ b/src/compas_invocations2/grasshopper.py @@ -54,14 +54,14 @@ def _clear_directory(path_to_dir): elif os.path.isdir(file_path): shutil.rmtree(file_path) except Exception as e: - invoke.Exit(f"Failed to delete {file_path}: {e}") + raise invoke.Exit(f"Failed to delete {file_path}: {e}") def _get_version_from_toml(toml_file: str) -> str: pyproject_data = toml.load(toml_file) version = pyproject_data.get("tool", {}).get("bumpversion", {}).get("current_version", None) if not version: - invoke.Exit("Failed to get version from pyproject.toml. Please provide a version number.") + raise invoke.Exit("Failed to get version from pyproject.toml. Please provide a version number.") return version @@ -98,20 +98,23 @@ def yakerize( target_rhino: str = "rh8", ) -> bool: """Create a Grasshopper YAK package from the current project.""" - if target_rhino not in ["rh6", "rh7", "rh8"]: - invoke.Exit("Invalid target Rhino version. Must be one of: rh6, rh7, rh8") - + # https://developer.rhino3d.com/guides/yak/the-anatomy-of-a-package/ + if target_rhino.split("_")[0] not in ["rh6", "rh7", "rh8"]: + raise invoke.Exit( + f"""Invalid target Rhino version `{target_rhino}`. Must be one of: rh6, rh7, rh8. + Minor version is optional and can be appended with a '_' (e.g. rh8_15).""" + ) gh_components_dir = gh_components_dir or _get_user_object_path(ctx) if not gh_components_dir: - invoke.Exit("Please provide the path to the directory containing the .ghuser files.") + raise invoke.Exit("Please provide the path to the directory containing the .ghuser files.") readme_path = readme_path or os.path.join(ctx.base_folder, "README.md") if not os.path.exists(readme_path): - invoke.Exit(f"Readme file not found at {readme_path}. Please provide a valid path.") + raise invoke.Exit(f"Readme file not found at {readme_path}. Please provide a valid path.") license_path = license_path or os.path.join(ctx.base_folder, "LICENSE") if not os.path.exists(license_path): - invoke.Exit(f"License file not found at {license_path}. Please provide a valid path.") + raise invoke.Exit(f"License file not found at {license_path}. Please provide a valid path.") version = version or _get_version_from_toml(os.path.join(ctx.base_folder, "pyproject.toml")) target_dir = os.path.join(ctx.base_folder, "dist", "yak_package") @@ -147,7 +150,7 @@ def yakerize( try: yak_exe_path = _download_yak_executable(target_parent) except ValueError: - invoke.Exit("Failed to download the yak executable") + raise invoke.Exit("Failed to download the yak executable") else: yak_exe_path = os.path.abspath(yak_exe_path) @@ -156,9 +159,9 @@ def yakerize( # not using `ctx.run()` here to get properly formatted output (unicode+colors) os.system(f"{yak_exe_path} build --platform any") except Exception as e: - invoke.Exit(f"Failed to build the yak package: {e}") + raise invoke.Exit(f"Failed to build the yak package: {e}") if not any([f.endswith(".yak") for f in os.listdir(target_dir)]): - invoke.Exit("No .yak file was created in the build directory.") + raise invoke.Exit("No .yak file was created in the build directory.") # filename is what tells YAK the target Rhino version..? taget_file = next((f for f in os.listdir(target_dir) if f.endswith(".yak"))) @@ -173,16 +176,16 @@ def publish_yak(ctx, yak_file: str, test_server: bool = False): """Publish a YAK package to the YAK server.""" if not os.path.exists(yak_file) or not os.path.isfile(yak_file): - invoke.Exit(f"Yak file not found at {yak_file}. Please provide a valid path.") + raise invoke.Exit(f"Yak file not found at {yak_file}. Please provide a valid path.") if not yak_file.endswith(".yak"): - invoke.Exit("Invalid file type. Must be a .yak file.") + raise invoke.Exit("Invalid file type. Must be a .yak file.") with chdir(ctx.base_folder): with tempfile.TemporaryDirectory("actions.publish_yak") as action_dir: try: _download_yak_executable(action_dir) except ValueError: - invoke.Exit("Failed to download the yak executable") + raise invoke.Exit("Failed to download the yak executable") yak_exe_path: str = os.path.join(action_dir, "yak.exe") if test_server: