From af8c4036934e4c08a7650ef00ebf392cb01be274 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Fri, 10 Jul 2026 13:26:39 +0100 Subject: [PATCH 1/4] gh-153511: Removes Tools/msi, Tools/nuget, and legacy py.exe launcher sources --- .github/workflows/build.yml | 18 - .github/workflows/reusable-context.yml | 4 - .github/workflows/reusable-windows-msi.yml | 31 - ...-07-10-13-25-55.gh-issue-153511.dCPBwU.rst | 3 + PC/classicAppCompat.can.xml | 1 - PC/classicAppCompat.cat | Bin 10984 -> 0 bytes PC/classicAppCompat.sccd | 41 - PC/launcher-usage.txt | 31 - PC/launcher.c | 2047 ---------- PC/launcher2.c | 2858 -------------- PC/layout/README.md | 341 ++ PC/layout/support/appxmanifest.py | 2 + PC/pylauncher.rc | 67 - PC/pyshellext.cpp | 603 --- PC/pyshellext.def | 5 - PC/pyshellext.rc | 51 - PCbuild/pcbuild.proj | 4 - PCbuild/pcbuild.sln | 108 - PCbuild/pylauncher.vcxproj | 114 - PCbuild/pylauncher.vcxproj.filters | 28 - PCbuild/pywlauncher.vcxproj | 114 - PCbuild/pywlauncher.vcxproj.filters | 28 - PCbuild/readme.txt | 4 +- PCbuild/venvlauncher.vcxproj | 2 +- PCbuild/venvwlauncher.vcxproj | 5 +- Tools/README | 4 - Tools/buildbot/buildmsi.bat | 6 +- Tools/msi/README.txt | 530 --- Tools/msi/appendpath/appendpath.wixproj | 19 - Tools/msi/appendpath/appendpath.wxs | 40 - Tools/msi/appendpath/appendpath_en-US.wxl | 6 - Tools/msi/build.bat | 103 - Tools/msi/buildrelease.bat | 261 -- Tools/msi/bundle/Default.ARM64.xsl | 28 - Tools/msi/bundle/Default.thm | 141 - Tools/msi/bundle/Default.wxl | 133 - Tools/msi/bundle/SideBar.png | Bin 51948 -> 0 bytes Tools/msi/bundle/bootstrap/LICENSE.txt | 25 - .../PythonBootstrapperApplication.cpp | 3323 ----------------- Tools/msi/bundle/bootstrap/pch.cpp | 1 - Tools/msi/bundle/bootstrap/pch.h | 60 - Tools/msi/bundle/bootstrap/pythonba.cpp | 76 - Tools/msi/bundle/bootstrap/pythonba.def | 18 - Tools/msi/bundle/bootstrap/pythonba.sln | 22 - Tools/msi/bundle/bootstrap/pythonba.vcxproj | 73 - Tools/msi/bundle/bootstrap/resource.h | 25 - Tools/msi/bundle/bundle.targets | 129 - Tools/msi/bundle/bundle.wxl | 7 - Tools/msi/bundle/bundle.wxs | 128 - Tools/msi/bundle/full.wixproj | 21 - Tools/msi/bundle/packagegroups/core.wxs | 62 - Tools/msi/bundle/packagegroups/crt.wxs | 23 - Tools/msi/bundle/packagegroups/dev.wxs | 44 - Tools/msi/bundle/packagegroups/doc.wxs | 28 - Tools/msi/bundle/packagegroups/exe.wxs | 64 - .../msi/bundle/packagegroups/freethreaded.wxs | 62 - Tools/msi/bundle/packagegroups/launcher.wxs | 35 - Tools/msi/bundle/packagegroups/lib.wxs | 62 - .../bundle/packagegroups/packageinstall.wxs | 26 - Tools/msi/bundle/packagegroups/pip.wxs | 25 - .../msi/bundle/packagegroups/postinstall.wxs | 109 - Tools/msi/bundle/packagegroups/tcltk.wxs | 68 - Tools/msi/bundle/packagegroups/test.wxs | 62 - Tools/msi/bundle/releaselocal.wixproj | 21 - Tools/msi/bundle/releaseweb.wixproj | 21 - Tools/msi/bundle/snapshot.wixproj | 26 - Tools/msi/common.wxs | 138 - Tools/msi/common_en-US.wxl_template | 18 - Tools/msi/core/core.wixproj | 19 - Tools/msi/core/core.wxs | 13 - Tools/msi/core/core_d.wixproj | 19 - Tools/msi/core/core_d.wxs | 14 - Tools/msi/core/core_en-US.wxl | 5 - Tools/msi/core/core_files.wxs | 37 - Tools/msi/core/core_pdb.wixproj | 19 - Tools/msi/core/core_pdb.wxs | 14 - Tools/msi/csv_to_wxs.py | 127 - Tools/msi/dev/dev.wixproj | 29 - Tools/msi/dev/dev.wxs | 17 - Tools/msi/dev/dev_d.wixproj | 19 - Tools/msi/dev/dev_d.wxs | 13 - Tools/msi/dev/dev_en-US.wxl | 5 - Tools/msi/dev/dev_files.wxs | 38 - Tools/msi/doc/doc.wixproj | 37 - Tools/msi/doc/doc.wxs | 43 - Tools/msi/doc/doc_en-US.wxl_template | 7 - Tools/msi/exe/exe.wixproj | 41 - Tools/msi/exe/exe.wxs | 33 - Tools/msi/exe/exe_d.wixproj | 21 - Tools/msi/exe/exe_d.wxs | 13 - Tools/msi/exe/exe_en-US.wxl_template | 8 - Tools/msi/exe/exe_files.wxs | 84 - Tools/msi/exe/exe_pdb.wixproj | 21 - Tools/msi/exe/exe_pdb.wxs | 13 - Tools/msi/exe/exe_reg.wxs | 25 - Tools/msi/freethreaded/freethreaded.wixproj | 20 - Tools/msi/freethreaded/freethreaded.wxs | 18 - Tools/msi/freethreaded/freethreaded_d.wixproj | 20 - Tools/msi/freethreaded/freethreaded_d.wxs | 16 - .../freethreaded_en-US.wxl_template | 8 - Tools/msi/freethreaded/freethreaded_files.wxs | 187 - .../msi/freethreaded/freethreaded_pdb.wixproj | 20 - Tools/msi/freethreaded/freethreaded_pdb.wxs | 15 - Tools/msi/generate_md5.py | 27 - Tools/msi/get_externals.bat | 91 - Tools/msi/launcher/launcher.wixproj | 46 - Tools/msi/launcher/launcher.wxs | 75 - Tools/msi/launcher/launcher_en-US.wxl | 17 - Tools/msi/launcher/launcher_files.wxs | 44 - Tools/msi/launcher/launcher_reg.wxs | 50 - Tools/msi/lib/lib.wixproj | 35 - Tools/msi/lib/lib.wxs | 19 - Tools/msi/lib/lib_d.wixproj | 19 - Tools/msi/lib/lib_d.wxs | 13 - Tools/msi/lib/lib_en-US.wxl | 5 - Tools/msi/lib/lib_files.wxs | 128 - Tools/msi/lib/lib_pdb.wixproj | 19 - Tools/msi/lib/lib_pdb.wxs | 13 - Tools/msi/make_appx.ps1 | 71 - Tools/msi/make_cat.ps1 | 45 - Tools/msi/make_zip.proj | 40 - Tools/msi/msi.props | 234 -- Tools/msi/msi.targets | 96 - Tools/msi/path/path.wixproj | 19 - Tools/msi/path/path.wxs | 40 - Tools/msi/path/path_en-US.wxl | 6 - Tools/msi/pip/pip.wixproj | 19 - Tools/msi/pip/pip.wxs | 39 - Tools/msi/pip/pip_en-US.wxl | 6 - Tools/msi/purge.py | 76 - Tools/msi/sdktools.psm1 | 48 - Tools/msi/sign_build.ps1 | 34 - Tools/msi/tcltk/tcltk.wixproj | 42 - Tools/msi/tcltk/tcltk.wxs | 67 - Tools/msi/tcltk/tcltk_d.wixproj | 28 - Tools/msi/tcltk/tcltk_d.wxs | 13 - Tools/msi/tcltk/tcltk_en-US.wxl_template | 12 - Tools/msi/tcltk/tcltk_files.wxs | 49 - Tools/msi/tcltk/tcltk_pdb.wixproj | 19 - Tools/msi/tcltk/tcltk_pdb.wxs | 13 - Tools/msi/tcltk/tcltk_reg.wxs | 48 - Tools/msi/test/test.wixproj | 29 - Tools/msi/test/test.wxs | 17 - Tools/msi/test/test_d.wixproj | 19 - Tools/msi/test/test_d.wxs | 13 - Tools/msi/test/test_en-US.wxl | 7 - Tools/msi/test/test_files.wxs | 42 - Tools/msi/test/test_pdb.wixproj | 19 - Tools/msi/test/test_pdb.wxs | 13 - Tools/msi/testrelease.bat | 115 - Tools/msi/ucrt/ucrt.wixproj | 28 - Tools/msi/ucrt/ucrt.wxs | 15 - Tools/msi/ucrt/ucrt_en-US.wxl | 5 - Tools/msi/uploadrelease.bat | 109 - Tools/msi/uploadrelease.proj | 113 - Tools/msi/uploadrelease.ps1 | 171 - Tools/msi/wix.props | 10 - Tools/nuget/build.bat | 70 - Tools/nuget/make_pkg.proj | 63 - Tools/nuget/python.nuspec | 18 - Tools/nuget/pythonarm32.nuspec | 20 - Tools/nuget/pythondaily.nuspec | 19 - Tools/nuget/pythondaily.symbols.nuspec | 22 - Tools/nuget/pythonx86.nuspec | 19 - 164 files changed, 353 insertions(+), 15889 deletions(-) delete mode 100644 .github/workflows/reusable-windows-msi.yml create mode 100644 Misc/NEWS.d/next/Build/2026-07-10-13-25-55.gh-issue-153511.dCPBwU.rst delete mode 100644 PC/classicAppCompat.can.xml delete mode 100644 PC/classicAppCompat.cat delete mode 100644 PC/classicAppCompat.sccd delete mode 100644 PC/launcher-usage.txt delete mode 100644 PC/launcher.c delete mode 100644 PC/launcher2.c create mode 100644 PC/layout/README.md delete mode 100644 PC/pylauncher.rc delete mode 100644 PC/pyshellext.cpp delete mode 100644 PC/pyshellext.def delete mode 100644 PC/pyshellext.rc delete mode 100644 PCbuild/pylauncher.vcxproj delete mode 100644 PCbuild/pylauncher.vcxproj.filters delete mode 100644 PCbuild/pywlauncher.vcxproj delete mode 100644 PCbuild/pywlauncher.vcxproj.filters delete mode 100644 Tools/msi/README.txt delete mode 100644 Tools/msi/appendpath/appendpath.wixproj delete mode 100644 Tools/msi/appendpath/appendpath.wxs delete mode 100644 Tools/msi/appendpath/appendpath_en-US.wxl delete mode 100644 Tools/msi/build.bat delete mode 100644 Tools/msi/buildrelease.bat delete mode 100644 Tools/msi/bundle/Default.ARM64.xsl delete mode 100644 Tools/msi/bundle/Default.thm delete mode 100644 Tools/msi/bundle/Default.wxl delete mode 100644 Tools/msi/bundle/SideBar.png delete mode 100644 Tools/msi/bundle/bootstrap/LICENSE.txt delete mode 100644 Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp delete mode 100644 Tools/msi/bundle/bootstrap/pch.cpp delete mode 100644 Tools/msi/bundle/bootstrap/pch.h delete mode 100644 Tools/msi/bundle/bootstrap/pythonba.cpp delete mode 100644 Tools/msi/bundle/bootstrap/pythonba.def delete mode 100644 Tools/msi/bundle/bootstrap/pythonba.sln delete mode 100644 Tools/msi/bundle/bootstrap/pythonba.vcxproj delete mode 100644 Tools/msi/bundle/bootstrap/resource.h delete mode 100644 Tools/msi/bundle/bundle.targets delete mode 100644 Tools/msi/bundle/bundle.wxl delete mode 100644 Tools/msi/bundle/bundle.wxs delete mode 100644 Tools/msi/bundle/full.wixproj delete mode 100644 Tools/msi/bundle/packagegroups/core.wxs delete mode 100644 Tools/msi/bundle/packagegroups/crt.wxs delete mode 100644 Tools/msi/bundle/packagegroups/dev.wxs delete mode 100644 Tools/msi/bundle/packagegroups/doc.wxs delete mode 100644 Tools/msi/bundle/packagegroups/exe.wxs delete mode 100644 Tools/msi/bundle/packagegroups/freethreaded.wxs delete mode 100644 Tools/msi/bundle/packagegroups/launcher.wxs delete mode 100644 Tools/msi/bundle/packagegroups/lib.wxs delete mode 100644 Tools/msi/bundle/packagegroups/packageinstall.wxs delete mode 100644 Tools/msi/bundle/packagegroups/pip.wxs delete mode 100644 Tools/msi/bundle/packagegroups/postinstall.wxs delete mode 100644 Tools/msi/bundle/packagegroups/tcltk.wxs delete mode 100644 Tools/msi/bundle/packagegroups/test.wxs delete mode 100644 Tools/msi/bundle/releaselocal.wixproj delete mode 100644 Tools/msi/bundle/releaseweb.wixproj delete mode 100644 Tools/msi/bundle/snapshot.wixproj delete mode 100644 Tools/msi/common.wxs delete mode 100644 Tools/msi/common_en-US.wxl_template delete mode 100644 Tools/msi/core/core.wixproj delete mode 100644 Tools/msi/core/core.wxs delete mode 100644 Tools/msi/core/core_d.wixproj delete mode 100644 Tools/msi/core/core_d.wxs delete mode 100644 Tools/msi/core/core_en-US.wxl delete mode 100644 Tools/msi/core/core_files.wxs delete mode 100644 Tools/msi/core/core_pdb.wixproj delete mode 100644 Tools/msi/core/core_pdb.wxs delete mode 100644 Tools/msi/csv_to_wxs.py delete mode 100644 Tools/msi/dev/dev.wixproj delete mode 100644 Tools/msi/dev/dev.wxs delete mode 100644 Tools/msi/dev/dev_d.wixproj delete mode 100644 Tools/msi/dev/dev_d.wxs delete mode 100644 Tools/msi/dev/dev_en-US.wxl delete mode 100644 Tools/msi/dev/dev_files.wxs delete mode 100644 Tools/msi/doc/doc.wixproj delete mode 100644 Tools/msi/doc/doc.wxs delete mode 100644 Tools/msi/doc/doc_en-US.wxl_template delete mode 100644 Tools/msi/exe/exe.wixproj delete mode 100644 Tools/msi/exe/exe.wxs delete mode 100644 Tools/msi/exe/exe_d.wixproj delete mode 100644 Tools/msi/exe/exe_d.wxs delete mode 100644 Tools/msi/exe/exe_en-US.wxl_template delete mode 100644 Tools/msi/exe/exe_files.wxs delete mode 100644 Tools/msi/exe/exe_pdb.wixproj delete mode 100644 Tools/msi/exe/exe_pdb.wxs delete mode 100644 Tools/msi/exe/exe_reg.wxs delete mode 100644 Tools/msi/freethreaded/freethreaded.wixproj delete mode 100644 Tools/msi/freethreaded/freethreaded.wxs delete mode 100644 Tools/msi/freethreaded/freethreaded_d.wixproj delete mode 100644 Tools/msi/freethreaded/freethreaded_d.wxs delete mode 100644 Tools/msi/freethreaded/freethreaded_en-US.wxl_template delete mode 100644 Tools/msi/freethreaded/freethreaded_files.wxs delete mode 100644 Tools/msi/freethreaded/freethreaded_pdb.wixproj delete mode 100644 Tools/msi/freethreaded/freethreaded_pdb.wxs delete mode 100644 Tools/msi/generate_md5.py delete mode 100644 Tools/msi/get_externals.bat delete mode 100644 Tools/msi/launcher/launcher.wixproj delete mode 100644 Tools/msi/launcher/launcher.wxs delete mode 100644 Tools/msi/launcher/launcher_en-US.wxl delete mode 100644 Tools/msi/launcher/launcher_files.wxs delete mode 100644 Tools/msi/launcher/launcher_reg.wxs delete mode 100644 Tools/msi/lib/lib.wixproj delete mode 100644 Tools/msi/lib/lib.wxs delete mode 100644 Tools/msi/lib/lib_d.wixproj delete mode 100644 Tools/msi/lib/lib_d.wxs delete mode 100644 Tools/msi/lib/lib_en-US.wxl delete mode 100644 Tools/msi/lib/lib_files.wxs delete mode 100644 Tools/msi/lib/lib_pdb.wixproj delete mode 100644 Tools/msi/lib/lib_pdb.wxs delete mode 100644 Tools/msi/make_appx.ps1 delete mode 100644 Tools/msi/make_cat.ps1 delete mode 100644 Tools/msi/make_zip.proj delete mode 100644 Tools/msi/msi.props delete mode 100644 Tools/msi/msi.targets delete mode 100644 Tools/msi/path/path.wixproj delete mode 100644 Tools/msi/path/path.wxs delete mode 100644 Tools/msi/path/path_en-US.wxl delete mode 100644 Tools/msi/pip/pip.wixproj delete mode 100644 Tools/msi/pip/pip.wxs delete mode 100644 Tools/msi/pip/pip_en-US.wxl delete mode 100644 Tools/msi/purge.py delete mode 100644 Tools/msi/sdktools.psm1 delete mode 100644 Tools/msi/sign_build.ps1 delete mode 100644 Tools/msi/tcltk/tcltk.wixproj delete mode 100644 Tools/msi/tcltk/tcltk.wxs delete mode 100644 Tools/msi/tcltk/tcltk_d.wixproj delete mode 100644 Tools/msi/tcltk/tcltk_d.wxs delete mode 100644 Tools/msi/tcltk/tcltk_en-US.wxl_template delete mode 100644 Tools/msi/tcltk/tcltk_files.wxs delete mode 100644 Tools/msi/tcltk/tcltk_pdb.wixproj delete mode 100644 Tools/msi/tcltk/tcltk_pdb.wxs delete mode 100644 Tools/msi/tcltk/tcltk_reg.wxs delete mode 100644 Tools/msi/test/test.wixproj delete mode 100644 Tools/msi/test/test.wxs delete mode 100644 Tools/msi/test/test_d.wixproj delete mode 100644 Tools/msi/test/test_d.wxs delete mode 100644 Tools/msi/test/test_en-US.wxl delete mode 100644 Tools/msi/test/test_files.wxs delete mode 100644 Tools/msi/test/test_pdb.wixproj delete mode 100644 Tools/msi/test/test_pdb.wxs delete mode 100644 Tools/msi/testrelease.bat delete mode 100644 Tools/msi/ucrt/ucrt.wixproj delete mode 100644 Tools/msi/ucrt/ucrt.wxs delete mode 100644 Tools/msi/ucrt/ucrt_en-US.wxl delete mode 100644 Tools/msi/uploadrelease.bat delete mode 100644 Tools/msi/uploadrelease.proj delete mode 100644 Tools/msi/uploadrelease.ps1 delete mode 100644 Tools/msi/wix.props delete mode 100644 Tools/nuget/build.bat delete mode 100644 Tools/nuget/make_pkg.proj delete mode 100644 Tools/nuget/python.nuspec delete mode 100644 Tools/nuget/pythonarm32.nuspec delete mode 100644 Tools/nuget/pythondaily.nuspec delete mode 100644 Tools/nuget/pythondaily.symbols.nuspec delete mode 100644 Tools/nuget/pythonx86.nuspec diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b541dbd0c23d326..0e5f1fbf530eed8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -181,22 +181,6 @@ jobs: free-threading: ${{ matrix.free-threading }} interpreter: ${{ matrix.interpreter }} - build-windows-msi: - # ${{ '' } is a hack to nest jobs under the same sidebar category. - name: Windows MSI${{ '' }} # zizmor: ignore[obfuscation] - needs: build-context - if: fromJSON(needs.build-context.outputs.run-windows-msi) - strategy: - fail-fast: false - matrix: - arch: - - x86 - - x64 - - arm64 - uses: ./.github/workflows/reusable-windows-msi.yml - with: - arch: ${{ matrix.arch }} - build-macos: name: >- macOS @@ -659,7 +643,6 @@ jobs: - check-generated-files - check-c-api-docs - build-windows - - build-windows-msi - build-macos - build-ubuntu - build-ubuntu-ssltests @@ -680,7 +663,6 @@ jobs: allowed-failures: >- build-android, build-emscripten, - build-windows-msi, build-ubuntu-ssltests, test-hypothesis, cifuzz, diff --git a/.github/workflows/reusable-context.yml b/.github/workflows/reusable-context.yml index 6595aef0377e500..c998cbff181dd12 100644 --- a/.github/workflows/reusable-context.yml +++ b/.github/workflows/reusable-context.yml @@ -47,9 +47,6 @@ on: # yamllint disable-line rule:truthy run-wasi: description: Whether to run the WASI tests value: ${{ jobs.compute-changes.outputs.run-wasi }} # bool - run-windows-msi: - description: Whether to run the MSI installer smoke tests - value: ${{ jobs.compute-changes.outputs.run-windows-msi }} # bool run-windows-tests: description: Whether to run the Windows tests value: ${{ jobs.compute-changes.outputs.run-windows-tests }} # bool @@ -73,7 +70,6 @@ jobs: run-ubuntu: ${{ steps.changes.outputs.run-ubuntu }} run-emscripten: ${{ steps.changes.outputs.run-emscripten }} run-wasi: ${{ steps.changes.outputs.run-wasi }} - run-windows-msi: ${{ steps.changes.outputs.run-windows-msi }} run-windows-tests: ${{ steps.changes.outputs.run-windows-tests }} steps: - name: Set up Python diff --git a/.github/workflows/reusable-windows-msi.yml b/.github/workflows/reusable-windows-msi.yml deleted file mode 100644 index d57046ae5ca8f00..000000000000000 --- a/.github/workflows/reusable-windows-msi.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Reusable Windows MSI - -on: - workflow_call: - inputs: - arch: - description: CPU architecture - required: true - type: string - -permissions: - contents: read - -env: - FORCE_COLOR: 1 - -jobs: - build: - name: installer for ${{ inputs.arch }} - runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2025' }} - timeout-minutes: 60 - env: - ARCH: ${{ inputs.arch }} - IncludeFreethreaded: true - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - name: Build CPython installer - run: ./Tools/msi/build.bat --doc -"${ARCH}" - shell: bash diff --git a/Misc/NEWS.d/next/Build/2026-07-10-13-25-55.gh-issue-153511.dCPBwU.rst b/Misc/NEWS.d/next/Build/2026-07-10-13-25-55.gh-issue-153511.dCPBwU.rst new file mode 100644 index 000000000000000..0d9163c8dc4d0ee --- /dev/null +++ b/Misc/NEWS.d/next/Build/2026-07-10-13-25-55.gh-issue-153511.dCPBwU.rst @@ -0,0 +1,3 @@ +Removes the legacy MSI and Nuget build scripts, the legacy ``py.exe`` +launcher (now replaced by the Python install manager), and adds a +``PC/layout`` readme. diff --git a/PC/classicAppCompat.can.xml b/PC/classicAppCompat.can.xml deleted file mode 100644 index df361f8e3e2cd32..000000000000000 --- a/PC/classicAppCompat.can.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/PC/classicAppCompat.cat b/PC/classicAppCompat.cat deleted file mode 100644 index 3d213596accffa255e1443126228f7ab1bb2899c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10984 zcmds-2UHW=*2mN6y(2ZC2!g;Q^rF&x??r{sdzU6H0Z~vu1rd-EiWET*8>k>CMUZX* zq=TSRlwNJ$1cl4J*Za!%y|><5U)D-i_Q{!(nKNha-`@W-08Vg{m`pHMBX*q%N&>^< z1YZLi;hHa?C;&2IxG92|0cL89VgTp}1}Ou% zImX|`)yp}++Y3btP8HZl!a!o==-}z?<$&S>I0!A|3{1MN_I}>}-cA8XWp6(pZ$C_c zD|iGIKtcGOf&rncZ(xMt2lxmkoPq1N?z$L1cgFx94~)GdQdtqHjs(D4!?ougNdOX7 z+a?+S@0kR_1OomoXZ!PN;D2#7;A-RDJ8$3ugyWzzgjGlm1D77;*7cUcFv=hfEo*nf z^P(GYSEdsIzA{GwdajY#x*t1GRWVzdF;%wcOdiXOve>7YQFedZuaUYU)_6G-$`j3e{CnZ zIuJab`&p@|i}I9!EQ=NVV|YlzLZW?RT6V4*tX|-`P8w8JQ@icWpiGHxq((#&!fznk zCh6@W96=LxDyJ$Uilwdv^(dUFsr^1RDlq+}{ZVT3AXVuv8`u2hxruDW+rF%NugXcu zRnMhYDeZrKKY6D1{Ls1n=&K()FWqnvJLBu>M`@B3(-?}tr7qJQg2AB>Xg&`56r7RA z04sQ_Ir*T_?+EHW9;w3t1vm-6P$QTTj3)}5cqxQ@mwheTSFUd*owk?tWd~>oKhq&d z0b<p37F$e@y4-+ zE&%~PvZA6vK|vy(J1bMf-rG~u$KBQ2$6wUm&qH)~4f3IZJ*yD3loi1bb?^~6(S-MFr?T67{F%724JzqfA{Yk{Q~^|sXq(==M20k2EzQo0LZrIancYh z1RO>*2FUNmJth)_C?HJC_cP=%kw#x$Q<<9-Fed2Pf4E1#j}LGh3c2_^MtDIu{Nd3xK3l}vToIW4bpADZz*eYTR z?)$HZ;qrSR4Roc#@d&Y%3}n%D&ZhA=e-4jCXUelND*2dEhx(IpTkay9!qR*h&a8f- z#2H#rue;mBdrylwXN6C7MWLa}hXLy-wOoGJCY~~A+ggM_`nysWZNHS&>R0^u_d0pC z@p6Z*n4v)@Z+gv7N0Z;LdLHjKj4IVAeHQrng#AqEa}fb*eMa11PW`-sB|>{DrP8^! z!y9flOVQ{4bXe>9q13pK&X3csH#|HVX(9$XuuM@{bCK4F6r zsey;#CaP9#5k4&vr<0?GO`m3wO=3&B;oN!cvJ#EnPKbZ5UH&*RBa4}gk2W-vMY^{^!p z1`{Q_(TVFfAPp-$85COYG4a0a!3@6YT6T+Hjrpm3lnBtMb44muhwjKYbI;+}W~EVU zZL0p6`7Fk90a`ORRxQt_;pBJ)kScv3RqB3Fg&ae#k_REYV0(k&HcZa_Tf#Hwj-X3` z^fV1FhGj6f6^NEFcJ9^7NohNhW4kpjrNA~uUVG!#fe zD-jS|KBjv$vcX%g?Xtp@N1Z<>8`Z3&$hM2&wKt-%N>4xYXuYafRg%p7t}J(~NWSj! zi^Nm0DJt6TTWO;%8`B~$gh_a-f+Z8l8FW5Mg(yB==Xqm@y7I;7glbgSgJAbgKSlR< zM_W926>=l4U2h7e`z|o)A^>{6y+B(JGyi5?{tI5CBL$t_O0&ZewK*+=RM4d1-qrnG zmrf*+_Dpq>w7|}0B)F+(2!F8>UUUfMPX?F}Vfwd_t_3qxmG1ALtCSzdJY!XoYOFqm z>O_++tJtwL-ypwP{6K){U7zs9MDv49^N4PjM{}od9{cXU@-jG6wZ2&K9P3!OVA;vH z%UUwVw9!Bd7M2#jDS!?fS+XJA@maq(bh5 z%iODckp|_I6ki@5as=_#`TzQ(e+-0F?$DbIFO$q<@u`rFLDvwakp z#7gK3w$EK!k)JP>KS?ybIuIil-SAeUuVVs!`04VoZnfd0QM>-euc$J9H#6?&g9&+K z1BgXUiHG%Bo+e(1dV{MAK*8k9T(l?$hA*;QsZ?TzsM;V=)xq>|C)Z_yf)s#4_K=ju zxa}A-hVPqg9OugiVIMBuypz%c?Atwn5r&}TB8M15PC@J-$`Hlvyo{5E0265J58`g4 zh6sRzJop`P{e)#V@8XNsPodiKYO16DtXYYZ3y`gmQucC6-41cIpiGjX?Z z>*MF`aLWD%20^z4ofh;}f*06qe*j`OX!JH|PwtQvB&`u(0O;-5AZeBU7HLK8cQE@m z#t-;IwAw~n{)6-ji&u21$;0bUQ;ajq3*_*N9GY)5nU3kfRWP5w9Ky<;;e=*#w3&i_ zdzEM1bZ^+}ZZ@gd+9%)o_S1D=J-MB_mgQv$Ue@tp=y56Ku@Z{VazB^O~+H?iUE6eP1l3TO(4k1Y z>*^?rvr`YK#5Z?_T;Y8i$OM5tFHTUA>+N|;HCiUH0DW+MrS;sztd8{v(!xTttX;!q zdgBePGOyIS?C{%5NAXpsMs#HoS08#}A6Uk_sHnanQdM*{dn4d;^*r9ay#0Gnb!2~$ z?plMm`ew)l+=o%8?-f~_jt9$5)Jj?@wz*;vv%-x(BV13q1W#l;b0><}b|9lB*4z(lLJy;cn#b zeG_HV>w zDp?#UD1hDqa3t&ijtIV6FhL+ncRW=%T$YLpw;8AOhgI{R2FG@R1c-y4BPos+7YDfp z0`52SzpbA8dz^b0DA1)}Y6g=POqCUS$0Z?ru|+IPjONSpHO!Yc!8o#30ST5fl}uNq z6N-sB{9Phn8tRY7W^0>OhU}{jSMGokkA0R%jXXNBVNFfu_9(NEIw(}l9gRO$)3h${ zK`+XXA*()4|90^TP0Ea9L#bsALN{-p>4W5lM%uaJ_bi3z6E7U}>?-3bw6m|*J@9JW zK=Z1W+}Rr{W9lNtA_WY9cScbLAkqg^>% zi95ciWVrLeTZY-N^Gv-XCb@0<6P*eupEmNZRT#hc);-%(o(C8+GMp76#ryEgzGcp> z9!oGodfW3=((NM|_mVZG%)^0-yqvXI`Yw0+xP5x&pdC^;eR@%+j1ir&kN z>wST!0NDpx?~Q6Bb5iX%Y_pl#dY|Uk5MhUXW!yEU*;*G}KYF{6>l?_OS$e^JT#+S9 zEuo2hJ*f{M(z0*zxHb}y=T5INaqw1~zuPLO zLf0*aRE8{D5Pg?C1hTuxtEbi^GLsa-L-zj$ad~YQnR8J z#%pY__(h45iqN1|AC%k^tYmW$7>RYyJb0Ai)3~ zycS*~R_&jHBv_MziQ+CHg#j^PwDdM1t#^nB5>g+~1+;f;kdVjzF+%>;;J?IU?ET#G zg2xjiq-clMl5=Ygm&sKYi_n?I2@B$MHLvt47n^p1b=hWbHj0`)6?-?dwkFW|c}2<@J*n z{D=>B#(y|llyzaL{L}TJ!jbbCbb-azZLLVTdfHoINwexXT{d}g?t1lgccwiP>pr{Z z2esbg4)#Ic-{88rJVPQl>(M&m(`R_=E^&5Tl`GTgS0P#}WuXBSgIg&p6Zc|A=bPYC zjt`Ioj?@Iln`Z7uf$xHdCo_DCDk0g#-`G(M~JH&0V@HszHcv-SN zqeGBoGZvlEW6*g*%fYVvuIzQwEW1#A>SZ2#2Zy5Nqal$7KJvs|g5g%<{ym%WI{LA+ z;g%+I3EzY^-d=ear+MYoxUYj?s&rDKp8G><_gUWujWuVA80^MqOMPBJ4;X~h7gJlz zA3{b9n3Dt%CKM^y=yqtm@|Xv?3K1Nefge*O(8OH(KoGRer7QBH0uQvj>!YICnVm$T=s7LK6o@8(o#cntuhXSI%{enR8B9Qbw!-EY@)Y5X# znQQ6CKR%%jjP||q>d=_pd`-nAkJovC#vbLC5+w^r6g2;D?yIhn^k~vA_516u(1^d9v#rX!*V2(+?8XQLp}R4I_eRl zCId@Iko^&J)@PwIK1(g<_qEOB96cOA#ZycZ?fI?)bue4fN-j3z^uD9Rq5D&U*9K_@ z=>-tfez&bgtT`v|8uYY!(-C1?Pebbk@&q{B&r>teQ-GcMX!UKkyh1i_M45p`! z74kLu8+cMWn``s+T}Wg|pID38Xq%4Evh?;m@%1`+0d){K@S{7Yi3`TV(I07IY>ZSj){|8Mq|w6SN@^;?s;WwgC_WIb+qw$J zZ^N3ndO8Xl2VgvXkp7N-fv)zBcpNo2e~3i@2Y<6CO`c$tKJ7F;^Q3<}O`h1hpb1h? z*zYIcU$m|8^UB~1oWDHdpYjUuV;cdySTQt!mIAkWz-6l&M}e|3Mrwf$!;Y47et zaVfm^42!>#u$NQVojMxDeN$%-ztrMI36xd(J(IIOm==h7BMP$R$7Ot%pYqD9O93=T9KbPy;p=m?P&7;cw8)1!CRx3L0%-*|{ipiAznqVQk zlVgH~9+YKIsu*|l;udkkTG4wU%FAn`?jPu1ES~;dZui<=_INz%;d zo~D4=kKArM`@(DD)nirvsoW0qFVMTdg-Q@V0AWB%7(i{Oc2+y79GKdHX(OPuV}q&P z(SIzp`-`#vxTC;8xB@-|B|yIO7jV))xG%W91%=QloPNZ^kF8EQD>&A(Ur+AkS(0w9 zC-zb?yth`T%0$mK9=AQ)A|2|@E~#fpO4A>5SMBj+t%S&Hs~4IDa_22*Ec@Ni9x@jD zUTCK-2Df0Iy*>=(k6Awd>QP5RNNUMGfoG%55bra|n{sw~>H_<7+~H%%2t>|U3G?u*l~@f z-zyojDtWD=PlJz9v7$9i!PqFiK?v&v%GL$Akf(yOw=Wp@EO%sYGOXZstb_ewvHjfrbegukJk)7=dXD1m$Vi*RntOQhO7H!YP zm?%^B=~hdV-ccG(F6;FN%n#!7RjImXR@Jc`bq8$G7Pe|j7V5F+7_I<}^CcUsNo7~E z&gCbKg(rG23|<1wY@4xBk47;M3ZC&W1Y|?J- zF9HX!Sm4M``~!DJQGXd?4YV~y9lY)RMU@SVMI9WSFsD2ML|g(q0fpVeVSqRn8p#RK zfjB~(AsC1U#0Fvj762aGg+T~t8G}Uu27Eh0Ji)gY_)P@K`G+{NEl88RJ7-8W(L9!R z4C@O&l9iv!<9%nguzeNvxQl3_m&SnYLexln)_c7<32fPE&i{j z6{6qnPbG>yva`t;G*_5j>`ZsMc;UO7UF2|+7R9B56EbuAw0!NePV;y+Waj6|Z$*Zu zx?Kz|^n5X%xykuOx<6&5`8s)OY(Z@{TRB20y82$!HC}1tJ-#W2x%HL02zyIDnotoL z^T0gc8^rN6am)`fMRaW${nY9iQBB<)Mcwn^(F@~pzGR~(0@8R%F$1>@xV&o@$04Kd zQ6vS)vg?lO!5=n?HA>e<{30;!-a#eUwj#1JnMZTYZT+~rejS`E)n}kwQJ>l41Wjaa zTMx8QnWl%H^_L@kmnJ-uFeWBKe`vK{)P$dBy`@ZVK%ly^WH9x1ky2i3*FHtvvDi() zjC?vL;N_zLwDzd|>DBajQTAs%6_)qPhg9UR8u<3*zGyOi>pEioq-O-E|Jwde_cK-9 z3H|9Tu6@#?HC+}&BJ)&u1BrooGJcNNcOoRXNFNSJ4!*QW8VNY3NuP^@b%MKqQri{x zM(+Q=aw2a(_}~FRLQEt?4TrJ8nIL`&d_2L**|m8`9J|&P+$L=j@+k1wXaE~k5b+=8 z)t+Y|h=?FCBFN|sS6hIWmF7qNhn4{;3##x|I}KvM&jug}3@$&OMFWdX1QZ26r~#1w z9DpDpl7k;Gn2-$&+;bz@{!VxEM~BjfV}uTpdk%-81b~CPk7*F9R~R1DG6{#lNpg|7 zJKv$P_`M+oJY_f*3-KW8JHqYL7On8IE0EPK`(B~yRHx6>ZdaPw=pY}WSv_H=9C9&C`&gO=oPZJV>RYBmJ61VG4JW^NWNav%> zb>t`Vn7i2e9I=RCyTYe;#>3(3{U<2mi*aXkc;QnP2o?fQR&LL%idgN3r~le%?;U;A||+SUi{2U zOjlw2q$6TZF#vXJ&j2t{6m|x{z6Y?A0Jau^9EBF76e{qPdwMXrpEujg7Y)U>+iW&u z8HYt6JJHYE0BqWSfh+ua;y}qvPdP_zF~*^H_Wg#fWvhp1QuoLG>G5>Oj5YlSp_EAH zy$cf51qecy-f?HLm1U-tcIu_O%b8QQt4ig#H0xq=rno^EgHzeiP&1xF(2n)wC6hU> zv)rHiJ5{+U_@*PTd9U#Reld@#1zWFbqCrK}%tj;;?iZ2iKhjnpyUCU-<)xyupZoHnk(kEoly;#thhUYK zh3KB^j{eXt!8xIP`pW0|=Q$63_%7$nZ74xfXsB(%6U)G+X&@lO7-4U!N>ugODyTDH M - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 diff --git a/PC/launcher-usage.txt b/PC/launcher-usage.txt deleted file mode 100644 index aad103509daa283..000000000000000 --- a/PC/launcher-usage.txt +++ /dev/null @@ -1,31 +0,0 @@ -Python Launcher for Windows Version %s - -usage: -%s [launcher-args] [python-args] [script [script-args]] - -Launcher arguments: --2 : Launch the latest Python 2.x version --3 : Launch the latest Python 3.x version --X.Y : Launch the specified Python version - -The above default to an architecture native runtime, but will select any -available. Add a "-32" to the argument to only launch 32-bit runtimes, -or add "-64" to omit 32-bit runtimes (this latter option is deprecated). - -To select a specific runtime, use the -V: options. - --V:TAG : Launch a Python runtime with the specified tag --V:COMPANY/TAG : Launch a Python runtime from the specified company and - with the specified tag - --0 --list : List the available pythons --0p --list-paths : List with paths - -If no options are given but a script is specified, the script is checked for a -shebang line. Otherwise, an active virtual environment or global default will -be selected. - -See https://docs.python.org/using/windows.html#python-launcher-for-windows for -additional configuration. - -The following help text is from Python: diff --git a/PC/launcher.c b/PC/launcher.c deleted file mode 100644 index 5667fc851aa5904..000000000000000 --- a/PC/launcher.c +++ /dev/null @@ -1,2047 +0,0 @@ -/* - * Copyright (C) 2011-2013 Vinay Sajip. - * Licensed to PSF under a contributor agreement. - * - * Based on the work of: - * - * Mark Hammond (original author of Python version) - * Curt Hagenlocher (job management) - */ - -#include -#include -#include -#include - -#define BUFSIZE 256 -#define MSGSIZE 1024 - -/* Build options. */ -#define SKIP_PREFIX -#define SEARCH_PATH - -/* Error codes */ - -#define RC_NO_STD_HANDLES 100 -#define RC_CREATE_PROCESS 101 -#define RC_BAD_VIRTUAL_PATH 102 -#define RC_NO_PYTHON 103 -#define RC_NO_MEMORY 104 -/* - * SCRIPT_WRAPPER is used to choose one of the variants of an executable built - * from this source file. If not defined, the PEP 397 Python launcher is built; - * if defined, a script launcher of the type used by setuptools is built, which - * looks for a script name related to the executable name and runs that script - * with the appropriate Python interpreter. - * - * SCRIPT_WRAPPER should be undefined in the source, and defined in a VS project - * which builds the setuptools-style launcher. - */ -#if defined(SCRIPT_WRAPPER) -#define RC_NO_SCRIPT 105 -#endif -/* - * VENV_REDIRECT is used to choose the variant that looks for an adjacent or - * one-level-higher pyvenv.cfg, and uses its "home" property to locate and - * launch the original python.exe. - */ -#if defined(VENV_REDIRECT) -#define RC_NO_VENV_CFG 106 -#define RC_BAD_VENV_CFG 107 -#endif - -/* Just for now - static definition */ - -static FILE * log_fp = NULL; - -static wchar_t * -skip_whitespace(wchar_t * p) -{ - while (*p && isspace(*p)) - ++p; - return p; -} - -static void -debug(wchar_t * format, ...) -{ - va_list va; - - if (log_fp != NULL) { - va_start(va, format); - vfwprintf_s(log_fp, format, va); - va_end(va); - } -} - -static void -winerror(int rc, wchar_t * message, int size) -{ - FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - message, size, NULL); -} - -static void -error(int rc, wchar_t * format, ... ) -{ - va_list va; - wchar_t message[MSGSIZE]; - wchar_t win_message[MSGSIZE]; - int len; - - va_start(va, format); - len = _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va); - va_end(va); - - if (rc == 0) { /* a Windows error */ - winerror(GetLastError(), win_message, MSGSIZE); - if (len >= 0) { - _snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %ls", - win_message); - } - } - -#if !defined(_WINDOWS) - fwprintf(stderr, L"%ls\n", message); -#else - MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...", - MB_OK); -#endif - exit(rc); -} - -/* - * This function is here to simplify memory management - * and to treat blank values as if they are absent. - */ -static wchar_t * get_env(wchar_t * key) -{ - /* This is not thread-safe, just like getenv */ - static wchar_t buf[BUFSIZE]; - DWORD result = GetEnvironmentVariableW(key, buf, BUFSIZE); - - if (result >= BUFSIZE) { - /* Large environment variable. Accept some leakage */ - wchar_t *buf2 = (wchar_t*)malloc(sizeof(wchar_t) * (result+1)); - if (buf2 == NULL) { - error(RC_NO_MEMORY, L"Could not allocate environment buffer"); - } - GetEnvironmentVariableW(key, buf2, result); - return buf2; - } - - if (result == 0) - /* Either some error, e.g. ERROR_ENVVAR_NOT_FOUND, - or an empty environment variable. */ - return NULL; - - return buf; -} - -#if defined(Py_DEBUG) -/* Do not define EXECUTABLEPATH_VALUE in debug builds as it'll - never point to the debug build. */ -#if defined(_WINDOWS) - -#define PYTHON_EXECUTABLE L"pythonw_d.exe" - -#else - -#define PYTHON_EXECUTABLE L"python_d.exe" - -#endif -#else -#if defined(_WINDOWS) - -#define PYTHON_EXECUTABLE L"pythonw.exe" -#define EXECUTABLEPATH_VALUE L"WindowedExecutablePath" - -#else - -#define PYTHON_EXECUTABLE L"python.exe" -#define EXECUTABLEPATH_VALUE L"ExecutablePath" - -#endif -#endif - -#define MAX_VERSION_SIZE 8 - -typedef struct { - wchar_t version[MAX_VERSION_SIZE]; /* m.n */ - int bits; /* 32 or 64 */ - wchar_t executable[MAX_PATH]; - wchar_t exe_display[MAX_PATH]; -} INSTALLED_PYTHON; - -/* - * To avoid messing about with heap allocations, just assume we can allocate - * statically and never have to deal with more versions than this. - */ -#define MAX_INSTALLED_PYTHONS 100 - -static INSTALLED_PYTHON installed_pythons[MAX_INSTALLED_PYTHONS]; - -static size_t num_installed_pythons = 0; - -/* - * To hold SOFTWARE\Python\PythonCore\X.Y...\InstallPath - * The version name can be longer than MAX_VERSION_SIZE, but will be - * truncated to just X.Y for comparisons. - */ -#define IP_BASE_SIZE 80 -#define IP_VERSION_SIZE 8 -#define IP_SIZE (IP_BASE_SIZE + IP_VERSION_SIZE) -#define CORE_PATH L"SOFTWARE\\Python\\PythonCore" -/* - * Installations from the Microsoft Store will set the same registry keys, - * but because of a limitation in Windows they cannot be enumerated normally - * (unless you have no other Python installations... which is probably false - * because that's the most likely way to get this launcher!) - * This key is under HKEY_LOCAL_MACHINE - */ -#define LOOKASIDE_PATH L"SOFTWARE\\Microsoft\\AppModel\\Lookaside\\user\\Software\\Python\\PythonCore" - -static wchar_t * location_checks[] = { - L"\\", - L"\\PCbuild\\win32\\", - L"\\PCbuild\\amd64\\", - /* To support early 32bit versions of Python that stuck the build binaries - * directly in PCbuild... */ - L"\\PCbuild\\", - NULL -}; - -static INSTALLED_PYTHON * -find_existing_python(const wchar_t * path) -{ - INSTALLED_PYTHON * result = NULL; - size_t i; - INSTALLED_PYTHON * ip; - - for (i = 0, ip = installed_pythons; i < num_installed_pythons; i++, ip++) { - if (_wcsicmp(path, ip->executable) == 0) { - result = ip; - break; - } - } - return result; -} - -static INSTALLED_PYTHON * -find_existing_python2(int bits, const wchar_t * version) -{ - INSTALLED_PYTHON * result = NULL; - size_t i; - INSTALLED_PYTHON * ip; - - for (i = 0, ip = installed_pythons; i < num_installed_pythons; i++, ip++) { - if (bits == ip->bits && _wcsicmp(version, ip->version) == 0) { - result = ip; - break; - } - } - return result; -} - -static void -_locate_pythons_for_key(HKEY root, LPCWSTR subkey, REGSAM flags, int bits, - int display_name_only) -{ - HKEY core_root, ip_key; - LSTATUS status = RegOpenKeyExW(root, subkey, 0, flags, &core_root); - wchar_t message[MSGSIZE]; - DWORD i; - size_t n; - BOOL ok, append_name; - DWORD type, data_size, attrs; - INSTALLED_PYTHON * ip, * pip; - wchar_t ip_version[IP_VERSION_SIZE]; - wchar_t ip_path[IP_SIZE]; - wchar_t * check; - wchar_t ** checkp; - wchar_t *key_name = (root == HKEY_LOCAL_MACHINE) ? L"HKLM" : L"HKCU"; - - if (status != ERROR_SUCCESS) - debug(L"locate_pythons_for_key: unable to open PythonCore key in %ls\n", - key_name); - else { - ip = &installed_pythons[num_installed_pythons]; - for (i = 0; num_installed_pythons < MAX_INSTALLED_PYTHONS; i++) { - status = RegEnumKeyW(core_root, i, ip_version, IP_VERSION_SIZE); - if (status != ERROR_SUCCESS) { - if (status != ERROR_NO_MORE_ITEMS) { - /* unexpected error */ - winerror(status, message, MSGSIZE); - debug(L"Can't enumerate registry key for version %ls: %ls\n", - ip_version, message); - } - break; - } - else { - wcsncpy_s(ip->version, MAX_VERSION_SIZE, ip_version, - MAX_VERSION_SIZE-1); - /* Still treating version as "x.y" rather than sys.winver - * When PEP 514 tags are properly used, we shouldn't need - * to strip this off here. - */ - check = wcsrchr(ip->version, L'-'); - if (check && !wcscmp(check, L"-32")) { - *check = L'\0'; - } - _snwprintf_s(ip_path, IP_SIZE, _TRUNCATE, - L"%ls\\%ls\\InstallPath", subkey, ip_version); - status = RegOpenKeyExW(root, ip_path, 0, flags, &ip_key); - if (status != ERROR_SUCCESS) { - winerror(status, message, MSGSIZE); - /* Note: 'message' already has a trailing \n*/ - debug(L"%ls\\%ls: %ls", key_name, ip_path, message); - continue; - } - data_size = sizeof(ip->executable) - 1; - append_name = FALSE; -#ifdef EXECUTABLEPATH_VALUE - status = RegQueryValueExW(ip_key, EXECUTABLEPATH_VALUE, NULL, &type, - (LPBYTE)ip->executable, &data_size); -#else - status = ERROR_FILE_NOT_FOUND; /* actual error doesn't matter */ -#endif - if (status != ERROR_SUCCESS || type != REG_SZ || !data_size) { - append_name = TRUE; - data_size = sizeof(ip->executable) - 1; - status = RegQueryValueExW(ip_key, NULL, NULL, &type, - (LPBYTE)ip->executable, &data_size); - if (status != ERROR_SUCCESS) { - winerror(status, message, MSGSIZE); - debug(L"%ls\\%ls: %ls\n", key_name, ip_path, message); - RegCloseKey(ip_key); - continue; - } - } - RegCloseKey(ip_key); - if (type != REG_SZ) { - continue; - } - - data_size = data_size / sizeof(wchar_t) - 1; /* for NUL */ - if (ip->executable[data_size - 1] == L'\\') - --data_size; /* reg value ended in a backslash */ - /* ip->executable is data_size long */ - for (checkp = location_checks; *checkp; ++checkp) { - check = *checkp; - if (append_name) { - _snwprintf_s(&ip->executable[data_size], - MAX_PATH - data_size, - MAX_PATH - data_size, - L"%ls%ls", check, PYTHON_EXECUTABLE); - } - attrs = GetFileAttributesW(ip->executable); - if (attrs == INVALID_FILE_ATTRIBUTES) { - winerror(GetLastError(), message, MSGSIZE); - debug(L"locate_pythons_for_key: %ls: %ls", - ip->executable, message); - } - else if (attrs & FILE_ATTRIBUTE_DIRECTORY) { - debug(L"locate_pythons_for_key: '%ls' is a directory\n", - ip->executable); - } - else if (find_existing_python(ip->executable)) { - debug(L"locate_pythons_for_key: %ls: already found\n", - ip->executable); - } - else { - /* check the executable type. */ - if (bits) { - ip->bits = bits; - } else { - ok = GetBinaryTypeW(ip->executable, &attrs); - if (!ok) { - debug(L"Failure getting binary type: %ls\n", - ip->executable); - } - else { - if (attrs == SCS_64BIT_BINARY) - ip->bits = 64; - else if (attrs == SCS_32BIT_BINARY) - ip->bits = 32; - else - ip->bits = 0; - } - } - if (ip->bits == 0) { - debug(L"locate_pythons_for_key: %ls: \ -invalid binary type: %X\n", - ip->executable, attrs); - } - else { - if (display_name_only) { - /* display just the executable name. This is - * primarily for the Store installs */ - const wchar_t *name = wcsrchr(ip->executable, L'\\'); - if (name) { - wcscpy_s(ip->exe_display, MAX_PATH, name+1); - } - } - if (wcschr(ip->executable, L' ') != NULL) { - /* has spaces, so quote, and set original as - * the display name */ - if (!ip->exe_display[0]) { - wcscpy_s(ip->exe_display, MAX_PATH, ip->executable); - } - n = wcslen(ip->executable); - memmove(&ip->executable[1], - ip->executable, n * sizeof(wchar_t)); - ip->executable[0] = L'\"'; - ip->executable[n + 1] = L'\"'; - ip->executable[n + 2] = L'\0'; - } - debug(L"locate_pythons_for_key: %ls \ -is a %dbit executable\n", - ip->executable, ip->bits); - if (find_existing_python2(ip->bits, ip->version)) { - debug(L"locate_pythons_for_key: %ls-%i: already \ -found\n", ip->version, ip->bits); - } - else { - ++num_installed_pythons; - pip = ip++; - if (num_installed_pythons >= - MAX_INSTALLED_PYTHONS) - break; - } - } - } - } - } - } - RegCloseKey(core_root); - } -} - -static int -compare_pythons(const void * p1, const void * p2) -{ - INSTALLED_PYTHON * ip1 = (INSTALLED_PYTHON *) p1; - INSTALLED_PYTHON * ip2 = (INSTALLED_PYTHON *) p2; - /* note reverse sorting on version */ - int result = CompareStringW(LOCALE_INVARIANT, SORT_DIGITSASNUMBERS, - ip2->version, -1, ip1->version, -1); - switch (result) { - case 0: - error(0, L"CompareStringW failed"); - return 0; - case CSTR_LESS_THAN: - return -1; - case CSTR_EQUAL: - return ip2->bits - ip1->bits; /* 64 before 32 */ - case CSTR_GREATER_THAN: - return 1; - default: - return 0; // This should never be reached. - } -} - -static void -locate_pythons_for_key(HKEY root, REGSAM flags) -{ - _locate_pythons_for_key(root, CORE_PATH, flags, 0, FALSE); -} - -static void -locate_store_pythons(void) -{ -#if defined(_M_X64) - /* 64bit process, so look in native registry */ - _locate_pythons_for_key(HKEY_LOCAL_MACHINE, LOOKASIDE_PATH, - KEY_READ, 64, TRUE); -#else - /* 32bit process, so check that we're on 64bit OS */ - BOOL f64 = FALSE; - if (IsWow64Process(GetCurrentProcess(), &f64) && f64) { - _locate_pythons_for_key(HKEY_LOCAL_MACHINE, LOOKASIDE_PATH, - KEY_READ | KEY_WOW64_64KEY, 64, TRUE); - } -#endif -} - -static void -locate_venv_python(void) -{ - static wchar_t venv_python[MAX_PATH]; - INSTALLED_PYTHON * ip; - wchar_t *virtual_env = get_env(L"VIRTUAL_ENV"); - DWORD attrs; - - /* Check for VIRTUAL_ENV environment variable */ - if (virtual_env == NULL || virtual_env[0] == L'\0') { - return; - } - - /* Check for a python executable in the venv */ - debug(L"Checking for Python executable in virtual env '%ls'\n", virtual_env); - _snwprintf_s(venv_python, MAX_PATH, _TRUNCATE, - L"%ls\\Scripts\\%ls", virtual_env, PYTHON_EXECUTABLE); - attrs = GetFileAttributesW(venv_python); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"Python executable %ls missing from virtual env\n", venv_python); - return; - } - - ip = &installed_pythons[num_installed_pythons++]; - wcscpy_s(ip->executable, MAX_PATH, venv_python); - ip->bits = 0; - wcscpy_s(ip->version, MAX_VERSION_SIZE, L"venv"); -} - -static void -locate_all_pythons(void) -{ - /* venv Python is highest priority */ - locate_venv_python(); -#if defined(_M_X64) - /* If we are a 64bit process, first hit the 32bit keys. */ - debug(L"locating Pythons in 32bit registry\n"); - locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_32KEY); - locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_32KEY); -#else - /* If we are a 32bit process on a 64bit Windows, first hit the 64bit keys.*/ - BOOL f64 = FALSE; - if (IsWow64Process(GetCurrentProcess(), &f64) && f64) { - debug(L"locating Pythons in 64bit registry\n"); - locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_64KEY); - locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_64KEY); - } -#endif - /* now hit the "native" key for this process bittedness. */ - debug(L"locating Pythons in native registry\n"); - locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ); - locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ); - /* Store-installed Python is lowest priority */ - locate_store_pythons(); - qsort(installed_pythons, num_installed_pythons, sizeof(INSTALLED_PYTHON), - compare_pythons); -} - -static INSTALLED_PYTHON * -find_python_by_version(wchar_t const * wanted_ver) -{ - INSTALLED_PYTHON * result = NULL; - INSTALLED_PYTHON * ip = installed_pythons; - size_t i, n; - size_t wlen = wcslen(wanted_ver); - int bits = 0; - - if (wcsstr(wanted_ver, L"-32")) { - bits = 32; - wlen -= wcslen(L"-32"); - } - else if (wcsstr(wanted_ver, L"-64")) { /* Added option to select 64 bit explicitly */ - bits = 64; - wlen -= wcslen(L"-64"); - } - for (i = 0; i < num_installed_pythons; i++, ip++) { - n = wcslen(ip->version); - /* - * If wlen is greater than 1, we're probably trying to find a specific - * version and thus want an exact match: 3.1 != 3.10. Otherwise, we - * just want a prefix match. - */ - if ((wlen > 1) && (n != wlen)) { - continue; - } - if (n > wlen) { - n = wlen; - } - if ((wcsncmp(ip->version, wanted_ver, n) == 0) && - /* bits == 0 => don't care */ - ((bits == 0) || (ip->bits == bits))) { - result = ip; - break; - } - } - return result; -} - - -static wchar_t appdata_ini_path[MAX_PATH]; -static wchar_t launcher_ini_path[MAX_PATH]; - -/* - * Get a value either from the environment or a configuration file. - * The key passed in will either be "python", "python2" or "python3". - */ -static wchar_t * -get_configured_value(wchar_t * key) -{ -/* - * Note: this static value is used to return a configured value - * obtained either from the environment or configuration file. - * This should be OK since there wouldn't be any concurrent calls. - */ - static wchar_t configured_value[MSGSIZE]; - wchar_t * result = NULL; - wchar_t * found_in = L"environment"; - DWORD size; - - /* First, search the environment. */ - _snwprintf_s(configured_value, MSGSIZE, _TRUNCATE, L"py_%ls", key); - result = get_env(configured_value); - if (result == NULL && appdata_ini_path[0]) { - /* Not in environment: check local configuration. */ - size = GetPrivateProfileStringW(L"defaults", key, NULL, - configured_value, MSGSIZE, - appdata_ini_path); - if (size > 0) { - result = configured_value; - found_in = appdata_ini_path; - } - } - if (result == NULL && launcher_ini_path[0]) { - /* Not in environment or local: check global configuration. */ - size = GetPrivateProfileStringW(L"defaults", key, NULL, - configured_value, MSGSIZE, - launcher_ini_path); - if (size > 0) { - result = configured_value; - found_in = launcher_ini_path; - } - } - if (result) { - debug(L"found configured value '%ls=%ls' in %ls\n", - key, result, found_in ? found_in : L"(unknown)"); - } else { - debug(L"found no configured value for '%ls'\n", key); - } - return result; -} - -static INSTALLED_PYTHON * -locate_python(wchar_t * wanted_ver, BOOL from_shebang) -{ - static wchar_t config_key [] = { L"pythonX" }; - static wchar_t * last_char = &config_key[sizeof(config_key) / - sizeof(wchar_t) - 2]; - INSTALLED_PYTHON * result = NULL; - size_t n = wcslen(wanted_ver); - wchar_t * configured_value; - - if (num_installed_pythons == 0) - locate_all_pythons(); - - if (n == 1) { /* just major version specified */ - *last_char = *wanted_ver; - configured_value = get_configured_value(config_key); - if (configured_value != NULL) - wanted_ver = configured_value; - } - if (*wanted_ver) { - result = find_python_by_version(wanted_ver); - debug(L"search for Python version '%ls' found ", wanted_ver); - if (result) { - debug(L"'%ls'\n", result->executable); - } else { - debug(L"no interpreter\n"); - } - } - else { - *last_char = L'\0'; /* look for an overall default */ - result = find_python_by_version(L"venv"); - if (result == NULL) { - configured_value = get_configured_value(config_key); - if (configured_value) - result = find_python_by_version(configured_value); - } - /* Not found a value yet - try by major version. - * If we're looking for an interpreter specified in a shebang line, - * we want to try Python 2 first, then Python 3 (for Unix and backward - * compatibility). If we're being called interactively, assume the user - * wants the latest version available, so try Python 3 first, then - * Python 2. - */ - if (result == NULL) - result = find_python_by_version(from_shebang ? L"2" : L"3"); - if (result == NULL) - result = find_python_by_version(from_shebang ? L"3" : L"2"); - debug(L"search for default Python found "); - if (result) { - debug(L"version %ls at '%ls'\n", - result->version, result->executable); - } else { - debug(L"no interpreter\n"); - } - } - return result; -} - -#if defined(SCRIPT_WRAPPER) -/* - * Check for a script located alongside the executable - */ - -#if defined(_WINDOWS) -#define SCRIPT_SUFFIX L"-script.pyw" -#else -#define SCRIPT_SUFFIX L"-script.py" -#endif - -static wchar_t wrapped_script_path[MAX_PATH]; - -/* Locate the script being wrapped. - * - * This code should store the name of the wrapped script in - * wrapped_script_path, or terminate the program with an error if there is no - * valid wrapped script file. - */ -static void -locate_wrapped_script(void) -{ - wchar_t * p; - size_t plen; - DWORD attrs; - - plen = GetModuleFileNameW(NULL, wrapped_script_path, MAX_PATH); - p = wcsrchr(wrapped_script_path, L'.'); - if (p == NULL) { - debug(L"GetModuleFileNameW returned value has no extension: %ls\n", - wrapped_script_path); - error(RC_NO_SCRIPT, L"Wrapper name '%ls' is not valid.", wrapped_script_path); - } - - wcsncpy_s(p, MAX_PATH - (p - wrapped_script_path) + 1, SCRIPT_SUFFIX, _TRUNCATE); - attrs = GetFileAttributesW(wrapped_script_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", wrapped_script_path); - error(RC_NO_SCRIPT, L"Script file '%ls' is not present.", wrapped_script_path); - } - - debug(L"Using wrapped script file '%ls'\n", wrapped_script_path); -} -#endif - -/* - * Process creation code - */ - -static BOOL -safe_duplicate_handle(HANDLE in, HANDLE * pout) -{ - BOOL ok; - HANDLE process = GetCurrentProcess(); - DWORD rc; - - *pout = NULL; - ok = DuplicateHandle(process, in, process, pout, 0, TRUE, - DUPLICATE_SAME_ACCESS); - if (!ok) { - rc = GetLastError(); - if (rc == ERROR_INVALID_HANDLE) { - debug(L"DuplicateHandle returned ERROR_INVALID_HANDLE\n"); - ok = TRUE; - } - else { - debug(L"DuplicateHandle returned %d\n", rc); - } - } - return ok; -} - -static BOOL WINAPI -ctrl_c_handler(DWORD code) -{ - return TRUE; /* We just ignore all control events. */ -} - -static void -run_child(wchar_t * cmdline) -{ - HANDLE job; - JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; - DWORD rc; - BOOL ok; - STARTUPINFOW si; - PROCESS_INFORMATION pi; - -#if defined(_WINDOWS) - /* - When explorer launches a Windows (GUI) application, it displays - the "app starting" (the "pointer + hourglass") cursor for a number - of seconds, or until the app does something UI-ish (eg, creating a - window, or fetching a message). As this launcher doesn't do this - directly, that cursor remains even after the child process does these - things. We avoid that by doing a simple post+get message. - See http://bugs.python.org/issue17290 - */ - MSG msg; - - PostMessage(0, 0, 0, 0); - GetMessage(&msg, 0, 0, 0); -#endif - - debug(L"run_child: about to run '%ls'\n", cmdline); - job = CreateJobObject(NULL, NULL); - ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation, - &info, sizeof(info), &rc); - if (!ok || (rc != sizeof(info)) || !job) - error(RC_CREATE_PROCESS, L"Job information querying failed"); - info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | - JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; - ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info, - sizeof(info)); - if (!ok) - error(RC_CREATE_PROCESS, L"Job information setting failed"); - memset(&si, 0, sizeof(si)); - GetStartupInfoW(&si); - ok = safe_duplicate_handle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput); - if (!ok) - error(RC_NO_STD_HANDLES, L"stdin duplication failed"); - ok = safe_duplicate_handle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput); - if (!ok) - error(RC_NO_STD_HANDLES, L"stdout duplication failed"); - ok = safe_duplicate_handle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError); - if (!ok) - error(RC_NO_STD_HANDLES, L"stderr duplication failed"); - - ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE); - if (!ok) - error(RC_CREATE_PROCESS, L"control handler setting failed"); - - si.dwFlags = STARTF_USESTDHANDLES; - ok = CreateProcessW(NULL, cmdline, NULL, NULL, TRUE, - 0, NULL, NULL, &si, &pi); - if (!ok) - error(RC_CREATE_PROCESS, L"Unable to create process using '%ls'", cmdline); - AssignProcessToJobObject(job, pi.hProcess); - CloseHandle(pi.hThread); - WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE); - ok = GetExitCodeProcess(pi.hProcess, &rc); - if (!ok) - error(RC_CREATE_PROCESS, L"Failed to get exit code of process"); - debug(L"child process exit code: %d\n", rc); - exit(rc); -} - -static void -invoke_child(wchar_t * executable, wchar_t * suffix, wchar_t * cmdline) -{ - wchar_t * child_command; - size_t child_command_size; - BOOL no_suffix = (suffix == NULL) || (*suffix == L'\0'); - BOOL no_cmdline = (*cmdline == L'\0'); - - if (no_suffix && no_cmdline) - run_child(executable); - else { - if (no_suffix) { - /* add 2 for space separator + terminating NUL. */ - child_command_size = wcslen(executable) + wcslen(cmdline) + 2; - } - else { - /* add 3 for 2 space separators + terminating NUL. */ - child_command_size = wcslen(executable) + wcslen(suffix) + - wcslen(cmdline) + 3; - } - child_command = calloc(child_command_size, sizeof(wchar_t)); - if (child_command == NULL) - error(RC_CREATE_PROCESS, L"unable to allocate %zd bytes for child command.", - child_command_size); - if (no_suffix) - _snwprintf_s(child_command, child_command_size, - child_command_size - 1, L"%ls %ls", - executable, cmdline); - else - _snwprintf_s(child_command, child_command_size, - child_command_size - 1, L"%ls %ls %ls", - executable, suffix, cmdline); - run_child(child_command); - free(child_command); - } -} - -typedef struct { - wchar_t *shebang; - BOOL search; -} SHEBANG; - -static SHEBANG builtin_virtual_paths [] = { - { L"/usr/bin/env python", TRUE }, - { L"/usr/bin/python", FALSE }, - { L"/usr/local/bin/python", FALSE }, - { L"python", FALSE }, - { NULL, FALSE }, -}; - -/* For now, a static array of commands. */ - -#define MAX_COMMANDS 100 - -typedef struct { - wchar_t key[MAX_PATH]; - wchar_t value[MSGSIZE]; -} COMMAND; - -static COMMAND commands[MAX_COMMANDS]; -static int num_commands = 0; - -#if defined(SKIP_PREFIX) - -static wchar_t * builtin_prefixes [] = { - /* These must be in an order that the longest matches should be found, - * i.e. if the prefix is "/usr/bin/env ", it should match that entry - * *before* matching "/usr/bin/". - */ - L"/usr/bin/env ", - L"/usr/bin/", - L"/usr/local/bin/", - NULL -}; - -static wchar_t * skip_prefix(wchar_t * name) -{ - wchar_t ** pp = builtin_prefixes; - wchar_t * result = name; - wchar_t * p; - size_t n; - - for (; p = *pp; pp++) { - n = wcslen(p); - if (_wcsnicmp(p, name, n) == 0) { - result += n; /* skip the prefix */ - if (p[n - 1] == L' ') /* No empty strings in table, so n > 1 */ - result = skip_whitespace(result); - break; - } - } - return result; -} - -#endif - -#if defined(SEARCH_PATH) - -static COMMAND path_command; - -static COMMAND * find_on_path(wchar_t * name) -{ - wchar_t * pathext; - size_t varsize; - wchar_t * context = NULL; - wchar_t * extension; - COMMAND * result = NULL; - DWORD len; - errno_t rc; - - wcscpy_s(path_command.key, MAX_PATH, name); - if (wcschr(name, L'.') != NULL) { - /* assume it has an extension. */ - len = SearchPathW(NULL, name, NULL, MSGSIZE, path_command.value, NULL); - if (len) { - result = &path_command; - } - } - else { - /* No extension - search using registered extensions. */ - rc = _wdupenv_s(&pathext, &varsize, L"PATHEXT"); - if (rc == 0) { - extension = wcstok_s(pathext, L";", &context); - while (extension) { - len = SearchPathW(NULL, name, extension, MSGSIZE, path_command.value, NULL); - if (len) { - result = &path_command; - break; - } - extension = wcstok_s(NULL, L";", &context); - } - free(pathext); - } - } - return result; -} - -#endif - -static COMMAND * find_command(wchar_t * name) -{ - COMMAND * result = NULL; - COMMAND * cp = commands; - int i; - - for (i = 0; i < num_commands; i++, cp++) { - if (_wcsicmp(cp->key, name) == 0) { - result = cp; - break; - } - } -#if defined(SEARCH_PATH) - if (result == NULL) - result = find_on_path(name); -#endif - return result; -} - -static void -update_command(COMMAND * cp, wchar_t * name, wchar_t * cmdline) -{ - wcsncpy_s(cp->key, MAX_PATH, name, _TRUNCATE); - wcsncpy_s(cp->value, MSGSIZE, cmdline, _TRUNCATE); -} - -static void -add_command(wchar_t * name, wchar_t * cmdline) -{ - if (num_commands >= MAX_COMMANDS) { - debug(L"can't add %ls = '%ls': no room\n", name, cmdline); - } - else { - COMMAND * cp = &commands[num_commands++]; - - update_command(cp, name, cmdline); - } -} - -static void -read_config_file(wchar_t * config_path) -{ - wchar_t keynames[MSGSIZE]; - wchar_t value[MSGSIZE]; - DWORD read; - wchar_t * key; - COMMAND * cp; - wchar_t * cmdp; - - read = GetPrivateProfileStringW(L"commands", NULL, NULL, keynames, MSGSIZE, - config_path); - if (read == MSGSIZE - 1) { - debug(L"read_commands: %ls: not enough space for names\n", config_path); - } - key = keynames; - while (*key) { - read = GetPrivateProfileStringW(L"commands", key, NULL, value, MSGSIZE, - config_path); - if (read == MSGSIZE - 1) { - debug(L"read_commands: %ls: not enough space for %ls\n", - config_path, key); - } - cmdp = skip_whitespace(value); - if (*cmdp) { - cp = find_command(key); - if (cp == NULL) - add_command(key, value); - else - update_command(cp, key, value); - } - key += wcslen(key) + 1; - } -} - -static void read_commands(void) -{ - if (launcher_ini_path[0]) - read_config_file(launcher_ini_path); - if (appdata_ini_path[0]) - read_config_file(appdata_ini_path); -} - -static BOOL -parse_shebang(wchar_t * shebang_line, int nchars, wchar_t ** command, - wchar_t ** suffix, BOOL *search) -{ - BOOL rc = FALSE; - SHEBANG * vpp; - size_t plen; - wchar_t * p; - wchar_t zapped; - wchar_t * endp = shebang_line + nchars - 1; - COMMAND * cp; - wchar_t * skipped; - - *command = NULL; /* failure return */ - *suffix = NULL; - *search = FALSE; - - if ((*shebang_line++ == L'#') && (*shebang_line++ == L'!')) { - shebang_line = skip_whitespace(shebang_line); - if (*shebang_line) { - *command = shebang_line; - for (vpp = builtin_virtual_paths; vpp->shebang; ++vpp) { - plen = wcslen(vpp->shebang); - if (wcsncmp(shebang_line, vpp->shebang, plen) == 0) { - rc = TRUE; - *search = vpp->search; - /* We can do this because all builtin commands contain - * "python". - */ - *command = wcsstr(shebang_line, L"python"); - break; - } - } - if (vpp->shebang == NULL) { - /* - * Not found in builtins - look in customized commands. - * - * We can't permanently modify the shebang line in case - * it's not a customized command, but we can temporarily - * stick a NUL after the command while searching for it, - * then put back the char we zapped. - */ -#if defined(SKIP_PREFIX) - skipped = skip_prefix(shebang_line); -#else - skipped = shebang_line; -#endif - p = wcspbrk(skipped, L" \t\r\n"); - if (p != NULL) { - zapped = *p; - *p = L'\0'; - } - cp = find_command(skipped); - if (p != NULL) - *p = zapped; - if (cp != NULL) { - *command = cp->value; - if (p != NULL) - *suffix = skip_whitespace(p); - } - } - /* remove trailing whitespace */ - while ((endp > shebang_line) && isspace(*endp)) - --endp; - if (endp > shebang_line) - endp[1] = L'\0'; - } - } - return rc; -} - -/* #define CP_UTF8 65001 defined in winnls.h */ -#define CP_UTF16LE 1200 -#define CP_UTF16BE 1201 -#define CP_UTF32LE 12000 -#define CP_UTF32BE 12001 - -typedef struct { - int length; - char sequence[4]; - UINT code_page; -} BOM; - -/* - * Strictly, we don't need to handle UTF-16 and UTF-32, since Python itself - * doesn't. Never mind, one day it might - there's no harm leaving it in. - */ -static BOM BOMs[] = { - { 3, { 0xEF, 0xBB, 0xBF }, CP_UTF8 }, /* UTF-8 - keep first */ - /* Test UTF-32LE before UTF-16LE since UTF-16LE BOM is a prefix - * of UTF-32LE BOM. */ - { 4, { 0xFF, 0xFE, 0x00, 0x00 }, CP_UTF32LE }, /* UTF-32LE */ - { 4, { 0x00, 0x00, 0xFE, 0xFF }, CP_UTF32BE }, /* UTF-32BE */ - { 2, { 0xFF, 0xFE }, CP_UTF16LE }, /* UTF-16LE */ - { 2, { 0xFE, 0xFF }, CP_UTF16BE }, /* UTF-16BE */ - { 0 } /* sentinel */ -}; - -static BOM * -find_BOM(char * buffer) -{ -/* - * Look for a BOM in the input and return a pointer to the - * corresponding structure, or NULL if not found. - */ - BOM * result = NULL; - BOM *bom; - - for (bom = BOMs; bom->length; bom++) { - if (strncmp(bom->sequence, buffer, bom->length) == 0) { - result = bom; - break; - } - } - return result; -} - -static char * -find_terminator(char * buffer, int len, BOM *bom) -{ - char * result = NULL; - char * end = buffer + len; - char * p; - char c; - int cp; - - for (p = buffer; p < end; p++) { - c = *p; - if (c == '\r') { - result = p; - break; - } - if (c == '\n') { - result = p; - break; - } - } - if (result != NULL) { - cp = bom->code_page; - - /* adjustments to include all bytes of the char */ - /* no adjustment needed for UTF-8 or big endian */ - if (cp == CP_UTF16LE) - ++result; - else if (cp == CP_UTF32LE) - result += 3; - ++result; /* point just past terminator */ - } - return result; -} - -static BOOL -validate_version(wchar_t * p) -{ - /* - Version information should start with the major version, - Optionally followed by a period and a minor version, - Optionally followed by a minus and one of 32 or 64. - Valid examples: - 2 - 3 - 2.7 - 3.6 - 2.7-32 - The intent is to add to the valid patterns: - 3.10 - 3-32 - 3.6-64 - 3-64 - */ - BOOL result = (p != NULL); /* Default to False if null pointer. */ - - result = result && iswdigit(*p); /* Result = False if first string element is not a digit. */ - - while (result && iswdigit(*p)) /* Require a major version */ - ++p; /* Skip all leading digit(s) */ - if (result && (*p == L'.')) /* Allow . for major minor separator.*/ - { - result = iswdigit(*++p); /* Must be at least one digit */ - while (result && iswdigit(*++p)) ; /* Skip any more Digits */ - } - if (result && (*p == L'-')) { /* Allow - for Bits Separator */ - switch(*++p){ - case L'3': /* 3 is OK */ - result = (*++p == L'2') && !*++p; /* only if followed by 2 and ended.*/ - break; - case L'6': /* 6 is OK */ - result = (*++p == L'4') && !*++p; /* only if followed by 4 and ended.*/ - break; - default: - result = FALSE; - break; - } - } - result = result && !*p; /* Must have reached EOS */ - return result; - -} - -typedef struct { - unsigned short min; - unsigned short max; - wchar_t version[MAX_VERSION_SIZE]; -} PYC_MAGIC; - -static PYC_MAGIC magic_values[] = { - { 50823, 50823, L"2.0" }, - { 60202, 60202, L"2.1" }, - { 60717, 60717, L"2.2" }, - { 62011, 62021, L"2.3" }, - { 62041, 62061, L"2.4" }, - { 62071, 62131, L"2.5" }, - { 62151, 62161, L"2.6" }, - { 62171, 62211, L"2.7" }, - { 3000, 3131, L"3.0" }, - { 3141, 3151, L"3.1" }, - { 3160, 3180, L"3.2" }, - { 3190, 3230, L"3.3" }, - { 3250, 3310, L"3.4" }, - { 3320, 3351, L"3.5" }, - { 3360, 3379, L"3.6" }, - { 3390, 3399, L"3.7" }, - { 3400, 3419, L"3.8" }, - { 3420, 3429, L"3.9" }, - { 3430, 3449, L"3.10" }, - /* Allow 50 magic numbers per version from here on */ - { 3450, 3499, L"3.11" }, - { 3500, 3549, L"3.12" }, - { 3550, 3599, L"3.13" }, - { 3600, 3649, L"3.14" }, - { 3650, 3699, L"3.15" }, - { 3700, 3749, L"3.16" }, - { 0 } -}; - -static INSTALLED_PYTHON * -find_by_magic(unsigned short magic) -{ - INSTALLED_PYTHON * result = NULL; - PYC_MAGIC * mp; - - for (mp = magic_values; mp->min; mp++) { - if ((magic >= mp->min) && (magic <= mp->max)) { - result = locate_python(mp->version, FALSE); - if (result != NULL) - break; - } - } - return result; -} - -static void -maybe_handle_shebang(wchar_t ** argv, wchar_t * cmdline) -{ -/* - * Look for a shebang line in the first argument. If found - * and we spawn a child process, this never returns. If it - * does return then we process the args "normally". - * - * argv[0] might be a filename with a shebang. - */ - FILE * fp; - errno_t rc = _wfopen_s(&fp, *argv, L"rb"); - char buffer[BUFSIZE]; - wchar_t shebang_line[BUFSIZE + 1]; - size_t read; - char *p; - char * start; - char * shebang_alias = (char *) shebang_line; - BOM* bom; - int i, j, nchars = 0; - int header_len; - BOOL is_virt; - BOOL search; - wchar_t * command; - wchar_t * suffix; - COMMAND *cmd = NULL; - INSTALLED_PYTHON * ip; - - if (rc == 0) { - read = fread(buffer, sizeof(char), BUFSIZE, fp); - debug(L"maybe_handle_shebang: read %zd bytes\n", read); - fclose(fp); - - if ((read >= 4) && (buffer[3] == '\n') && (buffer[2] == '\r')) { - ip = find_by_magic((((unsigned char)buffer[1]) << 8 | - (unsigned char)buffer[0]) & 0xFFFF); - if (ip != NULL) { - debug(L"script file is compiled against Python %ls\n", - ip->version); - invoke_child(ip->executable, NULL, cmdline); - } - } - /* Look for BOM */ - bom = find_BOM(buffer); - if (bom == NULL) { - start = buffer; - debug(L"maybe_handle_shebang: BOM not found, using UTF-8\n"); - bom = BOMs; /* points to UTF-8 entry - the default */ - } - else { - debug(L"maybe_handle_shebang: BOM found, code page %u\n", - bom->code_page); - start = &buffer[bom->length]; - } - p = find_terminator(start, BUFSIZE, bom); - /* - * If no CR or LF was found in the heading, - * we assume it's not a shebang file. - */ - if (p == NULL) { - debug(L"maybe_handle_shebang: No line terminator found\n"); - } - else { - /* - * Found line terminator - parse the shebang. - * - * Strictly, we don't need to handle UTF-16 anf UTF-32, - * since Python itself doesn't. - * Never mind, one day it might. - */ - header_len = (int) (p - start); - switch(bom->code_page) { - case CP_UTF8: - nchars = MultiByteToWideChar(bom->code_page, - 0, - start, header_len, shebang_line, - BUFSIZE); - break; - case CP_UTF16BE: - if (header_len % 2 != 0) { - debug(L"maybe_handle_shebang: UTF-16BE, but an odd number \ -of bytes: %d\n", header_len); - /* nchars = 0; Not needed - initialised to 0. */ - } - else { - for (i = header_len; i > 0; i -= 2) { - shebang_alias[i - 1] = start[i - 2]; - shebang_alias[i - 2] = start[i - 1]; - } - nchars = header_len / sizeof(wchar_t); - } - break; - case CP_UTF16LE: - if ((header_len % 2) != 0) { - debug(L"UTF-16LE, but an odd number of bytes: %d\n", - header_len); - /* nchars = 0; Not needed - initialised to 0. */ - } - else { - /* no actual conversion needed. */ - memcpy(shebang_line, start, header_len); - nchars = header_len / sizeof(wchar_t); - } - break; - case CP_UTF32BE: - if (header_len % 4 != 0) { - debug(L"UTF-32BE, but not divisible by 4: %d\n", - header_len); - /* nchars = 0; Not needed - initialised to 0. */ - } - else { - for (i = header_len, j = header_len / 2; i > 0; i -= 4, - j -= 2) { - shebang_alias[j - 1] = start[i - 2]; - shebang_alias[j - 2] = start[i - 1]; - } - nchars = header_len / sizeof(wchar_t); - } - break; - case CP_UTF32LE: - if (header_len % 4 != 0) { - debug(L"UTF-32LE, but not divisible by 4: %d\n", - header_len); - /* nchars = 0; Not needed - initialised to 0. */ - } - else { - for (i = header_len, j = header_len / 2; i > 0; i -= 4, - j -= 2) { - shebang_alias[j - 1] = start[i - 3]; - shebang_alias[j - 2] = start[i - 4]; - } - nchars = header_len / sizeof(wchar_t); - } - break; - } - if (nchars > 0) { - shebang_line[--nchars] = L'\0'; - is_virt = parse_shebang(shebang_line, nchars, &command, - &suffix, &search); - if (command != NULL) { - debug(L"parse_shebang: found command: %ls\n", command); - if (!is_virt) { - invoke_child(command, suffix, cmdline); - } - else { - suffix = wcschr(command, L' '); - if (suffix != NULL) { - *suffix++ = L'\0'; - suffix = skip_whitespace(suffix); - } - if (wcsncmp(command, L"python", 6)) - error(RC_BAD_VIRTUAL_PATH, L"Unknown virtual \ -path '%ls'", command); - command += 6; /* skip past "python" */ - if (search && ((*command == L'\0') || isspace(*command))) { - /* Command is eligible for path search, and there - * is no version specification. - */ - debug(L"searching PATH for python executable\n"); - cmd = find_on_path(PYTHON_EXECUTABLE); - debug(L"Python on path: %ls\n", cmd ? cmd->value : L""); - if (cmd) { - debug(L"located python on PATH: %ls\n", cmd->value); - invoke_child(cmd->value, suffix, cmdline); - /* Exit here, as we have found the command */ - return; - } - /* FALL THROUGH: No python found on PATH, so fall - * back to locating the correct installed python. - */ - } - if (*command && !validate_version(command)) - error(RC_BAD_VIRTUAL_PATH, L"Invalid version \ -specification: '%ls'.\nIn the first line of the script, 'python' needs to be \ -followed by a valid version specifier.\nPlease check the documentation.", - command); - /* TODO could call validate_version(command) */ - ip = locate_python(command, TRUE); - if (ip == NULL) { - error(RC_NO_PYTHON, L"Requested Python version \ -(%ls) is not installed", command); - } - else { - invoke_child(ip->executable, suffix, cmdline); - } - } - } - } - } - } -} - -static wchar_t * -skip_me(wchar_t * cmdline) -{ - BOOL quoted; - wchar_t c; - wchar_t * result = cmdline; - - quoted = cmdline[0] == L'\"'; - if (!quoted) - c = L' '; - else { - c = L'\"'; - ++result; - } - result = wcschr(result, c); - if (result == NULL) /* when, for example, just exe name on command line */ - result = L""; - else { - ++result; /* skip past space or closing quote */ - result = skip_whitespace(result); - } - return result; -} - -static DWORD version_high = 0; -static DWORD version_low = 0; - -static void -get_version_info(wchar_t * version_text, size_t size) -{ - WORD maj, min, rel, bld; - - if (!version_high && !version_low) - wcsncpy_s(version_text, size, L"0.1", _TRUNCATE); /* fallback */ - else { - maj = HIWORD(version_high); - min = LOWORD(version_high); - rel = HIWORD(version_low); - bld = LOWORD(version_low); - _snwprintf_s(version_text, size, _TRUNCATE, L"%d.%d.%d.%d", maj, - min, rel, bld); - } -} - -static void -show_help_text(wchar_t ** argv) -{ - wchar_t version_text [MAX_PATH]; -#if defined(_M_X64) - BOOL canDo64bit = TRUE; -#else - /* If we are a 32bit process on a 64bit Windows, first hit the 64bit keys. */ - BOOL canDo64bit = FALSE; - IsWow64Process(GetCurrentProcess(), &canDo64bit); -#endif - - get_version_info(version_text, MAX_PATH); - fwprintf(stdout, L"\ -Python Launcher for Windows Version %ls\n\n", version_text); - fwprintf(stdout, L"\ -usage:\n\ -%ls [launcher-args] [python-args] [script [script-args]]\n\n", argv[0]); - fputws(L"\ -Launcher arguments:\n\n\ --2 : Launch the latest Python 2.x version\n\ --3 : Launch the latest Python 3.x version\n\ --X.Y : Launch the specified Python version\n", stdout); - if (canDo64bit) { - fputws(L"\ - The above all default to 64 bit if a matching 64 bit python is present.\n\ --X.Y-32: Launch the specified 32bit Python version\n\ --X-32 : Launch the latest 32bit Python X version\n\ --X.Y-64: Launch the specified 64bit Python version\n\ --X-64 : Launch the latest 64bit Python X version", stdout); - } - fputws(L"\n-0 --list : List the available pythons", stdout); - fputws(L"\n-0p --list-paths : List with paths", stdout); - fputws(L"\n\n If no script is specified the specified interpreter is opened.", stdout); - fputws(L"\nIf an exact version is not given, using the latest version can be overridden by", stdout); - fputws(L"\nany of the following, (in priority order):", stdout); - fputws(L"\n An active virtual environment", stdout); - fputws(L"\n A shebang line in the script (if present)", stdout); - fputws(L"\n With -2 or -3 flag a matching PY_PYTHON2 or PY_PYTHON3 Environment variable", stdout); - fputws(L"\n A PY_PYTHON Environment variable", stdout); - fputws(L"\n From [defaults] in py.ini in your %LOCALAPPDATA%\\py.ini", stdout); - fputws(L"\n From [defaults] in py.ini beside py.exe (use `where py` to locate)", stdout); - fputws(L"\n\nThe following help text is from Python:\n\n", stdout); - fflush(stdout); -} - -static BOOL -show_python_list(wchar_t ** argv) -{ - /* - * Display options -0 - */ - INSTALLED_PYTHON * result = NULL; - INSTALLED_PYTHON * ip = installed_pythons; /* List of installed pythons */ - INSTALLED_PYTHON * defpy = locate_python(L"", FALSE); - size_t i = 0; - wchar_t *p = argv[1]; - wchar_t *ver_fmt = L"-%ls-%d"; - wchar_t *fmt = L"\n %ls"; - wchar_t *defind = L" *"; /* Default indicator */ - - /* - * Output informational messages to stderr to keep output - * clean for use in pipes, etc. - */ - fwprintf(stderr, - L"Installed Pythons found by %s Launcher for Windows", argv[0]); - if (!_wcsicmp(p, L"-0p") || !_wcsicmp(p, L"--list-paths")) - fmt = L"\n %-15ls%ls"; /* include path */ - - if (num_installed_pythons == 0) /* We have somehow got here without searching for pythons */ - locate_all_pythons(); /* Find them, Populates installed_pythons */ - - if (num_installed_pythons == 0) /* No pythons found */ - fwprintf(stderr, L"\nNo Installed Pythons Found!"); - else - { - for (i = 0; i < num_installed_pythons; i++, ip++) { - wchar_t version[BUFSIZ]; - if (wcscmp(ip->version, L"venv") == 0) { - wcscpy_s(version, BUFSIZ, L"(venv)"); - } - else { - swprintf_s(version, BUFSIZ, ver_fmt, ip->version, ip->bits); - } - - if (ip->exe_display[0]) { - fwprintf(stdout, fmt, version, ip->exe_display); - } - else { - fwprintf(stdout, fmt, version, ip->executable); - } - /* If there is a default indicate it */ - if (defpy == ip) - fwprintf(stderr, defind); - } - } - - if ((defpy == NULL) && (num_installed_pythons > 0)) - /* We have pythons but none is the default */ - fwprintf(stderr, L"\n\nCan't find a Default Python.\n\n"); - else - fwprintf(stderr, L"\n\n"); /* End with a blank line */ - return FALSE; /* If this has been called we cannot continue */ -} - -#if defined(VENV_REDIRECT) - -static int -find_home_value(const char *buffer, const char **start, DWORD *length) -{ - for (const char *s = strstr(buffer, "home"); s; s = strstr(s + 1, "\nhome")) { - if (*s == '\n') { - ++s; - } - for (int i = 4; i > 0 && *s; --i, ++s); - - while (*s && iswspace(*s)) { - ++s; - } - if (*s != L'=') { - continue; - } - - do { - ++s; - } while (*s && iswspace(*s)); - - *start = s; - char *nl = strchr(s, '\n'); - if (nl) { - *length = (DWORD)((ptrdiff_t)nl - (ptrdiff_t)s); - } else { - *length = (DWORD)strlen(s); - } - return 1; - } - return 0; -} -#endif - -static wchar_t * -wcsdup_pad(const wchar_t *s, int padding, int *newlen) -{ - size_t len = wcslen(s); - len += 1 + padding; - wchar_t *r = (wchar_t *)malloc(len * sizeof(wchar_t)); - if (!r) { - return NULL; - } - if (wcscpy_s(r, len, s)) { - free(r); - return NULL; - } - *newlen = len < MAXINT ? (int)len : MAXINT; - return r; -} - -static wchar_t * -get_process_name(void) -{ - DWORD bufferLen = MAX_PATH; - DWORD len = bufferLen; - wchar_t *r = NULL; - - while (!r) { - r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t)); - if (!r) { - error(RC_NO_MEMORY, L"out of memory"); - return NULL; - } - len = GetModuleFileNameW(NULL, r, bufferLen); - if (len == 0) { - free(r); - error(0, L"Failed to get module name"); - return NULL; - } else if (len == bufferLen && - GetLastError() == ERROR_INSUFFICIENT_BUFFER) { - free(r); - r = NULL; - bufferLen *= 2; - } - } - - return r; -} - -static int -process(int argc, wchar_t ** argv) -{ - wchar_t * wp; - wchar_t * command; - wchar_t * executable; - wchar_t * p; - wchar_t * argv0; - int rc = 0; - INSTALLED_PYTHON * ip; - BOOL valid; - DWORD size, attrs; - wchar_t message[MSGSIZE]; - void * version_data; - VS_FIXEDFILEINFO * file_info; - UINT block_size; -#if defined(VENV_REDIRECT) - wchar_t * venv_cfg_path; - int newlen; -#elif defined(SCRIPT_WRAPPER) - wchar_t * newcommand; - wchar_t * av[2]; - int newlen; - HRESULT hr; - int index; -#else - HRESULT hr; - int index; -#endif - - setvbuf(stderr, (char *)NULL, _IONBF, 0); - wp = get_env(L"PYLAUNCH_DEBUG"); - if ((wp != NULL) && (*wp != L'\0')) - log_fp = stderr; - -#if defined(_M_X64) - debug(L"launcher build: 64bit\n"); -#else - debug(L"launcher build: 32bit\n"); -#endif -#if defined(_WINDOWS) - debug(L"launcher executable: Windows\n"); -#else - debug(L"launcher executable: Console\n"); -#endif -#if !defined(VENV_REDIRECT) - /* Get the local appdata folder (non-roaming) */ - hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, - NULL, 0, appdata_ini_path); - if (hr != S_OK) { - debug(L"SHGetFolderPath failed: %X\n", hr); - appdata_ini_path[0] = L'\0'; - } - else { - wcsncat_s(appdata_ini_path, MAX_PATH, L"\\py.ini", _TRUNCATE); - attrs = GetFileAttributesW(appdata_ini_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", appdata_ini_path); - appdata_ini_path[0] = L'\0'; - } else { - debug(L"Using local configuration file '%ls'\n", appdata_ini_path); - } - } -#endif - argv0 = get_process_name(); - size = GetFileVersionInfoSizeW(argv0, &size); - if (size == 0) { - winerror(GetLastError(), message, MSGSIZE); - debug(L"GetFileVersionInfoSize failed: %ls\n", message); - } - else { - version_data = malloc(size); - if (version_data) { - valid = GetFileVersionInfoW(argv0, 0, size, - version_data); - if (!valid) - debug(L"GetFileVersionInfo failed: %X\n", GetLastError()); - else { - valid = VerQueryValueW(version_data, L"\\", - (LPVOID *) &file_info, &block_size); - if (!valid) - debug(L"VerQueryValue failed: %X\n", GetLastError()); - else { - version_high = file_info->dwFileVersionMS; - version_low = file_info->dwFileVersionLS; - } - } - free(version_data); - } - } - -#if defined(VENV_REDIRECT) - /* Allocate some extra space for new filenames */ - venv_cfg_path = wcsdup_pad(argv0, 32, &newlen); - if (!venv_cfg_path) { - error(RC_NO_MEMORY, L"Failed to copy module name"); - } - p = wcsrchr(venv_cfg_path, L'\\'); - - if (p == NULL) { - error(RC_NO_VENV_CFG, L"No pyvenv.cfg file"); - } - p[0] = L'\0'; - wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg"); - attrs = GetFileAttributesW(venv_cfg_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", venv_cfg_path); - p[0] = '\0'; - p = wcsrchr(venv_cfg_path, L'\\'); - if (p != NULL) { - p[0] = '\0'; - wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg"); - attrs = GetFileAttributesW(venv_cfg_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", venv_cfg_path); - error(RC_NO_VENV_CFG, L"No pyvenv.cfg file"); - } - } - } - debug(L"Using venv configuration file '%ls'\n", venv_cfg_path); -#else - /* Allocate some extra space for new filenames */ - if (wcscpy_s(launcher_ini_path, MAX_PATH, argv0)) { - error(RC_NO_MEMORY, L"Failed to copy module name"); - } - p = wcsrchr(launcher_ini_path, L'\\'); - - if (p == NULL) { - debug(L"GetModuleFileNameW returned value has no backslash: %ls\n", - launcher_ini_path); - launcher_ini_path[0] = L'\0'; - } - else { - p[0] = L'\0'; - wcscat_s(launcher_ini_path, MAX_PATH, L"\\py.ini"); - attrs = GetFileAttributesW(launcher_ini_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", launcher_ini_path); - launcher_ini_path[0] = L'\0'; - } else { - debug(L"Using global configuration file '%ls'\n", launcher_ini_path); - } - } -#endif - - command = skip_me(GetCommandLineW()); - debug(L"Called with command line: %ls\n", command); - -#if !defined(VENV_REDIRECT) - /* bpo-35811: The __PYVENV_LAUNCHER__ variable is used to - * override sys.executable and locate the original prefix path. - * However, if it is silently inherited by a non-venv Python - * process, that process will believe it is running in the venv - * still. This is the only place where *we* can clear it (that is, - * when py.exe is being used to launch Python), so we do. - */ - SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", NULL); -#endif - -#if defined(SCRIPT_WRAPPER) - /* The launcher is being used in "script wrapper" mode. - * There should therefore be a Python script named -script.py in - * the same directory as the launcher executable. - * Put the script name into argv as the first (script name) argument. - */ - - /* Get the wrapped script name - if the script is not present, this will - * terminate the program with an error. - */ - locate_wrapped_script(); - - /* Add the wrapped script to the start of command */ - newlen = wcslen(wrapped_script_path) + wcslen(command) + 2; /* ' ' + NUL */ - newcommand = malloc(sizeof(wchar_t) * newlen); - if (!newcommand) { - error(RC_NO_MEMORY, L"Could not allocate new command line"); - } - else { - wcscpy_s(newcommand, newlen, wrapped_script_path); - wcscat_s(newcommand, newlen, L" "); - wcscat_s(newcommand, newlen, command); - debug(L"Running wrapped script with command line '%ls'\n", newcommand); - read_commands(); - av[0] = wrapped_script_path; - av[1] = NULL; - maybe_handle_shebang(av, newcommand); - /* Returns if no shebang line - pass to default processing */ - command = newcommand; - valid = FALSE; - } -#elif defined(VENV_REDIRECT) - { - FILE *f; - char buffer[4096]; /* 4KB should be enough for anybody */ - char *start; - DWORD len, cch, cch_actual; - size_t cb; - if (_wfopen_s(&f, venv_cfg_path, L"r")) { - error(RC_BAD_VENV_CFG, L"Cannot read '%ls'", venv_cfg_path); - } - cb = fread_s(buffer, sizeof(buffer), sizeof(buffer[0]), - sizeof(buffer) / sizeof(buffer[0]), f); - fclose(f); - - if (!find_home_value(buffer, &start, &len)) { - error(RC_BAD_VENV_CFG, L"Cannot find home in '%ls'", - venv_cfg_path); - } - - cch = MultiByteToWideChar(CP_UTF8, 0, start, len, NULL, 0); - if (!cch) { - error(0, L"Cannot determine memory for home path"); - } - cch += (DWORD)wcslen(PYTHON_EXECUTABLE) + 4; /* include sep, null and quotes */ - executable = (wchar_t *)malloc(cch * sizeof(wchar_t)); - if (executable == NULL) { - error(RC_NO_MEMORY, L"A memory allocation failed"); - } - /* start with a quote - we'll skip this ahead, but want it for the final string */ - executable[0] = L'"'; - cch_actual = MultiByteToWideChar(CP_UTF8, 0, start, len, &executable[1], cch - 1); - if (!cch_actual) { - error(RC_BAD_VENV_CFG, L"Cannot decode home path in '%ls'", - venv_cfg_path); - } - cch_actual += 1; /* account for the first quote */ - executable[cch_actual] = L'\0'; - if (executable[cch_actual - 1] != L'\\') { - executable[cch_actual++] = L'\\'; - executable[cch_actual] = L'\0'; - } - if (wcscat_s(&executable[1], cch - 1, PYTHON_EXECUTABLE)) { - error(RC_BAD_VENV_CFG, L"Cannot create executable path from '%ls'", - venv_cfg_path); - } - /* there's no trailing quote, so we only have to skip one character for the test */ - if (GetFileAttributesW(&executable[1]) == INVALID_FILE_ATTRIBUTES) { - error(RC_NO_PYTHON, L"No Python at '%ls'", executable); - } - /* now append the final quote */ - wcscat_s(executable, cch, L"\""); - /* smuggle our original path through */ - if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", argv0)) { - error(0, L"Failed to set launcher environment"); - } - valid = 1; - } -#else - if (argc <= 1) { - valid = FALSE; - p = NULL; - } - else { - p = argv[1]; - if ((argc == 2) && // list version args - (!wcsncmp(p, L"-0", wcslen(L"-0")) || - !wcsncmp(p, L"--list", wcslen(L"--list")))) - { - show_python_list(argv); - return rc; - } - valid = valid && (*p == L'-') && validate_version(&p[1]); - if (valid) { - ip = locate_python(&p[1], FALSE); - if (ip == NULL) - { - fwprintf(stdout, \ - L"Python %ls not found!\n", &p[1]); - valid = show_python_list(argv); - error(RC_NO_PYTHON, L"Requested Python version (%ls) not \ -installed, use -0 for available pythons", &p[1]); - } - executable = ip->executable; - command += wcslen(p); - command = skip_whitespace(command); - } - else { - for (index = 1; index < argc; ++index) { - if (*argv[index] != L'-') - break; - } - if (index < argc) { - read_commands(); - maybe_handle_shebang(&argv[index], command); - } - } - } -#endif - - if (!valid) { - if ((argc == 2) && (!_wcsicmp(p, L"-h") || !_wcsicmp(p, L"--help"))) - show_help_text(argv); - if ((argc == 2) && - (!_wcsicmp(p, L"-0") || !_wcsicmp(p, L"--list") || - !_wcsicmp(p, L"-0p") || !_wcsicmp(p, L"--list-paths"))) - { - executable = NULL; /* Info call only */ - } - else { - /* look for the default Python */ - ip = locate_python(L"", FALSE); - if (ip == NULL) - error(RC_NO_PYTHON, L"Can't find a default Python."); - executable = ip->executable; - } - } - if (executable != NULL) - invoke_child(executable, NULL, command); - else - rc = RC_NO_PYTHON; - return rc; -} - -#if defined(_WINDOWS) - -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, - LPWSTR lpstrCmd, int nShow) -{ - return process(__argc, __wargv); -} - -#else - -int cdecl wmain(int argc, wchar_t ** argv) -{ - return process(argc, argv); -} - -#endif diff --git a/PC/launcher2.c b/PC/launcher2.c deleted file mode 100644 index 4dd18c8eb5462e9..000000000000000 --- a/PC/launcher2.c +++ /dev/null @@ -1,2858 +0,0 @@ -/* - * Rewritten Python launcher for Windows - * - * This new rewrite properly handles PEP 514 and allows any registered Python - * runtime to be launched. It also enables auto-install of versions when they - * are requested but no installation can be found. - */ - -#define __STDC_WANT_LIB_EXT1__ 1 - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MS_WINDOWS -#include "patchlevel.h" - -#define MAXLEN PATHCCH_MAX_CCH -#define MSGSIZE 1024 - -#define RC_NO_STD_HANDLES 100 -#define RC_CREATE_PROCESS 101 -#define RC_BAD_VIRTUAL_PATH 102 -#define RC_NO_PYTHON 103 -#define RC_NO_MEMORY 104 -#define RC_NO_SCRIPT 105 -#define RC_NO_VENV_CFG 106 -#define RC_BAD_VENV_CFG 107 -#define RC_NO_COMMANDLINE 108 -#define RC_INTERNAL_ERROR 109 -#define RC_DUPLICATE_ITEM 110 -#define RC_INSTALLING 111 -#define RC_NO_PYTHON_AT_ALL 112 -#define RC_NO_SHEBANG 113 -#define RC_RECURSIVE_SHEBANG 114 - -static FILE * log_fp = NULL; - -void -debug(wchar_t * format, ...) -{ - va_list va; - - if (log_fp != NULL) { - wchar_t buffer[MAXLEN]; - int r = 0; - va_start(va, format); - r = vswprintf_s(buffer, MAXLEN, format, va); - va_end(va); - - if (r <= 0) { - return; - } - fputws(buffer, log_fp); - while (r && isspace(buffer[r])) { - buffer[r--] = L'\0'; - } - if (buffer[0]) { - OutputDebugStringW(buffer); - } - } -} - - -void -formatWinerror(int rc, wchar_t * message, int size) -{ - FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - message, size, NULL); -} - - -void -winerror(int err, wchar_t * format, ... ) -{ - va_list va; - wchar_t message[MSGSIZE]; - wchar_t win_message[MSGSIZE]; - int len; - - if (err == 0) { - err = GetLastError(); - } - - va_start(va, format); - len = _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va); - va_end(va); - - formatWinerror(err, win_message, MSGSIZE); - if (len >= 0) { - _snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %s", - win_message); - } - -#if !defined(_WINDOWS) - fwprintf(stderr, L"%s\n", message); -#else - MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...", - MB_OK); -#endif -} - - -void -error(wchar_t * format, ... ) -{ - va_list va; - wchar_t message[MSGSIZE]; - - va_start(va, format); - _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va); - va_end(va); - -#if !defined(_WINDOWS) - fwprintf(stderr, L"%s\n", message); -#else - MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...", - MB_OK); -#endif -} - - -typedef BOOL (*PIsWow64Process2)(HANDLE, USHORT*, USHORT*); - - -USHORT -_getNativeMachine(void) -{ - static USHORT _nativeMachine = IMAGE_FILE_MACHINE_UNKNOWN; - if (_nativeMachine == IMAGE_FILE_MACHINE_UNKNOWN) { - USHORT processMachine; - HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); - PIsWow64Process2 IsWow64Process2 = kernel32 ? - (PIsWow64Process2)GetProcAddress(kernel32, "IsWow64Process2") : - NULL; - if (!IsWow64Process2) { - BOOL wow64Process; - if (!IsWow64Process(NULL, &wow64Process)) { - winerror(0, L"Checking process type"); - } else if (wow64Process) { - // We should always be a 32-bit executable, so if running - // under emulation, it must be a 64-bit host. - _nativeMachine = IMAGE_FILE_MACHINE_AMD64; - } else { - // Not running under emulation, and an old enough OS to not - // have IsWow64Process2, so assume it's x86. - _nativeMachine = IMAGE_FILE_MACHINE_I386; - } - } else if (!IsWow64Process2(NULL, &processMachine, &_nativeMachine)) { - winerror(0, L"Checking process type"); - } - } - return _nativeMachine; -} - - -bool -isAMD64Host(void) -{ - return _getNativeMachine() == IMAGE_FILE_MACHINE_AMD64; -} - - -bool -isARM64Host(void) -{ - return _getNativeMachine() == IMAGE_FILE_MACHINE_ARM64; -} - - -bool -isEnvVarSet(const wchar_t *name) -{ - /* only looking for non-empty, which means at least one character - and the null terminator */ - return GetEnvironmentVariableW(name, NULL, 0) >= 2; -} - - -bool -join(wchar_t *buffer, size_t bufferLength, const wchar_t *fragment) -{ - if (SUCCEEDED(PathCchCombineEx(buffer, bufferLength, buffer, fragment, PATHCCH_ALLOW_LONG_PATHS))) { - return true; - } - return false; -} - - -bool -split_parent(wchar_t *buffer, size_t bufferLength) -{ - return SUCCEEDED(PathCchRemoveFileSpec(buffer, bufferLength)); -} - - -int -_compare(const wchar_t *x, int xLen, const wchar_t *y, int yLen) -{ - // Empty strings sort first - if (!x || !xLen) { - return (!y || !yLen) ? 0 : -1; - } else if (!y || !yLen) { - return 1; - } - switch (CompareStringEx( - LOCALE_NAME_INVARIANT, NORM_IGNORECASE | SORT_DIGITSASNUMBERS, - x, xLen, y, yLen, - NULL, NULL, 0 - )) { - case CSTR_LESS_THAN: - return -1; - case CSTR_EQUAL: - return 0; - case CSTR_GREATER_THAN: - return 1; - default: - winerror(0, L"Error comparing '%.*s' and '%.*s' (compare)", xLen, x, yLen, y); - return -1; - } -} - - -int -_compareArgument(const wchar_t *x, int xLen, const wchar_t *y, int yLen) -{ - // Empty strings sort first - if (!x || !xLen) { - return (!y || !yLen) ? 0 : -1; - } else if (!y || !yLen) { - return 1; - } - switch (CompareStringEx( - LOCALE_NAME_INVARIANT, 0, - x, xLen, y, yLen, - NULL, NULL, 0 - )) { - case CSTR_LESS_THAN: - return -1; - case CSTR_EQUAL: - return 0; - case CSTR_GREATER_THAN: - return 1; - default: - winerror(0, L"Error comparing '%.*s' and '%.*s' (compareArgument)", xLen, x, yLen, y); - return -1; - } -} - -int -_comparePath(const wchar_t *x, int xLen, const wchar_t *y, int yLen) -{ - // Empty strings sort first - if (!x || !xLen) { - return !y || !yLen ? 0 : -1; - } else if (!y || !yLen) { - return 1; - } - switch (CompareStringOrdinal(x, xLen, y, yLen, TRUE)) { - case CSTR_LESS_THAN: - return -1; - case CSTR_EQUAL: - return 0; - case CSTR_GREATER_THAN: - return 1; - default: - winerror(0, L"Error comparing '%.*s' and '%.*s' (comparePath)", xLen, x, yLen, y); - return -1; - } -} - - -bool -_startsWith(const wchar_t *x, int xLen, const wchar_t *y, int yLen) -{ - if (!x || !y) { - return false; - } - yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen; - xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen; - return xLen >= yLen && 0 == _compare(x, yLen, y, yLen); -} - - -bool -_startsWithArgument(const wchar_t *x, int xLen, const wchar_t *y, int yLen) -{ - if (!x || !y) { - return false; - } - yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen; - xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen; - return xLen >= yLen && 0 == _compareArgument(x, yLen, y, yLen); -} - - -// Unlike regular startsWith, this function requires that the following -// character is either NULL (that is, the entire string matches) or is one of -// the characters in 'separators'. -bool -_startsWithSeparated(const wchar_t *x, int xLen, const wchar_t *y, int yLen, const wchar_t *separators) -{ - if (!x || !y) { - return false; - } - yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen; - xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen; - if (xLen < yLen) { - return false; - } - if (xLen == yLen) { - return 0 == _compare(x, xLen, y, yLen); - } - return separators && - 0 == _compare(x, yLen, y, yLen) && - wcschr(separators, x[yLen]) != NULL; -} - - - -/******************************************************************************\ - *** HELP TEXT *** -\******************************************************************************/ - - -int -showHelpText(wchar_t ** argv) -{ - // The help text is stored in launcher-usage.txt, which is compiled into - // the launcher and loaded at runtime if needed. - // - // The file must be UTF-8. There are two substitutions: - // %ls - PY_VERSION (as wchar_t*) - // %ls - argv[0] (as wchar_t*) - HRSRC res = FindResourceExW(NULL, L"USAGE", MAKEINTRESOURCE(1), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); - HGLOBAL resData = res ? LoadResource(NULL, res) : NULL; - const char *usage = resData ? (const char*)LockResource(resData) : NULL; - if (usage == NULL) { - winerror(0, L"Unable to load usage text"); - return RC_INTERNAL_ERROR; - } - - DWORD cbData = SizeofResource(NULL, res); - DWORD cchUsage = MultiByteToWideChar(CP_UTF8, 0, usage, cbData, NULL, 0); - if (!cchUsage) { - winerror(0, L"Unable to preprocess usage text"); - return RC_INTERNAL_ERROR; - } - - cchUsage += 1; - wchar_t *wUsage = (wchar_t*)malloc(cchUsage * sizeof(wchar_t)); - cchUsage = MultiByteToWideChar(CP_UTF8, 0, usage, cbData, wUsage, cchUsage); - if (!cchUsage) { - winerror(0, L"Unable to preprocess usage text"); - free((void *)wUsage); - return RC_INTERNAL_ERROR; - } - // Ensure null termination - wUsage[cchUsage] = L'\0'; - - fwprintf(stdout, wUsage, (L"" PY_VERSION), argv[0]); - fflush(stdout); - - free((void *)wUsage); - - return 0; -} - - -/******************************************************************************\ - *** SEARCH INFO *** -\******************************************************************************/ - - -struct _SearchInfoBuffer { - struct _SearchInfoBuffer *next; - wchar_t buffer[0]; -}; - - -typedef struct { - // the original string, managed by the OS - const wchar_t *originalCmdLine; - // pointer into the cmdline to mark what we've consumed - const wchar_t *restOfCmdLine; - // if known/discovered, the full executable path of our runtime - const wchar_t *executablePath; - // pointer and length into cmdline for the file to check for a - // shebang line, if any. Length can be -1 if the string is null - // terminated. - const wchar_t *scriptFile; - int scriptFileLength; - // pointer and length into cmdline or a static string with the - // name of the target executable. Length can be -1 if the string - // is null terminated. - const wchar_t *executable; - int executableLength; - // pointer and length into a string with additional interpreter - // arguments to include before restOfCmdLine. Length can be -1 if - // the string is null terminated. - const wchar_t *executableArgs; - int executableArgsLength; - // pointer and length into cmdline or a static string with the - // company name for PEP 514 lookup. Length can be -1 if the string - // is null terminated. - const wchar_t *company; - int companyLength; - // pointer and length into cmdline or a static string with the - // tag for PEP 514 lookup. Length can be -1 if the string is - // null terminated. - const wchar_t *tag; - int tagLength; - // if true, treats 'tag' as a non-PEP 514 filter - bool oldStyleTag; - // if true, ignores 'tag' when a high priority environment is found - // gh-92817: This is currently set when a tag is read from configuration, - // the environment, or a shebang, rather than the command line, and the - // only currently possible high priority environment is an active virtual - // environment - bool lowPriorityTag; - // if true, allow PEP 514 lookup to override 'executable' - bool allowExecutableOverride; - // if true, allow a nearby pyvenv.cfg to locate the executable - bool allowPyvenvCfg; - // if true, allow defaults (env/py.ini) to clarify/override tags - bool allowDefaults; - // if true, prefer windowed (console-less) executable - bool windowed; - // if true, only list detected runtimes without launching - bool list; - // if true, only list detected runtimes with paths without launching - bool listPaths; - // if true, display help message before continuing - bool help; - // if set, limits search to registry keys with the specified Company - // This is intended for debugging and testing only - const wchar_t *limitToCompany; - // dynamically allocated buffers to free later - struct _SearchInfoBuffer *_buffer; -} SearchInfo; - - -wchar_t * -allocSearchInfoBuffer(SearchInfo *search, int wcharCount) -{ - struct _SearchInfoBuffer *buffer = (struct _SearchInfoBuffer*)malloc( - sizeof(struct _SearchInfoBuffer) + - wcharCount * sizeof(wchar_t) - ); - if (!buffer) { - return NULL; - } - buffer->next = search->_buffer; - search->_buffer = buffer; - return buffer->buffer; -} - - -void -freeSearchInfo(SearchInfo *search) -{ - struct _SearchInfoBuffer *b = search->_buffer; - search->_buffer = NULL; - while (b) { - struct _SearchInfoBuffer *nextB = b->next; - free((void *)b); - b = nextB; - } -} - - -void -_debugStringAndLength(const wchar_t *s, int len, const wchar_t *name) -{ - if (!s) { - debug(L"%s: (null)\n", name); - } else if (len == 0) { - debug(L"%s: (empty)\n", name); - } else if (len < 0) { - debug(L"%s: %s\n", name, s); - } else { - debug(L"%s: %.*ls\n", name, len, s); - } -} - - -void -dumpSearchInfo(SearchInfo *search) -{ - if (!log_fp) { - return; - } - -#ifdef __clang__ -#define DEBUGNAME(s) L # s -#else -#define DEBUGNAME(s) # s -#endif -#define DEBUG(s) debug(L"SearchInfo." DEBUGNAME(s) L": %s\n", (search->s) ? (search->s) : L"(null)") -#define DEBUG_2(s, sl) _debugStringAndLength((search->s), (search->sl), L"SearchInfo." DEBUGNAME(s)) -#define DEBUG_BOOL(s) debug(L"SearchInfo." DEBUGNAME(s) L": %s\n", (search->s) ? L"True" : L"False") - DEBUG(originalCmdLine); - DEBUG(restOfCmdLine); - DEBUG(executablePath); - DEBUG_2(scriptFile, scriptFileLength); - DEBUG_2(executable, executableLength); - DEBUG_2(executableArgs, executableArgsLength); - DEBUG_2(company, companyLength); - DEBUG_2(tag, tagLength); - DEBUG_BOOL(oldStyleTag); - DEBUG_BOOL(lowPriorityTag); - DEBUG_BOOL(allowDefaults); - DEBUG_BOOL(allowExecutableOverride); - DEBUG_BOOL(windowed); - DEBUG_BOOL(list); - DEBUG_BOOL(listPaths); - DEBUG_BOOL(help); - DEBUG(limitToCompany); -#undef DEBUG_BOOL -#undef DEBUG_2 -#undef DEBUG -#undef DEBUGNAME -} - - -int -findArgv0Length(const wchar_t *buffer, int bufferLength) -{ - // Note: this implements semantics that are only valid for argv0. - // Specifically, there is no escaping of quotes, and quotes within - // the argument have no effect. A quoted argv0 must start and end - // with a double quote character; otherwise, it ends at the first - // ' ' or '\t'. - int quoted = buffer[0] == L'"'; - for (int i = 1; bufferLength < 0 || i < bufferLength; ++i) { - switch (buffer[i]) { - case L'\0': - return i; - case L' ': - case L'\t': - if (!quoted) { - return i; - } - break; - case L'"': - if (quoted) { - return i + 1; - } - break; - } - } - return bufferLength; -} - - -const wchar_t * -findArgv0End(const wchar_t *buffer, int bufferLength) -{ - return &buffer[findArgv0Length(buffer, bufferLength)]; -} - - -/******************************************************************************\ - *** COMMAND-LINE PARSING *** -\******************************************************************************/ - -// Adapted from https://stackoverflow.com/a/65583702 -typedef struct AppExecLinkFile { // For tag IO_REPARSE_TAG_APPEXECLINK - DWORD reparseTag; - WORD reparseDataLength; - WORD reserved; - ULONG version; - wchar_t stringList[MAX_PATH * 4]; // Multistring (Consecutive UTF-16 strings each ending with a NUL) - /* There are normally 4 strings here. Ex: - Package ID: L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe" - Entry Point: L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe!PythonRedirector" - Executable: L"C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.17.106910_x64__8wekyb3d8bbwe\AppInstallerPythonRedirector.exe" - Applic. Type: L"0" // Integer as ASCII. "0" = Desktop bridge application; Else sandboxed UWP application - */ -} AppExecLinkFile; - - -int -parseCommandLine(SearchInfo *search) -{ - if (!search || !search->originalCmdLine) { - return RC_NO_COMMANDLINE; - } - - const wchar_t *argv0End = findArgv0End(search->originalCmdLine, -1); - const wchar_t *tail = argv0End; // will be start of the executable name - const wchar_t *end = argv0End; // will be end of the executable name - search->restOfCmdLine = argv0End; // will be first space after argv0 - while (--tail != search->originalCmdLine) { - if (*tail == L'"' && end == argv0End) { - // Move the "end" up to the quote, so we also allow moving for - // a period later on. - end = argv0End = tail; - } else if (*tail == L'.' && end == argv0End) { - end = tail; - } else if (*tail == L'\\' || *tail == L'/') { - ++tail; - break; - } - } - if (tail == search->originalCmdLine && tail[0] == L'"') { - ++tail; - } - // Without special cases, we can now fill in the search struct - int tailLen = (int)(end ? (end - tail) : wcsnlen_s(tail, MAXLEN)); - search->executableLength = -1; - - // Our special cases are as follows -#define MATCHES(s) (0 == _comparePath(tail, tailLen, (s), -1)) -#define STARTSWITH(s) _startsWith(tail, tailLen, (s), -1) - if (MATCHES(L"py")) { - search->executable = L"python.exe"; - search->allowExecutableOverride = true; - search->allowDefaults = true; - } else if (MATCHES(L"pyw")) { - search->executable = L"pythonw.exe"; - search->allowExecutableOverride = true; - search->allowDefaults = true; - search->windowed = true; - } else if (MATCHES(L"py_d")) { - search->executable = L"python_d.exe"; - search->allowExecutableOverride = true; - search->allowDefaults = true; - } else if (MATCHES(L"pyw_d")) { - search->executable = L"pythonw_d.exe"; - search->allowExecutableOverride = true; - search->allowDefaults = true; - search->windowed = true; - } else if (STARTSWITH(L"python3")) { - search->executable = L"python.exe"; - search->tag = &tail[6]; - search->tagLength = tailLen - 6; - search->allowExecutableOverride = true; - search->oldStyleTag = true; - search->allowPyvenvCfg = true; - } else if (STARTSWITH(L"pythonw3")) { - search->executable = L"pythonw.exe"; - search->tag = &tail[7]; - search->tagLength = tailLen - 7; - search->allowExecutableOverride = true; - search->oldStyleTag = true; - search->allowPyvenvCfg = true; - search->windowed = true; - } else { - search->executable = tail; - search->executableLength = tailLen; - search->allowPyvenvCfg = true; - } -#undef STARTSWITH -#undef MATCHES - - // First argument might be one of our options. If so, consume it, - // update flags and then set restOfCmdLine. - const wchar_t *arg = search->restOfCmdLine; - while(*arg && isspace(*arg)) { ++arg; } -#define MATCHES(s) (0 == _compareArgument(arg, argLen, (s), -1)) -#define STARTSWITH(s) _startsWithArgument(arg, argLen, (s), -1) - if (*arg && *arg == L'-' && *++arg) { - tail = arg; - while (*tail && !isspace(*tail)) { ++tail; } - int argLen = (int)(tail - arg); - if (argLen > 0) { - if (STARTSWITH(L"2") || STARTSWITH(L"3")) { - // All arguments starting with 2 or 3 are assumed to be version tags - search->tag = arg; - search->tagLength = argLen; - search->oldStyleTag = true; - search->restOfCmdLine = tail; - } else if (STARTSWITH(L"V:") || STARTSWITH(L"-version:")) { - // Arguments starting with 'V:' specify company and/or tag - const wchar_t *argStart = wcschr(arg, L':') + 1; - const wchar_t *tagStart = wcschr(argStart, L'/') ; - if (tagStart) { - search->company = argStart; - search->companyLength = (int)(tagStart - argStart); - search->tag = tagStart + 1; - } else { - search->tag = argStart; - } - search->tagLength = (int)(tail - search->tag); - search->allowDefaults = false; - search->restOfCmdLine = tail; - } else if (MATCHES(L"0") || MATCHES(L"-list")) { - search->list = true; - search->restOfCmdLine = tail; - } else if (MATCHES(L"0p") || MATCHES(L"-list-paths")) { - search->listPaths = true; - search->restOfCmdLine = tail; - } else if (MATCHES(L"h") || MATCHES(L"-help")) { - search->help = true; - // Do not update restOfCmdLine so that we trigger the help - // message from whichever interpreter we select - } - } - } -#undef STARTSWITH -#undef MATCHES - - // Might have a script filename. If it looks like a filename, add - // it to the SearchInfo struct for later reference. - arg = search->restOfCmdLine; - while(*arg && isspace(*arg)) { ++arg; } - if (*arg && *arg != L'-') { - search->scriptFile = arg; - if (*arg == L'"') { - ++search->scriptFile; - while (*++arg && *arg != L'"') { } - } else { - while (*arg && !isspace(*arg)) { ++arg; } - } - search->scriptFileLength = (int)(arg - search->scriptFile); - } - - return 0; -} - - -int -_decodeShebang(SearchInfo *search, const char *buffer, int bufferLength, bool onlyUtf8, wchar_t **decoded, int *decodedLength) -{ - DWORD cp = CP_UTF8; - int wideLen = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, buffer, bufferLength, NULL, 0); - if (!wideLen) { - cp = CP_ACP; - wideLen = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, buffer, bufferLength, NULL, 0); - if (!wideLen) { - debug(L"# Failed to decode shebang line (0x%08X)\n", GetLastError()); - return RC_BAD_VIRTUAL_PATH; - } - } - wchar_t *b = allocSearchInfoBuffer(search, wideLen + 1); - if (!b) { - return RC_NO_MEMORY; - } - wideLen = MultiByteToWideChar(cp, 0, buffer, bufferLength, b, wideLen + 1); - if (!wideLen) { - debug(L"# Failed to decode shebang line (0x%08X)\n", GetLastError()); - return RC_BAD_VIRTUAL_PATH; - } - b[wideLen] = L'\0'; - *decoded = b; - *decodedLength = wideLen; - return 0; -} - - -bool -_shebangStartsWith(const wchar_t *buffer, int bufferLength, const wchar_t *prefix, const wchar_t **rest, int *firstArgumentLength) -{ - int prefixLength = (int)wcsnlen_s(prefix, MAXLEN); - if (bufferLength < prefixLength || !_startsWithArgument(buffer, bufferLength, prefix, prefixLength)) { - return false; - } - if (rest) { - *rest = &buffer[prefixLength]; - } - if (firstArgumentLength) { - int i = prefixLength; - while (i < bufferLength && !isspace(buffer[i])) { - i += 1; - } - *firstArgumentLength = i - prefixLength; - } - return true; -} - - -int -ensure_no_redirector_stub(wchar_t* filename, wchar_t* buffer) -{ - // Make sure we didn't find a reparse point that will open the Microsoft Store - // If we did, pretend there was no shebang and let normal handling take over - WIN32_FIND_DATAW findData; - HANDLE hFind = FindFirstFileW(buffer, &findData); - if (!hFind) { - // Let normal handling take over - debug(L"# Did not find %s on PATH\n", filename); - return RC_NO_SHEBANG; - } - - FindClose(hFind); - - if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && - findData.dwReserved0 & IO_REPARSE_TAG_APPEXECLINK)) { - return 0; - } - - HANDLE hReparsePoint = CreateFileW(buffer, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, NULL); - if (!hReparsePoint) { - // Let normal handling take over - debug(L"# Did not find %s on PATH\n", filename); - return RC_NO_SHEBANG; - } - - AppExecLinkFile appExecLink; - - if (!DeviceIoControl(hReparsePoint, FSCTL_GET_REPARSE_POINT, NULL, 0, &appExecLink, sizeof(appExecLink), NULL, NULL)) { - // Let normal handling take over - debug(L"# Did not find %s on PATH\n", filename); - CloseHandle(hReparsePoint); - return RC_NO_SHEBANG; - } - - CloseHandle(hReparsePoint); - - const wchar_t* redirectorPackageId = L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; - - if (0 == wcscmp(appExecLink.stringList, redirectorPackageId)) { - debug(L"# ignoring redirector that would launch store\n"); - return RC_NO_SHEBANG; - } - - return 0; -} - - -int -searchPath(SearchInfo *search, const wchar_t *shebang, int shebangLength) -{ - if (isEnvVarSet(L"PYLAUNCHER_NO_SEARCH_PATH")) { - return RC_NO_SHEBANG; - } - - wchar_t *command; - int commandLength; - if (!_shebangStartsWith(shebang, shebangLength, L"/usr/bin/env ", &command, &commandLength)) { - return RC_NO_SHEBANG; - } - - if (!commandLength || commandLength == MAXLEN) { - return RC_BAD_VIRTUAL_PATH; - } - - int lastDot = commandLength; - while (lastDot > 0 && command[lastDot] != L'.') { - lastDot -= 1; - } - if (!lastDot) { - lastDot = commandLength; - } - - wchar_t filename[MAXLEN]; - if (wcsncpy_s(filename, MAXLEN, command, commandLength)) { - return RC_BAD_VIRTUAL_PATH; - } - - const wchar_t *ext = L".exe"; - // If the command already has an extension, we do not want to add it again - if (!lastDot || _comparePath(&filename[lastDot], -1, ext, -1)) { - if (wcscat_s(filename, MAXLEN, L".exe")) { - return RC_BAD_VIRTUAL_PATH; - } - } - - debug(L"# Search PATH for %s\n", filename); - - wchar_t pathVariable[MAXLEN]; - int n = GetEnvironmentVariableW(L"PATH", pathVariable, MAXLEN); - if (!n) { - if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) { - return RC_NO_SHEBANG; - } - winerror(0, L"Failed to read PATH\n", filename); - return RC_INTERNAL_ERROR; - } - - wchar_t buffer[MAXLEN]; - n = SearchPathW(pathVariable, filename, NULL, MAXLEN, buffer, NULL); - if (!n) { - if (GetLastError() == ERROR_FILE_NOT_FOUND) { - debug(L"# Did not find %s on PATH\n", filename); - // If we didn't find it on PATH, let normal handling take over - return RC_NO_SHEBANG; - } - // Other errors should cause us to break - winerror(0, L"Failed to find %s on PATH\n", filename); - return RC_BAD_VIRTUAL_PATH; - } - - int result = ensure_no_redirector_stub(filename, buffer); - if (result) { - return result; - } - - // Check that we aren't going to call ourselves again - // If we are, pretend there was no shebang and let normal handling take over - if (GetModuleFileNameW(NULL, filename, MAXLEN) && - 0 == _comparePath(filename, -1, buffer, -1)) { - debug(L"# ignoring recursive shebang command\n"); - return RC_RECURSIVE_SHEBANG; - } - - wchar_t *buf = allocSearchInfoBuffer(search, n + 1); - if (!buf || wcscpy_s(buf, n + 1, buffer)) { - return RC_NO_MEMORY; - } - - search->executablePath = buf; - search->executableArgs = &command[commandLength]; - search->executableArgsLength = shebangLength - commandLength; - debug(L"# Found %s on PATH\n", buf); - - return 0; -} - - -int -_readIni(const wchar_t *section, const wchar_t *settingName, wchar_t *buffer, int bufferLength) -{ - wchar_t iniPath[MAXLEN]; - int n; - // Check for _PYLAUNCHER_INIDIR override (used for test isolation) - DWORD len = GetEnvironmentVariableW(L"_PYLAUNCHER_INIDIR", iniPath, MAXLEN); - if (len && len < MAXLEN) { - if (join(iniPath, MAXLEN, L"py.ini")) { - debug(L"# Reading from %s for %s/%s\n", iniPath, section, settingName); - n = GetPrivateProfileStringW(section, settingName, NULL, buffer, bufferLength, iniPath); - if (n) { - debug(L"# Found %s in %s\n", settingName, iniPath); - return n; - } - } - // When _PYLAUNCHER_INIDIR is set, skip the default locations - return 0; - } - if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, iniPath)) && - join(iniPath, MAXLEN, L"py.ini")) { - debug(L"# Reading from %s for %s/%s\n", iniPath, section, settingName); - n = GetPrivateProfileStringW(section, settingName, NULL, buffer, bufferLength, iniPath); - if (n) { - debug(L"# Found %s in %s\n", settingName, iniPath); - return n; - } else if (GetLastError() == ERROR_FILE_NOT_FOUND) { - debug(L"# Did not find file %s\n", iniPath); - } else { - winerror(0, L"Failed to read from %s\n", iniPath); - } - } - if (GetModuleFileNameW(NULL, iniPath, MAXLEN) && - SUCCEEDED(PathCchRemoveFileSpec(iniPath, MAXLEN)) && - join(iniPath, MAXLEN, L"py.ini")) { - debug(L"# Reading from %s for %s/%s\n", iniPath, section, settingName); - n = GetPrivateProfileStringW(section, settingName, NULL, buffer, MAXLEN, iniPath); - if (n) { - debug(L"# Found %s in %s\n", settingName, iniPath); - return n; - } else if (GetLastError() == ERROR_FILE_NOT_FOUND) { - debug(L"# Did not find file %s\n", iniPath); - } else { - winerror(0, L"Failed to read from %s\n", iniPath); - } - } - return 0; -} - - -bool -_findCommand(SearchInfo *search, const wchar_t *command, int commandLength) -{ - wchar_t commandBuffer[MAXLEN]; - wchar_t buffer[MAXLEN]; - wcsncpy_s(commandBuffer, MAXLEN, command, commandLength); - int n = _readIni(L"commands", commandBuffer, buffer, MAXLEN); - if (!n) { - return false; - } - wchar_t *path = allocSearchInfoBuffer(search, n + 1); - if (!path) { - return false; - } - wcscpy_s(path, n + 1, buffer); - search->executablePath = path; - return true; -} - - -int -_useShebangAsExecutable(SearchInfo *search, const wchar_t *shebang, int shebangLength) -{ - wchar_t buffer[MAXLEN]; - wchar_t script[MAXLEN]; - wchar_t command[MAXLEN]; - - int commandLength = 0; - int inQuote = 0; - - if (!shebang || !shebangLength) { - return 0; - } - - wchar_t *pC = command; - for (int i = 0; i < shebangLength; ++i) { - wchar_t c = shebang[i]; - if (isspace(c) && !inQuote) { - commandLength = i; - break; - } else if (c == L'"') { - inQuote = !inQuote; - } else if (c == L'/' || c == L'\\') { - *pC++ = L'\\'; - } else { - *pC++ = c; - } - } - *pC = L'\0'; - - if (!GetCurrentDirectoryW(MAXLEN, buffer) || - wcsncpy_s(script, MAXLEN, search->scriptFile, search->scriptFileLength) || - FAILED(PathCchCombineEx(buffer, MAXLEN, buffer, script, - PATHCCH_ALLOW_LONG_PATHS)) || - FAILED(PathCchRemoveFileSpec(buffer, MAXLEN)) || - FAILED(PathCchCombineEx(buffer, MAXLEN, buffer, command, - PATHCCH_ALLOW_LONG_PATHS)) - ) { - return RC_NO_MEMORY; - } - - int n = (int)wcsnlen(buffer, MAXLEN); - wchar_t *path = allocSearchInfoBuffer(search, n + 1); - if (!path) { - return RC_NO_MEMORY; - } - wcscpy_s(path, n + 1, buffer); - search->executablePath = path; - if (commandLength) { - search->executableArgs = &shebang[commandLength]; - search->executableArgsLength = shebangLength - commandLength; - } - return 0; -} - - -int -checkShebang(SearchInfo *search) -{ - // Do not check shebang if a tag was provided or if no script file - // was found on the command line. - if (search->tag || !search->scriptFile) { - return 0; - } - - if (search->scriptFileLength < 0) { - search->scriptFileLength = (int)wcsnlen_s(search->scriptFile, MAXLEN); - } - - wchar_t *scriptFile = (wchar_t*)malloc(sizeof(wchar_t) * (search->scriptFileLength + 1)); - if (!scriptFile) { - return RC_NO_MEMORY; - } - - wcsncpy_s(scriptFile, search->scriptFileLength + 1, - search->scriptFile, search->scriptFileLength); - - HANDLE hFile = CreateFileW(scriptFile, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, 0, NULL); - - if (hFile == INVALID_HANDLE_VALUE) { - debug(L"# Failed to open %s for shebang parsing (0x%08X)\n", - scriptFile, GetLastError()); - free(scriptFile); - return RC_NO_SCRIPT; - } - - DWORD bytesRead = 0; - unsigned char buffer[4096]; - if (!ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL)) { - debug(L"# Failed to read %s for shebang parsing (0x%08X)\n", - scriptFile, GetLastError()); - free(scriptFile); - return 0; - } - - CloseHandle(hFile); - debug(L"# Read %d bytes from %s to find shebang line\n", bytesRead, scriptFile); - free(scriptFile); - - - unsigned char *b = buffer; - bool onlyUtf8 = false; - if (bytesRead > 3 && *b == 0xEF) { - if (*++b == 0xBB && *++b == 0xBF) { - // Allow a UTF-8 BOM - ++b; - bytesRead -= 3; - onlyUtf8 = true; - } else { - debug(L"# Invalid BOM in shebang line"); - return 0; - } - } - if (bytesRead <= 2 || b[0] != '#' || b[1] != '!') { - // No shebang (#!) at start of line - debug(L"# No valid shebang line"); - return 0; - } - ++b; - --bytesRead; - while (--bytesRead > 0 && isspace(*++b)) { } - const unsigned char *start = b; - while (--bytesRead > 0 && *++b != '\r' && *b != '\n') { } - wchar_t *shebang; - int shebangLength; - // We add 1 when bytesRead==0, as in that case we hit EOF and b points - // to the last character in the file, not the newline - int exitCode = _decodeShebang(search, (const char*)start, (int)(b - start + (bytesRead == 0)), onlyUtf8, &shebang, &shebangLength); - if (exitCode) { - return exitCode; - } - debug(L"Shebang: %s\n", shebang); - - // Handle shebangs that we should search PATH for - int executablePathWasSetByUsrBinEnv = 0; - exitCode = searchPath(search, shebang, shebangLength); - if (exitCode == 0) { - executablePathWasSetByUsrBinEnv = 1; - } else if (exitCode != RC_NO_SHEBANG) { - return exitCode; - } - - // Handle some known, case-sensitive shebangs - const wchar_t *command; - int commandLength; - // Each template must end with "python" - static const wchar_t *shebangTemplates[] = { - L"/usr/bin/env python", - L"/usr/bin/python", - L"/usr/local/bin/python", - L"python", - NULL - }; - - for (const wchar_t **tmpl = shebangTemplates; *tmpl; ++tmpl) { - // Just to make sure we don't mess this up in the future - assert(0 == wcscmp(L"python", (*tmpl) + wcslen(*tmpl) - 6)); - - if (_shebangStartsWith(shebang, shebangLength, *tmpl, &command, &commandLength)) { - // Search for "python{command}" overrides. All templates end with - // "python", so we prepend it by jumping back 6 characters - if (_findCommand(search, &command[-6], commandLength + 6)) { - search->executableArgs = &command[commandLength]; - search->executableArgsLength = shebangLength - commandLength; - debug(L"# Treating shebang command '%.*s' as %s\n", - commandLength + 6, &command[-6], search->executablePath); - return 0; - } - - search->tag = command; - search->tagLength = commandLength; - // If we had 'python3.12.exe' then we want to strip the suffix - // off of the tag - if (search->tagLength >= 4) { - const wchar_t *suffix = &search->tag[search->tagLength - 4]; - if (0 == _comparePath(suffix, 4, L".exe", -1)) { - search->tagLength -= 4; - } - } - // If we had 'python3_d' then we want to strip the '_d' (any - // '.exe' is already gone) - if (search->tagLength >= 2) { - const wchar_t *suffix = &search->tag[search->tagLength - 2]; - if (0 == _comparePath(suffix, 2, L"_d", -1)) { - search->tagLength -= 2; - } - } - search->oldStyleTag = true; - search->lowPriorityTag = true; - search->executableArgs = &command[commandLength]; - search->executableArgsLength = shebangLength - commandLength; - if (search->tag && search->tagLength) { - debug(L"# Treating shebang command '%.*s' as 'py -%.*s'\n", - commandLength, command, search->tagLength, search->tag); - } else { - debug(L"# Treating shebang command '%.*s' as 'py'\n", - commandLength, command); - } - return 0; - } - } - - // Didn't match a template, but we found it on PATH - if (executablePathWasSetByUsrBinEnv) { - return 0; - } - - // Unrecognised executables are first tried as command aliases - commandLength = 0; - while (commandLength < shebangLength && !isspace(shebang[commandLength])) { - commandLength += 1; - } - if (_findCommand(search, shebang, commandLength)) { - search->executableArgs = &shebang[commandLength]; - search->executableArgsLength = shebangLength - commandLength; - debug(L"# Treating shebang command '%.*s' as %s\n", - commandLength, shebang, search->executablePath); - return 0; - } - - // Unrecognised commands are joined to the script's directory and treated - // as the executable path - return _useShebangAsExecutable(search, shebang, shebangLength); -} - - -int -checkDefaults(SearchInfo *search) -{ - if (!search->allowDefaults) { - return 0; - } - - // Only resolve old-style (or absent) tags to defaults - if (search->tag && search->tagLength && !search->oldStyleTag) { - return 0; - } - - // If tag is only a major version number, expand it from the environment - // or an ini file - const wchar_t *iniSettingName = NULL; - const wchar_t *envSettingName = NULL; - if (!search->tag || !search->tagLength) { - iniSettingName = L"python"; - envSettingName = L"py_python"; - } else if (0 == wcsncmp(search->tag, L"3", search->tagLength)) { - iniSettingName = L"python3"; - envSettingName = L"py_python3"; - } else if (0 == wcsncmp(search->tag, L"2", search->tagLength)) { - iniSettingName = L"python2"; - envSettingName = L"py_python2"; - } else { - debug(L"# Cannot select defaults for tag '%.*s'\n", search->tagLength, search->tag); - return 0; - } - - // First, try to read an environment variable - wchar_t buffer[MAXLEN]; - int n = GetEnvironmentVariableW(envSettingName, buffer, MAXLEN); - - // If none found, check in our two .ini files instead - if (!n) { - n = _readIni(L"defaults", iniSettingName, buffer, MAXLEN); - } - - if (n) { - wchar_t *tag = allocSearchInfoBuffer(search, n + 1); - if (!tag) { - return RC_NO_MEMORY; - } - wcscpy_s(tag, n + 1, buffer); - wchar_t *slash = wcschr(tag, L'/'); - if (!slash) { - search->tag = tag; - search->tagLength = n; - search->oldStyleTag = true; - } else { - search->company = tag; - search->companyLength = (int)(slash - tag); - search->tag = slash + 1; - search->tagLength = n - (search->companyLength + 1); - search->oldStyleTag = false; - } - // gh-92817: allow a high priority env to be selected even if it - // doesn't match the tag - search->lowPriorityTag = true; - } - - return 0; -} - -/******************************************************************************\ - *** ENVIRONMENT SEARCH *** -\******************************************************************************/ - -typedef struct EnvironmentInfo { - /* We use a binary tree and sort on insert */ - struct EnvironmentInfo *prev; - struct EnvironmentInfo *next; - /* parent is only used when constructing */ - struct EnvironmentInfo *parent; - const wchar_t *company; - const wchar_t *tag; - int internalSortKey; - const wchar_t *installDir; - const wchar_t *executablePath; - const wchar_t *executableArgs; - const wchar_t *architecture; - const wchar_t *displayName; - bool highPriority; -} EnvironmentInfo; - - -int -copyWstr(const wchar_t **dest, const wchar_t *src) -{ - if (!dest) { - return RC_NO_MEMORY; - } - if (!src) { - *dest = NULL; - return 0; - } - size_t n = wcsnlen_s(src, MAXLEN - 1) + 1; - wchar_t *buffer = (wchar_t*)malloc(n * sizeof(wchar_t)); - if (!buffer) { - return RC_NO_MEMORY; - } - wcsncpy_s(buffer, n, src, n - 1); - *dest = (const wchar_t*)buffer; - return 0; -} - - -EnvironmentInfo * -newEnvironmentInfo(const wchar_t *company, const wchar_t *tag) -{ - EnvironmentInfo *env = (EnvironmentInfo *)malloc(sizeof(EnvironmentInfo)); - if (!env) { - return NULL; - } - memset(env, 0, sizeof(EnvironmentInfo)); - int exitCode = copyWstr(&env->company, company); - if (exitCode) { - free((void *)env); - return NULL; - } - exitCode = copyWstr(&env->tag, tag); - if (exitCode) { - free((void *)env->company); - free((void *)env); - return NULL; - } - return env; -} - - -void -freeEnvironmentInfo(EnvironmentInfo *env) -{ - if (env) { - free((void *)env->company); - free((void *)env->tag); - free((void *)env->installDir); - free((void *)env->executablePath); - free((void *)env->executableArgs); - free((void *)env->displayName); - freeEnvironmentInfo(env->prev); - env->prev = NULL; - freeEnvironmentInfo(env->next); - env->next = NULL; - free((void *)env); - } -} - - -/* Specific string comparisons for sorting the tree */ - -int -_compareCompany(const wchar_t *x, const wchar_t *y) -{ - if (!x && !y) { - return 0; - } else if (!x) { - return -1; - } else if (!y) { - return 1; - } - - bool coreX = 0 == _compare(x, -1, L"PythonCore", -1); - bool coreY = 0 == _compare(y, -1, L"PythonCore", -1); - if (coreX) { - return coreY ? 0 : -1; - } else if (coreY) { - return 1; - } - return _compare(x, -1, y, -1); -} - - -int -_compareTag(const wchar_t *x, const wchar_t *y) -{ - if (!x && !y) { - return 0; - } else if (!x) { - return -1; - } else if (!y) { - return 1; - } - - // Compare up to the first dash. If not equal, that's our sort order - const wchar_t *xDash = wcschr(x, L'-'); - const wchar_t *yDash = wcschr(y, L'-'); - int xToDash = xDash ? (int)(xDash - x) : -1; - int yToDash = yDash ? (int)(yDash - y) : -1; - int r = _compare(x, xToDash, y, yToDash); - if (r) { - return r; - } - // If we're equal up to the first dash, we want to sort one with - // no dash *after* one with a dash. Otherwise, a reversed compare. - // This works out because environments are sorted in descending tag - // order, so that higher versions (probably) come first. - // For PythonCore, our "X.Y" structure ensures that higher versions - // come first. Everyone else will just have to deal with it. - if (xDash && yDash) { - return _compare(yDash, -1, xDash, -1); - } else if (xDash) { - return -1; - } else if (yDash) { - return 1; - } - return 0; -} - - -int -addEnvironmentInfo(EnvironmentInfo **root, EnvironmentInfo* parent, EnvironmentInfo *node) -{ - EnvironmentInfo *r = *root; - if (!r) { - *root = node; - node->parent = parent; - return 0; - } - // Sort by company name - switch (_compareCompany(node->company, r->company)) { - case -1: - return addEnvironmentInfo(&r->prev, r, node); - case 1: - return addEnvironmentInfo(&r->next, r, node); - case 0: - break; - } - // Then by tag (descending) - switch (_compareTag(node->tag, r->tag)) { - case -1: - return addEnvironmentInfo(&r->next, r, node); - case 1: - return addEnvironmentInfo(&r->prev, r, node); - case 0: - break; - } - // Then keep the one with the lowest internal sort key - if (node->internalSortKey < r->internalSortKey) { - // Replace the current node - node->parent = r->parent; - if (node->parent) { - if (node->parent->prev == r) { - node->parent->prev = node; - } else if (node->parent->next == r) { - node->parent->next = node; - } else { - debug(L"# Inconsistent parent value in tree\n"); - freeEnvironmentInfo(node); - return RC_INTERNAL_ERROR; - } - } else { - // If node has no parent, then it is the root. - *root = node; - } - - node->next = r->next; - node->prev = r->prev; - - debug(L"# replaced %s/%s/%i in tree\n", node->company, node->tag, node->internalSortKey); - freeEnvironmentInfo(r); - } else { - debug(L"# not adding %s/%s/%i to tree\n", node->company, node->tag, node->internalSortKey); - return RC_DUPLICATE_ITEM; - } - return 0; -} - - -/******************************************************************************\ - *** REGISTRY SEARCH *** -\******************************************************************************/ - - -int -_registryReadString(const wchar_t **dest, HKEY root, const wchar_t *subkey, const wchar_t *value) -{ - // Note that this is bytes (hence 'cb'), not characters ('cch') - DWORD cbData = 0; - DWORD flags = RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ; - - if (ERROR_SUCCESS != RegGetValueW(root, subkey, value, flags, NULL, NULL, &cbData)) { - return 0; - } - - wchar_t *buffer = (wchar_t*)malloc(cbData); - if (!buffer) { - return RC_NO_MEMORY; - } - - if (ERROR_SUCCESS == RegGetValueW(root, subkey, value, flags, NULL, buffer, &cbData)) { - *dest = buffer; - } else { - free((void *)buffer); - } - return 0; -} - - -int -_combineWithInstallDir(const wchar_t **dest, const wchar_t *installDir, const wchar_t *fragment, int fragmentLength) -{ - wchar_t buffer[MAXLEN]; - wchar_t fragmentBuffer[MAXLEN]; - if (wcsncpy_s(fragmentBuffer, MAXLEN, fragment, fragmentLength)) { - return RC_NO_MEMORY; - } - - if (FAILED(PathCchCombineEx(buffer, MAXLEN, installDir, fragmentBuffer, PATHCCH_ALLOW_LONG_PATHS))) { - return RC_NO_MEMORY; - } - - return copyWstr(dest, buffer); -} - - -bool -_isLegacyVersion(EnvironmentInfo *env) -{ - // Check if backwards-compatibility is required. - // Specifically PythonCore versions 2.X and 3.0 - 3.5 do not implement PEP 514. - if (0 != _compare(env->company, -1, L"PythonCore", -1)) { - return false; - } - - int versionMajor, versionMinor; - int n = swscanf_s(env->tag, L"%d.%d", &versionMajor, &versionMinor); - if (n != 2) { - debug(L"# %s/%s has an invalid version tag\n", env->company, env->tag); - return false; - } - - return versionMajor == 2 - || (versionMajor == 3 && versionMinor >= 0 && versionMinor <= 5); -} - -int -_registryReadLegacyEnvironment(const SearchInfo *search, HKEY root, EnvironmentInfo *env, const wchar_t *fallbackArch) -{ - // Backwards-compatibility for PythonCore versions which do not implement PEP 514. - int exitCode = _combineWithInstallDir( - &env->executablePath, - env->installDir, - search->executable, - search->executableLength - ); - if (exitCode) { - return exitCode; - } - - if (search->windowed) { - exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"WindowedExecutableArguments"); - } - else { - exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"ExecutableArguments"); - } - if (exitCode) { - return exitCode; - } - - if (fallbackArch) { - copyWstr(&env->architecture, fallbackArch); - } else { - DWORD binaryType; - BOOL success = GetBinaryTypeW(env->executablePath, &binaryType); - if (!success) { - return RC_NO_PYTHON; - } - - switch (binaryType) { - case SCS_32BIT_BINARY: - copyWstr(&env->architecture, L"32bit"); - break; - case SCS_64BIT_BINARY: - copyWstr(&env->architecture, L"64bit"); - break; - default: - return RC_NO_PYTHON; - } - } - - if (0 == _compare(env->architecture, -1, L"32bit", -1)) { - size_t tagLength = wcslen(env->tag); - if (tagLength <= 3 || 0 != _compare(&env->tag[tagLength - 3], 3, L"-32", 3)) { - const wchar_t *rawTag = env->tag; - wchar_t *realTag = (wchar_t*) malloc(sizeof(wchar_t) * (tagLength + 4)); - if (!realTag) { - return RC_NO_MEMORY; - } - - int count = swprintf_s(realTag, tagLength + 4, L"%s-32", env->tag); - if (count == -1) { - debug(L"# Failed to generate 32bit tag\n"); - free(realTag); - return RC_INTERNAL_ERROR; - } - - env->tag = realTag; - free((void*)rawTag); - } - } - - wchar_t buffer[MAXLEN]; - if (swprintf_s(buffer, MAXLEN, L"Python %s", env->tag)) { - copyWstr(&env->displayName, buffer); - } - - return 0; -} - - -int -_registryReadEnvironment(const SearchInfo *search, HKEY root, EnvironmentInfo *env, const wchar_t *fallbackArch) -{ - int exitCode = _registryReadString(&env->installDir, root, L"InstallPath", NULL); - if (exitCode) { - return exitCode; - } - if (!env->installDir) { - return RC_NO_PYTHON; - } - - if (_isLegacyVersion(env)) { - return _registryReadLegacyEnvironment(search, root, env, fallbackArch); - } - - // If pythonw.exe requested, check specific value - if (search->windowed) { - exitCode = _registryReadString(&env->executablePath, root, L"InstallPath", L"WindowedExecutablePath"); - if (!exitCode && env->executablePath) { - exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"WindowedExecutableArguments"); - } - } - if (exitCode) { - return exitCode; - } - - // Missing windowed path or non-windowed request means we use ExecutablePath - if (!env->executablePath) { - exitCode = _registryReadString(&env->executablePath, root, L"InstallPath", L"ExecutablePath"); - if (!exitCode && env->executablePath) { - exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"ExecutableArguments"); - } - } - if (exitCode) { - return exitCode; - } - - if (!env->executablePath) { - debug(L"# %s/%s has no executable path\n", env->company, env->tag); - return RC_NO_PYTHON; - } - - exitCode = _registryReadString(&env->architecture, root, NULL, L"SysArchitecture"); - if (exitCode) { - return exitCode; - } - - exitCode = _registryReadString(&env->displayName, root, NULL, L"DisplayName"); - if (exitCode) { - return exitCode; - } - - return 0; -} - -int -_registrySearchTags(const SearchInfo *search, EnvironmentInfo **result, HKEY root, int sortKey, const wchar_t *company, const wchar_t *fallbackArch) -{ - wchar_t buffer[256]; - int err = 0; - int exitCode = 0; - for (int i = 0; exitCode == 0; ++i) { - DWORD cchBuffer = sizeof(buffer) / sizeof(buffer[0]); - err = RegEnumKeyExW(root, i, buffer, &cchBuffer, NULL, NULL, NULL, NULL); - if (err) { - if (err != ERROR_NO_MORE_ITEMS) { - winerror(0, L"Failed to read installs (tags) from the registry"); - } - break; - } - HKEY subkey; - if (ERROR_SUCCESS == RegOpenKeyExW(root, buffer, 0, KEY_READ, &subkey)) { - EnvironmentInfo *env = newEnvironmentInfo(company, buffer); - env->internalSortKey = sortKey; - exitCode = _registryReadEnvironment(search, subkey, env, fallbackArch); - RegCloseKey(subkey); - if (exitCode == RC_NO_PYTHON) { - freeEnvironmentInfo(env); - exitCode = 0; - } else if (!exitCode) { - exitCode = addEnvironmentInfo(result, NULL, env); - if (exitCode) { - freeEnvironmentInfo(env); - if (exitCode == RC_DUPLICATE_ITEM) { - exitCode = 0; - } - } - } - } - } - return exitCode; -} - - -int -registrySearch(const SearchInfo *search, EnvironmentInfo **result, HKEY root, int sortKey, const wchar_t *fallbackArch) -{ - wchar_t buffer[256]; - int err = 0; - int exitCode = 0; - for (int i = 0; exitCode == 0; ++i) { - DWORD cchBuffer = sizeof(buffer) / sizeof(buffer[0]); - err = RegEnumKeyExW(root, i, buffer, &cchBuffer, NULL, NULL, NULL, NULL); - if (err) { - if (err != ERROR_NO_MORE_ITEMS) { - winerror(0, L"Failed to read distributors (company) from the registry"); - } - break; - } - if (search->limitToCompany && 0 != _compare(search->limitToCompany, -1, buffer, cchBuffer)) { - debug(L"# Skipping %s due to PYLAUNCHER_LIMIT_TO_COMPANY\n", buffer); - continue; - } - HKEY subkey; - if (ERROR_SUCCESS == RegOpenKeyExW(root, buffer, 0, KEY_READ, &subkey)) { - exitCode = _registrySearchTags(search, result, subkey, sortKey, buffer, fallbackArch); - RegCloseKey(subkey); - } - } - return exitCode; -} - - -/******************************************************************************\ - *** APP PACKAGE SEARCH *** -\******************************************************************************/ - -int -appxSearch(const SearchInfo *search, EnvironmentInfo **result, const wchar_t *packageFamilyName, const wchar_t *tag, int sortKey) -{ - wchar_t realTag[32]; - wchar_t buffer[MAXLEN]; - const wchar_t *exeName = search->executable; - if (!exeName || search->allowExecutableOverride) { - exeName = search->windowed ? L"pythonw.exe" : L"python.exe"; - } - - // Failure to get LocalAppData may just mean we're running as a user who - // doesn't have a profile directory. - // In this case, return "not found", but don't fail. - // Chances are they can't launch Store installs anyway. - if (FAILED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, buffer))) { - return RC_NO_PYTHON; - } - - if (!join(buffer, MAXLEN, L"Microsoft\\WindowsApps") || - !join(buffer, MAXLEN, packageFamilyName) || - !join(buffer, MAXLEN, exeName)) { - debug(L"# Failed to construct App Execution Alias path\n"); - return RC_INTERNAL_ERROR; - } - - if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(buffer)) { - return RC_NO_PYTHON; - } - - // Assume packages are native architecture, which means we need to append - // the '-arm64' on ARM64 host. - wcscpy_s(realTag, 32, tag); - if (isARM64Host()) { - wcscat_s(realTag, 32, L"-arm64"); - } - - EnvironmentInfo *env = newEnvironmentInfo(L"PythonCore", realTag); - if (!env) { - return RC_NO_MEMORY; - } - env->internalSortKey = sortKey; - if (isAMD64Host()) { - copyWstr(&env->architecture, L"64bit"); - } else if (isARM64Host()) { - copyWstr(&env->architecture, L"ARM64"); - } - - copyWstr(&env->executablePath, buffer); - - if (swprintf_s(buffer, MAXLEN, L"Python %s (Store)", tag)) { - copyWstr(&env->displayName, buffer); - } - - int exitCode = addEnvironmentInfo(result, NULL, env); - if (exitCode) { - freeEnvironmentInfo(env); - if (exitCode == RC_DUPLICATE_ITEM) { - exitCode = 0; - } - } - - - return exitCode; -} - - -/******************************************************************************\ - *** OVERRIDDEN EXECUTABLE PATH *** -\******************************************************************************/ - - -int -explicitOverrideSearch(const SearchInfo *search, EnvironmentInfo **result) -{ - if (!search->executablePath) { - return 0; - } - - EnvironmentInfo *env = newEnvironmentInfo(NULL, NULL); - if (!env) { - return RC_NO_MEMORY; - } - env->internalSortKey = 10; - int exitCode = copyWstr(&env->executablePath, search->executablePath); - if (exitCode) { - goto abort; - } - exitCode = copyWstr(&env->displayName, L"Explicit override"); - if (exitCode) { - goto abort; - } - exitCode = addEnvironmentInfo(result, NULL, env); - if (exitCode) { - goto abort; - } - return 0; - -abort: - freeEnvironmentInfo(env); - if (exitCode == RC_DUPLICATE_ITEM) { - exitCode = 0; - } - return exitCode; -} - - -/******************************************************************************\ - *** ACTIVE VIRTUAL ENVIRONMENT SEARCH *** -\******************************************************************************/ - -int -virtualenvSearch(const SearchInfo *search, EnvironmentInfo **result) -{ - int exitCode = 0; - EnvironmentInfo *env = NULL; - wchar_t buffer[MAXLEN]; - int n = GetEnvironmentVariableW(L"VIRTUAL_ENV", buffer, MAXLEN); - if (!n || !join(buffer, MAXLEN, L"Scripts") || !join(buffer, MAXLEN, search->executable)) { - return 0; - } - - DWORD attr = GetFileAttributesW(buffer); - if (INVALID_FILE_ATTRIBUTES == attr && search->lowPriorityTag) { - if (!split_parent(buffer, MAXLEN) || !join(buffer, MAXLEN, L"python.exe")) { - return 0; - } - attr = GetFileAttributesW(buffer); - } - - if (INVALID_FILE_ATTRIBUTES == attr) { - debug(L"Python executable %s missing from virtual env\n", buffer); - return 0; - } - - env = newEnvironmentInfo(NULL, NULL); - if (!env) { - return RC_NO_MEMORY; - } - env->highPriority = true; - env->internalSortKey = 20; - exitCode = copyWstr(&env->displayName, L"Active venv"); - if (exitCode) { - goto abort; - } - exitCode = copyWstr(&env->executablePath, buffer); - if (exitCode) { - goto abort; - } - exitCode = addEnvironmentInfo(result, NULL, env); - if (exitCode) { - goto abort; - } - return 0; - -abort: - freeEnvironmentInfo(env); - if (exitCode == RC_DUPLICATE_ITEM) { - return 0; - } - return exitCode; -} - -/******************************************************************************\ - *** COLLECT ENVIRONMENTS *** -\******************************************************************************/ - - -struct RegistrySearchInfo { - // Registry subkey to search - const wchar_t *subkey; - // Registry hive to search - HKEY hive; - // Flags to use when opening the subkey - DWORD flags; - // Internal sort key to select between "identical" environments discovered - // through different methods - int sortKey; - // Fallback value to assume for PythonCore entries missing a SysArchitecture value - const wchar_t *fallbackArch; -}; - - -struct RegistrySearchInfo REGISTRY_SEARCH[] = { - { - L"Software\\Python", - HKEY_CURRENT_USER, - KEY_READ, - 1, - NULL - }, - { - L"Software\\Python", - HKEY_LOCAL_MACHINE, - KEY_READ | KEY_WOW64_64KEY, - 3, - L"64bit" - }, - { - L"Software\\Python", - HKEY_LOCAL_MACHINE, - KEY_READ | KEY_WOW64_32KEY, - 4, - L"32bit" - }, - { NULL, 0, 0, 0, NULL } -}; - - -struct AppxSearchInfo { - // The package family name. Can be found for an installed package using the - // Powershell "Get-AppxPackage" cmdlet - const wchar_t *familyName; - // The tag to treat the installation as - const wchar_t *tag; - // Internal sort key to select between "identical" environments discovered - // through different methods - int sortKey; -}; - - -struct AppxSearchInfo APPX_SEARCH[] = { - // Releases made through the Store - { L"PythonSoftwareFoundation.Python.3.14_qbz5n2kfra8p0", L"3.14", 10 }, - { L"PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0", L"3.13", 10 }, - { L"PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0", L"3.12", 10 }, - { L"PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0", L"3.11", 10 }, - { L"PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0", L"3.10", 10 }, - { L"PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0", L"3.9", 10 }, - { L"PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0", L"3.8", 10 }, - - // Side-loadable releases. Note that the publisher ID changes whenever we - // change our code signing certificate subject, so the newer IDs have higher - // priorities (lower sortKey) - { L"PythonSoftwareFoundation.Python.3.14_3847v3x7pw1km", L"3.14", 11 }, - { L"PythonSoftwareFoundation.Python.3.13_3847v3x7pw1km", L"3.13", 11 }, - { L"PythonSoftwareFoundation.Python.3.12_3847v3x7pw1km", L"3.12", 11 }, - { L"PythonSoftwareFoundation.Python.3.11_3847v3x7pw1km", L"3.11", 11 }, - { L"PythonSoftwareFoundation.Python.3.11_hd69rhyc2wevp", L"3.11", 12 }, - { L"PythonSoftwareFoundation.Python.3.10_3847v3x7pw1km", L"3.10", 11 }, - { L"PythonSoftwareFoundation.Python.3.10_hd69rhyc2wevp", L"3.10", 12 }, - { L"PythonSoftwareFoundation.Python.3.9_3847v3x7pw1km", L"3.9", 11 }, - { L"PythonSoftwareFoundation.Python.3.9_hd69rhyc2wevp", L"3.9", 12 }, - { L"PythonSoftwareFoundation.Python.3.8_hd69rhyc2wevp", L"3.8", 12 }, - { NULL, NULL, 0 } -}; - - -int -collectEnvironments(const SearchInfo *search, EnvironmentInfo **result) -{ - int exitCode = 0; - HKEY root; - EnvironmentInfo *env = NULL; - - if (!result) { - debug(L"# collectEnvironments() was passed a NULL result\n"); - return RC_INTERNAL_ERROR; - } - *result = NULL; - - exitCode = explicitOverrideSearch(search, result); - if (exitCode) { - return exitCode; - } - - exitCode = virtualenvSearch(search, result); - if (exitCode) { - return exitCode; - } - - // If we aren't collecting all items to list them, we can exit now. - if (env && !(search->list || search->listPaths)) { - return 0; - } - - for (struct RegistrySearchInfo *info = REGISTRY_SEARCH; info->subkey; ++info) { - if (ERROR_SUCCESS == RegOpenKeyExW(info->hive, info->subkey, 0, info->flags, &root)) { - exitCode = registrySearch(search, result, root, info->sortKey, info->fallbackArch); - RegCloseKey(root); - } - if (exitCode) { - return exitCode; - } - } - - if (search->limitToCompany) { - debug(L"# Skipping APPX search due to PYLAUNCHER_LIMIT_TO_COMPANY\n"); - return 0; - } - - for (struct AppxSearchInfo *info = APPX_SEARCH; info->familyName; ++info) { - exitCode = appxSearch(search, result, info->familyName, info->tag, info->sortKey); - if (exitCode && exitCode != RC_NO_PYTHON) { - return exitCode; - } - } - - return 0; -} - - -/******************************************************************************\ - *** INSTALL ON DEMAND *** -\******************************************************************************/ - -struct StoreSearchInfo { - // The tag a user is looking for - const wchar_t *tag; - // The Store ID for a package if it can be installed from the Microsoft - // Store. These are obtained from the dashboard at - // https://partner.microsoft.com/dashboard - const wchar_t *storeId; -}; - - -struct StoreSearchInfo STORE_SEARCH[] = { - { L"3", /* 3.13 */ L"9PNRBTZXMB4Z" }, - { L"3.14", L"9NTRHQCBBPR8" }, - { L"3.13", L"9PNRBTZXMB4Z" }, - { L"3.12", L"9NCVDN91XZQP" }, - { L"3.11", L"9NRWMJP3717K" }, - { L"3.10", L"9PJPW5LDXLZ5" }, - { L"3.9", L"9P7QFQMJRFP7" }, - { L"3.8", L"9MSSZTT1N39L" }, - { NULL, NULL } -}; - - -int -_installEnvironment(const wchar_t *command, const wchar_t *arguments) -{ - SHELLEXECUTEINFOW siw = { - sizeof(SHELLEXECUTEINFOW), - SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE, - NULL, NULL, - command, arguments, NULL, - SW_SHOWNORMAL - }; - - debug(L"# Installing with %s %s\n", command, arguments); - if (isEnvVarSet(L"PYLAUNCHER_DRYRUN")) { - debug(L"# Exiting due to PYLAUNCHER_DRYRUN\n"); - fflush(stdout); - int mode = _setmode(_fileno(stdout), _O_U8TEXT); - if (arguments) { - fwprintf_s(stdout, L"\"%s\" %s\n", command, arguments); - } else { - fwprintf_s(stdout, L"\"%s\"\n", command); - } - fflush(stdout); - if (mode >= 0) { - _setmode(_fileno(stdout), mode); - } - return RC_INSTALLING; - } - - if (!ShellExecuteExW(&siw)) { - return RC_NO_PYTHON; - } - - if (!siw.hProcess) { - return RC_INSTALLING; - } - - WaitForSingleObjectEx(siw.hProcess, INFINITE, FALSE); - DWORD exitCode = 0; - if (GetExitCodeProcess(siw.hProcess, &exitCode) && exitCode == 0) { - return 0; - } - return RC_INSTALLING; -} - - -const wchar_t *WINGET_COMMAND = L"Microsoft\\WindowsApps\\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\\winget.exe"; -const wchar_t *WINGET_ARGUMENTS = L"install -q %s --exact --accept-package-agreements --source msstore"; - -const wchar_t *MSSTORE_COMMAND = L"ms-windows-store://pdp/?productid=%s"; - -int -installEnvironment(const SearchInfo *search) -{ - // No tag? No installing - if (!search->tag || !search->tagLength) { - debug(L"# Cannot install Python with no tag specified\n"); - return RC_NO_PYTHON; - } - - // PEP 514 tag but not PythonCore? No installing - if (!search->oldStyleTag && - search->company && search->companyLength && - 0 != _compare(search->company, search->companyLength, L"PythonCore", -1)) { - debug(L"# Cannot install for company %.*s\n", search->companyLength, search->company); - return RC_NO_PYTHON; - } - - const wchar_t *storeId = NULL; - for (struct StoreSearchInfo *info = STORE_SEARCH; info->tag; ++info) { - if (0 == _compare(search->tag, search->tagLength, info->tag, -1)) { - storeId = info->storeId; - break; - } - } - - if (!storeId) { - return RC_NO_PYTHON; - } - - int exitCode; - wchar_t command[MAXLEN]; - wchar_t arguments[MAXLEN]; - if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, command)) && - join(command, MAXLEN, WINGET_COMMAND) && - swprintf_s(arguments, MAXLEN, WINGET_ARGUMENTS, storeId)) { - if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(command)) { - formatWinerror(GetLastError(), arguments, MAXLEN); - debug(L"# Skipping %s: %s\n", command, arguments); - } else { - fputws(L"Launching winget to install Python. The following output is from the install process\n\ -***********************************************************************\n", stdout); - exitCode = _installEnvironment(command, arguments); - if (exitCode == RC_INSTALLING) { - fputws(L"***********************************************************************\n\ -Please check the install status and run your command again.", stderr); - return exitCode; - } else if (exitCode) { - return exitCode; - } - fputws(L"***********************************************************************\n\ -Install appears to have succeeded. Searching for new matching installs.\n", stdout); - return 0; - } - } - - if (swprintf_s(command, MAXLEN, MSSTORE_COMMAND, storeId)) { - fputws(L"Opening the Microsoft Store to install Python. After installation, " - L"please run your command again.\n", stderr); - exitCode = _installEnvironment(command, NULL); - if (exitCode) { - return exitCode; - } - return 0; - } - - return RC_NO_PYTHON; -} - -/******************************************************************************\ - *** ENVIRONMENT SELECT *** -\******************************************************************************/ - -bool -_companyMatches(const SearchInfo *search, const EnvironmentInfo *env) -{ - if (!search->company || !search->companyLength) { - return true; - } - return 0 == _compare(env->company, -1, search->company, search->companyLength); -} - - -bool -_tagMatches(const SearchInfo *search, const EnvironmentInfo *env, int searchTagLength) -{ - if (searchTagLength < 0) { - searchTagLength = search->tagLength; - } - if (!search->tag || !searchTagLength) { - return true; - } - return _startsWithSeparated(env->tag, -1, search->tag, searchTagLength, L".-"); -} - - -bool -_is32Bit(const EnvironmentInfo *env) -{ - if (env->architecture) { - return 0 == _compare(env->architecture, -1, L"32bit", -1); - } - return false; -} - - -int -_selectEnvironment(const SearchInfo *search, EnvironmentInfo *env, EnvironmentInfo **best) -{ - int exitCode = 0; - while (env) { - exitCode = _selectEnvironment(search, env->prev, best); - - if (exitCode && exitCode != RC_NO_PYTHON) { - return exitCode; - } else if (!exitCode && *best) { - return 0; - } - - if (env->highPriority && search->lowPriorityTag) { - // This environment is marked high priority, and the search allows - // it to be selected even though a tag is specified, so select it - // gh-92817: this allows an active venv to be selected even when a - // default tag has been found in py.ini or the environment - *best = env; - return 0; - } - - if (!search->oldStyleTag) { - if (_companyMatches(search, env) && _tagMatches(search, env, -1)) { - // Because of how our sort tree is set up, we will walk up the - // "prev" side and implicitly select the "best" best. By - // returning straight after a match, we skip the entire "next" - // branch and won't ever select a "worse" best. - *best = env; - return 0; - } - } else if (0 == _compare(env->company, -1, L"PythonCore", -1)) { - // Old-style tags can only match PythonCore entries - - // If the tag ends with -64, we want to exclude 32-bit runtimes - // (If the tag ends with -32, it will be filtered later) - int tagLength = search->tagLength; - bool exclude32Bit = false, only32Bit = false; - if (tagLength > 3) { - if (0 == _compareArgument(&search->tag[tagLength - 3], 3, L"-64", 3)) { - tagLength -= 3; - exclude32Bit = true; - } else if (0 == _compareArgument(&search->tag[tagLength - 3], 3, L"-32", 3)) { - tagLength -= 3; - only32Bit = true; - } - } - - if (_tagMatches(search, env, tagLength)) { - if (exclude32Bit && _is32Bit(env)) { - debug(L"# Excluding %s/%s because it looks like 32bit\n", env->company, env->tag); - } else if (only32Bit && !_is32Bit(env)) { - debug(L"# Excluding %s/%s because it doesn't look 32bit\n", env->company, env->tag); - } else { - *best = env; - return 0; - } - } - } - - env = env->next; - } - return RC_NO_PYTHON; -} - -int -selectEnvironment(const SearchInfo *search, EnvironmentInfo *root, EnvironmentInfo **best) -{ - if (!best) { - debug(L"# selectEnvironment() was passed a NULL best\n"); - return RC_INTERNAL_ERROR; - } - if (!root) { - *best = NULL; - return RC_NO_PYTHON_AT_ALL; - } - - EnvironmentInfo *result = NULL; - int exitCode = _selectEnvironment(search, root, &result); - if (!exitCode) { - *best = result; - } - - return exitCode; -} - - -/******************************************************************************\ - *** LIST ENVIRONMENTS *** -\******************************************************************************/ - -#define TAGWIDTH 16 - -int -_printEnvironment(const EnvironmentInfo *env, FILE *out, bool showPath, const wchar_t *argument) -{ - if (showPath) { - if (env->executablePath && env->executablePath[0]) { - if (env->executableArgs && env->executableArgs[0]) { - fwprintf(out, L" %-*s %s %s\n", TAGWIDTH, argument, env->executablePath, env->executableArgs); - } else { - fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->executablePath); - } - } else if (env->installDir && env->installDir[0]) { - fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->installDir); - } else { - fwprintf(out, L" %s\n", argument); - } - } else if (env->displayName) { - fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->displayName); - } else { - fwprintf(out, L" %s\n", argument); - } - return 0; -} - - -int -_listAllEnvironments(EnvironmentInfo *env, FILE * out, bool showPath, EnvironmentInfo *defaultEnv) -{ - wchar_t buffer[256]; - const int bufferSize = 256; - while (env) { - int exitCode = _listAllEnvironments(env->prev, out, showPath, defaultEnv); - if (exitCode) { - return exitCode; - } - - if (!env->company || !env->tag) { - buffer[0] = L'\0'; - } else if (0 == _compare(env->company, -1, L"PythonCore", -1)) { - swprintf_s(buffer, bufferSize, L"-V:%s", env->tag); - } else { - swprintf_s(buffer, bufferSize, L"-V:%s/%s", env->company, env->tag); - } - - if (env == defaultEnv) { - wcscat_s(buffer, bufferSize, L" *"); - } - - if (buffer[0]) { - exitCode = _printEnvironment(env, out, showPath, buffer); - if (exitCode) { - return exitCode; - } - } - - env = env->next; - } - return 0; -} - - -int -listEnvironments(EnvironmentInfo *env, FILE * out, bool showPath, EnvironmentInfo *defaultEnv) -{ - if (!env) { - fwprintf_s(stdout, L"No installed Pythons found!\n"); - return 0; - } - - /* TODO: Do we want to display these? - In favour, helps users see that '-3' is a good option - Against, repeats the next line of output - SearchInfo majorSearch; - EnvironmentInfo *major; - int exitCode; - - if (showPath) { - memset(&majorSearch, 0, sizeof(majorSearch)); - majorSearch.company = L"PythonCore"; - majorSearch.companyLength = -1; - majorSearch.tag = L"3"; - majorSearch.tagLength = -1; - majorSearch.oldStyleTag = true; - major = NULL; - exitCode = selectEnvironment(&majorSearch, env, &major); - if (!exitCode && major) { - exitCode = _printEnvironment(major, out, showPath, L"-3 *"); - isDefault = false; - if (exitCode) { - return exitCode; - } - } - majorSearch.tag = L"2"; - major = NULL; - exitCode = selectEnvironment(&majorSearch, env, &major); - if (!exitCode && major) { - exitCode = _printEnvironment(major, out, showPath, L"-2"); - if (exitCode) { - return exitCode; - } - } - } - */ - - int mode = _setmode(_fileno(out), _O_U8TEXT); - int exitCode = _listAllEnvironments(env, out, showPath, defaultEnv); - fflush(out); - if (mode >= 0) { - _setmode(_fileno(out), mode); - } - return exitCode; -} - - -/******************************************************************************\ - *** INTERPRETER LAUNCH *** -\******************************************************************************/ - - -int -calculateCommandLine(const SearchInfo *search, const EnvironmentInfo *launch, wchar_t *buffer, int bufferLength) -{ - int exitCode = 0; - const wchar_t *executablePath = NULL; - - // Construct command line from a search override, or else the selected - // environment's executablePath - if (search->executablePath) { - executablePath = search->executablePath; - } else if (launch && launch->executablePath) { - executablePath = launch->executablePath; - } - - // If we have an executable path, put it at the start of the command, but - // only if the search allowed an override. - // Otherwise, use the environment's installDir and the search's default - // executable name. - if (executablePath && search->allowExecutableOverride) { - if (wcschr(executablePath, L' ') && executablePath[0] != L'"') { - buffer[0] = L'"'; - exitCode = wcscpy_s(&buffer[1], bufferLength - 1, executablePath); - if (!exitCode) { - exitCode = wcscat_s(buffer, bufferLength, L"\""); - } - } else { - exitCode = wcscpy_s(buffer, bufferLength, executablePath); - } - } else if (launch) { - if (!launch->installDir) { - fwprintf_s(stderr, L"Cannot launch %s %s because no install directory was specified", - launch->company, launch->tag); - exitCode = RC_NO_PYTHON; - } else if (!search->executable || !search->executableLength) { - fwprintf_s(stderr, L"Cannot launch %s %s because no executable name is available", - launch->company, launch->tag); - exitCode = RC_NO_PYTHON; - } else { - wchar_t executable[256]; - wcsncpy_s(executable, 256, search->executable, search->executableLength); - if ((wcschr(launch->installDir, L' ') && launch->installDir[0] != L'"') || - (wcschr(executable, L' ') && executable[0] != L'"')) { - buffer[0] = L'"'; - exitCode = wcscpy_s(&buffer[1], bufferLength - 1, launch->installDir); - if (!exitCode) { - exitCode = join(buffer, bufferLength, executable) ? 0 : RC_NO_MEMORY; - } - if (!exitCode) { - exitCode = wcscat_s(buffer, bufferLength, L"\""); - } - } else { - exitCode = wcscpy_s(buffer, bufferLength, launch->installDir); - if (!exitCode) { - exitCode = join(buffer, bufferLength, executable) ? 0 : RC_NO_MEMORY; - } - } - } - } else { - exitCode = RC_NO_PYTHON; - } - - if (!exitCode && launch && launch->executableArgs) { - exitCode = wcscat_s(buffer, bufferLength, L" "); - if (!exitCode) { - exitCode = wcscat_s(buffer, bufferLength, launch->executableArgs); - } - } - - if (!exitCode && search->executableArgs) { - if (search->executableArgsLength < 0) { - exitCode = wcscat_s(buffer, bufferLength, search->executableArgs); - } else if (search->executableArgsLength > 0) { - int end = (int)wcsnlen_s(buffer, MAXLEN); - if (end < bufferLength - (search->executableArgsLength + 1)) { - exitCode = wcsncpy_s(&buffer[end], bufferLength - end, - search->executableArgs, search->executableArgsLength); - } - } - } - - if (!exitCode && search->restOfCmdLine) { - exitCode = wcscat_s(buffer, bufferLength, search->restOfCmdLine); - } - - return exitCode; -} - - - -BOOL -_safeDuplicateHandle(HANDLE in, HANDLE * pout, const wchar_t *nameForError) -{ - BOOL ok; - HANDLE process = GetCurrentProcess(); - DWORD rc; - - *pout = NULL; - ok = DuplicateHandle(process, in, process, pout, 0, TRUE, - DUPLICATE_SAME_ACCESS); - if (!ok) { - rc = GetLastError(); - if (rc == ERROR_INVALID_HANDLE) { - debug(L"DuplicateHandle returned ERROR_INVALID_HANDLE\n"); - ok = TRUE; - } - else { - winerror(0, L"Failed to duplicate %s handle", nameForError); - } - } - return ok; -} - -BOOL WINAPI -ctrl_c_handler(DWORD code) -{ - return TRUE; /* We just ignore all control events. */ -} - - -int -launchEnvironment(const SearchInfo *search, const EnvironmentInfo *launch, wchar_t *launchCommand) -{ - HANDLE job; - JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; - DWORD rc; - BOOL ok; - STARTUPINFOW si; - PROCESS_INFORMATION pi; - - // If this is a dryrun, do not actually launch - if (isEnvVarSet(L"PYLAUNCHER_DRYRUN")) { - debug(L"LaunchCommand: %s\n", launchCommand); - debug(L"# Exiting due to PYLAUNCHER_DRYRUN variable\n"); - fflush(stdout); - int mode = _setmode(_fileno(stdout), _O_U8TEXT); - fwprintf(stdout, L"%s\n", launchCommand); - fflush(stdout); - if (mode >= 0) { - _setmode(_fileno(stdout), mode); - } - return 0; - } - -#if defined(_WINDOWS) - /* - When explorer launches a Windows (GUI) application, it displays - the "app starting" (the "pointer + hourglass") cursor for a number - of seconds, or until the app does something UI-ish (eg, creating a - window, or fetching a message). As this launcher doesn't do this - directly, that cursor remains even after the child process does these - things. We avoid that by doing a simple post+get message. - See http://bugs.python.org/issue17290 - */ - MSG msg; - - PostMessage(0, 0, 0, 0); - GetMessage(&msg, 0, 0, 0); -#endif - - debug(L"# about to run: %s\n", launchCommand); - job = CreateJobObject(NULL, NULL); - ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation, - &info, sizeof(info), &rc); - if (!ok || (rc != sizeof(info)) || !job) { - winerror(0, L"Failed to query job information"); - return RC_CREATE_PROCESS; - } - info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | - JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; - ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info, - sizeof(info)); - if (!ok) { - winerror(0, L"Failed to update job information"); - return RC_CREATE_PROCESS; - } - memset(&si, 0, sizeof(si)); - GetStartupInfoW(&si); - if (!_safeDuplicateHandle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput, L"stdin") || - !_safeDuplicateHandle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput, L"stdout") || - !_safeDuplicateHandle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError, L"stderr")) { - return RC_NO_STD_HANDLES; - } - - ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE); - if (!ok) { - winerror(0, L"Failed to update Control-C handler"); - return RC_NO_STD_HANDLES; - } - - si.dwFlags = STARTF_USESTDHANDLES; - ok = CreateProcessW(NULL, launchCommand, NULL, NULL, TRUE, - 0, NULL, NULL, &si, &pi); - if (!ok) { - winerror(0, L"Unable to create process using '%s'", launchCommand); - return RC_CREATE_PROCESS; - } - AssignProcessToJobObject(job, pi.hProcess); - CloseHandle(pi.hThread); - WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE); - ok = GetExitCodeProcess(pi.hProcess, &rc); - if (!ok) { - winerror(0, L"Failed to get exit code of process"); - return RC_CREATE_PROCESS; - } - debug(L"child process exit code: %d\n", rc); - return rc; -} - - -/******************************************************************************\ - *** PROCESS CONTROLLER *** -\******************************************************************************/ - - -int -performSearch(SearchInfo *search, EnvironmentInfo **envs) -{ - // First parse the command line for options - int exitCode = parseCommandLine(search); - if (exitCode) { - return exitCode; - } - - // Check for a shebang line in our script file - // (or return quickly if no script file was specified) - exitCode = checkShebang(search); - switch (exitCode) { - case 0: - case RC_NO_SHEBANG: - case RC_RECURSIVE_SHEBANG: - break; - case RC_NO_SCRIPT: - if (!_comparePath(search->scriptFile, search->scriptFileLength, L"install", -1) || - !_comparePath(search->scriptFile, search->scriptFileLength, L"uninstall", -1) || - !_comparePath(search->scriptFile, search->scriptFileLength, L"list", -1) || - !_comparePath(search->scriptFile, search->scriptFileLength, L"help", -1)) { - fprintf( - stderr, - "WARNING: The '%.*ls' command is unavailable because this is the legacy py.exe command.\n" - "If you have already installed the Python install manager, open Installed Apps and " - "remove 'Python Launcher' to enable the new py.exe command.\n", - search->scriptFileLength, - search->scriptFile - ); - } - break; - default: - return exitCode; - } - - // Resolve old-style tags (possibly from a shebang) against py.ini entries - // and environment variables. - exitCode = checkDefaults(search); - if (exitCode) { - return exitCode; - } - - // If debugging is enabled, list our search criteria - dumpSearchInfo(search); - - // Find all matching environments - exitCode = collectEnvironments(search, envs); - if (exitCode) { - return exitCode; - } - - return 0; -} - - -int -process(int argc, wchar_t ** argv) -{ - int exitCode = 0; - int searchExitCode = 0; - SearchInfo search = {0}; - EnvironmentInfo *envs = NULL; - EnvironmentInfo *env = NULL; - wchar_t launchCommand[MAXLEN]; - - memset(launchCommand, 0, sizeof(launchCommand)); - - if (isEnvVarSet(L"PYLAUNCHER_DEBUG")) { - setvbuf(stderr, (char *)NULL, _IONBF, 0); - log_fp = stderr; - debug(L"argv0: %s\nversion: %S\n", argv[0], PY_VERSION); - } - - DWORD len = GetEnvironmentVariableW(L"PYLAUNCHER_LIMIT_TO_COMPANY", NULL, 0); - if (len > 1) { - wchar_t *limitToCompany = allocSearchInfoBuffer(&search, len); - if (!limitToCompany) { - exitCode = RC_NO_MEMORY; - winerror(0, L"Failed to allocate internal buffer"); - goto abort; - } - search.limitToCompany = limitToCompany; - if (0 == GetEnvironmentVariableW(L"PYLAUNCHER_LIMIT_TO_COMPANY", limitToCompany, len)) { - exitCode = RC_INTERNAL_ERROR; - winerror(0, L"Failed to read PYLAUNCHER_LIMIT_TO_COMPANY variable"); - goto abort; - } - } - - search.originalCmdLine = GetCommandLineW(); - - exitCode = performSearch(&search, &envs); - if (exitCode) { - goto abort; - } - - // Display the help text, but only exit on error - if (search.help) { - exitCode = showHelpText(argv); - if (exitCode) { - goto abort; - } - } - - // Select best environment - // This is early so that we can show the default when listing, but all - // responses to any errors occur later. - searchExitCode = selectEnvironment(&search, envs, &env); - - // List all environments, then exit - if (search.list || search.listPaths) { - exitCode = listEnvironments(envs, stdout, search.listPaths, env); - goto abort; - } - - // When debugging, list all discovered environments anyway - if (log_fp) { - exitCode = listEnvironments(envs, log_fp, true, NULL); - if (exitCode) { - goto abort; - } - } - - // We searched earlier, so if we didn't find anything, now we react - exitCode = searchExitCode; - // If none found, and if permitted, install it - if (((exitCode == RC_NO_PYTHON) && isEnvVarSet(L"PYLAUNCHER_ALLOW_INSTALL")) || - isEnvVarSet(L"PYLAUNCHER_ALWAYS_INSTALL")) { - exitCode = installEnvironment(&search); - if (!exitCode) { - // Successful install, so we need to re-scan and select again - env = NULL; - exitCode = performSearch(&search, &envs); - if (exitCode) { - goto abort; - } - exitCode = selectEnvironment(&search, envs, &env); - } - } - if (exitCode == RC_NO_PYTHON) { - fputws(L"No suitable Python runtime found\n", stderr); - fputws(L"Pass --list (-0) to see all detected environments on your machine\n", stderr); - if (!isEnvVarSet(L"PYLAUNCHER_ALLOW_INSTALL") && search.oldStyleTag) { - fputws(L"or set environment variable PYLAUNCHER_ALLOW_INSTALL to use winget\n" - L"or open the Microsoft Store to the requested version.\n", stderr); - } - goto abort; - } - if (exitCode == RC_NO_PYTHON_AT_ALL) { - fputws(L"No installed Python found!\n", stderr); - goto abort; - } - if (exitCode) { - goto abort; - } - - if (env) { - debug(L"env.company: %s\nenv.tag: %s\n", env->company, env->tag); - } else { - debug(L"env.company: (null)\nenv.tag: (null)\n"); - } - - exitCode = calculateCommandLine(&search, env, launchCommand, sizeof(launchCommand) / sizeof(launchCommand[0])); - if (exitCode) { - goto abort; - } - - // Launch selected runtime - exitCode = launchEnvironment(&search, env, launchCommand); - -abort: - freeSearchInfo(&search); - freeEnvironmentInfo(envs); - return exitCode; -} - - -#if defined(_WINDOWS) - -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, - LPWSTR lpstrCmd, int nShow) -{ - return process(__argc, __wargv); -} - -#else - -int cdecl wmain(int argc, wchar_t ** argv) -{ - return process(argc, argv); -} - -#endif diff --git a/PC/layout/README.md b/PC/layout/README.md new file mode 100644 index 000000000000000..086cc23194b1b96 --- /dev/null +++ b/PC/layout/README.md @@ -0,0 +1,341 @@ +# PC/layout script + +This document provides an overview of the `PC/layout` script, which is used to +convert from a build tree to a ready-to-run directory of files. It's the +equivalent of `make install` when using the Makefile. We use it for official +binary releases, but it also supports a lot of configuration and can be used for +whatever your custom scenario should look like. + +The script never builds anything. It reads files from an existing build tree +(`-b`) and the source tree (`-s`), then copies or zips them into a layout. It +does not require the Python being laid out to be runnable, and it can target a +different version from the one running the script (see [Build source +options](#build-source-options)). + +## Quick start + +Run the script as a directory with any Python 3 interpreter. From the +repository root: + +``` +python PC/layout --help +``` + +A useful invocation names an output directory and a preset. The build +directory is inferred from the running interpreter (`-b` overrides it), but a +`--preset-` (or explicit `--include-` options) is required; +otherwise the result is a bare, minimal layout that is rarely useful. + +``` +python PC/layout --copy dist --preset-default +``` + +### Output options + +The script produces one or both of the following outputs. At least one is +required for the run to do anything useful. + +* `--copy ` copies the layout into a directory. +* `--zip ` writes the layout into a ZIP file. + +Additional output-related options: + +* `--catalog ` writes a `.cdf` catalog definition for the non-binary + files, used later to produce a signed catalog. Compilation and signing are + separate steps. +* `--log ` writes all operations to a log file. +* `-t`, `--temp ` sets the temporary working directory used for generated + files (ZIP library, `._pth`, extracted pip, generated JSON). When omitted, a + temporary directory is created automatically. If `--copy` targets a Dev + Drive, an adjacent temp directory on the same drive is used for speed. +* `-v` increases verbosity. Repeat for more detail. + +### Layout options + +Optional components are selected with `--include-` flags. Presets group +these into the configurations we ship (see [Layouts](#layouts)). For a quick +throwaway layout, `-a`/`--include-all` turns on every option (except those +noted below as not affected by `--include-all`); it is intended for debugging +rather than for producing a real distribution, since it mixes components that +are never shipped together. The main component flags are: + +* `--include-stable` the stable ABI DLL (`python3.dll`). +* `--include-pip` pip, extracted from the bundled wheels in + `Lib/ensurepip/_bundled`. +* `--include-pip-user` a `pip.ini` defaulting to `--user` installs. +* `--include-tcltk` Tcl, Tk and tkinter. +* `--include-idle` IDLE. Implies `--include-tcltk`. +* `--include-tests` the test suite. +* `--include-tools` scripts from `Tools`. +* `--include-venv` venv and ensurepip, plus the venv launcher executables. +* `--include-dev` headers and `.lib` files. +* `--include-symbols` `.pdb` symbol files. +* `--include-underpth` a `python._pth` file. Not affected by + `--include-all`. +* `--include-launchers` extra launcher executables (used with + `--include-appxmanifest`). +* `--include-appxmanifest` an APPX/MSIX manifest. +* `--include-props` a `python.props` MSBuild file. +* `--include-nuspec` a `python.nuspec` NuGet spec. +* `--include-chm` the CHM documentation. +* `--include-html-doc` the HTML documentation. +* `--include-freethreaded` free-threaded binaries. Not affected by + `--include-all`. +* `--include-alias`, `--include-alias3`, `--include-alias3x` aliased + entry-point executables (`python.exe`, `python3.exe`, `python3.x.exe` and + their `w` variants). +* `--include-install-json`, `--include-install-embed-json`, + `--include-install-test-json` a PyManager `__install__.json` for the + respective distribution. +* `--include-builddetails-json` a [PEP 739](https://peps.python.org/pep-0739/) + `build-details.json`. + +Layout behavior flags: + +* `-d`, `--debug` lays out the debug build (files with the `_d` suffix). The + debug binaries must already have been built (for example with + `PCbuild/build.bat -d`); the script never builds them. +* `-p`, `--precompile` includes `.pyc` files (at optimization levels 0, 1 and + 2) instead of, or in addition to, `.py` files. The bytecode is compiled + during layout, in-process, using the `py_compile` module of the interpreter + running the script. It does not launch the laid-out runtime and does not check + its version, so it assumes the running interpreter matches the target version; + compiling with a mismatched version produces `.pyc` files the target cannot + use. +* `-z`, `--zip-lib` puts the standard library into `python.zip` rather than + a `Lib` directory. +* `--flat-dlls` places extension modules and DLLs next to the executables + rather than in a `DLLs` directory. +* `--include-cat ` includes an already-built `.cat` catalog file. This + only copies the named file into the layout; it does not build or sign the + catalog and requires no external tooling. Producing the `.cat` from a `.cdf` + (see `--catalog`) is a separate step. + +### Source options + +These options tell the script where to read from and what it is laying out. + +* `-s`, `--source ` the repository root. Defaults to the repository + containing the script. +* `-b`, `--build ` the build directory containing the compiled binaries. + Defaults to the directory containing the running interpreter. +* `--arch ` the target architecture. When omitted, it + is inferred by reading the PE header of a binary in the build directory. +* `--doc-build ` the documentation build directory. Defaults to + `/Doc/build`. + +The following environment variables are read when set: + +* `PYTHON_HEXVERSION` / `PYTHONINCLUDE` override version detection when the + target version cannot be inferred by reading `Include/patchlevel.h` from the + source tree. Set one of these when laying out a version other than the one + running the script (see below). +* `TCL_LIBRARY` locates the Tcl library when `--include-tcltk` is set. If + unset, the script falls back to `TCL_LIBRARY.env` in the build directory. +* `PYTHON_NUSPEC_VERSION`, `PYTHON_PROPS_PLATFORM` fill in the generated + `python.nuspec` and `python.props` files. When unset, the version falls back + to the detected version number and the platform falls back to a value derived + from the target architecture. +* `APPX_DATA_PUBLISHER`, `APPX_DATA_WINVER`, `APPX_DATA_SHA256` fill in the + generated APPX manifest. When unset, they fall back to defaults: a placeholder + publisher certificate name, a `MaxVersionTested` of `10.0.22000.0`, and no + SCCD hash. These defaults are fine for local use but are not suitable for a + signed, Store-ready package. + +## Layouts + +This section lists the common presets, which reflect our supported +configurations. Each is selected with `--preset-` and expands to a fixed +set of `--include-` options. They should help you understand which to use +for your scenario, and they are useful for figuring out where to put a new file +that you've just added to CPython. + +A preset can be combined with additional `--include-` options to add +components on top of it; the options are cumulative. + +### Development Kit (`--preset-default`) + +``` +python PC/layout --copy dist --preset-default +``` + +The development kit layout. This is the closest equivalent to a full install +and contains the interpreter, the standard library in `Lib`, extension modules +in `DLLs`, headers and import libraries (`--include-dev`), symbols, pip, tcltk, +IDLE, venv, the test suite, HTML docs, the stable ABI DLL, aliases and a +`build-details.json`. + +### Python install manager (`--preset-pymanager`, `--preset-pymanager-test`) + +Layouts for the [Python install manager](https://github.com/python/pymanager). +This is now the primary way upstream CPython is distributed on Windows. Both +resemble the development kit but add a PyManager `__install__.json`. + +`pymanager-test` additionally includes the test suite and symbols; +`--preset-pymanager-test` is what produces the `py install PythonTest\` +packages. + +### Embeddable package (`--preset-embed`) + +``` +python PC/layout --zip embed.zip --preset-embed +``` + +The embeddable package: a minimal, self-contained runtime intended to be +bundled inside another application. The standard library is placed in a ZIP +(`--zip-lib`), DLLs are flattened next to the executables (`--flat-dlls`), the +library is precompiled, and a `._pth` file (`--underpth`) is included to fix the +search path and isolate the runtime from user site directories. It omits pip, +tcltk, IDLE, the tests and development files. + +`--preset-embed --include-install-embed-json` is what produces the +`py install PythonEmbed\` packages. + +### NuGet package (`--preset-nuget`) + +The NuGet package layout. Includes development files, pip, the stable ABI, +venv, aliases, a `build-details.json`, and the `python.props` and +`python.nuspec` files that describe the package to MSBuild and NuGet. + +### APPX package (`--preset-appx`) + +The APPX/MSIX package layout for the Windows Store distribution. Includes the +stable ABI, pip, tcltk, IDLE, venv, development files, launchers, aliases and an +APPX manifest. This is no longer used for upstream CPython releases. + +### Windows IoT Core (`--preset-iot`) + +A minimal layout for Windows IoT Core: aliases, the stable ABI and pip. This is +not used for upstream CPython releases. + +## Build source options + +The `-b` and `-s` options let you lay out a build that is separate from the +interpreter running the script, including a different Python version. + +* `-b`/`--build` points at the build tree with the compiled binaries. It does + not need to match the interpreter running the script. +* `-s`/`--source` points at the source tree that supplies `Lib`, `Include`, + `Tools` and version information. + +The target version is normally read from `Include/patchlevel.h` in the source +tree. When laying out a version that differs from the running interpreter and +the source tree is not the matching one, set `PYTHON_HEXVERSION` (a hex version +such as `0x030f00a0`) or `PYTHONINCLUDE` (a directory containing +`patchlevel.h`) so the correct DLL names, suffixes and version numbers are used. +The script validates the inferred version against `patchlevel.h` and stops with +an error if they disagree. + +## Tool structure + +The script is a package under `PC/layout`. `main.py` drives argument parsing, +builds the file list (`get_layout`), generates derived files +(`generate_source_files`) and copies or zips the result (`copy_files`). Support +modules live in `PC/layout/support`: + +* `options.py` defines the `OPTIONS` and `PRESETS` tables and generates the + `--include-*` and `--preset-*` command-line arguments. Add new options and + presets here. +* `constants.py` derives version numbers, DLL names and related constants from + `patchlevel.h` or the version environment variables. +* `filesets.py` provides the file-matching helpers (`FileStemSet`, + `FileNameSet`, `FileSuffixSet`) and the `rglob` globbing helper used + throughout `main.py`. +* `arch.py` infers the target architecture from a binary's PE header. +* `logging.py` configures logging and tracks whether errors occurred. +* `pip.py` extracts pip from the bundled wheels and lists the pip files. +* `catalog.py` generates the `.cdf`/`.cat` catalog definitions for signing. +* `props.py` generates `python.props` (NuGet/MSBuild). +* `nuspec.py` generates `python.nuspec` (NuGet). +* `appxmanifest.py` generates APPX/MSIX manifests. +* `pymanager.py` generates the PyManager `__install__.json`. +* `builddetails.py` generates the PEP 739 `build-details.json`. + +### Classifying new files + +`main.py` collects files by globbing shared directories -- the build output +directory (`-b`) for binaries and the source `Lib`, `Include` and `Tools` +directories -- and then filters them. A new file added to any of these is +usually picked up with no change here: build extension modules (`*.pyd`) land in +`DLLs` (or alongside the executables under `--flat-dlls`), other build DLLs land +next to them, and files under `Lib` are mirrored into the layout's `Lib`. + +Changes are only needed when a new file belongs to an optional component, so it +must be routed to the right `--include-` option rather than always +copied. The routing is driven by a set of file-matching tables near the top of +`main.py` (built from the `FileStemSet`, `FileNameSet` and `FileSuffixSet` +helpers in `support/filesets.py`). To classify a new file, add its name or +pattern to the matching table. + +Tables that gate files on an optional component (the file is only included when +the corresponding option is set): + +* `TEST_PYDS_ONLY`, `TEST_DLLS_ONLY`, `TEST_DIRS_ONLY` -- test-only extension + modules, DLLs and `Lib` directories; gated on `--include-tests`. Extension + module names starting with `_test` are already matched; add any other + test-only build module here (see also the note in `PCbuild/readme.txt`). +* `TCLTK_PYDS_ONLY`, `TCLTK_DLLS_ONLY`, `TCLTK_DIRS_ONLY`, `TCLTK_FILES_ONLY` -- + Tcl/Tk/tkinter extension modules (`_tkinter`), DLLs (`tcl*`, `tk*`, `zlib1`), + `Lib` directories (`tkinter`, `turtledemo`) and files (`turtle.py`); gated on + `--include-tcltk`. +* `IDLE_DIRS_ONLY` -- IDLE's `Lib` directory (`idlelib`); gated on + `--include-idle`. +* `VENV_DIRS_ONLY` -- `venv` and `ensurepip` `Lib` directories; gated on + `--include-venv`. + +Tables that exclude files outright: + +* `EXCLUDE_FROM_DLLS` -- build DLLs that are handled elsewhere and must not be + copied by the generic DLL glob (`python*`, `pyshellext`, `vcruntime*`). +* `EXCLUDE_FROM_LIB` -- never copied into `Lib` (`*.pyc`, `__pycache__`, + `*.pickle`). +* `EXCLUDE_FROM_COMPILE` -- `Lib` files that must not be precompiled + (`badsyntax_*`, `bad_*`). +* `EXCLUDE_FROM_CATALOG` -- suffixes omitted from the signing catalog because + they are signed separately (`.exe`, `.pyd`, `.dll`). + +Special cases in the debug and binary handling: + +* `REQUIRED_DLLS` (`libcrypto*`, `libssl*`, `libffi*`) -- always copied from the + build directory even for a release layout. Most build DLLs are filtered by + their `_d` suffix to match the debug or release build being laid out; these + dependencies do not follow that naming and would otherwise be dropped, so they + are matched here to force inclusion. Add a new external dependency DLL here if + it does not use the `_d` debug-suffix convention. + +When deciding where a new file goes, match it to the component it belongs to and +add it to the corresponding table. If it belongs in every layout and follows the +normal conventions, no change is needed. + + +## Contributing guidelines + +Contributions are welcome. The script supports a broad range of scenarios, and +extending it for a new one is encouraged. Because the same code produces the +official releases across every supported version and architecture, additions +have to respect a few constraints so that existing configurations keep working. + +The script runs in an unusual context, and new code must hold to the following +assumptions: + +* Do not assume the Python being laid out can be launched. The script must run + correctly using only the interpreter running it, which may differ from the + target. +* Do not assume the layout version matches the version running the script. + Derive version-dependent behavior from the detected version rather than from + `sys.version` of the running interpreter. (Precompilation with `--precompile` + is the one deliberate exception, and it assumes a matching interpreter.) +* The script may be used to lay out an older version, so behavior changes have + to be preserved, either behind an option or gated on a version check, rather + than replaced outright. +* The script never builds anything. Everything it emits must already exist in + the build or source tree. + +When adding features, keep the surface area aligned with upstream: + +* New `--include-` options for further customization are fine. +* Do not add presets beyond those used by upstream. Presets describe the + configurations we ship, so a new one implies a new supported release + artifact. +* Route new files through the classification tables (see [Classifying new + files](#classifying-new-files)) rather than adding one-off special cases. diff --git a/PC/layout/support/appxmanifest.py b/PC/layout/support/appxmanifest.py index 53977beb8af8346..ba7f1335c7348d5 100644 --- a/PC/layout/support/appxmanifest.py +++ b/PC/layout/support/appxmanifest.py @@ -151,6 +151,8 @@ """ +# This file is no longer included in our repo, but we keep the logic here for +# building with older releases. We always .is_file() before using it. SCCD_FILENAME = "PC/classicAppCompat.sccd" SPECIAL_LOOKUP = object() diff --git a/PC/pylauncher.rc b/PC/pylauncher.rc deleted file mode 100644 index 11862643aa69893..000000000000000 --- a/PC/pylauncher.rc +++ /dev/null @@ -1,67 +0,0 @@ -#include - -#include "python_ver_rc.h" - -#ifndef RT_MANIFEST -// bpo-45220: Cannot reliably #include RT_MANIFEST from -// anywhere, so we hardcode it -#define RT_MANIFEST 24 -#endif -// Include the manifest file that indicates we support all -// current versions of Windows. -1 RT_MANIFEST "python.manifest" - -#if defined(PY_ICON) -1 ICON DISCARDABLE "icons\python.ico" -#elif defined(PYW_ICON) -1 ICON DISCARDABLE "icons\pythonw.ico" -#else -1 ICON DISCARDABLE "icons\launcher.ico" -2 ICON DISCARDABLE "icons\py.ico" -3 ICON DISCARDABLE "icons\pyc.ico" -4 ICON DISCARDABLE "icons\pyd.ico" -5 ICON DISCARDABLE "icons\python.ico" -6 ICON DISCARDABLE "icons\pythonw.ico" -7 ICON DISCARDABLE "icons\setup.ico" -#endif - -1 USAGE "launcher-usage.txt" - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION PYVERSION64 - PRODUCTVERSION PYVERSION64 - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "000004b0" - BEGIN - VALUE "CompanyName", PYTHON_COMPANY "\0" - VALUE "FileDescription", "Python\0" - VALUE "FileVersion", PYTHON_VERSION - VALUE "InternalName", "Python Launcher\0" - VALUE "LegalCopyright", PYTHON_COPYRIGHT "\0" - VALUE "OriginalFilename", "py" PYTHON_DEBUG_EXT ".exe\0" - VALUE "ProductName", "Python\0" - VALUE "ProductVersion", PYTHON_VERSION - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0, 1200 - END -END \ No newline at end of file diff --git a/PC/pyshellext.cpp b/PC/pyshellext.cpp deleted file mode 100644 index d16de0b717151e5..000000000000000 --- a/PC/pyshellext.cpp +++ /dev/null @@ -1,603 +0,0 @@ -// Support back to Vista -#define _WIN32_WINNT _WIN32_WINNT_VISTA -#include - -// Use WRL to define a classic COM class -#define __WRL_CLASSIC_COM__ -#include - -#include -#include -#include -#include -#include - -#define DDWM_UPDATEWINDOW (WM_USER+3) - -static HINSTANCE hModule; -static CLIPFORMAT cfDropDescription; -static CLIPFORMAT cfDragWindow; - -#define CLASS_GUID "{BEA218D2-6950-497B-9434-61683EC065FE}" -static const LPCWSTR CLASS_SUBKEY = L"Software\\Classes\\CLSID\\" CLASS_GUID; -static const LPCWSTR DRAG_MESSAGE = L"Open with %1"; - -using namespace Microsoft::WRL; - -HRESULT FilenameListCchLengthA(LPCSTR pszSource, size_t cchMax, size_t *pcchLength, size_t *pcchCount) { - HRESULT hr = S_OK; - size_t count = 0; - size_t length = 0; - - while (pszSource && pszSource[0]) { - size_t oneLength; - hr = StringCchLengthA(pszSource, cchMax - length, &oneLength); - if (FAILED(hr)) { - return hr; - } - count += 1; - length += oneLength + (strchr(pszSource, ' ') ? 3 : 1); - pszSource = &pszSource[oneLength + 1]; - } - - *pcchCount = count; - *pcchLength = length; - return hr; -} - -HRESULT FilenameListCchLengthW(LPCWSTR pszSource, size_t cchMax, size_t *pcchLength, size_t *pcchCount) { - HRESULT hr = S_OK; - size_t count = 0; - size_t length = 0; - - while (pszSource && pszSource[0]) { - size_t oneLength; - hr = StringCchLengthW(pszSource, cchMax - length, &oneLength); - if (FAILED(hr)) { - return hr; - } - count += 1; - length += oneLength + (wcschr(pszSource, ' ') ? 3 : 1); - pszSource = &pszSource[oneLength + 1]; - } - - *pcchCount = count; - *pcchLength = length; - return hr; -} - -HRESULT FilenameListCchCopyA(STRSAFE_LPSTR pszDest, size_t cchDest, LPCSTR pszSource, LPCSTR pszSeparator) { - HRESULT hr = S_OK; - - while (pszSource[0]) { - STRSAFE_LPSTR newDest; - - hr = StringCchCopyExA(pszDest, cchDest, pszSource, &newDest, &cchDest, 0); - if (FAILED(hr)) { - return hr; - } - pszSource += (newDest - pszDest) + 1; - pszDest = PathQuoteSpacesA(pszDest) ? newDest + 2 : newDest; - - if (pszSource[0]) { - hr = StringCchCopyExA(pszDest, cchDest, pszSeparator, &newDest, &cchDest, 0); - if (FAILED(hr)) { - return hr; - } - pszDest = newDest; - } - } - - return hr; -} - -HRESULT FilenameListCchCopyW(STRSAFE_LPWSTR pszDest, size_t cchDest, LPCWSTR pszSource, LPCWSTR pszSeparator) { - HRESULT hr = S_OK; - - while (pszSource[0]) { - STRSAFE_LPWSTR newDest; - - hr = StringCchCopyExW(pszDest, cchDest, pszSource, &newDest, &cchDest, 0); - if (FAILED(hr)) { - return hr; - } - pszSource += (newDest - pszDest) + 1; - pszDest = PathQuoteSpacesW(pszDest) ? newDest + 2 : newDest; - - if (pszSource[0]) { - hr = StringCchCopyExW(pszDest, cchDest, pszSeparator, &newDest, &cchDest, 0); - if (FAILED(hr)) { - return hr; - } - pszDest = newDest; - } - } - - return hr; -} - -class DECLSPEC_UUID(CLASS_GUID) PyShellExt : public RuntimeClass< - RuntimeClassFlags, - IDropTarget, - IPersistFile -> -{ - LPOLESTR target, target_dir; - DWORD target_mode; - - IDataObject *data_obj; - -public: - PyShellExt() : target(NULL), target_dir(NULL), target_mode(0), data_obj(NULL) { - OutputDebugString(L"PyShellExt::PyShellExt"); - } - - ~PyShellExt() { - if (target) { - CoTaskMemFree(target); - } - if (target_dir) { - CoTaskMemFree(target_dir); - } - if (data_obj) { - data_obj->Release(); - } - } - -private: - HRESULT UpdateDropDescription(IDataObject *pDataObj) { - STGMEDIUM medium; - FORMATETC fmt = { - cfDropDescription, - NULL, - DVASPECT_CONTENT, - -1, - TYMED_HGLOBAL - }; - - auto hr = pDataObj->GetData(&fmt, &medium); - if (FAILED(hr)) { - OutputDebugString(L"PyShellExt::UpdateDropDescription - failed to get DROPDESCRIPTION format"); - return hr; - } - if (!medium.hGlobal) { - OutputDebugString(L"PyShellExt::UpdateDropDescription - DROPDESCRIPTION format had NULL hGlobal"); - ReleaseStgMedium(&medium); - return E_FAIL; - } - auto dd = (DROPDESCRIPTION*)GlobalLock(medium.hGlobal); - if (!dd) { - OutputDebugString(L"PyShellExt::UpdateDropDescription - failed to lock DROPDESCRIPTION hGlobal"); - ReleaseStgMedium(&medium); - return E_FAIL; - } - StringCchCopy(dd->szMessage, sizeof(dd->szMessage) / sizeof(dd->szMessage[0]), DRAG_MESSAGE); - StringCchCopy(dd->szInsert, sizeof(dd->szInsert) / sizeof(dd->szInsert[0]), PathFindFileNameW(target)); - dd->type = DROPIMAGE_MOVE; - - GlobalUnlock(medium.hGlobal); - ReleaseStgMedium(&medium); - - return S_OK; - } - - HRESULT GetDragWindow(IDataObject *pDataObj, HWND *phWnd) { - HRESULT hr; - HWND *pMem; - STGMEDIUM medium; - FORMATETC fmt = { - cfDragWindow, - NULL, - DVASPECT_CONTENT, - -1, - TYMED_HGLOBAL - }; - - hr = pDataObj->GetData(&fmt, &medium); - if (FAILED(hr)) { - OutputDebugString(L"PyShellExt::GetDragWindow - failed to get DragWindow format"); - return hr; - } - if (!medium.hGlobal) { - OutputDebugString(L"PyShellExt::GetDragWindow - DragWindow format had NULL hGlobal"); - ReleaseStgMedium(&medium); - return E_FAIL; - } - - pMem = (HWND*)GlobalLock(medium.hGlobal); - if (!pMem) { - OutputDebugString(L"PyShellExt::GetDragWindow - failed to lock DragWindow hGlobal"); - ReleaseStgMedium(&medium); - return E_FAIL; - } - - *phWnd = *pMem; - - GlobalUnlock(medium.hGlobal); - ReleaseStgMedium(&medium); - - return S_OK; - } - - HRESULT GetArguments(IDataObject *pDataObj, LPCWSTR *pArguments) { - HRESULT hr; - DROPFILES *pdropfiles; - - STGMEDIUM medium; - FORMATETC fmt = { - CF_HDROP, - NULL, - DVASPECT_CONTENT, - -1, - TYMED_HGLOBAL - }; - - hr = pDataObj->GetData(&fmt, &medium); - if (FAILED(hr)) { - OutputDebugString(L"PyShellExt::GetArguments - failed to get CF_HDROP format"); - return hr; - } - if (!medium.hGlobal) { - OutputDebugString(L"PyShellExt::GetArguments - CF_HDROP format had NULL hGlobal"); - ReleaseStgMedium(&medium); - return E_FAIL; - } - - pdropfiles = (DROPFILES*)GlobalLock(medium.hGlobal); - if (!pdropfiles) { - OutputDebugString(L"PyShellExt::GetArguments - failed to lock CF_HDROP hGlobal"); - ReleaseStgMedium(&medium); - return E_FAIL; - } - - if (pdropfiles->fWide) { - LPCWSTR files = (LPCWSTR)((char*)pdropfiles + pdropfiles->pFiles); - size_t len, count; - hr = FilenameListCchLengthW(files, 32767, &len, &count); - if (SUCCEEDED(hr)) { - LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1)); - if (args) { - hr = FilenameListCchCopyW(args, 32767, files, L" "); - if (SUCCEEDED(hr)) { - *pArguments = args; - } else { - CoTaskMemFree(args); - } - } else { - hr = E_OUTOFMEMORY; - } - } - } else { - LPCSTR files = (LPCSTR)((char*)pdropfiles + pdropfiles->pFiles); - size_t len, count; - hr = FilenameListCchLengthA(files, 32767, &len, &count); - if (SUCCEEDED(hr)) { - LPSTR temp = (LPSTR)CoTaskMemAlloc(sizeof(CHAR) * (len + 1)); - if (temp) { - hr = FilenameListCchCopyA(temp, 32767, files, " "); - if (SUCCEEDED(hr)) { - int wlen = MultiByteToWideChar(CP_ACP, 0, temp, (int)len, NULL, 0); - if (wlen) { - LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (wlen + 1)); - if (MultiByteToWideChar(CP_ACP, 0, temp, (int)len, args, wlen + 1)) { - *pArguments = args; - } else { - OutputDebugString(L"PyShellExt::GetArguments - failed to convert multi-byte to wide-char path"); - CoTaskMemFree(args); - hr = E_FAIL; - } - } else { - OutputDebugString(L"PyShellExt::GetArguments - failed to get length of wide-char path"); - hr = E_FAIL; - } - } - CoTaskMemFree(temp); - } else { - hr = E_OUTOFMEMORY; - } - } - } - - GlobalUnlock(medium.hGlobal); - ReleaseStgMedium(&medium); - - return hr; - } - - HRESULT NotifyDragWindow(HWND hwnd) { - LRESULT res; - - if (!hwnd) { - return S_FALSE; - } - - res = SendMessage(hwnd, DDWM_UPDATEWINDOW, 0, NULL); - - if (res) { - OutputDebugString(L"PyShellExt::NotifyDragWindow - failed to post DDWM_UPDATEWINDOW"); - return E_FAIL; - } - - return S_OK; - } - -public: - // IDropTarget implementation - - STDMETHODIMP DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { - HWND hwnd; - - OutputDebugString(L"PyShellExt::DragEnter"); - - pDataObj->AddRef(); - data_obj = pDataObj; - - *pdwEffect = DROPEFFECT_MOVE; - - if (FAILED(UpdateDropDescription(data_obj))) { - OutputDebugString(L"PyShellExt::DragEnter - failed to update drop description"); - } - if (FAILED(GetDragWindow(data_obj, &hwnd))) { - OutputDebugString(L"PyShellExt::DragEnter - failed to get drag window"); - } - if (FAILED(NotifyDragWindow(hwnd))) { - OutputDebugString(L"PyShellExt::DragEnter - failed to notify drag window"); - } - - return S_OK; - } - - STDMETHODIMP DragLeave() { - return S_OK; - } - - STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { - return S_OK; - } - - STDMETHODIMP Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { - LPCWSTR args; - - OutputDebugString(L"PyShellExt::Drop"); - *pdwEffect = DROPEFFECT_NONE; - - if (pDataObj != data_obj) { - OutputDebugString(L"PyShellExt::Drop - unexpected data object"); - return E_FAIL; - } - - data_obj->Release(); - data_obj = NULL; - - if (SUCCEEDED(GetArguments(pDataObj, &args))) { - OutputDebugString(args); - ShellExecute(NULL, NULL, target, args, target_dir, SW_NORMAL); - - CoTaskMemFree((LPVOID)args); - } else { - OutputDebugString(L"PyShellExt::Drop - failed to get launch arguments"); - } - - return S_OK; - } - - // IPersistFile implementation - - STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName) { - HRESULT hr; - size_t len; - - if (!ppszFileName) { - return E_POINTER; - } - - hr = StringCchLength(target, STRSAFE_MAX_CCH - 1, &len); - if (FAILED(hr)) { - return E_FAIL; - } - - *ppszFileName = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1)); - if (!*ppszFileName) { - return E_OUTOFMEMORY; - } - - hr = StringCchCopy(*ppszFileName, len + 1, target); - if (FAILED(hr)) { - CoTaskMemFree(*ppszFileName); - *ppszFileName = NULL; - return E_FAIL; - } - - return S_OK; - } - - STDMETHODIMP IsDirty() { - return S_FALSE; - } - - STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode) { - HRESULT hr; - size_t len; - - OutputDebugString(L"PyShellExt::Load"); - OutputDebugString(pszFileName); - - hr = StringCchLength(pszFileName, STRSAFE_MAX_CCH - 1, &len); - if (FAILED(hr)) { - OutputDebugString(L"PyShellExt::Load - failed to get string length"); - return hr; - } - - if (target) { - CoTaskMemFree(target); - } - if (target_dir) { - CoTaskMemFree(target_dir); - } - - target = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1)); - if (!target) { - OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY"); - return E_OUTOFMEMORY; - } - target_dir = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1)); - if (!target_dir) { - OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY"); - return E_OUTOFMEMORY; - } - - hr = StringCchCopy(target, len + 1, pszFileName); - if (FAILED(hr)) { - OutputDebugString(L"PyShellExt::Load - failed to copy string"); - return hr; - } - - hr = StringCchCopy(target_dir, len + 1, pszFileName); - if (FAILED(hr)) { - OutputDebugString(L"PyShellExt::Load - failed to copy string"); - return hr; - } - if (!PathRemoveFileSpecW(target_dir)) { - OutputDebugStringW(L"PyShellExt::Load - failed to remove filespec from target"); - return E_FAIL; - } - - OutputDebugString(target); - target_mode = dwMode; - OutputDebugString(L"PyShellExt::Load - S_OK"); - return S_OK; - } - - STDMETHODIMP Save(LPCOLESTR pszFileName, BOOL fRemember) { - return E_NOTIMPL; - } - - STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName) { - return E_NOTIMPL; - } - - STDMETHODIMP GetClassID(CLSID *pClassID) { - *pClassID = __uuidof(PyShellExt); - return S_OK; - } -}; - -CoCreatableClass(PyShellExt); - -STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv) { - return Module::GetModule().GetClassObject(rclsid, riid, ppv); -} - -STDAPI DllCanUnloadNow() { - return Module::GetModule().Terminate() ? S_OK : S_FALSE; -} - -STDAPI DllRegisterServer() { - LONG res; - LPSECURITY_ATTRIBUTES psecattr = NULL; - HKEY key, ipsKey; - WCHAR modname[MAX_PATH]; - DWORD modname_len; - - OutputDebugString(L"PyShellExt::DllRegisterServer"); - if (!hModule) { - OutputDebugString(L"PyShellExt::DllRegisterServer - module handle was not set"); - return SELFREG_E_CLASS; - } - modname_len = GetModuleFileName(hModule, modname, MAX_PATH); - if (modname_len == 0 || - (modname_len == MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { - OutputDebugString(L"PyShellExt::DllRegisterServer - failed to get module file name"); - return SELFREG_E_CLASS; - } - - DWORD disp; - res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, CLASS_SUBKEY, 0, NULL, 0, - KEY_ALL_ACCESS, psecattr, &key, &disp); - if (res == ERROR_ACCESS_DENIED) { - OutputDebugString(L"PyShellExt::DllRegisterServer - failed to write per-machine registration. Attempting per-user instead."); - res = RegCreateKeyEx(HKEY_CURRENT_USER, CLASS_SUBKEY, 0, NULL, 0, - KEY_ALL_ACCESS, psecattr, &key, &disp); - } - if (res != ERROR_SUCCESS) { - OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create class key"); - return SELFREG_E_CLASS; - } - - res = RegCreateKeyEx(key, L"InProcServer32", 0, NULL, 0, - KEY_ALL_ACCESS, psecattr, &ipsKey, NULL); - if (res != ERROR_SUCCESS) { - RegCloseKey(key); - OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create InProcServer32 key"); - return SELFREG_E_CLASS; - } - - res = RegSetValueEx(ipsKey, NULL, 0, - REG_SZ, (LPBYTE)modname, modname_len * sizeof(modname[0])); - - if (res != ERROR_SUCCESS) { - RegCloseKey(ipsKey); - RegCloseKey(key); - OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set server path"); - return SELFREG_E_CLASS; - } - - res = RegSetValueEx(ipsKey, L"ThreadingModel", 0, - REG_SZ, (LPBYTE)(L"Apartment"), sizeof(L"Apartment")); - - RegCloseKey(ipsKey); - RegCloseKey(key); - if (res != ERROR_SUCCESS) { - OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set threading model"); - return SELFREG_E_CLASS; - } - - SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); - - OutputDebugString(L"PyShellExt::DllRegisterServer - S_OK"); - return S_OK; -} - -STDAPI DllUnregisterServer() { - LONG res_lm, res_cu; - - res_lm = RegDeleteTree(HKEY_LOCAL_MACHINE, CLASS_SUBKEY); - if (res_lm != ERROR_SUCCESS && res_lm != ERROR_FILE_NOT_FOUND) { - OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-machine registration"); - return SELFREG_E_CLASS; - } - - res_cu = RegDeleteTree(HKEY_CURRENT_USER, CLASS_SUBKEY); - if (res_cu != ERROR_SUCCESS && res_cu != ERROR_FILE_NOT_FOUND) { - OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-user registration"); - return SELFREG_E_CLASS; - } - - if (res_lm == ERROR_FILE_NOT_FOUND && res_cu == ERROR_FILE_NOT_FOUND) { - OutputDebugString(L"PyShellExt::DllUnregisterServer - extension was not registered"); - return SELFREG_E_CLASS; - } - - SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); - - OutputDebugString(L"PyShellExt::DllUnregisterServer - S_OK"); - return S_OK; -} - -STDAPI_(BOOL) DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*) { - if (reason == DLL_PROCESS_ATTACH) { - hModule = hinst; - - cfDropDescription = RegisterClipboardFormat(CFSTR_DROPDESCRIPTION); - if (!cfDropDescription) { - OutputDebugString(L"PyShellExt::DllMain - failed to get CFSTR_DROPDESCRIPTION format"); - } - cfDragWindow = RegisterClipboardFormat(L"DragWindow"); - if (!cfDragWindow) { - OutputDebugString(L"PyShellExt::DllMain - failed to get DragWindow format"); - } - - DisableThreadLibraryCalls(hinst); - } - return TRUE; -} diff --git a/PC/pyshellext.def b/PC/pyshellext.def deleted file mode 100644 index 288a9adf982f197..000000000000000 --- a/PC/pyshellext.def +++ /dev/null @@ -1,5 +0,0 @@ -EXPORTS - DllRegisterServer PRIVATE - DllUnregisterServer PRIVATE - DllGetClassObject PRIVATE - DllCanUnloadNow PRIVATE diff --git a/PC/pyshellext.rc b/PC/pyshellext.rc deleted file mode 100644 index af797ce95d5077e..000000000000000 --- a/PC/pyshellext.rc +++ /dev/null @@ -1,51 +0,0 @@ -#include - -#include "python_ver_rc.h" - -#ifndef RT_MANIFEST -// bpo-45220: Cannot reliably #include RT_MANIFEST from -// anywhere, so we hardcode it -#define RT_MANIFEST 24 -#endif - -// Include the manifest file that indicates we support all -// current versions of Windows. -1 RT_MANIFEST "python.manifest" - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION PYVERSION64 - PRODUCTVERSION PYVERSION64 - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "000004b0" - BEGIN - VALUE "CompanyName", PYTHON_COMPANY "\0" - VALUE "FileDescription", "Python\0" - VALUE "FileVersion", PYTHON_VERSION - VALUE "InternalName", "Python Launcher Shell Extension\0" - VALUE "LegalCopyright", PYTHON_COPYRIGHT "\0" - VALUE "OriginalFilename", "pyshellext" PYTHON_DEBUG_EXT ".dll\0" - VALUE "ProductName", "Python\0" - VALUE "ProductVersion", PYTHON_VERSION - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0, 1200 - END -END \ No newline at end of file diff --git a/PCbuild/pcbuild.proj b/PCbuild/pcbuild.proj index 9d077bbd3f0ba27..53aec0276beaee2 100644 --- a/PCbuild/pcbuild.proj +++ b/PCbuild/pcbuild.proj @@ -63,10 +63,6 @@ - - - - diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln index 09a989d38648df4..e2cac3158831eed 100644 --- a/PCbuild/pcbuild.sln +++ b/PCbuild/pcbuild.sln @@ -111,16 +111,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xxlimited", "xxlimited.vcxp EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testbuffer", "_testbuffer.vcxproj", "{A2697BD3-28C1-4AEC-9106-8B748639FD16}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pylauncher", "pylauncher.vcxproj", "{7B2727B5-5A3F-40EE-A866-43A13CD31446}" - ProjectSection(ProjectDependencies) = postProject - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782} = {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pywlauncher", "pywlauncher.vcxproj", "{1D4B18D3-7C12-4ECB-9179-8531FF876CE6}" - ProjectSection(ProjectDependencies) = postProject - {7B2727B5-5A3F-40EE-A866-43A13CD31446} = {7B2727B5-5A3F-40EE-A866-43A13CD31446} - EndProjectSection -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_freeze_module", "_freeze_module.vcxproj", "{19C0C13F-47CA-4432-AFF3-799A296A4DDC}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_overlapped", "_overlapped.vcxproj", "{EB6E69DD-04BF-4543-9B92-49FAABCEAC2E}" @@ -131,8 +121,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testmultiphase", "_testmul EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testsinglephase", "_testsinglephase.vcxproj", "{2097F1C1-597C-4167-93E3-656A7D6339B2}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pyshellext", "pyshellext.vcxproj", "{0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testconsole", "_testconsole.vcxproj", "{B244E787-C445-441C-BDF4-5A4F1A3A1E51}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_asyncio", "_asyncio.vcxproj", "{384C224A-7474-476E-A01B-750EA7DE918C}" @@ -1079,70 +1067,6 @@ Global {A2697BD3-28C1-4AEC-9106-8B748639FD16}.Release|Win32.Build.0 = Release|Win32 {A2697BD3-28C1-4AEC-9106-8B748639FD16}.Release|x64.ActiveCfg = Release|x64 {A2697BD3-28C1-4AEC-9106-8B748639FD16}.Release|x64.Build.0 = Release|x64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|ARM.ActiveCfg = Debug|ARM - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|ARM.Build.0 = Debug|ARM - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|ARM64.Build.0 = Debug|ARM64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|Win32.ActiveCfg = Debug|Win32 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|Win32.Build.0 = Debug|Win32 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|x64.ActiveCfg = Debug|x64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|x64.Build.0 = Debug|x64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|ARM.Build.0 = PGInstrument|ARM - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|Win32.ActiveCfg = Release|Win32 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|Win32.Build.0 = Release|Win32 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|x64.ActiveCfg = Release|x64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|x64.Build.0 = Release|x64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|ARM.Build.0 = PGUpdate|ARM - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|Win32.ActiveCfg = Release|Win32 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|Win32.Build.0 = Release|Win32 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|x64.ActiveCfg = Release|x64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|x64.Build.0 = Release|x64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|ARM.ActiveCfg = Release|ARM - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|ARM.Build.0 = Release|ARM - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|ARM64.ActiveCfg = Release|ARM64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|ARM64.Build.0 = Release|ARM64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|Win32.ActiveCfg = Release|Win32 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|Win32.Build.0 = Release|Win32 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|x64.ActiveCfg = Release|x64 - {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|x64.Build.0 = Release|x64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|ARM.ActiveCfg = Debug|ARM - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|ARM.Build.0 = Debug|ARM - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|ARM64.Build.0 = Debug|ARM64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|Win32.ActiveCfg = Debug|Win32 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|Win32.Build.0 = Debug|Win32 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|x64.ActiveCfg = Debug|x64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|x64.Build.0 = Debug|x64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|ARM.Build.0 = PGInstrument|ARM - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|Win32.ActiveCfg = Release|Win32 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|Win32.Build.0 = Release|Win32 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|x64.ActiveCfg = Release|x64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|x64.Build.0 = Release|x64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|ARM.Build.0 = PGUpdate|ARM - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|Win32.ActiveCfg = Release|Win32 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|Win32.Build.0 = Release|Win32 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|x64.ActiveCfg = Release|x64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|x64.Build.0 = Release|x64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|ARM.ActiveCfg = Release|ARM - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|ARM.Build.0 = Release|ARM - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|ARM64.ActiveCfg = Release|ARM64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|ARM64.Build.0 = Release|ARM64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|Win32.ActiveCfg = Release|Win32 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|Win32.Build.0 = Release|Win32 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|x64.ActiveCfg = Release|x64 - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|x64.Build.0 = Release|x64 {19C0C13F-47CA-4432-AFF3-799A296A4DDC}.Debug|ARM.ActiveCfg = Debug|ARM {19C0C13F-47CA-4432-AFF3-799A296A4DDC}.Debug|ARM.Build.0 = Debug|ARM {19C0C13F-47CA-4432-AFF3-799A296A4DDC}.Debug|ARM64.ActiveCfg = Debug|ARM64 @@ -1293,38 +1217,6 @@ Global {2097F1C1-597C-4167-93E3-656A7D6339B2}.Release|Win32.Build.0 = Release|Win32 {2097F1C1-597C-4167-93E3-656A7D6339B2}.Release|x64.ActiveCfg = Release|x64 {2097F1C1-597C-4167-93E3-656A7D6339B2}.Release|x64.Build.0 = Release|x64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|ARM.ActiveCfg = Debug|ARM - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|ARM.Build.0 = Debug|ARM - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|ARM64.Build.0 = Debug|ARM64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|Win32.ActiveCfg = Debug|Win32 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|Win32.Build.0 = Debug|Win32 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|x64.ActiveCfg = Debug|x64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|x64.Build.0 = Debug|x64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|ARM.Build.0 = PGInstrument|ARM - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|x64.Build.0 = PGInstrument|x64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|ARM.Build.0 = PGUpdate|ARM - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|x64.Build.0 = PGUpdate|x64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|ARM.ActiveCfg = Release|ARM - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|ARM.Build.0 = Release|ARM - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|ARM64.ActiveCfg = Release|ARM64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|ARM64.Build.0 = Release|ARM64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|Win32.ActiveCfg = Release|Win32 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|Win32.Build.0 = Release|Win32 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|x64.ActiveCfg = Release|x64 - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|x64.Build.0 = Release|x64 {B244E787-C445-441C-BDF4-5A4F1A3A1E51}.Debug|ARM.ActiveCfg = Debug|ARM {B244E787-C445-441C-BDF4-5A4F1A3A1E51}.Debug|ARM.Build.0 = Debug|ARM {B244E787-C445-441C-BDF4-5A4F1A3A1E51}.Debug|ARM64.ActiveCfg = Debug|ARM64 diff --git a/PCbuild/pylauncher.vcxproj b/PCbuild/pylauncher.vcxproj deleted file mode 100644 index ca422ef4d02e932..000000000000000 --- a/PCbuild/pylauncher.vcxproj +++ /dev/null @@ -1,114 +0,0 @@ - - - - - Debug - ARM - - - Debug - ARM64 - - - Debug - Win32 - - - Debug - x64 - - - PGInstrument - ARM - - - PGInstrument - ARM64 - - - PGInstrument - Win32 - - - PGInstrument - x64 - - - PGUpdate - ARM - - - PGUpdate - ARM64 - - - PGUpdate - Win32 - - - PGUpdate - x64 - - - Release - ARM - - - Release - ARM64 - - - Release - Win32 - - - Release - x64 - - - - {7B2727B5-5A3F-40EE-A866-43A13CD31446} - pylauncher - py - false - - - - - Application - Unicode - - - - - - ClCompile - - - - - - - - - _CONSOLE;%(PreprocessorDefinitions) - MultiThreaded - - - shell32.lib;pathcch.lib;%(AdditionalDependencies) - Console - - - - - - - - - - - - - - - diff --git a/PCbuild/pylauncher.vcxproj.filters b/PCbuild/pylauncher.vcxproj.filters deleted file mode 100644 index f10f6d5669e283c..000000000000000 --- a/PCbuild/pylauncher.vcxproj.filters +++ /dev/null @@ -1,28 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - - - Resource Files - - - - - Resource Files - - - \ No newline at end of file diff --git a/PCbuild/pywlauncher.vcxproj b/PCbuild/pywlauncher.vcxproj deleted file mode 100644 index 1694548935a1653..000000000000000 --- a/PCbuild/pywlauncher.vcxproj +++ /dev/null @@ -1,114 +0,0 @@ - - - - - Debug - ARM - - - Debug - ARM64 - - - Debug - Win32 - - - Debug - x64 - - - PGInstrument - ARM - - - PGInstrument - ARM64 - - - PGInstrument - Win32 - - - PGInstrument - x64 - - - PGUpdate - ARM - - - PGUpdate - ARM64 - - - PGUpdate - Win32 - - - PGUpdate - x64 - - - Release - ARM - - - Release - ARM64 - - - Release - Win32 - - - Release - x64 - - - - {1D4B18D3-7C12-4ECB-9179-8531FF876CE6} - pywlauncher - pyw - false - - - - - Application - Unicode - - - - - - ClCompile - - - - - - - - - _WINDOWS;%(PreprocessorDefinitions) - MultiThreaded - - - shell32.lib;pathcch.lib;%(AdditionalDependencies) - Windows - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/PCbuild/pywlauncher.vcxproj.filters b/PCbuild/pywlauncher.vcxproj.filters deleted file mode 100644 index f10f6d5669e283c..000000000000000 --- a/PCbuild/pywlauncher.vcxproj.filters +++ /dev/null @@ -1,28 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - - - Resource Files - - - - - Resource Files - - - \ No newline at end of file diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt index 7c5eab2eb75a1de..674926c8d4ab7a2 100644 --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -23,7 +23,7 @@ external dependencies. To build, simply run the "build.bat" script without any arguments. After this succeeds, you can open the "pcbuild.sln" solution in Visual Studio to continue development. -To build an installer package, refer to the README in the Tools/msi folder. +To build an installer package, refer to PC/layout. The solution currently supports two platforms. The Win32 platform is used to build standard x86-compatible 32-bit binaries, output into the @@ -420,8 +420,6 @@ _testclinic_limited extension, the file Modules/_testclinic_limited.c: * Save and exit Visual Studio. * Add `;_testclinic_limited` to `` in PCbuild\pcbuild.proj. -* Update "exts" in Tools\msi\lib\lib_files.wxs file or in - Tools\msi\test\test_files.wxs file (for tests). * PC\layout\main.py needs updating if you add a test-only extension whose name doesn't start with "_test". * Add the extension to PCbuild\readme.txt (this file). diff --git a/PCbuild/venvlauncher.vcxproj b/PCbuild/venvlauncher.vcxproj index a2e8ffa82b10eb7..3f8a479fcf9858e 100644 --- a/PCbuild/venvlauncher.vcxproj +++ b/PCbuild/venvlauncher.vcxproj @@ -113,7 +113,7 @@ - + diff --git a/PCbuild/venvwlauncher.vcxproj b/PCbuild/venvwlauncher.vcxproj index f2aaf83fe2b3785..3aa0bb2cdefeaa8 100644 --- a/PCbuild/venvwlauncher.vcxproj +++ b/PCbuild/venvwlauncher.vcxproj @@ -110,10 +110,7 @@ - - - - + diff --git a/Tools/README b/Tools/README index 90acb2614820ee4..3984bce6b9b6768 100644 --- a/Tools/README +++ b/Tools/README @@ -34,10 +34,6 @@ jit Tooling for building the JIT. lockbench Benchmarks for PyMutex and critical sections. -msi Support for packaging Python as an MSI package on Windows. - -nuget Files for the NuGet package manager for .NET. - patchcheck Tools for checking and applying patches to the Python source code and verifying the integrity of patch files. diff --git a/Tools/buildbot/buildmsi.bat b/Tools/buildbot/buildmsi.bat index 6804d794799509b..b0e196df2649be6 100644 --- a/Tools/buildbot/buildmsi.bat +++ b/Tools/buildbot/buildmsi.bat @@ -3,7 +3,9 @@ setlocal pushd -@rem build both snapshot MSIs -call "%~dp0..\msi\build.bat" -x86 -x64 +@rem The MSI project is gone, so instead we will just +@rem build and then do a default layout. +call "%~dp0..\..\PCbuild\build.bat" -e -k -v %* +call "%~dp0..\..\python.exe" "%~dp0..\..\PC\layout" --preset-default -o "%~dp0..\..\PCbuild\output" popd \ No newline at end of file diff --git a/Tools/msi/README.txt b/Tools/msi/README.txt deleted file mode 100644 index 4b3de9e82f6f1ac..000000000000000 --- a/Tools/msi/README.txt +++ /dev/null @@ -1,530 +0,0 @@ -Quick Build Info -================ - -For testing, the installer should be built with the Tools/msi/build.bat -script: - - build.bat [-x86] [-x64] [-ARM64] [--doc] - -For an official release, the installer should be built with the -Tools/msi/buildrelease.bat script and environment variables: - - set PYTHON= - set SPHINXBUILD= - set PATH=;%PATH% - - buildrelease.bat [-x86] [-x64] [-ARM64] [-D] [-B] - [-o ] [-c ] - -See the Building the Installer section for more information. - -Overview -======== - -Python is distributed on Windows as an installer that will configure the -user's system. This allows users to have a functioning copy of Python -without having to build it themselves. - -The main tasks of the installer are: - -* copy required files into the expected layout -* configure system settings so the installation can be located by - other programs -* add entry points for modifying, repairing and uninstalling Python -* make it easy to launch Python, its documentation, and IDLE - -Each of these is discussed in a later section of this document. - -Structure of the Installer -========================== - -The installer is structured as a 'layout', which consists of a number of -CAB and MSI files and a single EXE. - -The EXE is the main entry point into the installer. It contains the UI -and command-line logic, as well as the ability to locate and optionally -download other parts of the layout. - -Each MSI contains the logic required to install a component or feature -of Python. These MSIs should not be launched directly by users. MSIs can -be embedded into the EXE or automatically downloaded as needed. - -Each CAB contains the files making up a Python installation. CABs are -embedded into their associated MSI and are never seen by users. - -MSIs are only required when the related feature or component is being -installed. When components are not selected for installation, the -associated MSI is not downloaded. This allows the installer to offer -options to install debugging symbols and binaries without increasing -the initial download size by separating them into their own MSIs. - -Building the Installer -====================== - -Before building the installer, download the extra build dependencies -using Tools\msi\get_externals.bat. (Note that this is in addition to the -similarly named file in PCbuild.) - -One of the dependencies used in the build process is WiX, a toolset that -allows developers to create installers for Windows Installer, the -Windows installation engine. If you're not using the copy of WiX -installed by Tools\msi\get_externals.bat, you'll need to set the -"WixInstallPath" environment variable before building. - -Additionally, ensure that "MSVC v14x - VS 20xx C++ ARM64/ARM64EC build tools" -is selected under "Desktop Development with C++" in the "Visual Studio Installer", -even if you're not building on ARM64. This is required because we've upgraded -to WiX 3.14, which requires these tools for Python 3.10 and later versions. - -For testing, the installer should be built with the Tools/msi/build.bat -script: - - build.bat [-x86] [-x64] [-ARM64] [--doc] [--test-marker] [--pack] - -This script will build the required configurations of Python and -generate an installer layout in PCbuild/(win32|amd64)/en-us. - -Specify -x86, -x64 and/or -ARM64 to build for each platform. If none are -specified, both x64 and x86 will be built. Currently, both the debug and -release versions of Python are required for the installer. - -Specify --doc to include the documentation files. Ensure %PYTHON% and -%SPHINXBUILD% are set when passing this option. - -Specify --test-marker to build an installer that works side-by-side with -an official Python release. All registry keys and install locations will -include an extra marker to avoid overwriting files. This marker is -currently an 'x' prefix, but may change at any time. - -Specify --pack to build an installer that does not require all MSIs to -be available alongside. This takes longer, but is easier to share. - - -For an official release, the installer should be built with the -Tools/msi/buildrelease.bat script: - - set PYTHON= - set SPHINXBUILD= - set PATH=;%PATH% - - buildrelease.bat [-x86] [-x64] [-ARM64] [-D] [-B] - [-o ] [-c ] - -Specify -x86, -x64 and/or -ARM64 to build for each platform. If none are -specified, both x64 and x86 will be built. Currently, both the debug and -release versions of Python are required for the installer. - -Specify -D to skip rebuilding the documentation. The documentation is -required for a release and the build will fail if it is not available. -Ensure %PYTHON% and %SPHINXBUILD% are set if you omit this option. - -Specify -B to skip rebuilding Python. This is useful to only rebuild the -installer layout after a previous call to buildrelease.bat. - -Specify -o to set an output directory. The installer layouts will be -copied to platform-specific subdirectories of this path. - -Specify -c to choose a code-signing certificate to be used for all the -signable binaries in Python as well as each file making up the -installer. Official releases of Python must be signed. - - -If WiX is not found on your system, it will be automatically downloaded -and extracted to the externals/ directory. - -To manually build layouts of the installer, build one of the projects in -the bundle folder. - - msbuild bundle\snapshot.wixproj - msbuild bundle\releaseweb.wixproj - msbuild bundle\releaselocal.wixproj - msbuild bundle\full.wixproj - -snapshot.wixproj produces a test installer versioned based on the date. - -releaseweb.wixproj produces a release installer that does not embed any -of the layout. - -releaselocal.wixproj produces a release installer that embeds the files -required for a default installation. - -full.wixproj produces a test installer that embeds the entire layout. - -The following properties may be passed when building these projects. - - /p:BuildForRelease=(true|false) - When true, adds extra verification to ensure a complete installer is - produced. Defaults to false. - - /p:ReleaseUri=(any URI) - Used to generate unique IDs for the installers to allow side-by-side - installation. Forks of Python can use the same installer infrastructure - by providing a unique URI for this property. It does not need to be an - active internet address. Defaults to $(ComputerName). - - Official releases use https://www.python.org/(architecture name) - - /p:DownloadUrlBase=(any URI) - Specifies the base of a URL where missing parts of the installer layout - can be downloaded from. The build version and architecture will be - appended to create the full address. If omitted, missing components will - not be automatically downloaded. - - /p:DownloadUrl=(any URI) - Specifies the full URL where missing parts of the installer layout can - be downloaded from. Should normally include '{2}', which will be - substituted for the filename. If omitted, missing components will not be - automatically downloaded. If specified, this value overrides - DownloadUrlBase. - - /p:SigningCertificate=(certificate name) - Specifies the certificate to sign the installer layout with. If omitted, - the layout will not be signed. - - /p:RebuildAll=(true|false) - When true, rebuilds all of the MSIs making up the layout. Defaults to - true. - -Uploading the Installer -======================= - -For official releases, the uploadrelease.bat script should be used. - -You will require PuTTY so that plink.exe and pscp.exe can be used, and your -SSH key can be activated in pageant.exe. PuTTY should be either on your path -or in %ProgramFiles(x86)%\PuTTY. - -To include signatures for each uploaded file, you will need gpg2.exe on your -path or have run get_externals.bat. You may also need to "gpg2.exe --import" -your key before running the upload script. - - uploadrelease.bat --host --user [--dry-run] [--no-gpg] - -The host is the URL to the server. This can be provided by the Release -Manager. You should be able to SSH to this address. - -The username is your own username, which you have permission to SSH into -the server containing downloads. - -Use --dry-run to display the generated upload commands without executing -them. Signatures for each file will be generated but not uploaded unless ---no-gpg is also passed. - -Use --no-gpg to suppress signature generation and upload. - -The default target directory (which appears in uploadrelease.proj) is -correct for official Python releases, but may be overridden with ---target for other purposes. This path should generally not include -any version specifier, as that will be added automatically. - -Modifying the Installer -======================= - -The code for the installer is divided into three main groups: packages, -the bundle and the bootstrap application. - -Packages --------- - -Packages appear as subdirectories of Tools/msi (other than the bundle/ -directory). The project file is a .wixproj and the build output is a -single MSI. Packages are built with the WiX Toolset. Some project files -share source files and use preprocessor directives to enable particular -features. These are typically used to keep the sources close when the -files are related, but produce multiple independent packages. - -A package is the smallest element that may be independently installed or -uninstalled (as used in this installer). For example, the test suite has -its own package, as users can choose to add or remove it after the -initial installation. - -All the files installed by a single package should be related, though -some packages may not install any files. For example, the pip package -executes the ensurepip package, but does not add or remove any of its -own files. (It is represented as a package because of its -installed/uninstalled nature, as opposed to the "precompile standard -library" option, for example.) Dependencies between packages are handled -by the bundle, but packages should detect when dependencies are missing -and raise an error. - -Packages that include a lot of files may use an InstallFiles element in -the .wixproj file to generate sources. See lib/lib.wixproj for an -example, and msi.targets and csv_to_wxs.py for the implementation. This -element is also responsible for generating the code for cleaning up and -removing __pycache__ folders in any directory containing .py files. - -All packages are built with the Tools/msi/common.wxs file, and so any -directory or property in this file may be referenced. Of particular -interest: - - REGISTRYKEY (property) - The registry key for the current installation. - - InstallDirectory (directory) - The root install directory for the current installation. Subdirectories - are also specified in this file (DLLs, Lib, etc.) - - MenuDir (directory) - The Start Menu folder for the current installation. - - UpgradeTable (property) - Every package should reference this property to include upgrade - information. - - OptionalFeature (Component) - Packages that may be enabled or disabled should reference this component - and have an OPTIONAL_FEATURES entry in the bootstrap application to - properly handle Modify and Upgrade. - -The .wxl_template file is specially handled by the build system for this -project to perform {{substitutions}} as defined in msi.targets. They -should be included in projects as items, where .wxl files -are normally included as items. - -Bundle ------- - -The bundle is compiled to the main EXE entry point that for most users -will represent the Python installer. It is built from Tools/msi/bundle -with packages references in Tools/msi/bundle/packagegroups. - -Build logic for the bundle is in bundle.targets, but should be invoked -through one of the .wixproj files as described in Building the -Installer. - -The UI is separated between Default.thm (UI layout), Default.wxl -(strings), bundle.wxs (properties) and the bootstrap application. -Bundle.wxs also contains the chain, which is the list of packages to -install and the order they should be installed in. These refer to named -package groups in bundle/packagegroups. - -Each package group specifies one or more packages to install. Most -packages require two separate entries to support both per-user and -all-users installations. Because these reuse the same package, it does -not increase the overall size of the package. - -Package groups refer to payload groups, which allow better control over -embedding and downloading files than the default settings. Whether files -are embedded and where they are downloaded from depends on settings -created by the project files. - -Package references can include install conditions that determine when to -install the package. When a package is a dependency for others, the -condition should be crafted to ensure it is installed. - -MSI packages are installed or uninstalled based on their current state -and the install condition. This makes them most suitable for features -that are clearly present or absent from the user's machine. - -EXE packages are executed based on a customisable condition that can be -omitted. This makes them suitable for pre- or post-install tasks that -need to run regardless of whether features have been added or removed. - -Bootstrap Application ---------------------- - -The bootstrap application is a C++ application that controls the UI and -installation. While it does not directly compile into the main EXE of -the installer, it forms the main active component. Most of the -installation functionality is provided by WiX, and so the bootstrap -application is predominantly responsible for the code behind the UI that -is defined in the Default.thm file. The bootstrap application code is in -bundle/bootstrap and is built automatically when building the bundle. - -Installation Layout -=================== - -There are two installation layouts for Python on Windows, with the only -differences being supporting files. A layout is selected implicitly -based on whether the install is for all users of the machine or just for -the user performing the installation. - -The default installation location when installing for all users is -"%ProgramFiles%\Python3X" for the 64-bit interpreter and -"%ProgramFiles(x86)%\Python3X-32" for the 32-bit interpreter. (Note that -the latter path is equivalent to "%ProgramFiles%\Python3X-32" when -running a 32-bit version of Windows.) This location requires -administrative privileges to install or later modify the installation. - -The default installation location when installing for the current user -is "%LocalAppData%\Programs\Python\Python3X" for the 64-bit interpreter -and "%LocalAppData%\Programs\Python\Python3X-32" for the 32-bit -interpreter. Only the current user can access this location. This -provides a suitable level of protection against malicious modification -of Python's files. - -(Default installation locations are set in Tools\msi\bundle\bundle.wxs.) - -Within this install directory is the following approximate layout: - -.\python[w].exe The core executable files -.\python3x.dll The core interpreter -.\python3.dll The stable ABI reference -.\DLLs Stdlib extensions (*.pyd) and dependencies -.\Doc Documentation (*.html) -.\include Development headers (*.h) -.\Lib Standard library -.\Lib\test Test suite -.\libs Development libraries (*.lib) -.\Scripts Launcher scripts (*.exe, *.py) -.\tcl Tcl dependencies (*.dll, *.tcl and others) -.\Tools Tool scripts (*.py) - -When installed for all users, the following files are installed to -"%SystemRoot%" (typically "C:\Windows") to ensure they are always -available on PATH. (See Launching Python below.) For the current user, -they are installed in "%LocalAppData%\Programs\Python\PyLauncher". - -.\py[w].exe PEP 397 launcher - - -System Settings -=============== - -On installation, registry keys are created so that other applications -can locate and identify installations of Python. The locations of these -keys vary based on the install type. - -For 64-bit interpreters installed for all users, the root key is: - HKEY_LOCAL_MACHINE\Software\Python\PythonCore\3.X - -For 32-bit interpreters installed for all users on a 64-bit operating -system, the root key is: - HKEY_LOCAL_MACHINE\Software\Wow6432Node\Python\PythonCore\3.X-32 - -For 32-bit interpreters installed for all users on a 32-bit operating -system, the root key is: - HKEY_LOCAL_MACHINE\Software\Python\PythonCore\3.X-32 - -For 64-bit interpreters installed for the current user: - HKEY_CURRENT_USER\Software\Python\PythonCore\3.X - -For 32-bit interpreters installed for the current user: - HKEY_CURRENT_USER\Software\Python\PythonCore\3.X-32 - -When the core Python executables are installed, a key "InstallPath" is -created within the root key with its default value set to the -executable's install directory. A value named "ExecutablePath" is added -with the full path to the main Python interpreter, and a key -"InstallGroup" is created with its default value set to the product -name "Python 3.X". - -When the Python standard library is installed, a key "PythonPath" is -created within the root key with its default value set to the full path -to the Lib folder followed by the path to the DLLs folder, separated by -a semicolon. - -When the documentation is installed, a key "Help" is created within the -root key, with a subkey "Main Python Documentation" with its default -value set to the full path to the main index.html file. - - -The py.exe launcher is installed as part of a regular Python install, -but using a separate mechanism that allows it to more easily span -versions of Python. As a result, it has different root keys for its -registry entries: - -When installed for all users on a 64-bit operating system, the -launcher's root key is: - HKEY_LOCAL_MACHINE\Software\Wow6432Node\Python\Launcher - -When installed for all users on a 32-bit operating system, the -launcher's root key is: - HKEY_LOCAL_MACHINE\Software\Python\Launcher - -When installed for the current user: - HKEY_CURRENT_USER\Software\Python\Launcher - -When the launcher is installed, a key "InstallPath" is created within -its root key with its default value set to the launcher's install -directory. File associations are also created for .py, .pyw, .pyc and -.pyo files. - -Launching Python -================ - -When a feature offering user entry points in the Start Menu is -installed, a folder "Python 3.X" is created. Every shortcut should be -created within this folder, and each shortcut should include the version -and platform to allow users to identify the shortcut in a search results -page. - -The core Python executables creates a shortcut "Python 3.X (32-bit)" or -"Python 3.X (64-bit)" depending on the interpreter. - -The documentation creates a shortcut "Python 3.X 32-bit Manuals" or -"Python 3.X 64-bit Manuals". The documentation is identical for all -platforms, but the shortcuts need to be separate to avoid uninstallation -conflicts. - -Installing IDLE creates a shortcut "IDLE (Python 3.X 32-bit)" or "IDLE -(Python 3.X 64-bit)" depending on the interpreter. - - -For users who often launch Python from a Command Prompt, an option is -provided to add the directory containing python.exe to the user or -system PATH variable. If the option is selected, the install directory -and the Scripts directory will be added at the start of the system PATH -for an all users install and the user PATH for a per-user install. - -When the user only has one version of Python installed, this will behave -as expected. However, because Windows searches the system PATH before -the user PATH, users cannot override a system-wide installation of -Python on their PATH. Further, because the installer can only prepend to -the path, later installations of Python will take precedence over -earlier installations, regardless of interpreter version. - -Because it is not possible to automatically create a sensible PATH -configuration, users are recommended to use the py.exe launcher and -manually modify their PATH variable to add Scripts directories in their -preferred order. System-wide installations of Python should consider not -modifying PATH, or using an alternative technology to modify their -users' PATH variables. - - -The py.exe launcher is recommended because it uses a consistent and -sensible search order for Python installations. User installations are -preferred over system-wide installs, and later versions are preferred -regardless of installation order (with the exception that py.exe -currently prefers 2.x versions over 3.x versions without the -3 command -line argument). - -For both 32-bit and 64-bit interpreters, the 32-bit version of the -launcher is installed. This ensures that the search order is always -consistent (as the 64-bit launcher is subtly different from the 32-bit -launcher) and also avoids the need to install it multiple times. Future -versions of Python will upgrade the launcher in-place, using Windows -Installer's upgrade functionality to avoid conflicts with earlier -installed versions. - -When installed, file associations are created for .py, .pyc and .pyo -files to launch with py.exe and .pyw files to launch with pyw.exe. This -makes Python files respect shebang lines by default and also avoids -conflicts between multiple Python installations. - - -Repair, Modify and Uninstall -============================ - -After installation, Python may be modified, repaired or uninstalled by -running the original EXE again or via the Programs and Features applet -(formerly known as Add or Remove Programs). - -Modifications allow features to be added or removed. The install -directory and kind (all users/single user) cannot be modified. Because -Windows Installer caches installation packages, removing features will -not require internet access unless the package cache has been corrupted -or deleted. Adding features that were not previously installed and are -not embedded or otherwise available will require internet access. - -Repairing will rerun the installation for all currently installed -features, restoring files and registry keys that have been modified or -removed. This operation generally will not redownload any files unless -the cached packages have been corrupted or deleted. - -Removing Python will clean up all the files and registry keys that were -created by the installer, as well as __pycache__ folders that are -explicitly handled by the installer. Python packages installed later -using a tool like pip will not be removed. Some components may be -installed by other installers and these will not be removed if another -product has a dependency on them. diff --git a/Tools/msi/appendpath/appendpath.wixproj b/Tools/msi/appendpath/appendpath.wixproj deleted file mode 100644 index 897087678c56f63..000000000000000 --- a/Tools/msi/appendpath/appendpath.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {12B59A06-37CC-4558-A9C8-DAE922E64EF3} - 2.0 - appendpath - Package - ICE71 - - - - - - - - - - - diff --git a/Tools/msi/appendpath/appendpath.wxs b/Tools/msi/appendpath/appendpath.wxs deleted file mode 100644 index bba186cf60fd10a..000000000000000 --- a/Tools/msi/appendpath/appendpath.wxs +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - NOT ALLUSERS=1 - - - - - - - - - - - - ALLUSERS=1 - - - - - - - - - - - - - - - - diff --git a/Tools/msi/appendpath/appendpath_en-US.wxl b/Tools/msi/appendpath/appendpath_en-US.wxl deleted file mode 100644 index 19a2e7734f8c31e..000000000000000 --- a/Tools/msi/appendpath/appendpath_en-US.wxl +++ /dev/null @@ -1,6 +0,0 @@ - - - Append to Path - AppendPath - No !(loc.ProductName) installation was detected. - diff --git a/Tools/msi/build.bat b/Tools/msi/build.bat deleted file mode 100644 index 2fe8a475e7e3a30..000000000000000 --- a/Tools/msi/build.bat +++ /dev/null @@ -1,103 +0,0 @@ -@echo off -setlocal -set D=%~dp0 -set PCBUILD=%D%..\..\PCbuild\ - -set BUILDX86= -set BUILDX64= -set BUILDARM64= -set BUILDDOC= -set BUILDTEST= -set BUILDPACK= -set REBUILD= - -:CheckOpts -if "%~1" EQU "-h" goto Help -if /I "%~1" EQU "-x86" (set BUILDX86=1) && shift && goto CheckOpts -if /I "%~1" EQU "-Win32" (set BUILDX86=1) && shift && goto CheckOpts -if /I "%~1" EQU "-x64" (set BUILDX64=1) && shift && goto CheckOpts -if /I "%~1" EQU "-arm64" (set BUILDARM64=1) && shift && goto CheckOpts -if "%~1" EQU "--doc" (set BUILDDOC=1) && shift && goto CheckOpts -if "%~1" EQU "--no-test-marker" (set BUILDTEST=) && shift && goto CheckOpts -if "%~1" EQU "--test-marker" (set BUILDTEST=--test-marker) && shift && goto CheckOpts -if "%~1" EQU "--pack" (set BUILDPACK=1) && shift && goto CheckOpts -if "%~1" EQU "-r" (set REBUILD=-r) && shift && goto CheckOpts -rem %IncludeFreethreaded% is recognised by the MSI build, but not the regular build. -rem We use it to build twice and then build the installer with its extra option -if /I "%~1" EQU "--disable-gil" (set IncludeFreethreaded=true) && shift && goto CheckOpts - -if not defined BUILDX86 if not defined BUILDX64 if not defined BUILDARM64 (set BUILDX86=1) && (set BUILDX64=1) - -call "%D%get_externals.bat" -call "%PCBUILD%find_msbuild.bat" %MSBUILD% -if ERRORLEVEL 1 (echo Cannot locate MSBuild.exe on PATH or as MSBUILD variable & exit /b 2) - -if defined BUILDX86 call "%PCBUILD%build.bat" -p Win32 -d -e %REBUILD% %BUILDTEST% -if errorlevel 1 exit /B %ERRORLEVEL% -if defined BUILDX86 call "%PCBUILD%build.bat" -p Win32 -e %REBUILD% %BUILDTEST% -if errorlevel 1 exit /B %ERRORLEVEL% - -if defined BUILDX64 call "%PCBUILD%build.bat" -p x64 -d -e %REBUILD% %BUILDTEST% -if errorlevel 1 exit /B %ERRORLEVEL% -if defined BUILDX64 call "%PCBUILD%build.bat" -p x64 -e %REBUILD% %BUILDTEST% -if errorlevel 1 exit /B %ERRORLEVEL% - -if defined BUILDARM64 call "%PCBUILD%build.bat" -p ARM64 -d -e %REBUILD% %BUILDTEST% -if errorlevel 1 exit /B %ERRORLEVEL% -if defined BUILDARM64 call "%PCBUILD%build.bat" -p ARM64 -e %REBUILD% %BUILDTEST% -if errorlevel 1 exit /B %ERRORLEVEL% - -if /I "%IncludeFreethreaded%"=="true" ( - rem Cannot "exit /B" inside an if block because %ERRORLEVEL% will be wrong. - rem We just skip everything after the first "errorlevel 1" and then exit after - if defined BUILDX86 call "%PCBUILD%build.bat" -p Win32 -d -e %REBUILD% %BUILDTEST% --disable-gil - if not errorlevel 1 if defined BUILDX86 call "%PCBUILD%build.bat" -p Win32 -e %REBUILD% %BUILDTEST% --disable-gil - - if not errorlevel 1 if defined BUILDX64 call "%PCBUILD%build.bat" -p x64 -d -e %REBUILD% %BUILDTEST% --disable-gil - if not errorlevel 1 if defined BUILDX64 call "%PCBUILD%build.bat" -p x64 -e %REBUILD% %BUILDTEST% --disable-gil - - if not errorlevel 1 if defined BUILDARM64 call "%PCBUILD%build.bat" -p ARM64 -d -e %REBUILD% %BUILDTEST% --disable-gil - if not errorlevel 1 if defined BUILDARM64 call "%PCBUILD%build.bat" -p ARM64 -e %REBUILD% %BUILDTEST% --disable-gil -) -if errorlevel 1 exit /B %ERRORLEVEL% - -if defined BUILDDOC call "%PCBUILD%..\Doc\make.bat" html -if errorlevel 1 exit /B %ERRORLEVEL% - -rem Build the launcher MSI separately -%MSBUILD% "%D%launcher\launcher.wixproj" /p:Platform=x86 -if errorlevel 1 exit /B %ERRORLEVEL% - -set BUILD_CMD="%D%bundle\snapshot.wixproj" -if defined BUILDTEST ( - set BUILD_CMD=%BUILD_CMD% /p:UseTestMarker=true -) -if defined BUILDPACK ( - set BUILD_CMD=%BUILD_CMD% /p:Pack=true -) -if defined REBUILD ( - set BUILD_CMD=%BUILD_CMD% /t:Rebuild -) - -if defined BUILDX86 %MSBUILD% /p:Platform=x86 %BUILD_CMD% -if errorlevel 1 exit /B %ERRORLEVEL% - -if defined BUILDX64 %MSBUILD% /p:Platform=x64 %BUILD_CMD% -if errorlevel 1 exit /B %ERRORLEVEL% - -if defined BUILDARM64 %MSBUILD% /p:Platform=ARM64 %BUILD_CMD% -if errorlevel 1 exit /B %ERRORLEVEL% - -exit /B 0 - -:Help -echo build.bat [-x86] [-x64] [-arm64] [--doc] [-h] [--test-marker] [--pack] [-r] -echo. -echo -x86 Build x86 installers -echo -x64 Build x64 installers -echo -ARM64 Build ARM64 installers -echo --doc Build documentation -echo --test-marker Build with test markers -echo --no-test-marker Build without test markers (default) -echo --pack Embed core MSIs into installer -echo -r Rebuild rather than incremental build diff --git a/Tools/msi/buildrelease.bat b/Tools/msi/buildrelease.bat deleted file mode 100644 index 77fb47792088531..000000000000000 --- a/Tools/msi/buildrelease.bat +++ /dev/null @@ -1,261 +0,0 @@ -@setlocal -@echo off - -rem This script is intended for building official releases of Python. -rem To use it to build alternative releases, you should clone this file -rem and modify the following three URIs. - -rem These two will ensure that your release can be installed -rem alongside an official Python release, by modifying the GUIDs used -rem for all components. -rem -rem The following substitutions will be applied to the release URI: -rem Variable Description Example -rem {arch} architecture amd64, win32 -rem Do not change the scheme to https. Otherwise, releases built with this -rem script will not be upgradable to/from official releases of Python. -set RELEASE_URI=http://www.python.org/{arch} - -rem This is the URL that will be used to download installation files. -rem The files available from the default URL *will* conflict with your -rem installer. Trust me, you don't want them, even if it seems like a -rem good idea. -rem -rem The following substitutions will be applied to the download URL: -rem Variable Description Example -rem {version} version number 3.5.0 -rem {arch} architecture amd64, win32 -rem {releasename} release name a1, b2, rc3 (or blank for final) -rem {msi} MSI filename core.msi -set DOWNLOAD_URL=https://www.python.org/ftp/python/{version}/{arch}{releasename}/{msi} - -set D=%~dp0 -set PCBUILD=%D%..\..\PCbuild\ -if NOT DEFINED Py_OutDir set Py_OutDir=%PCBUILD% -set EXTERNALS=%D%..\..\externals\windows-installer\ - -set BUILDX86= -set BUILDX64= -set BUILDARM64= -set TARGET=Rebuild -set TESTTARGETDIR= -set PGO=-m test -q --pgo -set BUILDMSI=1 -set BUILDNUGET=1 -set BUILDZIP=1 - - -:CheckOpts -if "%1" EQU "-h" goto Help -if "%1" EQU "-c" (set CERTNAME=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "--certificate" (set CERTNAME=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "-o" (set OUTDIR=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "--out" (set OUTDIR=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "-D" (set SKIPDOC=1) && shift && goto CheckOpts -if "%1" EQU "--skip-doc" (set SKIPDOC=1) && shift && goto CheckOpts -if "%1" EQU "-B" (set SKIPBUILD=1) && shift && goto CheckOpts -if "%1" EQU "--skip-build" (set SKIPBUILD=1) && shift && goto CheckOpts -if "%1" EQU "--download" (set DOWNLOAD_URL=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "--test" (set TESTTARGETDIR=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "-b" (set TARGET=Build) && shift && goto CheckOpts -if "%1" EQU "--build" (set TARGET=Build) && shift && goto CheckOpts -if /I "%1" EQU "-x86" (set BUILDX86=1) && shift && goto CheckOpts -if /I "%1" EQU "-Win32" (set BUILDX86=1) && shift && goto CheckOpts -if /I "%1" EQU "-x64" (set BUILDX64=1) && shift && goto CheckOpts -if /I "%1" EQU "-arm64" (set BUILDARM64=1) && shift && goto CheckOpts -if "%1" EQU "--pgo" (set PGO=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "--skip-pgo" (set PGO=) && shift && goto CheckOpts -if "%1" EQU "--skip-nuget" (set BUILDNUGET=) && shift && goto CheckOpts -if "%1" EQU "--skip-zip" (set BUILDZIP=) && shift && goto CheckOpts -if "%1" EQU "--skip-msi" (set BUILDMSI=) && shift && goto CheckOpts - -if "%1" NEQ "" echo Invalid option: "%1" && exit /B 1 - -if not defined BUILDX86 if not defined BUILDX64 if not defined BUILDARM64 (set BUILDX86=1) && (set BUILDX64=1) - -if not exist "%GIT%" where git > "%TEMP%\git.loc" 2> nul && set /P GIT= < "%TEMP%\git.loc" & del "%TEMP%\git.loc" -if not exist "%GIT%" echo Cannot find Git on PATH && exit /B 1 - -call "%D%get_externals.bat" -call "%PCBUILD%find_msbuild.bat" %MSBUILD% -if ERRORLEVEL 1 (echo Cannot locate MSBuild.exe on PATH or as MSBUILD variable & exit /b 2) - -:builddoc -if "%SKIPBUILD%" EQU "1" goto skipdoc -if "%SKIPDOC%" EQU "1" goto skipdoc - -call "%D%..\..\doc\make.bat" html -if errorlevel 1 exit /B %ERRORLEVEL% -:skipdoc - -if defined BUILDX86 ( - call :build x86 - if errorlevel 1 exit /B %ERRORLEVEL% -) - -if defined BUILDX64 ( - call :build x64 "%PGO%" - if errorlevel 1 exit /B %ERRORLEVEL% -) - -if defined BUILDARM64 ( - call :build ARM64 - if errorlevel 1 exit /B %ERRORLEVEL% -) - -if defined TESTTARGETDIR ( - call "%D%testrelease.bat" -t "%TESTTARGETDIR%" - if errorlevel 1 exit /B %ERRORLEVEL% -) - -exit /B 0 - -:build -@setlocal -@echo off - -if "%1" EQU "x86" ( - set PGO= - set BUILD=%Py_OutDir%win32\ - set BUILD_PLAT=Win32 - set OUTDIR_PLAT=win32 - set OBJDIR_PLAT=x86 -) else if "%1" EQU "x64" ( - set BUILD=%Py_OutDir%amd64\ - set PGO=%~2 - set BUILD_PLAT=x64 - set OUTDIR_PLAT=amd64 - set OBJDIR_PLAT=x64 -) else if "%1" EQU "ARM64" ( - set BUILD=%Py_OutDir%arm64\ - set PGO=%~2 - set BUILD_PLAT=ARM64 - set OUTDIR_PLAT=arm64 - set OBJDIR_PLAT=arm64 -) else ( - echo Unknown platform %1 - exit /B 1 -) - -if exist "%BUILD%en-us" ( - echo Deleting %BUILD%en-us - rmdir /q/s "%BUILD%en-us" - if errorlevel 1 exit /B %ERRORLEVEL% -) - -if exist "%D%obj\Debug_%OBJDIR_PLAT%" ( - echo Deleting "%D%obj\Debug_%OBJDIR_PLAT%" - rmdir /q/s "%D%obj\Debug_%OBJDIR_PLAT%" - if errorlevel 1 exit /B %ERRORLEVEL% -) - -if exist "%D%obj\Release_%OBJDIR_PLAT%" ( - echo Deleting "%D%obj\Release_%OBJDIR_PLAT%" - rmdir /q/s "%D%obj\Release_%OBJDIR_PLAT%" - if errorlevel 1 exit /B %ERRORLEVEL% -) - -if not "%CERTNAME%" EQU "" ( - set CERTOPTS="/p:SigningCertificate=%CERTNAME%" -) else ( - set CERTOPTS= -) -if not "%PGO%" EQU "" ( - set PGOOPTS=--pgo-job "%PGO%" -) else ( - set PGOOPTS= -) -if not "%SKIPBUILD%" EQU "1" ( - @echo call "%PCBUILD%build.bat" -e -p %BUILD_PLAT% -t %TARGET% %PGOOPTS% %CERTOPTS% - @call "%PCBUILD%build.bat" -e -p %BUILD_PLAT% -t %TARGET% %PGOOPTS% %CERTOPTS% - @if errorlevel 1 exit /B %ERRORLEVEL% - @rem build.bat turns echo back on, so we disable it again - @echo off - - @echo call "%PCBUILD%build.bat" -d -e -p %BUILD_PLAT% -t %TARGET% - @call "%PCBUILD%build.bat" -d -e -p %BUILD_PLAT% -t %TARGET% - @if errorlevel 1 exit /B %ERRORLEVEL% - @rem build.bat turns echo back on, so we disable it again - @echo off -) - -if "%OUTDIR_PLAT%" EQU "win32" ( - %MSBUILD% "%D%launcher\launcher.wixproj" /p:Platform=x86 %CERTOPTS% /p:ReleaseUri=%RELEASE_URI% - if errorlevel 1 exit /B %ERRORLEVEL% -) else if not exist "%Py_OutDir%win32\en-us\launcher.msi" ( - %MSBUILD% "%D%launcher\launcher.wixproj" /p:Platform=x86 %CERTOPTS% /p:ReleaseUri=%RELEASE_URI% - if errorlevel 1 exit /B %ERRORLEVEL% -) - -set BUILDOPTS=/p:Platform=%1 /p:BuildForRelease=true /p:DownloadUrl=%DOWNLOAD_URL% /p:DownloadUrlBase=%DOWNLOAD_URL_BASE% /p:ReleaseUri=%RELEASE_URI% -if defined BUILDMSI ( - %MSBUILD% "%D%bundle\releaselocal.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=true - if errorlevel 1 exit /B %ERRORLEVEL% -) - -if defined BUILDZIP ( - if "%BUILD_PLAT%" EQU "ARM64" ( - echo Skipping embeddable ZIP generation for ARM64 platform - ) else ( - %MSBUILD% "%D%make_zip.proj" /t:Build %BUILDOPTS% %CERTOPTS% /p:OutputPath="%BUILD%en-us" - if errorlevel 1 exit /B %ERRORLEVEL% - ) -) - -if defined BUILDNUGET ( - if "%BUILD_PLAT%" EQU "ARM64" ( - echo Skipping Nuget package generation for ARM64 platform - ) else ( - %MSBUILD% "%D%..\nuget\make_pkg.proj" /t:Build /p:Configuration=Release /p:Platform=%1 /p:OutputPath="%BUILD%en-us" - if errorlevel 1 exit /B %ERRORLEVEL% - ) -) - -if not "%OUTDIR%" EQU "" ( - mkdir "%OUTDIR%\%OUTDIR_PLAT%" - mkdir "%OUTDIR%\%OUTDIR_PLAT%\binaries" - mkdir "%OUTDIR%\%OUTDIR_PLAT%\symbols" - robocopy "%BUILD%en-us" "%OUTDIR%\%OUTDIR_PLAT%" /XF "*.wixpdb" - robocopy "%BUILD%\" "%OUTDIR%\%OUTDIR_PLAT%\binaries" *.exe *.dll *.pyd /XF "_test*" /XF "*_d.*" /XF "_freeze*" /XF "tcl*" /XF "tk*" /XF "*_test.*" - robocopy "%BUILD%\" "%OUTDIR%\%OUTDIR_PLAT%\symbols" *.pdb /XF "_test*" /XF "*_d.*" /XF "_freeze*" /XF "tcl*" /XF "tk*" /XF "*_test.*" -) - -exit /B 0 - -:Help -echo buildrelease.bat [--out DIR] [-x86] [-x64] [-arm64] [--certificate CERTNAME] [--build] [--pgo COMMAND] -echo [--skip-build] [--skip-doc] [--skip-nuget] [--skip-zip] [--skip-pgo] -echo [--download DOWNLOAD URL] [--test TARGETDIR] -echo [-h] -echo. -echo --out (-o) Specify an additional output directory for installers -echo -x86 Build x86 installers -echo -x64 Build x64 installers -echo -arm64 Build ARM64 installers -echo --build (-b) Incrementally build Python rather than rebuilding -echo --skip-build (-B) Do not build Python (just do the installers) -echo --skip-doc (-D) Do not build documentation -echo --pgo Specify PGO command for x64 installers -echo --skip-pgo Build x64 installers without using PGO -echo --skip-msi Do not build executable/MSI packages -echo --skip-nuget Do not build Nuget packages -echo --skip-zip Do not build embeddable package -echo --download Specify the full download URL for MSIs -echo --test Specify the test directory to run the installer tests -echo -h Display this help information -echo. -echo If no architecture is specified, all architectures will be built. -echo If --test is not specified, the installer tests are not run. -echo. -echo For the --pgo option, any Python command line can be used, or 'default' to -echo use the default task (-m test --pgo). -echo. -echo x86 and ARM64 builds will never use PGO. ARM64 builds will never generate -echo embeddable or Nuget packages. -echo. -echo The following substitutions will be applied to the download URL: -echo Variable Description Example -echo {version} version number 3.5.0 -echo {arch} architecture amd64, win32 -echo {releasename} release name a1, b2, rc3 (or blank for final) -echo {msi} MSI filename core.msi diff --git a/Tools/msi/bundle/Default.ARM64.xsl b/Tools/msi/bundle/Default.ARM64.xsl deleted file mode 100644 index b28b3f264adbc2d..000000000000000 --- a/Tools/msi/bundle/Default.ARM64.xsl +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/Default.thm b/Tools/msi/bundle/Default.thm deleted file mode 100644 index 6e908ac373442d9..000000000000000 --- a/Tools/msi/bundle/Default.thm +++ /dev/null @@ -1,141 +0,0 @@ - - - #(loc.Caption) - Segoe UI - Segoe UI - Segoe UI - Segoe UI - Segoe UI - Segoe UI - Segoe UI - - - #(loc.HelpHeader) - - - #(loc.HelpText) - - - - #(loc.InstallHeader) - - - #(loc.InstallMessage) - - #(loc.DeprecationMessage) - - - - - #(loc.ShortInstallLauncherAllUsersLabel) - #(loc.ShortPrependPathLabel) - - - - - #(loc.InstallUpgradeHeader) - - - #(loc.InstallUpgradeMessage) - - - - - - - - #(loc.InstallHeader) - - - - - - - - #(loc.Custom1Header) - - - #(loc.Include_docLabel) - #(loc.Include_docHelpLabel) - - #(loc.Include_pipLabel) - #(loc.Include_pipHelpLabel) - - #(loc.Include_tcltkLabel) - #(loc.Include_tcltkHelpLabel) - - #(loc.Include_testLabel) - #(loc.Include_testHelpLabel) - - #(loc.Include_launcherLabel) - #(loc.InstallLauncherAllUsersLabel) - - - - - - - - #(loc.Custom2Header) - - - #(loc.InstallAllUsersLabel) - #(loc.AssociateFilesLabel) - #(loc.ShortcutsLabel) - #(loc.PrependPathLabel) - #(loc.PrecompileLabel) - #(loc.Include_symbolsLabel) - #(loc.Include_debugLabel) - #(loc.Include_freethreadedLabel) - - #(loc.CustomLocationLabel) - - - #(loc.CustomLocationHelpLabel) - - - - - - - #(loc.ProgressHeader) - - - #(loc.ProgressLabel) - #(loc.OverallProgressPackageText) - - - - - #(loc.ModifyHeader) - - - - - - - - - - #(loc.SuccessHeader) - - - - - - - #(loc.SuccessRestartText) - - - - - #(loc.FailureHeader) - - - #(loc.FailureHyperlinkLogText) - - #(loc.FailureRestartText) - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/Default.wxl b/Tools/msi/bundle/Default.wxl deleted file mode 100644 index 335c1d922d97b52..000000000000000 --- a/Tools/msi/bundle/Default.wxl +++ /dev/null @@ -1,133 +0,0 @@ - - - [WixBundleName] Setup - Installing - Setup - Updating - Modify - Repairing - Repair - Removing - Uninstall - - You will be prompted for Administrator privileges to install a C Runtime Library update (KB2999226). - - -Continue? - - &Cancel - &Close - Install [WixBundleName] - Select Install Now to install Python with default settings, or choose Customize to enable or disable features. - Version [WixBundleVersion] - Upgrade to [WixBundleName] - Select Upgrade Now to keep your current settings, or choose Customize to enable or disable features. - Are you sure you want to cancel? - Previous version - Setup Help - Visit <a href="http://docs.python.org/[ShortVersion]/using/windows.html">docs.python.org/[ShortVersion]/using/windows.html</a> for the full list of options, including the ability to enable and disable specific features. - -"/passive" to display progress without requiring user interaction - -"/quiet" to install/uninstall without displaying any UI - -"/simple" to prevent user customization - -"/uninstall" to remove Python (without confirmation) - -"/layout [\[]directory[\]]" to pre-download all components - -"/log [\[]filename[\]]" to specify log files location - [WixBundleName] <a href="#">license terms</a>. - I &agree to the license terms and conditions - &Install Now - [TargetDir] - -Includes IDLE, pip and documentation -Creates shortcuts and file associations - C&ustomize installation - Choose location and features - &Install - Use settings preselected by your administrator - -[SimpleInstallDescription] - Up&grade Now - [TargetDir] - -Replaces your existing installation without changing settings. -Select Customize to review current options. - C&ustomize installation - Choose location and features - Optional Features - Advanced Options - Customize install location - You will require write permissions for the selected location. - &Install - &Next - &Back - B&rowse - &Documentation - Installs the Python documentation files. - &pip - Installs pip, which can download and install other Python packages. - Tcl/Tk, turtle and &IDLE - Installs tkinter, turtle and the IDLE development environment. - Python &test suite - Installs the standard library test suite. - py &launcher - Installs the global 'py' launcher to make it easier to start Python. - Use Programs and Features to remove the 'py' launcher. - Upgrades the global 'py' launcher from the previous version. - - Associate &files with Python (requires the 'py' launcher) - Create shortcuts for installed applications - Add Python to &environment variables - Add &python.exe to PATH - Append Python to &environment variables - Append &python.exe to PATH - Install Python [ShortVersion] for &all users - for &all users (requires admin privileges) - Use admin privi&leges when installing py.exe - Python Launcher is already installed - &Precompile standard library - Download debugging &symbols - Download debu&g binaries (requires VS 2017 or later) - Download &free-threaded binaries - - [ActionLikeInstallation] Progress - [ActionLikeInstalling]: - Initializing... - Modify Setup - &Modify - Add or remove individual features. - &Repair - Ensure all current features are correctly installed. - &Uninstall - Remove the entire [WixBundleName] installation. - [ActionLikeInstallation] was successful - &Launch - You may need to restart your computer to finish updating files. - &Restart - New to Python? Start with the <a href="https://docs.python.org/[ShortVersion]/tutorial/index.html">online tutorial</a> and <a href="https://docs.python.org/[ShortVersion]/index.html">documentation</a>. At your terminal, type "py" to launch Python, or search for Python in your Start menu. - -See <a href="https://docs.python.org/[ShortVersion]/whatsnew/[ShortVersion].html">what's new</a> in this release, or find more info about <a href="https://docs.python.org/[ShortVersion]/using/windows.html">using Python on Windows</a>. - Thank you for using [WixBundleName]. - Thank you for using [WixBundleName]. - -Feel free to post at <a href="https://discuss.python.org/c/users/7">discuss.python.org</a> if you continue to encounter issues. - Thank you for using [WixBundleName]. - -Feel free to post at <a href="https://discuss.python.org/c/users/7">discuss.python.org</a> if you encountered problems. - Setup failed - One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the <a href="#">log file</a>. - You must restart your computer to complete the rollback of the software. - &Restart - Unable to install [WixBundleName] due to an existing install. Use Programs and Features to modify, repair or remove [WixBundleName]. - At least Windows 10 or Windows Server 2016 are required to install [WixBundleName] - -Visit <a href="https://www.python.org/downloads/">python.org</a> to download an earlier version of Python. - Disable path length limit - Changes your machine configuration to allow programs, including Python, to bypass the 260 character "MAX_PATH" limitation. - - NOTE: This installer is being retired and will no longer be available after Python 3.15. <a href="https://docs.python.org/using/windows">More info</a> - diff --git a/Tools/msi/bundle/SideBar.png b/Tools/msi/bundle/SideBar.png deleted file mode 100644 index 52f15dd46c18fe5af1dbb57b91bf4782d763732d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51948 zcmV*2KzF~1P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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�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 diff --git a/Tools/msi/bundle/bootstrap/LICENSE.txt b/Tools/msi/bundle/bootstrap/LICENSE.txt deleted file mode 100644 index 5791a7e81d74ff8..000000000000000 --- a/Tools/msi/bundle/bootstrap/LICENSE.txt +++ /dev/null @@ -1,25 +0,0 @@ -This license applies to the bootstrapper application that is embedded within the installer. It has no impact on the licensing for the rest of the installer or Python itself, as no code covered by this license exists in any other part of the product. - ---- - -Microsoft Reciprocal License (MS-RL) - -This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. - -1. Definitions - The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. - A "contribution" is the original software, or any additions or changes to the software. - A "contributor" is any person that distributes its contribution under this license. - "Licensed patents" are a contributor's patent claims that read directly on its contribution. - -2. Grant of Rights - (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - -3. Conditions and Limitations - (A) Reciprocal Grants- For any file you distribute that contains code from the software (in source code or binary format), you must provide recipients the source code to that file along with a copy of this license, which license will govern that file. You may license other files that are entirely your own work and do not contain code from the software under any terms you choose. - (B) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - (C) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. - (D) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. - (E) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. - (F) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. diff --git a/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp b/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp deleted file mode 100644 index 1e0df5084ff0795..000000000000000 --- a/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp +++ /dev/null @@ -1,3323 +0,0 @@ -//------------------------------------------------------------------------------------------------- -// -// Copyright (c) 2004, Outercurve Foundation. -// This software is released under Microsoft Reciprocal License (MS-RL). -// The license and further copyright text can be found in the file -// LICENSE.TXT at the root directory of the distribution. -// -//------------------------------------------------------------------------------------------------- - - -#include "pch.h" - -static const LPCWSTR PYBA_WINDOW_CLASS = L"PythonBA"; -static const DWORD PYBA_ACQUIRE_PERCENTAGE = 30; -static const LPCWSTR PYBA_VARIABLE_BUNDLE_FILE_VERSION = L"WixBundleFileVersion"; - -enum PYBA_STATE { - PYBA_STATE_INITIALIZING, - PYBA_STATE_INITIALIZED, - PYBA_STATE_HELP, - PYBA_STATE_DETECTING, - PYBA_STATE_DETECTED, - PYBA_STATE_PLANNING, - PYBA_STATE_PLANNED, - PYBA_STATE_APPLYING, - PYBA_STATE_CACHING, - PYBA_STATE_CACHED, - PYBA_STATE_EXECUTING, - PYBA_STATE_EXECUTED, - PYBA_STATE_APPLIED, - PYBA_STATE_FAILED, -}; - -static const int WM_PYBA_SHOW_HELP = WM_APP + 100; -static const int WM_PYBA_DETECT_PACKAGES = WM_APP + 101; -static const int WM_PYBA_PLAN_PACKAGES = WM_APP + 102; -static const int WM_PYBA_APPLY_PACKAGES = WM_APP + 103; -static const int WM_PYBA_CHANGE_STATE = WM_APP + 104; -static const int WM_PYBA_SHOW_FAILURE = WM_APP + 105; - -// This enum must be kept in the same order as the PAGE_NAMES array. -enum PAGE { - PAGE_LOADING, - PAGE_HELP, - PAGE_INSTALL, - PAGE_UPGRADE, - PAGE_SIMPLE_INSTALL, - PAGE_CUSTOM1, - PAGE_CUSTOM2, - PAGE_MODIFY, - PAGE_PROGRESS, - PAGE_PROGRESS_PASSIVE, - PAGE_SUCCESS, - PAGE_FAILURE, - COUNT_PAGE, -}; - -// This array must be kept in the same order as the PAGE enum. -static LPCWSTR PAGE_NAMES[] = { - L"Loading", - L"Help", - L"Install", - L"Upgrade", - L"SimpleInstall", - L"Custom1", - L"Custom2", - L"Modify", - L"Progress", - L"ProgressPassive", - L"Success", - L"Failure", -}; - -enum CONTROL_ID { - // Non-paged controls - ID_CLOSE_BUTTON = THEME_FIRST_ASSIGN_CONTROL_ID, - ID_MINIMIZE_BUTTON, - - // Welcome page - ID_INSTALL_BUTTON, - ID_INSTALL_CUSTOM_BUTTON, - ID_INSTALL_SIMPLE_BUTTON, - ID_INSTALL_UPGRADE_BUTTON, - ID_INSTALL_UPGRADE_CUSTOM_BUTTON, - ID_INSTALL_CANCEL_BUTTON, - ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, - - // Customize Page - ID_TARGETDIR_EDITBOX, - ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, - ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX, - ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, - ID_CUSTOM_INCLUDE_LAUNCHER_HELP_LABEL, - ID_CUSTOM_COMPILE_ALL_CHECKBOX, - ID_CUSTOM_BROWSE_BUTTON, - ID_CUSTOM_BROWSE_BUTTON_LABEL, - ID_CUSTOM_INSTALL_BUTTON, - ID_CUSTOM_NEXT_BUTTON, - ID_CUSTOM1_BACK_BUTTON, - ID_CUSTOM2_BACK_BUTTON, - ID_CUSTOM1_CANCEL_BUTTON, - ID_CUSTOM2_CANCEL_BUTTON, - - // Modify page - ID_MODIFY_BUTTON, - ID_REPAIR_BUTTON, - ID_UNINSTALL_BUTTON, - ID_MODIFY_CANCEL_BUTTON, - - // Progress page - ID_CACHE_PROGRESS_PACKAGE_TEXT, - ID_CACHE_PROGRESS_BAR, - ID_CACHE_PROGRESS_TEXT, - - ID_EXECUTE_PROGRESS_PACKAGE_TEXT, - ID_EXECUTE_PROGRESS_BAR, - ID_EXECUTE_PROGRESS_TEXT, - ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, - - ID_OVERALL_PROGRESS_PACKAGE_TEXT, - ID_OVERALL_PROGRESS_BAR, - ID_OVERALL_CALCULATED_PROGRESS_BAR, - ID_OVERALL_PROGRESS_TEXT, - - ID_PROGRESS_CANCEL_BUTTON, - - // Success page - ID_SUCCESS_TEXT, - ID_SUCCESS_RESTART_TEXT, - ID_SUCCESS_RESTART_BUTTON, - ID_SUCCESS_CANCEL_BUTTON, - ID_SUCCESS_MAX_PATH_BUTTON, - - // Failure page - ID_FAILURE_LOGFILE_LINK, - ID_FAILURE_MESSAGE_TEXT, - ID_FAILURE_RESTART_TEXT, - ID_FAILURE_RESTART_BUTTON, - ID_FAILURE_CANCEL_BUTTON -}; - -static THEME_ASSIGN_CONTROL_ID CONTROL_ID_NAMES[] = { - { ID_CLOSE_BUTTON, L"CloseButton" }, - { ID_MINIMIZE_BUTTON, L"MinimizeButton" }, - - { ID_INSTALL_BUTTON, L"InstallButton" }, - { ID_INSTALL_CUSTOM_BUTTON, L"InstallCustomButton" }, - { ID_INSTALL_SIMPLE_BUTTON, L"InstallSimpleButton" }, - { ID_INSTALL_UPGRADE_BUTTON, L"InstallUpgradeButton" }, - { ID_INSTALL_UPGRADE_CUSTOM_BUTTON, L"InstallUpgradeCustomButton" }, - { ID_INSTALL_CANCEL_BUTTON, L"InstallCancelButton" }, - { ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, L"InstallLauncherAllUsers" }, - - { ID_TARGETDIR_EDITBOX, L"TargetDir" }, - { ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, L"AssociateFiles" }, - { ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX, L"InstallAllUsers" }, - { ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, L"CustomInstallLauncherAllUsers" }, - { ID_CUSTOM_INCLUDE_LAUNCHER_HELP_LABEL, L"Include_launcherHelp" }, - { ID_CUSTOM_COMPILE_ALL_CHECKBOX, L"CompileAll" }, - { ID_CUSTOM_BROWSE_BUTTON, L"CustomBrowseButton" }, - { ID_CUSTOM_BROWSE_BUTTON_LABEL, L"CustomBrowseButtonLabel" }, - { ID_CUSTOM_INSTALL_BUTTON, L"CustomInstallButton" }, - { ID_CUSTOM_NEXT_BUTTON, L"CustomNextButton" }, - { ID_CUSTOM1_BACK_BUTTON, L"Custom1BackButton" }, - { ID_CUSTOM2_BACK_BUTTON, L"Custom2BackButton" }, - { ID_CUSTOM1_CANCEL_BUTTON, L"Custom1CancelButton" }, - { ID_CUSTOM2_CANCEL_BUTTON, L"Custom2CancelButton" }, - - { ID_MODIFY_BUTTON, L"ModifyButton" }, - { ID_REPAIR_BUTTON, L"RepairButton" }, - { ID_UNINSTALL_BUTTON, L"UninstallButton" }, - { ID_MODIFY_CANCEL_BUTTON, L"ModifyCancelButton" }, - - { ID_CACHE_PROGRESS_PACKAGE_TEXT, L"CacheProgressPackageText" }, - { ID_CACHE_PROGRESS_BAR, L"CacheProgressbar" }, - { ID_CACHE_PROGRESS_TEXT, L"CacheProgressText" }, - { ID_EXECUTE_PROGRESS_PACKAGE_TEXT, L"ExecuteProgressPackageText" }, - { ID_EXECUTE_PROGRESS_BAR, L"ExecuteProgressbar" }, - { ID_EXECUTE_PROGRESS_TEXT, L"ExecuteProgressText" }, - { ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, L"ExecuteProgressActionDataText" }, - { ID_OVERALL_PROGRESS_PACKAGE_TEXT, L"OverallProgressPackageText" }, - { ID_OVERALL_PROGRESS_BAR, L"OverallProgressbar" }, - { ID_OVERALL_CALCULATED_PROGRESS_BAR, L"OverallCalculatedProgressbar" }, - { ID_OVERALL_PROGRESS_TEXT, L"OverallProgressText" }, - { ID_PROGRESS_CANCEL_BUTTON, L"ProgressCancelButton" }, - - { ID_SUCCESS_TEXT, L"SuccessText" }, - { ID_SUCCESS_RESTART_TEXT, L"SuccessRestartText" }, - { ID_SUCCESS_RESTART_BUTTON, L"SuccessRestartButton" }, - { ID_SUCCESS_CANCEL_BUTTON, L"SuccessCancelButton" }, - { ID_SUCCESS_MAX_PATH_BUTTON, L"SuccessMaxPathButton" }, - - { ID_FAILURE_LOGFILE_LINK, L"FailureLogFileLink" }, - { ID_FAILURE_MESSAGE_TEXT, L"FailureMessageText" }, - { ID_FAILURE_RESTART_TEXT, L"FailureRestartText" }, - { ID_FAILURE_RESTART_BUTTON, L"FailureRestartButton" }, - { ID_FAILURE_CANCEL_BUTTON, L"FailureCancelButton" }, -}; - -static struct { LPCWSTR regName; LPCWSTR variableName; } OPTIONAL_FEATURES[] = { - { L"core_d", L"Include_debug" }, - { L"core_pdb", L"Include_symbols" }, - { L"dev", L"Include_dev" }, - { L"doc", L"Include_doc" }, - { L"exe", L"Include_exe" }, - { L"lib", L"Include_lib" }, - { L"path", L"PrependPath" }, - { L"appendpath", L"AppendPath" }, - { L"pip", L"Include_pip" }, - { L"tcltk", L"Include_tcltk" }, - { L"test", L"Include_test" }, - { L"tools", L"Include_tools" }, - { L"Shortcuts", L"Shortcuts" }, - // Include_launcher and AssociateFiles are handled separately and so do - // not need to be included in this list. - { L"freethreaded", L"Include_freethreaded" }, - { nullptr, nullptr } -}; - - - -class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication { - void ShowPage(DWORD newPageId) { - // Process each control for special handling in the new page. - ProcessPageControls(ThemeGetPage(_theme, newPageId)); - - // Enable disable controls per-page. - if (_pageIds[PAGE_INSTALL] == newPageId || - _pageIds[PAGE_SIMPLE_INSTALL] == newPageId || - _pageIds[PAGE_UPGRADE] == newPageId) { - InstallPage_Show(); - } else if (_pageIds[PAGE_CUSTOM1] == newPageId) { - Custom1Page_Show(); - } else if (_pageIds[PAGE_CUSTOM2] == newPageId) { - Custom2Page_Show(); - } else if (_pageIds[PAGE_MODIFY] == newPageId) { - ModifyPage_Show(); - } else if (_pageIds[PAGE_SUCCESS] == newPageId) { - SuccessPage_Show(); - } else if (_pageIds[PAGE_FAILURE] == newPageId) { - FailurePage_Show(); - } - - // Prevent repainting while switching page to avoid ugly flickering - _suppressPaint = TRUE; - ThemeShowPage(_theme, newPageId, SW_SHOW); - ThemeShowPage(_theme, _visiblePageId, SW_HIDE); - _suppressPaint = FALSE; - InvalidateRect(_theme->hwndParent, nullptr, TRUE); - _visiblePageId = newPageId; - - // On the install page set the focus to the install button or - // the next enabled control if install is disabled - if (_pageIds[PAGE_INSTALL] == newPageId) { - ThemeSetFocus(_theme, ID_INSTALL_BUTTON); - } else if (_pageIds[PAGE_SIMPLE_INSTALL] == newPageId) { - ThemeSetFocus(_theme, ID_INSTALL_SIMPLE_BUTTON); - } - } - - // - // Handles control clicks - // - void OnCommand(CONTROL_ID id) { - LPWSTR defaultDir = nullptr; - LPWSTR targetDir = nullptr; - LONGLONG elevated, crtInstalled, installAllUsers; - BOOL checked, launcherChecked; - WCHAR wzPath[MAX_PATH] = { }; - BROWSEINFOW browseInfo = { }; - PIDLIST_ABSOLUTE pidl = nullptr; - DWORD pageId; - HRESULT hr = S_OK; - - switch(id) { - case ID_CLOSE_BUTTON: - OnClickCloseButton(); - break; - - // Install commands - case ID_INSTALL_SIMPLE_BUTTON: __fallthrough; - case ID_INSTALL_UPGRADE_BUTTON: __fallthrough; - case ID_INSTALL_BUTTON: - SavePageSettings(); - - hr = BalGetNumericVariable(L"InstallAllUsers", &installAllUsers); - ExitOnFailure(hr, L"Failed to get install scope"); - - hr = _engine->SetVariableNumeric(L"CompileAll", installAllUsers); - ExitOnFailure(hr, L"Failed to update CompileAll"); - - hr = EnsureTargetDir(); - ExitOnFailure(hr, L"Failed to set TargetDir"); - - OnPlan(BOOTSTRAPPER_ACTION_INSTALL); - break; - - case ID_CUSTOM1_BACK_BUTTON: - SavePageSettings(); - if (_modifying) { - GoToPage(PAGE_MODIFY); - } else if (_upgrading) { - GoToPage(PAGE_UPGRADE); - } else { - GoToPage(PAGE_INSTALL); - } - break; - - case ID_INSTALL_CUSTOM_BUTTON: __fallthrough; - case ID_INSTALL_UPGRADE_CUSTOM_BUTTON: __fallthrough; - case ID_CUSTOM2_BACK_BUTTON: - SavePageSettings(); - GoToPage(PAGE_CUSTOM1); - break; - - case ID_CUSTOM_NEXT_BUTTON: - SavePageSettings(); - GoToPage(PAGE_CUSTOM2); - break; - - case ID_CUSTOM_INSTALL_BUTTON: - SavePageSettings(); - - hr = EnsureTargetDir(); - ExitOnFailure(hr, L"Failed to set TargetDir"); - - hr = BalGetStringVariable(L"TargetDir", &targetDir); - if (SUCCEEDED(hr)) { - // TODO: Check whether directory exists and contains another installation - ReleaseStr(targetDir); - } - - OnPlan(_command.action); - break; - - case ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX: - checked = ThemeIsControlChecked(_theme, ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX); - _engine->SetVariableNumeric(L"InstallLauncherAllUsers", checked); - - ThemeControlElevates(_theme, ID_INSTALL_BUTTON, WillElevate()); - break; - - case ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX: - checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX); - _engine->SetVariableNumeric(L"InstallLauncherAllUsers", checked); - - ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, WillElevate()); - break; - - case ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX: - checked = ThemeIsControlChecked(_theme, ID_CUSTOM_INSTALL_ALL_USERS_CHECKBOX); - _engine->SetVariableNumeric(L"InstallAllUsers", checked); - - ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, WillElevate()); - ThemeControlEnable(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, !checked); - if (checked) { - _engine->SetVariableNumeric(L"CompileAll", 1); - ThemeSendControlMessage(_theme, ID_CUSTOM_COMPILE_ALL_CHECKBOX, BM_SETCHECK, BST_CHECKED, 0); - } - ThemeGetTextControl(_theme, ID_TARGETDIR_EDITBOX, &targetDir); - if (targetDir) { - // Check the current value against the default to see - // if we should switch it automatically. - hr = BalGetStringVariable( - checked ? L"DefaultJustForMeTargetDir" : L"DefaultAllUsersTargetDir", - &defaultDir - ); - - if (SUCCEEDED(hr) && defaultDir) { - LPWSTR formatted = nullptr; - if (defaultDir[0] && SUCCEEDED(BalFormatString(defaultDir, &formatted))) { - if (wcscmp(formatted, targetDir) == 0) { - ReleaseStr(defaultDir); - defaultDir = nullptr; - ReleaseStr(formatted); - formatted = nullptr; - - hr = BalGetStringVariable( - checked ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir", - &defaultDir - ); - if (SUCCEEDED(hr) && defaultDir && defaultDir[0] && SUCCEEDED(BalFormatString(defaultDir, &formatted))) { - ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, formatted); - ReleaseStr(formatted); - } - } else { - ReleaseStr(formatted); - } - } - - ReleaseStr(defaultDir); - } - } - break; - - case ID_CUSTOM_BROWSE_BUTTON: - browseInfo.hwndOwner = _hWnd; - browseInfo.pszDisplayName = wzPath; - browseInfo.lpszTitle = _theme->sczCaption; - browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI; - pidl = ::SHBrowseForFolderW(&browseInfo); - if (pidl && ::SHGetPathFromIDListW(pidl, wzPath)) { - ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, wzPath); - } - - if (pidl) { - ::CoTaskMemFree(pidl); - } - break; - - // Modify commands - case ID_MODIFY_BUTTON: - // Some variables cannot be modified - _engine->SetVariableString(L"InstallAllUsersState", L"disable"); - _engine->SetVariableString(L"InstallLauncherAllUsersState", L"disable"); - _engine->SetVariableString(L"TargetDirState", L"disable"); - _engine->SetVariableString(L"CustomBrowseButtonState", L"disable"); - _modifying = TRUE; - GoToPage(PAGE_CUSTOM1); - break; - - case ID_REPAIR_BUTTON: - OnPlan(BOOTSTRAPPER_ACTION_REPAIR); - break; - - case ID_UNINSTALL_BUTTON: - OnPlan(BOOTSTRAPPER_ACTION_UNINSTALL); - break; - - case ID_SUCCESS_MAX_PATH_BUTTON: - EnableMaxPathSupport(); - ThemeControlEnable(_theme, ID_SUCCESS_MAX_PATH_BUTTON, FALSE); - break; - } - - LExit: - return; - } - - void InstallPage_Show() { - // Ensure the All Users install button has a UAC shield - BOOL elevated = WillElevate(); - ThemeControlElevates(_theme, ID_INSTALL_BUTTON, elevated); - ThemeControlElevates(_theme, ID_INSTALL_SIMPLE_BUTTON, elevated); - ThemeControlElevates(_theme, ID_INSTALL_UPGRADE_BUTTON, elevated); - - LONGLONG blockedLauncher; - if (SUCCEEDED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher)) && blockedLauncher) { - LOC_STRING *pLocString = nullptr; - if (SUCCEEDED(LocGetString(_wixLoc, L"#(loc.ShortInstallLauncherBlockedLabel)", &pLocString)) && pLocString) { - ThemeSetTextControl(_theme, ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, pLocString->wzText); - } - } - } - - void Custom1Page_Show() { - LONGLONG installLauncherAllUsers; - - if (FAILED(BalGetNumericVariable(L"InstallLauncherAllUsers", &installLauncherAllUsers))) { - installLauncherAllUsers = 0; - } - - ThemeSendControlMessage(_theme, ID_CUSTOM_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, BM_SETCHECK, - installLauncherAllUsers ? BST_CHECKED : BST_UNCHECKED, 0); - - LOC_STRING *pLocString = nullptr; - LPCWSTR locKey = L"#(loc.Include_launcherHelp)"; - LONGLONG blockedLauncher; - - if (SUCCEEDED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher)) && blockedLauncher) { - locKey = L"#(loc.Include_launcherRemove)"; - } else if (SUCCEEDED(BalGetNumericVariable(L"DetectedOldLauncher", &blockedLauncher)) && blockedLauncher) { - locKey = L"#(loc.Include_launcherUpgrade)"; - } - - if (SUCCEEDED(LocGetString(_wixLoc, locKey, &pLocString)) && pLocString) { - ThemeSetTextControl(_theme, ID_CUSTOM_INCLUDE_LAUNCHER_HELP_LABEL, pLocString->wzText); - } - } - - void Custom2Page_Show() { - HRESULT hr; - LONGLONG installAll, includeLauncher; - - if (FAILED(BalGetNumericVariable(L"InstallAllUsers", &installAll))) { - installAll = 0; - } - - if (WillElevate()) { - ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, TRUE); - ThemeShowControl(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, SW_HIDE); - } else { - ThemeControlElevates(_theme, ID_CUSTOM_INSTALL_BUTTON, FALSE); - ThemeShowControl(_theme, ID_CUSTOM_BROWSE_BUTTON_LABEL, SW_SHOW); - } - - if (SUCCEEDED(BalGetNumericVariable(L"Include_launcher", &includeLauncher)) && includeLauncher) { - ThemeControlEnable(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, TRUE); - } else { - ThemeSendControlMessage(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, BM_SETCHECK, BST_UNCHECKED, 0); - ThemeControlEnable(_theme, ID_CUSTOM_ASSOCIATE_FILES_CHECKBOX, FALSE); - } - - LPWSTR targetDir = nullptr; - hr = BalGetStringVariable(L"TargetDir", &targetDir); - if (SUCCEEDED(hr) && targetDir && targetDir[0]) { - ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, targetDir); - StrFree(targetDir); - } else if (SUCCEEDED(hr)) { - StrFree(targetDir); - targetDir = nullptr; - - LPWSTR defaultTargetDir = nullptr; - hr = BalGetStringVariable(L"DefaultCustomTargetDir", &defaultTargetDir); - if (SUCCEEDED(hr) && defaultTargetDir && !defaultTargetDir[0]) { - StrFree(defaultTargetDir); - defaultTargetDir = nullptr; - - hr = BalGetStringVariable( - installAll ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir", - &defaultTargetDir - ); - } - if (SUCCEEDED(hr) && defaultTargetDir) { - if (defaultTargetDir[0] && SUCCEEDED(BalFormatString(defaultTargetDir, &targetDir))) { - ThemeSetTextControl(_theme, ID_TARGETDIR_EDITBOX, targetDir); - StrFree(targetDir); - } - StrFree(defaultTargetDir); - } - } - } - - void ModifyPage_Show() { - ThemeControlEnable(_theme, ID_REPAIR_BUTTON, !_suppressRepair); - } - - void SuccessPage_Show() { - // on the "Success" page, check if the restart button should be enabled. - BOOL showRestartButton = FALSE; - LOC_STRING *successText = nullptr; - HRESULT hr = S_OK; - - if (_restartRequired) { - if (BOOTSTRAPPER_RESTART_PROMPT == _command.restart) { - showRestartButton = TRUE; - } - } - - switch (_plannedAction) { - case BOOTSTRAPPER_ACTION_INSTALL: - hr = LocGetString(_wixLoc, L"#(loc.SuccessInstallMessage)", &successText); - break; - case BOOTSTRAPPER_ACTION_MODIFY: - hr = LocGetString(_wixLoc, L"#(loc.SuccessModifyMessage)", &successText); - break; - case BOOTSTRAPPER_ACTION_REPAIR: - hr = LocGetString(_wixLoc, L"#(loc.SuccessRepairMessage)", &successText); - break; - case BOOTSTRAPPER_ACTION_UNINSTALL: - hr = LocGetString(_wixLoc, L"#(loc.SuccessRemoveMessage)", &successText); - break; - } - - if (successText) { - LPWSTR formattedString = nullptr; - BalFormatString(successText->wzText, &formattedString); - if (formattedString) { - ThemeSetTextControl(_theme, ID_SUCCESS_TEXT, formattedString); - StrFree(formattedString); - } - } - - ThemeControlEnable(_theme, ID_SUCCESS_RESTART_TEXT, showRestartButton); - ThemeControlEnable(_theme, ID_SUCCESS_RESTART_BUTTON, showRestartButton); - - if (_command.action != BOOTSTRAPPER_ACTION_INSTALL || - !IsWindowsVersionOrGreater(10, 0, 0)) { - ThemeControlEnable(_theme, ID_SUCCESS_MAX_PATH_BUTTON, FALSE); - } else { - DWORD dataType = 0, buffer = 0, bufferLen = sizeof(buffer); - HKEY hKey; - LRESULT res = RegOpenKeyExW( - HKEY_LOCAL_MACHINE, - L"SYSTEM\\CurrentControlSet\\Control\\FileSystem", - 0, - KEY_READ, - &hKey - ); - if (res == ERROR_SUCCESS) { - res = RegQueryValueExW(hKey, L"LongPathsEnabled", nullptr, &dataType, - (LPBYTE)&buffer, &bufferLen); - RegCloseKey(hKey); - } - else { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Failed to open SYSTEM\\CurrentControlSet\\Control\\FileSystem: error code %d", res); - } - if (res == ERROR_SUCCESS && dataType == REG_DWORD && buffer == 0) { - ThemeControlElevates(_theme, ID_SUCCESS_MAX_PATH_BUTTON, TRUE); - } - else { - if (res == ERROR_SUCCESS) - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Failed to read LongPathsEnabled value: error code %d", res); - else - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Hiding MAX_PATH button because it is already enabled"); - ThemeControlEnable(_theme, ID_SUCCESS_MAX_PATH_BUTTON, FALSE); - } - } - } - - void FailurePage_Show() { - // on the "Failure" page, show error message and check if the restart button should be enabled. - - // if there is a log file variable then we'll assume the log file exists. - BOOL showLogLink = (_bundle.sczLogVariable && *_bundle.sczLogVariable); - BOOL showErrorMessage = FALSE; - BOOL showRestartButton = FALSE; - - if (FAILED(_hrFinal)) { - LPWSTR unformattedText = nullptr; - LPWSTR text = nullptr; - - // If we know the failure message, use that. - if (_failedMessage && *_failedMessage) { - StrAllocString(&unformattedText, _failedMessage, 0); - } else { - // try to get the error message from the error code. - StrAllocFromError(&unformattedText, _hrFinal, nullptr); - if (!unformattedText || !*unformattedText) { - StrAllocFromError(&unformattedText, E_FAIL, nullptr); - } - } - - if (E_WIXSTDBA_CONDITION_FAILED == _hrFinal) { - if (unformattedText) { - StrAllocString(&text, unformattedText, 0); - } - } else { - StrAllocFormatted(&text, L"0x%08x - %ls", _hrFinal, unformattedText); - } - - if (text) { - ThemeSetTextControl(_theme, ID_FAILURE_MESSAGE_TEXT, text); - showErrorMessage = TRUE; - } - - ReleaseStr(text); - ReleaseStr(unformattedText); - } - - if (_restartRequired && BOOTSTRAPPER_RESTART_PROMPT == _command.restart) { - showRestartButton = TRUE; - } - - ThemeControlEnable(_theme, ID_FAILURE_LOGFILE_LINK, showLogLink); - ThemeControlEnable(_theme, ID_FAILURE_MESSAGE_TEXT, showErrorMessage); - ThemeControlEnable(_theme, ID_FAILURE_RESTART_TEXT, showRestartButton); - ThemeControlEnable(_theme, ID_FAILURE_RESTART_BUTTON, showRestartButton); - } - - static void EnableMaxPathSupport() { - LPWSTR targetDir = nullptr, defaultDir = nullptr; - HRESULT hr = BalGetStringVariable(L"TargetDir", &targetDir); - if (FAILED(hr) || !targetDir || !targetDir[0]) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to get TargetDir"); - return; - } - - LPWSTR pythonw = nullptr; - StrAllocFormatted(&pythonw, L"%ls\\pythonw.exe", targetDir); - if (!pythonw || !pythonw[0]) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to construct pythonw.exe path"); - return; - } - - LPCWSTR arguments = L"-c \"import winreg; " - "winreg.SetValueEx(" - "winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, " - "r'SYSTEM\\CurrentControlSet\\Control\\FileSystem'), " - "'LongPathsEnabled', " - "None, " - "winreg.REG_DWORD, " - "1" - ")\""; - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Executing %ls %ls", pythonw, arguments); - HINSTANCE res = ShellExecuteW(0, L"runas", pythonw, arguments, NULL, SW_HIDE); - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "return code 0x%08x", res); - } - -public: // IBootstrapperApplication - virtual STDMETHODIMP OnStartup() { - HRESULT hr = S_OK; - DWORD dwUIThreadId = 0; - - // create UI thread - _hUiThread = ::CreateThread(nullptr, 0, UiThreadProc, this, 0, &dwUIThreadId); - if (!_hUiThread) { - ExitWithLastError(hr, "Failed to create UI thread."); - } - - LExit: - return hr; - } - - - virtual STDMETHODIMP_(int) OnShutdown() { - int nResult = IDNOACTION; - - // wait for UI thread to terminate - if (_hUiThread) { - ::WaitForSingleObject(_hUiThread, INFINITE); - ReleaseHandle(_hUiThread); - } - - // If a restart was required. - if (_restartRequired && _allowRestart) { - nResult = IDRESTART; - } - - return nResult; - } - - virtual STDMETHODIMP_(int) OnDetectRelatedMsiPackage( - __in_z LPCWSTR wzPackageId, - __in_z LPCWSTR /*wzProductCode*/, - __in BOOL fPerMachine, - __in DWORD64 /*dw64Version*/, - __in BOOTSTRAPPER_RELATED_OPERATION operation - ) { - // Only check launcher_AllUsers because we'll find the same packages - // twice if we check launcher_JustForMe as well. - if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_AllUsers", -1)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Detected existing launcher install"); - - LONGLONG blockedLauncher, detectedLauncher; - if (FAILED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher))) { - blockedLauncher = 0; - } - - // Get the prior DetectedLauncher value so we can see if we've - // detected more than one, and then update the stored variable - // (we use the original value later on via the local). - if (FAILED(BalGetNumericVariable(L"DetectedLauncher", &detectedLauncher))) { - detectedLauncher = 0; - } - if (!detectedLauncher) { - _engine->SetVariableNumeric(L"DetectedLauncher", 1); - } - - if (blockedLauncher) { - // Nothing else to do, we're already blocking - } - else if (BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE == operation) { - // Found a higher version, so we can't install ours. - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Higher version launcher has been detected."); - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Launcher will not be installed"); - _engine->SetVariableNumeric(L"BlockedLauncher", 1); - } - else if (detectedLauncher) { - if (!blockedLauncher) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Multiple launcher installs have been detected."); - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "No launcher will be installed or upgraded until one has been removed."); - _engine->SetVariableNumeric(L"BlockedLauncher", 1); - } - } - else if (BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE == operation) { - // Found an older version, so let's run the equivalent as an upgrade - // This overrides "unknown" all users options, but will leave alone - // any that have already been set/detected. - // User can deselect the option to include the launcher, but cannot - // change it from the current per user/machine setting. - LONGLONG includeLauncher, includeLauncherAllUsers; - if (FAILED(BalGetNumericVariable(L"Include_launcher", &includeLauncher))) { - includeLauncher = -1; - } - if (FAILED(BalGetNumericVariable(L"InstallLauncherAllUsers", &includeLauncherAllUsers))) { - includeLauncherAllUsers = -1; - } - - if (includeLauncher < 0) { - _engine->SetVariableNumeric(L"Include_launcher", 1); - } - if (includeLauncherAllUsers < 0) { - _engine->SetVariableNumeric(L"InstallLauncherAllUsers", fPerMachine ? 1 : 0); - } else if (includeLauncherAllUsers != fPerMachine ? 1 : 0) { - // Requested AllUsers option is inconsistent, so block - _engine->SetVariableNumeric(L"BlockedLauncher", 1); - } - _engine->SetVariableNumeric(L"DetectedOldLauncher", 1); - } - } - return CheckCanceled() ? IDCANCEL : IDNOACTION; - } - - virtual STDMETHODIMP_(int) OnDetectRelatedBundle( - __in LPCWSTR wzBundleId, - __in BOOTSTRAPPER_RELATION_TYPE relationType, - __in LPCWSTR /*wzBundleTag*/, - __in BOOL fPerMachine, - __in DWORD64 /*dw64Version*/, - __in BOOTSTRAPPER_RELATED_OPERATION operation - ) { - BalInfoAddRelatedBundleAsPackage(&_bundle.packages, wzBundleId, relationType, fPerMachine); - - // Remember when our bundle would cause a downgrade. - if (BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE == operation) { - _downgradingOtherVersion = TRUE; - } else if (BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE == operation) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Detected previous version - planning upgrade"); - _upgrading = TRUE; - - LoadOptionalFeatureStates(_engine); - } else if (BOOTSTRAPPER_RELATED_OPERATION_NONE == operation) { - if (_command.action == BOOTSTRAPPER_ACTION_INSTALL) { - LOC_STRING *pLocString = nullptr; - if (SUCCEEDED(LocGetString(_wixLoc, L"#(loc.FailureExistingInstall)", &pLocString)) && pLocString) { - BalFormatString(pLocString->wzText, &_failedMessage); - } else { - BalFormatString(L"Cannot install [WixBundleName] because it is already installed.", &_failedMessage); - } - BalLog( - BOOTSTRAPPER_LOG_LEVEL_ERROR, - "Related bundle %ls is preventing install", - wzBundleId - ); - SetState(PYBA_STATE_FAILED, E_WIXSTDBA_CONDITION_FAILED); - } - } - - return CheckCanceled() ? IDCANCEL : IDOK; - } - - - virtual STDMETHODIMP_(void) OnDetectPackageComplete( - __in LPCWSTR wzPackageId, - __in HRESULT hrStatus, - __in BOOTSTRAPPER_PACKAGE_STATE state - ) { } - - - virtual STDMETHODIMP_(void) OnDetectComplete(__in HRESULT hrStatus) { - if (SUCCEEDED(hrStatus) && _baFunction) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running detect complete BA function"); - _baFunction->OnDetectComplete(); - } - - if (SUCCEEDED(hrStatus)) { - // Update launcher install states - // If we didn't detect any existing installs, Include_launcher and - // InstallLauncherAllUsers will both be -1, so we will set to their - // defaults and leave the options enabled. - // Otherwise, if we detected an existing install, we disable the - // options so they remain fixed. - // The code in OnDetectRelatedMsiPackage is responsible for figuring - // out whether existing installs are compatible with the settings in - // place during detection. - LONGLONG blockedLauncher; - if (SUCCEEDED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher)) - && blockedLauncher) { - _engine->SetVariableNumeric(L"Include_launcher", 0); - _engine->SetVariableNumeric(L"InstallLauncherAllUsers", 0); - _engine->SetVariableString(L"InstallLauncherAllUsersState", L"disable"); - _engine->SetVariableString(L"Include_launcherState", L"disable"); - } - else { - LONGLONG includeLauncher, includeLauncherAllUsers, associateFiles; - - if (FAILED(BalGetNumericVariable(L"Include_launcher", &includeLauncher))) { - includeLauncher = -1; - } - if (FAILED(BalGetNumericVariable(L"InstallLauncherAllUsers", &includeLauncherAllUsers))) { - includeLauncherAllUsers = -1; - } - if (FAILED(BalGetNumericVariable(L"AssociateFiles", &associateFiles))) { - associateFiles = -1; - } - - if (includeLauncherAllUsers < 0) { - includeLauncherAllUsers = 0; - _engine->SetVariableNumeric(L"InstallLauncherAllUsers", includeLauncherAllUsers); - } - - if (includeLauncher < 0) { - if (BOOTSTRAPPER_ACTION_LAYOUT == _command.action || - (BOOTSTRAPPER_ACTION_INSTALL == _command.action && !_upgrading)) { - // When installing/downloading, we include the launcher - // (though downloads should ignore this setting anyway) - _engine->SetVariableNumeric(L"Include_launcher", 1); - } else { - // Any other action, we should have detected an existing - // install (e.g. on remove/modify), so if we didn't, we - // assume it's not selected. - _engine->SetVariableNumeric(L"Include_launcher", 0); - _engine->SetVariableNumeric(L"AssociateFiles", 0); - } - } - - if (associateFiles < 0) { - auto hr = LoadAssociateFilesStateFromKey( - _engine, - includeLauncherAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER - ); - if (FAILED(hr)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to load AssociateFiles state: error code 0x%08X", hr); - } else if (hr == S_OK) { - associateFiles = 1; - } - _engine->SetVariableNumeric(L"AssociateFiles", associateFiles); - } - } - } - - if (SUCCEEDED(hrStatus)) { - hrStatus = EvaluateConditions(); - } - - if (SUCCEEDED(hrStatus)) { - // Ensure the default path has been set - hrStatus = EnsureTargetDir(); - } - - SetState(PYBA_STATE_DETECTED, hrStatus); - - // If we're not interacting with the user or we're doing a layout or we're just after a force restart - // then automatically start planning. - if (BOOTSTRAPPER_DISPLAY_FULL > _command.display || - BOOTSTRAPPER_ACTION_LAYOUT == _command.action || - BOOTSTRAPPER_ACTION_UNINSTALL == _command.action || - BOOTSTRAPPER_RESUME_TYPE_REBOOT == _command.resumeType) { - if (SUCCEEDED(hrStatus)) { - ::PostMessageW(_hWnd, WM_PYBA_PLAN_PACKAGES, 0, _command.action); - } - } - } - - - virtual STDMETHODIMP_(int) OnPlanRelatedBundle( - __in_z LPCWSTR /*wzBundleId*/, - __inout_z BOOTSTRAPPER_REQUEST_STATE* pRequestedState - ) { - return CheckCanceled() ? IDCANCEL : IDOK; - } - - - virtual STDMETHODIMP_(int) OnPlanPackageBegin( - __in_z LPCWSTR wzPackageId, - __inout BOOTSTRAPPER_REQUEST_STATE *pRequestState - ) { - HRESULT hr = S_OK; - BAL_INFO_PACKAGE* pPackage = nullptr; - - if (_nextPackageAfterRestart) { - // After restart we need to finish the dependency registration for our package so allow the package - // to go present. - if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, _nextPackageAfterRestart, -1)) { - // Do not allow a repair because that could put us in a perpetual restart loop. - if (BOOTSTRAPPER_REQUEST_STATE_REPAIR == *pRequestState) { - *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT; - } - - ReleaseNullStr(_nextPackageAfterRestart); // no more skipping now. - } else { - // not the matching package, so skip it. - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Skipping package: %ls, after restart because it was applied before the restart.", wzPackageId); - - *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; - } - } else if ((_plannedAction == BOOTSTRAPPER_ACTION_INSTALL || _plannedAction == BOOTSTRAPPER_ACTION_MODIFY) && - SUCCEEDED(BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage))) { - BOOL f = FALSE; - if (SUCCEEDED(_engine->EvaluateCondition(pPackage->sczInstallCondition, &f)) && f) { - *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT; - } - } - - return CheckCanceled() ? IDCANCEL : IDOK; - } - - virtual STDMETHODIMP_(int) OnPlanMsiFeature( - __in_z LPCWSTR wzPackageId, - __in_z LPCWSTR wzFeatureId, - __inout BOOTSTRAPPER_FEATURE_STATE* pRequestedState - ) { - LONGLONG install; - - if (wcscmp(wzFeatureId, L"AssociateFiles") == 0 || wcscmp(wzFeatureId, L"Shortcuts") == 0) { - if (SUCCEEDED(_engine->GetVariableNumeric(wzFeatureId, &install)) && install) { - *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_LOCAL; - } else { - *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_ABSENT; - } - } else { - *pRequestedState = BOOTSTRAPPER_FEATURE_STATE_LOCAL; - } - return CheckCanceled() ? IDCANCEL : IDNOACTION; - } - - virtual STDMETHODIMP_(void) OnPlanComplete(__in HRESULT hrStatus) { - if (SUCCEEDED(hrStatus) && _baFunction) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running plan complete BA function"); - _baFunction->OnPlanComplete(); - } - - SetState(PYBA_STATE_PLANNED, hrStatus); - - if (SUCCEEDED(hrStatus)) { - ::PostMessageW(_hWnd, WM_PYBA_APPLY_PACKAGES, 0, 0); - } - - _startedExecution = FALSE; - _calculatedCacheProgress = 0; - _calculatedExecuteProgress = 0; - } - - - virtual STDMETHODIMP_(int) OnCachePackageBegin( - __in_z LPCWSTR wzPackageId, - __in DWORD cCachePayloads, - __in DWORD64 dw64PackageCacheSize - ) { - if (wzPackageId && *wzPackageId) { - BAL_INFO_PACKAGE* pPackage = nullptr; - HRESULT hr = BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage); - LPCWSTR wz = (SUCCEEDED(hr) && pPackage->sczDisplayName) ? pPackage->sczDisplayName : wzPackageId; - - ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_PACKAGE_TEXT, wz); - - // If something started executing, leave it in the overall progress text. - if (!_startedExecution) { - ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, wz); - } - } - - return __super::OnCachePackageBegin(wzPackageId, cCachePayloads, dw64PackageCacheSize); - } - - - virtual STDMETHODIMP_(int) OnCacheAcquireProgress( - __in_z LPCWSTR wzPackageOrContainerId, - __in_z_opt LPCWSTR wzPayloadId, - __in DWORD64 dw64Progress, - __in DWORD64 dw64Total, - __in DWORD dwOverallPercentage - ) { - WCHAR wzProgress[5] = { }; - -#ifdef DEBUG - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnCacheAcquireProgress() - container/package: %ls, payload: %ls, progress: %I64u, total: %I64u, overall progress: %u%%", wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage); -#endif - - ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallPercentage); - ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_TEXT, wzProgress); - - ThemeSetProgressControl(_theme, ID_CACHE_PROGRESS_BAR, dwOverallPercentage); - - _calculatedCacheProgress = dwOverallPercentage * PYBA_ACQUIRE_PERCENTAGE / 100; - ThemeSetProgressControl(_theme, ID_OVERALL_CALCULATED_PROGRESS_BAR, _calculatedCacheProgress + _calculatedExecuteProgress); - - SetTaskbarButtonProgress(_calculatedCacheProgress + _calculatedExecuteProgress); - - return __super::OnCacheAcquireProgress(wzPackageOrContainerId, wzPayloadId, dw64Progress, dw64Total, dwOverallPercentage); - } - - - virtual STDMETHODIMP_(int) OnCacheAcquireComplete( - __in_z LPCWSTR wzPackageOrContainerId, - __in_z_opt LPCWSTR wzPayloadId, - __in HRESULT hrStatus, - __in int nRecommendation - ) { - SetProgressState(hrStatus); - return __super::OnCacheAcquireComplete(wzPackageOrContainerId, wzPayloadId, hrStatus, nRecommendation); - } - - - virtual STDMETHODIMP_(int) OnCacheVerifyComplete( - __in_z LPCWSTR wzPackageId, - __in_z LPCWSTR wzPayloadId, - __in HRESULT hrStatus, - __in int nRecommendation - ) { - SetProgressState(hrStatus); - return __super::OnCacheVerifyComplete(wzPackageId, wzPayloadId, hrStatus, nRecommendation); - } - - - virtual STDMETHODIMP_(void) OnCacheComplete(__in HRESULT /*hrStatus*/) { - ThemeSetTextControl(_theme, ID_CACHE_PROGRESS_PACKAGE_TEXT, L""); - SetState(PYBA_STATE_CACHED, S_OK); // we always return success here and let OnApplyComplete() deal with the error. - } - - - virtual STDMETHODIMP_(int) OnError( - __in BOOTSTRAPPER_ERROR_TYPE errorType, - __in LPCWSTR wzPackageId, - __in DWORD dwCode, - __in_z LPCWSTR wzError, - __in DWORD dwUIHint, - __in DWORD /*cData*/, - __in_ecount_z_opt(cData) LPCWSTR* /*rgwzData*/, - __in int nRecommendation - ) { - int nResult = nRecommendation; - LPWSTR sczError = nullptr; - - if (BOOTSTRAPPER_DISPLAY_EMBEDDED == _command.display) { - HRESULT hr = _engine->SendEmbeddedError(dwCode, wzError, dwUIHint, &nResult); - if (FAILED(hr)) { - nResult = IDERROR; - } - } else if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) { - // If this is an authentication failure, let the engine try to handle it for us. - if (BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_SERVER == errorType || BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_PROXY == errorType) { - nResult = IDTRYAGAIN; - } else // show a generic error message box. - { - BalRetryErrorOccurred(wzPackageId, dwCode); - - if (!_showingInternalUIThisPackage) { - // If no error message was provided, use the error code to try and get an error message. - if (!wzError || !*wzError || BOOTSTRAPPER_ERROR_TYPE_WINDOWS_INSTALLER != errorType) { - HRESULT hr = StrAllocFromError(&sczError, dwCode, nullptr); - if (FAILED(hr) || !sczError || !*sczError) { - StrAllocFormatted(&sczError, L"0x%x", dwCode); - } - } - - nResult = ::MessageBoxW(_hWnd, sczError ? sczError : wzError, _theme->sczCaption, dwUIHint); - } - } - - SetProgressState(HRESULT_FROM_WIN32(dwCode)); - } else { - // just take note of the error code and let things continue. - BalRetryErrorOccurred(wzPackageId, dwCode); - } - - ReleaseStr(sczError); - return nResult; - } - - - virtual STDMETHODIMP_(int) OnExecuteMsiMessage( - __in_z LPCWSTR wzPackageId, - __in INSTALLMESSAGE mt, - __in UINT uiFlags, - __in_z LPCWSTR wzMessage, - __in DWORD cData, - __in_ecount_z_opt(cData) LPCWSTR* rgwzData, - __in int nRecommendation - ) { -#ifdef DEBUG - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnExecuteMsiMessage() - package: %ls, message: %ls", wzPackageId, wzMessage); -#endif - if (BOOTSTRAPPER_DISPLAY_FULL == _command.display && (INSTALLMESSAGE_WARNING == mt || INSTALLMESSAGE_USER == mt)) { - int nResult = ::MessageBoxW(_hWnd, wzMessage, _theme->sczCaption, uiFlags); - return nResult; - } - - if (INSTALLMESSAGE_ACTIONSTART == mt) { - ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, wzMessage); - } - - return __super::OnExecuteMsiMessage(wzPackageId, mt, uiFlags, wzMessage, cData, rgwzData, nRecommendation); - } - - - virtual STDMETHODIMP_(int) OnProgress(__in DWORD dwProgressPercentage, __in DWORD dwOverallProgressPercentage) { - WCHAR wzProgress[5] = { }; - -#ifdef DEBUG - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnProgress() - progress: %u%%, overall progress: %u%%", dwProgressPercentage, dwOverallProgressPercentage); -#endif - - ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage); - ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_TEXT, wzProgress); - - ThemeSetProgressControl(_theme, ID_OVERALL_PROGRESS_BAR, dwOverallProgressPercentage); - SetTaskbarButtonProgress(dwOverallProgressPercentage); - - return __super::OnProgress(dwProgressPercentage, dwOverallProgressPercentage); - } - - - virtual STDMETHODIMP_(int) OnExecutePackageBegin(__in_z LPCWSTR wzPackageId, __in BOOL fExecute) { - LPWSTR sczFormattedString = nullptr; - - _startedExecution = TRUE; - - if (wzPackageId && *wzPackageId) { - BAL_INFO_PACKAGE* pPackage = nullptr; - BalInfoFindPackageById(&_bundle.packages, wzPackageId, &pPackage); - - LPCWSTR wz = wzPackageId; - if (pPackage) { - LOC_STRING* pLocString = nullptr; - - switch (pPackage->type) { - case BAL_INFO_PACKAGE_TYPE_BUNDLE_ADDON: - LocGetString(_wixLoc, L"#(loc.ExecuteAddonRelatedBundleMessage)", &pLocString); - break; - - case BAL_INFO_PACKAGE_TYPE_BUNDLE_PATCH: - LocGetString(_wixLoc, L"#(loc.ExecutePatchRelatedBundleMessage)", &pLocString); - break; - - case BAL_INFO_PACKAGE_TYPE_BUNDLE_UPGRADE: - LocGetString(_wixLoc, L"#(loc.ExecuteUpgradeRelatedBundleMessage)", &pLocString); - break; - } - - if (pLocString) { - // If the wix developer is showing a hidden variable in the UI, then obviously they don't care about keeping it safe - // so don't go down the rabbit hole of making sure that this is securely freed. - BalFormatString(pLocString->wzText, &sczFormattedString); - } - - wz = sczFormattedString ? sczFormattedString : pPackage->sczDisplayName ? pPackage->sczDisplayName : wzPackageId; - } - - _showingInternalUIThisPackage = pPackage && pPackage->fDisplayInternalUI; - - ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, wz); - ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, wz); - } else { - _showingInternalUIThisPackage = FALSE; - } - - ReleaseStr(sczFormattedString); - return __super::OnExecutePackageBegin(wzPackageId, fExecute); - } - - - virtual int __stdcall OnExecuteProgress( - __in_z LPCWSTR wzPackageId, - __in DWORD dwProgressPercentage, - __in DWORD dwOverallProgressPercentage - ) { - WCHAR wzProgress[8] = { }; - -#ifdef DEBUG - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: OnExecuteProgress() - package: %ls, progress: %u%%, overall progress: %u%%", wzPackageId, dwProgressPercentage, dwOverallProgressPercentage); -#endif - - ::StringCchPrintfW(wzProgress, countof(wzProgress), L"%u%%", dwOverallProgressPercentage); - ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_TEXT, wzProgress); - - ThemeSetProgressControl(_theme, ID_EXECUTE_PROGRESS_BAR, dwOverallProgressPercentage); - - _calculatedExecuteProgress = dwOverallProgressPercentage * (100 - PYBA_ACQUIRE_PERCENTAGE) / 100; - ThemeSetProgressControl(_theme, ID_OVERALL_CALCULATED_PROGRESS_BAR, _calculatedCacheProgress + _calculatedExecuteProgress); - - SetTaskbarButtonProgress(_calculatedCacheProgress + _calculatedExecuteProgress); - - return __super::OnExecuteProgress(wzPackageId, dwProgressPercentage, dwOverallProgressPercentage); - } - - - virtual STDMETHODIMP_(int) OnExecutePackageComplete( - __in_z LPCWSTR wzPackageId, - __in HRESULT hrExitCode, - __in BOOTSTRAPPER_APPLY_RESTART restart, - __in int nRecommendation - ) { - SetProgressState(hrExitCode); - - if (_wcsnicmp(wzPackageId, L"path_", 5) == 0 && SUCCEEDED(hrExitCode)) { - SendMessageTimeoutW( - HWND_BROADCAST, - WM_SETTINGCHANGE, - 0, - reinterpret_cast(L"Environment"), - SMTO_ABORTIFHUNG, - 1000, - nullptr - ); - } - - int nResult = __super::OnExecutePackageComplete(wzPackageId, hrExitCode, restart, nRecommendation); - - return nResult; - } - - - virtual STDMETHODIMP_(void) OnExecuteComplete(__in HRESULT hrStatus) { - ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_PACKAGE_TEXT, L""); - ThemeSetTextControl(_theme, ID_EXECUTE_PROGRESS_ACTIONDATA_TEXT, L""); - ThemeSetTextControl(_theme, ID_OVERALL_PROGRESS_PACKAGE_TEXT, L""); - ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, FALSE); // no more cancel. - - SetState(PYBA_STATE_EXECUTED, S_OK); // we always return success here and let OnApplyComplete() deal with the error. - SetProgressState(hrStatus); - } - - - virtual STDMETHODIMP_(int) OnResolveSource( - __in_z LPCWSTR wzPackageOrContainerId, - __in_z_opt LPCWSTR wzPayloadId, - __in_z LPCWSTR wzLocalSource, - __in_z_opt LPCWSTR wzDownloadSource - ) { - int nResult = IDERROR; // assume we won't resolve source and that is unexpected. - - if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) { - if (wzDownloadSource) { - nResult = IDDOWNLOAD; - } else { - // prompt to change the source location. - OPENFILENAMEW ofn = { }; - WCHAR wzFile[MAX_PATH] = { }; - - ::StringCchCopyW(wzFile, countof(wzFile), wzLocalSource); - - ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = _hWnd; - ofn.lpstrFile = wzFile; - ofn.nMaxFile = countof(wzFile); - ofn.lpstrFilter = L"All Files\0*.*\0"; - ofn.nFilterIndex = 1; - ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; - ofn.lpstrTitle = _theme->sczCaption; - - if (::GetOpenFileNameW(&ofn)) { - HRESULT hr = _engine->SetLocalSource(wzPackageOrContainerId, wzPayloadId, ofn.lpstrFile); - nResult = SUCCEEDED(hr) ? IDRETRY : IDERROR; - } else { - nResult = IDCANCEL; - } - } - } else if (wzDownloadSource) { - // If doing a non-interactive install and download source is available, let's try downloading the package silently - nResult = IDDOWNLOAD; - } - // else there's nothing more we can do in non-interactive mode - - return CheckCanceled() ? IDCANCEL : nResult; - } - - - virtual STDMETHODIMP_(int) OnApplyComplete(__in HRESULT hrStatus, __in BOOTSTRAPPER_APPLY_RESTART restart) { - _restartResult = restart; // remember the restart result so we return the correct error code no matter what the user chooses to do in the UI. - - // If a restart was encountered and we are not suppressing restarts, then restart is required. - _restartRequired = (BOOTSTRAPPER_APPLY_RESTART_NONE != restart && BOOTSTRAPPER_RESTART_NEVER < _command.restart); - // If a restart is required and we're not displaying a UI or we are not supposed to prompt for restart then allow the restart. - _allowRestart = _restartRequired && (BOOTSTRAPPER_DISPLAY_FULL > _command.display || BOOTSTRAPPER_RESTART_PROMPT < _command.restart); - - // If we are showing UI, wait a beat before moving to the final screen. - if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) { - ::Sleep(250); - } - - SetState(PYBA_STATE_APPLIED, hrStatus); - SetTaskbarButtonProgress(100); // show full progress bar, green, yellow, or red - - return IDNOACTION; - } - - virtual STDMETHODIMP_(void) OnLaunchApprovedExeComplete(__in HRESULT hrStatus, __in DWORD /*processId*/) { - } - - -private: - // - // UiThreadProc - entrypoint for UI thread. - // - static DWORD WINAPI UiThreadProc(__in LPVOID pvContext) { - HRESULT hr = S_OK; - PythonBootstrapperApplication* pThis = (PythonBootstrapperApplication*)pvContext; - BOOL comInitialized = FALSE; - BOOL ret = FALSE; - MSG msg = { }; - - // Initialize COM and theme. - hr = ::CoInitialize(nullptr); - BalExitOnFailure(hr, "Failed to initialize COM."); - comInitialized = TRUE; - - hr = ThemeInitialize(pThis->_hModule); - BalExitOnFailure(hr, "Failed to initialize theme manager."); - - hr = pThis->InitializeData(); - BalExitOnFailure(hr, "Failed to initialize data in bootstrapper application."); - - // Create main window. - pThis->InitializeTaskbarButton(); - hr = pThis->CreateMainWindow(); - BalExitOnFailure(hr, "Failed to create main window."); - - pThis->ValidateOperatingSystem(); - - if (FAILED(pThis->_hrFinal)) { - pThis->SetState(PYBA_STATE_FAILED, hr); - ::PostMessageW(pThis->_hWnd, WM_PYBA_SHOW_FAILURE, 0, 0); - } else { - // Okay, we're ready for packages now. - pThis->SetState(PYBA_STATE_INITIALIZED, hr); - ::PostMessageW(pThis->_hWnd, BOOTSTRAPPER_ACTION_HELP == pThis->_command.action ? WM_PYBA_SHOW_HELP : WM_PYBA_DETECT_PACKAGES, 0, 0); - } - - // message pump - while (0 != (ret = ::GetMessageW(&msg, nullptr, 0, 0))) { - if (-1 == ret) { - hr = E_UNEXPECTED; - BalExitOnFailure(hr, "Unexpected return value from message pump."); - } else if (!ThemeHandleKeyboardMessage(pThis->_theme, msg.hwnd, &msg)) { - ::TranslateMessage(&msg); - ::DispatchMessageW(&msg); - } - } - - // Succeeded thus far, check to see if anything went wrong while actually - // executing changes. - if (FAILED(pThis->_hrFinal)) { - hr = pThis->_hrFinal; - } else if (pThis->CheckCanceled()) { - hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); - } - - LExit: - // destroy main window - pThis->DestroyMainWindow(); - - // initiate engine shutdown - DWORD dwQuit = HRESULT_CODE(hr); - if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == pThis->_restartResult) { - dwQuit = ERROR_SUCCESS_REBOOT_INITIATED; - } else if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == pThis->_restartResult) { - dwQuit = ERROR_SUCCESS_REBOOT_REQUIRED; - } - pThis->_engine->Quit(dwQuit); - - ReleaseTheme(pThis->_theme); - ThemeUninitialize(); - - // uninitialize COM - if (comInitialized) { - ::CoUninitialize(); - } - - return hr; - } - - // - // ParseVariablesFromUnattendXml - reads options from unattend.xml if it - // exists - // - HRESULT ParseVariablesFromUnattendXml() { - HRESULT hr = S_OK; - LPWSTR sczUnattendXmlPath = nullptr; - IXMLDOMDocument *pixdUnattend = nullptr; - IXMLDOMNodeList *pNodes = nullptr; - IXMLDOMNode *pNode = nullptr; - long cNodes; - DWORD dwAttr; - LPWSTR scz = nullptr; - BOOL bValue; - int iValue; - BOOL tryConvert; - BSTR bstrValue = nullptr; - - hr = BalFormatString(L"[WixBundleOriginalSourceFolder]unattend.xml", &sczUnattendXmlPath); - BalExitOnFailure(hr, "Failed to calculate path to unattend.xml"); - - if (!FileExistsEx(sczUnattendXmlPath, &dwAttr)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_VERBOSE, "Did not find %ls", sczUnattendXmlPath); - hr = S_FALSE; - goto LExit; - } - - hr = XmlLoadDocumentFromFile(sczUnattendXmlPath, &pixdUnattend); - BalExitOnFailure1(hr, "Failed to read %ls", sczUnattendXmlPath); - - // get the list of variables users have overridden - hr = XmlSelectNodes(pixdUnattend, L"/Options/Option", &pNodes); - if (S_FALSE == hr) { - ExitFunction1(hr = S_OK); - } - BalExitOnFailure(hr, "Failed to select option nodes."); - - hr = pNodes->get_length((long*)&cNodes); - BalExitOnFailure(hr, "Failed to get option node count."); - - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Reading settings from %ls", sczUnattendXmlPath); - - for (DWORD i = 0; i < cNodes; ++i) { - hr = XmlNextElement(pNodes, &pNode, nullptr); - BalExitOnFailure(hr, "Failed to get next node."); - - // @Name - hr = XmlGetAttributeEx(pNode, L"Name", &scz); - BalExitOnFailure(hr, "Failed to get @Name."); - - tryConvert = TRUE; - hr = XmlGetAttribute(pNode, L"Value", &bstrValue); - if (FAILED(hr) || !bstrValue || !*bstrValue) { - hr = XmlGetText(pNode, &bstrValue); - tryConvert = FALSE; - } - BalExitOnFailure(hr, "Failed to get @Value."); - - if (tryConvert && - CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, bstrValue, -1, L"yes", -1)) { - _engine->SetVariableNumeric(scz, 1); - } else if (tryConvert && - CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, bstrValue, -1, L"no", -1)) { - _engine->SetVariableNumeric(scz, 0); - } else if (tryConvert && ::StrToIntExW(bstrValue, STIF_DEFAULT, &iValue)) { - _engine->SetVariableNumeric(scz, iValue); - } else { - _engine->SetVariableString(scz, bstrValue); - } - - ReleaseNullBSTR(bstrValue); - ReleaseNullStr(scz); - ReleaseNullObject(pNode); - } - - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Finished reading from %ls", sczUnattendXmlPath); - - LExit: - ReleaseObject(pNode); - ReleaseObject(pNodes); - ReleaseObject(pixdUnattend); - ReleaseStr(sczUnattendXmlPath); - - return hr; - } - - - // - // InitializeData - initializes all the package information. - // - HRESULT InitializeData() { - HRESULT hr = S_OK; - LPWSTR sczModulePath = nullptr; - IXMLDOMDocument *pixdManifest = nullptr; - - hr = BalManifestLoad(_hModule, &pixdManifest); - BalExitOnFailure(hr, "Failed to load bootstrapper application manifest."); - - hr = ParseOverridableVariablesFromXml(pixdManifest); - BalExitOnFailure(hr, "Failed to read overridable variables."); - - if (_command.action == BOOTSTRAPPER_ACTION_MODIFY) { - LoadOptionalFeatureStates(_engine); - } - - hr = ParseVariablesFromUnattendXml(); - ExitOnFailure(hr, "Failed to read unattend.ini file."); - - hr = ProcessCommandLine(&_language); - ExitOnFailure(hr, "Unknown commandline parameters."); - - hr = PathRelativeToModule(&sczModulePath, nullptr, _hModule); - BalExitOnFailure(hr, "Failed to get module path."); - - hr = LoadLocalization(sczModulePath, _language); - ExitOnFailure(hr, "Failed to load localization."); - - hr = LoadTheme(sczModulePath, _language); - ExitOnFailure(hr, "Failed to load theme."); - - hr = BalInfoParseFromXml(&_bundle, pixdManifest); - BalExitOnFailure(hr, "Failed to load bundle information."); - - hr = BalConditionsParseFromXml(&_conditions, pixdManifest, _wixLoc); - BalExitOnFailure(hr, "Failed to load conditions from XML."); - - hr = LoadBootstrapperBAFunctions(); - BalExitOnFailure(hr, "Failed to load bootstrapper functions."); - - hr = UpdateUIStrings(_command.action); - BalExitOnFailure(hr, "Failed to load UI strings."); - - GetBundleFileVersion(); - // don't fail if we couldn't get the version info; best-effort only - LExit: - ReleaseObject(pixdManifest); - ReleaseStr(sczModulePath); - - return hr; - } - - - // - // ProcessCommandLine - process the provided command line arguments. - // - HRESULT ProcessCommandLine(__inout LPWSTR* psczLanguage) { - HRESULT hr = S_OK; - int argc = 0; - LPWSTR* argv = nullptr; - LPWSTR sczVariableName = nullptr; - LPWSTR sczVariableValue = nullptr; - - if (_command.wzCommandLine && *_command.wzCommandLine) { - argv = ::CommandLineToArgvW(_command.wzCommandLine, &argc); - ExitOnNullWithLastError(argv, hr, "Failed to get command line."); - - for (int i = 0; i < argc; ++i) { - if (argv[i][0] == L'-' || argv[i][0] == L'/') { - if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1)) { - if (i + 1 >= argc) { - hr = E_INVALIDARG; - BalExitOnFailure(hr, "Must specify a language."); - } - - ++i; - - hr = StrAllocString(psczLanguage, &argv[i][0], 0); - BalExitOnFailure(hr, "Failed to copy language."); - } else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"simple", -1)) { - _engine->SetVariableNumeric(L"SimpleInstall", 1); - } - } else if (_overridableVariables) { - int value; - const wchar_t* pwc = wcschr(argv[i], L'='); - if (pwc) { - hr = StrAllocString(&sczVariableName, argv[i], pwc - argv[i]); - BalExitOnFailure(hr, "Failed to copy variable name."); - - hr = DictKeyExists(_overridableVariables, sczVariableName); - if (E_NOTFOUND == hr) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Ignoring attempt to set non-overridable variable: '%ls'.", sczVariableName); - hr = S_OK; - continue; - } - ExitOnFailure(hr, "Failed to check the dictionary of overridable variables."); - - hr = StrAllocString(&sczVariableValue, ++pwc, 0); - BalExitOnFailure(hr, "Failed to copy variable value."); - - if (::StrToIntEx(sczVariableValue, STIF_DEFAULT, &value)) { - hr = _engine->SetVariableNumeric(sczVariableName, value); - } else { - hr = _engine->SetVariableString(sczVariableName, sczVariableValue); - } - BalExitOnFailure(hr, "Failed to set variable."); - } else { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Ignoring unknown argument: %ls", argv[i]); - } - } - } - } - - LExit: - if (argv) { - ::LocalFree(argv); - } - - ReleaseStr(sczVariableName); - ReleaseStr(sczVariableValue); - - return hr; - } - - HRESULT LoadLocalization(__in_z LPCWSTR wzModulePath, __in_z_opt LPCWSTR wzLanguage) { - HRESULT hr = S_OK; - LPWSTR sczLocPath = nullptr; - LPCWSTR wzLocFileName = L"Default.wxl"; - - hr = LocProbeForFile(wzModulePath, wzLocFileName, wzLanguage, &sczLocPath); - BalExitOnFailure2(hr, "Failed to probe for loc file: %ls in path: %ls", wzLocFileName, wzModulePath); - - hr = LocLoadFromFile(sczLocPath, &_wixLoc); - BalExitOnFailure1(hr, "Failed to load loc file from path: %ls", sczLocPath); - - if (WIX_LOCALIZATION_LANGUAGE_NOT_SET != _wixLoc->dwLangId) { - ::SetThreadLocale(_wixLoc->dwLangId); - } - - hr = StrAllocString(&_confirmCloseMessage, L"#(loc.ConfirmCancelMessage)", 0); - ExitOnFailure(hr, "Failed to initialize confirm message loc identifier."); - - hr = LocLocalizeString(_wixLoc, &_confirmCloseMessage); - BalExitOnFailure1(hr, "Failed to localize confirm close message: %ls", _confirmCloseMessage); - - LExit: - ReleaseStr(sczLocPath); - - return hr; - } - - - HRESULT LoadTheme(__in_z LPCWSTR wzModulePath, __in_z_opt LPCWSTR wzLanguage) { - HRESULT hr = S_OK; - LPWSTR sczThemePath = nullptr; - LPCWSTR wzThemeFileName = L"Default.thm"; - LPWSTR sczCaption = nullptr; - - hr = LocProbeForFile(wzModulePath, wzThemeFileName, wzLanguage, &sczThemePath); - BalExitOnFailure2(hr, "Failed to probe for theme file: %ls in path: %ls", wzThemeFileName, wzModulePath); - - hr = ThemeLoadFromFile(sczThemePath, &_theme); - BalExitOnFailure1(hr, "Failed to load theme from path: %ls", sczThemePath); - - hr = ThemeLocalize(_theme, _wixLoc); - BalExitOnFailure1(hr, "Failed to localize theme: %ls", sczThemePath); - - // Update the caption if there are any formatted strings in it. - // If the wix developer is showing a hidden variable in the UI, then - // obviously they don't care about keeping it safe so don't go down the - // rabbit hole of making sure that this is securely freed. - hr = BalFormatString(_theme->sczCaption, &sczCaption); - if (SUCCEEDED(hr)) { - ThemeUpdateCaption(_theme, sczCaption); - } - - LExit: - ReleaseStr(sczCaption); - ReleaseStr(sczThemePath); - - return hr; - } - - - HRESULT ParseOverridableVariablesFromXml(__in IXMLDOMDocument* pixdManifest) { - HRESULT hr = S_OK; - IXMLDOMNode* pNode = nullptr; - IXMLDOMNodeList* pNodes = nullptr; - DWORD cNodes = 0; - LPWSTR scz = nullptr; - BOOL hidden = FALSE; - - // get the list of variables users can override on the command line - hr = XmlSelectNodes(pixdManifest, L"/BootstrapperApplicationData/WixStdbaOverridableVariable", &pNodes); - if (S_FALSE == hr) { - ExitFunction1(hr = S_OK); - } - ExitOnFailure(hr, "Failed to select overridable variable nodes."); - - hr = pNodes->get_length((long*)&cNodes); - ExitOnFailure(hr, "Failed to get overridable variable node count."); - - if (cNodes) { - hr = DictCreateStringList(&_overridableVariables, 32, DICT_FLAG_NONE); - ExitOnFailure(hr, "Failed to create the string dictionary."); - - for (DWORD i = 0; i < cNodes; ++i) { - hr = XmlNextElement(pNodes, &pNode, nullptr); - ExitOnFailure(hr, "Failed to get next node."); - - // @Name - hr = XmlGetAttributeEx(pNode, L"Name", &scz); - ExitOnFailure(hr, "Failed to get @Name."); - - hr = XmlGetYesNoAttribute(pNode, L"Hidden", &hidden); - - if (!hidden) { - hr = DictAddKey(_overridableVariables, scz); - ExitOnFailure1(hr, "Failed to add \"%ls\" to the string dictionary.", scz); - } - - // prepare next iteration - ReleaseNullObject(pNode); - } - } - - LExit: - ReleaseObject(pNode); - ReleaseObject(pNodes); - ReleaseStr(scz); - return hr; - } - - - // - // Get the file version of the bootstrapper and record in bootstrapper log file - // - HRESULT GetBundleFileVersion() { - HRESULT hr = S_OK; - ULARGE_INTEGER uliVersion = { }; - LPWSTR sczCurrentPath = nullptr; - - hr = PathForCurrentProcess(&sczCurrentPath, nullptr); - BalExitOnFailure(hr, "Failed to get bundle path."); - - hr = FileVersion(sczCurrentPath, &uliVersion.HighPart, &uliVersion.LowPart); - BalExitOnFailure(hr, "Failed to get bundle file version."); - - hr = _engine->SetVariableVersion(PYBA_VARIABLE_BUNDLE_FILE_VERSION, uliVersion.QuadPart); - BalExitOnFailure(hr, "Failed to set WixBundleFileVersion variable."); - - LExit: - ReleaseStr(sczCurrentPath); - - return hr; - } - - - // - // CreateMainWindow - creates the main install window. - // - HRESULT CreateMainWindow() { - HRESULT hr = S_OK; - HICON hIcon = reinterpret_cast(_theme->hIcon); - WNDCLASSW wc = { }; - DWORD dwWindowStyle = 0; - int x = CW_USEDEFAULT; - int y = CW_USEDEFAULT; - POINT ptCursor = { }; - HMONITOR hMonitor = nullptr; - MONITORINFO mi = { }; - COLORREF fg, bg; - HBRUSH bgBrush; - - // If the theme did not provide an icon, try using the icon from the bundle engine. - if (!hIcon) { - HMODULE hBootstrapperEngine = ::GetModuleHandleW(nullptr); - if (hBootstrapperEngine) { - hIcon = ::LoadIconW(hBootstrapperEngine, MAKEINTRESOURCEW(1)); - } - } - - fg = RGB(0, 0, 0); - bg = RGB(255, 255, 255); - bgBrush = (HBRUSH)(COLOR_WINDOW+1); - if (_theme->dwFontId < _theme->cFonts) { - THEME_FONT *font = &_theme->rgFonts[_theme->dwFontId]; - fg = font->crForeground; - bg = font->crBackground; - bgBrush = font->hBackground; - RemapColor(&fg, &bg, &bgBrush); - } - - // Register the window class and create the window. - wc.lpfnWndProc = PythonBootstrapperApplication::WndProc; - wc.hInstance = _hModule; - wc.hIcon = hIcon; - wc.hCursor = ::LoadCursorW(nullptr, (LPCWSTR)IDC_ARROW); - wc.hbrBackground = bgBrush; - wc.lpszMenuName = nullptr; - wc.lpszClassName = PYBA_WINDOW_CLASS; - if (!::RegisterClassW(&wc)) { - ExitWithLastError(hr, "Failed to register window."); - } - - _registered = TRUE; - - // Calculate the window style based on the theme style and command display value. - dwWindowStyle = _theme->dwStyle; - if (BOOTSTRAPPER_DISPLAY_NONE >= _command.display) { - dwWindowStyle &= ~WS_VISIBLE; - } - - // Don't show the window if there is a splash screen (it will be made visible when the splash screen is hidden) - if (::IsWindow(_command.hwndSplashScreen)) { - dwWindowStyle &= ~WS_VISIBLE; - } - - // Center the window on the monitor with the mouse. - if (::GetCursorPos(&ptCursor)) { - hMonitor = ::MonitorFromPoint(ptCursor, MONITOR_DEFAULTTONEAREST); - if (hMonitor) { - mi.cbSize = sizeof(mi); - if (::GetMonitorInfoW(hMonitor, &mi)) { - x = mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - _theme->nWidth) / 2; - y = mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - _theme->nHeight) / 2; - } - } - } - - _hWnd = ::CreateWindowExW( - 0, - wc.lpszClassName, - _theme->sczCaption, - dwWindowStyle, - x, - y, - _theme->nWidth, - _theme->nHeight, - HWND_DESKTOP, - nullptr, - _hModule, - this - ); - ExitOnNullWithLastError(_hWnd, hr, "Failed to create window."); - - hr = S_OK; - - LExit: - return hr; - } - - - // - // InitializeTaskbarButton - initializes taskbar button for progress. - // - void InitializeTaskbarButton() { - HRESULT hr = S_OK; - - hr = ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), reinterpret_cast(&_taskbarList)); - if (REGDB_E_CLASSNOTREG == hr) { - // not supported before Windows 7 - ExitFunction1(hr = S_OK); - } - BalExitOnFailure(hr, "Failed to create ITaskbarList3. Continuing."); - - _taskbarButtonCreatedMessage = ::RegisterWindowMessageW(L"TaskbarButtonCreated"); - BalExitOnNullWithLastError(_taskbarButtonCreatedMessage, hr, "Failed to get TaskbarButtonCreated message. Continuing."); - - LExit: - return; - } - - // - // DestroyMainWindow - clean up all the window registration. - // - void DestroyMainWindow() { - if (::IsWindow(_hWnd)) { - ::DestroyWindow(_hWnd); - _hWnd = nullptr; - _taskbarButtonOK = FALSE; - } - - if (_registered) { - ::UnregisterClassW(PYBA_WINDOW_CLASS, _hModule); - _registered = FALSE; - } - } - - - // - // WndProc - standard windows message handler. - // - static LRESULT CALLBACK WndProc( - __in HWND hWnd, - __in UINT uMsg, - __in WPARAM wParam, - __in LPARAM lParam - ) { -#pragma warning(suppress:4312) - auto pBA = reinterpret_cast(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)); - - switch (uMsg) { - case WM_NCCREATE: { - LPCREATESTRUCT lpcs = reinterpret_cast(lParam); - pBA = reinterpret_cast(lpcs->lpCreateParams); -#pragma warning(suppress:4244) - ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast(pBA)); - break; - } - - case WM_NCDESTROY: { - LRESULT lres = ThemeDefWindowProc(pBA ? pBA->_theme : nullptr, hWnd, uMsg, wParam, lParam); - ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0); - return lres; - } - - case WM_CREATE: - if (!pBA->OnCreate(hWnd)) { - return -1; - } - break; - - case WM_QUERYENDSESSION: - return IDCANCEL != pBA->OnSystemShutdown(static_cast(lParam), IDCANCEL); - - case WM_CLOSE: - // If the user chose not to close, do *not* let the default window proc handle the message. - if (!pBA->OnClose()) { - return 0; - } - break; - - case WM_DESTROY: - ::PostQuitMessage(0); - break; - - case WM_PAINT: __fallthrough; - case WM_ERASEBKGND: - if (pBA && pBA->_suppressPaint) { - return TRUE; - } - break; - - case WM_PYBA_SHOW_HELP: - pBA->OnShowHelp(); - return 0; - - case WM_PYBA_DETECT_PACKAGES: - pBA->OnDetect(); - return 0; - - case WM_PYBA_PLAN_PACKAGES: - pBA->OnPlan(static_cast(lParam)); - return 0; - - case WM_PYBA_APPLY_PACKAGES: - pBA->OnApply(); - return 0; - - case WM_PYBA_CHANGE_STATE: - pBA->OnChangeState(static_cast(lParam)); - return 0; - - case WM_PYBA_SHOW_FAILURE: - pBA->OnShowFailure(); - return 0; - - case WM_COMMAND: - switch (LOWORD(wParam)) { - // Customize commands - // Success/failure commands - case ID_SUCCESS_RESTART_BUTTON: __fallthrough; - case ID_FAILURE_RESTART_BUTTON: - pBA->OnClickRestartButton(); - return 0; - - case IDCANCEL: __fallthrough; - case ID_INSTALL_CANCEL_BUTTON: __fallthrough; - case ID_CUSTOM1_CANCEL_BUTTON: __fallthrough; - case ID_CUSTOM2_CANCEL_BUTTON: __fallthrough; - case ID_MODIFY_CANCEL_BUTTON: __fallthrough; - case ID_PROGRESS_CANCEL_BUTTON: __fallthrough; - case ID_SUCCESS_CANCEL_BUTTON: __fallthrough; - case ID_FAILURE_CANCEL_BUTTON: __fallthrough; - case ID_CLOSE_BUTTON: - pBA->OnCommand(ID_CLOSE_BUTTON); - return 0; - - default: - pBA->OnCommand((CONTROL_ID)LOWORD(wParam)); - } - break; - - case WM_NOTIFY: - if (lParam) { - LPNMHDR pnmhdr = reinterpret_cast(lParam); - switch (pnmhdr->code) { - case NM_CLICK: __fallthrough; - case NM_RETURN: - switch (static_cast(pnmhdr->idFrom)) { - case ID_FAILURE_LOGFILE_LINK: - pBA->OnClickLogFileLink(); - return 1; - } - } - } - break; - - case WM_CTLCOLORSTATIC: - case WM_CTLCOLORBTN: - if (pBA) { - HBRUSH brush = nullptr; - if (pBA->SetControlColor((HWND)lParam, (HDC)wParam, &brush)) { - return (LRESULT)brush; - } - } - break; - } - - if (pBA && pBA->_taskbarList && uMsg == pBA->_taskbarButtonCreatedMessage) { - pBA->_taskbarButtonOK = TRUE; - return 0; - } - - return ThemeDefWindowProc(pBA ? pBA->_theme : nullptr, hWnd, uMsg, wParam, lParam); - } - - // - // OnCreate - finishes loading the theme. - // - BOOL OnCreate(__in HWND hWnd) { - HRESULT hr = S_OK; - - hr = ThemeLoadControls(_theme, hWnd, CONTROL_ID_NAMES, countof(CONTROL_ID_NAMES)); - BalExitOnFailure(hr, "Failed to load theme controls."); - - C_ASSERT(COUNT_PAGE == countof(PAGE_NAMES)); - C_ASSERT(countof(_pageIds) == countof(PAGE_NAMES)); - - ThemeGetPageIds(_theme, PAGE_NAMES, _pageIds, countof(_pageIds)); - - // Initialize the text on all "application" (non-page) controls. - for (DWORD i = 0; i < _theme->cControls; ++i) { - THEME_CONTROL* pControl = _theme->rgControls + i; - LPWSTR text = nullptr; - - if (!pControl->wPageId && pControl->sczText && *pControl->sczText) { - HRESULT hrFormat; - - // If the wix developer is showing a hidden variable in the UI, - // then obviously they don't care about keeping it safe so don't - // go down the rabbit hole of making sure that this is securely - // freed. - hrFormat = BalFormatString(pControl->sczText, &text); - if (SUCCEEDED(hrFormat)) { - ThemeSetTextControl(_theme, pControl->wId, text); - ReleaseStr(text); - } - } - } - - LExit: - return SUCCEEDED(hr); - } - - void RemapColor(COLORREF *fg, COLORREF *bg, HBRUSH *bgBrush) { - if (*fg == RGB(0, 0, 0)) { - *fg = GetSysColor(COLOR_WINDOWTEXT); - } else if (*fg == RGB(128, 128, 128)) { - *fg = GetSysColor(COLOR_GRAYTEXT); - } - if (*bgBrush && *bg == RGB(255, 255, 255)) { - *bg = GetSysColor(COLOR_WINDOW); - *bgBrush = GetSysColorBrush(COLOR_WINDOW); - } - } - - BOOL SetControlColor(HWND hWnd, HDC hDC, HBRUSH *brush) { - for (int i = 0; i < _theme->cControls; ++i) { - if (_theme->rgControls[i].hWnd != hWnd) { - continue; - } - - DWORD fontId = _theme->rgControls[i].dwFontId; - if (fontId > _theme->cFonts) { - fontId = 0; - } - THEME_FONT *fnt = &_theme->rgFonts[fontId]; - - COLORREF fg = fnt->crForeground, bg = fnt->crBackground; - *brush = fnt->hBackground; - RemapColor(&fg, &bg, brush); - ::SetTextColor(hDC, fg); - ::SetBkColor(hDC, bg); - - return TRUE; - } - return FALSE; - } - - // - // OnShowFailure - display the failure page. - // - void OnShowFailure() { - SetState(PYBA_STATE_FAILED, S_OK); - - // If the UI should be visible, display it now and hide the splash screen - if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) { - ::ShowWindow(_theme->hwndParent, SW_SHOW); - } - - _engine->CloseSplashScreen(); - - return; - } - - - // - // OnShowHelp - display the help page. - // - void OnShowHelp() { - SetState(PYBA_STATE_HELP, S_OK); - - // If the UI should be visible, display it now and hide the splash screen - if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) { - ::ShowWindow(_theme->hwndParent, SW_SHOW); - } - - _engine->CloseSplashScreen(); - - return; - } - - - // - // OnDetect - start the processing of packages. - // - void OnDetect() { - HRESULT hr = S_OK; - - if (_baFunction) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running detect BA function"); - hr = _baFunction->OnDetect(); - BalExitOnFailure(hr, "Failed calling detect BA function."); - } - - SetState(PYBA_STATE_DETECTING, hr); - - // If the UI should be visible, display it now and hide the splash screen - if (BOOTSTRAPPER_DISPLAY_NONE < _command.display) { - ::ShowWindow(_theme->hwndParent, SW_SHOW); - } - - _engine->CloseSplashScreen(); - - // Tell the core we're ready for the packages to be processed now. - hr = _engine->Detect(); - BalExitOnFailure(hr, "Failed to start detecting chain."); - - LExit: - if (FAILED(hr)) { - SetState(PYBA_STATE_DETECTING, hr); - } - - return; - } - - HRESULT UpdateUIStrings(__in BOOTSTRAPPER_ACTION action) { - HRESULT hr = S_OK; - LPCWSTR likeInstalling = nullptr; - LPCWSTR likeInstallation = nullptr; - switch (action) { - case BOOTSTRAPPER_ACTION_INSTALL: - likeInstalling = L"Installing"; - likeInstallation = L"Installation"; - break; - case BOOTSTRAPPER_ACTION_MODIFY: - // For modify, we actually want to pass INSTALL - action = BOOTSTRAPPER_ACTION_INSTALL; - likeInstalling = L"Modifying"; - likeInstallation = L"Modification"; - break; - case BOOTSTRAPPER_ACTION_REPAIR: - likeInstalling = L"Repairing"; - likeInstallation = L"Repair"; - break; - case BOOTSTRAPPER_ACTION_UNINSTALL: - likeInstalling = L"Uninstalling"; - likeInstallation = L"Uninstallation"; - break; - } - - if (likeInstalling) { - LPWSTR locName = nullptr; - LOC_STRING *locText = nullptr; - hr = StrAllocFormatted(&locName, L"#(loc.%ls)", likeInstalling); - if (SUCCEEDED(hr)) { - hr = LocGetString(_wixLoc, locName, &locText); - ReleaseStr(locName); - } - _engine->SetVariableString( - L"ActionLikeInstalling", - SUCCEEDED(hr) && locText ? locText->wzText : likeInstalling - ); - } - - if (likeInstallation) { - LPWSTR locName = nullptr; - LOC_STRING *locText = nullptr; - hr = StrAllocFormatted(&locName, L"#(loc.%ls)", likeInstallation); - if (SUCCEEDED(hr)) { - hr = LocGetString(_wixLoc, locName, &locText); - ReleaseStr(locName); - } - _engine->SetVariableString( - L"ActionLikeInstallation", - SUCCEEDED(hr) && locText ? locText->wzText : likeInstallation - ); - } - return hr; - } - - // - // OnPlan - plan the detected changes. - // - void OnPlan(__in BOOTSTRAPPER_ACTION action) { - HRESULT hr = S_OK; - - _plannedAction = action; - - hr = UpdateUIStrings(action); - BalExitOnFailure(hr, "Failed to update strings"); - - // If we are going to apply a downgrade, bail. - if (_downgradingOtherVersion && BOOTSTRAPPER_ACTION_UNINSTALL < action) { - if (_suppressDowngradeFailure) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "A newer version of this product is installed but downgrade failure has been suppressed; continuing..."); - } else { - hr = HRESULT_FROM_WIN32(ERROR_PRODUCT_VERSION); - BalExitOnFailure(hr, "Cannot install a product when a newer version is installed."); - } - } - - SetState(PYBA_STATE_PLANNING, hr); - - if (_baFunction) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Running plan BA function"); - _baFunction->OnPlan(); - } - - hr = _engine->Plan(action); - BalExitOnFailure(hr, "Failed to start planning packages."); - - LExit: - if (FAILED(hr)) { - SetState(PYBA_STATE_PLANNING, hr); - } - - return; - } - - - // - // OnApply - apply the packages. - // - void OnApply() { - HRESULT hr = S_OK; - - SetState(PYBA_STATE_APPLYING, hr); - SetProgressState(hr); - SetTaskbarButtonProgress(0); - - hr = _engine->Apply(_hWnd); - BalExitOnFailure(hr, "Failed to start applying packages."); - - ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, TRUE); // ensure the cancel button is enabled before starting. - - LExit: - if (FAILED(hr)) { - SetState(PYBA_STATE_APPLYING, hr); - } - - return; - } - - - // - // OnChangeState - change state. - // - void OnChangeState(__in PYBA_STATE state) { - LPWSTR unformattedText = nullptr; - - _state = state; - - // If our install is at the end (success or failure) and we're not showing full UI - // then exit (prompt for restart if required). - if ((PYBA_STATE_APPLIED <= _state && BOOTSTRAPPER_DISPLAY_FULL > _command.display)) { - // If a restart was required but we were not automatically allowed to - // accept the reboot then do the prompt. - if (_restartRequired && !_allowRestart) { - StrAllocFromError(&unformattedText, HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED), nullptr); - - _allowRestart = IDOK == ::MessageBoxW( - _hWnd, - unformattedText ? unformattedText : L"The requested operation is successful. Changes will not be effective until the system is rebooted.", - _theme->sczCaption, - MB_ICONEXCLAMATION | MB_OKCANCEL - ); - } - - // Quietly exit. - ::PostMessageW(_hWnd, WM_CLOSE, 0, 0); - } else { // try to change the pages. - DWORD newPageId = 0; - DeterminePageId(_state, &newPageId); - - if (_visiblePageId != newPageId) { - ShowPage(newPageId); - } - } - - ReleaseStr(unformattedText); - } - - // - // Called before showing a page to handle all controls. - // - void ProcessPageControls(THEME_PAGE *pPage) { - if (!pPage) { - return; - } - - for (DWORD i = 0; i < pPage->cControlIndices; ++i) { - THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i]; - BOOL enableControl = TRUE; - - // If this is a named control, try to set its default state. - if (pControl->sczName && *pControl->sczName) { - // If this is a checkable control, try to set its default state - // to the state of a matching named Burn variable. - if (IsCheckable(pControl)) { - LONGLONG llValue = 0; - HRESULT hr = BalGetNumericVariable(pControl->sczName, &llValue); - - // If the control value isn't set then disable it. - if (!SUCCEEDED(hr)) { - enableControl = FALSE; - } else { - ThemeSendControlMessage( - _theme, - pControl->wId, - BM_SETCHECK, - SUCCEEDED(hr) && llValue ? BST_CHECKED : BST_UNCHECKED, - 0 - ); - } - } - - // Hide or disable controls based on the control name with 'State' appended - LPWSTR controlName = nullptr; - HRESULT hr = StrAllocFormatted(&controlName, L"%lsState", pControl->sczName); - if (SUCCEEDED(hr)) { - LPWSTR controlState = nullptr; - hr = BalGetStringVariable(controlName, &controlState); - if (SUCCEEDED(hr) && controlState && *controlState) { - if (controlState[0] == '[') { - LPWSTR formatted = nullptr; - if (SUCCEEDED(BalFormatString(controlState, &formatted))) { - StrFree(controlState); - controlState = formatted; - } - } - - if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, controlState, -1, L"disable", -1)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Disable control %ls", pControl->sczName); - enableControl = FALSE; - } else if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, controlState, -1, L"hide", -1)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Hide control %ls", pControl->sczName); - // TODO: This doesn't work - ThemeShowControl(_theme, pControl->wId, SW_HIDE); - } else { - // An explicit state can override the lack of a - // backing variable. - enableControl = TRUE; - } - } - StrFree(controlState); - } - StrFree(controlName); - controlName = nullptr; - - - // If a command link has a note, then add it. - if ((pControl->dwStyle & BS_TYPEMASK) == BS_COMMANDLINK || - (pControl->dwStyle & BS_TYPEMASK) == BS_DEFCOMMANDLINK) { - hr = StrAllocFormatted(&controlName, L"#(loc.%lsNote)", pControl->sczName); - if (SUCCEEDED(hr)) { - LOC_STRING *locText = nullptr; - hr = LocGetString(_wixLoc, controlName, &locText); - if (SUCCEEDED(hr) && locText && locText->wzText && locText->wzText[0]) { - LPWSTR text = nullptr; - hr = BalFormatString(locText->wzText, &text); - if (SUCCEEDED(hr) && text && text[0]) { - ThemeSendControlMessage(_theme, pControl->wId, BCM_SETNOTE, 0, (LPARAM)text); - ReleaseStr(text); - text = nullptr; - } - } - ReleaseStr(controlName); - controlName = nullptr; - } - hr = S_OK; - } - } - - ThemeControlEnable(_theme, pControl->wId, enableControl); - - // Format the text in each of the new page's controls - if (pControl->sczText && *pControl->sczText) { - // If the wix developer is showing a hidden variable - // in the UI, then obviously they don't care about - // keeping it safe so don't go down the rabbit hole - // of making sure that this is securely freed. - LPWSTR text = nullptr; - HRESULT hr = BalFormatString(pControl->sczText, &text); - if (SUCCEEDED(hr)) { - ThemeSetTextControl(_theme, pControl->wId, text); - } - } - } - } - - // - // OnClose - called when the window is trying to be closed. - // - BOOL OnClose() { - BOOL close = FALSE; - - // If we've already succeeded or failed or showing the help page, just close (prompts are annoying if the bootstrapper is done). - if (PYBA_STATE_APPLIED <= _state || PYBA_STATE_HELP == _state) { - close = TRUE; - } else { - // prompt the user or force the cancel if there is no UI. - close = PromptCancel( - _hWnd, - BOOTSTRAPPER_DISPLAY_FULL != _command.display, - _confirmCloseMessage ? _confirmCloseMessage : L"Are you sure you want to cancel?", - _theme->sczCaption - ); - } - - // If we're doing progress then we never close, we just cancel to let rollback occur. - if (PYBA_STATE_APPLYING <= _state && PYBA_STATE_APPLIED > _state) { - // If we canceled disable cancel button since clicking it again is silly. - if (close) { - ThemeControlEnable(_theme, ID_PROGRESS_CANCEL_BUTTON, FALSE); - } - - close = FALSE; - } - - return close; - } - - // - // OnClickCloseButton - close the application. - // - void OnClickCloseButton() { - ::SendMessageW(_hWnd, WM_CLOSE, 0, 0); - } - - - - // - // OnClickRestartButton - allows the restart and closes the app. - // - void OnClickRestartButton() { - AssertSz(_restartRequired, "Restart must be requested to be able to click on the restart button."); - - _allowRestart = TRUE; - ::SendMessageW(_hWnd, WM_CLOSE, 0, 0); - - return; - } - - - // - // OnClickLogFileLink - show the log file. - // - void OnClickLogFileLink() { - HRESULT hr = S_OK; - LPWSTR sczLogFile = nullptr; - - hr = BalGetStringVariable(_bundle.sczLogVariable, &sczLogFile); - BalExitOnFailure1(hr, "Failed to get log file variable '%ls'.", _bundle.sczLogVariable); - - hr = ShelExec(L"notepad.exe", sczLogFile, L"open", nullptr, SW_SHOWDEFAULT, _hWnd, nullptr); - BalExitOnFailure1(hr, "Failed to open log file target: %ls", sczLogFile); - - LExit: - ReleaseStr(sczLogFile); - - return; - } - - - // - // SetState - // - void SetState(__in PYBA_STATE state, __in HRESULT hrStatus) { - if (FAILED(hrStatus)) { - _hrFinal = hrStatus; - } - - if (FAILED(_hrFinal)) { - state = PYBA_STATE_FAILED; - } - - if (_state != state) { - ::PostMessageW(_hWnd, WM_PYBA_CHANGE_STATE, 0, state); - } - } - - // - // GoToPage - // - void GoToPage(__in PAGE page) { - _installPage = page; - ::PostMessageW(_hWnd, WM_PYBA_CHANGE_STATE, 0, _state); - } - - void DeterminePageId(__in PYBA_STATE state, __out DWORD* pdwPageId) { - LONGLONG simple; - - if (BOOTSTRAPPER_DISPLAY_PASSIVE == _command.display) { - switch (state) { - case PYBA_STATE_INITIALIZED: - *pdwPageId = BOOTSTRAPPER_ACTION_HELP == _command.action - ? _pageIds[PAGE_HELP] - : _pageIds[PAGE_LOADING]; - break; - - case PYBA_STATE_HELP: - *pdwPageId = _pageIds[PAGE_HELP]; - break; - - case PYBA_STATE_DETECTING: - *pdwPageId = _pageIds[PAGE_LOADING] - ? _pageIds[PAGE_LOADING] - : _pageIds[PAGE_PROGRESS_PASSIVE] - ? _pageIds[PAGE_PROGRESS_PASSIVE] - : _pageIds[PAGE_PROGRESS]; - break; - - case PYBA_STATE_DETECTED: __fallthrough; - case PYBA_STATE_PLANNING: __fallthrough; - case PYBA_STATE_PLANNED: __fallthrough; - case PYBA_STATE_APPLYING: __fallthrough; - case PYBA_STATE_CACHING: __fallthrough; - case PYBA_STATE_CACHED: __fallthrough; - case PYBA_STATE_EXECUTING: __fallthrough; - case PYBA_STATE_EXECUTED: - *pdwPageId = _pageIds[PAGE_PROGRESS_PASSIVE] - ? _pageIds[PAGE_PROGRESS_PASSIVE] - : _pageIds[PAGE_PROGRESS]; - break; - - default: - *pdwPageId = 0; - break; - } - } else if (BOOTSTRAPPER_DISPLAY_FULL == _command.display) { - switch (state) { - case PYBA_STATE_INITIALIZING: - *pdwPageId = 0; - break; - - case PYBA_STATE_INITIALIZED: - *pdwPageId = BOOTSTRAPPER_ACTION_HELP == _command.action - ? _pageIds[PAGE_HELP] - : _pageIds[PAGE_LOADING]; - break; - - case PYBA_STATE_HELP: - *pdwPageId = _pageIds[PAGE_HELP]; - break; - - case PYBA_STATE_DETECTING: - *pdwPageId = _pageIds[PAGE_LOADING]; - break; - - case PYBA_STATE_DETECTED: - if (_installPage == PAGE_LOADING) { - switch (_command.action) { - case BOOTSTRAPPER_ACTION_INSTALL: - if (_upgrading) { - _installPage = PAGE_UPGRADE; - } else if (SUCCEEDED(BalGetNumericVariable(L"SimpleInstall", &simple)) && simple) { - _installPage = PAGE_SIMPLE_INSTALL; - } else { - _installPage = PAGE_INSTALL; - } - break; - - case BOOTSTRAPPER_ACTION_MODIFY: __fallthrough; - case BOOTSTRAPPER_ACTION_REPAIR: __fallthrough; - case BOOTSTRAPPER_ACTION_UNINSTALL: - _installPage = PAGE_MODIFY; - break; - } - } - *pdwPageId = _pageIds[_installPage]; - break; - - case PYBA_STATE_PLANNING: __fallthrough; - case PYBA_STATE_PLANNED: __fallthrough; - case PYBA_STATE_APPLYING: __fallthrough; - case PYBA_STATE_CACHING: __fallthrough; - case PYBA_STATE_CACHED: __fallthrough; - case PYBA_STATE_EXECUTING: __fallthrough; - case PYBA_STATE_EXECUTED: - *pdwPageId = _pageIds[PAGE_PROGRESS]; - break; - - case PYBA_STATE_APPLIED: - *pdwPageId = _pageIds[PAGE_SUCCESS]; - break; - - case PYBA_STATE_FAILED: - *pdwPageId = _pageIds[PAGE_FAILURE]; - break; - } - } - } - - BOOL WillElevate() { - static BAL_CONDITION WILL_ELEVATE_CONDITION = { - L"not WixBundleElevated and (" - /*Elevate when installing for all users*/ - L"InstallAllUsers or " - /*Elevate when installing the launcher for all users and it was not detected*/ - L"(Include_launcher and InstallLauncherAllUsers and not BlockedLauncher)" - L")", - L"" - }; - BOOL result; - - return SUCCEEDED(BalConditionEvaluate(&WILL_ELEVATE_CONDITION, _engine, &result, nullptr)) && result; - } - - BOOL IsCrtInstalled() { - if (_crtInstalledToken > 0) { - return TRUE; - } else if (_crtInstalledToken == 0) { - return FALSE; - } - - // Check whether at least CRT v10.0.10137.0 is available. - // It should only be installed as a Windows Update package, which means - // we don't need to worry about 32-bit/64-bit. - LPCWSTR crtFile = L"ucrtbase.dll"; - - DWORD cbVer = GetFileVersionInfoSizeW(crtFile, nullptr); - if (!cbVer) { - _crtInstalledToken = 0; - return FALSE; - } - - void *pData = malloc(cbVer); - if (!pData) { - _crtInstalledToken = 0; - return FALSE; - } - - if (!GetFileVersionInfoW(crtFile, 0, cbVer, pData)) { - free(pData); - _crtInstalledToken = 0; - return FALSE; - } - - VS_FIXEDFILEINFO *ffi; - UINT cb; - BOOL result = FALSE; - - if (VerQueryValueW(pData, L"\\", (LPVOID*)&ffi, &cb) && - ffi->dwFileVersionMS == 0x000A0000 && ffi->dwFileVersionLS >= 0x27990000) { - result = TRUE; - } - - free(pData); - _crtInstalledToken = result ? 1 : 0; - return result; - } - - HRESULT EvaluateConditions() { - HRESULT hr = S_OK; - BOOL result = FALSE; - - for (DWORD i = 0; i < _conditions.cConditions; ++i) { - BAL_CONDITION* pCondition = _conditions.rgConditions + i; - - hr = BalConditionEvaluate(pCondition, _engine, &result, &_failedMessage); - BalExitOnFailure(hr, "Failed to evaluate condition."); - - if (!result) { - // Hope they didn't have hidden variables in their message, because it's going in the log in plaintext. - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "%ls", _failedMessage); - - hr = E_WIXSTDBA_CONDITION_FAILED; - // todo: remove in WiX v4, in case people are relying on v3.x logging behavior - BalExitOnFailure1(hr, "Bundle condition evaluated to false: %ls", pCondition->sczCondition); - } - } - - ReleaseNullStrSecure(_failedMessage); - - LExit: - return hr; - } - - - void SetTaskbarButtonProgress(__in DWORD dwOverallPercentage) { - HRESULT hr = S_OK; - - if (_taskbarButtonOK) { - hr = _taskbarList->SetProgressValue(_hWnd, dwOverallPercentage, 100UL); - BalExitOnFailure1(hr, "Failed to set taskbar button progress to: %d%%.", dwOverallPercentage); - } - - LExit: - return; - } - - - void SetTaskbarButtonState(__in TBPFLAG tbpFlags) { - HRESULT hr = S_OK; - - if (_taskbarButtonOK) { - hr = _taskbarList->SetProgressState(_hWnd, tbpFlags); - BalExitOnFailure1(hr, "Failed to set taskbar button state.", tbpFlags); - } - - LExit: - return; - } - - - void SetProgressState(__in HRESULT hrStatus) { - TBPFLAG flag = TBPF_NORMAL; - - if (IsCanceled() || HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) == hrStatus) { - flag = TBPF_PAUSED; - } else if (IsRollingBack() || FAILED(hrStatus)) { - flag = TBPF_ERROR; - } - - SetTaskbarButtonState(flag); - } - - - HRESULT LoadBootstrapperBAFunctions() { - HRESULT hr = S_OK; - LPWSTR sczBafPath = nullptr; - - hr = PathRelativeToModule(&sczBafPath, L"bafunctions.dll", _hModule); - BalExitOnFailure(hr, "Failed to get path to BA function DLL."); - -#ifdef DEBUG - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "PYBA: LoadBootstrapperBAFunctions() - BA function DLL %ls", sczBafPath); -#endif - - _hBAFModule = ::LoadLibraryW(sczBafPath); - if (_hBAFModule) { - auto pfnBAFunctionCreate = reinterpret_cast(::GetProcAddress(_hBAFModule, "CreateBootstrapperBAFunction")); - BalExitOnNullWithLastError1(pfnBAFunctionCreate, hr, "Failed to get CreateBootstrapperBAFunction entry-point from: %ls", sczBafPath); - - hr = pfnBAFunctionCreate(_engine, _hBAFModule, &_baFunction); - BalExitOnFailure(hr, "Failed to create BA function."); - } -#ifdef DEBUG - else { - BalLogError(HRESULT_FROM_WIN32(::GetLastError()), "PYBA: LoadBootstrapperBAFunctions() - Failed to load DLL %ls", sczBafPath); - } -#endif - - LExit: - if (_hBAFModule && !_baFunction) { - ::FreeLibrary(_hBAFModule); - _hBAFModule = nullptr; - } - ReleaseStr(sczBafPath); - - return hr; - } - - BOOL IsCheckable(THEME_CONTROL* pControl) { - if (!pControl->sczName || !pControl->sczName[0]) { - return FALSE; - } - - if (pControl->type == THEME_CONTROL_TYPE_CHECKBOX) { - return TRUE; - } - - if (pControl->type == THEME_CONTROL_TYPE_BUTTON) { - if ((pControl->dwStyle & BS_TYPEMASK) == BS_AUTORADIOBUTTON) { - return TRUE; - } - } - - return FALSE; - } - - void SavePageSettings() { - DWORD pageId = 0; - THEME_PAGE* pPage = nullptr; - - DeterminePageId(_state, &pageId); - pPage = ThemeGetPage(_theme, pageId); - if (!pPage) { - return; - } - - for (DWORD i = 0; i < pPage->cControlIndices; ++i) { - // Loop through all the checkable controls and set a Burn variable - // with that name to true or false. - THEME_CONTROL* pControl = _theme->rgControls + pPage->rgdwControlIndices[i]; - if (IsCheckable(pControl) && ThemeControlEnabled(_theme, pControl->wId)) { - BOOL checked = ThemeIsControlChecked(_theme, pControl->wId); - _engine->SetVariableNumeric(pControl->sczName, checked ? 1 : 0); - } - - // Loop through all the editbox controls with names and set a - // Burn variable with that name to the contents. - if (THEME_CONTROL_TYPE_EDITBOX == pControl->type && pControl->sczName && *pControl->sczName) { - LPWSTR sczValue = nullptr; - ThemeGetTextControl(_theme, pControl->wId, &sczValue); - _engine->SetVariableString(pControl->sczName, sczValue); - } - } - } - - static bool IsTargetPlatformx64(__in IBootstrapperEngine* pEngine) { - WCHAR platform[8]; - DWORD platformLen = 8; - - if (FAILED(pEngine->GetVariableString(L"TargetPlatform", platform, &platformLen))) { - return S_FALSE; - } - - return ::CompareStringW(LOCALE_NEUTRAL, 0, platform, -1, L"x64", -1) == CSTR_EQUAL; - } - - static bool IsTargetPlatformARM64(__in IBootstrapperEngine* pEngine) { - WCHAR platform[8]; - DWORD platformLen = 8; - - if (FAILED(pEngine->GetVariableString(L"TargetPlatform", platform, &platformLen))) { - return S_FALSE; - } - - return ::CompareStringW(LOCALE_NEUTRAL, 0, platform, -1, L"ARM64", -1) == CSTR_EQUAL; - } - - static HRESULT LoadOptionalFeatureStatesFromKey( - __in IBootstrapperEngine* pEngine, - __in HKEY hkHive, - __in LPCWSTR subkey - ) { - HKEY hKey; - LRESULT res; - - if (IsTargetPlatformx64(pEngine) || IsTargetPlatformARM64(pEngine)) { - res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); - } else { - res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_32KEY, &hKey); - } - if (res == ERROR_FILE_NOT_FOUND) { - return S_FALSE; - } - if (res != ERROR_SUCCESS) { - return HRESULT_FROM_WIN32(res); - } - - for (auto p = OPTIONAL_FEATURES; p->regName; ++p) { - res = RegQueryValueExW(hKey, p->regName, nullptr, nullptr, nullptr, nullptr); - if (res == ERROR_FILE_NOT_FOUND) { - pEngine->SetVariableNumeric(p->variableName, 0); - } else if (res == ERROR_SUCCESS) { - pEngine->SetVariableNumeric(p->variableName, 1); - } else { - RegCloseKey(hKey); - return HRESULT_FROM_WIN32(res); - } - } - - RegCloseKey(hKey); - return S_OK; - } - - static HRESULT LoadTargetDirFromKey( - __in IBootstrapperEngine* pEngine, - __in HKEY hkHive, - __in LPCWSTR subkey - ) { - HKEY hKey; - LRESULT res; - DWORD dataType; - BYTE buffer[1024]; - DWORD bufferLen = sizeof(buffer); - - if (IsTargetPlatformx64(pEngine) || IsTargetPlatformARM64(pEngine)) { - res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); - } else { - res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_32KEY, &hKey); - } - if (res == ERROR_FILE_NOT_FOUND) { - return S_FALSE; - } - if (res != ERROR_SUCCESS) { - return HRESULT_FROM_WIN32(res); - } - - res = RegQueryValueExW(hKey, nullptr, nullptr, &dataType, buffer, &bufferLen); - if (res == ERROR_SUCCESS && dataType == REG_SZ && bufferLen < sizeof(buffer)) { - pEngine->SetVariableString(L"TargetDir", reinterpret_cast(buffer)); - } - RegCloseKey(hKey); - return HRESULT_FROM_WIN32(res); - } - - static HRESULT LoadAssociateFilesStateFromKey( - __in IBootstrapperEngine* pEngine, - __in HKEY hkHive - ) { - const LPCWSTR subkey = L"Software\\Python\\PyLauncher"; - HKEY hKey; - LRESULT res; - HRESULT hr; - - res = RegOpenKeyExW(hkHive, subkey, 0, KEY_READ | KEY_WOW64_32KEY, &hKey); - - if (res == ERROR_FILE_NOT_FOUND) { - return S_FALSE; - } - if (res != ERROR_SUCCESS) { - return HRESULT_FROM_WIN32(res); - } - - res = RegQueryValueExW(hKey, L"AssociateFiles", nullptr, nullptr, nullptr, nullptr); - if (res == ERROR_FILE_NOT_FOUND) { - hr = S_FALSE; - } else if (res == ERROR_SUCCESS) { - hr = S_OK; - } else { - hr = HRESULT_FROM_WIN32(res); - } - - RegCloseKey(hKey); - return hr; - } - - static void LoadOptionalFeatureStates(__in IBootstrapperEngine* pEngine) { - WCHAR subkeyFmt[256]; - WCHAR subkey[256]; - DWORD subkeyLen; - HRESULT hr; - HKEY hkHive; - - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading state of optional features"); - - // Get the registry key from the bundle, to save having to duplicate it - // in multiple places. - subkeyLen = sizeof(subkeyFmt) / sizeof(subkeyFmt[0]); - hr = pEngine->GetVariableString(L"OptionalFeaturesRegistryKey", subkeyFmt, &subkeyLen); - BalExitOnFailure(hr, "Failed to locate registry key"); - subkeyLen = sizeof(subkey) / sizeof(subkey[0]); - hr = pEngine->FormatString(subkeyFmt, subkey, &subkeyLen); - BalExitOnFailure1(hr, "Failed to format %ls", subkeyFmt); - - // Check the current user's registry for existing features - hkHive = HKEY_CURRENT_USER; - hr = LoadOptionalFeatureStatesFromKey(pEngine, hkHive, subkey); - BalExitOnFailure1(hr, "Failed to read from HKCU\\%ls", subkey); - if (hr == S_FALSE) { - // Now check the local machine registry - hkHive = HKEY_LOCAL_MACHINE; - hr = LoadOptionalFeatureStatesFromKey(pEngine, hkHive, subkey); - BalExitOnFailure1(hr, "Failed to read from HKLM\\%ls", subkey); - if (hr == S_OK) { - // Found a system-wide install, so enable these settings. - pEngine->SetVariableNumeric(L"InstallAllUsers", 1); - pEngine->SetVariableNumeric(L"CompileAll", 1); - } - } - - if (hr == S_OK) { - // Cannot change InstallAllUsersState when upgrading. While there's - // no good reason to not allow installing a per-user and an all-user - // version simultaneously, Burn can't handle the state management - // and will need to uninstall the old one. - pEngine->SetVariableString(L"InstallAllUsersState", L"disable"); - - // Get the previous install directory. This can be changed by the - // user. - subkeyLen = sizeof(subkeyFmt) / sizeof(subkeyFmt[0]); - hr = pEngine->GetVariableString(L"TargetDirRegistryKey", subkeyFmt, &subkeyLen); - BalExitOnFailure(hr, "Failed to locate registry key"); - subkeyLen = sizeof(subkey) / sizeof(subkey[0]); - hr = pEngine->FormatString(subkeyFmt, subkey, &subkeyLen); - BalExitOnFailure1(hr, "Failed to format %ls", subkeyFmt); - LoadTargetDirFromKey(pEngine, hkHive, subkey); - } - - LExit: - return; - } - - HRESULT EnsureTargetDir() { - LONGLONG installAllUsers; - LPWSTR targetDir = nullptr, defaultDir = nullptr; - HRESULT hr = BalGetStringVariable(L"TargetDir", &targetDir); - if (FAILED(hr) || !targetDir || !targetDir[0]) { - ReleaseStr(targetDir); - targetDir = nullptr; - - hr = BalGetNumericVariable(L"InstallAllUsers", &installAllUsers); - ExitOnFailure(hr, L"Failed to get install scope"); - - hr = BalGetStringVariable( - installAllUsers ? L"DefaultAllUsersTargetDir" : L"DefaultJustForMeTargetDir", - &defaultDir - ); - BalExitOnFailure(hr, "Failed to get the default install directory"); - - if (!defaultDir || !defaultDir[0]) { - BalLogError(E_INVALIDARG, "Default install directory is blank"); - } - - hr = BalFormatString(defaultDir, &targetDir); - BalExitOnFailure1(hr, "Failed to format '%ls'", defaultDir); - - hr = _engine->SetVariableString(L"TargetDir", targetDir); - BalExitOnFailure(hr, "Failed to set install target directory"); - } - LExit: - ReleaseStr(defaultDir); - ReleaseStr(targetDir); - return hr; - } - - void ValidateOperatingSystem() { - LOC_STRING *pLocString = nullptr; - - if (IsWindowsServer()) { - if (IsWindowsVersionOrGreater(10, 0, 0)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Target OS is Windows Server 2016 or later"); - return; - } else if (IsWindowsVersionOrGreater(6, 2, 0)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2012"); - } else if (IsWindowsVersionOrGreater(6, 1, 1)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2008 R2"); - } else if (IsWindowsVersionOrGreater(6, 1, 0)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2008 R2"); - } else if (IsWindowsVersionOrGreater(6, 0, 0)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2008"); - } else { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Server 2003 or earlier"); - } - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Windows Server 2016 or later is required to continue installation"); - } else { - if (IsWindows10OrGreater()) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Target OS is Windows 10 or later"); - return; - } else if (IsWindows8Point1OrGreater()) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows 8.1"); - } else if (IsWindows8OrGreater()) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows 8"); - } else if (IsWindows7OrGreater()) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows 7"); - } else if (IsWindowsVistaOrGreater()) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows Vista"); - } else { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Detected Windows XP or earlier"); - } - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Windows 10 or later is required to continue installation"); - } - - LocGetString(_wixLoc, L"#(loc.FailureOldOS)", &pLocString); - if (pLocString && pLocString->wzText) { - BalFormatString(pLocString->wzText, &_failedMessage); - } - - _hrFinal = E_WIXSTDBA_CONDITION_FAILED; - } - -public: - // - // Constructor - initialize member variables. - // - PythonBootstrapperApplication( - __in HMODULE hModule, - __in BOOL fPrereq, - __in HRESULT hrHostInitialization, - __in IBootstrapperEngine* pEngine, - __in const BOOTSTRAPPER_COMMAND* pCommand - ) : CBalBaseBootstrapperApplication(pEngine, pCommand, 3, 3000) { - _hModule = hModule; - memcpy_s(&_command, sizeof(_command), pCommand, sizeof(BOOTSTRAPPER_COMMAND)); - - LONGLONG llInstalled = 0; - HRESULT hr = BalGetNumericVariable(L"WixBundleInstalled", &llInstalled); - if (SUCCEEDED(hr) && BOOTSTRAPPER_RESUME_TYPE_REBOOT != _command.resumeType && 0 < llInstalled && BOOTSTRAPPER_ACTION_INSTALL == _command.action) { - _command.action = BOOTSTRAPPER_ACTION_MODIFY; - } else if (0 == llInstalled && (BOOTSTRAPPER_ACTION_MODIFY == _command.action || BOOTSTRAPPER_ACTION_REPAIR == _command.action)) { - _command.action = BOOTSTRAPPER_ACTION_INSTALL; - } - - _plannedAction = BOOTSTRAPPER_ACTION_UNKNOWN; - - - // When resuming from restart doing some install-like operation, try to find the package that forced the - // restart. We'll use this information during planning. - _nextPackageAfterRestart = nullptr; - - if (BOOTSTRAPPER_RESUME_TYPE_REBOOT == _command.resumeType && BOOTSTRAPPER_ACTION_UNINSTALL < _command.action) { - // Ensure the forced restart package variable is null when it is an empty string. - HRESULT hr = BalGetStringVariable(L"WixBundleForcedRestartPackage", &_nextPackageAfterRestart); - if (FAILED(hr) || !_nextPackageAfterRestart || !*_nextPackageAfterRestart) { - ReleaseNullStr(_nextPackageAfterRestart); - } - } - - _crtInstalledToken = -1; - pEngine->SetVariableNumeric(L"CRTInstalled", IsCrtInstalled() ? 1 : 0); - - _wixLoc = nullptr; - memset(&_bundle, 0, sizeof(_bundle)); - memset(&_conditions, 0, sizeof(_conditions)); - _confirmCloseMessage = nullptr; - _failedMessage = nullptr; - - _language = nullptr; - _theme = nullptr; - memset(_pageIds, 0, sizeof(_pageIds)); - _hUiThread = nullptr; - _registered = FALSE; - _hWnd = nullptr; - - _state = PYBA_STATE_INITIALIZING; - _visiblePageId = 0; - _installPage = PAGE_LOADING; - _hrFinal = hrHostInitialization; - - _downgradingOtherVersion = FALSE; - _restartResult = BOOTSTRAPPER_APPLY_RESTART_NONE; - _restartRequired = FALSE; - _allowRestart = FALSE; - - _suppressDowngradeFailure = FALSE; - _suppressRepair = FALSE; - _modifying = FALSE; - _upgrading = FALSE; - - _overridableVariables = nullptr; - _taskbarList = nullptr; - _taskbarButtonCreatedMessage = UINT_MAX; - _taskbarButtonOK = FALSE; - _showingInternalUIThisPackage = FALSE; - - _suppressPaint = FALSE; - - pEngine->AddRef(); - _engine = pEngine; - - _hBAFModule = nullptr; - _baFunction = nullptr; - } - - - // - // Destructor - release member variables. - // - ~PythonBootstrapperApplication() { - AssertSz(!::IsWindow(_hWnd), "Window should have been destroyed before destructor."); - AssertSz(!_theme, "Theme should have been released before destructor."); - - ReleaseObject(_taskbarList); - ReleaseDict(_overridableVariables); - ReleaseStr(_failedMessage); - ReleaseStr(_confirmCloseMessage); - BalConditionsUninitialize(&_conditions); - BalInfoUninitialize(&_bundle); - LocFree(_wixLoc); - - ReleaseStr(_language); - ReleaseStr(_nextPackageAfterRestart); - ReleaseNullObject(_engine); - - if (_hBAFModule) { - ::FreeLibrary(_hBAFModule); - _hBAFModule = nullptr; - } - } - -private: - HMODULE _hModule; - BOOTSTRAPPER_COMMAND _command; - IBootstrapperEngine* _engine; - BOOTSTRAPPER_ACTION _plannedAction; - - LPWSTR _nextPackageAfterRestart; - - WIX_LOCALIZATION* _wixLoc; - BAL_INFO_BUNDLE _bundle; - BAL_CONDITIONS _conditions; - LPWSTR _failedMessage; - LPWSTR _confirmCloseMessage; - - LPWSTR _language; - THEME* _theme; - DWORD _pageIds[countof(PAGE_NAMES)]; - HANDLE _hUiThread; - BOOL _registered; - HWND _hWnd; - - PYBA_STATE _state; - HRESULT _hrFinal; - DWORD _visiblePageId; - PAGE _installPage; - - BOOL _startedExecution; - DWORD _calculatedCacheProgress; - DWORD _calculatedExecuteProgress; - - BOOL _downgradingOtherVersion; - BOOTSTRAPPER_APPLY_RESTART _restartResult; - BOOL _restartRequired; - BOOL _allowRestart; - - BOOL _suppressDowngradeFailure; - BOOL _suppressRepair; - BOOL _modifying; - BOOL _upgrading; - - int _crtInstalledToken; - - STRINGDICT_HANDLE _overridableVariables; - - ITaskbarList3* _taskbarList; - UINT _taskbarButtonCreatedMessage; - BOOL _taskbarButtonOK; - BOOL _showingInternalUIThisPackage; - - BOOL _suppressPaint; - - HMODULE _hBAFModule; - IBootstrapperBAFunction* _baFunction; -}; - -// -// CreateBootstrapperApplication - creates a new IBootstrapperApplication object. -// -HRESULT CreateBootstrapperApplication( - __in HMODULE hModule, - __in BOOL fPrereq, - __in HRESULT hrHostInitialization, - __in IBootstrapperEngine* pEngine, - __in const BOOTSTRAPPER_COMMAND* pCommand, - __out IBootstrapperApplication** ppApplication - ) { - HRESULT hr = S_OK; - - if (fPrereq) { - hr = E_INVALIDARG; - ExitWithLastError(hr, "Failed to create UI thread."); - } - - PythonBootstrapperApplication* pApplication = nullptr; - - pApplication = new PythonBootstrapperApplication(hModule, fPrereq, hrHostInitialization, pEngine, pCommand); - ExitOnNull(pApplication, hr, E_OUTOFMEMORY, "Failed to create new standard bootstrapper application object."); - - *ppApplication = pApplication; - pApplication = nullptr; - -LExit: - ReleaseObject(pApplication); - return hr; -} diff --git a/Tools/msi/bundle/bootstrap/pch.cpp b/Tools/msi/bundle/bootstrap/pch.cpp deleted file mode 100644 index 1d9f38c57d63f19..000000000000000 --- a/Tools/msi/bundle/bootstrap/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" diff --git a/Tools/msi/bundle/bootstrap/pch.h b/Tools/msi/bundle/bootstrap/pch.h deleted file mode 100644 index 6d0974b34c61e79..000000000000000 --- a/Tools/msi/bundle/bootstrap/pch.h +++ /dev/null @@ -1,60 +0,0 @@ -//------------------------------------------------------------------------------------------------- -// -// Copyright (c) 2004, Outercurve Foundation. -// This software is released under Microsoft Reciprocal License (MS-RL). -// The license and further copyright text can be found in the file -// LICENSE.TXT at the root directory of the distribution. -// -// -// -// Precompiled header for standard bootstrapper application. -// -//------------------------------------------------------------------------------------------------- - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dutil.h" -#include "memutil.h" -#include "dictutil.h" -#include "dirutil.h" -#include "fileutil.h" -#include "locutil.h" -#include "logutil.h" -#include "pathutil.h" -#include "resrutil.h" -#include "shelutil.h" -#include "strutil.h" -#include "thmutil.h" -#include "uriutil.h" -#include "xmlutil.h" - -#include "IBootstrapperEngine.h" -#include "IBootstrapperApplication.h" - -#include "BalBaseBootstrapperApplication.h" -#include "balinfo.h" -#include "balcondition.h" - -HRESULT CreateBootstrapperApplication( - __in HMODULE hModule, - __in BOOL fPrereq, - __in HRESULT hrHostInitialization, - __in IBootstrapperEngine* pEngine, - __in const BOOTSTRAPPER_COMMAND* pCommand, - __out IBootstrapperApplication** ppApplication -); - -#include "IBootstrapperBAFunction.h" - diff --git a/Tools/msi/bundle/bootstrap/pythonba.cpp b/Tools/msi/bundle/bootstrap/pythonba.cpp deleted file mode 100644 index 0ce45ad31d958a5..000000000000000 --- a/Tools/msi/bundle/bootstrap/pythonba.cpp +++ /dev/null @@ -1,76 +0,0 @@ -//------------------------------------------------------------------------------------------------- -// -// Copyright (c) 2004, Outercurve Foundation. -// This software is released under Microsoft Reciprocal License (MS-RL). -// The license and further copyright text can be found in the file -// LICENSE.TXT at the root directory of the distribution. -// -// -// -// Setup chainer/bootstrapper standard UI for WiX toolset. -// -//------------------------------------------------------------------------------------------------- - -#include "pch.h" - -static HINSTANCE vhInstance = NULL; - -extern "C" BOOL WINAPI DllMain( - IN HINSTANCE hInstance, - IN DWORD dwReason, - IN LPVOID /* pvReserved */ - ) -{ - switch(dwReason) - { - case DLL_PROCESS_ATTACH: - ::DisableThreadLibraryCalls(hInstance); - vhInstance = hInstance; - break; - - case DLL_PROCESS_DETACH: - vhInstance = NULL; - break; - } - - return TRUE; -} - - -extern "C" HRESULT WINAPI BootstrapperApplicationCreate( - __in IBootstrapperEngine* pEngine, - __in const BOOTSTRAPPER_COMMAND* pCommand, - __out IBootstrapperApplication** ppApplication - ) -{ - HRESULT hr = S_OK; - - BalInitialize(pEngine); - - hr = CreateBootstrapperApplication(vhInstance, FALSE, S_OK, pEngine, pCommand, ppApplication); - BalExitOnFailure(hr, "Failed to create bootstrapper application interface."); - -LExit: - return hr; -} - - -extern "C" void WINAPI BootstrapperApplicationDestroy() -{ - BalUninitialize(); -} - - -extern "C" HRESULT WINAPI MbaPrereqBootstrapperApplicationCreate( - __in HRESULT hrHostInitialization, - __in IBootstrapperEngine* pEngine, - __in const BOOTSTRAPPER_COMMAND* pCommand, - __out IBootstrapperApplication** ppApplication - ) -{ - return E_NOTIMPL; -} - - -extern "C" void WINAPI MbaPrereqBootstrapperApplicationDestroy() -{ } diff --git a/Tools/msi/bundle/bootstrap/pythonba.def b/Tools/msi/bundle/bootstrap/pythonba.def deleted file mode 100644 index 29b3fa50dfa9f70..000000000000000 --- a/Tools/msi/bundle/bootstrap/pythonba.def +++ /dev/null @@ -1,18 +0,0 @@ -;------------------------------------------------------------------------------------------------- -; -; Copyright (c) 2004, Outercurve Foundation. -; This software is released under Microsoft Reciprocal License (MS-RL). -; The license and further copyright text can be found in the file -; LICENSE.TXT at the root directory of the distribution. -; -; -; -; WiX Standard Bootstrapper Application DLL entry points. -; -;------------------------------------------------------------------------------------------------- - -EXPORTS - BootstrapperApplicationCreate - BootstrapperApplicationDestroy - MbaPrereqBootstrapperApplicationCreate - MbaPrereqBootstrapperApplicationDestroy diff --git a/Tools/msi/bundle/bootstrap/pythonba.sln b/Tools/msi/bundle/bootstrap/pythonba.sln deleted file mode 100644 index bf43fed9018e4f3..000000000000000 --- a/Tools/msi/bundle/bootstrap/pythonba.sln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30501.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pythonba", "pythonba.vcxproj", "{7A09B132-B3EE-499B-A700-A4B2157FEA3D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7A09B132-B3EE-499B-A700-A4B2157FEA3D}.Debug|Win32.ActiveCfg = Debug|Win32 - {7A09B132-B3EE-499B-A700-A4B2157FEA3D}.Debug|Win32.Build.0 = Debug|Win32 - {7A09B132-B3EE-499B-A700-A4B2157FEA3D}.Release|Win32.ActiveCfg = Release|Win32 - {7A09B132-B3EE-499B-A700-A4B2157FEA3D}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/Tools/msi/bundle/bootstrap/pythonba.vcxproj b/Tools/msi/bundle/bootstrap/pythonba.vcxproj deleted file mode 100644 index 4b38582f0d31e51..000000000000000 --- a/Tools/msi/bundle/bootstrap/pythonba.vcxproj +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - Debug - Win32 - - - Release - Win32 - - - - Release - Win32 - v145 - v143 - v142 - v141 - {7A09B132-B3EE-499B-A700-A4B2157FEA3D} - PythonBA - - - - - DynamicLibrary - Unicode - $(PySourcePath)PCbuild\obj\ - $(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)$(ArchName)_$(Configuration)\msi_$(ProjectName)\ - $(IntDir.Replace(`\\`, `\`)) - $(IntDir) - - - - - ARM64=1;%(PreprocessorDefinitions) - _CRT_STDIO_LEGACY_WIDE_SPECIFIERS=1;%(PreprocessorDefinitions) - $(WixInstallPath)sdk\inc - Use - pch.h - MultiThreaded - - - comctl32.lib;gdiplus.lib;msimg32.lib;shlwapi.lib;wininet.lib;dutil.lib;balutil.lib;version.lib;uxtheme.lib;%(AdditionalDependencies) - $(WixInstallPath)sdk\vs2017\lib\x86 - pythonba.def - true - - - - - - - Create - - - - - - - - - - - diff --git a/Tools/msi/bundle/bootstrap/resource.h b/Tools/msi/bundle/bootstrap/resource.h deleted file mode 100644 index d951e651f6d20d7..000000000000000 --- a/Tools/msi/bundle/bootstrap/resource.h +++ /dev/null @@ -1,25 +0,0 @@ -//------------------------------------------------------------------------------------------------- -// -// Copyright (c) 2004, Outercurve Foundation. -// This software is released under Microsoft Reciprocal License (MS-RL). -// The license and further copyright text can be found in the file -// LICENSE.TXT at the root directory of the distribution. -// -//------------------------------------------------------------------------------------------------- - -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// -#define IDC_STATIC -1 - - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1003 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/Tools/msi/bundle/bundle.targets b/Tools/msi/bundle/bundle.targets deleted file mode 100644 index cb3effb4434843a..000000000000000 --- a/Tools/msi/bundle/bundle.targets +++ /dev/null @@ -1,129 +0,0 @@ - - - - 2.0 - Bundle - - Release - 1132;1135;1140 - $(OutputName)-$(PythonVersion) - $(OutputName)-$(MajorVersionNumber).$(MinorVersionNumber).$(MicroVersionNumber).$(RevisionNumber) - $(OutputName)-amd64 - $(OutputName)-arm64 - $(OutputName)-$(OutputSuffix) - $(OutputName)-d - $(OutputName) - - $(OutputPath)en-us\ - $(OutputPath) - - - $(DownloadUrlBase.TrimEnd(`/`))/{version}/{arch}{releasename}/{msi} - $(DefineConstants);DownloadUrl=$(DownloadUrl.Replace(`{version}`, `$(MajorVersionNumber).$(MinorVersionNumber).$(MicroVersionNumber)`).Replace(`{arch}`, `$(ArchName)`).Replace(`{releasename}`, `$(ReleaseLevelName)`).Replace(`{msi}`, `{2}`)) - $(DefineConstants);DownloadUrl={2} - - - - - WixUtilExtension - WixUtilExtension - - - WixDependencyExtension - WixDependencyExtension - - - WixBalExtension - WixBalExtension - - - - - - - - - - - - - - - - - - - - - - - BuildForRelease=$(BuildForRelease) - - - - - - - - - - - - - - - - - - - Build - - - - - Rebuild - - - - - - - - - - - - - - - - $(DefineConstants);BootstrapApp=$(BootstrapAppPath) - - - - - - - - - $(DefineConstants);DefaultWxl=Default.wxl - - - - - - - - - $(DefineConstants);DefaultWxl=$(IntermediateOutputPath)Default.wxl - - - - - diff --git a/Tools/msi/bundle/bundle.wxl b/Tools/msi/bundle/bundle.wxl deleted file mode 100644 index d7a65c48dc4dc53..000000000000000 --- a/Tools/msi/bundle/bundle.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - C Runtime Update (KB2999226) - Precompiling standard library - Precompiling standard library (-O) - Precompiling standard library (-OO) - diff --git a/Tools/msi/bundle/bundle.wxs b/Tools/msi/bundle/bundle.wxs deleted file mode 100644 index 3fcb00553f5edd3..000000000000000 --- a/Tools/msi/bundle/bundle.wxs +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/bundle/full.wixproj b/Tools/msi/bundle/full.wixproj deleted file mode 100644 index bdbdd8e175c0332..000000000000000 --- a/Tools/msi/bundle/full.wixproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - {3E204ADD-238D-4D10-852C-4F859325C839} - python - full - - - - - - - $(DefineConstants); - CompressMSI=yes; - CompressPDB=yes; - CompressMSI_D=yes; - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/core.wxs b/Tools/msi/bundle/packagegroups/core.wxs deleted file mode 100644 index eb3d0b7733cf0fd..000000000000000 --- a/Tools/msi/bundle/packagegroups/core.wxs +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/crt.wxs b/Tools/msi/bundle/packagegroups/crt.wxs deleted file mode 100644 index f931da1403b2278..000000000000000 --- a/Tools/msi/bundle/packagegroups/crt.wxs +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/dev.wxs b/Tools/msi/bundle/packagegroups/dev.wxs deleted file mode 100644 index 4284dba2c205b73..000000000000000 --- a/Tools/msi/bundle/packagegroups/dev.wxs +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/doc.wxs b/Tools/msi/bundle/packagegroups/doc.wxs deleted file mode 100644 index 6639ff5c25b24be..000000000000000 --- a/Tools/msi/bundle/packagegroups/doc.wxs +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/exe.wxs b/Tools/msi/bundle/packagegroups/exe.wxs deleted file mode 100644 index 79464c4aca66222..000000000000000 --- a/Tools/msi/bundle/packagegroups/exe.wxs +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/freethreaded.wxs b/Tools/msi/bundle/packagegroups/freethreaded.wxs deleted file mode 100644 index 121ca34ab661578..000000000000000 --- a/Tools/msi/bundle/packagegroups/freethreaded.wxs +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/launcher.wxs b/Tools/msi/bundle/packagegroups/launcher.wxs deleted file mode 100644 index 080598a0a486ef4..000000000000000 --- a/Tools/msi/bundle/packagegroups/launcher.wxs +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/lib.wxs b/Tools/msi/bundle/packagegroups/lib.wxs deleted file mode 100644 index 0b3fbc00c4a35db..000000000000000 --- a/Tools/msi/bundle/packagegroups/lib.wxs +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/packageinstall.wxs b/Tools/msi/bundle/packagegroups/packageinstall.wxs deleted file mode 100644 index e5e7d4d1a94cd1f..000000000000000 --- a/Tools/msi/bundle/packagegroups/packageinstall.wxs +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/pip.wxs b/Tools/msi/bundle/packagegroups/pip.wxs deleted file mode 100644 index 201a6c445b23691..000000000000000 --- a/Tools/msi/bundle/packagegroups/pip.wxs +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/postinstall.wxs b/Tools/msi/bundle/packagegroups/postinstall.wxs deleted file mode 100644 index 24c72e6357ab082..000000000000000 --- a/Tools/msi/bundle/packagegroups/postinstall.wxs +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/bundle/packagegroups/tcltk.wxs b/Tools/msi/bundle/packagegroups/tcltk.wxs deleted file mode 100644 index 0d029a90cd966ed..000000000000000 --- a/Tools/msi/bundle/packagegroups/tcltk.wxs +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/packagegroups/test.wxs b/Tools/msi/bundle/packagegroups/test.wxs deleted file mode 100644 index 32acaef64efea07..000000000000000 --- a/Tools/msi/bundle/packagegroups/test.wxs +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/releaselocal.wixproj b/Tools/msi/bundle/releaselocal.wixproj deleted file mode 100644 index 0c3dee7ad81ea19..000000000000000 --- a/Tools/msi/bundle/releaselocal.wixproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - {FCD43AC9-969F-49A1-8AC5-EDC27599D1EB} - python - - - - - - - - $(DefineConstants); - CompressMSI=yes; - CompressPDB=no; - CompressMSI_D=no - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/releaseweb.wixproj b/Tools/msi/bundle/releaseweb.wixproj deleted file mode 100644 index 350c735878c0b4a..000000000000000 --- a/Tools/msi/bundle/releaseweb.wixproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - {71CDE213-CB39-4BD9-B89D-BBB878689144} - python - webinstall - - - - - - - $(DefineConstants); - CompressMSI=no; - CompressPDB=no; - CompressMSI_D=no - - - - - \ No newline at end of file diff --git a/Tools/msi/bundle/snapshot.wixproj b/Tools/msi/bundle/snapshot.wixproj deleted file mode 100644 index cc450432990635e..000000000000000 --- a/Tools/msi/bundle/snapshot.wixproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - {8A4A1162-4BF9-4FF6-9A98-315F01E44932} - python - - - - - - - - $(DefineConstants);CompressMSI=no; - - - $(DefineConstants);CompressMSI=yes; - - - $(DefineConstants); - CompressPDB=no; - CompressMSI_D=no; - - - - - \ No newline at end of file diff --git a/Tools/msi/common.wxs b/Tools/msi/common.wxs deleted file mode 100644 index 73da474e4181f11..000000000000000 --- a/Tools/msi/common.wxs +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - OPTIONALFEATURESREGISTRYKEY - - - - - - - - - - - - - - - - - - - - - Installed OR NOT MISSING_CORE - - - - Installed OR NOT DOWNGRADE - Installed OR TARGETDIR OR Suppress_TARGETDIR_Check - - - UPGRADE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/common_en-US.wxl_template b/Tools/msi/common_en-US.wxl_template deleted file mode 100644 index adb8f40be9a8e23..000000000000000 --- a/Tools/msi/common_en-US.wxl_template +++ /dev/null @@ -1,18 +0,0 @@ - - - 1033 - en-us - Python {{ShortVersion}} - Python {{LongVersion}} ({{Bitness}}) - Python {{LongVersion}} !(loc.Descriptor) ({{Bitness}}) - Python {{LongVersion}} !(loc.Descriptor) ({{Bitness}}) - Python {{LongVersion}} !(loc.Descriptor) ({{Bitness}} symbols) - Python {{LongVersion}} !(loc.Descriptor) ({{Bitness}} symbols) - Python {{LongVersion}} !(loc.Descriptor) ({{Bitness}} debug) - Python {{LongVersion}} !(loc.Descriptor) ({{Bitness}} debug) - Python Software Foundation - A newer version of !(loc.ProductName) is already installed. - An incorrect version of a prerequisite package is installed. Please uninstall any other versions of !(loc.ProductName) and try installing this again. - The TARGETDIR variable must be provided when invoking this installer. - https://www.python.org/ - diff --git a/Tools/msi/core/core.wixproj b/Tools/msi/core/core.wixproj deleted file mode 100644 index 68e8bab3109dcce..000000000000000 --- a/Tools/msi/core/core.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {1B4502D5-B627-4F50-ABEA-4CC5A8E88265} - 2.0 - core - Package - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/core/core.wxs b/Tools/msi/core/core.wxs deleted file mode 100644 index 21a8bcdf9710174..000000000000000 --- a/Tools/msi/core/core.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/msi/core/core_d.wixproj b/Tools/msi/core/core_d.wixproj deleted file mode 100644 index 5b296bf35f95807..000000000000000 --- a/Tools/msi/core/core_d.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {D3677DCF-098A-4398-9FA5-8E74AC37E0DF} - 2.0 - core_d - Package - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/core/core_d.wxs b/Tools/msi/core/core_d.wxs deleted file mode 100644 index 1cee0973aac312f..000000000000000 --- a/Tools/msi/core/core_d.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Tools/msi/core/core_en-US.wxl b/Tools/msi/core/core_en-US.wxl deleted file mode 100644 index 7977470d7c1c0eb..000000000000000 --- a/Tools/msi/core/core_en-US.wxl +++ /dev/null @@ -1,5 +0,0 @@ - - - Core Interpreter - core - diff --git a/Tools/msi/core/core_files.wxs b/Tools/msi/core/core_files.wxs deleted file mode 100644 index 8b21501078ea2e6..000000000000000 --- a/Tools/msi/core/core_files.wxs +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/core/core_pdb.wixproj b/Tools/msi/core/core_pdb.wixproj deleted file mode 100644 index 9c8838970b6fee9..000000000000000 --- a/Tools/msi/core/core_pdb.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {E98E7539-64E7-4DCE-AACD-01E3ADE40EFD} - 2.0 - core_pdb - Package - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/core/core_pdb.wxs b/Tools/msi/core/core_pdb.wxs deleted file mode 100644 index 1fe7d1e7a944cb9..000000000000000 --- a/Tools/msi/core/core_pdb.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Tools/msi/csv_to_wxs.py b/Tools/msi/csv_to_wxs.py deleted file mode 100644 index 235c8f8b0be5812..000000000000000 --- a/Tools/msi/csv_to_wxs.py +++ /dev/null @@ -1,127 +0,0 @@ -''' -Processes a CSV file containing a list of files into a WXS file with -components for each listed file. - -The CSV columns are: - source of file, target for file, group name - -Usage:: - py txt_to_wxs.py [path to file list .csv] [path to destination .wxs] - -This is necessary to handle structures where some directories only -contain other directories. MSBuild is not able to generate the -Directory entries in the WXS file correctly, as it operates on files. -Python, however, can easily fill in the gap. -''' - -__author__ = "Steve Dower " - -import csv -import re -import sys - -from collections import defaultdict -from itertools import chain, zip_longest -from pathlib import PureWindowsPath -from uuid import uuid1 - -ID_CHAR_SUBS = { - '-': '_', - '+': '_P', -} - -def make_id(path): - return re.sub( - r'[^A-Za-z0-9_.]', - lambda m: ID_CHAR_SUBS.get(m.group(0), '_'), - str(path).rstrip('/\\'), - flags=re.I - ) - -DIRECTORIES = set() - -def main(file_source, install_target): - with open(file_source, 'r', newline='') as f: - files = list(csv.reader(f)) - - assert len(files) == len(set(make_id(f[1]) for f in files)), "Duplicate file IDs exist" - - directories = defaultdict(set) - cache_directories = defaultdict(set) - groups = defaultdict(list) - for source, target, group, disk_id, condition in files: - target = PureWindowsPath(target) - groups[group].append((source, target, disk_id, condition)) - - if target.suffix.lower() in {".py", ".pyw"}: - cache_directories[group].add(target.parent) - - for dirname in target.parents: - parent = make_id(dirname.parent) - if parent and parent != '.': - directories[parent].add(dirname.name) - - lines = [ - '', - ' ', - ] - for dir_parent in sorted(directories): - lines.append(' '.format(dir_parent)) - for dir_name in sorted(directories[dir_parent]): - lines.append(' '.format(dir_parent, make_id(dir_name), dir_name)) - lines.append(' ') - for dir_parent in (make_id(d) for group in cache_directories.values() for d in group): - lines.append(' '.format(dir_parent)) - lines.append(' '.format(dir_parent)) - lines.append(' ') - lines.append(' ') - - for group in sorted(groups): - lines.extend([ - ' ', - ' '.format(group), - ]) - for source, target, disk_id, condition in groups[group]: - lines.append(' '.format(make_id(target), make_id(target.parent))) - if condition: - lines.append(' {}'.format(condition)) - - if disk_id: - lines.append(' '.format(make_id(target), target.name, source, disk_id)) - else: - lines.append(' '.format(make_id(target), target.name, source)) - lines.append(' ') - - create_folders = {make_id(p) + "___pycache__" for p in cache_directories[group]} - remove_folders = {make_id(p2) for p1 in cache_directories[group] for p2 in chain((p1,), p1.parents)} - create_folders.discard(".") - remove_folders.discard(".") - if create_folders or remove_folders: - lines.append(' '.format(group, uuid1())) - lines.extend(' '.format(p) for p in create_folders) - lines.extend(' '.format(p) for p in create_folders) - lines.extend(' '.format(p) for p in create_folders | remove_folders) - lines.append(' ') - - lines.extend([ - ' ', - ' ', - ]) - lines.append('') - - # Check if the file matches. If so, we don't want to touch it so - # that we can skip rebuilding. - try: - with open(install_target, 'r') as f: - if all(x.rstrip('\r\n') == y for x, y in zip_longest(f, lines)): - print('File is up to date') - return - except IOError: - pass - - with open(install_target, 'w') as f: - f.writelines(line + '\n' for line in lines) - print('Wrote {} lines to {}'.format(len(lines), install_target)) - -if __name__ == '__main__': - main(sys.argv[1], sys.argv[2]) diff --git a/Tools/msi/dev/dev.wixproj b/Tools/msi/dev/dev.wixproj deleted file mode 100644 index 4052e4b5368273a..000000000000000 --- a/Tools/msi/dev/dev.wixproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - {5F23F608-D74B-4259-A0CE-8DC65CC7FE53} - 2.0 - dev - Package - - - - - - - - - - - - $(PySourcePath) - !(bindpath.src) - $(PySourcePath) - - dev_include - - - - - diff --git a/Tools/msi/dev/dev.wxs b/Tools/msi/dev/dev.wxs deleted file mode 100644 index 15a08364bead043..000000000000000 --- a/Tools/msi/dev/dev.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/dev/dev_d.wixproj b/Tools/msi/dev/dev_d.wixproj deleted file mode 100644 index b3b05326d41963f..000000000000000 --- a/Tools/msi/dev/dev_d.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {C11B4945-76BD-4137-B2E3-649460117A77} - 2.0 - dev_d - Package - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/dev/dev_d.wxs b/Tools/msi/dev/dev_d.wxs deleted file mode 100644 index 20bf9ee256f2f85..000000000000000 --- a/Tools/msi/dev/dev_d.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/msi/dev/dev_en-US.wxl b/Tools/msi/dev/dev_en-US.wxl deleted file mode 100644 index 2546e13e47a091d..000000000000000 --- a/Tools/msi/dev/dev_en-US.wxl +++ /dev/null @@ -1,5 +0,0 @@ - - - Development Libraries - dev - diff --git a/Tools/msi/dev/dev_files.wxs b/Tools/msi/dev/dev_files.wxs deleted file mode 100644 index a9039d03f5f6fa1..000000000000000 --- a/Tools/msi/dev/dev_files.wxs +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/doc/doc.wixproj b/Tools/msi/doc/doc.wixproj deleted file mode 100644 index 83687968faebea8..000000000000000 --- a/Tools/msi/doc/doc.wixproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - {0D62A2BB-5F71-4447-8C8C-9708407B3674} - 2.0 - doc - Package - - ICE43 - - - - $(PySourcePath)Doc\build\html\ - $(DocHtmlPath)\ - - - - doc_html - - - - - - - - - $(DocHtmlPath) - !(bindpath.doc_html) - $(DocHtmlPath)..\ - Doc_ - doc_html - - - - - \ No newline at end of file diff --git a/Tools/msi/doc/doc.wxs b/Tools/msi/doc/doc.wxs deleted file mode 100644 index 49798c752633fb2..000000000000000 --- a/Tools/msi/doc/doc.wxs +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/doc/doc_en-US.wxl_template b/Tools/msi/doc/doc_en-US.wxl_template deleted file mode 100644 index 809556e29fb9b13..000000000000000 --- a/Tools/msi/doc/doc_en-US.wxl_template +++ /dev/null @@ -1,7 +0,0 @@ - - - doc - Documentation - Python {{ShortVersion}} Manuals ({{Bitness}}) - View the !(loc.ProductName) documentation. - diff --git a/Tools/msi/exe/exe.wixproj b/Tools/msi/exe/exe.wixproj deleted file mode 100644 index 592c8d2f65ae6e0..000000000000000 --- a/Tools/msi/exe/exe.wixproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - {6BD53305-B03E-49DC-85FB-5551B8CCC843} - 2.0 - exe - Package - - - - ICE43 - - - - $(DefineConstants);Include_Vcruntime140_1_dll=1 - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/exe/exe.wxs b/Tools/msi/exe/exe.wxs deleted file mode 100644 index 05a8d775193e38f..000000000000000 --- a/Tools/msi/exe/exe.wxs +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/exe/exe_d.wixproj b/Tools/msi/exe/exe_d.wixproj deleted file mode 100644 index cf085bed4ddcf95..000000000000000 --- a/Tools/msi/exe/exe_d.wixproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - {B1CA739C-8DB0-403B-9010-D79507507CE9} - 2.0 - exe_d - Package - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/exe/exe_d.wxs b/Tools/msi/exe/exe_d.wxs deleted file mode 100644 index 3b327bc26d7893c..000000000000000 --- a/Tools/msi/exe/exe_d.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/msi/exe/exe_en-US.wxl_template b/Tools/msi/exe/exe_en-US.wxl_template deleted file mode 100644 index 4cd9e3e654b2a1e..000000000000000 --- a/Tools/msi/exe/exe_en-US.wxl_template +++ /dev/null @@ -1,8 +0,0 @@ - - - Executables - executable - Python {{ShortVersion}} ({{Bitness}}) - Launches the !(loc.ProductName) interpreter. - https://www.python.org/ - diff --git a/Tools/msi/exe/exe_files.wxs b/Tools/msi/exe/exe_files.wxs deleted file mode 100644 index 15bf6a06634bf20..000000000000000 --- a/Tools/msi/exe/exe_files.wxs +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/exe/exe_pdb.wixproj b/Tools/msi/exe/exe_pdb.wixproj deleted file mode 100644 index bf1213e9d499680..000000000000000 --- a/Tools/msi/exe/exe_pdb.wixproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - {4A1F7045-8EE2-4276-ABB8-5E0C40E5F38B} - 2.0 - exe_pdb - Package - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/exe/exe_pdb.wxs b/Tools/msi/exe/exe_pdb.wxs deleted file mode 100644 index f706cf3b0e0cf3f..000000000000000 --- a/Tools/msi/exe/exe_pdb.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/msi/exe/exe_reg.wxs b/Tools/msi/exe/exe_reg.wxs deleted file mode 100644 index 4443c2155485bec..000000000000000 --- a/Tools/msi/exe/exe_reg.wxs +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/freethreaded/freethreaded.wixproj b/Tools/msi/freethreaded/freethreaded.wixproj deleted file mode 100644 index 0b4bd055d779773..000000000000000 --- a/Tools/msi/freethreaded/freethreaded.wixproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - {1B4502D5-B627-4F50-ABEA-4CC5A8E88265} - 2.0 - freethreaded - Package - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/freethreaded/freethreaded.wxs b/Tools/msi/freethreaded/freethreaded.wxs deleted file mode 100644 index 063aa28bf09fceb..000000000000000 --- a/Tools/msi/freethreaded/freethreaded.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/freethreaded/freethreaded_d.wixproj b/Tools/msi/freethreaded/freethreaded_d.wixproj deleted file mode 100644 index e1563d4f907126f..000000000000000 --- a/Tools/msi/freethreaded/freethreaded_d.wixproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - {D3677DCF-098A-4398-9FA5-8E74AC37E0DF} - 2.0 - freethreaded_d - Package - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/freethreaded/freethreaded_d.wxs b/Tools/msi/freethreaded/freethreaded_d.wxs deleted file mode 100644 index cddf22a6c803d3e..000000000000000 --- a/Tools/msi/freethreaded/freethreaded_d.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/Tools/msi/freethreaded/freethreaded_en-US.wxl_template b/Tools/msi/freethreaded/freethreaded_en-US.wxl_template deleted file mode 100644 index b9747eb256d24b1..000000000000000 --- a/Tools/msi/freethreaded/freethreaded_en-US.wxl_template +++ /dev/null @@ -1,8 +0,0 @@ - - - Freethreaded Interpreter - freethreaded - Python {{ShortVersion}} ({{Bitness}}, freethreaded) - Launches the !(loc.ProductName) freethreaded interpreter. - https://www.python.org/ - diff --git a/Tools/msi/freethreaded/freethreaded_files.wxs b/Tools/msi/freethreaded/freethreaded_files.wxs deleted file mode 100644 index fdbcaea38eb3175..000000000000000 --- a/Tools/msi/freethreaded/freethreaded_files.wxs +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/freethreaded/freethreaded_pdb.wixproj b/Tools/msi/freethreaded/freethreaded_pdb.wixproj deleted file mode 100644 index 789a4f55ae51918..000000000000000 --- a/Tools/msi/freethreaded/freethreaded_pdb.wixproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - {E98E7539-64E7-4DCE-AACD-01E3ADE40EFD} - 2.0 - freethreaded_pdb - Package - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/freethreaded/freethreaded_pdb.wxs b/Tools/msi/freethreaded/freethreaded_pdb.wxs deleted file mode 100644 index 302ac416fe92753..000000000000000 --- a/Tools/msi/freethreaded/freethreaded_pdb.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/Tools/msi/generate_md5.py b/Tools/msi/generate_md5.py deleted file mode 100644 index 9e4c14731bd11db..000000000000000 --- a/Tools/msi/generate_md5.py +++ /dev/null @@ -1,27 +0,0 @@ -import hashlib -import os -import sys - -def main(): - filenames, hashes, sizes = [], [], [] - - for file in sys.argv[1:]: - if not os.path.isfile(file): - continue - - with open(file, 'rb') as f: - data = f.read() - md5 = hashlib.md5() - md5.update(data) - filenames.append(os.path.split(file)[1]) - hashes.append(md5.hexdigest()) - sizes.append(str(len(data))) - - print('{:40s} {:<32s} {:<9s}'.format('File', 'MD5', 'Size')) - for f, h, s in zip(filenames, hashes, sizes): - print('{:40s} {:>32s} {:>9s}'.format(f, h, s)) - - - -if __name__ == "__main__": - sys.exit(int(main() or 0)) diff --git a/Tools/msi/get_externals.bat b/Tools/msi/get_externals.bat deleted file mode 100644 index f6602ce9588ff41..000000000000000 --- a/Tools/msi/get_externals.bat +++ /dev/null @@ -1,91 +0,0 @@ -@echo off -setlocal -rem Simple script to fetch source for external libraries - -set HERE=%~dp0 -if "%PCBUILD%"=="" (set PCBUILD=%HERE%..\..\PCbuild\) -if "%EXTERNALS_DIR%"=="" (set EXTERNALS_DIR=%HERE%..\..\externals\windows-installer) -if "%NUGET%"=="" (set NUGET=%EXTERNALS_DIR%\..\nuget.exe) -if "%NUGET_URL%"=="" (set NUGET_URL=https://aka.ms/nugetclidl) - -set DO_FETCH=true -set DO_CLEAN=false - -:CheckOpts -if "%~1"=="--python" (set PYTHON=%2) & shift & shift & goto CheckOpts -if "%~1"=="--organization" (set ORG=%2) & shift & shift & goto CheckOpts -if "%~1"=="-c" (set DO_CLEAN=true) & shift & goto CheckOpts -if "%~1"=="--clean" (set DO_CLEAN=true) & shift & goto CheckOpts -if "%~1"=="--clean-only" (set DO_FETCH=false) & goto clean -if "x%~1" NEQ "x" goto usage - -if "%DO_CLEAN%"=="false" goto fetch -:clean -echo.Cleaning up external libraries. -if exist "%EXTERNALS_DIR%" ( - rem Sometimes this fails the first time; try it twice - rmdir /s /q "%EXTERNALS_DIR%" || rmdir /s /q "%EXTERNALS_DIR%" -) - -if "%DO_FETCH%"=="false" goto end -:fetch - -if "%ORG%"=="" (set ORG=python) - -call "%PCBUILD%\find_python.bat" "%PYTHON%" - -echo.Fetching external libraries... - -set libraries= - -for %%e in (%libraries%) do ( - if exist "%EXTERNALS_DIR%\%%e" ( - echo.%%e already exists, skipping. - ) else ( - echo.Fetching %%e... - %PYTHON% "%PCBUILD%get_external.py" -e "%EXTERNALS_DIR%" -O %ORG% %%e - ) -) - -echo.Fetching external tools... - -set binaries= -rem We always use whatever's latest in the repo for these -set binaries=%binaries% binutils -set binaries=%binaries% gpg -set binaries=%binaries% htmlhelp -set binaries=%binaries% nuget -set binaries=%binaries% redist-1 -set binaries=%binaries% wix-314 - -for %%b in (%binaries%) do ( - if exist "%EXTERNALS_DIR%\%%b" ( - echo.%%b already exists, skipping. - ) else ( - echo.Fetching %%b... - %PYTHON% "%PCBUILD%get_external.py" -e "%EXTERNALS_DIR%" -b -O %ORG% %%b - ) -) - -echo Finished. -goto end - -:usage -echo.Valid options: -c, --clean, --clean-only, --organization, --python, -echo.--no-tkinter, --no-openssl -echo. -echo.Pull all sources and binaries necessary for compiling optional extension -echo.modules that rely on external libraries. -echo. -echo.The --organization option determines which github organization to download -echo.from, the --python option determines which Python 3.6+ interpreter to use -echo.with PCbuild\get_external.py. -echo. -echo.Use the -c or --clean option to remove the entire externals directory. -echo. -echo.Use the --clean-only option to do the same cleaning, without pulling in -echo.anything new. -echo. -exit /b -1 - -:end diff --git a/Tools/msi/launcher/launcher.wixproj b/Tools/msi/launcher/launcher.wixproj deleted file mode 100644 index de770bdd3006c22..000000000000000 --- a/Tools/msi/launcher/launcher.wixproj +++ /dev/null @@ -1,46 +0,0 @@ - - - - {921CF0E6-AEBC-4376-BA1D-CD46EBFE6DA5} - 2.0 - launcher - Package - UpgradeCode=1B68A0EC-4DD3-5134-840E-73854B0863F1;SuppressUpgradeTable=1;$(DefineConstants) - true - ICE80 - <_Rebuild>Build - - - - - - - - - - - - - - <_Rebuild>Rebuild - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/launcher/launcher.wxs b/Tools/msi/launcher/launcher.wxs deleted file mode 100644 index 49f1f7b8c1762ee..000000000000000 --- a/Tools/msi/launcher/launcher.wxs +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NOT Installed AND NOT ALLUSERS=1 - NOT Installed AND ALLUSERS=1 - - - UPGRADE or REMOVE_350_LAUNCHER or REMOVE_360A1_LAUNCHER or UPGRADE_3_11_0 or UPGRADE_3_11_1 - - UPGRADE or REMOVE_350_LAUNCHER or REMOVE_360A1_LAUNCHER - - - - - Installed OR NOT DOWNGRADE OR UPGRADE_3_11_0 OR UPGRADE_3_11_1 - - Installed OR NOT DOWNGRADE - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/launcher/launcher_en-US.wxl b/Tools/msi/launcher/launcher_en-US.wxl deleted file mode 100644 index a7e3827c528bb07..000000000000000 --- a/Tools/msi/launcher/launcher_en-US.wxl +++ /dev/null @@ -1,17 +0,0 @@ - - - 1033 - en-us - Python Launcher - Python Launcher - Python Launcher - Python Software Foundation - A newer version of the Python launcher is already installed. - The TARGETDIR variable must be provided when invoking this installer. - Python File - Python File (no console) - Compiled Python File - Python Extension Module - Python Zip Application File - Python Zip Application File (no console) - diff --git a/Tools/msi/launcher/launcher_files.wxs b/Tools/msi/launcher/launcher_files.wxs deleted file mode 100644 index d9a230e2d35ace2..000000000000000 --- a/Tools/msi/launcher/launcher_files.wxs +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - NOT ALLUSERS=1 - - - - - - ALLUSERS=1 - - - - - VersionNT64 AND NOT ARM64_SHELLEXT - - - - - - NOT VersionNT64 AND NOT ARM64_SHELLEXT - - - - - - ARM64_SHELLEXT - - - - - - - diff --git a/Tools/msi/launcher/launcher_reg.wxs b/Tools/msi/launcher/launcher_reg.wxs deleted file mode 100644 index e8d9d24d43fa8ce..000000000000000 --- a/Tools/msi/launcher/launcher_reg.wxs +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/lib/lib.wixproj b/Tools/msi/lib/lib.wixproj deleted file mode 100644 index 3ea46dd40ea4ce9..000000000000000 --- a/Tools/msi/lib/lib.wixproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - {11367E76-3337-4602-8F1E-77DB4F370D7E} - 2.0 - lib - Package - - - - - - - - - - - - - $(PySourcePath)Lib - !(bindpath.src)Lib\ - $(PySourcePath)Lib - Lib\ - lib_py - true - - - - - diff --git a/Tools/msi/lib/lib.wxs b/Tools/msi/lib/lib.wxs deleted file mode 100644 index e417e31b33783ed..000000000000000 --- a/Tools/msi/lib/lib.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/lib/lib_d.wixproj b/Tools/msi/lib/lib_d.wixproj deleted file mode 100644 index 587a82c1929da7d..000000000000000 --- a/Tools/msi/lib/lib_d.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {6C443CD3-8258-4335-BA03-49DA9C34CE4D} - 2.0 - lib_d - Package - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/lib/lib_d.wxs b/Tools/msi/lib/lib_d.wxs deleted file mode 100644 index 7968d179743a009..000000000000000 --- a/Tools/msi/lib/lib_d.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/msi/lib/lib_en-US.wxl b/Tools/msi/lib/lib_en-US.wxl deleted file mode 100644 index 305bcc73afc8082..000000000000000 --- a/Tools/msi/lib/lib_en-US.wxl +++ /dev/null @@ -1,5 +0,0 @@ - - - Standard Library - lib - diff --git a/Tools/msi/lib/lib_files.wxs b/Tools/msi/lib/lib_files.wxs deleted file mode 100644 index 4d44299f7839099..000000000000000 --- a/Tools/msi/lib/lib_files.wxs +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/lib/lib_pdb.wixproj b/Tools/msi/lib/lib_pdb.wixproj deleted file mode 100644 index db1b5bb316cfead..000000000000000 --- a/Tools/msi/lib/lib_pdb.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {5E0BCE93-D1AC-4591-BBCB-3A2BE5A4B3D1} - 2.0 - lib_pdb - Package - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/lib/lib_pdb.wxs b/Tools/msi/lib/lib_pdb.wxs deleted file mode 100644 index 7918893a9a25050..000000000000000 --- a/Tools/msi/lib/lib_pdb.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/msi/make_appx.ps1 b/Tools/msi/make_appx.ps1 deleted file mode 100644 index e32bd76a37f135a..000000000000000 --- a/Tools/msi/make_appx.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -<# -.Synopsis - Compiles and signs an APPX package -.Description - Given the file listing, ensures all the contents are signed - and builds and signs the final package. -.Parameter mapfile - The location on disk of the text mapping file. -.Parameter msix - The path and name to store the APPX/MSIX. -.Parameter sign - When set, signs the APPX/MSIX. Packages to be published to - the store should not be signed. -.Parameter description - Description to embed in the signature (optional). -.Parameter certname - The name of the certificate to sign with (optional). -.Parameter certsha1 - The SHA1 hash of the certificate to sign with (optional). -#> -param( - [Parameter(Mandatory=$true)][string]$layout, - [Parameter(Mandatory=$true)][string]$msix, - [switch]$sign, - [string]$description, - [string]$certname, - [string]$certsha1, - [string]$certfile -) - -$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent; -Import-Module $tools\sdktools.psm1 -WarningAction SilentlyContinue -Force - -Set-Alias makeappx (Find-Tool "makeappx.exe") -Scope Script -Set-Alias makepri (Find-Tool "makepri.exe") -Scope Script - -$msixdir = Split-Path $msix -Parent -if ($msixdir) { - $msixdir = (mkdir -Force $msixdir).FullName -} else { - $msixdir = Get-Location -} -$msix = Join-Path $msixdir (Split-Path $msix -Leaf) - -pushd $layout -try { - if (Test-Path resources.pri) { - del resources.pri - } - $name = ([xml](gc AppxManifest.xml)).Package.Identity.Name - makepri new /pr . /mn AppxManifest.xml /in $name /cf _resources.xml /of _resources.pri /mf appx /o - if (-not $? -or -not (Test-Path _resources.map.txt)) { - throw "makepri step failed" - } - $lines = gc _resources.map.txt - $lines | ?{ -not ($_ -match '"_resources[\w\.]+?"') } | Out-File _resources.map.txt -Encoding utf8 - makeappx pack /f _resources.map.txt /m AppxManifest.xml /o /p $msix - if (-not $?) { - throw "makeappx step failed" - } -} finally { - popd -} - -if ($sign) { - Sign-File -certname $certname -certsha1 $certsha1 -certfile $certfile -description $description -files $msix - - if (-not $?) { - throw "Package signing failed" - } -} diff --git a/Tools/msi/make_cat.ps1 b/Tools/msi/make_cat.ps1 deleted file mode 100644 index 9ea3ddd495719e6..000000000000000 --- a/Tools/msi/make_cat.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -<# -.Synopsis - Compiles and signs a catalog file. -.Description - Given the CDF definition file, builds and signs a catalog. -.Parameter catalog - The path to the catalog definition file to compile and - sign. It is assumed that the .cat file will be the same - name with a new extension. -.Parameter outfile - The path to move the built .cat file to (optional). -.Parameter description - The description to add to the signature (optional). -.Parameter certname - The name of the certificate to sign with (optional). -.Parameter certsha1 - The SHA1 hash of the certificate to sign with (optional). -#> -param( - [Parameter(Mandatory=$true)][string]$catalog, - [string]$outfile, - [switch]$sign, - [string]$description, - [string]$certname, - [string]$certsha1, - [string]$certfile -) - -$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent; -Import-Module $tools\sdktools.psm1 -WarningAction SilentlyContinue -Force - -Set-Alias MakeCat (Find-Tool "makecat.exe") -Scope Script - -MakeCat $catalog -if (-not $?) { - throw "Catalog compilation failed" -} -if ($sign) { - Sign-File -certname $certname -certsha1 $certsha1 -certfile $certfile -description $description -files @($catalog -replace 'cdf$', 'cat') -} - -if ($outfile) { - Split-Path -Parent $outfile | ?{ $_ } | %{ mkdir -Force $_; } - Move-Item ($catalog -replace 'cdf$', 'cat') $outfile -} diff --git a/Tools/msi/make_zip.proj b/Tools/msi/make_zip.proj deleted file mode 100644 index 125a434e51f4147..000000000000000 --- a/Tools/msi/make_zip.proj +++ /dev/null @@ -1,40 +0,0 @@ - - - - {10487945-15D1-4092-A214-338395C4116B} - python - - false - - - - - - false - python-$(PythonVersion)-embed-$(ArchName) - .zip - $(OutputPath)\$(TargetName)$(TargetExt) - rmdir /q/s "$(IntermediateOutputPath)\zip_$(ArchName)" - "$(PythonExe)" "$(PySourcePath)PC\layout" - $(Arguments) -b "$(BuildPath.TrimEnd(`\`))" -s "$(PySourcePath.TrimEnd(`\`))" - $(Arguments) -t "$(IntermediateOutputPath)\zip_$(ArchName)" - $(Arguments) --zip "$(TargetPath)" - $(Arguments) --precompile --zip-lib --include-underpth --include-stable --flat-dlls - $(Environment)%0D%0Aset PYTHONPATH=$(PySourcePath)Lib - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/msi.props b/Tools/msi/msi.props deleted file mode 100644 index 097af60715448f1..000000000000000 --- a/Tools/msi/msi.props +++ /dev/null @@ -1,234 +0,0 @@ - - - - $(OutputName) - false - false - $(SuppressIces);ICE03;ICE57;ICE61 - 1026 - false - true - Release - x86 - perUser - <_MakeCatCommand Condition="'$(_MakeCatCommand)' == ''">makecat - true - - - - - - - - - $(ComputerName)/$(ArchName)/ - $(ReleaseUri)/ - - - - - - - - WixUtilExtension - WixUtilExtension - - - - - $(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)$(ArchName)_$(Configuration)\msi_$(OutputName) - $(IntermediateOutputPath)_$(OutputSuffix) - $(BuildPath) - $(OutputPath)\ - $(OutputPath) - true - $(ExternalsDir)\windows-installer\redist-1\$(Platform) - $([System.IO.Path]::GetFullPath($(CRTRedist))) - $(tcltkDir)lib - python$(MajorVersionNumber)$(MinorVersionNumber)$(MicroVersionNumber)$(ReleaseLevelName).chm - - $(MajorVersionNumber).$(MinorVersionNumber).$(Field3Value).0 - - - - false - - - - $([System.Math]::Floor($([System.DateTime]::Now.Subtract($([System.DateTime]::new(2001, 1, 1))).TotalDays))) - $(MajorVersionNumber).$(MinorVersionNumber).$(MicroVersionNumber)dev$(RevisionNumber) - $(MajorVersionNumber).$(MinorVersionNumber).$(RevisionNumber).0 - - - - 32-bit - 64-bit - ARM64 - 32bit - 64bit - ARM64 - - $(DefineConstants); - Version=$(InstallerVersion); - ShortVersion=$(MajorVersionNumber).$(MinorVersionNumber); - LongVersion=$(PythonVersion); - MajorVersionNumber=$(MajorVersionNumber); - MinorVersionNumber=$(MinorVersionNumber); - UpgradeMinimumVersion=$(MajorVersionNumber).$(MinorVersionNumber).0.0; - NextMajorVersionNumber=$(MajorVersionNumber).$([msbuild]::Add($(MinorVersionNumber), 1)).0.0; - Bitness=$(Bitness); - PlatformArchitecture=$(PlatformArchitecture); - PyDebugExt=$(PyDebugExt); - PyArchExt=$(PyArchExt); - PyTestExt=$(PyTestExt); - PydTag=$(PydTag); - FreethreadedPydTag=$(FreethreadedPydTag); - OptionalFeatureName=$(OutputName); - ssltag=$(OpenSSLDLLSuffix); - Suffix32=$(PyArchExt); - IncludeFreethreaded=$(IncludeFreethreaded); - - - $(DefineConstants);CRTRedist=$(CRTRedist); - - - - - - generated_filelist - - - false - - - false - - - - - - - - - build - - - build - - - build - - - build_t - - - build_t - - - build_t - - - - src - - - tcltk - - - redist - - - - build32 - - - build64 - - - buildarm64 - - - build32t - - - build64t - - - buildarm64t - - - - - - - - - <_Uuid Include="CoreUpgradeCode"> - upgradecode - - <_Uuid Include="UpgradeCode"> - upgradecode/$(OutputName) - - <_Uuid Include="InstallDirectoryGuidSeed"> - installdirectoryseed - - <_Uuid Include="PythonExeComponentGuid"> - python.exe - - <_Uuid Include="PythonwExeComponentGuid"> - pythonw.exe - - <_Uuid Include="CommonPythonRegComponentGuid"> - registry - - <_Uuid Include="PythonRegComponentGuid"> - registry/$(OutputName) - - <_Uuid Include="FreethreadedPythonExeComponentGuid" Condition="$(IncludeFreethreaded)"> - freethreaded/python.exe - - <_Uuid Include="FreethreadedPythonwExeComponentGuid" Condition="$(IncludeFreethreaded)"> - freethreaded/pythonw.exe - - - - - <_Uuids>@(_Uuid->'("%(Identity)", "$(MajorVersionNumber).$(MinorVersionNumber)/%(Uri)")',',') - <_GenerateCommand>import uuid; print('\n'.join('{}={}'.format(i, uuid.uuid5(uuid.UUID('c8d9733e-a70c-43ff-ab0c-e26456f11083'), '$(ReleaseUri.Replace(`{arch}`, `$(ArchName)`))' + j)) for i,j in [$(_Uuids.Replace(`"`,`'`))])) - - - - - - - - - - $(DefineConstants);@(_UuidValue,';'); - - - diff --git a/Tools/msi/msi.targets b/Tools/msi/msi.targets deleted file mode 100644 index 99d1de4d9d83f58..000000000000000 --- a/Tools/msi/msi.targets +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - <_FileListTarget>$(IntermediateOutputPath)$(MSBuildProjectName).g.csv - <_InstallFilesTarget>$(IntermediateOutputPath)$(MSBuildProjectName).g.wxs - - - - - <_Source>%(Source)$([msbuild]::MakeRelative(%(SourceBase), %(FullPath))) - <_Target>%(Target_)$([msbuild]::MakeRelative(%(TargetBase), %(FullPath))) - - - <_CatalogFiles Include="@(InstallFiles)" Condition="%(InstallFiles.IncludeInCat) and ''!=$([System.IO.File]::ReadAllText(%(InstallFiles.FullPath)))" /> - - - - - - - - - - - - - - <_CatFileSourceTarget>$(IntermediateOutputPath)$(MSBuildProjectName).cdf - <_CatFileTarget>$(IntermediateOutputPath)python_$(MSBuildProjectName).cat - <_CatFile>[CatalogHeader] -Name=$([System.IO.Path]::GetFileName($(_CatFileTarget))) -ResultDir=$([System.IO.Path]::GetDirectoryName($(_CatFileTarget))) -PublicVersion=1 -CatalogVersion=2 -HashAlgorithms=SHA256 -PageHashes=false -EncodingType= - -[CatalogFiles] -@(_CatalogFiles->'<HASH>%(Filename)%(Extension)=%(FullPath)',' -') - - - - - - - - - - - - - - - <_Content>$([System.IO.File]::ReadAllText(%(WxlTemplate.FullPath)).Replace(`{{ShortVersion}}`, `$(MajorVersionNumber).$(MinorVersionNumber)$(PyTestExt)`).Replace(`{{LongVersion}}`, `$(PythonVersion)$(PyTestExt)`).Replace(`{{Bitness}}`, `$(Bitness)`)) - <_ExistingContent Condition="Exists('$(IntermediateOutputPath)%(WxlTemplate.Filename).wxl')">$([System.IO.File]::ReadAllText($(IntermediateOutputPath)%(WxlTemplate.Filename).wxl)) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/path/path.wixproj b/Tools/msi/path/path.wixproj deleted file mode 100644 index 2792e14666d3845..000000000000000 --- a/Tools/msi/path/path.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {91C99298-8E2E-4422-A5AF-CC4FFF9A58D3} - 2.0 - path - Package - ICE71 - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/path/path.wxs b/Tools/msi/path/path.wxs deleted file mode 100644 index 32854392e8bee3f..000000000000000 --- a/Tools/msi/path/path.wxs +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - NOT ALLUSERS=1 - - - - - - - - - - - - ALLUSERS=1 - - - - - - - - - - - - - - - - diff --git a/Tools/msi/path/path_en-US.wxl b/Tools/msi/path/path_en-US.wxl deleted file mode 100644 index 33a7886fe2d6e20..000000000000000 --- a/Tools/msi/path/path_en-US.wxl +++ /dev/null @@ -1,6 +0,0 @@ - - - Add to Path - Path - No !(loc.ProductName) installation was detected. - diff --git a/Tools/msi/pip/pip.wixproj b/Tools/msi/pip/pip.wixproj deleted file mode 100644 index 718c02c032b73b3..000000000000000 --- a/Tools/msi/pip/pip.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {91C99298-8E2E-4422-A5AF-CC4FFF9A58D3} - 2.0 - pip - Package - ICE71 - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/pip/pip.wxs b/Tools/msi/pip/pip.wxs deleted file mode 100644 index 627c4710a9fdfaa..000000000000000 --- a/Tools/msi/pip/pip.wxs +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - PYTHON_EXE - - - - - - - - - - - - - - - - - (&DefaultFeature=3) AND NOT (!DefaultFeature=3) - (&DefaultFeature=2) AND (!DefaultFeature=3) - - UpdatePip - - - - diff --git a/Tools/msi/pip/pip_en-US.wxl b/Tools/msi/pip/pip_en-US.wxl deleted file mode 100644 index cd0d9edf3a6b6fd..000000000000000 --- a/Tools/msi/pip/pip_en-US.wxl +++ /dev/null @@ -1,6 +0,0 @@ - - - pip Bootstrap - pip - No !(loc.ProductName) installation was detected. - diff --git a/Tools/msi/purge.py b/Tools/msi/purge.py deleted file mode 100644 index 4a13d368d7c744e..000000000000000 --- a/Tools/msi/purge.py +++ /dev/null @@ -1,76 +0,0 @@ -# Purges the Fastly cache for Windows download files -# -# Usage: -# py -3 purge.py 3.5.1rc1 -# - -__author__ = 'Steve Dower ' -__version__ = '1.0.0' - -import re -import sys - -from urllib.request import Request, urlopen - -VERSION_RE = re.compile(r'(\d+\.\d+\.\d+)([A-Za-z_]+\d+)?$') - -try: - m = VERSION_RE.match(sys.argv[1]) - if not m: - print('Invalid version:', sys.argv[1]) - print('Expected something like "3.5.1rc1"') - sys.exit(1) -except LookupError: - print('Missing version argument. Expected something like "3.5.1rc1"') - sys.exit(1) - -URL = "https://www.python.org/ftp/python/{}/".format(m.group(1)) -REL = m.group(2) or '' - -FILES = [ - "core.msi", - "core_d.msi", - "core_pdb.msi", - "dev.msi", - "dev_d.msi", - "doc.msi", - "exe.msi", - "exe_d.msi", - "exe_pdb.msi", - "launcher.msi", - "lib.msi", - "lib_d.msi", - "lib_pdb.msi", - "path.msi", - "pip.msi", - "tcltk.msi", - "tcltk_d.msi", - "tcltk_pdb.msi", - "test.msi", - "test_d.msi", - "test_pdb.msi", - "tools.msi", - "ucrt.msi", -] -PATHS = [ - "python-{}.exe".format(m.group(0)), - "python-{}-webinstall.exe".format(m.group(0)), - "python-{}-amd64.exe".format(m.group(0)), - "python-{}-amd64-webinstall.exe".format(m.group(0)), - "python-{}-arm64.exe".format(m.group(0)), - "python-{}-arm64-webinstall.exe".format(m.group(0)), - "python-{}-embed-amd64.zip".format(m.group(0)), - "python-{}-embed-win32.zip".format(m.group(0)), - "python-{}-embed-arm64.zip".format(m.group(0)), - *["win32{}/{}".format(REL, f) for f in FILES], - *["amd64{}/{}".format(REL, f) for f in FILES], - *["arm64{}/{}".format(REL, f) for f in FILES], -] -PATHS = PATHS + [p + ".asc" for p in PATHS] - -print('Purged:') -for n in PATHS: - u = URL + n - with urlopen(Request(u, method='PURGE', headers={'Fastly-Soft-Purge': 1})) as r: - r.read() - print(' ', u) diff --git a/Tools/msi/sdktools.psm1 b/Tools/msi/sdktools.psm1 deleted file mode 100644 index c5973f9abc6abf0..000000000000000 --- a/Tools/msi/sdktools.psm1 +++ /dev/null @@ -1,48 +0,0 @@ -function Find-Tool { - param([string]$toolname) - - $kitroot = (gp 'HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots\').KitsRoot10 - $tool = (gci -r "$kitroot\Bin\*\x64\$toolname" | sort FullName -Desc | select -First 1) - if (-not $tool) { - throw "$toolname is not available" - } - Write-Host "Found $toolname at $($tool.FullName)" - return $tool.FullName -} - -Set-Alias SignTool (Find-Tool "signtool.exe") -Scope Script - -function Sign-File { - param([string]$certname, [string]$certsha1, [string]$certfile, [string]$description, [string[]]$files) - - if (-not $description) { - $description = $env:SigningDescription; - if (-not $description) { - $description = "Python"; - } - } - if (-not $certsha1) { - $certsha1 = $env:SigningCertificateSha1; - } - if (-not $certname) { - $certname = $env:SigningCertificate; - } - if (-not $certfile) { - $certfile = $env:SigningCertificateFile; - } - - if (-not ($certsha1 -or $certname -or $certfile)) { - throw "No signing certificate specified" - } - - foreach ($a in $files) { - if ($certsha1) { - SignTool sign /sha1 $certsha1 /fd sha256 /tr http://timestamp.digicert.com/ /td sha256 /d $description $a - } elseif ($certname) { - SignTool sign /a /n $certname /fd sha256 /tr http://timestamp.digicert.com/ /td sha256 /d $description $a - } elseif ($certfile) { - SignTool sign /f $certfile /fd sha256 /tr http://timestamp.digicert.com/ /td sha256 /d $description $a - } - } -} - diff --git a/Tools/msi/sign_build.ps1 b/Tools/msi/sign_build.ps1 deleted file mode 100644 index d3f750454f522b0..000000000000000 --- a/Tools/msi/sign_build.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -<# -.Synopsis - Recursively signs the contents of a directory. -.Description - Given the file patterns, code signs the contents. -.Parameter root - The root directory to sign. -.Parameter patterns - The file patterns to sign -.Parameter description - The description to add to the signature (optional). -.Parameter certname - The name of the certificate to sign with (optional). -.Parameter certsha1 - The SHA1 hash of the certificate to sign with (optional). -#> -param( - [Parameter(Mandatory=$true)][string]$root, - [string[]]$patterns=@("*.exe", "*.dll", "*.pyd", "*.cat"), - [string]$description, - [string]$certname, - [string]$certsha1, - [string]$certfile -) - -$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent; -Import-Module $tools\sdktools.psm1 -WarningAction SilentlyContinue -Force - -pushd $root -try { - Sign-File -certname $certname -certsha1 $certsha1 -certfile $certfile -description $description -files (gci -r $patterns) -} finally { - popd -} \ No newline at end of file diff --git a/Tools/msi/tcltk/tcltk.wixproj b/Tools/msi/tcltk/tcltk.wixproj deleted file mode 100644 index c8b7ab77c4dc6b7..000000000000000 --- a/Tools/msi/tcltk/tcltk.wixproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - {DB350600-186C-4E52-BA98-26A7CECB067F} - 2.0 - tcltk - Package - - - - ICE43 - - - - - - - - - - - - - $(TclTkLibraryDir) - !(bindpath.tcltk) - $(TclTkLibraryDir) - tcl\ - tcltk_lib - - - - $(PySourcePath) - !(bindpath.src) - $(PySourcePath) - - tkinter_lib - - - - - diff --git a/Tools/msi/tcltk/tcltk.wxs b/Tools/msi/tcltk/tcltk.wxs deleted file mode 100644 index bad56d55cce3723..000000000000000 --- a/Tools/msi/tcltk/tcltk.wxs +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - PYTHON_EXE and PYTHONW_EXE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/tcltk/tcltk_d.wixproj b/Tools/msi/tcltk/tcltk_d.wixproj deleted file mode 100644 index 3266190da0c9a6a..000000000000000 --- a/Tools/msi/tcltk/tcltk_d.wixproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - {EDA1FA5A-E2AA-4EAF-B49B-87D981CD0F16} - 2.0 - tcltk_d - Package - - - - - - - - - - - - $(tcltkDir) - !(bindpath.tcltk) - $(tcltkDir)bin - DLLs\ - tcltk_dlls_d - - - - - \ No newline at end of file diff --git a/Tools/msi/tcltk/tcltk_d.wxs b/Tools/msi/tcltk/tcltk_d.wxs deleted file mode 100644 index 38aacd929995efa..000000000000000 --- a/Tools/msi/tcltk/tcltk_d.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/msi/tcltk/tcltk_en-US.wxl_template b/Tools/msi/tcltk/tcltk_en-US.wxl_template deleted file mode 100644 index f40fd6239deaa36..000000000000000 --- a/Tools/msi/tcltk/tcltk_en-US.wxl_template +++ /dev/null @@ -1,12 +0,0 @@ - - - Tcl/Tk Support - tcltk - No !(loc.ProductName) installation was detected. - IDLE (Python {{ShortVersion}} {{Bitness}}) - Launches IDLE, the interactive environment for !(loc.ProductName). - Python {{ShortVersion}} Module Docs ({{Bitness}}) - Start the !(loc.ProductName) documentation server. - &Edit with IDLE - Edit with IDLE {{ShortVersion}} ({{Bitness}}) - diff --git a/Tools/msi/tcltk/tcltk_files.wxs b/Tools/msi/tcltk/tcltk_files.wxs deleted file mode 100644 index 7c7784741d9178d..000000000000000 --- a/Tools/msi/tcltk/tcltk_files.wxs +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/tcltk/tcltk_pdb.wixproj b/Tools/msi/tcltk/tcltk_pdb.wixproj deleted file mode 100644 index 3370798a0559dba..000000000000000 --- a/Tools/msi/tcltk/tcltk_pdb.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {02053AFA-1831-499A-B3EA-D8B223D3C40D} - 2.0 - tcltk_pdb - Package - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/tcltk/tcltk_pdb.wxs b/Tools/msi/tcltk/tcltk_pdb.wxs deleted file mode 100644 index 55f209162553eb0..000000000000000 --- a/Tools/msi/tcltk/tcltk_pdb.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/msi/tcltk/tcltk_reg.wxs b/Tools/msi/tcltk/tcltk_reg.wxs deleted file mode 100644 index 2778bcc80669b6d..000000000000000 --- a/Tools/msi/tcltk/tcltk_reg.wxs +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - VersionNT > 600 - - - - - VersionNT > 600 - - - - - - VersionNT > 600 - - - - - - - VersionNT > 600 - - - - - - - - VersionNT = 600 - - - - - - - VersionNT = 600 - - - - - - - - diff --git a/Tools/msi/test/test.wixproj b/Tools/msi/test/test.wixproj deleted file mode 100644 index 8347e3f1d3db65b..000000000000000 --- a/Tools/msi/test/test.wixproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - {DE0B7CC2-4358-4131-B3F4-C31C7F2CD468} - 2.0 - test - Package - - - - - - - - - - - - $(PySourcePath) - !(bindpath.src) - $(PySourcePath) - - test_py - - - - - \ No newline at end of file diff --git a/Tools/msi/test/test.wxs b/Tools/msi/test/test.wxs deleted file mode 100644 index 9e497e73a94f5b5..000000000000000 --- a/Tools/msi/test/test.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/test/test_d.wixproj b/Tools/msi/test/test_d.wixproj deleted file mode 100644 index 33b04be1fe60d63..000000000000000 --- a/Tools/msi/test/test_d.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {41F5AE8D-24CD-4D03-BE75-AA6F7FAB4097} - 2.0 - test_d - Package - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/test/test_d.wxs b/Tools/msi/test/test_d.wxs deleted file mode 100644 index 9b26ff6a4bf4cc3..000000000000000 --- a/Tools/msi/test/test_d.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/msi/test/test_en-US.wxl b/Tools/msi/test/test_en-US.wxl deleted file mode 100644 index e615c7a6b7f7062..000000000000000 --- a/Tools/msi/test/test_en-US.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - Test Suite - test - !(loc.FullProductName) native libtest - !(loc.ProductName) Native Test Modules - diff --git a/Tools/msi/test/test_files.wxs b/Tools/msi/test/test_files.wxs deleted file mode 100644 index 6862a5899db2bac..000000000000000 --- a/Tools/msi/test/test_files.wxs +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/test/test_pdb.wixproj b/Tools/msi/test/test_pdb.wixproj deleted file mode 100644 index 965f0edd9b49924..000000000000000 --- a/Tools/msi/test/test_pdb.wixproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {7CF48ADD-CFAA-499F-9A05-BA18440A3344} - 2.0 - test_pdb - Package - - - - - - - - - - - - \ No newline at end of file diff --git a/Tools/msi/test/test_pdb.wxs b/Tools/msi/test/test_pdb.wxs deleted file mode 100644 index 5a2ef3668400756..000000000000000 --- a/Tools/msi/test/test_pdb.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Tools/msi/testrelease.bat b/Tools/msi/testrelease.bat deleted file mode 100644 index db98f690151196c..000000000000000 --- a/Tools/msi/testrelease.bat +++ /dev/null @@ -1,115 +0,0 @@ -@setlocal enableextensions -@echo off - -set D=%~dp0 -set PCBUILD=%D%..\..\PCbuild\ - -set TARGETDIR=%TEMP% -set TESTX86= -set TESTX64= -set TESTALLUSER= -set TESTPERUSER= - -:CheckOpts -if "%1" EQU "-h" goto Help -if "%1" EQU "-x86" (set TESTX86=1) && shift && goto CheckOpts -if "%1" EQU "-x64" (set TESTX64=1) && shift && goto CheckOpts -if "%1" EQU "-t" (set TARGETDIR=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "--target" (set TARGETDIR=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "-a" (set TESTALLUSER=1) && shift && goto CheckOpts -if "%1" EQU "--alluser" (set TESTALLUSER=1) && shift && goto CheckOpts -if "%1" EQU "-p" (set TESTPERUSER=1) && shift && goto CheckOpts -if "%1" EQU "--peruser" (set TESTPERUSER=1) && shift && goto CheckOpts - -if not defined TESTX86 if not defined TESTX64 (set TESTX86=1) && (set TESTX64=1) -if not defined TESTALLUSER if not defined TESTPERUSER (set TESTALLUSER=1) && (set TESTPERUSER=1) - - -if defined TESTX86 ( - for %%f in ("%PCBUILD%win32\en-us\*.exe") do ( - if defined TESTALLUSER call :test "%%~ff" "%TARGETDIR%\%%~nf-alluser" "InstallAllUsers=1 CompileAll=1" - if errorlevel 1 exit /B - if defined TESTPERUSER call :test "%%~ff" "%TARGETDIR%\%%~nf-peruser" "InstallAllUsers=0 CompileAll=0" - if errorlevel 1 exit /B - ) -) - -if defined TESTX64 ( - for %%f in ("%PCBUILD%amd64\en-us\*.exe") do ( - if defined TESTALLUSER call :test "%%~ff" "%TARGETDIR%\%%~nf-alluser" "InstallAllUsers=1 CompileAll=1" - if errorlevel 1 exit /B - if defined TESTPERUSER call :test "%%~ff" "%TARGETDIR%\%%~nf-peruser" "InstallAllUsers=0 CompileAll=0" - if errorlevel 1 exit /B - ) -) - -exit /B 0 - -:test -@setlocal -@echo on - -@if not exist "%~1" exit /B 1 - -@set EXE=%~1 -@if not "%EXE:embed=%"=="%EXE%" exit /B 0 - -@set EXITCODE=0 -@echo Installing %1 into %2 -"%~1" /passive /log "%~2\install\log.txt" TargetDir="%~2\Python" Include_debug=1 Include_symbols=1 %~3 - -@if not errorlevel 1 ( - @echo Printing version - "%~2\Python\python.exe" -c "import sys; print(sys.version)" > "%~2\version.txt" 2>&1 -) - -@if not errorlevel 1 ( - @echo Capturing Start Menu - @dir /s/b "%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs" | findstr /ic:"python" > "%~2\startmenu.txt" 2>&1 - @dir /s/b "%APPDATA%\Microsoft\Windows\Start Menu\Programs" | findstr /ic:"python" >> "%~2\startmenu.txt" 2>&1 - - @echo Capturing registry - @for /F "usebackq" %%f in (`reg query HKCR /s /f python /k`) do @( - echo %%f >> "%~2\hkcr.txt" - reg query "%%f" /s >> "%~2\hkcr.txt" 2>&1 - ) - @reg query HKCU\Software\Python /s > "%~2\hkcu.txt" 2>&1 - @reg query HKLM\Software\Python /reg:32 /s > "%~2\hklm.txt" 2>&1 - @reg query HKLM\Software\Python /reg:64 /s >> "%~2\hklm.txt" 2>&1 - cmd /k exit 0 -) - -@if not errorlevel 1 ( - @echo Installing package - "%~2\Python\python.exe" -m pip install "azure<0.10" > "%~2\pip.txt" 2>&1 - @if not errorlevel 1 ( - "%~2\Python\python.exe" -m pip uninstall -y azure python-dateutil six >> "%~2\pip.txt" 2>&1 - ) -) -@if not errorlevel 1 ( - @echo Testing Tcl/tk - "%~2\Python\python.exe" -m test -uall -v test_ttk_guionly test_tk test_idle > "%~2\tcltk.txt" 2>&1 -) - -@set EXITCODE=%ERRORLEVEL% - -@echo Result was %EXITCODE% -@echo Removing %1 -"%~1" /passive /uninstall /log "%~2\uninstall\log.txt" - -@echo off -exit /B %EXITCODE% - -:Help -echo testrelease.bat [--target TARGET] [-x86] [-x64] [--alluser] [--peruser] [-h] -echo. -echo --target (-t) Specify the target directory for installs and logs -echo -x86 Run tests for x86 installers -echo -x64 Run tests for x64 installers -echo --alluser (-a) Run tests for all-user installs (requires Administrator) -echo --peruser (-p) Run tests for per-user installs -echo -h Display this help information -echo. -echo If no test architecture is specified, all architectures will be tested. -echo If no install type is selected, all install types will be tested. -echo. diff --git a/Tools/msi/ucrt/ucrt.wixproj b/Tools/msi/ucrt/ucrt.wixproj deleted file mode 100644 index 52a7e0e672b9aff..000000000000000 --- a/Tools/msi/ucrt/ucrt.wixproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - {E233091D-2EE3-49D8-A7F1-6E266CE1B6B1} - 2.0 - ucrt - Package - - - - - - - - - - - $(CRTRedist) - !(bindpath.redist)\ - $(CRTRedist)\ucrt - InstallDirectory\ - ucrt_dll - false - - - - - \ No newline at end of file diff --git a/Tools/msi/ucrt/ucrt.wxs b/Tools/msi/ucrt/ucrt.wxs deleted file mode 100644 index e9e2a9a9045115b..000000000000000 --- a/Tools/msi/ucrt/ucrt.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/Tools/msi/ucrt/ucrt_en-US.wxl b/Tools/msi/ucrt/ucrt_en-US.wxl deleted file mode 100644 index f42dd5fe44cd590..000000000000000 --- a/Tools/msi/ucrt/ucrt_en-US.wxl +++ /dev/null @@ -1,5 +0,0 @@ - - - C Runtime Library - ucrt - diff --git a/Tools/msi/uploadrelease.bat b/Tools/msi/uploadrelease.bat deleted file mode 100644 index f262489cce60857..000000000000000 --- a/Tools/msi/uploadrelease.bat +++ /dev/null @@ -1,109 +0,0 @@ -@setlocal -@echo off - -set D=%~dp0 -set PCBUILD=%D%..\..\PCbuild\ - -set HOST= -set USER= -set TARGET= -set DRYRUN=false -set NOUPLOAD= -set NOGPG= -set NOPURGE= -set NOTEST= - -:CheckOpts -if "%1" EQU "-h" goto Help -if "%1" EQU "-o" (set HOST=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "--host" (set HOST=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "-u" (set USER=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "--user" (set USER=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "-t" (set TARGET=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "--target" (set TARGET=%~2) && shift && shift && goto CheckOpts -if "%1" EQU "--dry-run" (set DRYRUN=true) && shift && goto CheckOpts -if "%1" EQU "--skip-upload" (set NOUPLOAD=true) && shift && goto CheckOpts -if "%1" EQU "--skip-gpg" (set NOGPG=true) && shift && goto CheckOpts -if "%1" EQU "--skip-purge" (set NOPURGE=true) && shift && goto CheckOpts -if "%1" EQU "--skip-test" (set NOTEST=true) && shift && goto CheckOpts -if "%1" EQU "-T" (set NOTEST=true) && shift && goto CheckOpts -if "%1" NEQ "" echo Unexpected argument "%1" & exit /B 1 - -if not defined PLINK where plink > "%TEMP%\plink.loc" 2> nul && set /P PLINK= < "%TEMP%\plink.loc" & del "%TEMP%\plink.loc" -if not defined PLINK where /R "%ProgramFiles(x86)%\PuTTY" plink > "%TEMP%\plink.loc" 2> nul && set /P PLINK= < "%TEMP%\plink.loc" & del "%TEMP%\plink.loc" -if not defined PLINK where /R "%ProgramFiles%\PuTTY" plink > "%TEMP%\plink.loc" 2> nul && set /P PLINK= < "%TEMP%\plink.loc" & del "%TEMP%\plink.loc" -if not defined PLINK where /R "%ProgramFiles(x86)%" plink > "%TEMP%\plink.loc" 2> nul && set /P PLINK= < "%TEMP%\plink.loc" & del "%TEMP%\plink.loc" -if not defined PLINK where /R "%ProgramFiles%" plink > "%TEMP%\plink.loc" 2> nul && set /P PLINK= < "%TEMP%\plink.loc" & del "%TEMP%\plink.loc" -if not defined PLINK echo Cannot locate plink.exe & exit /B 1 -echo Found plink.exe at %PLINK% - -if not defined PSCP where pscp > "%TEMP%\pscp.loc" 2> nul && set /P pscp= < "%TEMP%\pscp.loc" & del "%TEMP%\pscp.loc" -if not defined PSCP where /R "%ProgramFiles(x86)%\PuTTY" pscp > "%TEMP%\pscp.loc" 2> nul && set /P pscp= < "%TEMP%\pscp.loc" & del "%TEMP%\pscp.loc" -if not defined PSCP where /R "%ProgramFiles%\PuTTY" pscp > "%TEMP%\pscp.loc" 2> nul && set /P pscp= < "%TEMP%\pscp.loc" & del "%TEMP%\pscp.loc" -if not defined PSCP where /R "%ProgramFiles(x86)%" pscp > "%TEMP%\pscp.loc" 2> nul && set /P pscp= < "%TEMP%\pscp.loc" & del "%TEMP%\pscp.loc" -if not defined PSCP where /R "%ProgramFiles%" pscp > "%TEMP%\pscp.loc" 2> nul && set /P pscp= < "%TEMP%\pscp.loc" & del "%TEMP%\pscp.loc" -if not defined PSCP echo Cannot locate pscp.exe & exit /B 1 -echo Found pscp.exe at %PSCP% - -if defined NOGPG ( - set GPG= - echo Skipping GPG signature generation because of --skip-gpg -) else ( - if not defined GPG where gpg2 > "%TEMP%\gpg.loc" 2> nul && set /P GPG= < "%TEMP%\gpg.loc" & del "%TEMP%\gpg.loc" - if not defined GPG where /R "%PCBUILD%..\externals\windows-installer" gpg2 > "%TEMP%\gpg.loc" 2> nul && set /P GPG= < "%TEMP%\gpg.loc" & del "%TEMP%\gpg.loc" - if not defined GPG echo Cannot locate gpg2.exe. Signatures will not be uploaded & pause - echo Found gpg2.exe at %GPG% -) - -call "%PCBUILD%find_msbuild.bat" %MSBUILD% -if ERRORLEVEL 1 (echo Cannot locate MSBuild.exe on PATH or as MSBUILD variable & exit /b 2) -pushd "%D%" -if not defined NOUPLOAD ( - %MSBUILD% /v:m /nologo uploadrelease.proj /t:Upload /p:Platform=x86 - if errorlevel 1 goto :failed - %MSBUILD% /v:m /nologo uploadrelease.proj /t:Upload /p:Platform=x64 /p:IncludeDoc=false - if errorlevel 1 goto :failed -) -if not defined NOPURGE ( - %MSBUILD% /v:m /nologo uploadrelease.proj /t:Purge -) -if not defined NOTEST ( - call :test x86 - if errorlevel 1 goto :failed - call :test x64 - if errorlevel 1 goto :failed -) -%MSBUILD% /v:m /nologo uploadrelease.proj /t:ShowHashes /p:Platform=x86 -if errorlevel 1 goto :failed -%MSBUILD% /v:m /nologo uploadrelease.proj /t:ShowHashes /p:Platform=x64 /p:IncludeDoc=false -if errorlevel 1 goto :failed - -popd -exit /B 0 - -:test -%MSBUILD% /v:m /nologo uploadrelease.proj /t:Test /p:Platform=%1 -if errorlevel 1 ( - echo Test failed - purging and retrying - %MSBUILD% /v:m /nologo uploadrelease.proj /t:Purge - if errorlevel 1 exit /B - %MSBUILD% /v:m /nologo uploadrelease.proj /t:Test /p:Platform=%1 -) -exit /B - -:failed -popd -exit /B - -:Help -echo uploadrelease.bat --host HOST --user USERNAME [--target TARGET] [--dry-run] [-h] -echo. -echo --host (-o) Specify the upload host (required) -echo --user (-u) Specify the user on the host (required) -echo --target (-t) Specify the target directory on the host -echo --dry-run Display commands and filenames without executing them -echo --skip-gpg Does not generate GPG signatures before uploading -echo --skip-purge Does not perform CDN purge after uploading -echo --skip-test (-T) Does not perform post-upload tests -echo -h Display this help information -echo. diff --git a/Tools/msi/uploadrelease.proj b/Tools/msi/uploadrelease.proj deleted file mode 100644 index e70ae1de22fa78b..000000000000000 --- a/Tools/msi/uploadrelease.proj +++ /dev/null @@ -1,113 +0,0 @@ - - - - {2D69F2AB-D5D0-4344-84B5-EF6DB34A9BC9} - python - - - $(TARGET) - /srv/www.python.org/ftp/python - true - true - false - - - - - - - $(DownloadUrlBase.TrimEnd(`/`))/$(MajorVersionNumber).$(MinorVersionNumber).$(MicroVersionNumber) - $(DownloadUrl.Replace(`{version}`, `$(MajorVersionNumber).$(MinorVersionNumber).$(MicroVersionNumber)`).Replace(`{arch}`, `$(ArchName)`).Replace(`{releasename}`, `$(ReleaseLevelName)`).Replace(`{msi}`, ``).TrimEnd(`/`)) - - - - - $(MSITarget) - - - $(EXETarget) - - - $(EXETarget) - - - - - - - - - - - - - - - - - %(File.CopyTo) - - - - - - - - - - - - - - - - - - - - - $(TEMP)\%(Filename)_source - $(TEMP)\%(Filename)_source\%(Filename)%(Extension) - $(TEMP)\%(Filename)_layout - $(OutputPath)\%(Filename)_layoutlog - $(OutputPath)\%(Filename)_layoutlog\%(Filename).log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Tools/msi/uploadrelease.ps1 b/Tools/msi/uploadrelease.ps1 deleted file mode 100644 index a8669e5d65ed14b..000000000000000 --- a/Tools/msi/uploadrelease.ps1 +++ /dev/null @@ -1,171 +0,0 @@ -<# -.Synopsis - Uploads from a VSTS release build layout to python.org -.Description - Given the downloaded/extracted build artifact from a release - build run on python.visualstudio.com, this script uploads - the files to the correct locations. -.Parameter build - The location on disk of the extracted build artifact. -.Parameter user - The username to use when logging into the host. -.Parameter server - The host or PuTTY session name. -.Parameter target - The subdirectory on the host to copy files to. -.Parameter tests - The path to run download tests in. -.Parameter doc_htmlhelp - Optional path besides -build to locate CHM files. -.Parameter embed - Optional path besides -build to locate ZIP files. -.Parameter skipupload - Skip uploading -.Parameter skippurge - Skip purging the CDN -.Parameter skiptest - Skip the download tests -.Parameter skiphash - Skip displaying hashes -#> -param( - [Parameter(Mandatory=$true)][string]$build, - [Parameter(Mandatory=$true)][string]$user, - [string]$server="python-downloads", - [string]$target="/srv/www.python.org/ftp/python", - [string]$tests=${env:TEMP}, - [string]$doc_htmlhelp=$null, - [string]$embed=$null, - [switch]$skipupload, - [switch]$skippurge, - [switch]$skiptest, - [switch]$skiphash -) - -if (-not $build) { throw "-build option is required" } -if (-not $user) { throw "-user option is required" } - -$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent; - -if (-not ((Test-Path "$build\win32\python-*.exe") -or (Test-Path "$build\amd64\python-*.exe"))) { - throw "-build argument does not look like a 'build' directory" -} - -function find-putty-tool { - param ([string]$n) - $t = gcm $n -EA 0 - if (-not $t) { $t = gcm ".\$n" -EA 0 } - if (-not $t) { $t = gcm "${env:ProgramFiles}\PuTTY\$n" -EA 0 } - if (-not $t) { $t = gcm "${env:ProgramFiles(x86)}\PuTTY\$n" -EA 0 } - if (-not $t) { throw "Unable to locate $n.exe. Please put it on $PATH" } - return gi $t.Path -} - -$p = gci -r "$build\python-*.exe" | ` - ?{ $_.Name -match '^python-(\d+\.\d+\.\d+)((a|b|rc)\d+)?-.+' } | ` - select -first 1 | ` - %{ $Matches[1], $Matches[2] } - -"Uploading version $($p[0]) $($p[1])" -" from: $build" -" to: $($server):$target/$($p[0])" -"" - -if (-not $skipupload) { - # Upload files to the server - $pscp = find-putty-tool "pscp" - $plink = find-putty-tool "plink" - - "Upload using $pscp and $plink" - "" - - if ($doc_htmlhelp) { - $chm = gci -EA 0 $doc_htmlhelp\python*.chm, $doc_htmlhelp\python*.chm.asc - } else { - $chm = gci -EA 0 $build\python*.chm, $build\python*.chm.asc - } - - $d = "$target/$($p[0])/" - & $plink -batch $user@$server mkdir $d - & $plink -batch $user@$server chgrp downloads $d - & $plink -batch $user@$server chmod o+rx $d - if ($chm) { - & $pscp -batch $chm.FullName "$user@${server}:$d" - if (-not $?) { throw "Failed to upload $chm" } - } - - $dirs = gci "$build" -Directory - if ($embed) { - $dirs = ($dirs, (gi $embed)) | %{ $_ } - } - - foreach ($a in $dirs) { - "Uploading files from $($a.FullName)" - pushd "$($a.FullName)" - $exe = gci *.exe, *.exe.asc, *.zip, *.zip.asc - $msi = gci *.msi, *.msi.asc, *.msu, *.msu.asc - popd - - if ($exe) { - & $pscp -batch $exe.FullName "$user@${server}:$d" - if (-not $?) { throw "Failed to upload $exe" } - } - - if ($msi) { - $sd = "$d$($a.Name)$($p[1])/" - & $plink -batch $user@$server mkdir $sd - & $plink -batch $user@$server chgrp downloads $sd - & $plink -batch $user@$server chmod o+rx $sd - & $pscp -batch $msi.FullName "$user@${server}:$sd" - if (-not $?) { throw "Failed to upload $msi" } - & $plink -batch $user@$server chgrp downloads $sd* - & $plink -batch $user@$server chmod g-x,o+r $sd* - } - } - - & $plink -batch $user@$server chgrp downloads $d* - & $plink -batch $user@$server chmod g-x,o+r $d* - & $pscp -ls "$user@${server}:$d" -} - -if (-not $skippurge) { - # Run a CDN purge - py $tools\purge.py "$($p[0])$($p[1])" -} - -if (-not $skiptest) { - # Use each web installer to produce a layout. This will download - # each referenced file and validate their signatures/hashes. - gci "$build\*-webinstall.exe" -r -File | %{ - $d = mkdir "$tests\$($_.BaseName)" -Force - gci $d -r -File | del - $ic = copy $_ $d -PassThru - "Checking layout for $($ic.Name)" - Start-Process -wait $ic "/passive", "/layout", "$d\layout", "/log", "$d\log\install.log" - if (-not $?) { - Write-Error "Failed to validate layout of $($inst.Name)" - } - } -} - -if (-not $skiphash) { - # Display MD5 hash and size of each downloadable file - pushd $build - $files = gci python*.chm, *\*.exe, *\*.zip - if ($doc_htmlhelp) { - cd $doc_htmlhelp - $files = ($files, (gci python*.chm)) | %{ $_ } - } - if ($embed) { - cd $embed - $files = ($files, (gci *.zip)) | %{ $_ } - } - popd - - $hashes = $files | ` - Sort-Object Name | ` - Format-Table Name, @{Label="MD5"; Expression={(Get-FileHash $_ -Algorithm MD5).Hash}}, Length -AutoSize | ` - Out-String -Width 4096 - $hashes | clip - $hashes -} diff --git a/Tools/msi/wix.props b/Tools/msi/wix.props deleted file mode 100644 index 707c8aeacb648dc..000000000000000 --- a/Tools/msi/wix.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - $(MSBuildThisFileDirectory)\Wix\ - $(ExternalsDir)\windows-installer\wix-314\ - $(WixInstallPath)\Wix.targets - - \ No newline at end of file diff --git a/Tools/nuget/build.bat b/Tools/nuget/build.bat deleted file mode 100644 index b532bd74216831e..000000000000000 --- a/Tools/nuget/build.bat +++ /dev/null @@ -1,70 +0,0 @@ -@echo off -setlocal -set D=%~dp0 -set PCBUILD=%D%..\..\PCbuild\ -if "%Py_OutDir%"=="" set Py_OutDir=%PCBUILD% - -set BUILDX86= -set BUILDX64= -set BUILDARM32= -set REBUILD= -set OUTPUT= -set PACKAGES= -set PYTHON_EXE= - -:CheckOpts -if "%~1" EQU "-h" goto Help -if "%~1" EQU "-x86" (set BUILDX86=1) && shift && goto CheckOpts -if "%~1" EQU "-x64" (set BUILDX64=1) && shift && goto CheckOpts -if "%~1" EQU "-arm32" (set BUILDARM32=1) && shift && goto CheckOpts -if "%~1" EQU "-r" (set REBUILD=-r) && shift && goto CheckOpts -if "%~1" EQU "-o" (set OUTPUT="/p:OutputPath=%~2") && shift && shift && goto CheckOpts -if "%~1" EQU "--out" (set OUTPUT="/p:OutputPath=%~2") && shift && shift && goto CheckOpts -if "%~1" EQU "-p" (set PACKAGES=%PACKAGES% %~2) && shift && shift && goto CheckOpts -if "%~1" EQU "--python-exe" (set PYTHON_EXE="/p:PythonExe=%~2") && shift && shift && goto CheckOpts - -if not defined BUILDX86 if not defined BUILDX64 if not defined BUILDARM32 (set BUILDX86=1) && (set BUILDX64=1) && (set BUILDARM32=1) - -call "%D%..\msi\get_externals.bat" -call "%PCBUILD%find_msbuild.bat" %MSBUILD% -if ERRORLEVEL 1 (echo Cannot locate MSBuild.exe on PATH or as MSBUILD variable & exit /b 2) - -if defined PACKAGES set PACKAGES="/p:Packages=%PACKAGES%" - -if defined BUILDX86 ( - if defined REBUILD ( call "%PCBUILD%build.bat" -e -r - ) else if not exist "%Py_OutDir%win32\python.exe" call "%PCBUILD%build.bat" -e - if errorlevel 1 goto :eof - - %MSBUILD% "%D%make_pkg.proj" /p:Configuration=Release /p:Platform=x86 %OUTPUT% %PACKAGES% %PYTHON_EXE% - if errorlevel 1 goto :eof -) - -if defined BUILDX64 ( - if defined REBUILD ( call "%PCBUILD%build.bat" -p x64 -e -r - ) else if not exist "%Py_OutDir%amd64\python.exe" call "%PCBUILD%build.bat" -p x64 -e - if errorlevel 1 goto :eof - - %MSBUILD% "%D%make_pkg.proj" /p:Configuration=Release /p:Platform=x64 %OUTPUT% %PACKAGES% %PYTHON_EXE% - if errorlevel 1 goto :eof -) - -if defined BUILDARM32 ( - if defined REBUILD ( call "%PCBUILD%build.bat" -p ARM -e -r --no-tkinter - ) else if not exist "%Py_OutDir%arm32\python.exe" call "%PCBUILD%build.bat" -p ARM -e --no-tkinter - if errorlevel 1 goto :eof - - %MSBUILD% "%D%make_pkg.proj" /p:Configuration=Release /p:Platform=ARM %OUTPUT% %PACKAGES% %PYTHON_EXE% - if errorlevel 1 goto :eof -) - -exit /B 0 - -:Help -echo build.bat [-x86] [-x64] [--out DIR] [-r] [-h] -echo. -echo -x86 Build x86 installers -echo -x64 Build x64 installers -echo -r Rebuild rather than incremental build -echo --out [DIR] Override output directory -echo -h Show usage diff --git a/Tools/nuget/make_pkg.proj b/Tools/nuget/make_pkg.proj deleted file mode 100644 index 710ef3dcb5c01de..000000000000000 --- a/Tools/nuget/make_pkg.proj +++ /dev/null @@ -1,63 +0,0 @@ - - - - {10487945-15D1-4092-A214-338395C4116B} - python - $(OutputName)x86 - $(OutputName)arm32 - $(OutputName)daily - - false - true - - - - - - $(ExternalsDir)\windows-installer\nuget\nuget.exe - $(MajorVersionNumber).$(MinorVersionNumber).$(MicroVersionNumber) - $(NuspecVersion)-$(ReleaseLevelName) - $(MajorVersionNumber).$(MinorVersionNumber).$(DailyBuildVersion) - false - $(OutputName).$(NuspecVersion) - .nupkg - $(IntermediateOutputPath)\nuget_$(ArchName)\ - - rmdir /q/s "$(IntermediateOutputPath.TrimEnd(`\`))" - - "$(PythonExe)" "$(PySourcePath)PC\layout" - $(PythonArguments) -b "$(BuildPath.TrimEnd(`\`))" -s "$(PySourcePath.TrimEnd(`\`))" - $(PythonArguments) -t "$(IntermediateOutputPath)obj" - $(PythonArguments) --copy "$(IntermediateOutputPath)pkg" - $(PythonArguments) --preset-nuget - - "$(IntermediateOutputPath)pkg\pip.exe" -B -m pip install -U $(Packages) - - "$(Nuget)" pack "$(IntermediateOutputPath)pkg\python.nuspec" -BasePath "$(IntermediateOutputPath)pkg" - "$(Nuget)" pack "$(MSBuildThisFileDirectory)\$(OutputName).symbols.nuspec" -BasePath "$(BuildPath.TrimEnd(`\`))" - $(NugetArguments) -OutputDirectory "$(OutputPath.Trim(`\`))" - $(NugetArguments) -Version "$(NuspecVersion)" - $(NugetArguments) -NoPackageAnalysis -NonInteractive - - $(Environment)%0D%0Aset PYTHONPATH=$(PySourcePath)Lib - $(Environment)%0D%0Aset PYTHON_NUSPEC_VERSION=$(NuspecVersion) - $(Environment)%0D%0Aset PYTHON_PROPS_PLATFORM=$(Platform) - $(Environment)%0D%0Aset PYTHON_PROPS_PLATFORM=Win32 - $(Environment)%0D%0Amkdir "$(OutputPath.Trim(`\`))" >nul 2>nul - - - - - - - - - - - - - - - - - diff --git a/Tools/nuget/python.nuspec b/Tools/nuget/python.nuspec deleted file mode 100644 index 2da5f2037eb2342..000000000000000 --- a/Tools/nuget/python.nuspec +++ /dev/null @@ -1,18 +0,0 @@ - - - - python - Python - 0.0.0.0 - Python Software Foundation - tools\LICENSE.txt - https://www.python.org/ - Installs 64-bit Python for use in build scenarios. - https://www.python.org/static/favicon.ico - python - - - - - - diff --git a/Tools/nuget/pythonarm32.nuspec b/Tools/nuget/pythonarm32.nuspec deleted file mode 100644 index 2d197931edb32c3..000000000000000 --- a/Tools/nuget/pythonarm32.nuspec +++ /dev/null @@ -1,20 +0,0 @@ - - - - pythonarm32 - Python (ARM32) - Python Software Foundation - 0.0.0.0 - https://docs.python.org/3/license.html - https://www.python.org/ - false - Installs Python ARM32 for use in build scenarios. - https://www.python.org/static/favicon.ico - python - - - - - - - diff --git a/Tools/nuget/pythondaily.nuspec b/Tools/nuget/pythondaily.nuspec deleted file mode 100644 index 7df1983f42a6fe2..000000000000000 --- a/Tools/nuget/pythondaily.nuspec +++ /dev/null @@ -1,19 +0,0 @@ - - - - pythondaily - Python (Daily build) - Python Software Foundation - 0.0.0.0 - tools\LICENSE.txt - https://www.python.org/ - Installs an unsigned, untested build of Python for test purposes only. - https://www.python.org/static/favicon.ico - python - - - - - - - diff --git a/Tools/nuget/pythondaily.symbols.nuspec b/Tools/nuget/pythondaily.symbols.nuspec deleted file mode 100644 index 608e15c50e02d2d..000000000000000 --- a/Tools/nuget/pythondaily.symbols.nuspec +++ /dev/null @@ -1,22 +0,0 @@ - - - - pythondaily.symbols - Python (Daily build) - 0.0.0.0 - Python Software Foundation - https://www.python.org/ - Contains symbols for the daily build of Python. - https://www.python.org/static/favicon.ico - python - - - - - - - - - - - diff --git a/Tools/nuget/pythonx86.nuspec b/Tools/nuget/pythonx86.nuspec deleted file mode 100644 index ea878ba0bf390b6..000000000000000 --- a/Tools/nuget/pythonx86.nuspec +++ /dev/null @@ -1,19 +0,0 @@ - - - - pythonx86 - Python (32-bit) - Python Software Foundation - 0.0.0.0 - tools\LICENSE.txt - https://www.python.org/ - Installs 32-bit Python for use in build scenarios. - https://www.python.org/static/favicon.ico - python - - - - - - - From 2fd0e58275b40c3a548e35359601fcb2f434d1b6 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Fri, 10 Jul 2026 13:32:07 +0100 Subject: [PATCH 2/4] Remove legacy launcher tests --- Lib/test/test_launcher.py | 804 -------------------------------------- 1 file changed, 804 deletions(-) delete mode 100644 Lib/test/test_launcher.py diff --git a/Lib/test/test_launcher.py b/Lib/test/test_launcher.py deleted file mode 100644 index 795890bd7e4b473..000000000000000 --- a/Lib/test/test_launcher.py +++ /dev/null @@ -1,804 +0,0 @@ -import contextlib -import itertools -import os -import re -import shutil -import subprocess -import sys -import sysconfig -import tempfile -import unittest -from pathlib import Path -from test import support - -if sys.platform != "win32": - raise unittest.SkipTest("test only applies to Windows") - -# Get winreg after the platform check -import winreg - - -PY_EXE = "py.exe" -DEBUG_BUILD = False -if sys.executable.casefold().endswith("_d.exe".casefold()): - PY_EXE = "py_d.exe" - DEBUG_BUILD = True - -# Registry data to create. On removal, everything beneath top-level names will -# be deleted. -TEST_DATA = { - "PythonTestSuite": { - "DisplayName": "Python Test Suite", - "SupportUrl": "https://www.python.org/", - "3.100": { - "DisplayName": "X.Y version", - "InstallPath": { - None: sys.prefix, - "ExecutablePath": "X.Y.exe", - } - }, - "3.100-32": { - "DisplayName": "X.Y-32 version", - "InstallPath": { - None: sys.prefix, - "ExecutablePath": "X.Y-32.exe", - } - }, - "3.100-arm64": { - "DisplayName": "X.Y-arm64 version", - "InstallPath": { - None: sys.prefix, - "ExecutablePath": "X.Y-arm64.exe", - "ExecutableArguments": "-X fake_arg_for_test", - } - }, - "ignored": { - "DisplayName": "Ignored because no ExecutablePath", - "InstallPath": { - None: sys.prefix, - } - }, - }, - "PythonTestSuite1": { - "DisplayName": "Python Test Suite Single", - "3.100": { - "DisplayName": "Single Interpreter", - "InstallPath": { - None: sys.prefix, - "ExecutablePath": sys.executable, - } - } - }, -} - - -TEST_PY_ENV = dict( - PY_PYTHON="PythonTestSuite/3.100", - PY_PYTHON2="PythonTestSuite/3.100-32", - PY_PYTHON3="PythonTestSuite/3.100-arm64", -) - - -TEST_PY_DEFAULTS = "\n".join([ - "[defaults]", - *[f"{k[3:].lower()}={v}" for k, v in TEST_PY_ENV.items()], -]) - - -TEST_PY_COMMANDS = "\n".join([ - "[commands]", - "test-command=TEST_EXE.exe", -]) - - -def quote(s): - s = str(s) - return f'"{s}"' if " " in s else s - - -def create_registry_data(root, data): - def _create_registry_data(root, key, value): - if isinstance(value, dict): - # For a dict, we recursively create keys - with winreg.CreateKeyEx(root, key) as hkey: - for k, v in value.items(): - _create_registry_data(hkey, k, v) - elif isinstance(value, str): - # For strings, we set values. 'key' may be None in this case - winreg.SetValueEx(root, key, None, winreg.REG_SZ, value) - else: - raise TypeError("don't know how to create data for '{}'".format(value)) - - for k, v in data.items(): - _create_registry_data(root, k, v) - - -def enum_keys(root): - for i in itertools.count(): - try: - yield winreg.EnumKey(root, i) - except OSError as ex: - if ex.winerror == 259: - break - raise - - -def delete_registry_data(root, keys): - ACCESS = winreg.KEY_WRITE | winreg.KEY_ENUMERATE_SUB_KEYS - for key in list(keys): - with winreg.OpenKey(root, key, access=ACCESS) as hkey: - delete_registry_data(hkey, enum_keys(hkey)) - winreg.DeleteKey(root, key) - - -def is_installed(tag): - key = rf"Software\Python\PythonCore\{tag}\InstallPath" - for root, flag in [ - (winreg.HKEY_CURRENT_USER, 0), - (winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_64KEY), - (winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_32KEY), - ]: - try: - winreg.CloseKey(winreg.OpenKey(root, key, access=winreg.KEY_READ | flag)) - return True - except OSError: - pass - return False - - -class PreservePyIni: - def __init__(self, path, content): - self.path = Path(path) - self.content = content - self._preserved = None - - def __enter__(self): - try: - self._preserved = self.path.read_bytes() - except FileNotFoundError: - self._preserved = None - self.path.write_text(self.content, encoding="utf-16") - - def __exit__(self, *exc_info): - if self._preserved is None: - self.path.unlink() - else: - self.path.write_bytes(self._preserved) - - -class RunPyMixin: - py_exe = None - - @classmethod - def find_py(cls): - py_exe = None - if sysconfig.is_python_build(): - py_exe = Path(sys.executable).parent / PY_EXE - else: - for p in os.getenv("PATH").split(";"): - if p: - py_exe = Path(p) / PY_EXE - if py_exe.is_file(): - break - else: - py_exe = None - - # Test launch and check version, to exclude installs of older - # releases when running outside of a source tree - if py_exe: - try: - with subprocess.Popen( - [py_exe, "-h"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding="ascii", - errors="ignore", - ) as p: - p.stdin.close() - version = next(p.stdout, "\n").splitlines()[0].rpartition(" ")[2] - p.stdout.read() - p.wait(10) - if not sys.version.startswith(version): - py_exe = None - except OSError: - py_exe = None - - if not py_exe: - raise unittest.SkipTest( - "cannot locate '{}' for test".format(PY_EXE) - ) - return py_exe - - def get_py_exe(self): - if not self.py_exe: - self.py_exe = self.find_py() - return self.py_exe - - def run_py(self, args, env=None, allow_fail=False, expect_returncode=0, argv=None): - if not self.py_exe: - self.py_exe = self.find_py() - - ignore = {"VIRTUAL_ENV", "PY_PYTHON", "PY_PYTHON2", "PY_PYTHON3"} - env = { - **{k.upper(): v for k, v in os.environ.items() if k.upper() not in ignore}, - "PYLAUNCHER_DEBUG": "1", - "PYLAUNCHER_DRYRUN": "1", - "PYLAUNCHER_LIMIT_TO_COMPANY": "", - **{k.upper(): v for k, v in (env or {}).items()}, - } - if ini_dir := getattr(self, '_ini_dir', None): - env.setdefault("_PYLAUNCHER_INIDIR", ini_dir) - if not argv: - argv = [self.py_exe, *args] - with subprocess.Popen( - argv, - env=env, - executable=self.py_exe, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) as p: - p.stdin.close() - p.wait(10) - out = p.stdout.read().decode("utf-8", "replace") - err = p.stderr.read().decode("ascii", "replace").replace("\uFFFD", "?") - if p.returncode != expect_returncode and support.verbose and not allow_fail: - print("++ COMMAND ++") - print([self.py_exe, *args]) - print("++ STDOUT ++") - print(out) - print("++ STDERR ++") - print(err) - if allow_fail and p.returncode != expect_returncode: - raise subprocess.CalledProcessError(p.returncode, [self.py_exe, *args], out, err) - else: - self.assertEqual(expect_returncode, p.returncode) - data = { - s.partition(":")[0]: s.partition(":")[2].lstrip() - for s in err.splitlines() - if not s.startswith("#") and ":" in s - } - data["stdout"] = out - data["stderr"] = err - return data - - def py_ini(self, content): - ini_dir = getattr(self, '_ini_dir', None) - if not ini_dir: - local_appdata = os.environ.get("LOCALAPPDATA") - if not local_appdata: - raise unittest.SkipTest("LOCALAPPDATA environment variable is " - "missing or empty") - ini_dir = local_appdata - return PreservePyIni(Path(ini_dir) / "py.ini", content) - - @contextlib.contextmanager - def script(self, content, encoding="utf-8"): - file = Path(tempfile.mktemp(dir=os.getcwd()) + ".py") - if isinstance(content, bytes): - file.write_bytes(content) - else: - file.write_text(content, encoding=encoding) - try: - yield file - finally: - file.unlink() - - @contextlib.contextmanager - def fake_venv(self): - venv = Path.cwd() / "Scripts" - venv.mkdir(exist_ok=True, parents=True) - venv_exe = (venv / ("python_d.exe" if DEBUG_BUILD else "python.exe")) - venv_exe.touch() - try: - yield venv_exe, {"VIRTUAL_ENV": str(venv.parent)} - finally: - shutil.rmtree(venv) - - -class TestLauncher(unittest.TestCase, RunPyMixin): - @classmethod - def setUpClass(cls): - with winreg.CreateKey(winreg.HKEY_CURRENT_USER, rf"Software\Python") as key: - create_registry_data(key, TEST_DATA) - - if support.verbose: - p = subprocess.check_output("reg query HKCU\\Software\\Python /s") - #print(p.decode('mbcs')) - - cls._ini_dir = tempfile.mkdtemp() - cls.addClassCleanup(shutil.rmtree, cls._ini_dir, ignore_errors=True) - - @classmethod - def tearDownClass(cls): - with winreg.OpenKey(winreg.HKEY_CURRENT_USER, rf"Software\Python", access=winreg.KEY_WRITE | winreg.KEY_ENUMERATE_SUB_KEYS) as key: - delete_registry_data(key, TEST_DATA) - - - def test_version(self): - data = self.run_py(["-0"]) - self.assertEqual(self.py_exe, Path(data["argv0"])) - self.assertEqual(sys.version.partition(" ")[0], data["version"]) - - def test_help_option(self): - data = self.run_py(["-h"]) - self.assertEqual("True", data["SearchInfo.help"]) - - def test_list_option(self): - for opt, v1, v2 in [ - ("-0", "True", "False"), - ("-0p", "False", "True"), - ("--list", "True", "False"), - ("--list-paths", "False", "True"), - ]: - with self.subTest(opt): - data = self.run_py([opt]) - self.assertEqual(v1, data["SearchInfo.list"]) - self.assertEqual(v2, data["SearchInfo.listPaths"]) - - def test_list(self): - data = self.run_py(["--list"]) - found = {} - expect = {} - for line in data["stdout"].splitlines(): - m = re.match(r"\s*(.+?)\s+?(\*\s+)?(.+)$", line) - if m: - found[m.group(1)] = m.group(3) - for company in TEST_DATA: - company_data = TEST_DATA[company] - tags = [t for t in company_data if isinstance(company_data[t], dict)] - for tag in tags: - arg = f"-V:{company}/{tag}" - expect[arg] = company_data[tag]["DisplayName"] - expect.pop(f"-V:{company}/ignored", None) - - actual = {k: v for k, v in found.items() if k in expect} - try: - self.assertDictEqual(expect, actual) - except: - if support.verbose: - print("*** STDOUT ***") - print(data["stdout"]) - raise - - def test_list_paths(self): - data = self.run_py(["--list-paths"]) - found = {} - expect = {} - for line in data["stdout"].splitlines(): - m = re.match(r"\s*(.+?)\s+?(\*\s+)?(.+)$", line) - if m: - found[m.group(1)] = m.group(3) - for company in TEST_DATA: - company_data = TEST_DATA[company] - tags = [t for t in company_data if isinstance(company_data[t], dict)] - for tag in tags: - arg = f"-V:{company}/{tag}" - install = company_data[tag]["InstallPath"] - try: - expect[arg] = install["ExecutablePath"] - try: - expect[arg] += " " + install["ExecutableArguments"] - except KeyError: - pass - except KeyError: - expect[arg] = str(Path(install[None]) / Path(sys.executable).name) - - expect.pop(f"-V:{company}/ignored", None) - - actual = {k: v for k, v in found.items() if k in expect} - try: - self.assertDictEqual(expect, actual) - except: - if support.verbose: - print("*** STDOUT ***") - print(data["stdout"]) - raise - - def test_filter_to_company(self): - company = "PythonTestSuite" - data = self.run_py([f"-V:{company}/"]) - self.assertEqual("X.Y.exe", data["LaunchCommand"]) - self.assertEqual(company, data["env.company"]) - self.assertEqual("3.100", data["env.tag"]) - - def test_filter_to_company_with_default(self): - company = "PythonTestSuite" - data = self.run_py([f"-V:{company}/"], env=dict(PY_PYTHON="3.0")) - self.assertEqual("X.Y.exe", data["LaunchCommand"]) - self.assertEqual(company, data["env.company"]) - self.assertEqual("3.100", data["env.tag"]) - - def test_filter_to_tag(self): - company = "PythonTestSuite" - data = self.run_py(["-V:3.100"]) - self.assertEqual("X.Y.exe", data["LaunchCommand"]) - self.assertEqual(company, data["env.company"]) - self.assertEqual("3.100", data["env.tag"]) - - data = self.run_py(["-V:3.100-32"]) - self.assertEqual("X.Y-32.exe", data["LaunchCommand"]) - self.assertEqual(company, data["env.company"]) - self.assertEqual("3.100-32", data["env.tag"]) - - data = self.run_py(["-V:3.100-arm64"]) - self.assertEqual("X.Y-arm64.exe -X fake_arg_for_test", data["LaunchCommand"]) - self.assertEqual(company, data["env.company"]) - self.assertEqual("3.100-arm64", data["env.tag"]) - - def test_filter_to_company_and_tag(self): - company = "PythonTestSuite" - data = self.run_py([f"-V:{company}/3.1"], expect_returncode=103) - - data = self.run_py([f"-V:{company}/3.100"]) - self.assertEqual("X.Y.exe", data["LaunchCommand"]) - self.assertEqual(company, data["env.company"]) - self.assertEqual("3.100", data["env.tag"]) - - def test_filter_with_single_install(self): - company = "PythonTestSuite1" - data = self.run_py( - ["-V:Nonexistent"], - env={"PYLAUNCHER_LIMIT_TO_COMPANY": company}, - expect_returncode=103, - ) - - def test_search_major_3(self): - try: - data = self.run_py(["-3"], allow_fail=True) - except subprocess.CalledProcessError: - raise unittest.SkipTest("requires at least one Python 3.x install") - self.assertEqual("PythonCore", data["env.company"]) - self.assertStartsWith(data["env.tag"], "3.") - - def test_search_major_3_32(self): - try: - data = self.run_py(["-3-32"], allow_fail=True) - except subprocess.CalledProcessError: - if not any(is_installed(f"3.{i}-32") for i in range(5, 11)): - raise unittest.SkipTest("requires at least one 32-bit Python 3.x install") - raise - self.assertEqual("PythonCore", data["env.company"]) - self.assertStartsWith(data["env.tag"], "3.") - self.assertEndsWith(data["env.tag"], "-32") - - def test_search_major_2(self): - try: - data = self.run_py(["-2"], allow_fail=True) - except subprocess.CalledProcessError: - if not is_installed("2.7"): - raise unittest.SkipTest("requires at least one Python 2.x install") - raise - self.assertEqual("PythonCore", data["env.company"]) - self.assertStartsWith(data["env.tag"], "2.") - - def test_py_default(self): - with self.py_ini(TEST_PY_DEFAULTS): - data = self.run_py(["-arg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual("X.Y.exe -arg", data["stdout"].strip()) - - def test_py2_default(self): - with self.py_ini(TEST_PY_DEFAULTS): - data = self.run_py(["-2", "-arg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100-32", data["SearchInfo.tag"]) - self.assertEqual("X.Y-32.exe -arg", data["stdout"].strip()) - - def test_py3_default(self): - with self.py_ini(TEST_PY_DEFAULTS): - data = self.run_py(["-3", "-arg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100-arm64", data["SearchInfo.tag"]) - self.assertEqual("X.Y-arm64.exe -X fake_arg_for_test -arg", data["stdout"].strip()) - - def test_py_default_env(self): - data = self.run_py(["-arg"], env=TEST_PY_ENV) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual("X.Y.exe -arg", data["stdout"].strip()) - - def test_py2_default_env(self): - data = self.run_py(["-2", "-arg"], env=TEST_PY_ENV) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100-32", data["SearchInfo.tag"]) - self.assertEqual("X.Y-32.exe -arg", data["stdout"].strip()) - - def test_py3_default_env(self): - data = self.run_py(["-3", "-arg"], env=TEST_PY_ENV) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100-arm64", data["SearchInfo.tag"]) - self.assertEqual("X.Y-arm64.exe -X fake_arg_for_test -arg", data["stdout"].strip()) - - def test_py_default_short_argv0(self): - with self.py_ini(TEST_PY_DEFAULTS): - for argv0 in ['"py.exe"', 'py.exe', '"py"', 'py']: - with self.subTest(argv0): - data = self.run_py(["--version"], argv=f'{argv0} --version') - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual("X.Y.exe --version", data["stdout"].strip()) - - def test_py_default_in_list(self): - data = self.run_py(["-0"], env=TEST_PY_ENV) - default = None - for line in data["stdout"].splitlines(): - m = re.match(r"\s*-V:(.+?)\s+?\*\s+(.+)$", line) - if m: - default = m.group(1) - break - self.assertEqual("PythonTestSuite/3.100", default) - - def test_virtualenv_in_list(self): - with self.fake_venv() as (venv_exe, env): - data = self.run_py(["-0p"], env=env) - for line in data["stdout"].splitlines(): - m = re.match(r"\s*\*\s+(.+)$", line) - if m: - self.assertEqual(str(venv_exe), m.group(1)) - break - else: - if support.verbose: - print(data["stdout"]) - print(data["stderr"]) - self.fail("did not find active venv path") - - data = self.run_py(["-0"], env=env) - for line in data["stdout"].splitlines(): - m = re.match(r"\s*\*\s+(.+)$", line) - if m: - self.assertEqual("Active venv", m.group(1)) - break - else: - self.fail("did not find active venv entry") - - def test_virtualenv_with_env(self): - with self.fake_venv() as (venv_exe, env): - data1 = self.run_py([], env={**env, "PY_PYTHON": "PythonTestSuite/3"}) - data2 = self.run_py(["-V:PythonTestSuite/3"], env={**env, "PY_PYTHON": "PythonTestSuite/3"}) - # Compare stdout, because stderr goes via ascii - self.assertEqual(data1["stdout"].strip(), quote(venv_exe)) - self.assertEqual(data1["SearchInfo.lowPriorityTag"], "True") - # Ensure passing the argument doesn't trigger the same behaviour - self.assertNotEqual(data2["stdout"].strip(), quote(venv_exe)) - self.assertNotEqual(data2["SearchInfo.lowPriorityTag"], "True") - - def test_py_shebang(self): - with self.py_ini(TEST_PY_DEFAULTS): - with self.script("#! /usr/bin/python -prearg") as script: - data = self.run_py([script, "-postarg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg", data["stdout"].strip()) - - def test_python_shebang(self): - with self.py_ini(TEST_PY_DEFAULTS): - with self.script("#! python -prearg") as script: - data = self.run_py([script, "-postarg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg", data["stdout"].strip()) - - def test_py2_shebang(self): - with self.py_ini(TEST_PY_DEFAULTS): - with self.script("#! /usr/bin/python2 -prearg") as script: - data = self.run_py([script, "-postarg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100-32", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y-32.exe -prearg {quote(script)} -postarg", - data["stdout"].strip()) - - def test_py3_shebang(self): - with self.py_ini(TEST_PY_DEFAULTS): - with self.script("#! /usr/bin/python3 -prearg") as script: - data = self.run_py([script, "-postarg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100-arm64", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y-arm64.exe -X fake_arg_for_test -prearg {quote(script)} -postarg", - data["stdout"].strip()) - - def test_py_shebang_nl(self): - with self.py_ini(TEST_PY_DEFAULTS): - with self.script("#! /usr/bin/python -prearg\n") as script: - data = self.run_py([script, "-postarg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg", - data["stdout"].strip()) - - def test_py2_shebang_nl(self): - with self.py_ini(TEST_PY_DEFAULTS): - with self.script("#! /usr/bin/python2 -prearg\n") as script: - data = self.run_py([script, "-postarg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100-32", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y-32.exe -prearg {quote(script)} -postarg", - data["stdout"].strip()) - - def test_py3_shebang_nl(self): - with self.py_ini(TEST_PY_DEFAULTS): - with self.script("#! /usr/bin/python3 -prearg\n") as script: - data = self.run_py([script, "-postarg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100-arm64", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y-arm64.exe -X fake_arg_for_test -prearg {quote(script)} -postarg", - data["stdout"].strip()) - - def test_py_shebang_short_argv0(self): - with self.py_ini(TEST_PY_DEFAULTS): - with self.script("#! /usr/bin/python -prearg") as script: - # Override argv to only pass "py.exe" as the command - data = self.run_py([script, "-postarg"], argv=f'"py.exe" "{script}" -postarg') - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual(f'X.Y.exe -prearg "{script}" -postarg', data["stdout"].strip()) - - def test_py_shebang_valid_bom(self): - with self.py_ini(TEST_PY_DEFAULTS): - content = "#! /usr/bin/python -prearg".encode("utf-8") - with self.script(b"\xEF\xBB\xBF" + content) as script: - data = self.run_py([script, "-postarg"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg", data["stdout"].strip()) - - def test_py_shebang_invalid_bom(self): - with self.py_ini(TEST_PY_DEFAULTS): - content = "#! /usr/bin/python3 -prearg".encode("utf-8") - with self.script(b"\xEF\xAA\xBF" + content) as script: - data = self.run_py([script, "-postarg"]) - self.assertIn("Invalid BOM", data["stderr"]) - self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) - self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y.exe {quote(script)} -postarg", data["stdout"].strip()) - - def test_py_handle_64_in_ini(self): - with self.py_ini("\n".join(["[defaults]", "python=3.999-64"])): - # Expect this to fail, but should get oldStyleTag flipped on - data = self.run_py([], allow_fail=True, expect_returncode=103) - self.assertEqual("3.999-64", data["SearchInfo.tag"]) - self.assertEqual("True", data["SearchInfo.oldStyleTag"]) - - def test_search_path(self): - exe = Path("arbitrary-exe-name.exe").absolute() - exe.touch() - self.addCleanup(exe.unlink) - with self.py_ini(TEST_PY_DEFAULTS): - with self.script(f"#! /usr/bin/env {exe.stem} -prearg") as script: - data = self.run_py( - [script, "-postarg"], - env={"PATH": f"{exe.parent};{os.getenv('PATH')}"}, - ) - self.assertEqual(f"{quote(exe)} -prearg {quote(script)} -postarg", - data["stdout"].strip()) - - def test_search_path_exe(self): - # Leave the .exe on the name to ensure we don't add it a second time - exe = Path("arbitrary-exe-name.exe").absolute() - exe.touch() - self.addCleanup(exe.unlink) - with self.py_ini(TEST_PY_DEFAULTS): - with self.script(f"#! /usr/bin/env {exe.name} -prearg") as script: - data = self.run_py( - [script, "-postarg"], - env={"PATH": f"{exe.parent};{os.getenv('PATH')}"}, - ) - self.assertEqual(f"{quote(exe)} -prearg {quote(script)} -postarg", - data["stdout"].strip()) - - def test_recursive_search_path(self): - stem = self.get_py_exe().stem - with self.py_ini(TEST_PY_DEFAULTS): - with self.script(f"#! /usr/bin/env {stem}") as script: - data = self.run_py( - [script], - env={"PATH": f"{self.get_py_exe().parent};{os.getenv('PATH')}"}, - ) - # The recursive search is ignored and we get normal "py" behavior - self.assertEqual(f"X.Y.exe {quote(script)}", data["stdout"].strip()) - - def test_install(self): - data = self.run_py(["-V:3.10"], env={"PYLAUNCHER_ALWAYS_INSTALL": "1"}, expect_returncode=111) - cmd = data["stdout"].strip() - # If winget is runnable, we should find it. Otherwise, we'll be trying - # to open the Store. - try: - subprocess.check_call(["winget.exe", "--version"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - except FileNotFoundError: - self.assertIn("ms-windows-store://", cmd) - else: - self.assertIn("winget.exe", cmd) - # Both command lines include the store ID - self.assertIn("9PJPW5LDXLZ5", cmd) - - def test_literal_shebang_absolute(self): - with self.script("#! C:/some_random_app -witharg") as script: - data = self.run_py([script]) - self.assertEqual( - f"C:\\some_random_app -witharg {quote(script)}", - data["stdout"].strip(), - ) - - def test_literal_shebang_relative(self): - with self.script("#! ..\\some_random_app -witharg") as script: - data = self.run_py([script]) - self.assertEqual( - f"{quote(script.parent.parent / 'some_random_app')} -witharg {quote(script)}", - data["stdout"].strip(), - ) - - def test_literal_shebang_quoted(self): - with self.script('#! "some random app" -witharg') as script: - data = self.run_py([script]) - self.assertEqual( - f"{quote(script.parent / 'some random app')} -witharg {quote(script)}", - data["stdout"].strip(), - ) - - with self.script('#! some" random "app -witharg') as script: - data = self.run_py([script]) - self.assertEqual( - f"{quote(script.parent / 'some random app')} -witharg {quote(script)}", - data["stdout"].strip(), - ) - - def test_literal_shebang_quoted_escape(self): - with self.script('#! some\\" random "app -witharg') as script: - data = self.run_py([script]) - self.assertEqual( - f"{quote(script.parent / 'some/ random app')} -witharg {quote(script)}", - data["stdout"].strip(), - ) - - def test_literal_shebang_command(self): - with self.py_ini(TEST_PY_COMMANDS): - with self.script('#! test-command arg1') as script: - data = self.run_py([script]) - self.assertEqual( - f"TEST_EXE.exe arg1 {quote(script)}", - data["stdout"].strip(), - ) - - def test_literal_shebang_invalid_template(self): - with self.script('#! /usr/bin/not-python arg1') as script: - data = self.run_py([script]) - expect = script.parent / "/usr/bin/not-python" - self.assertEqual( - f"{quote(expect)} arg1 {quote(script)}", - data["stdout"].strip(), - ) - - def test_shebang_command_in_venv(self): - stem = "python-that-is-not-on-path" - - # First ensure that our test name doesn't exist, and the launcher does - # not match any installed env - with self.script(f'#! /usr/bin/env {stem} arg1') as script: - data = self.run_py([script], expect_returncode=103) - - with self.fake_venv() as (venv_exe, env): - # Put a "normal" Python on PATH as a distraction. - # The active VIRTUAL_ENV should be preferred when the name isn't an - # exact match. - exe = Path(Path(venv_exe).name).absolute() - exe.touch() - self.addCleanup(exe.unlink) - env["PATH"] = f"{exe.parent};{os.environ['PATH']}" - - with self.script(f'#! /usr/bin/env {stem} arg1') as script: - data = self.run_py([script], env=env) - self.assertEqual(data["stdout"].strip(), f"{quote(venv_exe)} arg1 {quote(script)}") - - with self.script(f'#! /usr/bin/env {exe.stem} arg1') as script: - data = self.run_py([script], env=env) - self.assertEqual(data["stdout"].strip(), f"{quote(exe)} arg1 {quote(script)}") - - def test_shebang_executable_extension(self): - with self.script('#! /usr/bin/env python3.99') as script: - data = self.run_py([script], expect_returncode=103) - expect = "# Search PATH for python3.99.exe" - actual = [line.strip() for line in data["stderr"].splitlines() - if line.startswith("# Search PATH")] - self.assertEqual([expect], actual) From a6de33493b9034b7a25d1016a0718a7dc0d831b0 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Fri, 10 Jul 2026 13:53:00 +0100 Subject: [PATCH 3/4] Additional changes from code review --- PC/store_info.txt | 156 ----------------------------- PCbuild/pyshellext.vcxproj | 117 ---------------------- PCbuild/pyshellext.vcxproj.filters | 28 ------ 3 files changed, 301 deletions(-) delete mode 100644 PC/store_info.txt delete mode 100644 PCbuild/pyshellext.vcxproj delete mode 100644 PCbuild/pyshellext.vcxproj.filters diff --git a/PC/store_info.txt b/PC/store_info.txt deleted file mode 100644 index d150ba17cbe62dd..000000000000000 --- a/PC/store_info.txt +++ /dev/null @@ -1,156 +0,0 @@ -# Overview - -NOTE: This file requires more content. - -Since Python 3.7.2, releases have been made through the Microsoft Store -to allow easy installation on Windows 10.0.17763.0 and later. - -# Building - -To build the store package, the PC/layout script should be used. -Execute the directory with the build of Python to package, and pass -"-h" for full command-line options. - -To sideload test builds, you will need a local certificate. -Instructions are available at -https://docs.microsoft.com/windows/uwp/packaging/create-certificate-package-signing. - -After exporting your certificate, you will need the subject name and -SHA256 hash. The `certutil -dump ` command will display this -information. - -To build for sideloading, use these commands in PowerShell: - -``` -$env:APPX_DATA_PUBLISHER= -$env:APPX_DATA_SHA256= -$env:SigningCertificateFile= - -python PC/layout --copy --include-appxmanifest -Tools/msi/make_appx.ps1 python.msix -sign - -Add-AppxPackage python.msix -``` - -(Note that only the last command requires PowerShell, and the others -can be used from Command Prompt. You can also double-click to install -the final package.) - -To build for publishing to the Store, use these commands: - -``` -$env:APPX_DATA_PUBLISHER = $null -$env:APPX_DATA_SHA256 = $null - -python PC/layout --copy --preset-appxmanifest --precompile -Tools/msi/make_appx.ps1 python.msix -``` - -Note that this package cannot be installed locally. It may only be -added to a submission for the store. - - -# Submission Metadata - -This file contains the text that we use to fill out the store listing -for the Microsoft Store. It needs to be entered manually when creating -a new submission via the dashboard at -https://partner.microsoft.com/dashboard. - -We keep it here for convenience and to allow it to be updated via pull -requests. - -When submitting a new app, the HeadlessAppBypass waiver will be needed. -To request this, send an email to PartnerOps@microsoft.com with the app -ID (12 character token available from the dashboard). The waiver needs -to be applied *before* uploading the package (as of November 2019). - -Ensure that the new app is named "Python.3.X", where X is the minor -version of the release. If the name provided initially does not match -the name used when building the package, the upload will fail. The -display name shown to users can be set later. - -## Title - -Python 3.9 - -## Short Title - -Python - -## Description - -Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a simple but effective approach to object-oriented programming. Python’s elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many areas on most platforms. - -The Python interpreter and the extensive standard library are freely available in source or binary form for all major platforms from the Python web site, https://www.python.org/, and may be freely distributed. The same site also contains distributions of and pointers to many free third party Python modules, programs and tools, and additional documentation. - -The Python interpreter is easily extended with new functions and data types implemented in C or C++ (or other languages callable from C). Python is also suitable as an extension language for customizable applications. - -## ShortDescription - -The Python 3.9 interpreter and runtime. - -## Copyright Trademark Information - -(c) Python Software Foundation - -## Additional License Terms - -Visit https://docs.python.org/3.9/license.html for latest license terms. - -PSF LICENSE AGREEMENT FOR PYTHON 3.9 - -1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and - the Individual or Organization ("Licensee") accessing and otherwise using Python - 3.9 software in source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby - grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, - analyze, test, perform and/or display publicly, prepare derivative works, - distribute, and otherwise use Python 3.9 alone or in any derivative - version, provided, however, that PSF's License Agreement and PSF's notice of - copyright, i.e., "Copyright © 2001 Python Software Foundation; All Rights - Reserved" are retained in Python 3.9 alone or in any derivative version - prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on or - incorporates Python 3.9 or any part thereof, and wants to make the - derivative work available to others as provided herein, then Licensee hereby - agrees to include in any such work a brief summary of the changes made to Python - 3.9. - -4. PSF is making Python 3.9 available to Licensee on an "AS IS" basis. - PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF - EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR - WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE - USE OF PYTHON 3.9 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.9 - FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF - MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.9, OR ANY DERIVATIVE - THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material breach of - its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any relationship - of agency, partnership, or joint venture between PSF and Licensee. This License - Agreement does not grant permission to use PSF trademarks or trade name in a - trademark sense to endorse or promote products or services of Licensee, or any - third party. - -8. By copying, installing or otherwise using Python 3.9, Licensee agrees - to be bound by the terms and conditions of this License Agreement. - -## Features - -* Easy to install Python runtime -* Supported by core CPython team -* Find Python, Pip and Idle on PATH - -## Search Terms - -* Python -* Scripting -* Interpreter - diff --git a/PCbuild/pyshellext.vcxproj b/PCbuild/pyshellext.vcxproj deleted file mode 100644 index ea432d6bc9a3f33..000000000000000 --- a/PCbuild/pyshellext.vcxproj +++ /dev/null @@ -1,117 +0,0 @@ - - - - - Debug - ARM - - - Debug - ARM64 - - - Debug - Win32 - - - Debug - x64 - - - PGInstrument - ARM - - - PGInstrument - ARM64 - - - PGInstrument - Win32 - - - PGInstrument - x64 - - - PGUpdate - ARM - - - PGUpdate - ARM64 - - - PGUpdate - Win32 - - - PGUpdate - x64 - - - Release - ARM - - - Release - ARM64 - - - Release - Win32 - - - Release - x64 - - - - {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782} - pyshellext - pyshellext - false - - - - - DynamicLibrary - Unicode - - - - - - ClCompile - - - - - - - - - _CONSOLE;%(PreprocessorDefinitions) - - - version.lib;shlwapi.lib;%(AdditionalDependencies) - Console - ..\PC\pyshellext.def - - - true - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/PCbuild/pyshellext.vcxproj.filters b/PCbuild/pyshellext.vcxproj.filters deleted file mode 100644 index 77cd3060857a80b..000000000000000 --- a/PCbuild/pyshellext.vcxproj.filters +++ /dev/null @@ -1,28 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - - - Resource Files - - - - - Source Files - - - \ No newline at end of file From 08a032b5a5a571e609b5ba34d20c2a9e4cf4e9a4 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Fri, 10 Jul 2026 18:47:37 +0100 Subject: [PATCH 4/4] Fix additional changes (missed in last commit) --- .azure-pipelines/windows-layout-steps.yml | 2 +- .gitattributes | 2 +- .github/CODEOWNERS | 8 ++------ .gitignore | 3 +-- Include/internal/pycore_magic_number.h | 3 --- InternalDocs/interpreter.md | 4 +--- Lib/test/test_tools/test_compute_changes.py | 9 --------- PCbuild/readme.txt | 8 -------- PCbuild/venvlauncher.vcxproj | 3 --- PCbuild/venvlauncher.vcxproj.filters | 7 +------ PCbuild/venvwlauncher.vcxproj.filters | 7 +------ README.rst | 4 ++-- Tools/build/compute-changes.py | 14 -------------- 13 files changed, 10 insertions(+), 64 deletions(-) diff --git a/.azure-pipelines/windows-layout-steps.yml b/.azure-pipelines/windows-layout-steps.yml index afd897817904947..e15729fac3443db 100644 --- a/.azure-pipelines/windows-layout-steps.yml +++ b/.azure-pipelines/windows-layout-steps.yml @@ -12,7 +12,7 @@ steps: displayName: Show layout info (${{ parameters.kind }}) - ${{ if eq(parameters.fulltest, 'true') }}: - - script: .\python.exe -m test -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 --junit-xml="$(Build.BinariesDirectory)\test-results-${{ parameters.kind }}.xml" --tempdir "$(Build.BinariesDirectory)\tmp-${{ parameters.kind }}-$(arch)" -i test_launcher + - script: .\python.exe -m test -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 --junit-xml="$(Build.BinariesDirectory)\test-results-${{ parameters.kind }}.xml" --tempdir "$(Build.BinariesDirectory)\tmp-${{ parameters.kind }}-$(arch)" workingDirectory: $(Build.BinariesDirectory)\layout-${{ parameters.kind }}-$(arch) displayName: ${{ parameters.kind }} Tests env: diff --git a/.gitattributes b/.gitattributes index 4d52f900b93179e..f00d82e6978c40b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,7 +19,7 @@ *.zip binary # Specific binary files -PC/classicAppCompat.* binary +# -- None right now -- # Text files that should not be subject to eol conversion [attr]noeol -text diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0cdf17838b5798f..fd929b0c468699b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -188,12 +188,8 @@ Tools/wasm/wasi @brettcannon @emmatyping @savannahostrowski PC/ @python/windows-team PCbuild/ @python/windows-team -# Windows installer packages -Tools/msi/ @python/windows-team -Tools/nuget/ @python/windows-team - -# Windows Launcher -PC/launcher.c @python/windows-team @vsajip +# Windows Venv launcher/redirector +PC/venvlauncher.c @python/windows-team @vsajip # ---------------------------------------------------------------------------- diff --git a/.gitignore b/.gitignore index 118eb5ee76e8051..2a8dd6a7c3eabd1 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,7 @@ gmon.out *.exe -# Ignore core dumps... but not Tools/msi/core/ or the like. +# Ignore core dumps... but not .../core/ subdirectories core !core/ @@ -158,7 +158,6 @@ Tools/unicode/data/ /coverage/ /externals/ /htmlcov/ -Tools/msi/obj Tools/ssl/amd64 Tools/ssl/win32 Tools/freeze/test/outdir diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h index 53ed570f99f118a..e0c5cfc02bbc359 100644 --- a/Include/internal/pycore_magic_number.h +++ b/Include/internal/pycore_magic_number.h @@ -310,9 +310,6 @@ Known values: You should always use the *upcoming* tag. For example, if 3.12a6 came out a week ago, I should put "Python 3.12a7" next to my new magic number. -Whenever PYC_MAGIC_NUMBER is changed, the ranges in the magic_values array in -PC/launcher.c must also be updated. - */ #define PYC_MAGIC_NUMBER 3704 diff --git a/InternalDocs/interpreter.md b/InternalDocs/interpreter.md index 7fc41a807dd5667..fa89996aa654291 100644 --- a/InternalDocs/interpreter.md +++ b/InternalDocs/interpreter.md @@ -307,9 +307,7 @@ With a new bytecode you must also change what is called the "magic number" for .pyc files: bump the value of the variable `MAGIC_NUMBER` in [`Lib/importlib/_bootstrap_external.py`](../Lib/importlib/_bootstrap_external.py). Changing this number will lead to all .pyc files with the old `MAGIC_NUMBER` -to be recompiled by the interpreter on import. Whenever `MAGIC_NUMBER` is -changed, the ranges in the `magic_values` array in -[`PC/launcher.c`](../PC/launcher.c) may also need to be updated. Changes to +to be recompiled by the interpreter on import. Changes to [`Lib/importlib/_bootstrap_external.py`](../Lib/importlib/_bootstrap_external.py) will take effect only after running `make regen-importlib`. diff --git a/Lib/test/test_tools/test_compute_changes.py b/Lib/test/test_tools/test_compute_changes.py index eb1ecd447a7ce0a..4c2b9966c4cd5b6 100644 --- a/Lib/test/test_tools/test_compute_changes.py +++ b/Lib/test/test_tools/test_compute_changes.py @@ -109,15 +109,6 @@ def test_unix(self): self.assertTrue(result.run_tests) self.assertFalse(result.run_windows_tests) - def test_msi(self): - for f in ( - ".github/workflows/reusable-windows-msi.yml", - "Tools/msi/build.bat", - ): - with self.subTest(f=f): - result = process_changed_files({Path(f)}) - self.assertTrue(result.run_windows_msi) - def test_all_run(self): for f in ( ".github/workflows/some-new-workflow.yml", diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt index 674926c8d4ab7a2..e9fa9680c368ad1 100644 --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -140,12 +140,6 @@ CPython in different ways: pythonw pythonw.exe, a variant of python.exe that doesn't open a Command Prompt window -pylauncher - py.exe, the Python Launcher for Windows, see - https://docs.python.org/3/using/windows.html#launcher -pywlauncher - pyw.exe, a variant of py.exe that doesn't open a Command Prompt - window _testembed _testembed.exe, a small program that embeds Python for testing purposes, used by test_capi.py @@ -156,8 +150,6 @@ _freeze_module _freeze_module.exe, used to regenerate frozen modules in Python after changes have been made to the corresponding source files (e.g. Lib\importlib\_bootstrap.py). -pyshellext - pyshellext.dll, the shell extension deployed with the launcher python3dll python3.dll, the PEP 384 Stable ABI dll (not installed on free-threaded builds) diff --git a/PCbuild/venvlauncher.vcxproj b/PCbuild/venvlauncher.vcxproj index 3f8a479fcf9858e..7afe923406b1d18 100644 --- a/PCbuild/venvlauncher.vcxproj +++ b/PCbuild/venvlauncher.vcxproj @@ -109,9 +109,6 @@ - - - diff --git a/PCbuild/venvlauncher.vcxproj.filters b/PCbuild/venvlauncher.vcxproj.filters index bc98687a6da0d3e..62839fd6b8e863f 100644 --- a/PCbuild/venvlauncher.vcxproj.filters +++ b/PCbuild/venvlauncher.vcxproj.filters @@ -9,12 +9,7 @@ - - Resource Files - - - - + Resource Files diff --git a/PCbuild/venvwlauncher.vcxproj.filters b/PCbuild/venvwlauncher.vcxproj.filters index 5193c38b12d53d5..babded59633f1f1 100644 --- a/PCbuild/venvwlauncher.vcxproj.filters +++ b/PCbuild/venvwlauncher.vcxproj.filters @@ -14,12 +14,7 @@ - - Resource Files - - - - + Resource Files diff --git a/README.rst b/README.rst index 48f86cdb86ed1d2..8505ec0a1e89dc7 100644 --- a/README.rst +++ b/README.rst @@ -76,8 +76,8 @@ to macOS framework and universal builds. Refer to `Mac/README.rst On Windows, see `PCbuild/readme.txt `_. -To build Windows installer, see `Tools/msi/README.txt -`_. +To build Windows packages, see `PC/layout/README.md +`_. If you wish, you can create a subdirectory and invoke configure from there. For example:: diff --git a/Tools/build/compute-changes.py b/Tools/build/compute-changes.py index 53d7b8fe32f89e6..4d491cc79ad0c8e 100644 --- a/Tools/build/compute-changes.py +++ b/Tools/build/compute-changes.py @@ -117,7 +117,6 @@ class Outputs: run_tests: bool = False run_ubuntu: bool = False run_wasi: bool = False - run_windows_msi: bool = False run_windows_tests: bool = False @@ -160,9 +159,6 @@ def compute_changes() -> None: if outputs.run_docs: print("Build documentation") - if outputs.run_windows_msi: - print("Build Windows MSI") - print(outputs) write_github_output(outputs) @@ -223,7 +219,6 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: run_ci_fuzz_stdlib = False run_docs = False run_windows_tests = False - run_windows_msi = False platforms_changed = set() has_platform_specific_change = True @@ -245,9 +240,6 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: run_tests = True run_windows_tests = True continue - if file.name == "reusable-windows-msi.yml": - run_windows_msi = True - continue if file.name == "reusable-macos.yml": run_tests = True platforms_changed.add("macos") @@ -288,10 +280,6 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: if doc_file: run_docs = True - # Check for changed MSI installer-related files - if file.parts[:2] == ("Tools", "msi"): - run_windows_msi = True - # Check which platform specific tests to run if run_tests: if not has_platform_specific_change or not platforms_changed: @@ -327,7 +315,6 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: run_tests=run_tests, run_ubuntu=run_ubuntu, run_wasi=run_wasi, - run_windows_msi=run_windows_msi, run_windows_tests=run_windows_tests, ) @@ -342,7 +329,6 @@ def process_target_branch(outputs: Outputs, git_branch: str) -> Outputs: if os.environ.get("GITHUB_EVENT_NAME", "").lower() == "workflow_dispatch": outputs.run_docs = True - outputs.run_windows_msi = True return outputs