Skip to content

Conversation

@mhsmith
Copy link

@mhsmith mhsmith commented Oct 9, 2025

Description

Initial work towards:

Requires Briefcase to be installed in the environment, including the following updates:

As discussed, this PR is sufficient to build a simple MSI installer (with no install-time options) from the miniconda3 recipe. After the Briefcase updates are merged, I'll post a comment here with instructions on how to do that.

@freakboy3742: FYI

Checklist - did you ...

  • Add a file to the news directory (using the template) for the next release's release notes?
  • Add / update necessary tests?
  • Add / update outdated documentation?

@conda-bot
Copy link
Contributor

We require contributors to sign our Contributor License Agreement and we don't have one on file for @mhsmith.

In order for us to review and merge your code, please e-sign the Contributor License Agreement PDF. We then need to manually verify your signature, merge the PR (conda/infrastructure#1222), and ping the bot to refresh the PR.

@travishathaway
Copy link
Contributor

@conda-bot check

@conda-bot conda-bot added the cla-signed [bot] added once the contributor has signed the CLA label Oct 9, 2025
msi_paths = list(dist_dir.glob("*.msi"))
if len(msi_paths) != 1:
raise RuntimeError(f"Found {len(msi_paths)} MSI files in {dist_dir}")
shutil.copy(msi_paths[0], info["_outpath"])
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be a move command instead? In heavy installers and reduced disk storage availability, it might be the difference between success and error.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

"enum": [
"all",
"exe",
"msi",
Copy link
Contributor

Choose a reason for hiding this comment

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

This change should be applied in _schema.py and then propagated.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

We should also have an integration test in test_examples.py once the dependencies are ready.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think most integration tests have installer_type: all, which will break if features are missing. I suggest replacing them with installer_type: {{ 'exe' if sys.platform == 'win32' else 'all' for those that fail because of missing features. That will also give us a good idea about when we have feature parity.

Copy link
Contributor

Choose a reason for hiding this comment

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

@mhsmith just for completeness, I've added (and working on finalizing it) the integration tests in test_examples.py in this branch: https://github.com/lrandersson/constructor/tree/briefcase
I branched from the branch that's in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

@marcoesters marcoesters changed the base branch from main to briefcase-integration October 14, 2025 15:01
@mhsmith
Copy link
Author

mhsmith commented Oct 29, 2025

Testing instructions

Check out the following repositories:

Run these commands:

conda env create -f constructor\dev\environment.yml
conda activate constructor-dev
pip install -e .\constructor .\briefcase
set INSTALLER_VERSION=25.9.0-99  # or any other build number
set PY_VER=3.13
set EXT=msi
constructor installers\products\miniconda3\recipe

The MSI will be generated in the working directory.

@mhsmith
Copy link
Author

mhsmith commented Oct 30, 2025

Remaining features required for production use

[Moved to #967]

Copy link
Contributor

@marcoesters marcoesters left a comment

Choose a reason for hiding this comment

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

My biggest change request is to get the dependencies right. Specifically, briefcase is missing from the list. I'm also a little concerned about how tightly this is bound to Python conventions.

Comment on lines 25 to 27
name = info["name"]
if not name:
raise ValueError("Name is empty")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
name = info["name"]
if not name:
raise ValueError("Name is empty")
if not (name := info.get("name")):
raise ValueError("Name is empty")

If we are concerned about empty values, we should use get, too.

Copy link
Contributor

Choose a reason for hiding this comment

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

I fixed it in mhsmith#1

Copy link
Author

Choose a reason for hiding this comment

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

I've copied that fix to this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think most integration tests have installer_type: all, which will break if features are missing. I suggest replacing them with installer_type: {{ 'exe' if sys.platform == 'win32' else 'all' for those that fail because of missing features. That will also give us a good idea about when we have feature parity.

if not name:
raise ValueError("Name is empty")

# Briefcase requires version numbers to be in the canonical Python format, and some
Copy link
Contributor

Choose a reason for hiding this comment

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

Python is not fully compatible with SemVer, so that could be a pretty significant limitation.

It will at least require a few version changes in our integration test examples:

Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed temporarily in mhsmith#1

Copy link
Author

@mhsmith mhsmith Dec 8, 2025

Choose a reason for hiding this comment

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

As far as MSI is concerned, the version number is only used for 2 purposes:

  • Display in the installed apps list. This is just for the user's information, so I think it's acceptable if some of the construct.yaml version gets moved into the name for display purposes, as long as all the information is still present.

  • Blocking the installer of an old version if a new version is already installed. Since constructor itself doesn't define any version ordering rules, it's impossible to do this perfectly for all version schemes. The current code covers a reasonably large number of cases, but it could be extended if necessary.

Notice the current code only uses the last valid Python package version number it finds. This is to accommodate the Miniconda construct.yaml file, which sets version to something like py313_25.1.2-3. It's better to transform this into 25.1.2.3 rather than 313.25.1.2.3.

Copy link
Author

@mhsmith mhsmith Dec 8, 2025

Choose a reason for hiding this comment

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

For versions with no numbers in them at all, we could either reject them as the PR currently does, or display a warning and fall back to a default like 0.0.1 or 1.0.0. That's probably a good idea, since it would allow all existing construct.yaml files to at least build, and the integration tests wouldn't need to change so much.

Copy link
Author

Choose a reason for hiding this comment

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

I've made it use a default of 0.0.1, so that if a valid version is added in the future, it'll be treated as an upgrade.

strip_chars = " .-_"
before = info["version"][:start].strip(strip_chars)
after = info["version"][end:].strip(strip_chars)
name = " ".join(s for s in [name, before, after] if s)
Copy link
Contributor

Choose a reason for hiding this comment

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

So, for something like Miniconda3-py310_25.9.1-3-Windows-x86_64.msi, we have the following?

name = Miniconda3 py310
version = 25.9.1.3

Copy link
Author

@mhsmith mhsmith Dec 8, 2025

Choose a reason for hiding this comment

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

Yes, those would be the name and version from Briefcase's point of view, and that's how they'd be displayed in the Windows apps list.

The current code can generate these values from a construct.yaml file where py310 is part of the version. There's an example of that in test_name_version in test_briefcase.py.

# different versions of a product.
def get_bundle_app_name(info, name):
# If reverse_domain_identifier is provided, use it as-is, but verify that the last
# component is a valid Python package name, as Briefcase requires.
Copy link
Contributor

Choose a reason for hiding this comment

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

Since constructor doesn't require Python-based packages, will we run into issues with non-Python namespaces?

Copy link
Author

Choose a reason for hiding this comment

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

As with the version, I guess we could make a best effort to transform it into something valid. In the case of MSI, this value is only used internally by Briefcase and is never visible to the user. So it doesn't matter if it's slightly different to the construct.yaml value, as long as it's consistent.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

Comment on lines 135 to 141
briefcase = Path(sysconfig.get_path("scripts")) / "briefcase.exe"
logger.info("Building installer")
run(
[briefcase, "package"] + (["-v"] if verbose else []),
cwd=tmp_dir,
check=True,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

I have two questions here:

  1. Is it possible to use briefcase as an API rather than a CLI?
  2. This looks like Windows - do we need any guards here?

Either way, we should make sure that briefcase.exe exists before calling it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I added 2) in mhsmith#1, the check for briefcase.exe is also there

Copy link
Author

@mhsmith mhsmith Dec 8, 2025

Choose a reason for hiding this comment

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

  1. No, the CLI is the only stable interface.
  2. The check in mhsmith#1 looks good to me. The fact that we're running on WIndows is already guarded by os_allowed in main.py.

Copy link
Author

Choose a reason for hiding this comment

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

I've copied the briefcase.exe check to this PR.

@@ -0,0 +1,9 @@
_conda constructor --prefix . --extract-conda-pkgs
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use absolute paths instead of relative paths.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

pyproject.toml Outdated
"jinja2",
"jsonschema >=4"
"jsonschema >=4",
"tomli-w >=1.2.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is only needed for MSI installers, this should probably only be added for Windows - same in the recipe.

This is also missing briefcase as a dependency.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

I've updated pyproject.toml, meta.yaml and environment.yml. A new enough version of Briefcase is now on conda-forge, so it no longer needs to be installed manually.

Copy link
Author

Choose a reason for hiding this comment

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

Briefcase 0.3.26 also has a couple of new MSI features:

  • Interactive selection of the install directory.
  • Uninstaller options – though as it says here, these currently only appear when you choose "Modify" from the app list rather than "Uninstall", so we'll need to revisit this in the future.

@github-project-automation github-project-automation bot moved this from 🆕 New to 🏗️ In Progress in 🔎 Review Nov 13, 2025
@mhsmith
Copy link
Author

mhsmith commented Nov 19, 2025

To keep all the discussion in one place, I've merged mhsmith#1 into this PR, and sent an invite to @lrandersson to give him access to to push to the branch directly.

@mhsmith
Copy link
Author

mhsmith commented Dec 8, 2025

On second thoughts, this PR is getting big enough already, and I'm probably not the best person to review the subsequent PRs. So I'll un-merge mhsmith#1 from this PR, and I suggest we proceed as follows:

  • I update this PR to deal with the existing comments, so we can merge it into the briefcase-integration branch.
  • Robin re-creates mhsmith#1 and mhsmith#2 as PRs in conda/constructor, targeted at the briefcase-integration branch.

@mhsmith mhsmith requested a review from marcoesters December 9, 2025 14:27
@lrandersson lrandersson mentioned this pull request Dec 9, 2025
3 tasks
Comment on lines 13 to 14
- briefcase >=0.3.26 # [win]
- tomli-w >=1.2.0 # [win]
Copy link
Contributor

Choose a reason for hiding this comment

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

Environment YAML files do not recognize these selectors as far as I know. the pillow line doesn't make sense. I'd rather remove those Windows specific dependencies here.

Those dependencies should go into extra-requirements-windows.txt

Copy link
Author

Choose a reason for hiding this comment

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

OK, I've moved the 2 new requirements to extra-requirements-windows.txt. The pillow line was already there, so I'll leave it alone to avoid conflict with any fix on the main branch.

@mhsmith mhsmith requested a review from marcoesters December 17, 2025 13:25
@github-project-automation github-project-automation bot moved this from 🏗️ In Progress to ✅ Approved in 🔎 Review Dec 17, 2025
@marcoesters marcoesters merged commit 26ee3bb into conda:briefcase-integration Dec 17, 2025
3 checks passed
@github-project-automation github-project-automation bot moved this from ✅ Approved to 🏁 Done in 🔎 Review Dec 17, 2025
marcoesters added a commit that referenced this pull request Dec 19, 2025
* Initial prototype as shown in demo

* Switch to install_launcher option

* Update schema properly

* Move MSI file rather than copying it

* Add fallbacks for invalid versions and app names

* Use absolute paths in install script

* Check that briefcase.exe exists

* Add briefcase to dependencies, and make it and tomli-w Windows-only

* Move Windows-specific dependencies from environment.yml to extra-requirements-windows.txt

---------

Co-authored-by: Marco Esters <mesters@anaconda.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed [bot] added once the contributor has signed the CLA

Projects

Status: 🏁 Done

Development

Successfully merging this pull request may close these issues.

6 participants