Repository: beeware/briefcase

Issue:
User installable MSI packages?
The Mu Editor MSI file I managed to produce in the context of #381 requires administrative privileges for installation.

It would be very nice if `briefcase` had the option to produce user installable MSI files (assuming there is such a thing -- I suppose/hope so, given that lots of software is user installable, nowadays). If not, that could be considered a user-experience regression with respect to Mu users...

Which of user-install/system-install should be the default is a secondary thing for now.

Thoughts?

Comments:

Patch:
diff --git a/changes/382.feature.rst b/changes/382.feature.rst
new file mode 100644
index 00000000..3cc4e55e
--- /dev/null
+++ b/changes/382.feature.rst
@@ -0,0 +1,1 @@
+Windows MSI installers can now be configured to ask the user whether they want a per-user or per-machine install.
diff --git a/changes/688.bugfix.rst b/changes/688.bugfix.rst
new file mode 100644
index 00000000..a7f2db90
--- /dev/null
+++ b/changes/688.bugfix.rst
@@ -0,0 +1,1 @@
+Windows MSI installers now install in ``Program Files``, rather than ``Program Files (x86)``.
diff --git a/changes/774.feature.rst b/changes/774.feature.rst
new file mode 100644
index 00000000..246c2e4b
--- /dev/null
+++ b/changes/774.feature.rst
@@ -0,0 +1,1 @@
+Temporary docker containers are now cleaned up after use. The wording of Docker progress messages has also been improved.
diff --git a/docs/reference/platforms/windows/msi.rst b/docs/reference/platforms/windows/msi.rst
index 870611e6..d767d4ae 100644
--- a/docs/reference/platforms/windows/msi.rst
+++ b/docs/reference/platforms/windows/msi.rst
@@ -43,7 +43,8 @@ installer; however, they are installed once and shared between all users on a
 computer.
 
 If ``true`` the installer will attempt to install the app as a per-machine app,
-available to all users. Defaults to a per-user install.
+available to all users. If ``false``, the installer will install as a per-user
+app. If undefined the installer will ask the user for their preference.
 
 ``version_triple``
 ------------------
diff --git a/src/briefcase/integrations/docker.py b/src/briefcase/integrations/docker.py
index a05cd19b..90a921f5 100644
--- a/src/briefcase/integrations/docker.py
+++ b/src/briefcase/integrations/docker.py
@@ -202,14 +202,15 @@ class Docker:
     def prepare(self):
         try:
             self.command.logger.info(
-                "Building Docker container image...", prefix=self.app.app_name
+                "Building Docker container image...",
+                prefix=self.app.app_name,
             )
             try:
                 system_requires = " ".join(self.app.system_requires)
             except AttributeError:
                 system_requires = ""
 
-            with self.command.input.wait_bar("Building container..."):
+            with self.command.input.wait_bar("Building Docker image..."):
                 self._subprocess.run(
                     [
                         "docker",
@@ -237,23 +238,24 @@ class Docker:
                 )
         except subprocess.CalledProcessError as e:
             raise BriefcaseCommandError(
-                f"Error building Docker container for {self.app.app_name}."
+                f"Error building Docker image for {self.app.app_name}."
             ) from e
 
     def run(self, args, env=None, **kwargs):
-        """Run a process inside the Docker container."""
-        # Set up the `docker run` invocation in interactive mode,
-        # with volume mounts for the platform and .briefcase directories.
-        # The :z suffix allows SELinux to modify the host mount; it is ignored
-        # on non-SELinux platforms.
+        """Run a process inside a Docker container."""
+        # Set up the `docker run` with volume mounts for the platform &
+        # .briefcase directories and to delete the temporary container
+        # after running the command.
+        # The :z suffix allows SELinux to modify the host mount; it is
+        # ignored on non-SELinux platforms.
         docker_args = [
             "docker",
             "run",
-            "--tty",
             "--volume",
             f"{self.command.platform_path}:/app:z",
             "--volume",
             f"{self.command.dot_briefcase_path}:/home/brutus/.briefcase:z",
+            "--rm",
         ]
 
         # If any environment variables have been defined, pass them in
@@ -262,10 +264,10 @@ class Docker:
             for key, value in env.items():
                 docker_args.extend(["--env", f"{key}={value}"])
 
-        # ... then the image name.
+        # ... then the image name to create the temporary container with
         docker_args.append(self.command.docker_image_tag(self.app))
 
-        # ... then add all the arguments
+        # ... then add the command (and its arguments) to run in the container
         for arg in args:
             arg = str(arg)
             if arg == sys.executable:
diff --git a/src/briefcase/platforms/windows/msi.py b/src/briefcase/platforms/windows/msi.py
index 0023f89d..204a48f7 100644
--- a/src/briefcase/platforms/windows/msi.py
+++ b/src/briefcase/platforms/windows/msi.py
@@ -77,13 +77,17 @@ class WindowsMSICreateCommand(WindowsMSIMixin, CreateCommand):
         try:
             install_scope = "perMachine" if app.system_installer else "perUser"
         except AttributeError:
-            # system_installer not defined in config; default to perUser install.
-            install_scope = "perUser"
+            # system_installer not defined in config; default to asking the user
+            install_scope = None
 
         return {
             "version_triple": version_triple,
             "guid": str(guid),
             "install_scope": install_scope,
+            # Template forward compatibility flags
+            # 2022-06-29: #775 added the need to pass for -arch 64 to candle.exe;
+            # Briefcase v0.3.8 didn't use that flag.
+            "_use_arch64": True,
         }
 
     def install_app_support_package(self, app: BaseConfig):
@@ -187,6 +191,8 @@ class WindowsMSIPackageCommand(WindowsMSIMixin, PackageCommand):
                         "WixUtilExtension",
                         "-ext",
                         "WixUIExtension",
+                        "-arch",
+                        "x64",
                         "-dSourceDir=src",
                         f"{app.app_name}.wxs",
                         f"{app.app_name}-manifest.wxs",


Test patch:
diff --git a/tests/integrations/docker/test_Docker__run.py b/tests/integrations/docker/test_Docker__run.py
index 3405acd5..253fff60 100644
--- a/tests/integrations/docker/test_Docker__run.py
+++ b/tests/integrations/docker/test_Docker__run.py
@@ -16,11 +16,11 @@ def test_simple_call(mock_docker, tmp_path, capsys):
         [
             "docker",
             "run",
-            "--tty",
             "--volume",
             f"{tmp_path / 'platform'}:/app:z",
             "--volume",
             f"{tmp_path / '.briefcase'}:/home/brutus/.briefcase:z",
+            "--rm",
             "briefcase/com.example.myapp:py3.X",
             "hello",
             "world",
@@ -40,11 +40,11 @@ def test_simple_call_with_arg(mock_docker, tmp_path, capsys):
         [
             "docker",
             "run",
-            "--tty",
             "--volume",
             f"{tmp_path / 'platform'}:/app:z",
             "--volume",
             f"{tmp_path / '.briefcase'}:/home/brutus/.briefcase:z",
+            "--rm",
             "briefcase/com.example.myapp:py3.X",
             "hello",
             "world",
@@ -64,11 +64,11 @@ def test_simple_call_with_path_arg(mock_docker, tmp_path, capsys):
         [
             "docker",
             "run",
-            "--tty",
             "--volume",
             f"{tmp_path / 'platform'}:/app:z",
             "--volume",
             f"{tmp_path / '.briefcase'}:/home/brutus/.briefcase:z",
+            "--rm",
             "briefcase/com.example.myapp:py3.X",
             "hello",
             os.fsdecode(tmp_path / "location"),
@@ -95,11 +95,11 @@ def test_simple_call_with_sys_executable_arg(
         [
             "docker",
             "run",
-            "--tty",
             "--volume",
             f"{tmp_path / 'platform'}:/app:z",
             "--volume",
             f"{tmp_path / '.briefcase'}:/home/brutus/.briefcase:z",
+            "--rm",
             "briefcase/com.example.myapp:py3.X",
             "hello",
             "python3.X",
@@ -124,11 +124,11 @@ def test_simple_verbose_call(mock_docker, tmp_path, capsys):
         [
             "docker",
             "run",
-            "--tty",
             "--volume",
             f"{tmp_path / 'platform'}:/app:z",
             "--volume",
             f"{tmp_path / '.briefcase'}:/home/brutus/.briefcase:z",
+            "--rm",
             "briefcase/com.example.myapp:py3.X",
             "hello",
             "world",
@@ -139,9 +139,10 @@ def test_simple_verbose_call(mock_docker, tmp_path, capsys):
     assert capsys.readouterr().out == (
         "\n"
         ">>> Running Command:\n"
-        ">>>     docker run --tty "
+        ">>>     docker run "
         f"--volume {tmp_path / 'platform'}:/app:z "
         f"--volume {tmp_path / '.briefcase'}:/home/brutus/.briefcase:z "
+        "--rm "
         "briefcase/com.example.myapp:py3.X "
         "hello world\n"
         ">>> Return code: 3\n"
diff --git a/tests/platforms/linux/appimage/test_build.py b/tests/platforms/linux/appimage/test_build.py
index b469d16c..022d6857 100644
--- a/tests/platforms/linux/appimage/test_build.py
+++ b/tests/platforms/linux/appimage/test_build.py
@@ -216,11 +216,11 @@ def test_build_appimage_with_docker(build_command, first_app, tmp_path):
         [
             "docker",
             "run",
-            "--tty",
             "--volume",
             f"{build_command.platform_path}:/app:z",
             "--volume",
             f"{build_command.dot_briefcase_path}:/home/brutus/.briefcase:z",
+            "--rm",
             "--env",
             "VERSION=0.0.1",
             f"briefcase/com.example.first-app:py3.{sys.version_info.minor}",
diff --git a/tests/platforms/windows/msi/test_create.py b/tests/platforms/windows/msi/test_create.py
index c01e00b1..9bbb09ed 100644
--- a/tests/platforms/windows/msi/test_create.py
+++ b/tests/platforms/windows/msi/test_create.py
@@ -83,7 +83,12 @@ def test_default_install_scope(first_app_config, tmp_path):
 
     context = command.output_format_template_context(first_app_config)
 
-    assert context["install_scope"] == "perUser"
+    assert context == {
+        "guid": "d666a4f1-c7b7-52cc-888a-3a35a7cc97e5",
+        "version_triple": "0.0.1",
+        "install_scope": None,
+        "_use_arch64": True,
+    }
 
 
 def test_per_machine_install_scope(first_app_config, tmp_path):
@@ -93,7 +98,12 @@ def test_per_machine_install_scope(first_app_config, tmp_path):
 
     context = command.output_format_template_context(first_app_config)
 
-    assert context["install_scope"] == "perMachine"
+    assert context == {
+        "guid": "d666a4f1-c7b7-52cc-888a-3a35a7cc97e5",
+        "version_triple": "0.0.1",
+        "install_scope": "perMachine",
+        "_use_arch64": True,
+    }
 
 
 def test_per_user_install_scope(first_app_config, tmp_path):
@@ -103,4 +113,9 @@ def test_per_user_install_scope(first_app_config, tmp_path):
 
     context = command.output_format_template_context(first_app_config)
 
-    assert context["install_scope"] == "perUser"
+    assert context == {
+        "guid": "d666a4f1-c7b7-52cc-888a-3a35a7cc97e5",
+        "version_triple": "0.0.1",
+        "install_scope": "perUser",
+        "_use_arch64": True,
+    }
diff --git a/tests/platforms/windows/msi/test_package.py b/tests/platforms/windows/msi/test_package.py
index ed4725e7..52c136fe 100644
--- a/tests/platforms/windows/msi/test_package.py
+++ b/tests/platforms/windows/msi/test_package.py
@@ -54,6 +54,8 @@ def test_package_msi(package_command, first_app_config, tmp_path):
                     "WixUtilExtension",
                     "-ext",
                     "WixUIExtension",
+                    "-arch",
+                    "x64",
                     "-dSourceDir=src",
                     "first-app.wxs",
                     "first-app-manifest.wxs",