-
Notifications
You must be signed in to change notification settings - Fork 178
MSI: Add support for installer branding #1235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: briefcase-integration
Are you sure you want to change the base?
Changes from 10 commits
3127731
f86250b
c99574b
651d970
3ceb47d
cefbf5b
1082e19
36d912c
52b5eb4
9db0b08
a886add
3af4b48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,8 +17,11 @@ | |
| IS_WINDOWS = sys.platform == "win32" | ||
| if IS_WINDOWS: | ||
| import tomli_w | ||
|
|
||
| from .imaging import write_images | ||
| else: | ||
| tomli_w = None # This file is only intended for Windows use | ||
| write_images = None # imaging.py requires PIL, which is only available on Windows | ||
|
|
||
| from . import preconda | ||
| from .jinja import render_template | ||
|
|
@@ -36,6 +39,13 @@ | |
| BRIEFCASE_DIR = Path(__file__).parent / "briefcase" | ||
| EXTERNAL_PACKAGE_PATH = "external" | ||
|
|
||
| # MSI Branding Limitations: | ||
| # The following EXE branding options are not supported for MSI installers | ||
| # because they require modifications to the WiX template in briefcase-windows-app-template: | ||
| # - welcome_file / welcome_text (custom welcome page text) | ||
| # - readme_file / readme_text (readme page) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the readme file is for PKG installers only anyway.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| # - conclusion_file / conclusion_text (finish page text) | ||
|
|
||
| # Default to a low version, so that if a valid version is provided in the future, it'll | ||
| # be treated as an upgrade. | ||
| DEFAULT_VERSION = "0.0.1" | ||
|
|
@@ -375,6 +385,15 @@ def prepare(self) -> None: | |
| external_dir = self.root / EXTERNAL_PACKAGE_PATH | ||
| external_dir.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| # Generate branding images for MSI installer | ||
| write_images(self.info, external_dir, installer_type="msi") | ||
|
|
||
| # Verify all branding images were generated | ||
| for image_name in ("welcome.bmp", "header.bmp", "icon.ico"): | ||
| image_path = external_dir / image_name | ||
| if not image_path.exists(): | ||
| raise RuntimeError(f"Failed to generate branding image: {image_path}") | ||
|
|
||
| # Note that the directory name "base" is also explicitly defined in `run_installation.bat` | ||
| base_dir = external_dir / "base" | ||
| base_dir.mkdir() | ||
|
|
@@ -512,6 +531,11 @@ def write_pyproject_toml(self, root: Path, external: Path) -> None: | |
| "uninstall_option": create_uninstall_options_list(self.info), | ||
| "post_install_script": str(root / "run_installation.bat"), | ||
| "pre_uninstall_script": str(root / "pre_uninstall.bat"), | ||
| # Branding images (generated by write_images in prepare()) | ||
| "installer_background": str(external / "welcome.bmp"), | ||
| "installer_banner": str(external / "header.bmp"), | ||
| # Briefcase expects icon path WITHOUT extension - it appends .ico | ||
| "icon": str(external / "icon"), | ||
| } | ||
| }, | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,11 @@ | |
| icon_size = 256, 256 | ||
| # These are for OSX | ||
| welcome_size_osx = 1227, 600 | ||
| # MSI/WiX image sizes | ||
| # TODO: MSI welcome/header have different aspect ratios than EXE (landscape vs portrait). | ||
| # Auto-resize will stretch images. May need to revisit scaling strategy if results are poor. | ||
| welcome_size_msi = (493, 312) | ||
| header_size_msi = (493, 58) | ||
|
|
||
|
|
||
| def new_background(size, color, bs=20, boxes=50): | ||
|
|
@@ -51,9 +56,9 @@ def add_text(im, xy, text, min_lines, line_height, font, color): | |
| return d | ||
|
|
||
|
|
||
| def mk_welcome_image(info): | ||
| def mk_welcome_image(info, size=welcome_size): | ||
| font = ImageFont.truetype(BytesIO(ttf_bytes), 20) | ||
| im = new_background(welcome_size, info["_color"]) | ||
| im = new_background(size, info["_color"]) | ||
| text = "\n".join([info["welcome_image_text"], info["version"]]) | ||
| add_text(im, (20, 100), text, 2, 30, font, white) | ||
| return im | ||
|
|
@@ -68,9 +73,9 @@ def mk_welcome_image_osx(info): | |
| return im | ||
|
|
||
|
|
||
| def mk_header_image(info): | ||
| def mk_header_image(info, size=header_size): | ||
| font = ImageFont.truetype(BytesIO(ttf_bytes), 20) | ||
| im = Image.new("RGB", header_size, color=white) | ||
| im = Image.new("RGB", size, color=white) | ||
| text = info["header_image_text"] | ||
| color = info["_color"] | ||
| add_text(im, (20, 15), text, 1, 20, font, color) | ||
|
|
@@ -99,19 +104,37 @@ def add_color_info(info): | |
| sys.exit("Error: color '%s' not defined" % color_name) | ||
|
|
||
|
|
||
| def write_images(info, dir_path, os="windows"): | ||
| if os == "windows": | ||
| def write_images(info, dir_path, installer_type="exe"): | ||
| if installer_type == "exe": | ||
|
lrandersson marked this conversation as resolved.
|
||
| instructions = [ | ||
| ("welcome", welcome_size, mk_welcome_image, ".bmp"), | ||
| ("header", header_size, mk_header_image, ".bmp"), | ||
| ("icon", icon_size, mk_icon_image, ".ico"), | ||
| ] | ||
| elif os == "osx": | ||
| elif installer_type == "pkg": | ||
| instructions = [ | ||
| ("welcome", welcome_size_osx, mk_welcome_image_osx, ".png"), | ||
| ] | ||
| elif installer_type == "msi": | ||
| instructions = [ | ||
| ( | ||
| "welcome", | ||
| welcome_size_msi, | ||
| lambda info: mk_welcome_image(info, welcome_size_msi), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the EXE installers also use lambda functions so that we have more explicit names for variables?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The lambdas are only needed for MSI because it passes sizes different from default. EXE uses default parameters, so lambdas would be redundant. I could change it just for consistency but provides with no functional benefit, let me know what you think.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's the other way round: the default parameters are the EXE parameters. Making it a lambda would make this more explicit, but that's a minor suggestion. |
||
| ".bmp", | ||
| ), | ||
| ( | ||
| "header", | ||
| header_size_msi, | ||
| lambda info: mk_header_image(info, header_size_msi), | ||
| ".bmp", | ||
| ), | ||
| ("icon", icon_size, mk_icon_image, ".ico"), | ||
| ] | ||
| else: | ||
| raise ValueError(f"OS {os} not supported. Choose `windows` or `osx`.") | ||
| raise ValueError( | ||
| f"Installer type '{installer_type}' not supported. Choose 'exe', 'pkg', or 'msi'." | ||
| ) | ||
|
|
||
| for name, size, function, ext in instructions: | ||
| key = name + "_image" | ||
|
|
@@ -123,13 +146,3 @@ def write_images(info, dir_path, os="windows"): | |
| im = function(info) | ||
| assert im.size == size | ||
| im.save(join(dir_path, name + ext)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| info = { | ||
| "name": "test", | ||
| "version": "0.3.1", | ||
| "default_image_color": "yellow", | ||
| "welcome_image": "../examples/miniconda/bird.png", | ||
| } | ||
| write_images(info, ".") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| ### Enhancements | ||
|
|
||
| * MSI: Add branding image support (`welcome_image`, `header_image`, `icon_image`). (#1235) | ||
|
|
||
| ### Bug fixes | ||
|
|
||
| * <news item> | ||
|
|
||
| ### Deprecations | ||
|
|
||
| * <news item> | ||
|
|
||
| ### Docs | ||
|
|
||
| * MSI: Document that text branding options (`welcome_file`, `welcome_text`, `readme_file`, `readme_text`, `conclusion_file`, `conclusion_text`) are not supported. (#1235) | ||
|
|
||
| ### Other | ||
|
|
||
| * <news item> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to add all the items that are not supported for MSI installers. You are being explicit with the supported formats already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See a886add