From 0affcf610822aa1863d3bfbeb9e2d9cba643746f Mon Sep 17 00:00:00 2001 From: Abdo Date: Fri, 10 Apr 2026 16:06:58 +0300 Subject: [PATCH 01/24] Allow running on Windows ARM64 --- src/briefcase/platforms/windows/__init__.py | 18 ++++++++---------- src/briefcase/platforms/windows/app.py | 4 +++- .../platforms/windows/visualstudio.py | 5 ++++- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/briefcase/platforms/windows/__init__.py b/src/briefcase/platforms/windows/__init__.py index 9ae3cd5ff0..fab8b50b10 100644 --- a/src/briefcase/platforms/windows/__init__.py +++ b/src/briefcase/platforms/windows/__init__.py @@ -79,7 +79,7 @@ def bundle_package_executable_path(self, app): return f"{app.formal_name}.exe" def bundle_package_path(self, app): - return self.bundle_path(app) / self.packaging_root + return self.bundle_path(app) / self.packaging_root() def binary_path(self, app): return self.package_path(app) / self.package_executable_path(app) @@ -90,10 +90,7 @@ def distribution_path(self, app): def verify_host(self): super().verify_host() - # The stub app only supports x86-64 right now, and our VisualStudio and WiX code - # is the same (#1887). However, we can package an external x86-64 app on any - # build machine. - if self.tools.host_arch != "AMD64": + if self.tools.host_arch not in ("AMD64", "ARM64"): if all(app.external_package_path for app in self.apps.values()): if not self.is_clone: self.console.warning(f""" @@ -101,11 +98,11 @@ def verify_host(self): ** WARNING: Possible architecture mismatch ** ************************************************************************* -The build machine is {self.tools.host_arch}, but Briefcase on Windows currently only -supports x86-64 installers. +The build machine is {self.tools.host_arch}, but Briefcase on Windows only +supports x86-64 and ARM64 installers. You are responsible for ensuring that the content of external_package_path -is compatible with x86-64. +is compatible with supported platforms. ************************************************************************* """) @@ -127,7 +124,8 @@ def verify_host(self): class WindowsCreateCommand(CreateCommand): def support_package_filename(self, support_revision): - return f"python-{self.python_version_tag}.{support_revision}-embed-amd64.zip" + arch = self.tools.host_arch.lower() + return f"python-{self.python_version_tag}.{support_revision}-embed-{arch}.zip" def support_package_url(self, support_revision): micro = re.match(r"\d+", str(support_revision)).group(0) @@ -594,7 +592,7 @@ def _package_msi(self, app): "-ext", self.tools.wix.ext_path("UI"), "-arch", - "x64", # Default is x86, regardless of the build machine. + "arm64" if self.tools.host_arch == "ARM64" else "x64", f"{app.app_name}.wxs", "-loc", "unicode.wxl", diff --git a/src/briefcase/platforms/windows/app.py b/src/briefcase/platforms/windows/app.py index 06dbd079fa..0ddbd548a5 100644 --- a/src/briefcase/platforms/windows/app.py +++ b/src/briefcase/platforms/windows/app.py @@ -23,9 +23,11 @@ class WindowsAppMixin(WindowsMixin): output_format = "app" - packaging_root = Path("src") supports_external_packaging = True + def packaging_root(self): + return Path("src") + def project_path(self, app): return self.bundle_path(app) diff --git a/src/briefcase/platforms/windows/visualstudio.py b/src/briefcase/platforms/windows/visualstudio.py index b7275fe3b2..45686e657a 100644 --- a/src/briefcase/platforms/windows/visualstudio.py +++ b/src/briefcase/platforms/windows/visualstudio.py @@ -21,7 +21,10 @@ class WindowsVisualStudioMixin(WindowsMixin): output_format = "VisualStudio" - packaging_root = Path("x64/Release") + + def packaging_root(self): + arch = "ARM" if self.tools.host_arch == "ARM64" else "x64" + return Path(f"{arch}/Release") def project_path(self, app): return self.bundle_path(app) / f"{app.formal_name}.sln" From dc9b6169536566977a2ada43745ac0fe824ac909 Mon Sep 17 00:00:00 2001 From: Abdo Date: Fri, 10 Apr 2026 18:13:32 +0300 Subject: [PATCH 02/24] Fix VS packaging root for ARM --- src/briefcase/platforms/windows/visualstudio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/briefcase/platforms/windows/visualstudio.py b/src/briefcase/platforms/windows/visualstudio.py index 45686e657a..b6df036cb6 100644 --- a/src/briefcase/platforms/windows/visualstudio.py +++ b/src/briefcase/platforms/windows/visualstudio.py @@ -23,7 +23,7 @@ class WindowsVisualStudioMixin(WindowsMixin): output_format = "VisualStudio" def packaging_root(self): - arch = "ARM" if self.tools.host_arch == "ARM64" else "x64" + arch = "ARM64" if self.tools.host_arch == "ARM64" else "x64" return Path(f"{arch}/Release") def project_path(self, app): From 9a0e6355a3f98296e0e1bab07844fef369ffd09b Mon Sep 17 00:00:00 2001 From: Abdo Date: Fri, 10 Apr 2026 18:16:25 +0300 Subject: [PATCH 03/24] Pass platform to MSBuild --- src/briefcase/platforms/windows/visualstudio.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/briefcase/platforms/windows/visualstudio.py b/src/briefcase/platforms/windows/visualstudio.py index b6df036cb6..22bef36339 100644 --- a/src/briefcase/platforms/windows/visualstudio.py +++ b/src/briefcase/platforms/windows/visualstudio.py @@ -60,6 +60,7 @@ def build_app(self, app: BaseConfig, **kwargs): with self.console.wait_bar("Building solution..."): try: + arch = "ARM64" if self.tools.host_arch == "ARM64" else "x64" self.tools.subprocess.run( [ self.tools.visualstudio.msbuild_path, @@ -68,6 +69,7 @@ def build_app(self, app: BaseConfig, **kwargs): "-property:RestorePackagesConfig=true", f"-target:{app.formal_name}", "-property:Configuration=Release", + f"-property:Platform={arch}", ( "-verbosity:detailed" if self.console.is_deep_debug From 86bd5dacda9753af73e9964e4feb325d7e628abf Mon Sep 17 00:00:00 2001 From: Abdo Date: Fri, 10 Apr 2026 20:47:50 +0300 Subject: [PATCH 04/24] Construct correct stub binary filename for ARM --- src/briefcase/commands/create.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/briefcase/commands/create.py b/src/briefcase/commands/create.py index 937bbf8f04..98c6f1a658 100644 --- a/src/briefcase/commands/create.py +++ b/src/briefcase/commands/create.py @@ -123,7 +123,15 @@ def support_package_url(self, support_revision: str) -> str: def stub_binary_filename(self, support_revision: str, is_console_app: bool) -> str: """The filename for the stub binary.""" stub_type = "Console" if is_console_app else "GUI" - return f"{stub_type}-Stub-{self.python_version_tag}-b{support_revision}.zip" + win_arm_suffix = ( + "-arm" + if self.tools.host_os == "Windows" and self.tools.host_arch == "ARM64" + else "" + ) + return ( + f"{stub_type}-Stub-{self.python_version_tag}-b{support_revision}" + f"{win_arm_suffix}.zip" + ) def stub_binary_url(self, support_revision: str, is_console_app: bool) -> str: """The URL of the stub binary to use for apps of this type.""" From 2c04b8021d963601575a2e09ee9b307bcc7188d5 Mon Sep 17 00:00:00 2001 From: Abdo Date: Mon, 13 Apr 2026 12:51:51 +0300 Subject: [PATCH 05/24] Refactor platform checks --- src/briefcase/platforms/windows/__init__.py | 5 ++++- src/briefcase/platforms/windows/visualstudio.py | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/briefcase/platforms/windows/__init__.py b/src/briefcase/platforms/windows/__init__.py index fab8b50b10..35f70cb573 100644 --- a/src/briefcase/platforms/windows/__init__.py +++ b/src/briefcase/platforms/windows/__init__.py @@ -88,6 +88,9 @@ def distribution_path(self, app): suffix = "zip" if app.packaging_format == "zip" else "msi" return self.dist_path / f"{app.formal_name}-{app.version}.{suffix}" + def windows_arch(self): + return "ARM64" if self.tools.host_arch == "ARM64" else "x64" + def verify_host(self): super().verify_host() if self.tools.host_arch not in ("AMD64", "ARM64"): @@ -592,7 +595,7 @@ def _package_msi(self, app): "-ext", self.tools.wix.ext_path("UI"), "-arch", - "arm64" if self.tools.host_arch == "ARM64" else "x64", + app.windows_arch().lower(), f"{app.app_name}.wxs", "-loc", "unicode.wxl", diff --git a/src/briefcase/platforms/windows/visualstudio.py b/src/briefcase/platforms/windows/visualstudio.py index 22bef36339..6a82a320f7 100644 --- a/src/briefcase/platforms/windows/visualstudio.py +++ b/src/briefcase/platforms/windows/visualstudio.py @@ -23,8 +23,7 @@ class WindowsVisualStudioMixin(WindowsMixin): output_format = "VisualStudio" def packaging_root(self): - arch = "ARM64" if self.tools.host_arch == "ARM64" else "x64" - return Path(f"{arch}/Release") + return Path(f"{self.windows_arch()}/Release") def project_path(self, app): return self.bundle_path(app) / f"{app.formal_name}.sln" @@ -60,7 +59,6 @@ def build_app(self, app: BaseConfig, **kwargs): with self.console.wait_bar("Building solution..."): try: - arch = "ARM64" if self.tools.host_arch == "ARM64" else "x64" self.tools.subprocess.run( [ self.tools.visualstudio.msbuild_path, @@ -69,7 +67,7 @@ def build_app(self, app: BaseConfig, **kwargs): "-property:RestorePackagesConfig=true", f"-target:{app.formal_name}", "-property:Configuration=Release", - f"-property:Platform={arch}", + f"-property:Platform={self.windows_arch()}", ( "-verbosity:detailed" if self.console.is_deep_debug From aa32d1c73a645b3141d10ab242f4dd099d3c35a9 Mon Sep 17 00:00:00 2001 From: Abdo Date: Mon, 13 Apr 2026 12:56:44 +0300 Subject: [PATCH 06/24] Add changes note --- changes/1887.feature.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/1887.feature.md diff --git a/changes/1887.feature.md b/changes/1887.feature.md new file mode 100644 index 0000000000..5371449d65 --- /dev/null +++ b/changes/1887.feature.md @@ -0,0 +1 @@ +Windows on ARM64 is now supported. From 2b9ca766a7c26bba187997c7096bbb8fff481363 Mon Sep 17 00:00:00 2001 From: Abdo Date: Mon, 13 Apr 2026 13:03:51 +0300 Subject: [PATCH 07/24] Add windows-11-arm runner --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ae7f1ed3c..624d5dce3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -235,7 +235,7 @@ jobs: fail-fast: false matrix: framework: [ "toga", "pyside6", "pygame", "console" ] - runner-os: [ "macos-15-intel", "macos-latest", "ubuntu-24.04", "ubuntu-24.04-arm", "windows-latest" ] + runner-os: [ "macos-15-intel", "macos-latest", "ubuntu-24.04", "ubuntu-24.04-arm", "windows-latest", "windows-11-arm"] verify-apps: name: Build app @@ -250,4 +250,4 @@ jobs: fail-fast: false matrix: framework: [ "toga", "pyside6", "pygame", "console" ] - runner-os: [ "macos-15-intel", "macos-15", "ubuntu-24.04", "ubuntu-24.04-arm", "windows-latest" ] + runner-os: [ "macos-15-intel", "macos-15", "ubuntu-24.04", "ubuntu-24.04-arm", "windows-latest", "windows-11-arm" ] From aece50500e9dc985740e98f6303a096299a8e80d Mon Sep 17 00:00:00 2001 From: Abdo Date: Mon, 13 Apr 2026 13:05:57 +0300 Subject: [PATCH 08/24] Add suffix for amd64 --- src/briefcase/commands/create.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/briefcase/commands/create.py b/src/briefcase/commands/create.py index 98c6f1a658..d0e9244749 100644 --- a/src/briefcase/commands/create.py +++ b/src/briefcase/commands/create.py @@ -123,14 +123,14 @@ def support_package_url(self, support_revision: str) -> str: def stub_binary_filename(self, support_revision: str, is_console_app: bool) -> str: """The filename for the stub binary.""" stub_type = "Console" if is_console_app else "GUI" - win_arm_suffix = ( - "-arm" - if self.tools.host_os == "Windows" and self.tools.host_arch == "ARM64" + win_suffix = ( + f"-{self.tools.host_arch.lower()}" + if self.tools.host_os == "Windows" else "" ) return ( f"{stub_type}-Stub-{self.python_version_tag}-b{support_revision}" - f"{win_arm_suffix}.zip" + f"{win_suffix}.zip" ) def stub_binary_url(self, support_revision: str, is_console_app: bool) -> str: From 209724f995d7971a43eccc4f89307d2c4f241b95 Mon Sep 17 00:00:00 2001 From: Abdo Date: Mon, 13 Apr 2026 13:41:08 +0300 Subject: [PATCH 09/24] Update tests --- src/briefcase/platforms/windows/__init__.py | 4 ++-- src/briefcase/platforms/windows/visualstudio.py | 4 ++-- tests/platforms/windows/app/create/test_create.py | 6 +++--- tests/platforms/windows/visualstudio/test_build.py | 1 + 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/briefcase/platforms/windows/__init__.py b/src/briefcase/platforms/windows/__init__.py index 35f70cb573..7cd889940c 100644 --- a/src/briefcase/platforms/windows/__init__.py +++ b/src/briefcase/platforms/windows/__init__.py @@ -88,7 +88,7 @@ def distribution_path(self, app): suffix = "zip" if app.packaging_format == "zip" else "msi" return self.dist_path / f"{app.formal_name}-{app.version}.{suffix}" - def windows_arch(self): + def architecture(self): return "ARM64" if self.tools.host_arch == "ARM64" else "x64" def verify_host(self): @@ -595,7 +595,7 @@ def _package_msi(self, app): "-ext", self.tools.wix.ext_path("UI"), "-arch", - app.windows_arch().lower(), + self.architecture().lower(), f"{app.app_name}.wxs", "-loc", "unicode.wxl", diff --git a/src/briefcase/platforms/windows/visualstudio.py b/src/briefcase/platforms/windows/visualstudio.py index 6a82a320f7..c9ee84ee0d 100644 --- a/src/briefcase/platforms/windows/visualstudio.py +++ b/src/briefcase/platforms/windows/visualstudio.py @@ -23,7 +23,7 @@ class WindowsVisualStudioMixin(WindowsMixin): output_format = "VisualStudio" def packaging_root(self): - return Path(f"{self.windows_arch()}/Release") + return Path(f"{self.architecture()}/Release") def project_path(self, app): return self.bundle_path(app) / f"{app.formal_name}.sln" @@ -67,7 +67,7 @@ def build_app(self, app: BaseConfig, **kwargs): "-property:RestorePackagesConfig=true", f"-target:{app.formal_name}", "-property:Configuration=Release", - f"-property:Platform={self.windows_arch()}", + f"-property:Platform={self.architecture()}", ( "-verbosity:detailed" if self.console.is_deep_debug diff --git a/tests/platforms/windows/app/create/test_create.py b/tests/platforms/windows/app/create/test_create.py index 61afc1fe80..c57c933ec2 100644 --- a/tests/platforms/windows/app/create/test_create.py +++ b/tests/platforms/windows/app/create/test_create.py @@ -28,9 +28,9 @@ def test_unsupported_host_os(create_command, host_os): create_command() -@pytest.mark.parametrize("host_arch", ["i686", "ARM64", "wonky"]) +@pytest.mark.parametrize("host_arch", ["i686", "wonky"]) def test_unsupported_arch(create_command, host_arch, first_app_config): - """Internal apps can only be developed on x86-64.""" + """Internal apps can only be developed on x86-64 and ARM64.""" create_command.tools.host_os = "Windows" create_command.tools.host_arch = host_arch create_command.apps["first"] = first_app_config @@ -42,7 +42,7 @@ def test_unsupported_arch(create_command, host_arch, first_app_config): create_command.verify_host() -@pytest.mark.parametrize("host_arch", ["i686", "ARM64", "wonky"]) +@pytest.mark.parametrize("host_arch", ["i686", "wonky"]) def test_unsupported_arch_external(create_command, host_arch, first_app_config, capsys): """External apps can be built on a different architecture, with a warning.""" create_command.tools.host_os = "Windows" diff --git a/tests/platforms/windows/visualstudio/test_build.py b/tests/platforms/windows/visualstudio/test_build.py index 7c6681ed1f..4d56a0f4f3 100644 --- a/tests/platforms/windows/visualstudio/test_build.py +++ b/tests/platforms/windows/visualstudio/test_build.py @@ -65,6 +65,7 @@ def test_build_app(build_command, first_app_config, tool_debug_mode, tmp_path): "-property:RestorePackagesConfig=true", "-target:First App", "-property:Configuration=Release", + f"-property:Platform={build_command.architecture()}", "-verbosity:detailed" if tool_debug_mode else "-verbosity:normal", ], check=True, From 873f553df41c51caa5950a57c1d437ac938c1e69 Mon Sep 17 00:00:00 2001 From: Abdo Date: Mon, 13 Apr 2026 13:49:56 +0300 Subject: [PATCH 10/24] Replace hardcoded amd64 suffix in tests --- tests/platforms/windows/app/create/test_create.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/platforms/windows/app/create/test_create.py b/tests/platforms/windows/app/create/test_create.py index c57c933ec2..32cc356f13 100644 --- a/tests/platforms/windows/app/create/test_create.py +++ b/tests/platforms/windows/app/create/test_create.py @@ -162,7 +162,7 @@ def test_support_package_url( expected_link = ( f"https://www.python.org/ftp/python" f"/{sys.version_info.major}.{sys.version_info.minor}.{micro}" - f"/python-{sys.version_info.major}.{sys.version_info.minor}.{revision}-embed-amd64.zip" + f"/python-{sys.version_info.major}.{sys.version_info.minor}.{revision}-embed-{create_command.tools.host_arch.lower()}.zip" ) assert create_command.support_package_url(revision) == expected_link From c25b340f0d64a7e48d2617318a1dc213c0b9523d Mon Sep 17 00:00:00 2001 From: Abdo Date: Mon, 13 Apr 2026 15:01:18 +0300 Subject: [PATCH 11/24] Update platform support tables --- docs/en/reference/platforms/index.md | 4 ++-- docs/en/reference/platforms/windows/index.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/reference/platforms/index.md b/docs/en/reference/platforms/index.md index b3aa540f32..439ad98711 100644 --- a/docs/en/reference/platforms/index.md +++ b/docs/en/reference/platforms/index.md @@ -153,7 +153,7 @@ {{ ci_tested }} - +{{ ci_tested }} @@ -165,7 +165,7 @@ {{ ci_tested }} - +{{ ci_tested }} diff --git a/docs/en/reference/platforms/windows/index.md b/docs/en/reference/platforms/windows/index.md index 8c3e0f3abc..aac47285eb 100644 --- a/docs/en/reference/platforms/windows/index.md +++ b/docs/en/reference/platforms/windows/index.md @@ -41,7 +41,7 @@ {{ ci_tested }} - +{{ ci_tested }} From 5adf8a4e19ad4503e74c34604337e6338d60d3a6 Mon Sep 17 00:00:00 2001 From: Abdo Date: Tue, 14 Apr 2026 05:29:05 +0300 Subject: [PATCH 12/24] Rename to vscode_platform and make a property --- src/briefcase/platforms/windows/__init__.py | 5 +++-- src/briefcase/platforms/windows/visualstudio.py | 4 ++-- tests/platforms/windows/visualstudio/test_build.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/briefcase/platforms/windows/__init__.py b/src/briefcase/platforms/windows/__init__.py index 7cd889940c..c3bfdf51e3 100644 --- a/src/briefcase/platforms/windows/__init__.py +++ b/src/briefcase/platforms/windows/__init__.py @@ -88,7 +88,8 @@ def distribution_path(self, app): suffix = "zip" if app.packaging_format == "zip" else "msi" return self.dist_path / f"{app.formal_name}-{app.version}.{suffix}" - def architecture(self): + @property + def vscode_platform(self): return "ARM64" if self.tools.host_arch == "ARM64" else "x64" def verify_host(self): @@ -595,7 +596,7 @@ def _package_msi(self, app): "-ext", self.tools.wix.ext_path("UI"), "-arch", - self.architecture().lower(), + self.vscode_platform.lower(), f"{app.app_name}.wxs", "-loc", "unicode.wxl", diff --git a/src/briefcase/platforms/windows/visualstudio.py b/src/briefcase/platforms/windows/visualstudio.py index c9ee84ee0d..206d2193e4 100644 --- a/src/briefcase/platforms/windows/visualstudio.py +++ b/src/briefcase/platforms/windows/visualstudio.py @@ -23,7 +23,7 @@ class WindowsVisualStudioMixin(WindowsMixin): output_format = "VisualStudio" def packaging_root(self): - return Path(f"{self.architecture()}/Release") + return Path(f"{self.vscode_platform}/Release") def project_path(self, app): return self.bundle_path(app) / f"{app.formal_name}.sln" @@ -67,7 +67,7 @@ def build_app(self, app: BaseConfig, **kwargs): "-property:RestorePackagesConfig=true", f"-target:{app.formal_name}", "-property:Configuration=Release", - f"-property:Platform={self.architecture()}", + f"-property:Platform={self.vscode_platform}", ( "-verbosity:detailed" if self.console.is_deep_debug diff --git a/tests/platforms/windows/visualstudio/test_build.py b/tests/platforms/windows/visualstudio/test_build.py index 4d56a0f4f3..7ca0327a3d 100644 --- a/tests/platforms/windows/visualstudio/test_build.py +++ b/tests/platforms/windows/visualstudio/test_build.py @@ -65,7 +65,7 @@ def test_build_app(build_command, first_app_config, tool_debug_mode, tmp_path): "-property:RestorePackagesConfig=true", "-target:First App", "-property:Configuration=Release", - f"-property:Platform={build_command.architecture()}", + f"-property:Platform={build_command.vscode_platform}", "-verbosity:detailed" if tool_debug_mode else "-verbosity:normal", ], check=True, From 1f923fb61f05824c19cc28bd197d11b268208bd6 Mon Sep 17 00:00:00 2001 From: Abdo Date: Tue, 14 Apr 2026 05:35:25 +0300 Subject: [PATCH 13/24] Convert packaging_root to a property --- src/briefcase/platforms/windows/__init__.py | 2 +- src/briefcase/platforms/windows/app.py | 1 + src/briefcase/platforms/windows/visualstudio.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/briefcase/platforms/windows/__init__.py b/src/briefcase/platforms/windows/__init__.py index c3bfdf51e3..04003fe529 100644 --- a/src/briefcase/platforms/windows/__init__.py +++ b/src/briefcase/platforms/windows/__init__.py @@ -79,7 +79,7 @@ def bundle_package_executable_path(self, app): return f"{app.formal_name}.exe" def bundle_package_path(self, app): - return self.bundle_path(app) / self.packaging_root() + return self.bundle_path(app) / self.packaging_root def binary_path(self, app): return self.package_path(app) / self.package_executable_path(app) diff --git a/src/briefcase/platforms/windows/app.py b/src/briefcase/platforms/windows/app.py index 0ddbd548a5..0559bdae95 100644 --- a/src/briefcase/platforms/windows/app.py +++ b/src/briefcase/platforms/windows/app.py @@ -25,6 +25,7 @@ class WindowsAppMixin(WindowsMixin): output_format = "app" supports_external_packaging = True + @property def packaging_root(self): return Path("src") diff --git a/src/briefcase/platforms/windows/visualstudio.py b/src/briefcase/platforms/windows/visualstudio.py index 206d2193e4..5e61a46357 100644 --- a/src/briefcase/platforms/windows/visualstudio.py +++ b/src/briefcase/platforms/windows/visualstudio.py @@ -22,6 +22,7 @@ class WindowsVisualStudioMixin(WindowsMixin): output_format = "VisualStudio" + @property def packaging_root(self): return Path(f"{self.vscode_platform}/Release") From c91b4379d1621dc375abdc538052db014470f43a Mon Sep 17 00:00:00 2001 From: Abdo Date: Tue, 14 Apr 2026 06:01:12 +0300 Subject: [PATCH 14/24] Refuse to run with a x86_64 interpreter --- src/briefcase/platforms/windows/__init__.py | 11 ++++++++ .../windows/app/create/test_create.py | 26 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/briefcase/platforms/windows/__init__.py b/src/briefcase/platforms/windows/__init__.py index 04003fe529..2ac6cfb41f 100644 --- a/src/briefcase/platforms/windows/__init__.py +++ b/src/briefcase/platforms/windows/__init__.py @@ -94,6 +94,17 @@ def vscode_platform(self): def verify_host(self): super().verify_host() + if ( + self.tools.host_arch == "ARM64" + and "AMD64" in self.tools.platform.python_compiler() + ): + raise UnsupportedHostError( + "The Python interpreter that is being used to run Briefcase has been " + "compiled for x86_64, and is running in emulation mode on ARM64" + "hardware. You must use a Python interpreter that has been " + "compiled for ARM64." + ) + if self.tools.host_arch not in ("AMD64", "ARM64"): if all(app.external_package_path for app in self.apps.values()): if not self.is_clone: diff --git a/tests/platforms/windows/app/create/test_create.py b/tests/platforms/windows/app/create/test_create.py index 32cc356f13..82c51bb0d3 100644 --- a/tests/platforms/windows/app/create/test_create.py +++ b/tests/platforms/windows/app/create/test_create.py @@ -1,4 +1,6 @@ +import platform import sys +from unittest.mock import MagicMock import pytest from packaging.version import Version @@ -82,6 +84,30 @@ def test_unsupported_32bit_python(create_command): create_command() +def test_verify_windows_cpu_arch(create_command): + """Running through x86_64 emulation on Windows ARM64 will raise an error.""" + + # Create a Mock object for the platform module + create_command.tools.platform = MagicMock(spec_set=platform) + + # Simulate that Mock platform is running on Windows ARM64 with an x86_64 Python interpreter + create_command.tools.host_arch = "ARM64" + create_command.tools.platform.python_compiler = MagicMock( + return_value="MSV v.1950 64 bit (AMD64)" + ) + + with pytest.raises( + UnsupportedHostError, + match=( + r"The Python interpreter that is being used to run Briefcase has been " + r"compiled for x86_64, and is running in emulation mode on ARM64" + r"hardware. You must use a Python interpreter that has been " + r"compiled for ARM64." + ), + ): + create_command.verify_host() + + def test_context(create_command, first_app_config): context = create_command.output_format_template_context(first_app_config) assert sorted(context.keys()) == [ From a2fae660611c318434dfc045b5c4dd49b1c48da6 Mon Sep 17 00:00:00 2001 From: Abdo Date: Tue, 14 Apr 2026 06:27:45 +0300 Subject: [PATCH 15/24] Fix non-Windows coverage --- tests/platforms/windows/app/create/test_create.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/platforms/windows/app/create/test_create.py b/tests/platforms/windows/app/create/test_create.py index 82c51bb0d3..3dd561e77a 100644 --- a/tests/platforms/windows/app/create/test_create.py +++ b/tests/platforms/windows/app/create/test_create.py @@ -91,6 +91,7 @@ def test_verify_windows_cpu_arch(create_command): create_command.tools.platform = MagicMock(spec_set=platform) # Simulate that Mock platform is running on Windows ARM64 with an x86_64 Python interpreter + create_command.tools.host_os = "Windows" create_command.tools.host_arch = "ARM64" create_command.tools.platform.python_compiler = MagicMock( return_value="MSV v.1950 64 bit (AMD64)" From 9a81caa8c516879e193d42bcd6bf5873c8101417 Mon Sep 17 00:00:00 2001 From: Abdo Date: Thu, 16 Apr 2026 12:25:34 +0300 Subject: [PATCH 16/24] Add missing space --- src/briefcase/platforms/windows/__init__.py | 2 +- tests/platforms/windows/app/create/test_create.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/briefcase/platforms/windows/__init__.py b/src/briefcase/platforms/windows/__init__.py index 2ac6cfb41f..ffa6370051 100644 --- a/src/briefcase/platforms/windows/__init__.py +++ b/src/briefcase/platforms/windows/__init__.py @@ -100,7 +100,7 @@ def verify_host(self): ): raise UnsupportedHostError( "The Python interpreter that is being used to run Briefcase has been " - "compiled for x86_64, and is running in emulation mode on ARM64" + "compiled for x86_64, and is running in emulation mode on ARM64 " "hardware. You must use a Python interpreter that has been " "compiled for ARM64." ) diff --git a/tests/platforms/windows/app/create/test_create.py b/tests/platforms/windows/app/create/test_create.py index 3dd561e77a..61b036e60e 100644 --- a/tests/platforms/windows/app/create/test_create.py +++ b/tests/platforms/windows/app/create/test_create.py @@ -101,7 +101,7 @@ def test_verify_windows_cpu_arch(create_command): UnsupportedHostError, match=( r"The Python interpreter that is being used to run Briefcase has been " - r"compiled for x86_64, and is running in emulation mode on ARM64" + r"compiled for x86_64, and is running in emulation mode on ARM64 " r"hardware. You must use a Python interpreter that has been " r"compiled for ARM64." ), From bd2e6a9722a2ed970399ff0f6c9b76ad23c986d5 Mon Sep 17 00:00:00 2001 From: Abdo Date: Thu, 16 Apr 2026 12:26:10 +0300 Subject: [PATCH 17/24] Update release note --- changes/1887.feature.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/1887.feature.md b/changes/1887.feature.md index 5371449d65..76117f8715 100644 --- a/changes/1887.feature.md +++ b/changes/1887.feature.md @@ -1 +1 @@ -Windows on ARM64 is now supported. +Briefcase can now build Windows apps for ARM64 devices. From c66079969507efc2c5394b2d80ae8a8617c236e5 Mon Sep 17 00:00:00 2001 From: Abdo Date: Thu, 16 Apr 2026 13:59:30 +0300 Subject: [PATCH 18/24] Detect ARM64 on older Python versions --- src/briefcase/integrations/base.py | 32 +++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/briefcase/integrations/base.py b/src/briefcase/integrations/base.py index 2b412bf586..b8df49821e 100644 --- a/src/briefcase/integrations/base.py +++ b/src/briefcase/integrations/base.py @@ -195,7 +195,7 @@ def __init__( self.base_path = Path(base_path) self.home_path = Path(os.path.expanduser(home_path or Path.home())) - self.host_arch = self.platform.machine() + self.host_arch = self._get_host_arch() self.host_os = self.platform.system() # Python is 32bit if its pointers can only address with 32 bits or fewer self.is_32bit_python = self.sys.maxsize <= 2**32 @@ -208,6 +208,36 @@ def __init__( ) ) + def _get_host_arch(self) -> str: + arch = self.platform.machine() + # On Windows with Python < 3.12, ``platform.machine()`` returns the + # emulated architecture (e.g. "AMD64") when an x86-64 Python interpreter + # is running under ARM64. ``IsWow64Process2()`` exposes the + # native machine type. + if ( + arch == "AMD64" + and self.platform.system() == "Windows" + and self.sys.version_info < (3, 12) + and self.sys.getwindowsversion().build >= 16299 # Windows 10 1709 + ): + import ctypes + from ctypes import wintypes + + IMAGE_FILE_MACHINE_ARM64 = 0xAA64 + kernel32 = ctypes.windll.kernel32 + process_machine = ctypes.c_ushort(0) + native_machine = ctypes.c_ushort(0) + if ( + kernel32.IsWow64Process2( + wintypes.HANDLE(kernel32.GetCurrentProcess()), + ctypes.byref(process_machine), + ctypes.byref(native_machine), + ) + and native_machine.value == IMAGE_FILE_MACHINE_ARM64 + ): + return "ARM64" + return arch + @cached_property def system_encoding(self) -> str: """The character encoding for the system's locale. From 9eceb52cda7f60e29f38c1acff32495301990106 Mon Sep 17 00:00:00 2001 From: Abdo Date: Thu, 16 Apr 2026 14:50:33 +0300 Subject: [PATCH 19/24] Add a test --- src/briefcase/integrations/base.py | 4 ++-- tests/integrations/base/test_ToolCache.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/briefcase/integrations/base.py b/src/briefcase/integrations/base.py index b8df49821e..753ff9f817 100644 --- a/src/briefcase/integrations/base.py +++ b/src/briefcase/integrations/base.py @@ -195,8 +195,8 @@ def __init__( self.base_path = Path(base_path) self.home_path = Path(os.path.expanduser(home_path or Path.home())) - self.host_arch = self._get_host_arch() self.host_os = self.platform.system() + self.host_arch = self._get_host_arch() # Python is 32bit if its pointers can only address with 32 bits or fewer self.is_32bit_python = self.sys.maxsize <= 2**32 @@ -216,7 +216,7 @@ def _get_host_arch(self) -> str: # native machine type. if ( arch == "AMD64" - and self.platform.system() == "Windows" + and self.host_os == "Windows" and self.sys.version_info < (3, 12) and self.sys.getwindowsversion().build >= 16299 # Windows 10 1709 ): diff --git a/tests/integrations/base/test_ToolCache.py b/tests/integrations/base/test_ToolCache.py index 07f4fb3cba..aef45cd60c 100644 --- a/tests/integrations/base/test_ToolCache.py +++ b/tests/integrations/base/test_ToolCache.py @@ -109,6 +109,29 @@ def test_host_arch_and_os(simple_tools): assert simple_tools.host_os == platform.system() +def test_get_host_arch_windows_arm64(dummy_console, monkeypatch, tmp_path): + """ARM64 is detected on Windows with a pre-3.12 Python x86_64 interpreter.""" + mock_ctypes = MagicMock() + mock_ctypes.c_ushort.return_value.value = 0xAA64 # IMAGE_FILE_MACHINE_ARM64 + + mock_platform = MagicMock() + mock_platform.machine.return_value = "AMD64" + mock_platform.system.return_value = "Windows" + + mock_sys = MagicMock() + mock_sys.version_info = (3, 11, 0) + mock_sys.getwindowsversion.return_value.build = 16299 + mock_sys.maxsize = 2**64 + + monkeypatch.setattr(ToolCache, "platform", mock_platform) + monkeypatch.setattr(ToolCache, "sys", mock_sys) + monkeypatch.setitem(sys.modules, "ctypes", mock_ctypes) + + tools = ToolCache(console=dummy_console, base_path=tmp_path) + + assert tools.host_arch == "ARM64" + + def test_base_path_is_path(dummy_console, simple_tools): """Base path is always a Path.""" # The BaseCommand tests have much more extensive tests for this path. From f435a2d94cf28e837f5f36a0920a2c5f4d19e054 Mon Sep 17 00:00:00 2001 From: Abdo Date: Thu, 16 Apr 2026 14:55:36 +0300 Subject: [PATCH 20/24] Update Visual Studio docs --- docs/en/reference/platforms/windows/visualstudio.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/en/reference/platforms/windows/visualstudio.md b/docs/en/reference/platforms/windows/visualstudio.md index 2d92bf0ba6..3ec4f4908d 100644 --- a/docs/en/reference/platforms/windows/visualstudio.md +++ b/docs/en/reference/platforms/windows/visualstudio.md @@ -60,7 +60,7 @@ All Windows apps, regardless of output format, use the same icon formats, have t ## Pre-requisites -Building the Visual Studio project requires that you install Visual Studio 2022 or later. Visual Studio 2022 Community Edition [can be downloaded for free from Microsoft](https://visualstudio.microsoft.com/vs/community/). You can also use the Professional or Enterprise versions if you have them. +Building the Visual Studio project requires that you install Visual Studio. Visual Studio Community Edition [can be downloaded for free from Microsoft](https://visualstudio.microsoft.com/vs/community/). You can also use the Professional or Enterprise versions if you have them. Briefcase will auto-detect the location of your Visual Studio installation, provided one of the following three things are true: @@ -75,6 +75,7 @@ When you install Visual Studio, there are many optional components. You should e - Desktop Development with C++ - All default packages - C++/CLI support for v143 build tools + - MSVS v143 VS 2022 C++ ARM64/x64 build tools ## Application configuration From a483bcb155e0df0085a0b94e112e574a07517f1a Mon Sep 17 00:00:00 2001 From: Abdo Date: Thu, 16 Apr 2026 15:36:37 +0300 Subject: [PATCH 21/24] Add additional test for more coverage --- tests/integrations/base/test_ToolCache.py | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/integrations/base/test_ToolCache.py b/tests/integrations/base/test_ToolCache.py index aef45cd60c..5195b6a225 100644 --- a/tests/integrations/base/test_ToolCache.py +++ b/tests/integrations/base/test_ToolCache.py @@ -132,6 +132,30 @@ def test_get_host_arch_windows_arm64(dummy_console, monkeypatch, tmp_path): assert tools.host_arch == "ARM64" +def test_get_host_arch_windows_not_arm64(dummy_console, monkeypatch, tmp_path): + """AMD64 is returned when IsWow64Process2 reports the native machine is not + ARM64.""" + mock_ctypes = MagicMock() + mock_ctypes.windll.kernel32.IsWow64Process2.return_value = 0 + + mock_platform = MagicMock() + mock_platform.machine.return_value = "AMD64" + mock_platform.system.return_value = "Windows" + + mock_sys = MagicMock() + mock_sys.version_info = (3, 11, 0) + mock_sys.getwindowsversion.return_value.build = 16299 + mock_sys.maxsize = 2**64 + + monkeypatch.setattr(ToolCache, "platform", mock_platform) + monkeypatch.setattr(ToolCache, "sys", mock_sys) + monkeypatch.setitem(sys.modules, "ctypes", mock_ctypes) + + tools = ToolCache(console=dummy_console, base_path=tmp_path) + + assert tools.host_arch == "AMD64" + + def test_base_path_is_path(dummy_console, simple_tools): """Base path is always a Path.""" # The BaseCommand tests have much more extensive tests for this path. From 05b5ce2c90c899327743983906bb27482926edcb Mon Sep 17 00:00:00 2001 From: Abdo Date: Thu, 16 Apr 2026 15:40:41 +0300 Subject: [PATCH 22/24] Fix spelling lint --- docs/en/reference/platforms/windows/visualstudio.md | 2 +- docs/spelling_wordlist | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/en/reference/platforms/windows/visualstudio.md b/docs/en/reference/platforms/windows/visualstudio.md index 3ec4f4908d..29cf43c432 100644 --- a/docs/en/reference/platforms/windows/visualstudio.md +++ b/docs/en/reference/platforms/windows/visualstudio.md @@ -75,7 +75,7 @@ When you install Visual Studio, there are many optional components. You should e - Desktop Development with C++ - All default packages - C++/CLI support for v143 build tools - - MSVS v143 VS 2022 C++ ARM64/x64 build tools + - MSVC v143 VS 2022 C++ ARM64/x64 build tools ## Application configuration diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index 8935009eaf..3c4387b701 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -237,3 +237,4 @@ Xauth Xcode Xr Xs +MSVC From 371794a97a5ac035d20c9692873105a33b9088a0 Mon Sep 17 00:00:00 2001 From: Abdo Date: Thu, 16 Apr 2026 16:14:12 +0300 Subject: [PATCH 23/24] Add a test for Python 3.12+ --- tests/integrations/base/test_ToolCache.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/integrations/base/test_ToolCache.py b/tests/integrations/base/test_ToolCache.py index 5195b6a225..feec097739 100644 --- a/tests/integrations/base/test_ToolCache.py +++ b/tests/integrations/base/test_ToolCache.py @@ -156,6 +156,25 @@ def test_get_host_arch_windows_not_arm64(dummy_console, monkeypatch, tmp_path): assert tools.host_arch == "AMD64" +def test_get_host_arch_windows_python312(dummy_console, monkeypatch, tmp_path): + """On Python 3.12+, IsWow64Process2 is not called; platform.machine() is used + directly.""" + mock_platform = MagicMock() + mock_platform.machine.return_value = "AMD64" + mock_platform.system.return_value = "Windows" + + mock_sys = MagicMock() + mock_sys.version_info = (3, 12, 0) + mock_sys.maxsize = 2**64 + + monkeypatch.setattr(ToolCache, "platform", mock_platform) + monkeypatch.setattr(ToolCache, "sys", mock_sys) + + tools = ToolCache(console=dummy_console, base_path=tmp_path) + + assert tools.host_arch == "AMD64" + + def test_base_path_is_path(dummy_console, simple_tools): """Base path is always a Path.""" # The BaseCommand tests have much more extensive tests for this path. From 9149fdd28e1eb7b4c3da73e2f5616e05cf86c397 Mon Sep 17 00:00:00 2001 From: Abdo Date: Fri, 17 Apr 2026 17:33:57 +0300 Subject: [PATCH 24/24] Update message about required VSCode components --- src/briefcase/integrations/visualstudio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/briefcase/integrations/visualstudio.py b/src/briefcase/integrations/visualstudio.py index ace35185a4..e42d13f961 100644 --- a/src/briefcase/integrations/visualstudio.py +++ b/src/briefcase/integrations/visualstudio.py @@ -18,7 +18,7 @@ class VisualStudio(Tool): - Default packages * Desktop Development with C++ - Default packages; plus - - C++/CLI support for v143 build tools + - MSVC v143 VS 2022 C++ ARM64/x64 build tools """ def __init__(