oscillode/scripts/pack.py

153 lines
4.7 KiB
Python
Raw Normal View History

2024-09-26 10:23:17 +02:00
# oscillode
# Copyright (C) 2024 oscillode Project Contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# blender_maxwell
# Copyright (C) 2024 blender_maxwell Project Contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import contextlib
import logging
import tempfile
import typing as typ
import zipfile
from pathlib import Path
import info
LogLevel: typ.TypeAlias = int
_PROJ_VERSION_STR = str(
tuple(int(el) for el in info.PROJ_SPEC['project']['version'].split('.'))
)
_PROJ_DESC_STR = info.PROJ_SPEC['project']['description']
BL_INFO_REPLACEMENTS = {
"'version': (0, 0, 0),": f"'version': {_PROJ_VERSION_STR},",
"'description': 'Placeholder',": f"'description': '{_PROJ_DESC_STR}',",
}
@contextlib.contextmanager
def zipped_addon( # noqa: PLR0913
path_addon_pkg: Path,
path_addon_zip: Path,
path_pyproject_toml: Path,
path_requirements_lock: Path,
initial_log_level: LogLevel = logging.INFO,
replace_if_exists: bool = False,
remove_after_close: bool = True,
) -> typ.Iterator[Path]:
"""Context manager exposing a folder as a (temporary) zip file.
2024-04-07 18:39:27 +02:00
Parameters:
path_addon_pkg: Path to the folder containing __init__.py of the Blender addon.
path_addon_zip: Path to the Addon ZIP to generate.
path_pyproject_toml: Path to the `pyproject.toml` of the project.
This is made available to the addon, to de-duplicate definition of name,
2024-04-07 18:39:27 +02:00
The .zip file is deleted afterwards, unless `remove_after_close` is specified.
"""
# Delete Existing ZIP (maybe)
if path_addon_zip.is_file():
if replace_if_exists:
msg = 'File already exists where ZIP would be made'
raise ValueError(msg)
path_addon_zip.unlink()
# Create New ZIP file of the addon directory
with zipfile.ZipFile(path_addon_zip, 'w', zipfile.ZIP_DEFLATED) as f_zip:
# Install Addon Files @ /*
for file_to_zip in path_addon_pkg.rglob('*'):
# Dynamically Alter 'bl_info' in __init__.py
## This is the only way to propagate ex. version information
if str(file_to_zip.relative_to(path_addon_pkg)) == '__init__.py':
with (
file_to_zip.open('r') as f_init,
tempfile.NamedTemporaryFile(mode='w') as f_tmp,
):
initpy = f_init.read()
for (
to_replace,
replacement,
) in BL_INFO_REPLACEMENTS.items():
initpy = initpy.replace(to_replace, replacement)
f_tmp.write(initpy)
# Write to ZIP
f_zip.writestr(
str(file_to_zip.relative_to(path_addon_pkg.parent)),
initpy,
)
# Write File to Zip
else:
f_zip.write(file_to_zip, file_to_zip.relative_to(path_addon_pkg.parent))
# Install pyproject.toml @ /pyproject.toml of Addon
f_zip.write(
path_pyproject_toml,
str(Path(path_addon_pkg.name) / Path(path_pyproject_toml.name)),
)
# Install requirements.lock @ /requirements.txt of Addon
f_zip.write(
path_requirements_lock,
str(Path(path_addon_pkg.name) / Path(path_requirements_lock.name)),
)
# Set Initial Log-Level
f_zip.writestr(
str(Path(path_addon_pkg.name) / info.BOOTSTRAP_LOG_LEVEL_FILENAME),
str(initial_log_level),
)
# Delete the ZIP
try:
yield path_addon_zip
finally:
if remove_after_close:
path_addon_zip.unlink()
####################
# - Run Blender w/Clean Addon Reinstall
####################
def main():
with zipped_addon(
path_addon_pkg=info.PATH_ADDON_PKG,
path_addon_zip=info.PATH_ADDON_ZIP,
path_pyproject_toml=info.PATH_ROOT / 'pyproject.toml',
path_requirements_lock=info.PATH_ROOT / 'requirements.lock',
replace_if_exists=True,
remove_after_close=False,
):
pass
if __name__ == '__main__':
main()