Add project-tools #142
81
scripts/project-helper-tools/consistency_check.py
Executable file
81
scripts/project-helper-tools/consistency_check.py
Executable file
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
def create_path_dict(startpath, max_depth):
|
||||
path_stucture_dict={}
|
||||
start_folder_name = os.path.basename(start_search_path)
|
||||
for root, dirs, files in os.walk(startpath, followlinks=True):
|
||||
# We are only interested in the files and folders inside the start path.
|
||||
cur_path = root.replace(startpath, start_folder_name)
|
||||
level = cur_path.count(os.sep)
|
||||
# Sanity check. We don't expect the directory tree to be too deep.
|
||||
# Therefore, we will stop if we go too deep.
|
||||
# This avoids infinite loops that can happen when we follow symlinks
|
||||
if level > max_depth:
|
||||
print("We have gone unexptibly deep in the file structure, stopping...")
|
||||
exit(1)
|
||||
|
||||
# Insert the data into the dictionary
|
||||
nested_dict = path_stucture_dict
|
||||
key_path = cur_path.split(os.sep)
|
||||
final_key = key_path[-1]
|
||||
for key in key_path[:-1]:
|
||||
nested_dict = nested_dict[key]
|
||||
|
||||
files_dict = {}
|
||||
for f in files:
|
||||
files_dict[f] = "file"
|
||||
|
||||
nested_dict[final_key] = files_dict
|
||||
|
||||
# Print the files structure to we can see the traversed file tree
|
||||
indent = ' ' * 4 * (level)
|
||||
print('{}{}/'.format(indent, os.path.basename(root)))
|
||||
subindent = ' ' * 4 * (level + 1)
|
||||
|
||||
for f in files:
|
||||
print('{}{}'.format(subindent, f))
|
||||
return path_stucture_dict
|
||||
|
||||
def check_if_structure_is_consistent(start_path, path_dict, error_list):
|
||||
for key in path_dict:
|
||||
cur_path = str(start_path) + os.sep + key
|
||||
print("Checking path: " + cur_path)
|
||||
if os.path.exists(cur_path):
|
||||
nested_item = path_dict[key]
|
||||
if type(nested_item) is not dict:
|
||||
if os.path.isfile(cur_path):
|
||||
continue
|
||||
else:
|
||||
# This must be a file, warn if it is not
|
||||
#print("ERROR: " + cur_path + " is not a file, when it should be!")
|
||||
error_list += ["ERROR: " + cur_path + " is not a file, when it should be!"]
|
||||
check_if_structure_is_consistent(cur_path, nested_item, error_list)
|
||||
else:
|
||||
#print("ERROR: " + cur_path + " doesn't exist!")
|
||||
error_list += ["ERROR: " + cur_path + " doesn't exist!"]
|
||||
|
||||
current_file_path=pathlib.Path(__file__)
|
||||
start_search_path=current_file_path.parent.parent.parent.resolve()
|
||||
#path_dict = create_path_dict(str(start_search_path), 5)
|
||||
|
||||
# path_dict pre-generated. This is the stucture the consistency check will ensure is there
|
||||
path_dict = {'pets': {'shared': {'pets': {'artifacts': {}}}, 'svn': {'tools': {'consistency_check.py': 'file'}}, 'local': {'blender': {}, 'scripts': {}, 'config': {}}}}
|
||||
# TODO perhaps make a function to pretty print out the path_dict for easier inspection
|
||||
|
||||
error_list = []
|
||||
check_if_structure_is_consistent(start_search_path.parent, path_dict, error_list)
|
||||
|
||||
print()
|
||||
if len(error_list) == 0:
|
||||
print("Consistency check: PASSED")
|
||||
exit(0)
|
||||
else:
|
||||
print("Consistency check: FAILED")
|
||||
print()
|
||||
for error in error_list:
|
||||
print(error)
|
||||
# Exit with error as we didn't pass the consistency check
|
||||
exit(1)
|
72
scripts/project-helper-tools/rollback_blender.sh
Executable file
72
scripts/project-helper-tools/rollback_blender.sh
Executable file
@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Get all archived sha hashes to use as ID numbers
|
||||
|
||||
ARCHIVE_DIR=../../shared/pets/artifacts/blender/previous/
|
||||
|
||||
cd $ARCHIVE_DIR
|
||||
|
||||
# TODO only rollbacks for one OS version
|
||||
|
||||
OS=linux
|
||||
|
||||
# Create an array with the available builds/files
|
||||
available_builds=($(ls -t *$OS*.sha256))
|
||||
|
||||
installed_build=$(cd ../ && ls *$OS*.sha256)
|
||||
|
||||
echo $installed_build
|
||||
|
||||
echo -e "Available builds:\n"
|
||||
|
||||
valid_ids=()
|
||||
|
||||
ITER=0
|
||||
|
||||
for file in ${available_builds[@]}; do
|
||||
file_date=$(stat -c '%y' $file)
|
||||
# Cutoff the the date string to only show "date hours:min"
|
||||
file_date=${file_date:0:19}
|
||||
if [ $file == $installed_build ]; then
|
||||
printf "\e[1mID:\e[0m \e[100m%3s " $ITER
|
||||
printf "(%s)" "$file_date"
|
||||
printf " <current>\e[0m"
|
||||
else
|
||||
printf "\e[1mID:\e[0m %3s " $ITER
|
||||
printf "(%s)" "$file_date"
|
||||
fi
|
||||
valid_ids+=($ITER)
|
||||
echo
|
||||
ITER=$(expr $ITER + 1)
|
||||
done
|
||||
|
||||
echo -e "\n"
|
||||
|
||||
choosen_blend_id=-1
|
||||
|
||||
prompt="Select which Blender build number to switch to. (press ENTER to confirm): "
|
||||
while read -rp "$prompt" num && [[ "$num" ]]; do
|
||||
|
||||
# Check if "num" is a valid number.
|
||||
[[ "$num" != *[[:digit:]]* ]] &&
|
||||
{ echo "You need to choose a number!"; continue; }
|
||||
|
||||
if [[ ! " ${valid_ids[*]} " =~ " ${num} " ]]; then
|
||||
# whatever you want to do when array doesn't contain value
|
||||
echo "$num is not an available ID!"
|
||||
continue
|
||||
fi
|
||||
|
||||
choosen_blend_id=$num
|
||||
|
||||
break
|
||||
done
|
||||
|
||||
((choosen_blend_id < 0)) && exit 0
|
||||
|
||||
choose_file=${available_builds[$choosen_blend_id]::-7}
|
||||
|
||||
# Remove the build we are replacing
|
||||
rm ../*$OS*
|
||||
# Copy over the choosen build
|
||||
cp $choose_file* ../
|
219
scripts/project-helper-tools/run_blender.py
Executable file
219
scripts/project-helper-tools/run_blender.py
Executable file
@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# The project base path (where shared, local and svn are located)
|
||||
PATH_BASE = Path(__file__).resolve().parent.parent.parent
|
||||
PATH_ARTIFACTS = PATH_BASE / 'shared' / 'artifacts'
|
||||
PATH_LOCAL = PATH_BASE / 'local'
|
||||
|
||||
|
||||
def setup_logger():
|
||||
# Create a logger
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# Create a StreamHandler that outputs log messages to stdout
|
||||
stream_handler = logging.StreamHandler(sys.stdout)
|
||||
stream_handler.setLevel(logging.DEBUG)
|
||||
|
||||
# Create a formatter for the log messages
|
||||
formatter = logging.Formatter('%(levelname)s - %(message)s')
|
||||
|
||||
# Set the formatter for the StreamHandler
|
||||
stream_handler.setFormatter(formatter)
|
||||
|
||||
# Add the StreamHandler to the logger
|
||||
logger.addHandler(stream_handler)
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
logger = setup_logger()
|
||||
|
||||
|
||||
@dataclass
|
||||
class BlenderBuild:
|
||||
archive: None
|
||||
checksum: None
|
||||
|
||||
|
||||
def extract_dmg(dmg_file: Path, internal_pah, dst_path: Path):
|
||||
# Execute hdiutil to mount the dmg file
|
||||
mount_process = subprocess.run(['hdiutil', 'attach', dmg_file, '-plist'], capture_output=True, text=True)
|
||||
mount_output = mount_process.stdout
|
||||
|
||||
# Parse the mount_output to retrieve the mounted volume name
|
||||
import plistlib
|
||||
plist_data = plistlib.loads(mount_output.encode('utf-8'))
|
||||
mount_point = plist_data['system-entities'][0]['mount-point']
|
||||
|
||||
# Ensure destination directory exists
|
||||
dst_path = dst_path / internal_pah
|
||||
dst_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Extract the contents of the mounted dmg to the destination directory
|
||||
file_in_dmg = os.path.join(mount_point, internal_pah)
|
||||
subprocess.run(['ditto', file_in_dmg, dst_path])
|
||||
|
||||
# Unmount the dmg file
|
||||
subprocess.run(['hdiutil', 'detach', mount_point])
|
||||
|
||||
|
||||
def extract_tar_xz(file_path: Path, dst_path: Path):
|
||||
dst_path.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(['tar', 'xf', file_path, '--directory', dst_path, '--strip-components=1'])
|
||||
|
||||
|
||||
def extract_zip(file_path: Path, dst_path: Path):
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
with zipfile.ZipFile(file_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(temp_dir)
|
||||
|
||||
try:
|
||||
src_path = [subdir for subdir in Path(temp_dir).iterdir()][0]
|
||||
except IndexError:
|
||||
logger.fatal("The archive %s does not contain any directory" % file_path.name)
|
||||
sys.exit(1)
|
||||
|
||||
dst_path.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(src_path, dst_path)
|
||||
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
|
||||
def compare_checksum(file1, file2):
|
||||
with open(file1, 'rb') as f1, open(file2, 'rb') as f2:
|
||||
hash1 = hashlib.sha256(f1.read()).hexdigest()
|
||||
hash2 = hashlib.sha256(f2.read()).hexdigest()
|
||||
|
||||
return hash1 == hash2
|
||||
|
||||
|
||||
def update_addons():
|
||||
# Check if we have the latest add-ons from shared
|
||||
studio_pipeline_artifacts = PATH_ARTIFACTS / 'blender-studio-pipeline'
|
||||
artifact_checksum = studio_pipeline_artifacts / 'main.zip.sha256'
|
||||
|
||||
if not artifact_checksum.exists():
|
||||
logger.error("Missing file %s" % artifact_checksum)
|
||||
logger.error("Could not update add-ons")
|
||||
return
|
||||
|
||||
local_checksum = PATH_LOCAL / 'main.zip.sha256'
|
||||
|
||||
if local_checksum.exists():
|
||||
if compare_checksum(local_checksum, artifact_checksum):
|
||||
logger.info("Already up to date")
|
||||
return
|
||||
|
||||
# Extract the archive in a temp location and move the addons content to local
|
||||
tmp_dir = Path(tempfile.mkdtemp())
|
||||
|
||||
# Extract the zip file to the temporary directory
|
||||
with zipfile.ZipFile(studio_pipeline_artifacts / 'main.zip', 'r') as zip_ref:
|
||||
zip_ref.extractall(tmp_dir)
|
||||
|
||||
# Get the path of the folder to copy
|
||||
src_path_base = tmp_dir / 'blender-studio-pipeline' / 'scripts-blender' / 'addons'
|
||||
dst_path_base = PATH_LOCAL / 'scripts' / 'addons'
|
||||
|
||||
# Get a list of directories inside the given directory
|
||||
addons = [subdir.name for subdir in src_path_base.iterdir() if subdir.is_dir()]
|
||||
|
||||
for addon_name in addons:
|
||||
logger.debug("Moving %s" % addon_name)
|
||||
src_dir_addon = src_path_base / addon_name
|
||||
dst_dir_addon = dst_path_base / addon_name
|
||||
if dst_dir_addon.exists():
|
||||
shutil.rmtree(dst_dir_addon)
|
||||
shutil.move(src_dir_addon, dst_dir_addon)
|
||||
|
||||
# Clean up the temporary directory
|
||||
shutil.rmtree(tmp_dir)
|
||||
|
||||
# Update the sha256 file
|
||||
shutil.copy(artifact_checksum, local_checksum)
|
||||
|
||||
|
||||
def update_blender():
|
||||
system_name = platform.system().lower()
|
||||
architecture = platform.machine()
|
||||
|
||||
# Check if we have the latest add-ons from shared
|
||||
artifacts_path = PATH_ARTIFACTS / 'blender'
|
||||
|
||||
blender_build = BlenderBuild
|
||||
|
||||
# Iterate over the files in the source directory
|
||||
for file_path in artifacts_path.iterdir():
|
||||
if file_path.is_file() \
|
||||
and system_name in file_path.name \
|
||||
and architecture in file_path.name \
|
||||
and file_path.name.endswith('sha256'):
|
||||
blender_build.checksum = file_path.name
|
||||
blender_build.archive = file_path.with_suffix('')
|
||||
break
|
||||
|
||||
artifact_checksum = artifacts_path / blender_build.checksum
|
||||
|
||||
if not artifact_checksum.exists():
|
||||
logger.error("Missing file %s" % artifact_checksum)
|
||||
logger.error("Could not update add-ons")
|
||||
return
|
||||
|
||||
local_checksum = PATH_LOCAL / 'blender' / f"{system_name}.sha256"
|
||||
|
||||
if local_checksum.exists():
|
||||
if compare_checksum(local_checksum, artifact_checksum):
|
||||
logger.info("Already up to date")
|
||||
return
|
||||
|
||||
src = artifacts_path / blender_build.archive
|
||||
dst = PATH_LOCAL / 'blender' / system_name
|
||||
|
||||
if system_name == 'linux':
|
||||
extract_tar_xz(src, dst)
|
||||
elif system_name == 'darwin':
|
||||
extract_dmg(src, 'Blender.app', dst)
|
||||
elif system_name == 'windows':
|
||||
extract_zip(src, dst)
|
||||
shutil.copy(artifact_checksum, local_checksum)
|
||||
|
||||
|
||||
def launch_blender():
|
||||
system_name = platform.system().lower()
|
||||
blender_path_base = PATH_LOCAL / 'blender' / system_name
|
||||
if system_name == 'linux':
|
||||
blender_path = blender_path_base / 'blender'
|
||||
elif system_name == 'darwin':
|
||||
blender_path = blender_path_base / 'Blender.app' / 'Contents' / 'MacOS' / 'Blender'
|
||||
elif system_name == 'windows':
|
||||
blender_path = blender_path_base / 'blender.exe'
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
# os.environ['BLENDER_USER_CONFIG'] = str(PATH_LOCAL / 'config')
|
||||
os.environ['BLENDER_USER_SCRIPTS'] = str(PATH_LOCAL / 'scripts')
|
||||
subprocess.run([blender_path])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info('Update Add-ons')
|
||||
update_addons()
|
||||
logger.info('Update Blender')
|
||||
update_blender()
|
||||
logger.info('Launch Blender')
|
||||
launch_blender()
|
66
scripts/project-helper-tools/run_blender.sh
Executable file
66
scripts/project-helper-tools/run_blender.sh
Executable file
@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# TODO error out if blender is not installed locally already and there is no blender to download on shared
|
||||
|
||||
cur_dir=$(pwd)
|
||||
|
||||
cd ../../local
|
||||
|
||||
DOWNLOAD_DIR=../shared/pets
|
||||
|
||||
update_addons() {
|
||||
zip_name="main.zip"
|
||||
|
||||
# Check if we have the latest addons from shared
|
||||
if [ -f $zip_name.sha256 ]; then
|
||||
shasum=$(cat $zip_name.sha256)
|
||||
pushd $DOWNLOAD_DIR/addons
|
||||
echo $shasum | sha256sum --check - && echo addons already at the latest version && popd && return 0
|
||||
popd
|
||||
fi
|
||||
|
||||
rm -fr ./scripts/*
|
||||
|
||||
extract_folder="blender-studio-pipeline/scripts-blender/addons"
|
||||
|
||||
# Record the extracted content
|
||||
# Remove the first 5 lines, remove the last two lines, and extract the fourth column
|
||||
unzip -l $DOWNLOAD_DIR/addons/$zip_name "$extract_folder/*" | tail -n +5 | head -n -2 | awk '{ print $4 }' > $zip_name.contents
|
||||
|
||||
unzip $DOWNLOAD_DIR/addons/$zip_name "$extract_folder/*" -d "./scripts"
|
||||
mv ./scripts/$extract_folder ./scripts/
|
||||
rm -fr ./scripts/blender-studio-pipeline
|
||||
cp $DOWNLOAD_DIR/addons/$zip_name.sha256 .
|
||||
}
|
||||
|
||||
update_blender() {
|
||||
os=linux
|
||||
|
||||
# Ensure the os folder exists
|
||||
mkdir -p blender/$os
|
||||
|
||||
if [ -f blender/$os.sha256 ]; then
|
||||
shasum=$(cat blender/$os.sha256)
|
||||
pushd $DOWNLOAD_DIR/artifacts/blender
|
||||
echo $shasum *$os*.tar.xz | sha256sum --check - && echo blender already at the latest version && popd && return 0
|
||||
popd
|
||||
fi
|
||||
|
||||
rm -fr blender/$os/*
|
||||
|
||||
echo Extracting Blender
|
||||
tar xf $DOWNLOAD_DIR/artifacts/blender/*$os*.tar.xz --directory blender/$os/ --strip-components=1 --checkpoint=.1000
|
||||
cp $DOWNLOAD_DIR/artifacts/blender/*$os*.sha256 blender/$os.sha256
|
||||
}
|
||||
|
||||
update_addons
|
||||
update_blender
|
||||
|
||||
os=linux
|
||||
cd blender/$os
|
||||
|
||||
export BLENDER_USER_CONFIG=../../config
|
||||
export BLENDER_USER_SCRIPTS=../../scripts
|
||||
|
||||
# Actually launch Blender
|
||||
./blender
|
29
scripts/project-helper-tools/update_addons.sh
Executable file
29
scripts/project-helper-tools/update_addons.sh
Executable file
@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
DOWNLOAD_DIR=../../shared/pets/artifacts/addons/
|
||||
|
||||
download_addon() {
|
||||
URL=$1
|
||||
OUT_NAME=$2
|
||||
|
||||
# Ensure that the download directory exists
|
||||
mkdir -p $DOWNLOAD_DIR
|
||||
|
||||
# Switch to the download directory
|
||||
pushd $DOWNLOAD_DIR
|
||||
|
||||
# TODO Can't check any shasums before downloading so always remove and redownload everything for now
|
||||
rm $OUT_NAME*
|
||||
|
||||
# Download the addons repo
|
||||
wget $URL -O $OUT_NAME
|
||||
sha256sum *.zip > $OUT_NAME.sha256
|
||||
|
||||
popd
|
||||
}
|
||||
|
||||
# download_addon <url to addon zip> <output name zip>
|
||||
|
||||
# Special download dir for monorepo with addons
|
||||
DOWNLOAD_DIR=../../shared/pets/artifacts/blender-studio-pipeline/
|
||||
download_addon https://projects.blender.org/studio/blender-studio-pipeline/archive/main.zip blender-studio-pipeline-main.zip
|
69
scripts/project-helper-tools/update_blender.sh
Executable file
69
scripts/project-helper-tools/update_blender.sh
Executable file
@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Stop execution on error
|
||||
set -e
|
||||
|
||||
HOMEPAGE="https://builder.blender.org/download/"
|
||||
|
||||
BLENDER_BRANCH=main
|
||||
DOWNLOAD_DIR=../../shared/pets/artifacts/blender
|
||||
|
||||
# Ensure that the download directory exists
|
||||
mkdir -p $DOWNLOAD_DIR
|
||||
|
||||
# Switch to the download directory
|
||||
pushd $DOWNLOAD_DIR
|
||||
|
||||
# .zip == Windows
|
||||
# .dmg == Mac
|
||||
# .tar.xz == Linux
|
||||
|
||||
# Create a dictinary with the above information
|
||||
declare -A extensions
|
||||
extensions=( ["windows"]="zip" ["darwin.x86_64"]="dmg" ["darwin.arm64"]="dmg" ["linux"]="tar.xz" )
|
||||
|
||||
latest_urls=$(wget --quiet -O - "$HOMEPAGE" | \
|
||||
grep -io '<a href=['"'"'"][^"'"'"']*['"'"'"]' | \
|
||||
sed -e 's/^<a href=["'"'"']//i' -e 's/["'"'"']$//i' | \
|
||||
grep +$BLENDER_BRANCH)
|
||||
|
||||
for os in windows darwin.arm64 linux
|
||||
do
|
||||
file_extention="${extensions[$os]}"
|
||||
|
||||
latest=$(echo "$latest_urls" | grep $os | grep $file_extention$ )
|
||||
|
||||
# Strip carridge returns from teh sha sum as the windows ones has it
|
||||
shasum=$(curl -s "$latest".sha256 | tr -d '\r')
|
||||
|
||||
echo sum: "$shasum" *"$file_extention"
|
||||
# Check if we need to download the file by looking at the shasum of the currently downloaded file (if any)
|
||||
if [ ! -f *$file_extention ]; then
|
||||
# Make sure that we don't have any lingering sha files
|
||||
rm *$file_extention* || true # "|| true" is here to ensure that we catch the case were there are no files to remove for "set -e"
|
||||
else
|
||||
# Skip downloading if shasum matches the current file on disk
|
||||
echo $shasum *$file_extention | sha256sum --check - && echo $os: already at latest version && continue
|
||||
|
||||
mkdir -p previous
|
||||
mv *$file_extention* previous/
|
||||
|
||||
pushd previous
|
||||
# Remove older backups if there are more than 10
|
||||
num_old_files=$(ls -1 *$file_extention | wc -l)
|
||||
if [ "$num_old_files" -ge 10 ]; then
|
||||
files_to_remove=$(ls -t *$file_extention | tail $((10-$num_old_files)))
|
||||
# Add the shasums as well
|
||||
shasums_to_remove=$(echo $files_to_remove | sed -e 's/$/.sha256')
|
||||
rm $files_to_remove $shasums_to_remove
|
||||
fi
|
||||
popd
|
||||
fi
|
||||
|
||||
wget -c "$latest"
|
||||
wget -c "$latest".sha256
|
||||
|
||||
echo $shasum *$file_extention | sha256sum --check - || (echo Downloaded file does not match its shasum, exiting! && exit 1)
|
||||
done
|
||||
|
||||
popd
|
Loading…
Reference in New Issue
Block a user