Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release: Boolean Tests #2312

Merged
merged 15 commits into from
Nov 5, 2024
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ COPY --chown=499 pyproject.toml .
COPY --chown=499 ./.git ./.git/

USER root
RUN trimesh-setup --install=test,gmsh,gltf_validator,llvmpipe,binvox
RUN trimesh-setup --install=test,gmsh,gltf_validator,llvmpipe,binvox,blender
USER user

RUN blender --version

# install things like pytest and make sure we're on Numpy 2.X
RUN pip install .[all] && \
python -c "import numpy as n; assert(n.__version__.startswith('2'))"
Expand Down
142 changes: 74 additions & 68 deletions docker/trimesh-setup
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ environment for `trimesh` in a Debian Docker image.
It probably isn't useful for most people unless you are running
this exact configuration.
"""

import argparse
import json
import logging
import os
import shutil
import subprocess
import sys
import tarfile
import tempfile
from fnmatch import fnmatch
from io import BytesIO

Expand Down Expand Up @@ -43,30 +42,40 @@ config_json = """
],
"test": [
"curl",
"git"
],
"git",
"libxkbcommon0"
],
"gmsh": ["libxft2", "libxinerama-dev", "libxcursor1","libgomp1"]
},
"fetch": {
"gltf_validator": {
"url": "https://github.com/KhronosGroup/glTF-Validator/releases/download/2.0.0-dev.3.8/gltf_validator-2.0.0-dev.3.8-linux64.tar.xz",
"sha256": "374c7807e28fe481b5075f3bb271f580ddfc0af3e930a0449be94ec2c1f6f49a",
"target": "$PATH",
"chmod": 755,
"chmod": {"gltf_validator": 755},
"extract_only": "gltf_validator"
},
"pandoc": {
"url": "https://github.com/jgm/pandoc/releases/download/3.1.1/pandoc-3.1.1-linux-amd64.tar.gz",
"sha256": "52b25f0115517e32047a06d821e63729108027bd06d9605fe8eac0fa83e0bf81",
"target": "$PATH",
"chmod": 755,
"chmod": {"pandoc": 755},
"extract_only": "pandoc"
},

"binvox": {
"url": "https://trimesh.s3-us-west-1.amazonaws.com/binvox",
"sha256": "82ee314a75986f67f1d2b5b3ccdfb3661fe57a6b428aa0e0f798fdb3e1734fe0",
"target": "$PATH",
"chmod": 755
"chmod": {"binvox": 755}
},

"blender": {
"url": "https://mirrors.ocf.berkeley.edu/blender/release/Blender4.2/blender-4.2.3-linux-x64.tar.xz",
"sha256": "3a64efd1982465395abab4259b4091d5c8c56054c7267e9633e4f702a71ea3f4",
"target": "$PATH",
"chmod": {"blender": 755},
"strip_components": 1
}
}
}
Expand Down Expand Up @@ -154,7 +163,22 @@ def fetch(url, sha256):
return data


def copy_to_path(file_path, prefix="~"):
def is_writable(path: str) -> bool:
if not os.path.isdir(path):
return False

test_fn = os.path.join(path, ".test_writeable_file")
try:
with open(test_fn, "w") as f:
f.write("can we write here?")
os.remove(test_fn)
return True
except BaseException as E:
print(path, E)
return False


def choose_in_path(prefix="~") -> str:
"""
Copy an executable file onto `PATH`, typically one of
the options in the current user's home directory.
Expand All @@ -167,18 +191,6 @@ def copy_to_path(file_path, prefix="~"):
The path prefix it is acceptable to copy into,
typically `~` for `/home/{current_user}`.
"""
# get the full path of the requested file
source = os.path.abspath(os.path.expanduser(file_path))

# get the file name
file_name = os.path.split(source)[-1]

# make sure the source file is readable and not empty
with open(source, "rb") as f:
file_data = f.read()
# check for empty files
if len(file_data) == 0:
raise ValueError(f"empty file: {file_path}")

# get all locations in PATH
candidates = [
Expand All @@ -202,13 +214,9 @@ def copy_to_path(file_path, prefix="~"):

# try writing to the shortest paths first
for index in argsort(scores):
path = os.path.join(candidates[index], file_name)
try:
shutil.copy(source, path)
print(f"wrote `{path}`")
path = candidates[index]
if is_writable(path):
return path
except BaseException:
pass

# none of our candidates worked
raise ValueError("unable to write to file")
Expand Down Expand Up @@ -259,17 +267,31 @@ def handle_fetch(
A hex string for the hash of the remote resource.
target : str
Target location on the local file system.
chmod : None or int.
chmod : None or dict
Change permissions for extracted files.
extract_skip : None or iterable
Skip a certain member of the archive.
extract_only : None or str
Extract *only* a single file from the archive,
overrides `extract_skip`.
Skip a certain member of the archive using
an `fnmatch` pattern, i.e. "lib/*"
extract_only : None or iterable
Extract only whitelisted files from the archive
using an `fnmatch` pattern, i.e. "lib/*"
strip_components : int
Strip off this many components from the file path
in the archive, i.e. at `1`, `a/b/c` is extracted to `target/b/c`
"""
if target.lower().strip() == "$path":
target = choose_in_path()
log.debug(f"identified destination as `{target}`")

if chmod is None:
chmod = {}

if extract_skip is None:
extract_skip = []
# if passed a single string
if isinstance(extract_only, str):
extract_only = [extract_only]

# get the raw bytes
log.debug(f"fetching: `{url}`")
raw = fetch(url=url, sha256=sha256)
Expand All @@ -284,10 +306,10 @@ def handle_fetch(
# get the archive
tar = tarfile.open(fileobj=BytesIO(raw), mode=mode)

if extract_skip is None:
extract_skip = []

for member in tar.getmembers():
if member.isdir():
continue

# final name after stripping components
name = "/".join(member.name.split("/")[strip_components:])

Expand All @@ -296,44 +318,28 @@ def handle_fetch(
log.debug(f"skipping: `{name}`")
continue

if extract_only is None:
path = os.path.join(target, name)
log.debug(f"extracting: `{path}`")
extract(tar=tar, member=member, path=path, chmod=chmod)
else:
name = name.split("/")[-1]
if name == extract_only:
if target.lower() == "$path":
with tempfile.TemporaryDirectory() as D:
path = os.path.join(D, name)
log.debug(f"extracting `{path}`")
extract(tar=tar, member=member, path=path, chmod=chmod)
copy_to_path(path)
return

path = os.path.join(target, name)
log.debug(f"extracting `{path}`")
extract(tar=tar, member=member, path=path, chmod=chmod)
return
if extract_only is not None and not any(
fnmatch(name, p) for p in extract_only
):
log.debug(f"skipping: `{name}`")
continue

path = os.path.join(target, name)
log.debug(f"extracting: `{path}`")
extract(tar=tar, member=member, path=path, chmod=chmod.get(name, None))

else:
# a single file
name = url.split("/")[-1].strip()
if target.lower() == "$path":
with tempfile.TemporaryDirectory() as D:
temp_path = os.path.join(D, name)
with open(temp_path, "wb") as f:
f.write(raw)
# move the file somewhere on the path
path = copy_to_path(temp_path)
else:
path = target
with open(path, "wb") as f:
f.write(raw)
path = os.path.join(target, name)
with open(path, "wb") as f:
f.write(raw)

current = chmod.get(name, None)
# apply chmod if requested
if chmod is not None:
if current is not None:
# python os.chmod takes an octal value
os.chmod(path, int(str(chmod), base=8))
os.chmod(path, int(str(current), base=8))


def load_config():
Expand All @@ -357,16 +363,16 @@ if __name__ == "__main__":
# collect `apt-get install`-able package
apt_select = []
handlers = {
"fetch": lambda x: handle_fetch(**x),
"apt": lambda x: apt_select.extend(x),
"fetch": lambda x: handle_fetch(**x),
}

# allow comma delimiters and de-duplicate
if args.install is None:
parser.print_help()
exit()
else:
select = set(" ".join(args.install).replace(",", " ").split())
select = " ".join(args.install).replace(",", " ").split()

log.debug(f'installing metapackages: `{", ".join(select)}`')

Expand Down
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ requires = ["setuptools >= 61.0", "wheel"]
[project]
name = "trimesh"
requires-python = ">=3.8"
version = "4.5.1"
version = "4.5.2"
authors = [{name = "Michael Dawson-Haggerty", email = "mikedh@kerfed.com"}]
license = {file = "LICENSE.md"}
description = "Import, export, process, analyze and view triangular meshes."
Expand All @@ -24,10 +24,12 @@ classifiers = [
"Topic :: Multimedia :: Graphics",
"Topic :: Multimedia :: Graphics :: 3D Modeling"
]
urls = {Homepage = "https://github.com/mikedh/trimesh"}

dependencies = ["numpy>=1.20"]

[project.urls]
homepage = "https://github.com/mikedh/trimesh"
documentation = "https://trimesh.org"

[project.readme]
file = "README.md"
content-type = "text/markdown"
Expand Down
Loading