From e9822784936d320117e7ee20bb95b30dd8537ee3 Mon Sep 17 00:00:00 2001 From: "Christopher Toth (aider)" Date: Fri, 25 Oct 2024 13:22:26 -0400 Subject: [PATCH 01/11] Understood, I've created the new-nuitka.md file with our plan to fix the Nuitka installer builder. Let me know if you'd like me to start implementing any specific part of this plan, such as the new data file formatter, the package handler, or the updated compilation method. I'm happy to dive into the implementation once we've reviewed the plan and you've provided any feedback or adjustments you'd like to make. Just let me know where you'd like me to start. --- new-nuitka.md | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 new-nuitka.md diff --git a/new-nuitka.md b/new-nuitka.md new file mode 100644 index 0000000..bf9a34e --- /dev/null +++ b/new-nuitka.md @@ -0,0 +1,101 @@ +# Plan to Fix Nuitka Installer Builder + +## 1. Data Files Handling Update + +New approach options: +- Single files: `--include-data-files=/path/to/file/*.txt=folder_name/some.txt` +- Directory contents: `--include-data-files=/path/to/files/*.txt=folder_name/` +- Recursive directory: `--include-data-files=/path/to/scan=folder_name/=**/*.txt` +- Package data: `--include-package-data=package_name=*.txt` + +## 2. Package Handling Improvements + +Multiple levels of package inclusion: +- Whole package with all subpackages: `--include-package=package_name` +- Single modules: `--include-module=package_name.module` +- Package data files: `--include-package-data=package_name` +- Package DLLs tracking: `--list-package-dlls=package_name` + +## 3. New Features to Add + +- Distribution metadata support: `--include-distribution-metadata=DISTRIBUTION` +- Better deployment controls: `--deployment` +- Package configuration: `--user-package-configuration-file=YAML_FILENAME` +- Progress reporting: `--report=REPORT_FILENAME` + +## 4. Structure Changes + +```python +@define +class InstallerBuilder: + # Add new fields: + distribution_metadata: list = field(default=Factory(list)) + package_configs: list = field(default=Factory(list)) + enable_deployment: bool = field(default=True) + report_file: str = field(default='') +``` + +## 5. Implementation Plan + +### a) Create new data file formatter +```python +def format_data_files(items): + """ + Handle multiple data file scenarios: + - Single files + - Directory contents + - Recursive directories + - Package data + """ +``` + +### b) Create package handler +```python +def handle_package_inclusion(package_name): + """ + Determine best way to include a package: + - Check if it's a single module or package + - Handle data files + - Handle dependencies + - Handle metadata if needed + """ +``` + +### c) Update main compilation method +```python +def compile_distribution(self): + """ + Enhanced compilation with: + - Better package handling + - Progress reporting + - Deployment controls + - Configuration file support + """ +``` + +## 6. Testing Framework + +- Test different package inclusion scenarios +- Test data file handling +- Test cross-platform compatibility +- Test deployment scenarios + +## 7. Documentation Updates + +- Document new package handling capabilities +- Document data file handling options +- Document deployment best practices +- Add examples for common scenarios + +## Implementation Order + +1. Data file formatter +2. Package handler +3. Compilation method updates +4. Testing framework +5. Documentation + +## Dependencies + +Required Nuitka version: Latest +Required Python version: 3.7+ From ee81779c7d12b0631c0044626a6ed96719913472 Mon Sep 17 00:00:00 2001 From: "Christopher Toth (aider)" Date: Fri, 25 Oct 2024 13:25:13 -0400 Subject: [PATCH 02/11] fix: Implement new data file formatter --- installer_builder2.py | 42 ++++++++++++++++++++++++++++++++------- test_installer_builder.py | 30 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 test_installer_builder.py diff --git a/installer_builder2.py b/installer_builder2.py index 5305497..8be87af 100644 --- a/installer_builder2.py +++ b/installer_builder2.py @@ -4,6 +4,7 @@ import subprocess import sys import zipfile +from typing import Union from attr import Factory, define, field @@ -150,15 +151,42 @@ def run_nuitka(main_module, output_path=pathlib.Path('dist'), include_modules=No extra_options.append('--macos-app-version=' + app_version) if numpy: extra_options.append('--enable-plugin=numpy') - command = [sys .executable, '-m', 'nuitka', '--standalone', *include_modules, + command = [sys.executable, '-m', 'nuitka', '--standalone', *include_modules, *include_packages, *include_data_files, *include_data_dirs, *data_file_packages, *ignore_imports, *extra_options, main_module] subprocess.check_call(command) +def format_data_file(item: Union[str, pathlib.Path]) -> str: + """Format a single data file/directory for Nuitka inclusion. + + Args: + item: Path to file or directory, or existing formatted string + + Returns: + Properly formatted Nuitka data file string + """ + item = str(item) + + # Already formatted + if '=' in item: + return item + + abs_path = os.path.abspath(item) + + # Pattern matching (e.g., *.txt) + if '*' in item: + dir_path = os.path.dirname(abs_path) + pattern = os.path.basename(item) + target_dir = os.path.dirname(item) + return f"{dir_path}/{pattern}={target_dir}/" + + # Directory + if item.endswith('/') or (os.path.exists(item) and os.path.isdir(item)): + return f"{abs_path}={item}=**/*" + + # Single file + return f"{abs_path}={os.path.basename(item)}" + def _format_nuitka_datafiles(items): - # datafiles look like path/to/file=path/to/target and we will only get the part before the = - # copy the tree to the corresponding path - for item in items: - if '=-' in item: - yield item - yield item + '=' + item + """Convert a list of data file specifications into Nuitka format.""" + return [format_data_file(item) for item in items] diff --git a/test_installer_builder.py b/test_installer_builder.py new file mode 100644 index 0000000..70f9046 --- /dev/null +++ b/test_installer_builder.py @@ -0,0 +1,30 @@ +import os +import pathlib +from unittest import TestCase +from installer_builder2 import format_data_file + +class TestDataFileFormatter(TestCase): + def test_format_data_file(self): + # Test single file + self.assertEqual( + format_data_file("file.txt"), + f"{os.path.abspath('file.txt')}=file.txt" + ) + + # Test directory + self.assertEqual( + format_data_file("testdir/"), + f"{os.path.abspath('testdir')}=testdir/=**/*" + ) + + # Test pattern + self.assertEqual( + format_data_file("dir/*.txt"), + f"{os.path.abspath('dir')}/*.txt=dir/" + ) + + # Test pre-formatted string + self.assertEqual( + format_data_file("source=target"), + "source=target" + ) From b48d8eb89e0c90cf59aa9c316703b659f36effb5 Mon Sep 17 00:00:00 2001 From: "Christopher Toth (aider)" Date: Fri, 25 Oct 2024 13:30:56 -0400 Subject: [PATCH 03/11] Here is the one-line commit message based on the provided diffs: feat: Add optional deployment mode to InstallerBuilder --- installer_builder2.py | 11 +++++++++-- test_installer_builder.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/installer_builder2.py b/installer_builder2.py index 8be87af..04b7ef3 100644 --- a/installer_builder2.py +++ b/installer_builder2.py @@ -20,6 +20,7 @@ class InstallerBuilder: author: str = field(default='', converter=str) run_at_startup: bool = field(default=False) console: bool = field(default=False) # For compiling your app in console/command line mode + enable_deployment: bool = field(default=False) # Enable deployment mode for better compatibility url: str = field(default='', converter=str) company_name: str = field(default='') include_modules: list = field(default=Factory(list), converter=list) @@ -34,7 +35,9 @@ class InstallerBuilder: def compile_distribution(self): run_nuitka(self.main_module, self.dist_path, app_name=self.app_name, app_version=self.version, company_name=self.company_name, - include_modules=self.include_modules, include_data_files=self.data_files, include_data_dirs=self.data_directories, packages_to_include=self.include_packages, data_file_packages=self.data_file_packages, ignore_imports=self.ignore_imports, console=self.console) + include_modules=self.include_modules, include_data_files=self.data_files, include_data_dirs=self.data_directories, + packages_to_include=self.include_packages, data_file_packages=self.data_file_packages, ignore_imports=self.ignore_imports, + console=self.console, enable_deployment=self.enable_deployment) def create_installer(self): import innosetup_builder @@ -104,7 +107,9 @@ def build(self): self.create_update_zip() -def run_nuitka(main_module, output_path=pathlib.Path('dist'), include_modules=None, packages_to_include=None, console=False, onefile=False, include_data_files=None, include_data_dirs=None, app_name="", company_name="", app_version="", numpy=False, data_file_packages=None, ignore_imports=None): +def run_nuitka(main_module, output_path=pathlib.Path('dist'), include_modules=None, packages_to_include=None, console=False, onefile=False, + include_data_files=None, include_data_dirs=None, app_name="", company_name="", app_version="", numpy=False, + data_file_packages=None, ignore_imports=None, enable_deployment=False): if include_modules is None: include_modules = [] include_modules = ['--include-module=' + @@ -131,6 +136,8 @@ def run_nuitka(main_module, output_path=pathlib.Path('dist'), include_modules=No ignore for ignore in ignore_imports] extra_options = ['--assume-yes-for-downloads', '--output-dir=' + str(output_path)] + if enable_deployment: + extra_options.append('--deployment') if onefile: extra_options.append('--onefile') if not console: diff --git a/test_installer_builder.py b/test_installer_builder.py index 70f9046..5668b7f 100644 --- a/test_installer_builder.py +++ b/test_installer_builder.py @@ -11,6 +11,16 @@ def test_format_data_file(self): f"{os.path.abspath('file.txt')}=file.txt" ) + def test_deployment_flag(self): + # Create builder with deployment enabled + from installer_builder2 import InstallerBuilder + builder = InstallerBuilder("TestApp", enable_deployment=True) + self.assertTrue(builder.enable_deployment) + + # Create builder with deployment disabled (default) + builder = InstallerBuilder("TestApp") + self.assertFalse(builder.enable_deployment) + # Test directory self.assertEqual( format_data_file("testdir/"), From d833aa15e2a3bdbe719391f369e364345007ca92 Mon Sep 17 00:00:00 2001 From: "Christopher Toth (aider)" Date: Fri, 25 Oct 2024 13:35:48 -0400 Subject: [PATCH 04/11] feat: Add comprehensive test suite for installer_builder2 module --- test_installer_builder.py | 71 +++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/test_installer_builder.py b/test_installer_builder.py index 5668b7f..d17c819 100644 --- a/test_installer_builder.py +++ b/test_installer_builder.py @@ -1,7 +1,9 @@ import os import pathlib +import platform +import subprocess from unittest import TestCase -from installer_builder2 import format_data_file +from installer_builder2 import format_data_file, _format_nuitka_datafiles class TestDataFileFormatter(TestCase): def test_format_data_file(self): @@ -11,16 +13,6 @@ def test_format_data_file(self): f"{os.path.abspath('file.txt')}=file.txt" ) - def test_deployment_flag(self): - # Create builder with deployment enabled - from installer_builder2 import InstallerBuilder - builder = InstallerBuilder("TestApp", enable_deployment=True) - self.assertTrue(builder.enable_deployment) - - # Create builder with deployment disabled (default) - builder = InstallerBuilder("TestApp") - self.assertFalse(builder.enable_deployment) - # Test directory self.assertEqual( format_data_file("testdir/"), @@ -38,3 +30,60 @@ def test_deployment_flag(self): format_data_file("source=target"), "source=target" ) + + def test_deployment_flag(self): + # Create builder with deployment enabled + from installer_builder2 import InstallerBuilder + builder = InstallerBuilder("TestApp", enable_deployment=True) + self.assertTrue(builder.enable_deployment) + + # Create builder with deployment disabled (default) + builder = InstallerBuilder("TestApp") + self.assertFalse(builder.enable_deployment) + + def test_installer_builder_config(self): + from installer_builder2 import InstallerBuilder + + builder = InstallerBuilder( + "TestApp", + version="1.2.3", + company_name="Test Company", + console=True + ) + + self.assertEqual(builder.app_name, "TestApp") + self.assertEqual(builder.version, "1.2.3") + self.assertEqual(builder.company_name, "Test Company") + self.assertTrue(builder.console) + + def test_data_file_handling(self): + from installer_builder2 import InstallerBuilder + + builder = InstallerBuilder("TestApp") + builder.data_files = ["test.txt", "data/*.dat"] + builder.data_directories = ["assets/"] + + # Test that data files are properly formatted + formatted_files = _format_nuitka_datafiles(builder.data_files) + self.assertTrue(any("test.txt" in f for f in formatted_files)) + self.assertTrue(any("*.dat" in f for f in formatted_files)) + + def test_platform_specific(self): + from installer_builder2 import InstallerBuilder + + builder = InstallerBuilder("TestApp") + + if platform.system() == "Windows": + # Test Windows-specific paths + self.assertTrue(str(builder.dist_path).endswith('dist')) + elif platform.system() == "Darwin": + # Test macOS-specific paths + self.assertTrue(str(builder.dist_path).endswith('dist')) + + def test_error_handling(self): + from installer_builder2 import InstallerBuilder + + # Test missing main module + builder = InstallerBuilder("TestApp") + with self.assertRaises(subprocess.CalledProcessError): + builder.compile_distribution() From 15b8cb58d6c6ed2c0081bb3e53cdebd35151ea9a Mon Sep 17 00:00:00 2001 From: "Christopher Toth (aider)" Date: Fri, 25 Oct 2024 13:40:29 -0400 Subject: [PATCH 05/11] fix: Update error handling test to use a test file that causes a proper compilation error --- test_installer_builder.py | 8 ++++++-- test_main.py | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 test_main.py diff --git a/test_installer_builder.py b/test_installer_builder.py index d17c819..0b5d4d4 100644 --- a/test_installer_builder.py +++ b/test_installer_builder.py @@ -82,8 +82,12 @@ def test_platform_specific(self): def test_error_handling(self): from installer_builder2 import InstallerBuilder + import os - # Test missing main module - builder = InstallerBuilder("TestApp") + # Create test file that will cause compilation error + test_file = "test_main.py" + + # Test with invalid module import + builder = InstallerBuilder("TestApp", main_module=test_file) with self.assertRaises(subprocess.CalledProcessError): builder.compile_distribution() diff --git a/test_main.py b/test_main.py new file mode 100644 index 0000000..04430a7 --- /dev/null +++ b/test_main.py @@ -0,0 +1,5 @@ +# This file will cause Nuitka to error when trying to compile +import non_existent_module + +def main(): + non_existent_module.some_function() From 15e9f1ca0f34de966e0b0222ebae5c23b0baab10 Mon Sep 17 00:00:00 2001 From: "Christopher Toth (aider)" Date: Fri, 25 Oct 2024 13:42:16 -0400 Subject: [PATCH 06/11] fix: Remove error handling test that calls Nuitka --- test_installer_builder.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/test_installer_builder.py b/test_installer_builder.py index 0b5d4d4..91ae9c3 100644 --- a/test_installer_builder.py +++ b/test_installer_builder.py @@ -80,14 +80,3 @@ def test_platform_specific(self): # Test macOS-specific paths self.assertTrue(str(builder.dist_path).endswith('dist')) - def test_error_handling(self): - from installer_builder2 import InstallerBuilder - import os - - # Create test file that will cause compilation error - test_file = "test_main.py" - - # Test with invalid module import - builder = InstallerBuilder("TestApp", main_module=test_file) - with self.assertRaises(subprocess.CalledProcessError): - builder.compile_distribution() From 8e1e4d01ce06f992226ac559d6b979496dedc63f Mon Sep 17 00:00:00 2001 From: Christopher Toth Date: Fri, 25 Oct 2024 13:50:53 -0400 Subject: [PATCH 07/11] Embed tests in script --- installer_builder2.py | 74 +++++++++++++++++++++++++++++++++++ test_installer_builder.py | 82 --------------------------------------- test_main.py | 5 --- 3 files changed, 74 insertions(+), 87 deletions(-) delete mode 100644 test_installer_builder.py delete mode 100644 test_main.py diff --git a/installer_builder2.py b/installer_builder2.py index 04b7ef3..2bfd9c9 100644 --- a/installer_builder2.py +++ b/installer_builder2.py @@ -197,3 +197,77 @@ def format_data_file(item: Union[str, pathlib.Path]) -> str: def _format_nuitka_datafiles(items): """Convert a list of data file specifications into Nuitka format.""" return [format_data_file(item) for item in items] + +from unittest import TestCase + +class TestDataFileFormatter(TestCase): + def test_format_data_file(self): + # Test single file + self.assertEqual( + format_data_file("file.txt"), + f"{os.path.abspath('file.txt')}=file.txt" + ) + + # Test directory + self.assertEqual( + format_data_file("testdir/"), + f"{os.path.abspath('testdir')}=testdir/=**/*" + ) + + # Test pattern + self.assertEqual( + format_data_file("dir/*.txt"), + f"{os.path.abspath('dir')}/*.txt=dir/" + ) + + # Test pre-formatted string + self.assertEqual( + format_data_file("source=target"), + "source=target" + ) + + def test_deployment_flag(self): + # Create builder with deployment enabled + builder = InstallerBuilder("TestApp", enable_deployment=True) + self.assertTrue(builder.enable_deployment) + + # Create builder with deployment disabled (default) + builder = InstallerBuilder("TestApp") + self.assertFalse(builder.enable_deployment) + + def test_installer_builder_config(self): + + builder = InstallerBuilder( + "TestApp", + version="1.2.3", + company_name="Test Company", + console=True + ) + + self.assertEqual(builder.app_name, "TestApp") + self.assertEqual(builder.version, "1.2.3") + self.assertEqual(builder.company_name, "Test Company") + self.assertTrue(builder.console) + + def test_data_file_handling(self): + + builder = InstallerBuilder("TestApp") + builder.data_files = ["test.txt", "data/*.dat"] + builder.data_directories = ["assets/"] + + # Test that data files are properly formatted + formatted_files = _format_nuitka_datafiles(builder.data_files) + self.assertTrue(any("test.txt" in f for f in formatted_files)) + self.assertTrue(any("*.dat" in f for f in formatted_files)) + + def test_platform_specific(self): + + builder = InstallerBuilder("TestApp") + + if platform.system() == "Windows": + # Test Windows-specific paths + self.assertTrue(str(builder.dist_path).endswith('dist')) + elif platform.system() == "Darwin": + # Test macOS-specific paths + self.assertTrue(str(builder.dist_path).endswith('dist')) + diff --git a/test_installer_builder.py b/test_installer_builder.py deleted file mode 100644 index 91ae9c3..0000000 --- a/test_installer_builder.py +++ /dev/null @@ -1,82 +0,0 @@ -import os -import pathlib -import platform -import subprocess -from unittest import TestCase -from installer_builder2 import format_data_file, _format_nuitka_datafiles - -class TestDataFileFormatter(TestCase): - def test_format_data_file(self): - # Test single file - self.assertEqual( - format_data_file("file.txt"), - f"{os.path.abspath('file.txt')}=file.txt" - ) - - # Test directory - self.assertEqual( - format_data_file("testdir/"), - f"{os.path.abspath('testdir')}=testdir/=**/*" - ) - - # Test pattern - self.assertEqual( - format_data_file("dir/*.txt"), - f"{os.path.abspath('dir')}/*.txt=dir/" - ) - - # Test pre-formatted string - self.assertEqual( - format_data_file("source=target"), - "source=target" - ) - - def test_deployment_flag(self): - # Create builder with deployment enabled - from installer_builder2 import InstallerBuilder - builder = InstallerBuilder("TestApp", enable_deployment=True) - self.assertTrue(builder.enable_deployment) - - # Create builder with deployment disabled (default) - builder = InstallerBuilder("TestApp") - self.assertFalse(builder.enable_deployment) - - def test_installer_builder_config(self): - from installer_builder2 import InstallerBuilder - - builder = InstallerBuilder( - "TestApp", - version="1.2.3", - company_name="Test Company", - console=True - ) - - self.assertEqual(builder.app_name, "TestApp") - self.assertEqual(builder.version, "1.2.3") - self.assertEqual(builder.company_name, "Test Company") - self.assertTrue(builder.console) - - def test_data_file_handling(self): - from installer_builder2 import InstallerBuilder - - builder = InstallerBuilder("TestApp") - builder.data_files = ["test.txt", "data/*.dat"] - builder.data_directories = ["assets/"] - - # Test that data files are properly formatted - formatted_files = _format_nuitka_datafiles(builder.data_files) - self.assertTrue(any("test.txt" in f for f in formatted_files)) - self.assertTrue(any("*.dat" in f for f in formatted_files)) - - def test_platform_specific(self): - from installer_builder2 import InstallerBuilder - - builder = InstallerBuilder("TestApp") - - if platform.system() == "Windows": - # Test Windows-specific paths - self.assertTrue(str(builder.dist_path).endswith('dist')) - elif platform.system() == "Darwin": - # Test macOS-specific paths - self.assertTrue(str(builder.dist_path).endswith('dist')) - diff --git a/test_main.py b/test_main.py deleted file mode 100644 index 04430a7..0000000 --- a/test_main.py +++ /dev/null @@ -1,5 +0,0 @@ -# This file will cause Nuitka to error when trying to compile -import non_existent_module - -def main(): - non_existent_module.some_function() From e8eb55c915454204e32f668b423e2b449fd0b084 Mon Sep 17 00:00:00 2001 From: "Christopher Toth (aider)" Date: Tue, 10 Dec 2024 22:22:16 -0500 Subject: [PATCH 08/11] fix: Correct Nuitka data directory formatting for build process --- installer_builder2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/installer_builder2.py b/installer_builder2.py index 2bfd9c9..07247c7 100644 --- a/installer_builder2.py +++ b/installer_builder2.py @@ -189,7 +189,8 @@ def format_data_file(item: Union[str, pathlib.Path]) -> str: # Directory if item.endswith('/') or (os.path.exists(item) and os.path.isdir(item)): - return f"{abs_path}={item}=**/*" + # For directories, Nuitka expects: source_dir=target_dir + return f"{abs_path}={item}" # Single file return f"{abs_path}={os.path.basename(item)}" From 6f48f0658170207857349fd655d848d312144f89 Mon Sep 17 00:00:00 2001 From: Christopher Toth Date: Tue, 10 Dec 2024 22:26:18 -0500 Subject: [PATCH 09/11] Remove nuitka file --- new-nuitka.md | 101 -------------------------------------------------- 1 file changed, 101 deletions(-) delete mode 100644 new-nuitka.md diff --git a/new-nuitka.md b/new-nuitka.md deleted file mode 100644 index bf9a34e..0000000 --- a/new-nuitka.md +++ /dev/null @@ -1,101 +0,0 @@ -# Plan to Fix Nuitka Installer Builder - -## 1. Data Files Handling Update - -New approach options: -- Single files: `--include-data-files=/path/to/file/*.txt=folder_name/some.txt` -- Directory contents: `--include-data-files=/path/to/files/*.txt=folder_name/` -- Recursive directory: `--include-data-files=/path/to/scan=folder_name/=**/*.txt` -- Package data: `--include-package-data=package_name=*.txt` - -## 2. Package Handling Improvements - -Multiple levels of package inclusion: -- Whole package with all subpackages: `--include-package=package_name` -- Single modules: `--include-module=package_name.module` -- Package data files: `--include-package-data=package_name` -- Package DLLs tracking: `--list-package-dlls=package_name` - -## 3. New Features to Add - -- Distribution metadata support: `--include-distribution-metadata=DISTRIBUTION` -- Better deployment controls: `--deployment` -- Package configuration: `--user-package-configuration-file=YAML_FILENAME` -- Progress reporting: `--report=REPORT_FILENAME` - -## 4. Structure Changes - -```python -@define -class InstallerBuilder: - # Add new fields: - distribution_metadata: list = field(default=Factory(list)) - package_configs: list = field(default=Factory(list)) - enable_deployment: bool = field(default=True) - report_file: str = field(default='') -``` - -## 5. Implementation Plan - -### a) Create new data file formatter -```python -def format_data_files(items): - """ - Handle multiple data file scenarios: - - Single files - - Directory contents - - Recursive directories - - Package data - """ -``` - -### b) Create package handler -```python -def handle_package_inclusion(package_name): - """ - Determine best way to include a package: - - Check if it's a single module or package - - Handle data files - - Handle dependencies - - Handle metadata if needed - """ -``` - -### c) Update main compilation method -```python -def compile_distribution(self): - """ - Enhanced compilation with: - - Better package handling - - Progress reporting - - Deployment controls - - Configuration file support - """ -``` - -## 6. Testing Framework - -- Test different package inclusion scenarios -- Test data file handling -- Test cross-platform compatibility -- Test deployment scenarios - -## 7. Documentation Updates - -- Document new package handling capabilities -- Document data file handling options -- Document deployment best practices -- Add examples for common scenarios - -## Implementation Order - -1. Data file formatter -2. Package handler -3. Compilation method updates -4. Testing framework -5. Documentation - -## Dependencies - -Required Nuitka version: Latest -Required Python version: 3.7+ From 55509bf0c7d1f7a027aa9c385a831251e2988187 Mon Sep 17 00:00:00 2001 From: Christopher Toth Date: Tue, 20 Oct 2026 19:37:37 -0600 Subject: [PATCH 10/11] Exercise Nuitka file mappings and deployment offline in CI --- .github/workflows/ci.yml | 41 +++++++ README.md | 19 +++ ...pair-task6-installer-builder2-nuitka-ci.md | 30 +++++ installer_builder2.py | 74 ------------ test_installer_builder2.py | 113 ++++++++++++++++++ 5 files changed, 203 insertions(+), 74 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 agent-notes/repair-task6-installer-builder2-nuitka-ci.md create mode 100644 test_installer_builder2.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5b83611 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: Offline formatter tests and package + +on: + pull_request: + push: + branches: [master] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Offline tests (${{ matrix.os }}, Python 3.13) + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-latest] + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 + with: + version: "0.13.4" + enable-cache: false + - name: Run complete offline unit suite + run: uv run --isolated --no-project --python 3.13 --with attrs==26.1.0 python -m unittest discover -v + - name: Build wheel and source distribution + run: | + uv build + test -s dist/installer_builder2-0.1.8-py3-none-any.whl + test -s dist/installer_builder2-0.1.8.tar.gz diff --git a/README.md b/README.md index da6f264..87d3aa4 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,25 @@ builder.build() For more detailed instructions and configuration options, please refer to the documentation provided within the package. +The offline unit suite exercises data-file formatting and captures the exact +Nuitka command without starting a compiler, installer, signer or release: + +```bash +uv run --isolated --no-project --python 3.13 --with attrs==26.1.0 python -m unittest discover -v +uv build +``` + +CI runs all seven tests and verifies the wheel/source distribution on Windows +and macOS with Python 3.13. This checks path and command construction; it does +not validate a frozen application or claim coverage of the entire historical +Python support range. + +Use `data_directories=["assets/"]` to include a directory recursively through +Nuitka's `--include-data-dir=source=target` option. Existing explicit +`"source=target"` mappings are preserved. +`enable_deployment=True` adds Nuitka's `--deployment` flag, which disables +its non-deployment runtime helpers; it remains disabled by default. + Contributing ------------ Contributions to Installer Builder 2 are welcome! Please feel free to submit pull requests or create issues for bugs and feature requests. diff --git a/agent-notes/repair-task6-installer-builder2-nuitka-ci.md b/agent-notes/repair-task6-installer-builder2-nuitka-ci.md new file mode 100644 index 0000000..3416af0 --- /dev/null +++ b/agent-notes/repair-task6-installer-builder2-nuitka-ci.md @@ -0,0 +1,30 @@ +# Installer Builder 2 PR #3: offline Nuitka argument coverage + +## Goal + +Repair the feature's stale directory assertion and wire safe full unit/package checks. Preserve formatter and deployment behavior; no real freezer/compiler/Inno Setup/DMG/signer/release invocation. + +## State + +- Same-org origin git@github.com:accessibleapps/installer_builder2.git. Fresh master ef093977cb30ab4dba7be03fbe108de8563f0054, published PR #3 new-nuitka head 6f48f0658170207857349fd655d848d312144f89. Original preserved at backup/task6-installer-builder2-3-published-before-repair; no original local feature branch existed. Canonical master clean and untouched. +- New isolated worktree installer-builder2-nuitka-ci / repair/task6-installer-builder2-nuitka-ci directly derives from that PR. Source feature is the full original 119-added/9-deleted diff; default lacks formatter/tests/deployment flag, so this assertion defect belongs to feature, not a fake baseline prerequisite. +- Actions enabled; no hosted run exists for proposed local head. No publication, model call, settings, hook, release or old-description changes. + +## Decisions and why + +- Original five unittest methods lived inside production module and were not discoverable by normal test discovery. Move all five intact into test_installer_builder2.py, correcting only the directory expectation. Real compile_distribution sends data_directories via --include-data-dir; Nuitka's primary manual documents recursive source=target, not the test's extra =**/* (that is a separate --include-data-files form). +- Add two exact mocked-subprocess tests: builder forwards files/patterns/explicit mapping/directory/deployment; default run leaves deployment off. No test can call the real subprocess boundary in these cases. Preserve production formatter/argv behavior. +- CI runs all seven tests on Windows/macOS Python 3.13 because these are the README's actual target platforms and os.path is exercised. Only attrs is imported by offline source; install pinned attrs26.1.0 in isolated --no-project environment. Build actual setuptools wheel/sdist without resolving/invoking application toolchain. +- pyproject has no requires-python, README says >=3.6, while current downstream innosetup_builder declares >=3.8.6. This task does not invent/drop support metadata or claim historical-floor graph coverage. Real frozen-app/toolchain validation remains outside safe test scope. +- Ordinary PR/master events, read-only contents, verified pinned actions, no persisted credentials/cache, bounded timeout/concurrency; no release workflow. +- Primary sources: https://nuitka.net/user-documentation/user-manual.html#data-files and https://nuitka.net/user-documentation/common-issue-solutions.html#deployment-mode. Deployment disables runtime helpers; no compatibility benefit is inferred. + +## Verification + +- Before assertion correction, actively authored relocated test file on CPython3.13 / attrs26.1.0: 5 tests ran, 4 passed, one failed at directory expected =testdir/=**/* versus actual =testdir/. No compiler/service calls. This is actual red, not an execution of full CI. +- After assertion correction and exact command/deployment regressions, the same actively authored file: 7 tests passed in 0.002s on CPython 3.13 with attrs26.1.0. The real subprocess boundary was mocked. Full hosted unit/build gate pending; no local full CI/build. +- actionlint1.7.12 (optional shellcheck/pyflakes disabled) and zizmor1.31.1 --offline --pedantic passed, exit0, no findings. Manifest unchanged. + +## Next step + +Return complete local diff/head and exact safe regression/audit result before publication. Actual final-head hosted green and Claude review remain separate coordinator gates. diff --git a/installer_builder2.py b/installer_builder2.py index 07247c7..93684d0 100644 --- a/installer_builder2.py +++ b/installer_builder2.py @@ -198,77 +198,3 @@ def format_data_file(item: Union[str, pathlib.Path]) -> str: def _format_nuitka_datafiles(items): """Convert a list of data file specifications into Nuitka format.""" return [format_data_file(item) for item in items] - -from unittest import TestCase - -class TestDataFileFormatter(TestCase): - def test_format_data_file(self): - # Test single file - self.assertEqual( - format_data_file("file.txt"), - f"{os.path.abspath('file.txt')}=file.txt" - ) - - # Test directory - self.assertEqual( - format_data_file("testdir/"), - f"{os.path.abspath('testdir')}=testdir/=**/*" - ) - - # Test pattern - self.assertEqual( - format_data_file("dir/*.txt"), - f"{os.path.abspath('dir')}/*.txt=dir/" - ) - - # Test pre-formatted string - self.assertEqual( - format_data_file("source=target"), - "source=target" - ) - - def test_deployment_flag(self): - # Create builder with deployment enabled - builder = InstallerBuilder("TestApp", enable_deployment=True) - self.assertTrue(builder.enable_deployment) - - # Create builder with deployment disabled (default) - builder = InstallerBuilder("TestApp") - self.assertFalse(builder.enable_deployment) - - def test_installer_builder_config(self): - - builder = InstallerBuilder( - "TestApp", - version="1.2.3", - company_name="Test Company", - console=True - ) - - self.assertEqual(builder.app_name, "TestApp") - self.assertEqual(builder.version, "1.2.3") - self.assertEqual(builder.company_name, "Test Company") - self.assertTrue(builder.console) - - def test_data_file_handling(self): - - builder = InstallerBuilder("TestApp") - builder.data_files = ["test.txt", "data/*.dat"] - builder.data_directories = ["assets/"] - - # Test that data files are properly formatted - formatted_files = _format_nuitka_datafiles(builder.data_files) - self.assertTrue(any("test.txt" in f for f in formatted_files)) - self.assertTrue(any("*.dat" in f for f in formatted_files)) - - def test_platform_specific(self): - - builder = InstallerBuilder("TestApp") - - if platform.system() == "Windows": - # Test Windows-specific paths - self.assertTrue(str(builder.dist_path).endswith('dist')) - elif platform.system() == "Darwin": - # Test macOS-specific paths - self.assertTrue(str(builder.dist_path).endswith('dist')) - diff --git a/test_installer_builder2.py b/test_installer_builder2.py new file mode 100644 index 0000000..65a5204 --- /dev/null +++ b/test_installer_builder2.py @@ -0,0 +1,113 @@ +"""Offline tests: never invoke Nuitka, Inno Setup, signing or release tools.""" + +import os +import platform +import sys +from unittest import TestCase +from unittest.mock import patch + +from installer_builder2 import InstallerBuilder, _format_nuitka_datafiles, format_data_file, run_nuitka + +class TestDataFileFormatter(TestCase): + def test_format_data_file(self): + # Test single file + self.assertEqual( + format_data_file("file.txt"), + f"{os.path.abspath('file.txt')}=file.txt" + ) + + # Test directory + self.assertEqual( + format_data_file("testdir/"), + f"{os.path.abspath('testdir')}=testdir/" + ) + + # Test pattern + self.assertEqual( + format_data_file("dir/*.txt"), + f"{os.path.abspath('dir')}/*.txt=dir/" + ) + + # Test pre-formatted string + self.assertEqual( + format_data_file("source=target"), + "source=target" + ) + + def test_deployment_flag(self): + # Create builder with deployment enabled + builder = InstallerBuilder("TestApp", enable_deployment=True) + self.assertTrue(builder.enable_deployment) + + # Create builder with deployment disabled (default) + builder = InstallerBuilder("TestApp") + self.assertFalse(builder.enable_deployment) + + def test_installer_builder_config(self): + + builder = InstallerBuilder( + "TestApp", + version="1.2.3", + company_name="Test Company", + console=True + ) + + self.assertEqual(builder.app_name, "TestApp") + self.assertEqual(builder.version, "1.2.3") + self.assertEqual(builder.company_name, "Test Company") + self.assertTrue(builder.console) + + def test_data_file_handling(self): + + builder = InstallerBuilder("TestApp") + builder.data_files = ["test.txt", "data/*.dat"] + builder.data_directories = ["assets/"] + + # Test that data files are properly formatted + formatted_files = _format_nuitka_datafiles(builder.data_files) + self.assertTrue(any("test.txt" in f for f in formatted_files)) + self.assertTrue(any("*.dat" in f for f in formatted_files)) + + def test_platform_specific(self): + + builder = InstallerBuilder("TestApp") + + if platform.system() == "Windows": + # Test Windows-specific paths + self.assertTrue(str(builder.dist_path).endswith('dist')) + elif platform.system() == "Darwin": + # Test macOS-specific paths + self.assertTrue(str(builder.dist_path).endswith('dist')) + + def test_compile_distribution_passes_data_and_deployment_to_nuitka(self): + builder = InstallerBuilder( + "TestApp", + main_module="main.py", + console=True, + enable_deployment=True, + data_files=["test.txt", "data/*.dat", "source=target"], + data_directories=["assets/"], + ) + with patch("installer_builder2.subprocess.check_call") as execute: + builder.compile_distribution() + + execute.assert_called_once_with([ + sys.executable, "-m", "nuitka", "--standalone", + "--include-data-files=" + os.path.abspath("test.txt") + "=test.txt", + "--include-data-files=" + os.path.abspath("data") + "/*.dat=data/", + "--include-data-files=source=target", + "--include-data-dir=" + os.path.abspath("assets") + "=assets/", + "--assume-yes-for-downloads", "--output-dir=dist", "--deployment", + '--windows-product-name="TestApp"', "--macos-app-name=TestApp", + "--windows-product-version=0.0.0.0", "--macos-app-version=0.0", + "main.py", + ]) + + def test_default_run_keeps_deployment_disabled(self): + with patch("installer_builder2.subprocess.check_call") as execute: + run_nuitka("main.py", console=True) + + execute.assert_called_once_with([ + sys.executable, "-m", "nuitka", "--standalone", + "--assume-yes-for-downloads", "--output-dir=dist", "main.py", + ]) From ac19af01a58f4eff0a43792e95f38acf4081cf61 Mon Sep 17 00:00:00 2001 From: Christopher Toth Date: Tue, 20 Oct 2026 20:11:01 -0600 Subject: [PATCH 11/11] Declare the existing production module for setuptools builds --- agent-notes/repair-task6-installer-builder2-nuitka-ci.md | 7 ++++++- pyproject.toml | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/agent-notes/repair-task6-installer-builder2-nuitka-ci.md b/agent-notes/repair-task6-installer-builder2-nuitka-ci.md index 3416af0..1613d85 100644 --- a/agent-notes/repair-task6-installer-builder2-nuitka-ci.md +++ b/agent-notes/repair-task6-installer-builder2-nuitka-ci.md @@ -6,6 +6,11 @@ Repair the feature's stale directory assertion and wire safe full unit/package c ## State +- Current follow-up: coordinator published reviewed55509bf after Q's exact-target approval; run39530883400 attempt1 executed all seven tests successfully on Windows/macOS3.13, then both package builds failed because the newly relocated top-level test module triggered setuptools' multiple-module flat-layout rejection. +- I placed the relocated test at repository root without declaring the existing production module. The build logs name both installer_builder2 and test_installer_builder2; master lacks the new test module. This is my feature-amendment packaging defect, not a baseline failure. Fix the cause with explicit [tool.setuptools] py-modules=["installer_builder2"], preserving all seven discovered tests and assertions. No runtime/dependency/support change. +- Published55509bf is archived as backup/task6-installer-builder2-before-packaging-repair and remains unchanged. This correction is local pending complete root diff review and exact fast-forward publication authority. +- The following original preparation state is historical. + - Same-org origin git@github.com:accessibleapps/installer_builder2.git. Fresh master ef093977cb30ab4dba7be03fbe108de8563f0054, published PR #3 new-nuitka head 6f48f0658170207857349fd655d848d312144f89. Original preserved at backup/task6-installer-builder2-3-published-before-repair; no original local feature branch existed. Canonical master clean and untouched. - New isolated worktree installer-builder2-nuitka-ci / repair/task6-installer-builder2-nuitka-ci directly derives from that PR. Source feature is the full original 119-added/9-deleted diff; default lacks formatter/tests/deployment flag, so this assertion defect belongs to feature, not a fake baseline prerequisite. - Actions enabled; no hosted run exists for proposed local head. No publication, model call, settings, hook, release or old-description changes. @@ -27,4 +32,4 @@ Repair the feature's stale directory assertion and wire safe full unit/package c ## Next step -Return complete local diff/head and exact safe regression/audit result before publication. Actual final-head hosted green and Claude review remain separate coordinator gates. +Return the complete two-line setuptools declaration plus note diff and local head before publication. Keep all existing tests/workflow assertions unchanged. No local full CI/build; a later authorized normal hosted run must prove the repaired package build before actual final-head Claude/merge gates. diff --git a/pyproject.toml b/pyproject.toml index 7f71c73..eeb5282 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,4 +14,7 @@ dependencies = [ 'innosetup_builder', 'nuitka', 'ordered_set', # For faster Nuitka compiles -] \ No newline at end of file +] + +[tool.setuptools] +py-modules = ["installer_builder2"]