Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions CONSTRUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,31 +585,33 @@ shown before the license information, right after the introduction.
File can be plain text (.txt), rich text (.rtf) or HTML (.html). If
both `welcome_file` and `welcome_text` are provided, `welcome_file` takes precedence.

If the installer is for Windows and the welcome file type is nsi,
If the installer is for Windows EXE and the welcome file type is nsi,
it will use the nsi script to add in extra pages before the installer
begins the installation process.
begins the installation process. (Not supported for MSI installers.)

### `welcome_text`

If `installer_type` is `pkg` on macOS, this message will be
shown before the license information, right after the introduction.
If this key is missing, it defaults to a message about Anaconda Cloud.
You can disable it altogether so it defaults to the system message
if you set this key to `""` (empty string).
if you set this key to `""` (empty string). (Not supported for MSI installers.)

### `readme_file`

If `installer_type` is `pkg` on macOS, this message will be
shown before the license information, right after the welcome screen.
File can be plain text (.txt), rich text (.rtf) or HTML (.html). If
both `readme_file` and `readme_text` are provided, `readme_file` takes precedence.
(Not supported for MSI installers.)

### `readme_text`

If `installer_type` is `pkg` on macOS, this message will be
shown before the license information, right after the welcome screen.
If this key is missing, it defaults to a message about Anaconda Cloud.
You can disable it altogether if you set this key to `""` (empty string).
(Not supported for MSI installers.)

### `post_install_pages`

Expand All @@ -630,7 +632,8 @@ plain text (.txt), rich text (.rtf) or HTML (.html). If both
`conclusion_file` and `conclusion_text` are provided,
`conclusion_file` takes precedence.

If the installer is for Windows, the file type must be nsi.
If the installer is for Windows EXE, the file type must be nsi.
(Not supported for MSI installers.)

### `conclusion_text`

Expand All @@ -640,6 +643,7 @@ The behaviour is slightly different across installer types:
You can disable it altogether so it defaults to the system message if you set this
key to `""` (empty string).
- EXE: The first line will be used as a title. The following lines will be used as text.
- MSI: Not supported.

### `extra_files`

Expand Down
12 changes: 8 additions & 4 deletions constructor/_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,31 +755,33 @@ class ConstructorConfiguration(BaseModel):
File can be plain text (.txt), rich text (.rtf) or HTML (.html). If
both `welcome_file` and `welcome_text` are provided, `welcome_file` takes precedence.

If the installer is for Windows and the welcome file type is nsi,
If the installer is for Windows EXE and the welcome file type is nsi,
it will use the nsi script to add in extra pages before the installer
begins the installation process.
begins the installation process. (Not supported for MSI installers.)
Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See a886add

"""
welcome_text: str | None = None
"""
If `installer_type` is `pkg` on macOS, this message will be
shown before the license information, right after the introduction.
If this key is missing, it defaults to a message about Anaconda Cloud.
You can disable it altogether so it defaults to the system message
if you set this key to `""` (empty string).
if you set this key to `""` (empty string). (Not supported for MSI installers.)
"""
readme_file: NonEmptyStr | None = None
"""
If `installer_type` is `pkg` on macOS, this message will be
shown before the license information, right after the welcome screen.
File can be plain text (.txt), rich text (.rtf) or HTML (.html). If
both `readme_file` and `readme_text` are provided, `readme_file` takes precedence.
(Not supported for MSI installers.)
"""
readme_text: str | None = None
"""
If `installer_type` is `pkg` on macOS, this message will be
shown before the license information, right after the welcome screen.
If this key is missing, it defaults to a message about Anaconda Cloud.
You can disable it altogether if you set this key to `""` (empty string).
(Not supported for MSI installers.)
"""
post_install_pages: NonEmptyStr | list[NonEmptyStr] | None = None
"""
Expand All @@ -800,7 +802,8 @@ class ConstructorConfiguration(BaseModel):
`conclusion_file` and `conclusion_text` are provided,
`conclusion_file` takes precedence.

If the installer is for Windows, the file type must be nsi.
If the installer is for Windows EXE, the file type must be nsi.
(Not supported for MSI installers.)
"""
conclusion_text: str | None = None
"""
Expand All @@ -810,6 +813,7 @@ class ConstructorConfiguration(BaseModel):
You can disable it altogether so it defaults to the system message if you set this
key to `""` (empty string).
- EXE: The first line will be used as a title. The following lines will be used as text.
- MSI: Not supported.
"""
extra_files: list[NonEmptyStr | dict[NonEmptyStr, NonEmptyStr]] = []
"""
Expand Down
24 changes: 24 additions & 0 deletions constructor/briefcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the readme file is for PKG installers only anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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"),
}
},
}
Expand Down
12 changes: 6 additions & 6 deletions constructor/data/construct.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@
}
],
"default": null,
"description": "If `installer_type` is `pkg` on macOS, this message will be shown at the end of the installer upon success. File can be plain text (.txt), rich text (.rtf) or HTML (.html). If both `conclusion_file` and `conclusion_text` are provided, `conclusion_file` takes precedence.\nIf the installer is for Windows, the file type must be nsi.",
"description": "If `installer_type` is `pkg` on macOS, this message will be shown at the end of the installer upon success. File can be plain text (.txt), rich text (.rtf) or HTML (.html). If both `conclusion_file` and `conclusion_text` are provided, `conclusion_file` takes precedence.\nIf the installer is for Windows EXE, the file type must be nsi. (Not supported for MSI installers.)",
"title": "Conclusion File"
},
"conclusion_text": {
Expand All @@ -526,7 +526,7 @@
}
],
"default": null,
"description": "A message that will be shown at the end of the installer upon success. The behaviour is slightly different across installer types:\n- PKG: If this key is missing, it defaults to a message about Anaconda Cloud. You can disable it altogether so it defaults to the system message if you set this key to `\"\"` (empty string).\n- EXE: The first line will be used as a title. The following lines will be used as text.",
"description": "A message that will be shown at the end of the installer upon success. The behaviour is slightly different across installer types:\n- PKG: If this key is missing, it defaults to a message about Anaconda Cloud. You can disable it altogether so it defaults to the system message if you set this key to `\"\"` (empty string).\n- EXE: The first line will be used as a title. The following lines will be used as text.\n- MSI: Not supported.",
"title": "Conclusion Text"
},
"conda_channel_alias": {
Expand Down Expand Up @@ -1093,7 +1093,7 @@
}
],
"default": null,
"description": "If `installer_type` is `pkg` on macOS, this message will be shown before the license information, right after the welcome screen. File can be plain text (.txt), rich text (.rtf) or HTML (.html). If both `readme_file` and `readme_text` are provided, `readme_file` takes precedence.",
"description": "If `installer_type` is `pkg` on macOS, this message will be shown before the license information, right after the welcome screen. File can be plain text (.txt), rich text (.rtf) or HTML (.html). If both `readme_file` and `readme_text` are provided, `readme_file` takes precedence. (Not supported for MSI installers.)",
"title": "Readme File"
},
"readme_text": {
Expand All @@ -1106,7 +1106,7 @@
}
],
"default": null,
"description": "If `installer_type` is `pkg` on macOS, this message will be shown before the license information, right after the welcome screen. If this key is missing, it defaults to a message about Anaconda Cloud. You can disable it altogether if you set this key to `\"\"` (empty string).",
"description": "If `installer_type` is `pkg` on macOS, this message will be shown before the license information, right after the welcome screen. If this key is missing, it defaults to a message about Anaconda Cloud. You can disable it altogether if you set this key to `\"\"` (empty string). (Not supported for MSI installers.)",
"title": "Readme Text"
},
"register_envs": {
Expand Down Expand Up @@ -1310,7 +1310,7 @@
}
],
"default": null,
"description": "If `installer_type` is `pkg` on macOS, this message will be shown before the license information, right after the introduction. File can be plain text (.txt), rich text (.rtf) or HTML (.html). If both `welcome_file` and `welcome_text` are provided, `welcome_file` takes precedence.\nIf the installer is for Windows and the welcome file type is nsi, it will use the nsi script to add in extra pages before the installer begins the installation process.",
"description": "If `installer_type` is `pkg` on macOS, this message will be shown before the license information, right after the introduction. File can be plain text (.txt), rich text (.rtf) or HTML (.html). If both `welcome_file` and `welcome_text` are provided, `welcome_file` takes precedence.\nIf the installer is for Windows EXE and the welcome file type is nsi, it will use the nsi script to add in extra pages before the installer begins the installation process. (Not supported for MSI installers.)",
"title": "Welcome File"
},
"welcome_image": {
Expand Down Expand Up @@ -1350,7 +1350,7 @@
}
],
"default": null,
"description": "If `installer_type` is `pkg` on macOS, this message will be shown before the license information, right after the introduction. If this key is missing, it defaults to a message about Anaconda Cloud. You can disable it altogether so it defaults to the system message if you set this key to `\"\"` (empty string).",
"description": "If `installer_type` is `pkg` on macOS, this message will be shown before the license information, right after the introduction. If this key is missing, it defaults to a message about Anaconda Cloud. You can disable it altogether so it defaults to the system message if you set this key to `\"\"` (empty string). (Not supported for MSI installers.)",
"title": "Welcome Text"
},
"windows_signing_tool": {
Expand Down
49 changes: 31 additions & 18 deletions constructor/imaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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":
Comment thread
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),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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"
Expand All @@ -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, ".")
4 changes: 2 additions & 2 deletions constructor/osxpkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,10 @@ def modify_xml(xml_path, info):
if not info["welcome_image"]:
background_path = None
else:
write_images(info, PACKAGES_DIR, os="osx")
write_images(info, PACKAGES_DIR, installer_type="pkg")
background_path = os.path.join(PACKAGES_DIR, "welcome.png")
elif "welcome_image_text" in info:
write_images(info, PACKAGES_DIR, os="osx")
write_images(info, PACKAGES_DIR, installer_type="pkg")
background_path = os.path.join(PACKAGES_DIR, "welcome.png")
else:
# Default to Anaconda's logo if the keys above were not specified
Expand Down
12 changes: 8 additions & 4 deletions docs/source/construct-yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,31 +585,33 @@ shown before the license information, right after the introduction.
File can be plain text (.txt), rich text (.rtf) or HTML (.html). If
both `welcome_file` and `welcome_text` are provided, `welcome_file` takes precedence.

If the installer is for Windows and the welcome file type is nsi,
If the installer is for Windows EXE and the welcome file type is nsi,
it will use the nsi script to add in extra pages before the installer
begins the installation process.
begins the installation process. (Not supported for MSI installers.)

### `welcome_text`

If `installer_type` is `pkg` on macOS, this message will be
shown before the license information, right after the introduction.
If this key is missing, it defaults to a message about Anaconda Cloud.
You can disable it altogether so it defaults to the system message
if you set this key to `""` (empty string).
if you set this key to `""` (empty string). (Not supported for MSI installers.)

### `readme_file`

If `installer_type` is `pkg` on macOS, this message will be
shown before the license information, right after the welcome screen.
File can be plain text (.txt), rich text (.rtf) or HTML (.html). If
both `readme_file` and `readme_text` are provided, `readme_file` takes precedence.
(Not supported for MSI installers.)

### `readme_text`

If `installer_type` is `pkg` on macOS, this message will be
shown before the license information, right after the welcome screen.
If this key is missing, it defaults to a message about Anaconda Cloud.
You can disable it altogether if you set this key to `""` (empty string).
(Not supported for MSI installers.)

### `post_install_pages`

Expand All @@ -630,7 +632,8 @@ plain text (.txt), rich text (.rtf) or HTML (.html). If both
`conclusion_file` and `conclusion_text` are provided,
`conclusion_file` takes precedence.

If the installer is for Windows, the file type must be nsi.
If the installer is for Windows EXE, the file type must be nsi.
(Not supported for MSI installers.)

### `conclusion_text`

Expand All @@ -640,6 +643,7 @@ The behaviour is slightly different across installer types:
You can disable it altogether so it defaults to the system message if you set this
key to `""` (empty string).
- EXE: The first line will be used as a title. The following lines will be used as text.
- MSI: Not supported.

### `extra_files`

Expand Down
Binary file removed examples/miniconda/bird.png
Binary file not shown.
19 changes: 19 additions & 0 deletions news/1235-msi-branding
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>
Loading
Loading