Skip to content

Commit

Permalink
Merge pull request #4 from compas-dev/yakerize
Browse files Browse the repository at this point in the history
Yakerize
  • Loading branch information
chenkasirer authored Jan 28, 2025
2 parents c22bea5 + a207a9d commit 77935f2
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ invoke >=0.14
ruff
sphinx_compas2_theme
twine
wheel
wheel
toml
194 changes: 194 additions & 0 deletions src/compas_invocations2/grasshopper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
"""
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

import invoke
import requests
import toml

from compas_invocations2.console import chdir

YAK_URL = r"https://files.mcneel.com/yak/tools/latest/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}")

target_path = os.path.join(target_dir, "yak.exe")
with open(target_path, "wb") as f:
f.write(response.content)
return target_path


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):
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:
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:
raise invoke.Exit("Failed to get version from pyproject.toml. Please provide a version number.")
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={
"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.",
"target_rhino": "(Optional) The target Rhino version for the package. Defaults to 'rh8'.",
}
)
def yakerize(
ctx,
manifest_path: str,
logo_path: str,
gh_components_dir: str = None,
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."""
# 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:
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):
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):
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")

#####################################################################
# 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
#####################################################################

# 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:
yak_exe_path = _download_yak_executable(target_parent)
except ValueError:
raise invoke.Exit("Failed to download the yak executable")
else:
yak_exe_path = os.path.abspath(yak_exe_path)

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 any")
except Exception as 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)]):
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")))
new_filename = taget_file.replace("any-any", f"{target_rhino}-any")
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):
raise invoke.Exit(f"Yak file not found at {yak_file}. Please provide a valid path.")
if not yak_file.endswith(".yak"):
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:
raise 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}")
else:
ctx.run(f"{yak_exe_path} push {yak_file}")

0 comments on commit 77935f2

Please sign in to comment.