Skip to content

Commit 77935f2

Browse files
authored
Merge pull request #4 from compas-dev/yakerize
Yakerize
2 parents c22bea5 + a207a9d commit 77935f2

File tree

3 files changed

+200
-1
lines changed

3 files changed

+200
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
* Added task `yakerize` to create YAK package for Grasshopper.
13+
1214
### Changed
1315

1416
* Task `build-cpython-ghuser-components` now uses `ghuser_cpython` configuration key.
17+
* Moved task `build-cpython-ghuser-components` from `build` to `grasshopper`.
18+
* Moved task `build-ghuser-components` from `build` to `grasshopper`.
1519

1620
### Removed
1721

requirements-dev.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ invoke >=0.14
66
ruff
77
sphinx_compas2_theme
88
twine
9-
wheel
9+
wheel
10+
toml
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
"""
2+
Adapted from: https://github.com/diffCheckOrg/diffCheck/blob/main/invokes/yakerize.py
3+
4+
Yakerize.py was originally developed as part of the DiffCheck plugin by
5+
Andrea Settimi, Damien Gilliard, Eleni Skevaki, Marirena Kladeftira (IBOIS, CRCL, EPFL) in 2024.
6+
It is distributed under the MIT License, provided this attribution is retained.
7+
"""
8+
9+
import os
10+
import shutil
11+
import tempfile
12+
13+
import invoke
14+
import requests
15+
import toml
16+
17+
from compas_invocations2.console import chdir
18+
19+
YAK_URL = r"https://files.mcneel.com/yak/tools/latest/yak.exe"
20+
21+
22+
def _download_yak_executable(target_dir: str):
23+
response = requests.get(YAK_URL)
24+
if response.status_code != 200:
25+
raise ValueError(f"Failed to download the yak.exe from url:{YAK_URL} with error : {response.status_code}")
26+
27+
target_path = os.path.join(target_dir, "yak.exe")
28+
with open(target_path, "wb") as f:
29+
f.write(response.content)
30+
return target_path
31+
32+
33+
def _set_version_in_manifest(manifest_path: str, version: str):
34+
with open(manifest_path, "r") as f:
35+
lines = f.readlines()
36+
37+
new_lines = []
38+
for line in lines:
39+
if "{{ version }}" in line:
40+
new_lines.append(line.replace("{{ version }}", version))
41+
else:
42+
new_lines.append(line)
43+
44+
with open(manifest_path, "w") as f:
45+
f.writelines(new_lines)
46+
47+
48+
def _clear_directory(path_to_dir):
49+
for f in os.listdir(path_to_dir):
50+
file_path = os.path.join(path_to_dir, f)
51+
try:
52+
if os.path.isfile(file_path) or os.path.islink(file_path):
53+
os.unlink(file_path)
54+
elif os.path.isdir(file_path):
55+
shutil.rmtree(file_path)
56+
except Exception as e:
57+
raise invoke.Exit(f"Failed to delete {file_path}: {e}")
58+
59+
60+
def _get_version_from_toml(toml_file: str) -> str:
61+
pyproject_data = toml.load(toml_file)
62+
version = pyproject_data.get("tool", {}).get("bumpversion", {}).get("current_version", None)
63+
if not version:
64+
raise invoke.Exit("Failed to get version from pyproject.toml. Please provide a version number.")
65+
return version
66+
67+
68+
def _get_user_object_path(context):
69+
if hasattr(context, "ghuser_cpython"):
70+
print("checking ghuser_cpython")
71+
return os.path.join(context.base_folder, context.ghuser_cpython.target_dir)
72+
elif hasattr(context, "ghuser"):
73+
print("checking ghuser")
74+
return os.path.join(context.base_folder, context.ghuser.target_dir)
75+
else:
76+
return None
77+
78+
79+
@invoke.task(
80+
help={
81+
"manifest_path": "Path to the manifest file.",
82+
"logo_path": "Path to the logo file.",
83+
"gh_components_dir": "(Optional) Path to the directory containing the .ghuser files.",
84+
"readme_path": "(Optional) Path to the readme file.",
85+
"license_path": "(Optional) Path to the license file.",
86+
"version": "(Optional) The version number to set in the manifest file.",
87+
"target_rhino": "(Optional) The target Rhino version for the package. Defaults to 'rh8'.",
88+
}
89+
)
90+
def yakerize(
91+
ctx,
92+
manifest_path: str,
93+
logo_path: str,
94+
gh_components_dir: str = None,
95+
readme_path: str = None,
96+
license_path: str = None,
97+
version: str = None,
98+
target_rhino: str = "rh8",
99+
) -> bool:
100+
"""Create a Grasshopper YAK package from the current project."""
101+
# https://developer.rhino3d.com/guides/yak/the-anatomy-of-a-package/
102+
if target_rhino.split("_")[0] not in ["rh6", "rh7", "rh8"]:
103+
raise invoke.Exit(
104+
f"""Invalid target Rhino version `{target_rhino}`. Must be one of: rh6, rh7, rh8.
105+
Minor version is optional and can be appended with a '_' (e.g. rh8_15)."""
106+
)
107+
gh_components_dir = gh_components_dir or _get_user_object_path(ctx)
108+
if not gh_components_dir:
109+
raise invoke.Exit("Please provide the path to the directory containing the .ghuser files.")
110+
111+
readme_path = readme_path or os.path.join(ctx.base_folder, "README.md")
112+
if not os.path.exists(readme_path):
113+
raise invoke.Exit(f"Readme file not found at {readme_path}. Please provide a valid path.")
114+
115+
license_path = license_path or os.path.join(ctx.base_folder, "LICENSE")
116+
if not os.path.exists(license_path):
117+
raise invoke.Exit(f"License file not found at {license_path}. Please provide a valid path.")
118+
119+
version = version or _get_version_from_toml(os.path.join(ctx.base_folder, "pyproject.toml"))
120+
target_dir = os.path.join(ctx.base_folder, "dist", "yak_package")
121+
122+
#####################################################################
123+
# Copy manifest, logo, misc folder (readme, license, etc)
124+
#####################################################################
125+
# if target dit exists, make sure it's empty
126+
if os.path.exists(target_dir) and os.path.isdir(target_dir):
127+
_clear_directory(target_dir)
128+
else:
129+
os.makedirs(target_dir, exist_ok=False)
130+
131+
manifest_target = shutil.copy(manifest_path, target_dir)
132+
_set_version_in_manifest(manifest_target, version)
133+
shutil.copy(logo_path, target_dir)
134+
135+
path_miscdir: str = os.path.join(target_dir, "misc")
136+
os.makedirs(path_miscdir, exist_ok=False)
137+
shutil.copy(readme_path, path_miscdir)
138+
shutil.copy(license_path, path_miscdir)
139+
140+
for f in os.listdir(gh_components_dir):
141+
if f.endswith(".ghuser"):
142+
shutil.copy(os.path.join(gh_components_dir, f), target_dir)
143+
144+
#####################################################################
145+
# Yak exe
146+
#####################################################################
147+
148+
# yak executable shouldn't be in the target directory, otherwise it will be included in the package
149+
target_parent = os.sep.join(target_dir.split(os.sep)[:-1])
150+
try:
151+
yak_exe_path = _download_yak_executable(target_parent)
152+
except ValueError:
153+
raise invoke.Exit("Failed to download the yak executable")
154+
else:
155+
yak_exe_path = os.path.abspath(yak_exe_path)
156+
157+
with chdir(target_dir):
158+
try:
159+
# not using `ctx.run()` here to get properly formatted output (unicode+colors)
160+
os.system(f"{yak_exe_path} build --platform any")
161+
except Exception as e:
162+
raise invoke.Exit(f"Failed to build the yak package: {e}")
163+
if not any([f.endswith(".yak") for f in os.listdir(target_dir)]):
164+
raise invoke.Exit("No .yak file was created in the build directory.")
165+
166+
# filename is what tells YAK the target Rhino version..?
167+
taget_file = next((f for f in os.listdir(target_dir) if f.endswith(".yak")))
168+
new_filename = taget_file.replace("any-any", f"{target_rhino}-any")
169+
os.rename(taget_file, new_filename)
170+
171+
172+
@invoke.task(
173+
help={"yak_file": "Path to the .yak file to publish.", "test_server": "True to publish to the test server."}
174+
)
175+
def publish_yak(ctx, yak_file: str, test_server: bool = False):
176+
"""Publish a YAK package to the YAK server."""
177+
178+
if not os.path.exists(yak_file) or not os.path.isfile(yak_file):
179+
raise invoke.Exit(f"Yak file not found at {yak_file}. Please provide a valid path.")
180+
if not yak_file.endswith(".yak"):
181+
raise invoke.Exit("Invalid file type. Must be a .yak file.")
182+
183+
with chdir(ctx.base_folder):
184+
with tempfile.TemporaryDirectory("actions.publish_yak") as action_dir:
185+
try:
186+
_download_yak_executable(action_dir)
187+
except ValueError:
188+
raise invoke.Exit("Failed to download the yak executable")
189+
190+
yak_exe_path: str = os.path.join(action_dir, "yak.exe")
191+
if test_server:
192+
ctx.run(f"{yak_exe_path} push --source https://test.yak.rhino3d.com {yak_file}")
193+
else:
194+
ctx.run(f"{yak_exe_path} push {yak_file}")

0 commit comments

Comments
 (0)