more Prep for CI

This commit is contained in:
Marcel Peterkau 2025-06-02 15:05:42 +02:00
parent 618ee4ce80
commit cf032fe516
3 changed files with 86 additions and 108 deletions

12
Jenkinsfile vendored
View File

@ -52,9 +52,17 @@ wifi_ap_password = DummyAP
} }
} }
stage('📦 Archive Firmware') { stage('📦 Find & Archive Firmware') {
steps { steps {
archiveArtifacts artifacts: "Software/.pio/build/${params.BUILD_ENV}/firmware.bin", fingerprint: true script {
def buildPath = "Software/.pio/build/${params.BUILD_ENV}"
def files = findFiles(glob: "${buildPath}/*.bin")
if (files.length == 1) {
archiveArtifacts artifacts: files[0].path, fingerprint: true
} else {
error "❌ Konnte keine eindeutige Firmware-Datei finden!"
}
}
} }
} }

3
Software/.gitignore vendored
View File

@ -3,3 +3,6 @@ data/
.vscode .vscode
wifi_credentials.ini wifi_credentials.ini
__pycache__ __pycache__
# Node-Tools für Build-Scripts
/tools_node/
/data_stripped/

View File

@ -11,23 +11,35 @@ import platform
Import("env") Import("env")
Import("projenv") Import("projenv")
# Überprüfe die Betriebssystemplattform def ensure_node_tool(package_name, binary_name=None):
if platform.system() == "Windows": """Installiert das Tool lokal, wenn es fehlt mit npm init bei Bedarf"""
# Setze die Pfade zu den Tools für Windows if binary_name is None:
html_minifier_path = os.path.join(os.getenv("APPDATA"), "npm", "html-minifier.cmd") binary_name = package_name
uglifyjs_path = os.path.join(os.getenv("APPDATA"), "npm", "uglifyjs.cmd")
terser_path = os.path.join(os.getenv("APPDATA"), "npm", "terser.cmd")
cssnano_path = os.path.join(os.getenv("APPDATA"), "npm", "cssnano.cmd")
elif platform.system() == "Linux":
# Setze die Namen der Tools für Linux
html_minifier_path = "html-minifier"
uglifyjs_path = "uglifyjs"
terser_path = "terser"
cssnano_path = "cssnano"
else:
# Hier könntest du weitere Bedingungen für andere Betriebssysteme hinzufügen
raise Exception("Unterstütztes Betriebssystem nicht erkannt")
project_dir = env.subst('$PROJECT_DIR')
tools_dir = os.path.join(project_dir, 'tools_node')
local_bin = os.path.join(tools_dir, 'node_modules', '.bin', binary_name)
os.makedirs(tools_dir, exist_ok=True)
# Initialisiere npm, falls noch nicht geschehen
if not os.path.isfile(os.path.join(tools_dir, 'package.json')):
print("🛠️ Initializing local npm project in tools_node...")
subprocess.run(['npm', 'init', '-y'], cwd=tools_dir, check=True)
# Installiere Tool (idempotent)
try:
subprocess.run(['npm', 'install', package_name], cwd=tools_dir, check=True)
except Exception as e:
print(f"❌ Fehler beim Installieren von {package_name}: {e}")
return local_bin if os.path.isfile(local_bin) else binary_name
# Tools sicherstellen
# Tools sicherstellen Package-Name und CLI-Binary ggf. unterschiedlich
html_minifier_path = ensure_node_tool("html-minifier")
terser_path = ensure_node_tool("terser")
cssnano_path = ensure_node_tool("cssnano-cli", "cssnano")
def minify_html(input_path, output_path): def minify_html(input_path, output_path):
subprocess.run([html_minifier_path, '--collapse-whitespace', '--remove-comments', input_path, '-o', output_path]) subprocess.run([html_minifier_path, '--collapse-whitespace', '--remove-comments', input_path, '-o', output_path])
@ -40,13 +52,7 @@ def minify_css(input_path, output_path):
def process_file(src_path, dest_path): def process_file(src_path, dest_path):
_, file_extension = os.path.splitext(src_path) _, file_extension = os.path.splitext(src_path)
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
# Extrahiere den Ordnerpfad im Zielverzeichnis
dest_dir = os.path.dirname(dest_path)
# Erstelle den Ordner und alle dazugehörigen Unterordner, falls sie nicht existieren
os.makedirs(dest_dir, exist_ok=True)
if file_extension.lower() == '.js': if file_extension.lower() == '.js':
minify_js(src_path, dest_path) minify_js(src_path, dest_path)
elif file_extension.lower() == '.css': elif file_extension.lower() == '.css':
@ -54,128 +60,89 @@ def process_file(src_path, dest_path):
elif file_extension.lower() in ['.html', '.htm']: elif file_extension.lower() in ['.html', '.htm']:
minify_html(src_path, dest_path) minify_html(src_path, dest_path)
else: else:
# Kopiere nicht bearbeitbare Dateien direkt in den Zielordner
shutil.copy2(src_path, dest_path) shutil.copy2(src_path, dest_path)
def strip_files(src_dir, dest_dir): def strip_files(src_dir, dest_dir):
# Erstelle den Zielordner und alle dazugehörigen Unterordner, falls sie nicht existieren
os.makedirs(dest_dir, exist_ok=True) os.makedirs(dest_dir, exist_ok=True)
# Durchlaufe alle Dateien und Unterverzeichnisse im Quellordner
for root, _, files in os.walk(src_dir): for root, _, files in os.walk(src_dir):
for filename in files: for filename in files:
src_path = os.path.join(root, filename) src_path = os.path.join(root, filename)
dest_path = os.path.relpath(src_path, src_dir) rel_path = os.path.relpath(src_path, src_dir)
dest_path = os.path.join(dest_dir, dest_path) dest_path = os.path.join(dest_dir, rel_path)
# Verarbeite nur Dateien (keine Unterverzeichnisse)
process_file(src_path, dest_path) process_file(src_path, dest_path)
def gzip_file(src_path, dst_path): def gzip_file(src_path, dst_path):
with open(src_path, 'rb') as src, gzip.open(dst_path, 'wb') as dst: with open(src_path, 'rb') as src, gzip.open(dst_path, 'wb') as dst:
for chunk in iter(lambda: src.read(4096), b""): for chunk in iter(lambda: src.read(4096), b""):
dst.write(chunk) dst.write(chunk)
def getListOfFiles(dirName): def getListOfFiles(dirName):
# create a list of file and sub directories entries = os.listdir(dirName)
# names in the given directory allFiles = []
listOfFile = os.listdir(dirName) for entry in entries:
allFiles = list()
# Iterate over all the entries
for entry in listOfFile:
# Create full path
fullPath = os.path.join(dirName, entry) fullPath = os.path.join(dirName, entry)
# If entry is a directory then get the list of files in this directory
if os.path.isdir(fullPath): if os.path.isdir(fullPath):
allFiles = allFiles + getListOfFiles(fullPath) allFiles += getListOfFiles(fullPath)
else: else:
allFiles.append(fullPath) allFiles.append(fullPath)
return allFiles return allFiles
def remove_prefix(text, prefix): def safe_relpath(path, start):
if text.startswith(prefix): return os.path.relpath(path, start).replace("\\", "/")
return text[len(prefix):]
return text # or whatever
# Compress files from 'data_src/' to 'data/'
def gzip_webfiles(source, target, env): def gzip_webfiles(source, target, env):
# Filetypes to compress
filetypes_to_gzip = ['.css', '.png', '.js', '.ico', '.woff2', '.json'] filetypes_to_gzip = ['.css', '.png', '.js', '.ico', '.woff2', '.json']
print('\nGZIP: Starting gzip-Process for LittleFS-Image...\n') print('\nGZIP: Starting gzip-Process for LittleFS-Image...\n')
data_src_dir_path = os.path.join(env.get('PROJECT_DIR'), 'data_src') src_dir = os.path.join(env.get('PROJECT_DIR'), 'data_src')
data_temp_dir_path = os.path.join(env.get('PROJECT_DIR'), 'data_stripped') temp_dir = os.path.join(env.get('PROJECT_DIR'), 'data_stripped')
strip_files(data_src_dir_path, data_temp_dir_path) dst_dir = env.get('PROJECT_DATA_DIR')
data_dir_path = env.get('PROJECT_DATA_DIR')
# check if data and datasrc exist. If the first exists and not the second, it renames it strip_files(src_dir, temp_dir)
if(os.path.exists(data_dir_path) and not os.path.exists(data_temp_dir_path)):
print('GZIP: Directory "'+data_dir_path + if os.path.exists(dst_dir):
'" exists, "'+data_temp_dir_path+'" is not found.') shutil.rmtree(dst_dir)
print('GZIP: Renaming "' + data_dir_path + os.mkdir(dst_dir)
'" to "' + data_temp_dir_path + '"')
os.rename(data_dir_path, data_temp_dir_path)
# Delete the 'data' directory
if(os.path.exists(data_dir_path)):
print('GZIP: Deleting the "data" directory ' + data_dir_path)
shutil.rmtree(data_dir_path)
# Recreate empty 'data' directory
print('GZIP: Re-creating an empty data directory ' + data_dir_path)
os.mkdir(data_dir_path)
# Determine the files to compress
files_to_copy = [] files_to_copy = []
files_to_gzip = [] files_to_gzip = []
all_data_src = getListOfFiles(data_temp_dir_path) for file in getListOfFiles(temp_dir):
for file in all_data_src: _, ext = os.path.splitext(file)
file_name, file_extension = os.path.splitext(file) if ext in filetypes_to_gzip:
print(file_name + " has filetype " + file_extension)
if file_extension in filetypes_to_gzip:
files_to_gzip.append(file) files_to_gzip.append(file)
else: else:
filename_subdir = remove_prefix(file, data_temp_dir_path) files_to_copy.append(safe_relpath(file, temp_dir))
files_to_copy.append(filename_subdir)
for file in files_to_copy: for file in files_to_copy:
print('GZIP: Copying file from: ' + data_temp_dir_path + file + ' to: ' + data_dir_path + file) full_dst = os.path.join(dst_dir, file)
os.makedirs(os.path.dirname(data_dir_path + file), exist_ok=True) os.makedirs(os.path.dirname(full_dst), exist_ok=True)
shutil.copy(data_temp_dir_path + file, data_dir_path + file) shutil.copy(os.path.join(temp_dir, file), full_dst)
# Compress and move files
was_error = False was_error = False
try: try:
for source_file_path in files_to_gzip: for src in files_to_gzip:
print('GZIP: compressing... ' + source_file_path) rel_path = safe_relpath(src, temp_dir)
filename_subdir = remove_prefix(source_file_path, data_temp_dir_path) dst_path = os.path.join(dst_dir, rel_path + '.gz')
target_file_path = data_dir_path + filename_subdir os.makedirs(os.path.dirname(dst_path), exist_ok=True)
os.makedirs(os.path.dirname(target_file_path), exist_ok=True) print('GZIP: compressing... ' + rel_path)
print('GZIP: Compressed... ' + target_file_path) gzip_file(src, dst_path)
gzip_file(source_file_path, target_file_path + ".gz")
except IOError as e: except IOError as e:
was_error = True was_error = True
print('GZIP: Failed to compress file: ' + source_file_path) print('GZIP: Fehler beim Komprimieren:', e)
# print( 'GZIP: EXCEPTION... {}'.format( e ) )
if was_error:
print('GZIP: Failure/Incomplete.\n')
else:
print('GZIP: Compressed correctly.\n')
shutil.rmtree(data_temp_dir_path)
return if was_error:
print('⚠️ GZIP: Nicht alle Dateien konnten verarbeitet werden.\n')
else:
print('✅ GZIP: Komprimierung abgeschlossen.\n')
shutil.rmtree(temp_dir)
def gzip_binffiles(source, target, env): def gzip_binffiles(source, target, env):
littlefsbin = target[0].get_abspath() littlefsbin = target[0].get_abspath()
targetbin = os.path.join(os.path.dirname(littlefsbin), 'filesystem.fs') tmpbin = os.path.join(os.path.dirname(littlefsbin), 'filesystem.fs')
shutil.copyfile(littlefsbin, targetbin) shutil.copyfile(littlefsbin, tmpbin)
gzip_file(targetbin, os.path.join(str(targetbin) + '.gz')) gzip_file(tmpbin, tmpbin + '.gz')
os.remove(targetbin) os.remove(tmpbin)
return
# IMPORTANT, this needs to be added to call the routine # Hooks setzen
env.AddPreAction('$BUILD_DIR/littlefs.bin', gzip_webfiles) env.AddPreAction('$BUILD_DIR/littlefs.bin', gzip_webfiles)
env.AddPostAction('$BUILD_DIR/littlefs.bin', gzip_binffiles) env.AddPostAction('$BUILD_DIR/littlefs.bin', gzip_binffiles)