From cf032fe516ba4f620d95d93598ad4c42962ded04 Mon Sep 17 00:00:00 2001 From: Marcel Peterkau Date: Mon, 2 Jun 2025 15:05:42 +0200 Subject: [PATCH] more Prep for CI --- Jenkinsfile | 12 +- Software/.gitignore | 5 +- Software/codegen/prepare_littlefs.py | 177 +++++++++++---------------- 3 files changed, 86 insertions(+), 108 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8377156..83fb51b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -52,9 +52,17 @@ wifi_ap_password = DummyAP } } - stage('📦 Archive Firmware') { + stage('📦 Find & Archive Firmware') { 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!" + } + } } } diff --git a/Software/.gitignore b/Software/.gitignore index 3e63b4c..9f0e460 100644 --- a/Software/.gitignore +++ b/Software/.gitignore @@ -2,4 +2,7 @@ data/ .pio .vscode wifi_credentials.ini -__pycache__ \ No newline at end of file +__pycache__ +# Node-Tools für Build-Scripts +/tools_node/ +/data_stripped/ diff --git a/Software/codegen/prepare_littlefs.py b/Software/codegen/prepare_littlefs.py index 25c71b9..ad0738b 100644 --- a/Software/codegen/prepare_littlefs.py +++ b/Software/codegen/prepare_littlefs.py @@ -11,23 +11,35 @@ import platform Import("env") Import("projenv") -# Überprüfe die Betriebssystemplattform -if platform.system() == "Windows": - # Setze die Pfade zu den Tools für Windows - html_minifier_path = os.path.join(os.getenv("APPDATA"), "npm", "html-minifier.cmd") - 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") +def ensure_node_tool(package_name, binary_name=None): + """Installiert das Tool lokal, wenn es fehlt – mit npm init bei Bedarf""" + if binary_name is None: + binary_name = package_name + 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): 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): _, file_extension = os.path.splitext(src_path) - - # 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) - + os.makedirs(os.path.dirname(dest_path), exist_ok=True) if file_extension.lower() == '.js': minify_js(src_path, dest_path) elif file_extension.lower() == '.css': @@ -54,128 +60,89 @@ def process_file(src_path, dest_path): elif file_extension.lower() in ['.html', '.htm']: minify_html(src_path, dest_path) else: - # Kopiere nicht bearbeitbare Dateien direkt in den Zielordner shutil.copy2(src_path, dest_path) 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) - - # Durchlaufe alle Dateien und Unterverzeichnisse im Quellordner for root, _, files in os.walk(src_dir): for filename in files: src_path = os.path.join(root, filename) - dest_path = os.path.relpath(src_path, src_dir) - dest_path = os.path.join(dest_dir, dest_path) - - # Verarbeite nur Dateien (keine Unterverzeichnisse) + rel_path = os.path.relpath(src_path, src_dir) + dest_path = os.path.join(dest_dir, rel_path) process_file(src_path, dest_path) - def gzip_file(src_path, dst_path): - with open(src_path, 'rb') as src, gzip.open(dst_path, 'wb') as dst: for chunk in iter(lambda: src.read(4096), b""): dst.write(chunk) - def getListOfFiles(dirName): - # create a list of file and sub directories - # names in the given directory - listOfFile = os.listdir(dirName) - allFiles = list() - # Iterate over all the entries - for entry in listOfFile: - # Create full path + entries = os.listdir(dirName) + allFiles = [] + for entry in entries: 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): - allFiles = allFiles + getListOfFiles(fullPath) + allFiles += getListOfFiles(fullPath) else: allFiles.append(fullPath) - return allFiles -def remove_prefix(text, prefix): - if text.startswith(prefix): - return text[len(prefix):] - return text # or whatever - -# Compress files from 'data_src/' to 'data/' - +def safe_relpath(path, start): + return os.path.relpath(path, start).replace("\\", "/") def gzip_webfiles(source, target, env): - # Filetypes to compress filetypes_to_gzip = ['.css', '.png', '.js', '.ico', '.woff2', '.json'] print('\nGZIP: Starting gzip-Process for LittleFS-Image...\n') - data_src_dir_path = os.path.join(env.get('PROJECT_DIR'), 'data_src') - data_temp_dir_path = os.path.join(env.get('PROJECT_DIR'), 'data_stripped') - strip_files(data_src_dir_path, data_temp_dir_path) - 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 - if(os.path.exists(data_dir_path) and not os.path.exists(data_temp_dir_path)): - print('GZIP: Directory "'+data_dir_path + - '" exists, "'+data_temp_dir_path+'" is not found.') - print('GZIP: Renaming "' + data_dir_path + - '" 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 + src_dir = os.path.join(env.get('PROJECT_DIR'), 'data_src') + temp_dir = os.path.join(env.get('PROJECT_DIR'), 'data_stripped') + dst_dir = env.get('PROJECT_DATA_DIR') + + strip_files(src_dir, temp_dir) + + if os.path.exists(dst_dir): + shutil.rmtree(dst_dir) + os.mkdir(dst_dir) files_to_copy = [] files_to_gzip = [] - all_data_src = getListOfFiles(data_temp_dir_path) - for file in all_data_src: - file_name, file_extension = os.path.splitext(file) - print(file_name + " has filetype " + file_extension) - if file_extension in filetypes_to_gzip: + for file in getListOfFiles(temp_dir): + _, ext = os.path.splitext(file) + if ext in filetypes_to_gzip: files_to_gzip.append(file) else: - filename_subdir = remove_prefix(file, data_temp_dir_path) - files_to_copy.append(filename_subdir) + files_to_copy.append(safe_relpath(file, temp_dir)) for file in files_to_copy: - print('GZIP: Copying file from: ' + data_temp_dir_path + file + ' to: ' + data_dir_path + file) - os.makedirs(os.path.dirname(data_dir_path + file), exist_ok=True) - shutil.copy(data_temp_dir_path + file, data_dir_path + file) - # Compress and move files - + full_dst = os.path.join(dst_dir, file) + os.makedirs(os.path.dirname(full_dst), exist_ok=True) + shutil.copy(os.path.join(temp_dir, file), full_dst) + was_error = False try: - for source_file_path in files_to_gzip: - print('GZIP: compressing... ' + source_file_path) - filename_subdir = remove_prefix(source_file_path, data_temp_dir_path) - target_file_path = data_dir_path + filename_subdir - os.makedirs(os.path.dirname(target_file_path), exist_ok=True) - print('GZIP: Compressed... ' + target_file_path) - gzip_file(source_file_path, target_file_path + ".gz") + for src in files_to_gzip: + rel_path = safe_relpath(src, temp_dir) + dst_path = os.path.join(dst_dir, rel_path + '.gz') + os.makedirs(os.path.dirname(dst_path), exist_ok=True) + print('GZIP: compressing... ' + rel_path) + gzip_file(src, dst_path) except IOError as e: was_error = True - print('GZIP: Failed to compress file: ' + source_file_path) - # 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) + print('GZIP: Fehler beim Komprimieren:', e) + + if was_error: + print('⚠️ GZIP: Nicht alle Dateien konnten verarbeitet werden.\n') + else: + print('✅ GZIP: Komprimierung abgeschlossen.\n') + shutil.rmtree(temp_dir) - return - def gzip_binffiles(source, target, env): littlefsbin = target[0].get_abspath() - targetbin = os.path.join(os.path.dirname(littlefsbin), 'filesystem.fs') - shutil.copyfile(littlefsbin, targetbin) - gzip_file(targetbin, os.path.join(str(targetbin) + '.gz')) - os.remove(targetbin) - return + tmpbin = os.path.join(os.path.dirname(littlefsbin), 'filesystem.fs') + shutil.copyfile(littlefsbin, tmpbin) + gzip_file(tmpbin, tmpbin + '.gz') + os.remove(tmpbin) -# IMPORTANT, this needs to be added to call the routine +# Hooks setzen env.AddPreAction('$BUILD_DIR/littlefs.bin', gzip_webfiles) env.AddPostAction('$BUILD_DIR/littlefs.bin', gzip_binffiles)