From 31ae49d6d1d5ae9c87b20983506d04a2e9f5c191 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Thu, 6 Jun 2024 18:19:59 -0500 Subject: [PATCH 01/24] feat(bdist_dmg, bdist_mac): extract bdist_dmg from bdist_mac --- cx_Freeze/__init__.py | 3 +- cx_Freeze/command/bdist_dmg.py | 105 ++++++++++++++++++++++++++++++++ cx_Freeze/command/bdist_mac.py | 99 +----------------------------- tests/test_command_bdist_dmg.py | 42 +++++++++++++ tests/test_command_bdist_mac.py | 27 -------- 5 files changed, 152 insertions(+), 124 deletions(-) create mode 100644 cx_Freeze/command/bdist_dmg.py create mode 100644 tests/test_command_bdist_dmg.py diff --git a/cx_Freeze/__init__.py b/cx_Freeze/__init__.py index 682cd13bb..61a930ec0 100644 --- a/cx_Freeze/__init__.py +++ b/cx_Freeze/__init__.py @@ -33,7 +33,8 @@ __all__ += ["bdist_msi"] elif sys.platform == "darwin": - from cx_Freeze.command.bdist_mac import bdist_dmg, bdist_mac + from cx_Freeze.command.bdist_dmg import bdist_dmg + from cx_Freeze.command.bdist_mac import bdist_mac __all__ += ["bdist_dmg", "bdist_mac"] else: diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py new file mode 100644 index 000000000..c739b7d07 --- /dev/null +++ b/cx_Freeze/command/bdist_dmg.py @@ -0,0 +1,105 @@ +"""Implements the 'bdist_dmg' command (create macOS dmg and/or app bundle).""" + +from __future__ import annotations + +import os +import shutil +import subprocess +from typing import ClassVar + +from setuptools import Command + +__all__ = ["bdist_dmg"] + + +class bdist_dmg(Command): + """Create a Mac DMG disk image containing the Mac application bundle.""" + + description = ( + "create a Mac DMG disk image containing the Mac application bundle" + ) + user_options: ClassVar[list[tuple[str, str | None, str]]] = [ + ("volume-label=", None, "Volume label of the DMG disk image"), + ( + "applications-shortcut=", + None, + "Boolean for whether to include " + "shortcut to Applications in the DMG disk image", + ), + ("silent", "s", "suppress all output except warnings"), + ] + + def initialize_options(self) -> None: + self.volume_label = self.distribution.get_fullname() + self.applications_shortcut = False + self.silent = None + + def finalize_options(self) -> None: + if self.silent is None: + self.silent = False + + def build_dmg(self) -> None: + # Remove DMG if it already exists + if os.path.exists(self.dmg_name): + os.unlink(self.dmg_name) + + # Make dist folder + self.dist_dir = os.path.join(self.build_dir, "dist") + if os.path.exists(self.dist_dir): + shutil.rmtree(self.dist_dir) + self.mkpath(self.dist_dir) + + # Copy App Bundle + dest_dir = os.path.join( + self.dist_dir, os.path.basename(self.bundle_dir) + ) + if self.silent: + shutil.copytree(self.bundle_dir, dest_dir, symlinks=True) + else: + self.copy_tree(self.bundle_dir, dest_dir, preserve_symlinks=True) + + createargs = [ + "hdiutil", + "create", + ] + if self.silent: + createargs += ["-quiet"] + createargs += [ + "-fs", + "HFSX", + "-format", + "UDZO", + self.dmg_name, + "-imagekey", + "zlib-level=9", + "-srcfolder", + self.dist_dir, + "-volname", + self.volume_label, + ] + + if self.applications_shortcut: + apps_folder_link = os.path.join(self.dist_dir, "Applications") + os.symlink( + "/Applications", apps_folder_link, target_is_directory=True + ) + + # Create the dmg + if subprocess.call(createargs) != 0: + msg = "creation of the dmg failed" + raise OSError(msg) + + def run(self) -> None: + # Create the application bundle + self.run_command("bdist_mac") + + # Find the location of the application bundle and the build dir + self.bundle_dir = self.get_finalized_command("bdist_mac").bundle_dir + self.build_dir = self.get_finalized_command("build_exe").build_base + + # Set the file name of the DMG to be built + self.dmg_name = os.path.join( + self.build_dir, self.volume_label + ".dmg" + ) + + self.execute(self.build_dmg, ()) diff --git a/cx_Freeze/command/bdist_mac.py b/cx_Freeze/command/bdist_mac.py index 5712d389d..1772e2963 100644 --- a/cx_Freeze/command/bdist_mac.py +++ b/cx_Freeze/command/bdist_mac.py @@ -1,5 +1,5 @@ -"""Implements the 'bdist_dmg' and 'bdist_mac' commands (create macOS dmg and/or -app blundle. +"""Implements the 'bdist_mac' commands (create macOS +app blundle). """ from __future__ import annotations @@ -21,100 +21,7 @@ ) from cx_Freeze.exception import OptionError -__all__ = ["bdist_dmg", "bdist_mac"] - - -class bdist_dmg(Command): - """Create a Mac DMG disk image containing the Mac application bundle.""" - - description = ( - "create a Mac DMG disk image containing the Mac application bundle" - ) - user_options: ClassVar[list[tuple[str, str | None, str]]] = [ - ("volume-label=", None, "Volume label of the DMG disk image"), - ( - "applications-shortcut=", - None, - "Boolean for whether to include " - "shortcut to Applications in the DMG disk image", - ), - ("silent", "s", "suppress all output except warnings"), - ] - - def initialize_options(self) -> None: - self.volume_label = self.distribution.get_fullname() - self.applications_shortcut = False - self.silent = None - - def finalize_options(self) -> None: - if self.silent is None: - self.silent = False - - def build_dmg(self) -> None: - # Remove DMG if it already exists - if os.path.exists(self.dmg_name): - os.unlink(self.dmg_name) - - # Make dist folder - self.dist_dir = os.path.join(self.build_dir, "dist") - if os.path.exists(self.dist_dir): - shutil.rmtree(self.dist_dir) - self.mkpath(self.dist_dir) - - # Copy App Bundle - dest_dir = os.path.join( - self.dist_dir, os.path.basename(self.bundle_dir) - ) - if self.silent: - shutil.copytree(self.bundle_dir, dest_dir, symlinks=True) - else: - self.copy_tree(self.bundle_dir, dest_dir, preserve_symlinks=True) - - createargs = [ - "hdiutil", - "create", - ] - if self.silent: - createargs += ["-quiet"] - createargs += [ - "-fs", - "HFSX", - "-format", - "UDZO", - self.dmg_name, - "-imagekey", - "zlib-level=9", - "-srcfolder", - self.dist_dir, - "-volname", - self.volume_label, - ] - - if self.applications_shortcut: - apps_folder_link = os.path.join(self.dist_dir, "Applications") - os.symlink( - "/Applications", apps_folder_link, target_is_directory=True - ) - - # Create the dmg - if subprocess.call(createargs) != 0: - msg = "creation of the dmg failed" - raise OSError(msg) - - def run(self) -> None: - # Create the application bundle - self.run_command("bdist_mac") - - # Find the location of the application bundle and the build dir - self.bundle_dir = self.get_finalized_command("bdist_mac").bundle_dir - self.build_dir = self.get_finalized_command("build_exe").build_base - - # Set the file name of the DMG to be built - self.dmg_name = os.path.join( - self.build_dir, self.volume_label + ".dmg" - ) - - self.execute(self.build_dmg, ()) +__all__ = ["bdist_mac"] class bdist_mac(Command): diff --git a/tests/test_command_bdist_dmg.py b/tests/test_command_bdist_dmg.py new file mode 100644 index 000000000..aa40b40f2 --- /dev/null +++ b/tests/test_command_bdist_dmg.py @@ -0,0 +1,42 @@ +"""Tests for cx_Freeze.command.bdist_dmg.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import pytest +from generate_samples import run_command + +bdist_mac = pytest.importorskip( + "cx_Freeze.command.bdist_mac", reason="macOS tests" +).bdist_mac + +if sys.platform != "darwin": + pytest.skip(reason="macOS tests", allow_module_level=True) + +DIST_ATTRS = { + "name": "foo", + "version": "0.0", + "description": "cx_Freeze script to test bdist_mac", + "executables": ["hello.py"], + "script_name": "setup.py", + "author": "Marcelo Duarte", + "author_email": "marcelotduarte@users.noreply.github.com", + "url": "https://github.com/marcelotduarte/cx_Freeze/", +} +SAMPLES_DIR = Path(__file__).resolve().parent.parent / "samples" + + +@pytest.mark.datafiles(SAMPLES_DIR / "simple") +def test_bdist_dmg(datafiles: Path) -> None: + """Test the simple sample with bdist_dmg.""" + name = "hello" + version = "0.1.2.3" + dist_created = datafiles / "build" + + run_command(datafiles, "python setup.py bdist_dmg") + + base_name = f"{name}-{version}" + file_created = dist_created / f"{base_name}.dmg" + assert file_created.is_file(), f"{base_name}.dmg" diff --git a/tests/test_command_bdist_mac.py b/tests/test_command_bdist_mac.py index 90e2dc11b..92b273ce8 100644 --- a/tests/test_command_bdist_mac.py +++ b/tests/test_command_bdist_mac.py @@ -4,7 +4,6 @@ import sys from pathlib import Path -from subprocess import run import pytest from generate_samples import run_command @@ -41,29 +40,3 @@ def test_bdist_mac(datafiles: Path) -> None: base_name = f"{name}-{version}" file_created = dist_created / f"{base_name}.app" assert file_created.is_dir(), f"{base_name}.app" - - -@pytest.mark.datafiles(SAMPLES_DIR / "simple") -def test_bdist_dmg(datafiles: Path) -> None: - """Test the simple sample with bdist_dmg.""" - name = "hello" - version = "0.1.2.3" - dist_created = datafiles / "build" - - process = run( - [sys.executable, "setup.py", "bdist_dmg"], - text=True, - capture_output=True, - check=False, - cwd=datafiles, - ) - if process.returncode != 0: - expected_err = "hdiutil: create failed - Resource busy" - if expected_err in process.stderr: - pytest.xfail(expected_err) - else: - pytest.fail(process.stderr) - - base_name = f"{name}-{version}" - file_created = dist_created / f"{base_name}.dmg" - assert file_created.is_file(), f"{base_name}.dmg" From cd8613176918130048e40c356ef40e9366c92995 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Mon, 10 Jun 2024 18:50:20 -0500 Subject: [PATCH 02/24] feat(bdist_dmg): add dependency for dmgbuild --- pyproject.toml | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index aea5179b2..b554b2a1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ dependencies = [ "patchelf>=0.14 ;sys_platform == 'linux' and platform_machine == 'armv7l'", "patchelf>=0.14 ;sys_platform == 'linux' and platform_machine == 'ppc64le'", "patchelf>=0.14 ;sys_platform == 'linux' and platform_machine == 's390x'", + "dmgbuild>=1.6.1 ;sys_platform == 'darwin'" ] dynamic = ["version"] diff --git a/requirements.txt b/requirements.txt index 1c11636d0..bfcee5f5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ patchelf>=0.14 ;sys_platform == 'linux' and platform_machine == 'aarch64' patchelf>=0.14 ;sys_platform == 'linux' and platform_machine == 'armv7l' patchelf>=0.14 ;sys_platform == 'linux' and platform_machine == 'ppc64le' patchelf>=0.14 ;sys_platform == 'linux' and platform_machine == 's390x' +dmgbuild>=1.6.1 ;sys_platform == 'darwin' From e4902b820c8532a41c1dbad2e38fb1f019a2ea15 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Mon, 10 Jun 2024 21:11:17 -0500 Subject: [PATCH 03/24] feat(bdist_dmg): add most of the params and a sample --- cx_Freeze/command/bdist_dmg.py | 84 ++++++++++++++++++++++++++++++++++ samples/dmg/hello.py | 21 +++++++++ samples/dmg/setup.py | 27 +++++++++++ 3 files changed, 132 insertions(+) create mode 100644 samples/dmg/hello.py create mode 100644 samples/dmg/setup.py diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index c739b7d07..478205327 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -27,12 +27,96 @@ class bdist_dmg(Command): "shortcut to Applications in the DMG disk image", ), ("silent", "s", "suppress all output except warnings"), + ("format=", None, "format of the disk image (default: UDZO)"), + ("filesystem=", None, "filesystem of the disk image (default: HFS+)"), + ( + "size=", + None, + "If defined, specifies the size of the filesystem within the image. If this is not defined, cx_Freeze (and then dmgbuild) will attempt to determine a reasonable size for the image. " + "If you set this, you should set it large enough to hold the files you intend to copy into the image. The syntax is the same as for the -size argument to hdiutil, i.e. you can use the suffixes `b`, `k`, `m`, `g`, `t`, `p` and `e` for bytes, kilobytes, megabytes, gigabytes, terabytes, exabytes and petabytes respectively.", + ), + ( + "background", + "b", + "A rgb color in the form #3344ff, svg " + "named color like goldenrod, a path to an image, or the words 'builtin-arrow'", + ), + ( + "show-status-bar", + None, + "Show the status bar in the Finder window. Default is False.", + ), + ( + "show-tab-view", + None, + "Show the tab view in the Finder window. Default is False.", + ), + ( + "show-path-bar", + None, + "Show the path bar in the Finder window. Default is False.", + ), + ( + "show-sidebar", + None, + "Show the sidebar in the Finder window. Default is False.", + ), + ( + "sidebar-width", + None, + "Width of the sidebar in the Finder window. Default is None.", + ), + ( + "window-rect", + None, + "Window rectangle in the form x,y,width,height" + "The position of the window in ((x, y), (w, h)) format, with y co-ordinates running from bottom to top. The Finder makes sure that the window will be on the user's display, so if you want your window at the top left of the display you could use (0, 100000) as the x, y co-ordinates. Unfortunately it doesn't appear to be possible to position the window relative to the top left or relative to the centre of the user's screen.", + ), + ( + "icon-locations", + None, + "A dictionary specifying the co-ordinates of items in the root directory of the disk image, where the keys are filenames and the values are (x, y) tuples. e.g.:" + 'icon-locations = { "Applications": (100, 100), "README.txt": (200, 100) }', + ), + ( + "default-view", + None, + 'The default view of the Finder window. Possible values are "icon-view", "list-view", "column-view", "coverflow".', + ), + ( + "show-icon-preview", + None, + "Show icon preview in the Finder window. Default is False.", + ), + ( + "license", + None, + "Dictionary specifying license details with 'default-language', 'licenses', and 'buttons'." + "default-language: Language code (e.g., 'en_US') if no matching system language." + "licenses: Map of language codes to license file paths (e.g., {'en_US': 'path/to/license_en.txt'})." + "buttons: Map of language codes to UI strings ([language, agree, disagree, print, save, instruction])." + "Example: {'default-language': 'en_US', 'licenses': {'en_US': 'path/to/license_en.txt'}, 'buttons': {'en_US': ['English', 'Agree', 'Disagree', 'Print', 'Save', 'Instruction text']}}", + ), ] def initialize_options(self) -> None: self.volume_label = self.distribution.get_fullname() self.applications_shortcut = False self.silent = None + self.format = "UDZO" + self.filesystem = "HFS+" + self.size = None + self.background = None + self.show_status_bar = False + self.show_tab_view = False + self.show_path_bar = False + self.show_sidebar = False + self.sidebar_width = None + self.window_rect = None + self.icon_locations = None + self.default_view = None + self.show_icon_preview = False + self.license = None def finalize_options(self) -> None: if self.silent is None: diff --git a/samples/dmg/hello.py b/samples/dmg/hello.py new file mode 100644 index 000000000..080a9fed2 --- /dev/null +++ b/samples/dmg/hello.py @@ -0,0 +1,21 @@ +import sys +from datetime import datetime, timezone + +today = datetime.now(tz=timezone.utc) +print("Hello from cx_Freeze") +print(f"The current date is {today:%B %d, %Y %H:%M:%S}\n") + +print(f"Executable: {sys.executable}") +print(f"Prefix: {sys.prefix}") +print(f"Default encoding: {sys.getdefaultencoding()}") +print(f"File system encoding: {sys.getfilesystemencoding()}\n") + +print("ARGUMENTS:") +for arg in sys.argv: + print(f"{arg}") +print() + +print("PATH:") +for path in sys.path: + print(f"{path}") +print() diff --git a/samples/dmg/setup.py b/samples/dmg/setup.py new file mode 100644 index 000000000..9fdbc5d8d --- /dev/null +++ b/samples/dmg/setup.py @@ -0,0 +1,27 @@ +"""A very simple setup script to create a single executable. + +hello.py is a very simple 'Hello, world' type script which also displays the +environment in which the script runs. + +Run the build process by running the command 'python setup.py build' + +If everything works well you should find a subdirectory in the build +subdirectory that contains the files needed to run the script without Python +""" + +from cx_Freeze import Executable, setup + +executables = [Executable("hello.py")] + +setup( + name="hello", + version="0.1.2.3", + description="Sample cx_Freeze script", + executables=executables, + options={ + "bdist_dmg": { + "applications_shortcut": True, + "volume_label": "Howdy Yall", + }, + }, +) From 2567c6579943ec5afcb0d7359b6062a59dfd5201 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 11 Jun 2024 13:31:02 -0500 Subject: [PATCH 04/24] feat(bdist_dmg): a bad version of a building dmg with dmgbuild --- cx_Freeze/command/bdist_dmg.py | 118 +++++++++++++++++++++++++-------- 1 file changed, 91 insertions(+), 27 deletions(-) diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index 478205327..c2648b6a8 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -2,6 +2,7 @@ from __future__ import annotations +from optparse import OptionError import os import shutil import subprocess @@ -9,6 +10,9 @@ from setuptools import Command +import cx_Freeze.icons +from cx_Freeze.exception import ExecError + __all__ = ["bdist_dmg"] @@ -39,7 +43,7 @@ class bdist_dmg(Command): "background", "b", "A rgb color in the form #3344ff, svg " - "named color like goldenrod, a path to an image, or the words 'builtin-arrow'", + "named color like goldenrod, a path to an image, or the words 'builtin-arrow'. Default is None.", ), ( "show-status-bar", @@ -119,8 +123,17 @@ def initialize_options(self) -> None: self.license = None def finalize_options(self) -> None: + if not self.volume_label: + raise OptionError(msg="volume-label must be set") + if self.applications_shortcut: + self._symlinks = {'Applications': '/Applications'} if self.silent is None: self.silent = False + if self.background: + self.background = self.background.strip() + + def finalize_dmgbuild_options(self): + pass def build_dmg(self) -> None: # Remove DMG if it already exists @@ -142,34 +155,85 @@ def build_dmg(self) -> None: else: self.copy_tree(self.bundle_dir, dest_dir, preserve_symlinks=True) - createargs = [ - "hdiutil", - "create", - ] - if self.silent: - createargs += ["-quiet"] - createargs += [ - "-fs", - "HFSX", - "-format", - "UDZO", - self.dmg_name, - "-imagekey", - "zlib-level=9", - "-srcfolder", - self.dist_dir, - "-volname", - self.volume_label, - ] - - if self.applications_shortcut: - apps_folder_link = os.path.join(self.dist_dir, "Applications") - os.symlink( - "/Applications", apps_folder_link, target_is_directory=True - ) + # executables = self.distribution.executables + # executable = executables[0] + # if len(executables) > 1: + # self.warn( + # "using the first executable as entrypoint: " + # f"{executable.target_name}" + # ) + # if executable.icon is None: + # icon_name = "logox128.png" + # icon_source_dir = os.path.dirname(cx_Freeze.icons.__file__) + # self.copy_file(os.path.join(icon_source_dir, icon_name), icons_dir) + # else: + # icon_name = executable.icon.name + # self.move_file(os.path.join(appdir, icon_name), icons_dir) + + + with open("settings.py", "w") as f: + # Disk Image Settings + f.write(f"filename = '{self.dmg_name}'\n") + f.write(f"volume_label = '{self.volume_label}'\n") + f.write(f"format = '{self.format}'\n") + f.write(f"filesystem = '{self.filesystem}'\n") + # f.write(f"size = {self.size}\n") + + # Content Settings + f.write(f"files = ['{self.dist_dir}']\n") + f.write(f"symlinks = {self._symlinks}\n") + # f.write(f"hide = [{self.hide}]\n") + # f.write(f"hide_extensions = [{self.hide_extensions}]\n") + if self.icon_locations: + f.write(f"icon_locations = { self.icon_locations}\n") + # Only one of these can be set + # f.write(f"icon = {self.icon}\n") + # f.write(f"badge_icon = {self.badge_icon}\n") + + # Window Settings + f.write(f"background = {self.background}\n") + f.write(f"show_status_bar = {self.show_status_bar}\n") + f.write(f"show_tab_view = {self.show_tab_view}\n") + f.write(f"show_pathbar = {self.show_path_bar}\n") + f.write(f"show_sidebar = {self.show_sidebar}\n") + f.write(f"sidebar_width = {self.sidebar_width}\n") + if self.window_rect: + f.write(f"window_rect = {self.window_rect}\n") + f.write(f"default_view = {self.default_view}\n") + f.write(f"show_icon_preview = {self.show_icon_preview}\n") + # f.write(f"include_icon_view_settings = {self.include_icon_view_settings}\n") + # f.write(f"include_list_view_settings = {self.include_list_view_settings}\n") + + # Icon View Settings + # f.write(f"arrange_by = {self.arrange_by}\n") + # f.write(f"grid_offset = {self.grid_offset}\n") + # f.write(f"grid_spacing = {self.grid_spacing}\n") + # f.write(f"scroll_position = {self.scroll_position}\n") + # f.write(f"label_pos = {self.label_pos}\n") + # f.write(f"text_size = {self.text_size}\n") + # f.write(f"icon_size = {self.icon_size}\n") + if self.icon_locations: + f.write(f"icon_locations = {self.icon_locations}\n") + + # List View Settings + # f.write(f"list_icon_size = {self.list_icon_size}\n") + # f.write(f"list_text_size = {self.list_text_size}\n") + # f.write(f"list_scroll_position = {self.list_scroll_position}\n") + # f.write(f"list_sort_by = {self.list_sort_by}\n") + # f.write(f"list_use_relative_dates = {self.list_use_relative_dates}\n") + # f.write(f"list_calculate_all_sizes = {self.list_calculate_all_sizes}\n") + # f.write(f"list_columns = {self.list_columns}\n") + # f.write(f"list_column_widths = {self.list_column_widths}\n") + # f.write(f"list_column_sort_directions = {self.list_column_sort_directions}\n") + + # License Settings + f.write(f"license = {self.license}\n") + + print('\n\n\n\n') + dmgargs = ['dmgbuild', '-s', 'settings.py', self.volume_label, self.dmg_name] # Create the dmg - if subprocess.call(createargs) != 0: + if subprocess.call(dmgargs) != 0: msg = "creation of the dmg failed" raise OSError(msg) From 4412021bb6234c1b93e7e5760c4c49cea70ae9b3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:31:35 +0000 Subject: [PATCH 05/24] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- cx_Freeze/command/bdist_dmg.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index c2648b6a8..0dc13649c 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -2,17 +2,14 @@ from __future__ import annotations -from optparse import OptionError import os import shutil import subprocess +from optparse import OptionError from typing import ClassVar from setuptools import Command -import cx_Freeze.icons -from cx_Freeze.exception import ExecError - __all__ = ["bdist_dmg"] @@ -126,13 +123,13 @@ def finalize_options(self) -> None: if not self.volume_label: raise OptionError(msg="volume-label must be set") if self.applications_shortcut: - self._symlinks = {'Applications': '/Applications'} + self._symlinks = {"Applications": "/Applications"} if self.silent is None: self.silent = False if self.background: self.background = self.background.strip() - def finalize_dmgbuild_options(self): + def finalize_dmgbuild_options(self) -> None: pass def build_dmg(self) -> None: @@ -170,7 +167,6 @@ def build_dmg(self) -> None: # icon_name = executable.icon.name # self.move_file(os.path.join(appdir, icon_name), icons_dir) - with open("settings.py", "w") as f: # Disk Image Settings f.write(f"filename = '{self.dmg_name}'\n") @@ -229,8 +225,14 @@ def build_dmg(self) -> None: # License Settings f.write(f"license = {self.license}\n") - print('\n\n\n\n') - dmgargs = ['dmgbuild', '-s', 'settings.py', self.volume_label, self.dmg_name] + print("\n\n\n\n") + dmgargs = [ + "dmgbuild", + "-s", + "settings.py", + self.volume_label, + self.dmg_name, + ] # Create the dmg if subprocess.call(dmgargs) != 0: From 1f21a80703cd56011927253d2900f5f50736837d Mon Sep 17 00:00:00 2001 From: Marcelo Duarte Date: Tue, 11 Jun 2024 16:33:13 -0300 Subject: [PATCH 06/24] fix OptionError import --- cx_Freeze/command/bdist_dmg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index 0dc13649c..1559b260b 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -5,11 +5,12 @@ import os import shutil import subprocess -from optparse import OptionError from typing import ClassVar from setuptools import Command +from cx_Freeze.exception import OptionError + __all__ = ["bdist_dmg"] From bc563515c1df4c6cd3e97f8c0f5224cd3630102a Mon Sep 17 00:00:00 2001 From: Marcelo Duarte Date: Tue, 11 Jun 2024 16:35:40 -0300 Subject: [PATCH 07/24] make bdist_dmg tests pass --- tests/test_command_bdist_dmg.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/test_command_bdist_dmg.py b/tests/test_command_bdist_dmg.py index aa40b40f2..a9d28c44d 100644 --- a/tests/test_command_bdist_dmg.py +++ b/tests/test_command_bdist_dmg.py @@ -4,13 +4,14 @@ import sys from pathlib import Path +from subprocess import run import pytest from generate_samples import run_command -bdist_mac = pytest.importorskip( - "cx_Freeze.command.bdist_mac", reason="macOS tests" -).bdist_mac +bdist_dmg = pytest.importorskip( + "cx_Freeze.command.bdist_dmg", reason="macOS tests" +).bdist_dmg if sys.platform != "darwin": pytest.skip(reason="macOS tests", allow_module_level=True) @@ -35,7 +36,19 @@ def test_bdist_dmg(datafiles: Path) -> None: version = "0.1.2.3" dist_created = datafiles / "build" - run_command(datafiles, "python setup.py bdist_dmg") + process = run( + [sys.executable, "setup.py", "bdist_dmg"], + text=True, + capture_output=True, + check=False, + cwd=datafiles, + ) + if process.returncode != 0: + expected_err = "hdiutil: create failed - Resource busy" + if expected_err in process.stderr: + pytest.xfail(expected_err) + else: + pytest.fail(process.stderr) base_name = f"{name}-{version}" file_created = dist_created / f"{base_name}.dmg" From 54d1fe96be0d6e6998b30b220ca32bb3ac1013ee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 19:35:56 +0000 Subject: [PATCH 08/24] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_command_bdist_dmg.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_command_bdist_dmg.py b/tests/test_command_bdist_dmg.py index a9d28c44d..491249892 100644 --- a/tests/test_command_bdist_dmg.py +++ b/tests/test_command_bdist_dmg.py @@ -7,7 +7,6 @@ from subprocess import run import pytest -from generate_samples import run_command bdist_dmg = pytest.importorskip( "cx_Freeze.command.bdist_dmg", reason="macOS tests" From 13656a278a71e7b62e2bf6053fb33e84ae00645d Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 11 Jun 2024 15:28:19 -0500 Subject: [PATCH 09/24] fix(bdist_dmg): tests --- cx_Freeze/command/bdist_dmg.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index 1559b260b..d766698ab 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -104,6 +104,7 @@ class bdist_dmg(Command): def initialize_options(self) -> None: self.volume_label = self.distribution.get_fullname() self.applications_shortcut = False + self._symlinks = {} self.silent = None self.format = "UDZO" self.filesystem = "HFS+" @@ -122,9 +123,10 @@ def initialize_options(self) -> None: def finalize_options(self) -> None: if not self.volume_label: - raise OptionError(msg="volume-label must be set") + msg = "volume-label must be set" + raise OptionError(msg) if self.applications_shortcut: - self._symlinks = {"Applications": "/Applications"} + self._symlinks["Applications"] = "/Applications" if self.silent is None: self.silent = False if self.background: From 1301703b5ab8488e6112ae9272096259b94cad42 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 11 Jun 2024 17:49:14 -0500 Subject: [PATCH 10/24] feat(bdist_dmg): add more functionality like backgrounds and icon rendering --- cx_Freeze/command/bdist_dmg.py | 205 +++++++++++++++++++++++---------- samples/dmg/setup.py | 26 ++++- 2 files changed, 168 insertions(+), 63 deletions(-) diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index d766698ab..d57d12a78 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -3,12 +3,14 @@ from __future__ import annotations import os +import pathlib import shutil import subprocess from typing import ClassVar from setuptools import Command +import cx_Freeze.icons from cx_Freeze.exception import OptionError __all__ = ["bdist_dmg"] @@ -102,10 +104,11 @@ class bdist_dmg(Command): ] def initialize_options(self) -> None: + self.silent = None self.volume_label = self.distribution.get_fullname() self.applications_shortcut = False self._symlinks = {} - self.silent = None + self._files = [] self.format = "UDZO" self.filesystem = "HFS+" self.size = None @@ -116,9 +119,29 @@ def initialize_options(self) -> None: self.show_sidebar = False self.sidebar_width = None self.window_rect = None + self.hide = None + self.hide_extensions = None self.icon_locations = None self.default_view = None self.show_icon_preview = False + self.include_icon_view_settings = "auto" + self.include_list_view_settings = "auto" + self.arrange_by = None + self.grid_offset = None + self.grid_spacing = None + self.scroll_position = None + self.label_pos = None + self.text_size = None + self.icon_size = None + self.list_icon_size = None + self.list_text_size = None + self.list_scroll_position = None + self.list_sort_by = None + self.list_use_relative_dates = None + self.list_calculate_all_sizes = None + self.list_columns = None + self.list_column_widths = None + self.list_column_sort_directions = None self.license = None def finalize_options(self) -> None: @@ -129,11 +152,31 @@ def finalize_options(self) -> None: self._symlinks["Applications"] = "/Applications" if self.silent is None: self.silent = False - if self.background: - self.background = self.background.strip() + + self.finalize_dmgbuild_options() def finalize_dmgbuild_options(self) -> None: - pass + if self.background: + self.background = self.background.strip() + if self.background == "builtin-arrow" and ( + self.icon_locations or self.window_rect + ): + msg = "background='builtin-arrow' cannot be used with icon_locations or window_rect" + raise OptionError(msg) + if not self.arrange_by: + self.arrange_by = None + if not self.grid_offset: + self.grid_offset = (0, 0) + if not self.grid_spacing: + self.grid_spacing = 100 + if not self.scroll_position: + self.scroll_position = (0, 0) + if not self.label_pos: + self.label_pos = "bottom" + if not self.text_size: + self.text_size = 16 + if not self.icon_size: + self.icon_size = 128 def build_dmg(self) -> None: # Remove DMG if it already exists @@ -155,78 +198,116 @@ def build_dmg(self) -> None: else: self.copy_tree(self.bundle_dir, dest_dir, preserve_symlinks=True) - # executables = self.distribution.executables - # executable = executables[0] - # if len(executables) > 1: - # self.warn( - # "using the first executable as entrypoint: " - # f"{executable.target_name}" - # ) - # if executable.icon is None: - # icon_name = "logox128.png" - # icon_source_dir = os.path.dirname(cx_Freeze.icons.__file__) - # self.copy_file(os.path.join(icon_source_dir, icon_name), icons_dir) - # else: - # icon_name = executable.icon.name - # self.move_file(os.path.join(appdir, icon_name), icons_dir) + # Add the App Bundle to the list of files + self._files.append(self.bundle_dir) + + # set the app_name for the application bundle + app_name = os.path.basename(self.bundle_dir) + # Set the defaults + if ( + self.background == "builtin-arrow" + and not self.icon_locations + and not self.window_rect + ): + self.icon_locations = { + "Applications": (500, 120), + app_name: (140, 120), + } + self.window_rect = ((100, 100), (640, 380)) + + executables = self.distribution.executables # type: ignore + executable = executables[0] + if len(executables) > 1: + self.warn( + "using the first executable as entrypoint: " + f"{executable.target_name}" + ) + if executable.icon is None: + icon_name = "setup.icns" + icon_source_dir = os.path.dirname(cx_Freeze.icons.__file__) + self.icon = os.path.join(icon_source_dir, icon_name) + else: + self.icon = pathlib.Path(executable.icon).resolve() with open("settings.py", "w") as f: + + def add_param(name, value) -> None: + # if value is a string, add quotes + if isinstance(value, (str, pathlib.Path)): + f.write(f"{name} = '{value}'\n") + else: + f.write(f"{name} = {value}\n") + # Disk Image Settings - f.write(f"filename = '{self.dmg_name}'\n") - f.write(f"volume_label = '{self.volume_label}'\n") - f.write(f"format = '{self.format}'\n") - f.write(f"filesystem = '{self.filesystem}'\n") - # f.write(f"size = {self.size}\n") + add_param("filename", self.dmg_name) + add_param("volume_label", self.volume_label) + add_param("format", self.format) + add_param("filesystem", self.filesystem) + add_param("size", self.size) # Content Settings - f.write(f"files = ['{self.dist_dir}']\n") - f.write(f"symlinks = {self._symlinks}\n") - # f.write(f"hide = [{self.hide}]\n") - # f.write(f"hide_extensions = [{self.hide_extensions}]\n") - if self.icon_locations: - f.write(f"icon_locations = { self.icon_locations}\n") + add_param("files", self._files) + add_param("symlinks", self._symlinks) + # unimplemented + add_param("hide", self.hide) + # unimplemented + add_param("hide_extensions", self.hide_extensions) # Only one of these can be set - # f.write(f"icon = {self.icon}\n") - # f.write(f"badge_icon = {self.badge_icon}\n") + if self.icon_locations: + add_param("icon_locations", self.icon_locations) + if self.icon: + add_param("icon", self.icon) + # We don't need to set this, as we only support icns + # add param ( "badge_icon", self.badge_icon) # Window Settings - f.write(f"background = {self.background}\n") - f.write(f"show_status_bar = {self.show_status_bar}\n") - f.write(f"show_tab_view = {self.show_tab_view}\n") - f.write(f"show_pathbar = {self.show_path_bar}\n") - f.write(f"show_sidebar = {self.show_sidebar}\n") - f.write(f"sidebar_width = {self.sidebar_width}\n") + add_param("background", self.background) + add_param("show_status_bar", self.show_status_bar) + add_param("show_tab_view", self.show_tab_view) + add_param("show_pathbar", self.show_path_bar) + add_param("show_sidebar", self.show_sidebar) + add_param("sidebar_width", self.sidebar_width) if self.window_rect: - f.write(f"window_rect = {self.window_rect}\n") - f.write(f"default_view = {self.default_view}\n") - f.write(f"show_icon_preview = {self.show_icon_preview}\n") - # f.write(f"include_icon_view_settings = {self.include_icon_view_settings}\n") - # f.write(f"include_list_view_settings = {self.include_list_view_settings}\n") - - # Icon View Settings - # f.write(f"arrange_by = {self.arrange_by}\n") - # f.write(f"grid_offset = {self.grid_offset}\n") - # f.write(f"grid_spacing = {self.grid_spacing}\n") - # f.write(f"scroll_position = {self.scroll_position}\n") - # f.write(f"label_pos = {self.label_pos}\n") - # f.write(f"text_size = {self.text_size}\n") - # f.write(f"icon_size = {self.icon_size}\n") + add_param("window_rect", self.window_rect) + if self.default_view: + add_param("default_view", self.default_view) + + add_param("show_icon_preview", self.show_icon_preview) + add_param( + "include_icon_view_settings", self.include_icon_view_settings + ) + add_param( + "include_list_view_settings", self.include_list_view_settings + ) + + # Icon View Settings\ + add_param("arrange_by", self.arrange_by) + add_param("grid_offset", self.grid_offset) + add_param("grid_spacing", self.grid_spacing) + add_param("scroll_position", self.scroll_position) + add_param("label_pos", self.label_pos) + add_param("text_size", self.text_size) + add_param("icon_size", self.icon_size) if self.icon_locations: - f.write(f"icon_locations = {self.icon_locations}\n") + add_param("icon_locations", self.icon_locations) # List View Settings - # f.write(f"list_icon_size = {self.list_icon_size}\n") - # f.write(f"list_text_size = {self.list_text_size}\n") - # f.write(f"list_scroll_position = {self.list_scroll_position}\n") - # f.write(f"list_sort_by = {self.list_sort_by}\n") - # f.write(f"list_use_relative_dates = {self.list_use_relative_dates}\n") - # f.write(f"list_calculate_all_sizes = {self.list_calculate_all_sizes}\n") - # f.write(f"list_columns = {self.list_columns}\n") - # f.write(f"list_column_widths = {self.list_column_widths}\n") - # f.write(f"list_column_sort_directions = {self.list_column_sort_directions}\n") + add_param("list_icon_size", self.list_icon_size) + add_param("list_text_size", self.list_text_size) + add_param("list_scroll_position", self.list_scroll_position) + add_param("list_sort_by", self.list_sort_by) + add_param("list_use_relative_dates", self.list_use_relative_dates) + add_param( + "list_calculate_all_sizes", self.list_calculate_all_sizes + ) + add_param("list_columns", self.list_columns) + add_param("list_column_widths", self.list_column_widths) + add_param( + "list_column_sort_directions", self.list_column_sort_directions + ) # License Settings - f.write(f"license = {self.license}\n") + add_param("license", self.license) print("\n\n\n\n") dmgargs = [ diff --git a/samples/dmg/setup.py b/samples/dmg/setup.py index 9fdbc5d8d..73c36497b 100644 --- a/samples/dmg/setup.py +++ b/samples/dmg/setup.py @@ -11,7 +11,9 @@ from cx_Freeze import Executable, setup -executables = [Executable("hello.py")] +executables = [ + Executable("hello.py", icon="../../cx_Freeze/icons/python.icns") +] setup( name="hello", @@ -19,9 +21,31 @@ description="Sample cx_Freeze script", executables=executables, options={ + "bdist_mac": { + "bundle_name": "hello", + }, "bdist_dmg": { "applications_shortcut": True, "volume_label": "Howdy Yall", + "background": "builtin-arrow", + # "icon_locations": { + # "Applications": (300, 100), + # "hello.app": (100, 100), + # }, + "license": { + "default-language": "en_US", + "licenses": {"en_US": "Do it right, do it legal, do it safe."}, + "buttons": { + "en_US": [ + "English", + "Agree", + "Disagree", + "Print", + "Save", + "If you agree, click Agree to continue the installation. If you do not agree, click Disagree to cancel the installation.", + ] + }, + }, }, }, ) From 879612cd1d794baeb93cf0dc407f5611eb1e168d Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Wed, 12 Jun 2024 19:10:13 -0500 Subject: [PATCH 11/24] feat(bdist_dmg): make the test run --- cx_Freeze/command/bdist_dmg.py | 46 ++++++++++++++++++++++------------ samples/dmg/setup.py | 4 --- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index d57d12a78..65ed1736a 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -11,6 +11,7 @@ from setuptools import Command import cx_Freeze.icons +from cx_Freeze import Executable from cx_Freeze.exception import OptionError __all__ = ["bdist_dmg"] @@ -215,8 +216,8 @@ def build_dmg(self) -> None: } self.window_rect = ((100, 100), (640, 380)) - executables = self.distribution.executables # type: ignore - executable = executables[0] + executables = self.distribution.executables # type: list[Executable] + executable: Executable = executables[0] if len(executables) > 1: self.warn( "using the first executable as entrypoint: " @@ -238,6 +239,10 @@ def add_param(name, value) -> None: else: f.write(f"{name} = {value}\n") + # Some fields expect and allow None, others don't + # so we need to check for None and not add them for + # the fields that don't allow it + # Disk Image Settings add_param("filename", self.dmg_name) add_param("volume_label", self.volume_label) @@ -248,10 +253,10 @@ def add_param(name, value) -> None: # Content Settings add_param("files", self._files) add_param("symlinks", self._symlinks) - # unimplemented - add_param("hide", self.hide) - # unimplemented - add_param("hide_extensions", self.hide_extensions) + if self.hide: + add_param("hide", self.hide) + if self.hide_extensions: + add_param("hide_extensions", self.hide_extensions) # Only one of these can be set if self.icon_locations: add_param("icon_locations", self.icon_locations) @@ -286,25 +291,34 @@ def add_param(name, value) -> None: add_param("grid_spacing", self.grid_spacing) add_param("scroll_position", self.scroll_position) add_param("label_pos", self.label_pos) - add_param("text_size", self.text_size) - add_param("icon_size", self.icon_size) + if self.text_size: + add_param("text_size", self.text_size) + if self.icon_size: + add_param("icon_size", self.icon_size) if self.icon_locations: add_param("icon_locations", self.icon_locations) # List View Settings - add_param("list_icon_size", self.list_icon_size) - add_param("list_text_size", self.list_text_size) - add_param("list_scroll_position", self.list_scroll_position) + if self.list_icon_size: + add_param("list_icon_size", self.list_icon_size) + if self.list_text_size: + add_param("list_text_size", self.list_text_size) + if self.list_scroll_position: + add_param("list_scroll_position", self.list_scroll_position) add_param("list_sort_by", self.list_sort_by) add_param("list_use_relative_dates", self.list_use_relative_dates) add_param( "list_calculate_all_sizes", self.list_calculate_all_sizes ) - add_param("list_columns", self.list_columns) - add_param("list_column_widths", self.list_column_widths) - add_param( - "list_column_sort_directions", self.list_column_sort_directions - ) + if self.list_columns: + add_param("list_columns", self.list_columns) + if self.list_column_widths: + add_param("list_column_widths", self.list_column_widths) + if self.list_column_sort_directions: + add_param( + "list_column_sort_directions", + self.list_column_sort_directions, + ) # License Settings add_param("license", self.license) diff --git a/samples/dmg/setup.py b/samples/dmg/setup.py index 73c36497b..dac341abe 100644 --- a/samples/dmg/setup.py +++ b/samples/dmg/setup.py @@ -28,10 +28,6 @@ "applications_shortcut": True, "volume_label": "Howdy Yall", "background": "builtin-arrow", - # "icon_locations": { - # "Applications": (300, 100), - # "hello.app": (100, 100), - # }, "license": { "default-language": "en_US", "licenses": {"en_US": "Do it right, do it legal, do it safe."}, From a669bf9a517ee0c5b4730af1811aaaf3aacaafea Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Wed, 12 Jun 2024 19:17:30 -0500 Subject: [PATCH 12/24] ci: don't fail all pipelines when one fails --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f943b8005..97aaeb0c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,7 @@ jobs: tests_extra: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ windows-latest ] python-version: ['3.10'] From 15f233b124ed8025ee85da88b0f38292715c870f Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Mon, 17 Jun 2024 19:06:23 -0500 Subject: [PATCH 13/24] fix(bdist_dmg):use build_dmg code entry point --- .gitignore | 2 ++ cx_Freeze/command/bdist_dmg.py | 26 +++++++++++++------------- samples/dmg/.gitignore | 1 + 3 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 samples/dmg/.gitignore diff --git a/.gitignore b/.gitignore index d6a8c9332..c09bbaa15 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ coverage.xml htmlcov tests/coverage_html_report tests/.coverage +.venv/ +.vscode/ diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index 65ed1736a..d2b6db84c 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -5,9 +5,9 @@ import os import pathlib import shutil -import subprocess from typing import ClassVar +from dmgbuild import build_dmg from setuptools import Command import cx_Freeze.icons @@ -323,19 +323,19 @@ def add_param(name, value) -> None: # License Settings add_param("license", self.license) - print("\n\n\n\n") - dmgargs = [ - "dmgbuild", - "-s", - "settings.py", - self.volume_label, - self.dmg_name, - ] + def log_handler(msg: dict[str, str]) -> None: + if not self.silent: + loggable = f"{','.join(f'{key}: {value}' for key, value in msg.items())}" + # Doesn't seem to function as expected + # self.announce(loggable, level=DEBUG) # noqa: ERA001 + print(loggable) - # Create the dmg - if subprocess.call(dmgargs) != 0: - msg = "creation of the dmg failed" - raise OSError(msg) + build_dmg( + self.dmg_name, + self.volume_label, + "settings.py", + callback=log_handler, + ) def run(self) -> None: # Create the application bundle diff --git a/samples/dmg/.gitignore b/samples/dmg/.gitignore new file mode 100644 index 000000000..fce19e421 --- /dev/null +++ b/samples/dmg/.gitignore @@ -0,0 +1 @@ +settings.py From c183fcd22e154a526ed9991414e80e21c8793d8c Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Mon, 17 Jun 2024 19:19:49 -0500 Subject: [PATCH 14/24] fix(test/bdist_dmg): use dmg sample for tests --- samples/dmg/setup.py | 7 ++++++- tests/test_command_bdist_dmg.py | 20 ++++---------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/samples/dmg/setup.py b/samples/dmg/setup.py index dac341abe..72757117f 100644 --- a/samples/dmg/setup.py +++ b/samples/dmg/setup.py @@ -12,7 +12,12 @@ from cx_Freeze import Executable, setup executables = [ - Executable("hello.py", icon="../../cx_Freeze/icons/python.icns") + Executable( + script="hello.py", + # You can also specify an icon for the executable that will be reused for the dmg + # only the first executable is used for the icon + # icon="../../cx_Freeze/icons/python.icns" #noqa: ERA001 + ) ] setup( diff --git a/tests/test_command_bdist_dmg.py b/tests/test_command_bdist_dmg.py index 491249892..41b37afc6 100644 --- a/tests/test_command_bdist_dmg.py +++ b/tests/test_command_bdist_dmg.py @@ -15,24 +15,13 @@ if sys.platform != "darwin": pytest.skip(reason="macOS tests", allow_module_level=True) -DIST_ATTRS = { - "name": "foo", - "version": "0.0", - "description": "cx_Freeze script to test bdist_mac", - "executables": ["hello.py"], - "script_name": "setup.py", - "author": "Marcelo Duarte", - "author_email": "marcelotduarte@users.noreply.github.com", - "url": "https://github.com/marcelotduarte/cx_Freeze/", -} SAMPLES_DIR = Path(__file__).resolve().parent.parent / "samples" -@pytest.mark.datafiles(SAMPLES_DIR / "simple") +@pytest.mark.datafiles(SAMPLES_DIR / "dmg") def test_bdist_dmg(datafiles: Path) -> None: """Test the simple sample with bdist_dmg.""" - name = "hello" - version = "0.1.2.3" + name = "Howdy Yall" dist_created = datafiles / "build" process = run( @@ -49,6 +38,5 @@ def test_bdist_dmg(datafiles: Path) -> None: else: pytest.fail(process.stderr) - base_name = f"{name}-{version}" - file_created = dist_created / f"{base_name}.dmg" - assert file_created.is_file(), f"{base_name}.dmg" + file_created = dist_created / f"{name}.dmg" + assert file_created.is_file(), f"{name}.dmg" From 337c6a75f162a7da9deec084bf5879f0922cf9bf Mon Sep 17 00:00:00 2001 From: Marcelo Duarte Date: Fri, 21 Jun 2024 01:08:12 -0300 Subject: [PATCH 15/24] remove fail-fast --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97aaeb0c9..f943b8005 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,6 @@ jobs: tests_extra: runs-on: ${{ matrix.os }} strategy: - fail-fast: false matrix: os: [ windows-latest ] python-version: ['3.10'] From db418e416207ccc6da4ddb29df0e7cb3939d9e22 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Fri, 21 Jun 2024 02:12:54 -0500 Subject: [PATCH 16/24] feat: update docs --- doc/src/bdist_dmg.rst | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/doc/src/bdist_dmg.rst b/doc/src/bdist_dmg.rst index 5436c24f3..ce8fb5f5b 100644 --- a/doc/src/bdist_dmg.rst +++ b/doc/src/bdist_dmg.rst @@ -19,6 +19,48 @@ installation. image * - .. option:: silent (-s) - suppress all output except warnings + * - .. option:: format + - Format of the DMG disk image Default is UDZO + * - .. option:: filesystem + - Filesystem of the DMG disk image Default is HFS+ + * - .. option:: size + - If defined, specifies the size of the filesystem within the image. + If this is not defined, cx_Freeze (and then dmgbuild) will attempt to determine a reasonable size for the image. + If you set this, you should set it large enough to hold the files you intend to copy into the image. The syntax is + the same as for the -size argument to hdiutil, i.e. you can use the suffixes `b`, `k`, `m`, `g`, `t`, `p` and `e` for + bytes, kilobytes, megabytes, gigabytes, terabytes, exabytes and petabytes respectively. + * - .. option:: background + - A rgb color in the form #3344ff, svg named color like goldenrod, a path to an image, or the words 'builtin-arrow'. Default is None. + * - .. option:: show-status-bar + - Show the status bar in the Finder window. Default is False. + * - .. option:: show-tab-view + - Show the tab view in the Finder window. Default is False. + * - .. option:: show-path-bar + - Show the path bar in the Finder window. Default is False. + * - .. option:: show-sidebar + - Show the sidebar in the Finder window. Default is False. + * - .. option:: sidebar-width + - Width of the sidebar in the Finder window. Default is None. + * - .. option:: windows-rect + - Window rectangle in the form x,y,width,height" + The position of the window in ((x, y), (w, h)) format, with y co-ordinates running from bottom to top. The Finder + makes sure that the window will be on the user's display, so if you want your window at the top left of the display + you could use (0, 100000) as the x, y co-ordinates. Unfortunately it doesn't appear to be possible to position the + window relative to the top left or relative to the centre of the user's screen. + * - .. option:: icon-locations + - A dictionary specifying the co-ordinates of items in the root directory of the disk image, where the keys are filenames and the values are (x, y) tuples. For example, + icon-locations = { "Applications": (100, 100), "README.txt": (200, 100) } + * - .. option:: default-view + - The default view of the Finder window. Possible values are "icon-view", "list-view", "column-view", "coverflow". + * - .. option:: show-icon-preview + - Show icon preview in the Finder window. Default is False. + * - .. option:: license + - Dictionary specifying license details with 'default-language', 'licenses', and 'buttons'. + default-language: Language code (e.g., 'en_US') if no matching system language. + licenses: Map of language codes to license file paths (e.g., {'en_US': 'path/to/license_en.txt'}). + buttons: Map of language codes to UI strings ([language, agree, disagree, print, save, instruction]). + Example: {'default-language': 'en_US', 'licenses': {'en_US': 'path/to/license_en.txt'}, 'buttons': {'en_US': ['English', 'Agree', 'Disagree', 'Print', 'Save', 'Instruction text']}} + This is the equivalent help to specify the same options on the command line: From 5aac5e6415615ad1a5a522c286a892a89a51bd59 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Fri, 21 Jun 2024 02:13:21 -0500 Subject: [PATCH 17/24] fix(bdist_dmg): update from review --- cx_Freeze/command/bdist_dmg.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index d2b6db84c..7dd517cdd 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -37,8 +37,11 @@ class bdist_dmg(Command): ( "size=", None, - "If defined, specifies the size of the filesystem within the image. If this is not defined, cx_Freeze (and then dmgbuild) will attempt to determine a reasonable size for the image. " - "If you set this, you should set it large enough to hold the files you intend to copy into the image. The syntax is the same as for the -size argument to hdiutil, i.e. you can use the suffixes `b`, `k`, `m`, `g`, `t`, `p` and `e` for bytes, kilobytes, megabytes, gigabytes, terabytes, exabytes and petabytes respectively.", + "If defined, specifies the size of the filesystem within the image. " + "If this is not defined, cx_Freeze (and then dmgbuild) will attempt to determine a reasonable size for the image. " + "If you set this, you should set it large enough to hold the files you intend to copy into the image. The syntax is " + "the same as for the -size argument to hdiutil, i.e. you can use the suffixes `b`, `k`, `m`, `g`, `t`, `p` and `e` for " + "bytes, kilobytes, megabytes, gigabytes, terabytes, exabytes and petabytes respectively.", ), ( "background", @@ -75,7 +78,10 @@ class bdist_dmg(Command): "window-rect", None, "Window rectangle in the form x,y,width,height" - "The position of the window in ((x, y), (w, h)) format, with y co-ordinates running from bottom to top. The Finder makes sure that the window will be on the user's display, so if you want your window at the top left of the display you could use (0, 100000) as the x, y co-ordinates. Unfortunately it doesn't appear to be possible to position the window relative to the top left or relative to the centre of the user's screen.", + "The position of the window in ((x, y), (w, h)) format, with y co-ordinates running from bottom to top. The Finder " + " makes sure that the window will be on the user's display, so if you want your window at the top left of the display " + "you could use (0, 100000) as the x, y co-ordinates. Unfortunately it doesn't appear to be possible to position the " + "window relative to the top left or relative to the centre of the user's screen.", ), ( "icon-locations", @@ -125,6 +131,10 @@ def initialize_options(self) -> None: self.icon_locations = None self.default_view = None self.show_icon_preview = False + self.license = None + + + # Non-exposed options self.include_icon_view_settings = "auto" self.include_list_view_settings = "auto" self.arrange_by = None @@ -143,7 +153,6 @@ def initialize_options(self) -> None: self.list_columns = None self.list_column_widths = None self.list_column_sort_directions = None - self.license = None def finalize_options(self) -> None: if not self.volume_label: @@ -326,9 +335,7 @@ def add_param(name, value) -> None: def log_handler(msg: dict[str, str]) -> None: if not self.silent: loggable = f"{','.join(f'{key}: {value}' for key, value in msg.items())}" - # Doesn't seem to function as expected - # self.announce(loggable, level=DEBUG) # noqa: ERA001 - print(loggable) + self.announce(loggable) build_dmg( self.dmg_name, From a1b738864652e81c2254cd63b756b3fc5d6e7b08 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Jun 2024 07:13:30 +0000 Subject: [PATCH 18/24] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- cx_Freeze/command/bdist_dmg.py | 1 - doc/src/bdist_dmg.rst | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index 7dd517cdd..22e073a7b 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -133,7 +133,6 @@ def initialize_options(self) -> None: self.show_icon_preview = False self.license = None - # Non-exposed options self.include_icon_view_settings = "auto" self.include_list_view_settings = "auto" diff --git a/doc/src/bdist_dmg.rst b/doc/src/bdist_dmg.rst index ce8fb5f5b..333e0be35 100644 --- a/doc/src/bdist_dmg.rst +++ b/doc/src/bdist_dmg.rst @@ -25,9 +25,9 @@ installation. - Filesystem of the DMG disk image Default is HFS+ * - .. option:: size - If defined, specifies the size of the filesystem within the image. - If this is not defined, cx_Freeze (and then dmgbuild) will attempt to determine a reasonable size for the image. - If you set this, you should set it large enough to hold the files you intend to copy into the image. The syntax is - the same as for the -size argument to hdiutil, i.e. you can use the suffixes `b`, `k`, `m`, `g`, `t`, `p` and `e` for + If this is not defined, cx_Freeze (and then dmgbuild) will attempt to determine a reasonable size for the image. + If you set this, you should set it large enough to hold the files you intend to copy into the image. The syntax is + the same as for the -size argument to hdiutil, i.e. you can use the suffixes `b`, `k`, `m`, `g`, `t`, `p` and `e` for bytes, kilobytes, megabytes, gigabytes, terabytes, exabytes and petabytes respectively. * - .. option:: background - A rgb color in the form #3344ff, svg named color like goldenrod, a path to an image, or the words 'builtin-arrow'. Default is None. @@ -43,9 +43,9 @@ installation. - Width of the sidebar in the Finder window. Default is None. * - .. option:: windows-rect - Window rectangle in the form x,y,width,height" - The position of the window in ((x, y), (w, h)) format, with y co-ordinates running from bottom to top. The Finder - makes sure that the window will be on the user's display, so if you want your window at the top left of the display - you could use (0, 100000) as the x, y co-ordinates. Unfortunately it doesn't appear to be possible to position the + The position of the window in ((x, y), (w, h)) format, with y co-ordinates running from bottom to top. The Finder + makes sure that the window will be on the user's display, so if you want your window at the top left of the display + you could use (0, 100000) as the x, y co-ordinates. Unfortunately it doesn't appear to be possible to position the window relative to the top left or relative to the centre of the user's screen. * - .. option:: icon-locations - A dictionary specifying the co-ordinates of items in the root directory of the disk image, where the keys are filenames and the values are (x, y) tuples. For example, @@ -60,7 +60,7 @@ installation. licenses: Map of language codes to license file paths (e.g., {'en_US': 'path/to/license_en.txt'}). buttons: Map of language codes to UI strings ([language, agree, disagree, print, save, instruction]). Example: {'default-language': 'en_US', 'licenses': {'en_US': 'path/to/license_en.txt'}, 'buttons': {'en_US': ['English', 'Agree', 'Disagree', 'Print', 'Save', 'Instruction text']}} - + This is the equivalent help to specify the same options on the command line: From 518874de3e648e51604ce787f783fd0604c79ae5 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Fri, 21 Jun 2024 02:15:00 -0500 Subject: [PATCH 19/24] fix(bdist_dmg): drop pathlib --- cx_Freeze/command/bdist_dmg.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cx_Freeze/command/bdist_dmg.py b/cx_Freeze/command/bdist_dmg.py index 22e073a7b..7a5fedcb6 100644 --- a/cx_Freeze/command/bdist_dmg.py +++ b/cx_Freeze/command/bdist_dmg.py @@ -3,7 +3,6 @@ from __future__ import annotations import os -import pathlib import shutil from typing import ClassVar @@ -236,13 +235,13 @@ def build_dmg(self) -> None: icon_source_dir = os.path.dirname(cx_Freeze.icons.__file__) self.icon = os.path.join(icon_source_dir, icon_name) else: - self.icon = pathlib.Path(executable.icon).resolve() + self.icon = os.path.abspath(executable.icon) with open("settings.py", "w") as f: def add_param(name, value) -> None: # if value is a string, add quotes - if isinstance(value, (str, pathlib.Path)): + if isinstance(value, (str)): f.write(f"{name} = '{value}'\n") else: f.write(f"{name} = {value}\n") From 8f877e635cec51194a67b75c028ea163a5dc49ee Mon Sep 17 00:00:00 2001 From: Marcelo Duarte Date: Fri, 21 Jun 2024 23:38:58 -0300 Subject: [PATCH 20/24] make sphinx happy --- doc/src/bdist_dmg.rst | 70 ++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/doc/src/bdist_dmg.rst b/doc/src/bdist_dmg.rst index 333e0be35..ecea6751d 100644 --- a/doc/src/bdist_dmg.rst +++ b/doc/src/bdist_dmg.rst @@ -20,46 +20,62 @@ installation. * - .. option:: silent (-s) - suppress all output except warnings * - .. option:: format - - Format of the DMG disk image Default is UDZO + - Format of the DMG disk image Default is UDZO * - .. option:: filesystem - - Filesystem of the DMG disk image Default is HFS+ + - Filesystem of the DMG disk image Default is HFS+ * - .. option:: size - - If defined, specifies the size of the filesystem within the image. - If this is not defined, cx_Freeze (and then dmgbuild) will attempt to determine a reasonable size for the image. - If you set this, you should set it large enough to hold the files you intend to copy into the image. The syntax is - the same as for the -size argument to hdiutil, i.e. you can use the suffixes `b`, `k`, `m`, `g`, `t`, `p` and `e` for - bytes, kilobytes, megabytes, gigabytes, terabytes, exabytes and petabytes respectively. + - If defined, specifies the size of the filesystem within the image. + If this is not defined, cx_Freeze (and then dmgbuild) will attempt to + determine a reasonable size for the image. If you set this, you should + set it large enough to hold the files you intend to copy into the image. + The syntax is the same as for the -size argument to hdiutil, i.e. you + can use the suffixes `b`, `k`, `m`, `g`, `t`, `p` and `e` for bytes, + kilobytes,megabytes, gigabytes, terabytes, exabytes and petabytes + respectively. * - .. option:: background - - A rgb color in the form #3344ff, svg named color like goldenrod, a path to an image, or the words 'builtin-arrow'. Default is None. + - A rgb color in the form #3344ff, svg named color like goldenrod, a path + to an image, or the words 'builtin-arrow'. Default is None. * - .. option:: show-status-bar - - Show the status bar in the Finder window. Default is False. + - Show the status bar in the Finder window. Default is False. * - .. option:: show-tab-view - - Show the tab view in the Finder window. Default is False. + - Show the tab view in the Finder window. Default is False. * - .. option:: show-path-bar - - Show the path bar in the Finder window. Default is False. + - Show the path bar in the Finder window. Default is False. * - .. option:: show-sidebar - - Show the sidebar in the Finder window. Default is False. + - Show the sidebar in the Finder window. Default is False. * - .. option:: sidebar-width - - Width of the sidebar in the Finder window. Default is None. + - Width of the sidebar in the Finder window. Default is None. * - .. option:: windows-rect - - Window rectangle in the form x,y,width,height" - The position of the window in ((x, y), (w, h)) format, with y co-ordinates running from bottom to top. The Finder - makes sure that the window will be on the user's display, so if you want your window at the top left of the display - you could use (0, 100000) as the x, y co-ordinates. Unfortunately it doesn't appear to be possible to position the - window relative to the top left or relative to the centre of the user's screen. + - Window rectangle in the form x,y,width,height" + The position of the window in ((x, y), (w, h)) format, with y co-ordinates + running from bottom to top. The Finder makes sure that the window will be + on the user's display, so if you want your window at the top left of the + display you could use (0, 100000) as the x, y co-ordinates. Unfortunately + it doesn't appear to be possible to position the window relative to the top + left or relative to the centre of the user's screen. * - .. option:: icon-locations - - A dictionary specifying the co-ordinates of items in the root directory of the disk image, where the keys are filenames and the values are (x, y) tuples. For example, - icon-locations = { "Applications": (100, 100), "README.txt": (200, 100) } + - A dictionary specifying the co-ordinates of items in the root directory of + the disk image, where the keys are filenames and the values are (x, y) + tuples. For example, + icon-locations = { "Applications": (100, 100), "README.txt": (200, 100) } * - .. option:: default-view - - The default view of the Finder window. Possible values are "icon-view", "list-view", "column-view", "coverflow". + - The default view of the Finder window. Possible values are + "icon-view", "list-view", "column-view", "coverflow". * - .. option:: show-icon-preview - - Show icon preview in the Finder window. Default is False. + - Show icon preview in the Finder window. Default is False. * - .. option:: license - - Dictionary specifying license details with 'default-language', 'licenses', and 'buttons'. - default-language: Language code (e.g., 'en_US') if no matching system language. - licenses: Map of language codes to license file paths (e.g., {'en_US': 'path/to/license_en.txt'}). - buttons: Map of language codes to UI strings ([language, agree, disagree, print, save, instruction]). - Example: {'default-language': 'en_US', 'licenses': {'en_US': 'path/to/license_en.txt'}, 'buttons': {'en_US': ['English', 'Agree', 'Disagree', 'Print', 'Save', 'Instruction text']}} + - Dictionary specifying license details with 'default-language', 'licenses', and + 'buttons'. + + default-language: Language code (e.g., 'en_US') if no matching system + language. + licenses: Map of language codes to license file paths + (e.g., {'en_US': 'path/to/license_en.txt'}). + buttons: Map of language codes to UI strings + ([language, agree, disagree, print, save, instruction]). + Example: {'default-language': 'en_US', 'licenses': {'en_US': 'path/to/license_en.txt'}, + 'buttons': {'en_US': ['English', 'Agree', 'Disagree', 'Print', 'Save', + 'Instruction text']}} This is the equivalent help to specify the same options on the command line: From cffc18b7d917b46eb2c26d087133e9cb64295064 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 22 Jun 2024 02:39:07 +0000 Subject: [PATCH 21/24] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/src/bdist_dmg.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/bdist_dmg.rst b/doc/src/bdist_dmg.rst index ecea6751d..116423c5b 100644 --- a/doc/src/bdist_dmg.rst +++ b/doc/src/bdist_dmg.rst @@ -56,7 +56,7 @@ installation. * - .. option:: icon-locations - A dictionary specifying the co-ordinates of items in the root directory of the disk image, where the keys are filenames and the values are (x, y) - tuples. For example, + tuples. For example, icon-locations = { "Applications": (100, 100), "README.txt": (200, 100) } * - .. option:: default-view - The default view of the Finder window. Possible values are From 6c9f303d2b97648053965646959626973d0f16b3 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 25 Jun 2024 14:10:36 -0500 Subject: [PATCH 22/24] feat(bdist_dmg): add more tests + docs --- doc/src/bdist_dmg.rst | 3 +++ samples/dmg_layout/hello.py | 21 ++++++++++++++++ samples/dmg_layout/setup.py | 44 +++++++++++++++++++++++++++++++++ tests/test_command_bdist_dmg.py | 24 ++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 samples/dmg_layout/hello.py create mode 100644 samples/dmg_layout/setup.py diff --git a/doc/src/bdist_dmg.rst b/doc/src/bdist_dmg.rst index 116423c5b..4cd487440 100644 --- a/doc/src/bdist_dmg.rst +++ b/doc/src/bdist_dmg.rst @@ -78,6 +78,9 @@ installation. 'Instruction text']}} +The above options come from the `dmgbuild` package. For more information, see +the `dmgbuild documentation `_. + This is the equivalent help to specify the same options on the command line: .. code-block:: console diff --git a/samples/dmg_layout/hello.py b/samples/dmg_layout/hello.py new file mode 100644 index 000000000..080a9fed2 --- /dev/null +++ b/samples/dmg_layout/hello.py @@ -0,0 +1,21 @@ +import sys +from datetime import datetime, timezone + +today = datetime.now(tz=timezone.utc) +print("Hello from cx_Freeze") +print(f"The current date is {today:%B %d, %Y %H:%M:%S}\n") + +print(f"Executable: {sys.executable}") +print(f"Prefix: {sys.prefix}") +print(f"Default encoding: {sys.getdefaultencoding()}") +print(f"File system encoding: {sys.getfilesystemencoding()}\n") + +print("ARGUMENTS:") +for arg in sys.argv: + print(f"{arg}") +print() + +print("PATH:") +for path in sys.path: + print(f"{path}") +print() diff --git a/samples/dmg_layout/setup.py b/samples/dmg_layout/setup.py new file mode 100644 index 000000000..a94a877ce --- /dev/null +++ b/samples/dmg_layout/setup.py @@ -0,0 +1,44 @@ +"""A very simple setup script to create a single executable. + +hello.py is a very simple 'Hello, world' type script which also displays the +environment in which the script runs. + +Run the build process by running the command 'python setup.py build' + +If everything works well you should find a subdirectory in the build +subdirectory that contains the files needed to run the script without Python +""" + +from cx_Freeze import Executable, setup + +executables = [ + Executable( + script="hello.py", + # You can also specify an icon for the executable that will be reused for the dmg + # only the first executable is used for the icon + # icon="../../cx_Freeze/icons/python.icns" #noqa: ERA001 + ) +] + +setup( + name="hello", + version="0.1.2.3", + description="Sample cx_Freeze script", + executables=executables, + options={ + "bdist_mac": { + "bundle_name": "hello", + }, + "bdist_dmg": { + "applications_shortcut": True, + "volume_label": "Howdy Yall", + # from the svg color list, but all of these work too https://dmgbuild.readthedocs.io/en/latest/settings.html#background + "background": "darkviolet", + "show_status_bar": True, + "show_tab_view": True, + "show_path_bar": True, + "show_sidebar": True, + "sidebar_width": 150, + }, + }, +) diff --git a/tests/test_command_bdist_dmg.py b/tests/test_command_bdist_dmg.py index 41b37afc6..6287a5e4a 100644 --- a/tests/test_command_bdist_dmg.py +++ b/tests/test_command_bdist_dmg.py @@ -40,3 +40,27 @@ def test_bdist_dmg(datafiles: Path) -> None: file_created = dist_created / f"{name}.dmg" assert file_created.is_file(), f"{name}.dmg" + + +@pytest.mark.datafiles(SAMPLES_DIR / "dmg_layout") +def test_bdist_dmg_custom_layout(datafiles: Path) -> None: + """Test the simple sample with bdist_dmg.""" + name = "Howdy Yall" + dist_created = datafiles / "build" + + process = run( + [sys.executable, "setup.py", "bdist_dmg"], + text=True, + capture_output=True, + check=False, + cwd=datafiles, + ) + if process.returncode != 0: + expected_err = "hdiutil: create failed - Resource busy" + if expected_err in process.stderr: + pytest.xfail(expected_err) + else: + pytest.fail(process.stderr) + + file_created = dist_created / f"{name}.dmg" + assert file_created.is_file(), f"{name}.dmg" From cf02ba47f9f9969ca9a9782018e4b6965bfa9d60 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 25 Jun 2024 22:08:11 -0500 Subject: [PATCH 23/24] feat(bdist_dmg): more tests for coverage) --- samples/dmg_layout/hello2.py | 21 +++++++++++++++++++++ samples/dmg_layout/setup.py | 14 +++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 samples/dmg_layout/hello2.py diff --git a/samples/dmg_layout/hello2.py b/samples/dmg_layout/hello2.py new file mode 100644 index 000000000..080a9fed2 --- /dev/null +++ b/samples/dmg_layout/hello2.py @@ -0,0 +1,21 @@ +import sys +from datetime import datetime, timezone + +today = datetime.now(tz=timezone.utc) +print("Hello from cx_Freeze") +print(f"The current date is {today:%B %d, %Y %H:%M:%S}\n") + +print(f"Executable: {sys.executable}") +print(f"Prefix: {sys.prefix}") +print(f"Default encoding: {sys.getdefaultencoding()}") +print(f"File system encoding: {sys.getfilesystemencoding()}\n") + +print("ARGUMENTS:") +for arg in sys.argv: + print(f"{arg}") +print() + +print("PATH:") +for path in sys.path: + print(f"{path}") +print() diff --git a/samples/dmg_layout/setup.py b/samples/dmg_layout/setup.py index a94a877ce..e04142e9b 100644 --- a/samples/dmg_layout/setup.py +++ b/samples/dmg_layout/setup.py @@ -17,7 +17,8 @@ # You can also specify an icon for the executable that will be reused for the dmg # only the first executable is used for the icon # icon="../../cx_Freeze/icons/python.icns" #noqa: ERA001 - ) + ), + Executable(script="hello2.py"), ] setup( @@ -39,6 +40,17 @@ "show_path_bar": True, "show_sidebar": True, "sidebar_width": 150, + "silent": False, + "default_view": "icon-view", + "list_icon_size": 48, + "list_text_size": 12, + "list_scroll_position": (0, 0), + "list_columns": ["name", "size"], + "list_column_widths": {"name": 200, "size": 100}, + "list_column_sort_directions": { + "name": "ascending", + "size": "ascending", + }, }, }, ) From 5befc252dd4d30dd7da0f825e80ca08532f67462 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Wed, 26 Jun 2024 11:54:52 -0500 Subject: [PATCH 24/24] fix(docs): fix bdist_dmg docs --- doc/src/bdist_dmg.rst | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/doc/src/bdist_dmg.rst b/doc/src/bdist_dmg.rst index 4cd487440..4a100c9a9 100644 --- a/doc/src/bdist_dmg.rst +++ b/doc/src/bdist_dmg.rst @@ -35,33 +35,33 @@ installation. * - .. option:: background - A rgb color in the form #3344ff, svg named color like goldenrod, a path to an image, or the words 'builtin-arrow'. Default is None. - * - .. option:: show-status-bar + * - .. option:: show_status_bar - Show the status bar in the Finder window. Default is False. - * - .. option:: show-tab-view + * - .. option:: show_tab_view - Show the tab view in the Finder window. Default is False. - * - .. option:: show-path-bar + * - .. option:: show_path_bar - Show the path bar in the Finder window. Default is False. - * - .. option:: show-sidebar + * - .. option:: show_sidebar - Show the sidebar in the Finder window. Default is False. - * - .. option:: sidebar-width + * - .. option:: sidebar_width - Width of the sidebar in the Finder window. Default is None. - * - .. option:: windows-rect + * - .. option:: windows_rect - Window rectangle in the form x,y,width,height" - The position of the window in ((x, y), (w, h)) format, with y co-ordinates + The position of the window in ((x, y), (w, h)) format, with y coordinates running from bottom to top. The Finder makes sure that the window will be on the user's display, so if you want your window at the top left of the - display you could use (0, 100000) as the x, y co-ordinates. Unfortunately + display you could use (0, 100000) as the x, y coordinates. Unfortunately it doesn't appear to be possible to position the window relative to the top left or relative to the centre of the user's screen. - * - .. option:: icon-locations - - A dictionary specifying the co-ordinates of items in the root directory of + * - .. option:: icon_locations + - A dictionary specifying the coordinates of items in the root directory of the disk image, where the keys are filenames and the values are (x, y) tuples. For example, - icon-locations = { "Applications": (100, 100), "README.txt": (200, 100) } - * - .. option:: default-view + icon_locations = { "Applications": (100, 100), "README.txt": (200, 100) } + * - .. option:: default_view - The default view of the Finder window. Possible values are "icon-view", "list-view", "column-view", "coverflow". - * - .. option:: show-icon-preview + * - .. option:: show_icon_preview - Show icon preview in the Finder window. Default is False. * - .. option:: license - Dictionary specifying license details with 'default-language', 'licenses', and @@ -77,6 +77,11 @@ installation. 'buttons': {'en_US': ['English', 'Agree', 'Disagree', 'Print', 'Save', 'Instruction text']}} +.. versionadded:: 7.2 + ``format``, ``filesystem``, ``size``, ``background``, ``show_status_bar``, + ``show_tab_view``, ``show_path_bar``, ``show_sidebar``, ``sidebar_width``, + ``windows_rect``, ``icon_locations``, ``default_view``, ``show_icon_preview``, + ``license`` options. The above options come from the `dmgbuild` package. For more information, see the `dmgbuild documentation `_.