From 253209149389e6793a052034e1f2d97691086f18 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Thu, 6 Dec 2018 21:09:53 -0800 Subject: [PATCH] [3.7] bpo-34977: Add Windows App Store package (GH-10245) --- .azure-pipelines/windows-appx-test.yml | 65 ++ .gitattributes | 1 + Lib/test/test_pathlib.py | 2 +- Lib/test/test_venv.py | 1 + Lib/venv/__init__.py | 69 +- .../2018-10-30-13-39-17.bpo-34977.0l7_QV.rst | 1 + PC/classicAppCompat.can.xml | 1 + PC/classicAppCompat.cat | Bin 0 -> 10984 bytes PC/classicAppCompat.sccd | 28 + PC/getpathp.c | 8 +- PC/icons/pythonwx150.png | Bin 0 -> 8187 bytes PC/icons/pythonwx44.png | Bin 0 -> 2232 bytes PC/icons/pythonx150.png | Bin 0 -> 8271 bytes PC/icons/pythonx44.png | Bin 0 -> 2178 bytes PC/icons/pythonx50.png | Bin 0 -> 2190 bytes PC/launcher.c | 221 ++++++- PC/layout/__init__.py | 0 PC/layout/__main__.py | 14 + PC/layout/main.py | 612 ++++++++++++++++++ PC/layout/support/__init__.py | 0 PC/layout/support/appxmanifest.py | 487 ++++++++++++++ PC/layout/support/catalog.py | 44 ++ PC/layout/support/constants.py | 28 + .../distutils.command.bdist_wininst.py | 25 + PC/layout/support/filesets.py | 100 +++ PC/layout/support/logging.py | 93 +++ PC/layout/support/options.py | 122 ++++ PC/layout/support/pip.py | 79 +++ PC/layout/support/props.py | 110 ++++ .../nuget => PC/layout/support}/python.props | 0 PC/pylauncher.rc | 6 + PC/python_uwp.cpp | 226 +++++++ PC/store_info.txt | 146 +++++ PCbuild/_tkinter.vcxproj | 6 + PCbuild/find_msbuild.bat | 10 + PCbuild/pcbuild.proj | 3 + PCbuild/pcbuild.sln | 72 +++ PCbuild/python.props | 3 +- PCbuild/python_uwp.vcxproj | 86 +++ PCbuild/pythoncore.vcxproj | 15 + PCbuild/pythonw_uwp.vcxproj | 86 +++ PCbuild/venvlauncher.vcxproj | 85 +++ PCbuild/venvwlauncher.vcxproj | 85 +++ Tools/msi/buildrelease.bat | 13 +- Tools/msi/make_appx.ps1 | 71 ++ Tools/msi/make_cat.ps1 | 34 + Tools/msi/make_zip.proj | 9 +- Tools/msi/make_zip.py | 250 ------- Tools/msi/sdktools.psm1 | 43 ++ Tools/msi/sign_build.ps1 | 34 + Tools/nuget/make_pkg.proj | 38 +- 51 files changed, 3094 insertions(+), 338 deletions(-) create mode 100644 .azure-pipelines/windows-appx-test.yml create mode 100644 Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst create mode 100644 PC/classicAppCompat.can.xml create mode 100644 PC/classicAppCompat.cat create mode 100644 PC/classicAppCompat.sccd create mode 100644 PC/icons/pythonwx150.png create mode 100644 PC/icons/pythonwx44.png create mode 100644 PC/icons/pythonx150.png create mode 100644 PC/icons/pythonx44.png create mode 100644 PC/icons/pythonx50.png create mode 100644 PC/layout/__init__.py create mode 100644 PC/layout/__main__.py create mode 100644 PC/layout/main.py create mode 100644 PC/layout/support/__init__.py create mode 100644 PC/layout/support/appxmanifest.py create mode 100644 PC/layout/support/catalog.py create mode 100644 PC/layout/support/constants.py create mode 100644 PC/layout/support/distutils.command.bdist_wininst.py create mode 100644 PC/layout/support/filesets.py create mode 100644 PC/layout/support/logging.py create mode 100644 PC/layout/support/options.py create mode 100644 PC/layout/support/pip.py create mode 100644 PC/layout/support/props.py rename {Tools/nuget => PC/layout/support}/python.props (100%) create mode 100644 PC/python_uwp.cpp create mode 100644 PC/store_info.txt create mode 100644 PCbuild/python_uwp.vcxproj create mode 100644 PCbuild/pythonw_uwp.vcxproj create mode 100644 PCbuild/venvlauncher.vcxproj create mode 100644 PCbuild/venvwlauncher.vcxproj create mode 100644 Tools/msi/make_appx.ps1 create mode 100644 Tools/msi/make_cat.ps1 delete mode 100644 Tools/msi/make_zip.py create mode 100644 Tools/msi/sdktools.psm1 create mode 100644 Tools/msi/sign_build.ps1 diff --git a/.azure-pipelines/windows-appx-test.yml b/.azure-pipelines/windows-appx-test.yml new file mode 100644 index 000000000000..9840c0a1221f --- /dev/null +++ b/.azure-pipelines/windows-appx-test.yml @@ -0,0 +1,65 @@ +jobs: +- job: Prebuild + displayName: Pre-build checks + + pool: + vmImage: ubuntu-16.04 + + steps: + - template: ./prebuild-checks.yml + + +- job: Windows_Appx_Tests + displayName: Windows Appx Tests + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['tests.run'], 'true')) + + pool: + vmImage: vs2017-win2016 + + strategy: + matrix: + win64: + arch: amd64 + buildOpt: '-p x64' + testRunTitle: '$(Build.SourceBranchName)-win64-appx' + testRunPlatform: win64 + maxParallel: 2 + + steps: + - checkout: self + clean: true + fetchDepth: 5 + + - powershell: | + # Relocate build outputs outside of source directory to make cleaning faster + Write-Host '##vso[task.setvariable variable=Py_IntDir]$(Build.BinariesDirectory)\obj' + # UNDONE: Do not build to a different directory because of broken tests + Write-Host '##vso[task.setvariable variable=Py_OutDir]$(Build.SourcesDirectory)\PCbuild' + Write-Host '##vso[task.setvariable variable=EXTERNAL_DIR]$(Build.BinariesDirectory)\externals' + displayName: Update build locations + + - script: PCbuild\build.bat -e $(buildOpt) + displayName: 'Build CPython' + + - script: python.bat PC\layout -vv -s "$(Build.SourcesDirectory)" -b "$(Py_OutDir)\$(arch)" -t "$(Py_IntDir)\layout-tmp-$(arch)" --copy "$(Py_IntDir)\layout-$(arch)" --precompile --preset-appx --include-tests + displayName: 'Create APPX layout' + + - script: .\python.exe -m test.pythoninfo + workingDirectory: $(Py_IntDir)\layout-$(arch) + displayName: 'Display build info' + + - script: .\python.exe -m test -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 --junit-xml="$(Build.BinariesDirectory)\test-results.xml" --tempdir "$(Py_IntDir)\tmp-$(arch)" + workingDirectory: $(Py_IntDir)\layout-$(arch) + displayName: 'Tests' + env: + PREFIX: $(Py_IntDir)\layout-$(arch) + + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + inputs: + testResultsFiles: '$(Build.BinariesDirectory)\test-results.xml' + mergeTestResults: true + testRunTitle: $(testRunTitle) + platform: $(testRunPlatform) + condition: succeededOrFailed() diff --git a/.gitattributes b/.gitattributes index 4a487c3c2a14..16237bb2b3ac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,6 +19,7 @@ # Specific binary files Lib/test/sndhdrdata/sndhdr.* binary +PC/classicAppCompat.* binary # Text files that should not be subject to eol conversion Lib/test/cjkencodings/* -text diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index e436db995ce4..056507ef6fe8 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1519,7 +1519,7 @@ class _BasePathTest(object): # resolves to 'dirB/..' first before resolving to parent of dirB. self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) # Now create absolute symlinks - d = support._longpath(tempfile.mkdtemp(suffix='-dirD')) + d = support._longpath(tempfile.mkdtemp(suffix='-dirD', dir=os.getcwd())) self.addCleanup(support.rmtree, d) os.symlink(os.path.join(d), join('dirA', 'linkX')) os.symlink(join('dirB'), os.path.join(d, 'linkY')) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 461fe7afd213..22a3b78852f8 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -243,6 +243,7 @@ class BasicTest(BaseTest): self.assertIn('include-system-site-packages = %s\n' % s, data) @unittest.skipUnless(can_symlink(), 'Needs symlinks') + @unittest.skipIf(os.name == 'nt', 'Symlinks are never used on Windows') def test_symlinking(self): """ Test symlinking works as expected diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index 716129d13987..5438b0d4e508 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -9,6 +9,7 @@ import os import shutil import subprocess import sys +import sysconfig import types logger = logging.getLogger(__name__) @@ -63,10 +64,11 @@ class EnvBuilder: self.system_site_packages = False self.create_configuration(context) self.setup_python(context) + if not self.upgrade: + self.setup_scripts(context) if self.with_pip: self._setup_pip(context) if not self.upgrade: - self.setup_scripts(context) self.post_setup(context) if true_system_site_packages: # We had set it to False before, now @@ -157,14 +159,6 @@ class EnvBuilder: f.write('include-system-site-packages = %s\n' % incl) f.write('version = %d.%d.%d\n' % sys.version_info[:3]) - if os.name == 'nt': - def include_binary(self, f): - if f.endswith(('.pyd', '.dll')): - result = True - else: - result = f.startswith('python') and f.endswith('.exe') - return result - def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): """ Try symlinking a file, and if that fails, fall back to copying. @@ -194,9 +188,9 @@ class EnvBuilder: binpath = context.bin_path path = context.env_exe copier = self.symlink_or_copy - copier(context.executable, path) dirname = context.python_dir if os.name != 'nt': + copier(context.executable, path) if not os.path.islink(path): os.chmod(path, 0o755) for suffix in ('python', 'python3'): @@ -208,32 +202,33 @@ class EnvBuilder: if not os.path.islink(path): os.chmod(path, 0o755) else: - subdir = 'DLLs' - include = self.include_binary - files = [f for f in os.listdir(dirname) if include(f)] - for f in files: - src = os.path.join(dirname, f) - dst = os.path.join(binpath, f) - if dst != context.env_exe: # already done, above - copier(src, dst) - dirname = os.path.join(dirname, subdir) - if os.path.isdir(dirname): - files = [f for f in os.listdir(dirname) if include(f)] - for f in files: - src = os.path.join(dirname, f) - dst = os.path.join(binpath, f) - copier(src, dst) - # copy init.tcl over - for root, dirs, files in os.walk(context.python_dir): - if 'init.tcl' in files: - tcldir = os.path.basename(root) - tcldir = os.path.join(context.env_dir, 'Lib', tcldir) - if not os.path.exists(tcldir): - os.makedirs(tcldir) - src = os.path.join(root, 'init.tcl') - dst = os.path.join(tcldir, 'init.tcl') - shutil.copyfile(src, dst) - break + # For normal cases, the venvlauncher will be copied from + # our scripts folder. For builds, we need to copy it + # manually. + if sysconfig.is_python_build(True): + suffix = '.exe' + if context.python_exe.lower().endswith('_d.exe'): + suffix = '_d.exe' + + src = os.path.join(dirname, "venvlauncher" + suffix) + dst = os.path.join(binpath, context.python_exe) + copier(src, dst) + + src = os.path.join(dirname, "venvwlauncher" + suffix) + dst = os.path.join(binpath, "pythonw" + suffix) + copier(src, dst) + + # copy init.tcl over + for root, dirs, files in os.walk(context.python_dir): + if 'init.tcl' in files: + tcldir = os.path.basename(root) + tcldir = os.path.join(context.env_dir, 'Lib', tcldir) + if not os.path.exists(tcldir): + os.makedirs(tcldir) + src = os.path.join(root, 'init.tcl') + dst = os.path.join(tcldir, 'init.tcl') + shutil.copyfile(src, dst) + break def _setup_pip(self, context): """Installs or upgrades pip in a virtual environment""" @@ -320,7 +315,7 @@ class EnvBuilder: dstfile = os.path.join(dstdir, f) with open(srcfile, 'rb') as f: data = f.read() - if not srcfile.endswith('.exe'): + if not srcfile.endswith(('.exe', '.pdb')): try: data = data.decode('utf-8') data = self.replace_variables(data, context) diff --git a/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst b/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst new file mode 100644 index 000000000000..8e1a4ba84880 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst @@ -0,0 +1 @@ +Adds support for building a Windows App Store package diff --git a/PC/classicAppCompat.can.xml b/PC/classicAppCompat.can.xml new file mode 100644 index 000000000000..f00475c8da31 --- /dev/null +++ b/PC/classicAppCompat.can.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/PC/classicAppCompat.cat b/PC/classicAppCompat.cat new file mode 100644 index 0000000000000000000000000000000000000000..3d213596accffa255e1443126228f7ab1bb2899c GIT binary patch literal 10984 zc-q~X1yoe+*7&FBZX^c~K@d1Ibf|Q9hYCY?OG(XuC@7$Ul*AAcf*=NUyWgK6buJ8k4GR!D&i2+Obda_@pKks1E>`k)g^U3<@ZhpjZa#T zzNF4Zx%{T1$|NX0^5~#hyA|9P_o@QUqq1sB!qAi1R)*F#0W(P(CiAtbfjh&KW7(D zCx0(b6g9Y1U?CA5k)eaVhnJ^4iUVNVEg_|2&~dTz_44y_^hYXr`FeZ#V*Fje9uxrC zZao5!#}sfl2&2 zJ3RGCERxK<$KEx~Nz^5Dkmtj+T`=Qj%P(c;Q$P6V+)I5Usb%!JlPGm4XfpSsVo?wI zX?__dOZfY+;HISnyXLg)Tvu2F|5NQWsEmeI$E#uK5}ye5@D_ycP_}jA>t#5CD)Mwr zb$BFGeH-dw7(+{E6(uSl{kh#SO0z%}sZU$i{Ny+ZY{WV~t$VG@O3KzOq*p8QysS!^ ztGh6AelRNaefQ-X&Z1|1Tztumv!a_r5V(|8n!_+S6avl1K_7!F@(5rC*OHwV3jK

c)Y4;e2 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + MIIq5AYJKoZIhvcNAQcCoIIq1TCCKtECAQExDzANBglghkgBZQMEAgEFADCCARAGCSsGAQQBgjcKAaCCAQEwgf4wDAYKKwYBBAGCNwwBAQQQaM+L42jwBUGvBczrtolMmhcNMTgxMTMwMDA1OTAzWjAOBgorBgEEAYI3DAEDBQAwgbwwKgQUWKcU3R38DGPlKK33XGIwKtVL1r4xEjAQBgorBgEEAYI3DAIDMQKCADCBjQQg3K+KBOQX7HfxjRNZC9cx8gIPkEhPRO1nJFRdWQrVEJ4xaTAQBgorBgEEAYI3DAIDMQKCADBVBgorBgEEAYI3AgEEMUcwRTAQBgorBgEEAYI3AgEZogKAADAxMA0GCWCGSAFlAwQCAQUABCDcr4oE5Bfsd/GNE1kL1zHyAg+QSE9E7WckVF1ZCtUQnqCCFFAwggZSMIIEOqADAgECAhMzAAMu49KhfNamygpWAAIAAy7jMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQ0wCwYDVQQLEwRNT1BSMScwJQYDVQQDEx5NaWNyb3NvZnQgTWFya2V0cGxhY2UgQ0EgRyAwMTMwHhcNMTgxMTMwMDA1NTA1WhcNMTgxMjAzMDA1NTA1WjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwpcimfAx3HEpba1GLL/gDaRVddHE5PXTRmwlgaz8kt6/rq5rlrPFnCnbIc5818v0xJIznastbmrq26xyCEHyMLBKnyneTKE36I7+TGjcY0D7ow+o2vY7LDKMCTGlh31fx1Tvrl+5xTbWX5jdLU/3MB5faeOGh+0Knzwx1KDoXWgPtfXnD8I5jxJieoWoCwCjKTJgBOklLy9nbOalxf0h+xQRy2p5fj+PxAwQPgHWft36AF7/IMbt9FcXMtg4xdpnTYz4OV3dFOPz4m3M8HwVgNMv89W/1Ozc7uOyZt0Ij1baT6r2L3IjYg5ftzpGqaDOFcWlyDFSdhMR6BIKW8xEpAgMBAAGjggHCMIIBvjAYBgNVHSUBAf8EDjAMBgorBgEEAYI3TBwBMB0GA1UdDgQWBBRdpGYiCytx83FYzPSl+o97YzpxGzAPBgNVHREECDAGggRNT1BSMB8GA1UdIwQYMBaAFEnYB1RFhpclHtZZcRLDcpt0OE3oMGIGA1UdHwRbMFkwV6BVoFOGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyME1hcmtldHBsYWNlJTIwQ0ElMjBHJTIwMDEzKDIpLmNybDBvBggrBgEFBQcBAQRjMGEwXwYIKwYBBQUHMAKGU2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwTWFya2V0cGxhY2UlMjBDQSUyMEclMjAwMTMoMikuY3J0MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgXgMDwGCSsGAQQBgjcVBwQvMC0GJSsGAQQBgjcVCIOS9kTqrxCDkY0wgqzgLIKinDE0g+6NOIaE7wACAWQCARYwIAYJKwYBBAGCNxUKAQH/BBAwDjAMBgorBgEEAYI3TBwBMA0GCSqGSIb3DQEBCwUAA4ICAQB3Dk3rXH52CDq/z1fwqn9xI5WGjGmu6oAE4HSc3sNdFrSVMMGm4gTlYGWSZ0wJUUf16mVr/rdXhxuR3MZn+m4Bhdl8KQqYjYbIvCUVj0o9nZ+yT6foeY8bKnB+K5h6rol+mjDj5IfcutC4x2Kx5RrtDtRTSoKA63iZ74DYngPpBGBBgaS2c/QzgqPRAMMRqy2KBDP0miCnpR3F4YlzHGyOZwyHhESjYd9kwF47+msuHS04JZpnGHIvBppKN9XQzH3WezNnnX3lz4AyAUMsMFuARqEnacUhrAHL9n5zMv9CzxDYN1r1/aDh/788RuGuZM+E3NtmbxJJ7j6T5/VtXNBRgKtIq8d2+11j6qvKLigOTxSC25/A70BZBEvllLFnvc1vA2LrC9drwt1KpSmWie1nvpilw7o+gHMOG9utUxGha2VuVizuVNGCywTRRjvmGS1QqTfaun1URVrLfnDINXuTgN1Vwp0J5IGpJ3D8yj01NDQ/RworE+3W/R531NBYova9QRhU/igEw/Aa/q8wjZ4Pzxr9oBIo0Ta3Tv6qIggaWXw0U9+F0J7SCqIhn0d0ATO+E1Qs/SxZIAICLwmqzoLYUAh8q153esBs4uesueqgt5ueyHK8V3WjMS4wxEyVN5ZMET3hFtEshsZC31tLDdjq750U4SgQVmoYSm3F3ZOKQDCCBtcwggS/oAMCAQICCmESRKIAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTExMDMyODIxMDkzOVoXDTMxMDMyODIxMTkzOVowfTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEnMCUGA1UEAxMeTWljcm9zb2Z0IE1hcmtldFBsYWNlIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAubUaSwGYVsE3MAnPfvmozUhAB3qxBABgJRW1vDp4+tVinXxD32f7k1K89JQ6zDOgS/iDgULC+yFK1K/1Qjac/0M7P6c8v5LSjnWGlERLa/qY32j46S7SLQcit3g2jgoTTO03eUG+9yHZUTGV/FJdRYB8uXhrznJBa+Y+yGwiQKF+m6XFeBH/KORoKFx+dmMoy9EWJ/m/o9IiUj2kzm9C691+vZ/I2w0Bj93W9SPPkV2PCNHlzgfIAoeajWpHmi38Wi3xZHonkzAVBHxPsCBppOoNsWvmAfUM7eBthkSPvFruekyDCPNEYhfGqgqtqLkoBebXLZCOVybF7wTQaLvse60//3P003icRcCoQYgY4NAqrF7j80o5U7DkeXxcB0xvengsaKgiAaV1DKkRbpe98wCqr1AASvm5rAJUYMU+mXmOieV2EelY2jGrenWe9FQpNXYV1NoWBh0WKoFxttoWYAnF705bIWtSZsz08ZfK6WLX4GXNLcPBlgCzfTm1sdKYASWdBbH2haaNhPapFhQQBJHKwnVW2iXErImhuPi45W3MVTZ5D9ASshZx69cLYY6xAdIa+89Kf/uRrsGOVZfahDuDw+NI183iAyzC8z/QRt2P32LYxP0xrCdqVh+DJo2i4NoE8Uk1usCdbVRuBMBQl/AwpOTq7IMvHGElf65CqzUCAwEAAaOCAUswggFHMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBQPU8s/FmEl/mCJHdO5fOiQrbOU0TAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCjuZmM8ZVNDgp9wHsL4RY8KJ8nLinvxFTphNGCrxaLknkYG5pmMhVlX+UB/tSiW8W13W60nggz9u5xwMx7v/1t/Tgm6g2brVyOKI5A7u6/2SIJwkJKFw953K0YIKVT28w9zl8dSJnmRnyR0G86ncWbF6CLQ6A6lBQ9o2mTGVqDr4m35WKAnc6YxUUM1y74mbzFFZr63VHsCcOp3pXWnUqAY1rb6Q6NX1b3clncKqLFm0EjKHcQ56grTbwuuB7pMdh/IFCJR01MQzQbDtpEisbOeZUi43YVAAHKqI1EO9bRwg3frCjwAbml9MmI4utMW94gWFgvrMxIX+n42RBDIjf3Ot3jkT6gt3XeTTmO9bptgblZimhERdkFRUFpVtkocJeLoGuuzP93uH/Yp032wzRH+XmMgujfZv+vnfllJqxdowoQLx55FxLLeTeYfwi/xMSjZO2gNven3U/3KeSCd1kUOFS3AOrwZ0UNOXJeW5JQC6Vfd1BavFZ6FAta1fMLu3WFvNB+FqeHUaU3ya7rmtxJnzk29DeSqXgGNmVSywBS4NajI5jJIKAA6UhNJlsg8CHYwUOKf5ej8OoQCkbadUxXygAfxCfW2YBbujtI+PoyejRFxWUjYFWO5LeTI62UMyqfOEiqugoYjNxmQZla2s4YHVuqIC34R85FQlg9pKQBsDCCBxswggUDoAMCAQICEzMAAABCs21EHGjyqKYAAAAAAEIwDQYJKoZIhvcNAQELBQAwfTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEnMCUGA1UEAxMeTWljcm9zb2Z0IE1hcmtldFBsYWNlIFBDQSAyMDExMB4XDTE4MDQyMDE2NDI0NFoXDTIxMDQyMDE2NDI0NFowgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDTALBgNVBAsTBE1PUFIxJzAlBgNVBAMTHk1pY3Jvc29mdCBNYXJrZXRwbGFjZSBDQSBHIDAxMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOZ2KM9Pq1YCOiqWOivmHjUtkMgznTMP/Mr2YfzZeIIJySg1F4WxFZc4jagGHHNof9NRT+GGnktWsXkZuH1DzQEG4Ps1ln8+4vhbDglqu5ymDnd6RmsyoD+8xfc8bBIvE5o6R+ES4/GVD5TqNsOrWbwETaIZVbmTulJLoTS1WSsSjowmbc+sHqZiY8BNJNThUEmXSjuHqkQKKshuiFWYEqOTitp71mBLyH1wN7/jThRzGpolOeFusRNJdb8sEqvNzEN9Qh+Kp6ndzrnjE+t8ixXW3lShyyOOZqQMwsQn9q9T0v7Q69GuojBTFBOHKwigcCHr4xahuN+ZYMk0xGg+sm3Uj7I9mrWTSTiIRMZNIWq3sFg4+rFg48NYfRlXUpONmL7vXq6v1pIU99d2MXQ6uUrnUr1/n5ZiHGCeFcvWwqO8BYHdcTlrSOkayfFp7W9oCk9QO4Xy0h9cQRedRo2kvdTHxIuJS70Hdv6oePPF2ZFaLucUzzwsR4/XMAVKY8Vsm950omsSSOImsMtzavUdQM+wZFxvHTRqVDkF3quPdME0bCZOWB4hQJmd+o2clw+1mpwPu0/M92nA9FJg7MGPxkFaYW7g26jSqUJZ9AcX+Xa5TSIeqMZt3cRVjMTx0T/v73Sv8TpalqIQ5Fde1+hFK07sOAm3TwgzvlVJnbYgp0/rAgMBAAGjggGCMIIBfjASBgkrBgEEAYI3FQEEBQIDAgACMCMGCSsGAQQBgjcVAgQWBBSbJnDhuc3nQXuKuACsPflEbwjbozAdBgNVHQ4EFgQUSdgHVEWGlyUe1llxEsNym3Q4TegwEQYDVR0gBAowCDAGBgRVHSAAMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB8GA1UdIwQYMBaAFA9Tyz8WYSX+YIkd07l86JCts5TRMFcGA1UdHwRQME4wTKBKoEiGRmh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY01hclBDQTIwMTFfMjAxMS0wMy0yOC5jcmwwWwYIKwYBBQUHAQEETzBNMEsGCCsGAQUFBzAChj9odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY01hclBDQTIwMTFfMjAxMS0wMy0yOC5jcnQwDQYJKoZIhvcNAQELBQADggIBAIa2oa6kvuIHCNfz7anlL0W9tOCt8gQNkxOGRK3yliQIelNQahDJojyEFlHQ2BcHL5oZit3WeSDoYddhojx6YzJIWwfGwtVqgc0JFDKJJ2ZXRYMRsuy01Hn25xob+zRMS6VmV1axQn6uwOSMcgYmzoroh6edjPKu7qXcpt6LmhF2qFvLySA7wBCwfI/rR5/PX6I7a07Av7PpbY6/+2ujd8m1H3hwMrb4Hq3z6gcq62zJ3nDXUbC0Bp6Jt2kV9f0rEFpDK9oxE2qrGBUf8c3O2XirHOgAjRyWjWWtVms+MP8qBIA1NSLrBmToEWVP3sEkQZWMkoZWo4rYEJZpX7UIgdDc9zYNakgTCJqPhqn8AE1sgSSnpqAdMkkP41rTlFCv2ig2QVzDerjGfEv+uPDnlAT0kucbBJxHHvUC4aqUxaTSa0sy2bZ6NWFx8/u0gW8JahzxYvvvZL8SfwaA9P4ETb8pH1jw+6N/LfM2zJrNKhf5hjKa0VDOXUpkYq60OqVVnWJ6oJaSIWNkZKfzPnl/UHA8Bh4qfVrhc9H5PExPhhB9WVTsjf4r+OOVuolJldThcWQqljiPjk5rultr63G5xLyFpxNi4BCrcNQBJFB5wKgOWOyjQTVWTmh2ESaeqZ2aWBjftFHlxJ/qYc7WOGJV0+cHGkB/dvFxmKnv6tuWexiMMYIVUTCCFU0CAQEwgaQwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDTALBgNVBAsTBE1PUFIxJzAlBgNVBAMTHk1pY3Jvc29mdCBNYXJrZXRwbGFjZSBDQSBHIDAxMwITMwADLuPSoXzWpsoKVgACAAMu4zANBglghkgBZQMEAgEFAKCBlTAYBgkqhkiG9w0BCQMxCwYJKwYBBAGCNwoBMC8GCSqGSIb3DQEJBDEiBCAS0d3bw2YOODvKFr0S4e3BDnaDcZXUKeBO77yvkWzVojBIBgorBgEEAYI3AgEMMTowOKAegBwATQBpAGMAcgBvAHMAbwBmAHQAIABDAG8AcgBwoRaAFGh0dHA6Ly9NaWNyb3NvZnQuY29tMA0GCSqGSIb3DQEBAQUABIIBABoap3Y+2k+zFz2cCmkc8xxHnpIygLsUSRMXeXdjPVcYx3o5cPLIixnL6p8+LIrlIagPg23mzTEmnjZaO4aaexk+3XojlHj22w/bEigEDnKyWt5bHeS0UNHJbxEFYRfd84IP1+mSH4c4+GuU9p3LsAMh6wN03MYrGmczUOnlP6YlxHNQbQxnV0sl14yOE5ni9oT4y+l+SllvbV3/Jhwpov68aoP/2MazqxR4QyGfSxhCPJ4UuDHU7IrpnTxGBTL1/oUU8ED0FxyDoH/Sc5OhTLInFqbZaVzm5Mpr12wYUBL4nE5h0Kf6BCKdgM8a+Ti3wMUsBoC79ff3jE9U/xwSneOhghLlMIIS4QYKKwYBBAGCNwMDATGCEtEwghLNBgkqhkiG9w0BBwKgghK+MIISugIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBUQYLKoZIhvcNAQkQAQSgggFABIIBPDCCATgCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQghPy22lwuCYESw8jYhb4F9ZDPJ1LPgSSZgJDkyXYzVt4CBlv98KtAoBgTMjAxODExMzAwMTA1MTkuMTM4WjAEgAIB9KCB0KSBzTCByjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046RDA4Mi00QkZELUVFQkExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIHNlcnZpY2Wggg48MIIE8TCCA9mgAwIBAgITMwAAAOIYOHtm6erB2AAAAAAA4jANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0xODA4MjMyMDI3MDNaFw0xOTExMjMyMDI3MDNaMIHKMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpEMDgyLTRCRkQtRUVCQTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgc2VydmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKirA72FF3NCLW5mfLO/D0EZ5Ycs00oiMSissXLB6WF9GNdP78QzFwAypxW/+qZSczqaHbDH8hlbxkzf3DiYgAdpQjnGkLujwKtWSaP29/lVf7jFqHy9v6eH+LdOi0LvtrPRW34MyCvpxZyOW4H1h3PkxCBL5Ra21sDqgcVL1me0osw8QTURXmI4LyeLdTH3CcI2AgNDXTjsFBf3QsO+JYyAOYWrTcLnywVN6DrigmgrDJk5w+wR4VrHfl2T9PRZbZ+UDt13wwyB9d6IURuzV8lHsAVfF8t9S0aGVPmkQ3c2waOhHpsp6VEM+T5D2Ph8xJX1r82z67WRlmGcOP2NWC0CAwEAAaOCARswggEXMB0GA1UdDgQWBBSJPpD6BsP2p+crDJL232voEtLxezAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQARQHu7ISeBuJSHKuDRI04704cH0B7BYzeEIrD15awviMRcYIfIOHpvGzZOWQgP2Hm0Rr7kvTUu1VrSSaQ7i1gPWdhqMmw5WBnSS5bxeMhhx9UsASeE84vUu82NeZapGSjH38YAb4WT+TtiTkcoI59rA+CTCq108ttIxVfZcr3id76OETIH0HvhlnxOOWjwGy4ul6Za5RoTLG/oo2rrGmVi3FwrNWGezYLBODuEsjzG36lCRtBKC2ZAHfbOz5wtkUHbqh79mUKocjP4r3qxf5TN87yf6g1uTx+J8pdnAi5iHt+ZtangWqnVTE8PoIREWhBVlGFfQdkELUx2Or90aAqWMIIGcTCCBFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcNMjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEwRA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQedGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKxXf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4GkbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEAAaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0gAQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOhIW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS+7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlKkVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon/VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOiPPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCIIYdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7aKLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQcdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+NR4Iuto229Nfj950iEkSoYICzjCCAjcCAQEwgfihgdCkgc0wgcoxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkQwODItNEJGRC1FRUJBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBzZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQByQCUheEOevaI9Zc/3QGrkX42iC6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA36ppYDAiGA8yMDE4MTEyOTIxMzQyNFoYDzIwMTgxMTMwMjEzNDI0WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDfqmlgAgEAMAoCAQACAitfAgH/MAcCAQACAhGtMAoCBQDfq7rgAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAbAXXPR9wy4NA0892GGqetaZF+pNClpGcfEpSuHABaZ4Gzr1nY1nmrhexTtr/U6omHALRWzkQwthk0cy+mnEHXyOZGmoEEpgrLgK3AAP5NbK/XbtHQRyZJQyhZScFbOyQycoE8QQalSVOhWxk/bbBMQaQiYVMIexNd/T0KgaDDUMxggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAOIYOHtm6erB2AAAAAAA4jANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCCr9IiSbx6s8MLdxldRG49+4h6CbicW8hWXAicI3jNmhDCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIN8BpJSmQCGubWwVa4tW+aMveoHMX/nDnVN8fiDOMsrLMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAADiGDh7ZunqwdgAAAAAAOIwIgQgTkOfRvGEZNbr5/hgWclsL4/Q7SOZihE/U0lz2wEMIGcwDQYJKoZIhvcNAQELBQAEggEATlxnCfTzFfTMDvK085zlYPVCroKYW6gKFYnbAhNmrNzcxqALKmIYXpFU7B6HH/vYzkUfCyXpf5tsyEWu0oTySOjyAZ9+2vdaG8nEgjOp0L737lcitgusIjpWtta3Ik0b+mzffnvyjrgTSuKDDni3mxGfvJU77k1Ctempma4H2FJso6Bur0PRH99vIYDu4lHigOSLbeyjR5CiDciBwEVUSA0FxhoFNX1yfpxz3sukOvkaoTduREIjH5LxUjNI1ZTMK/ZkeETI8IPRpWVzAc8q7CujErHKo4sdKej/O2cfUTUHplFLVCGGExpJUCg5FH5jVUUFt75ad8503sdGplggVQ== diff --git a/PC/getpathp.c b/PC/getpathp.c index bc85b58abff1..4075463f2260 100644 --- a/PC/getpathp.c +++ b/PC/getpathp.c @@ -536,10 +536,16 @@ static _PyInitError get_program_full_path(const _PyCoreConfig *core_config, PyCalculatePath *calculate, _PyPathConfig *config) { + const wchar_t *pyvenv_launcher; wchar_t program_full_path[MAXPATHLEN+1]; memset(program_full_path, 0, sizeof(program_full_path)); - if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) { + /* The launcher may need to force the executable path to a + * different environment, so override it here. */ + pyvenv_launcher = _wgetenv(L"__PYVENV_LAUNCHER__"); + if (pyvenv_launcher && pyvenv_launcher[0]) { + wcscpy_s(program_full_path, MAXPATHLEN+1, pyvenv_launcher); + } else if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) { /* GetModuleFileName should never fail when passed NULL */ return _Py_INIT_ERR("Cannot determine program path"); } diff --git a/PC/icons/pythonwx150.png b/PC/icons/pythonwx150.png new file mode 100644 index 0000000000000000000000000000000000000000..4c3eb316739c7989cabaad145e278b17741afe92 GIT binary patch literal 8187 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 literal 0 Hc-jL100001 diff --git a/PC/icons/pythonwx44.png b/PC/icons/pythonwx44.png new file mode 100644 index 0000000000000000000000000000000000000000..e3b32a871f90a747f46d0732b5bdd0cfddebb448 GIT binary patch literal 2232 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 literal 0 Hc-jL100001 diff --git a/PC/icons/pythonx44.png b/PC/icons/pythonx44.png new file mode 100644 index 0000000000000000000000000000000000000000..3881daaef2335addf7df393d9bc1f7b8560ec8b0 GIT binary patch literal 2178 zc-jGi2z~d7P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2p362K~z{r?OEGb z)KwJbp@$y(1A6emht}#vvr-cg7*sT%2n(~c6e&>~@8%`%8q&>HSwxnKCJ2(ISmGr$ zCDcKKA}%rtINWr`YZyi%U;8`1%Ul>n8CjaG_FCTp=67cQzH`n#dw=^763`+eBjJhU zk3uGcU-4 zncg3InZ;tMH(qSO>qF~?BKL=6GCXxJo|&5kwA$Pw2fe)&Xq*bzpYvf*X+cLv$1&}% z1t$>*D-JdxG^0xMK-UB34FoZG>|ig32FxY$H;rMd_F%}O?HDlo8_@g?-gcr6kxO^V z1hNskG#4Y^*#bC{fU__9Up3{b{tcjJG;mS#?4IBq+rr{-e=A_%^}Jf`cUn?XQn=^D zx`H`4Vnl>mHp1UP!W``TAUZl4pYN~2jEz5|{Z>cd!L*r~MF^djgQuo#@E#m7G6IYk zBKE^Wuq@w=(D}J|dPXMtq*15!=CowIu4HwN571IU81I@2GMEzaj3jW4)iV+7R*XK z{Z6N|YsbECH;|c`iK~BH$N8(RD6PGSk{TC&IqSs0Ia~15^o@98>N+|3e*x{6P*tTT zEm|PQb*}3wfil7)mVAf73%03b+JELIJZb$}9Se0duI^0h>2(R?oY1>@NpcrMq=IHZ+bWj@ezR91UIhgLgR+rxZ$k zr+letO{$irw}6)Y?kv|8Xyojx!G{a>S6s{L{7tGO~^`)mTb2s zpyTG|W(<3`0G4I>rc>6rxaE0>S-e#-9sj|1z-3{~DnjvlCZ3MVZtK{H07}2z#b@1R zsdDI2|_!bj+D_oYMscM~+C)PU0c$xYby*x>+tBjIr zGMJ9jO~?M~9no;H#SAMrS85wXX<0W;rk*HW&hz4QgzC3g?{NJWR;h=T<3J{Z9uo%A zv57S=GB*f~OQhb(EL`%!OS$T?jJqj6qV%ucxuJ2kxj{HRQl@SJuI!cHN6~LDeOUJm zf0mdVgz|80>J7_*s~)i%jLB4clNzr4AP!f_&jR(Qd_F)Q@?S6(SDylWKQMKdLv`Bx zeL#98`g~g9UHA>_Z_Nm`Oi4Zb_)D`7Jf1M^@V^4Wg`CJur|CAPeV%KU0Dnp2et=xa z$*Uw8_CMhsK(`2c-bn;PXi})(m-Phh%L-eqR%B&mp|rFV<>lq7G8-r>D^s86<>etI zC8g7MyGPg(2{!A&f_*pJ0ZLntAF#40js%r7lEdIR$B~VF}5`}__GKETws$%qN5mRFE@$o?rzDv-? ze)f~PgrMS6_XeW+c;!Z-ls;5{B+8o(UQAF0(x|ZMqg#YM?;`^tW>pO8UG+FwdkHD) zkE`vcEcM-fMO5E@5~Zl-r6ScjFUb2&&d|hltIe%8&(9N~l8sxpZnn!jyo*OLE6jsJ z8~vV%7eCpIG z6jeJheDyv>)YyJSRJPxUQ*OTzr^fd4vdbW9;MD1JB4U?n%9wJS>X?$7I{w-L65gTH z4URBV)21VKZTs~&_1ylj`CE}u@GD$bTtM3yKd=j`MV=o5E~E;{2|7tp+?4U`4V09R zk%@dn!Yw-8;0W7nHWXGhtL-<^l(GF&zBmBN>jUSvVtxV<0eU~PV!BiBySU6)kyN*j_n4! z1GXF_$+n|>h<0;cJ5{|$2#?n3hJ;LLrl#5?=eAcjUPkJ+<4E~zFD9)m zKv6}5Z1Xbj!$XqnE$fARlf-JHG+p%z1d0k9GjwZ{heB8e6>?xBKapA&8 zoez(S%b^;{3L)FkrWm4H#?@#cCgyQxd5=s8KdMN#{w7=gw)^m?i)!sZIwrXN(#XsB z7Qy{*JY~hAgvc5}zMxWYQ(zbD5qv7opVx=p#zI2=0hlMklUF*9)Bpeg07*qoM6N<$ Ef?z!-4*&oF literal 0 Hc-jL100001 diff --git a/PC/icons/pythonx50.png b/PC/icons/pythonx50.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc3aecd0242b9670e3633faeb10201140793f83 GIT binary patch literal 2190 zc-jGu2yyp`P)N2bZe?^J zG%hhNG!sB*!~g&Y3`s;mRA@u(n0ru^*BQp0PCAox(wVl?=}iCYKk0NPooO3yHN=3o zAdo0gqGFTQAhgw0yo4AV1Oyh4 z+tcTq?<}(HGU9q+|KOc@X8Gj%&iDTIec$IC95)tY@g!nH?q$m4YMK;dGbmP^ZX$(nkRt4iBG z0s{k0{{H@$K7G1NYw_a69U@niwtb9&JYkK2xSGa5Tul!Q;y&LU6JjQeNRzirwt3m( z)B}Tfyyk%^>!*xJZaO!cmpx96&B&vYudgqnqN0$Mm4(8>LR3~(qN=KDK;pWns0i8F z+1RsZ4}5%l9Ii8}Akop$=;-J`XLm31D$Pj!rV0nMZX!}&fUPNc*qw152R<*v$uG;0 zU(tZxzJ8d^W`u-<*sV2cAmQQR5OMPBD$L(^5wmw^!876uyt3mQW^Vf&Gs5+l_ODOj zu{jMdZ%o0|4X5C@`wVWF8qnR{4V_MBv)+Rb`&L=jZ1<$fJ^oUHT%-jyR8t zhB|qjRaoNyA{lN9;?M6SV$zzUSaUc_-X9PUFtFaJg6v2yfalIkct>PnW&D@$+{Ts) z%OC{jLO^UF#CwEz*9!45Cay}rszc}H^DYCjJ?#c&Z9j{dTR&qZ#BdWr4Yw8I#dnh= z#AF3AAvhkv2Q%gKPaGsTI9Toj_4W1W?d_FGh_H0^pxJU4&Fw;6Hae13q(Ovy!nq*C zY7OFr6>(Vk;W@Q}7Q)52cK@YIml}EbGnKk+3Ql-A1hAX~)si+lMpxHCUc- z6?67v-`8f{-qRdPgE)pstB*>E@hgttPs?MmBIYc=g)6}AcQn<}9*7jEX$9Nk)kvS4VIFx{xB_Ossve{%Cn-vZ< zaTOs}9>EI=!Um*n4e)(1A#^~^KY{OpfR+pa(nI}9A!y<3jD&@S$^Kn;B99OkGzdDB z81A7E(xHUS+BuYf;9LxX5Lz4i1<-zx5Gx6>oB;m-s+R({mhhTYVGtxhA!y+&NOE#A zx;YzSCT8zCFKyPwp+;b{8bq9&i@_j?*#$uJC#=FUtqRovg!mg!^(Ij1&-Q8)Acqx# z7S4j?=jWrmzLgN1i=hyMY*rg?$2Kb*YAA&N&J=Wa(VS}8Qq_S3c$)x!1x$Y-#6qCL z59lZmL!YM*v~U*0WHQMUt>;ebaDS#8D#M{e`Glup(e6*Na927O>`a5-jugz_b_(;h zCQB_Mz=4db=T1UWEvv9twUVkr*=sFtbhiKvo9!W_t*X6YxPg{cm`8}Ygm{$@ zUWAwh7}KLQJD((6zcmYn@aGS~v?58X77=cBEcY4rObzi8%(g04;O?V$cpPtMGUGfvy-d z(6W_86<#3(lQ((*#Z!6z2Kios1Ste9oCWdr_C{M<8;Wk3Sq*!eP0T4K$T3v~2Rror z2D)T60aPm)+zIg#<;E&s6A)e0FBF0n0)*;ax^!vBgG#U0%cE`_PqNlCl=UE(m_rw8 z`Plh^mQ`RY8CV4YAXFP+t;Qjh{=FbXWKd90f-}c?h8GfK^HuCo;2t(5_bCwztDORxI=s?nggX@cf;%VyF>eY`Y_^zk; zlIN)Bh2-1K{HC26=n(lh*O{Z%`W-)is$TVELuS&*RvF1aSFq)pd2}~%mc%UF19@I8 zH+sP&x?pDik7S@*zmkdLZnXwd&9-B3jo+%}MlYB|0ouP-RTyT6w%Tgl0g*Oa@w5ig zlp-L6#Xh5!8@*r>>=Q;K2j0;RZFpHr+A0J8&03BA9sv@smKxn4!jG5?zhZ`k$CdAB zw>LS|)`7Nx;I)Z1-Eu+R&U4gtemMT$lFfKR&CLaIFxDF-dc)d0k_(d(`^+d Q0000007*qoM6N<$g6^^h%K!iX literal 0 Hc-jL100001 diff --git a/PC/launcher.c b/PC/launcher.c index 7d666aae4ab1..0242f2639119 100644 --- a/PC/launcher.c +++ b/PC/launcher.c @@ -28,7 +28,7 @@ #define RC_NO_PYTHON 103 #define RC_NO_MEMORY 104 /* - * SCRIPT_WRAPPER is used to choose between two variants of an executable built + * 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 @@ -40,6 +40,15 @@ #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 */ @@ -97,7 +106,7 @@ error(int rc, wchar_t * format, ... ) #if !defined(_WINDOWS) fwprintf(stderr, L"%ls\n", message); #else - MessageBox(NULL, message, TEXT("Python Launcher is sorry to say ..."), + MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...", MB_OK); #endif exit(rc); @@ -131,6 +140,17 @@ static wchar_t * get_env(wchar_t * key) return buf; } +#if defined(_DEBUG) +#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" @@ -139,6 +159,7 @@ static wchar_t * get_env(wchar_t * key) #define PYTHON_EXECUTABLE L"python.exe" +#endif #endif #define MAX_VERSION_SIZE 4 @@ -1118,6 +1139,7 @@ static PYC_MAGIC magic_values[] = { { 3320, 3351, L"3.5" }, { 3360, 3379, L"3.6" }, { 3390, 3399, L"3.7" }, + { 3400, 3409, L"3.8" }, { 0 } }; @@ -1456,6 +1478,87 @@ show_python_list(wchar_t ** argv) 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() +{ + 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) { @@ -1463,21 +1566,27 @@ process(int argc, wchar_t ** argv) wchar_t * command; wchar_t * executable; wchar_t * p; + wchar_t * argv0; int rc = 0; - size_t plen; INSTALLED_PYTHON * ip; BOOL valid; DWORD size, attrs; - HRESULT hr; wchar_t message[MSGSIZE]; void * version_data; VS_FIXEDFILEINFO * file_info; UINT block_size; - int index; -#if defined(SCRIPT_WRAPPER) +#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); @@ -1495,6 +1604,7 @@ process(int argc, wchar_t ** argv) #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); @@ -1503,9 +1613,7 @@ process(int argc, wchar_t ** argv) appdata_ini_path[0] = L'\0'; } else { - plen = wcslen(appdata_ini_path); - p = &appdata_ini_path[plen]; - wcsncpy_s(p, MAX_PATH - plen, L"\\py.ini", _TRUNCATE); + 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); @@ -1514,8 +1622,9 @@ process(int argc, wchar_t ** argv) debug(L"Using local configuration file '%ls'\n", appdata_ini_path); } } - plen = GetModuleFileNameW(NULL, launcher_ini_path, MAX_PATH); - size = GetFileVersionInfoSizeW(launcher_ini_path, &size); +#endif + argv0 = get_process_name(); + size = GetFileVersionInfoSizeW(argv0, &size); if (size == 0) { winerror(GetLastError(), message, MSGSIZE); debug(L"GetFileVersionInfoSize failed: %ls\n", message); @@ -1523,7 +1632,7 @@ process(int argc, wchar_t ** argv) else { version_data = malloc(size); if (version_data) { - valid = GetFileVersionInfoW(launcher_ini_path, 0, size, + valid = GetFileVersionInfoW(argv0, 0, size, version_data); if (!valid) debug(L"GetFileVersionInfo failed: %X\n", GetLastError()); @@ -1540,15 +1649,51 @@ process(int argc, wchar_t ** argv) 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 { - wcsncpy_s(p, MAX_PATH - (p - launcher_ini_path), L"\\py.ini", - _TRUNCATE); + 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); @@ -1557,6 +1702,7 @@ process(int argc, wchar_t ** argv) 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); @@ -1592,6 +1738,52 @@ process(int argc, wchar_t ** argv) 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) + 1 + 1; /* include sep and null */ + executable = (wchar_t *)malloc(cch * sizeof(wchar_t)); + cch_actual = MultiByteToWideChar(CP_UTF8, 0, start, len, executable, cch); + if (!cch_actual) { + error(RC_BAD_VENV_CFG, L"Cannot decode home path in '%ls'", + venv_cfg_path); + } + if (executable[cch_actual - 1] != L'\\') { + executable[cch_actual++] = L'\\'; + executable[cch_actual] = L'\0'; + } + if (wcscat_s(executable, cch, PYTHON_EXECUTABLE)) { + error(RC_BAD_VENV_CFG, L"Cannot create executable path from '%ls'", + venv_cfg_path); + } + if (GetFileAttributesW(executable) == INVALID_FILE_ATTRIBUTES) { + error(RC_NO_PYTHON, L"No Python at '%ls'", executable); + } + if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", argv0)) { + error(0, L"Failed to set launcher environment"); + } + valid = 1; + } #else if (argc <= 1) { valid = FALSE; @@ -1599,7 +1791,6 @@ process(int argc, wchar_t ** argv) } else { p = argv[1]; - plen = wcslen(p); if ((argc == 2) && // list version args (!wcsncmp(p, L"-0", wcslen(L"-0")) || !wcsncmp(p, L"--list", wcslen(L"--list")))) diff --git a/PC/layout/__init__.py b/PC/layout/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/PC/layout/__main__.py b/PC/layout/__main__.py new file mode 100644 index 000000000000..f7aa1e6d261f --- /dev/null +++ b/PC/layout/__main__.py @@ -0,0 +1,14 @@ +import sys + +try: + import layout +except ImportError: + # Failed to import our package, which likely means we were started directly + # Add the additional search path needed to locate our module. + from pathlib import Path + + sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +from layout.main import main + +sys.exit(int(main() or 0)) diff --git a/PC/layout/main.py b/PC/layout/main.py new file mode 100644 index 000000000000..82d0536ca920 --- /dev/null +++ b/PC/layout/main.py @@ -0,0 +1,612 @@ +""" +Generates a layout of Python for Windows from a build. + +See python make_layout.py --help for usage. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import argparse +import functools +import os +import re +import shutil +import subprocess +import sys +import tempfile +import zipfile + +from pathlib import Path + +if __name__ == "__main__": + # Started directly, so enable relative imports + __path__ = [str(Path(__file__).resolve().parent)] + +from .support.appxmanifest import * +from .support.catalog import * +from .support.constants import * +from .support.filesets import * +from .support.logging import * +from .support.options import * +from .support.pip import * +from .support.props import * + +BDIST_WININST_FILES_ONLY = FileNameSet("wininst-*", "bdist_wininst.py") +BDIST_WININST_STUB = "PC/layout/support/distutils.command.bdist_wininst.py" + +TEST_PYDS_ONLY = FileStemSet("xxlimited", "_ctypes_test", "_test*") +TEST_DIRS_ONLY = FileNameSet("test", "tests") + +IDLE_DIRS_ONLY = FileNameSet("idlelib") + +TCLTK_PYDS_ONLY = FileStemSet("tcl*", "tk*", "_tkinter") +TCLTK_DIRS_ONLY = FileNameSet("tkinter", "turtledemo") +TCLTK_FILES_ONLY = FileNameSet("turtle.py") + +VENV_DIRS_ONLY = FileNameSet("venv", "ensurepip") + +EXCLUDE_FROM_PYDS = FileStemSet("python*", "pyshellext") +EXCLUDE_FROM_LIB = FileNameSet("*.pyc", "__pycache__", "*.pickle") +EXCLUDE_FROM_PACKAGED_LIB = FileNameSet("readme.txt") +EXCLUDE_FROM_COMPILE = FileNameSet("badsyntax_*", "bad_*") +EXCLUDE_FROM_CATALOG = FileSuffixSet(".exe", ".pyd", ".dll") + +REQUIRED_DLLS = FileStemSet("libcrypto*", "libssl*") + +LIB2TO3_GRAMMAR_FILES = FileNameSet("Grammar.txt", "PatternGrammar.txt") + +PY_FILES = FileSuffixSet(".py") +PYC_FILES = FileSuffixSet(".pyc") +CAT_FILES = FileSuffixSet(".cat") +CDF_FILES = FileSuffixSet(".cdf") + +DATA_DIRS = FileNameSet("data") + +TOOLS_DIRS = FileNameSet("scripts", "i18n", "pynche", "demo", "parser") +TOOLS_FILES = FileSuffixSet(".py", ".pyw", ".txt") + + +def get_lib_layout(ns): + def _c(f): + if f in EXCLUDE_FROM_LIB: + return False + if f.is_dir(): + if f in TEST_DIRS_ONLY: + return ns.include_tests + if f in TCLTK_DIRS_ONLY: + return ns.include_tcltk + if f in IDLE_DIRS_ONLY: + return ns.include_idle + if f in VENV_DIRS_ONLY: + return ns.include_venv + else: + if f in TCLTK_FILES_ONLY: + return ns.include_tcltk + if f in BDIST_WININST_FILES_ONLY: + return ns.include_bdist_wininst + return True + + for dest, src in rglob(ns.source / "Lib", "**/*", _c): + yield dest, src + + if not ns.include_bdist_wininst: + src = ns.source / BDIST_WININST_STUB + yield Path("distutils/command/bdist_wininst.py"), src + + +def get_tcltk_lib(ns): + if not ns.include_tcltk: + return + + tcl_lib = os.getenv("TCL_LIBRARY") + if not tcl_lib or not os.path.isdir(tcl_lib): + try: + with open(ns.build / "TCL_LIBRARY.env", "r", encoding="utf-8-sig") as f: + tcl_lib = f.read().strip() + except FileNotFoundError: + pass + if not tcl_lib or not os.path.isdir(tcl_lib): + warn("Failed to find TCL_LIBRARY") + return + + for dest, src in rglob(Path(tcl_lib).parent, "**/*"): + yield "tcl/{}".format(dest), src + + +def get_layout(ns): + def in_build(f, dest="", new_name=None): + n, _, x = f.rpartition(".") + n = new_name or n + src = ns.build / f + if ns.debug and src not in REQUIRED_DLLS: + if not src.stem.endswith("_d"): + src = src.parent / (src.stem + "_d" + src.suffix) + if not n.endswith("_d"): + n += "_d" + f = n + "." + x + yield dest + n + "." + x, src + if ns.include_symbols: + pdb = src.with_suffix(".pdb") + if pdb.is_file(): + yield dest + n + ".pdb", pdb + if ns.include_dev: + lib = src.with_suffix(".lib") + if lib.is_file(): + yield "libs/" + n + ".lib", lib + + yield from in_build("python_uwp.exe", new_name="python") + yield from in_build("pythonw_uwp.exe", new_name="pythonw") + + yield from in_build(PYTHON_DLL_NAME) + + if ns.include_launchers: + if ns.include_pip: + yield from in_build("python_uwp.exe", new_name="pip") + if ns.include_idle: + yield from in_build("pythonw_uwp.exe", new_name="idle") + + if ns.include_stable: + yield from in_build(PYTHON_STABLE_DLL_NAME) + + for dest, src in rglob(ns.build, "vcruntime*.dll"): + yield dest, src + + for dest, src in rglob(ns.build, ("*.pyd", "*.dll")): + if src.stem.endswith("_d") != bool(ns.debug) and src not in REQUIRED_DLLS: + continue + if src in EXCLUDE_FROM_PYDS: + continue + if src in TEST_PYDS_ONLY and not ns.include_tests: + continue + if src in TCLTK_PYDS_ONLY and not ns.include_tcltk: + continue + + yield from in_build(src.name, dest="" if ns.flat_dlls else "DLLs/") + + if ns.zip_lib: + zip_name = PYTHON_ZIP_NAME + yield zip_name, ns.temp / zip_name + else: + for dest, src in get_lib_layout(ns): + yield "Lib/{}".format(dest), src + + if ns.include_venv: + yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/", "python") + yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/", "pythonw") + + if ns.include_tools: + + def _c(d): + if d.is_dir(): + return d in TOOLS_DIRS + return d in TOOLS_FILES + + for dest, src in rglob(ns.source / "Tools", "**/*", _c): + yield "Tools/{}".format(dest), src + + if ns.include_underpth: + yield PYTHON_PTH_NAME, ns.temp / PYTHON_PTH_NAME + + if ns.include_dev: + + def _c(d): + if d.is_dir(): + return d.name != "internal" + return True + + for dest, src in rglob(ns.source / "Include", "**/*.h", _c): + yield "include/{}".format(dest), src + src = ns.source / "PC" / "pyconfig.h" + yield "include/pyconfig.h", src + + for dest, src in get_tcltk_lib(ns): + yield dest, src + + if ns.include_pip: + pip_dir = get_pip_dir(ns) + if not pip_dir.is_dir(): + log_warning("Failed to find {} - pip will not be included", pip_dir) + else: + pkg_root = "packages/{}" if ns.zip_lib else "Lib/site-packages/{}" + for dest, src in rglob(pip_dir, "**/*"): + if src in EXCLUDE_FROM_LIB or src in EXCLUDE_FROM_PACKAGED_LIB: + continue + yield pkg_root.format(dest), src + + if ns.include_chm: + for dest, src in rglob(ns.doc_build / "htmlhelp", PYTHON_CHM_NAME): + yield "Doc/{}".format(dest), src + + if ns.include_html_doc: + for dest, src in rglob(ns.doc_build / "html", "**/*"): + yield "Doc/html/{}".format(dest), src + + if ns.include_props: + for dest, src in get_props_layout(ns): + yield dest, src + + for dest, src in get_appx_layout(ns): + yield dest, src + + if ns.include_cat: + if ns.flat_dlls: + yield ns.include_cat.name, ns.include_cat + else: + yield "DLLs/{}".format(ns.include_cat.name), ns.include_cat + + +def _compile_one_py(src, dest, name, optimize): + import py_compile + + if dest is not None: + dest = str(dest) + + try: + return Path( + py_compile.compile( + str(src), + dest, + str(name), + doraise=True, + optimize=optimize, + invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, + ) + ) + except py_compile.PyCompileError: + log_warning("Failed to compile {}", src) + return None + + +def _py_temp_compile(src, ns, dest_dir=None): + if not ns.precompile or src not in PY_FILES or src.parent in DATA_DIRS: + return None + + dest = (dest_dir or ns.temp) / (src.stem + ".py") + return _compile_one_py(src, dest.with_suffix(".pyc"), dest, optimize=2) + + +def _write_to_zip(zf, dest, src, ns): + pyc = _py_temp_compile(src, ns) + if pyc: + try: + zf.write(str(pyc), dest.with_suffix(".pyc")) + finally: + try: + pyc.unlink() + except: + log_exception("Failed to delete {}", pyc) + return + + if src in LIB2TO3_GRAMMAR_FILES: + from lib2to3.pgen2.driver import load_grammar + + tmp = ns.temp / src.name + try: + shutil.copy(src, tmp) + load_grammar(str(tmp)) + for f in ns.temp.glob(src.stem + "*.pickle"): + zf.write(str(f), str(dest.parent / f.name)) + try: + f.unlink() + except: + log_exception("Failed to delete {}", f) + except: + log_exception("Failed to compile {}", src) + finally: + try: + tmp.unlink() + except: + log_exception("Failed to delete {}", tmp) + + zf.write(str(src), str(dest)) + + +def generate_source_files(ns): + if ns.zip_lib: + zip_name = PYTHON_ZIP_NAME + zip_path = ns.temp / zip_name + if zip_path.is_file(): + zip_path.unlink() + elif zip_path.is_dir(): + log_error( + "Cannot create zip file because a directory exists by the same name" + ) + return + log_info("Generating {} in {}", zip_name, ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: + for dest, src in get_lib_layout(ns): + _write_to_zip(zf, dest, src, ns) + + if ns.include_underpth: + log_info("Generating {} in {}", PYTHON_PTH_NAME, ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + with open(ns.temp / PYTHON_PTH_NAME, "w", encoding="utf-8") as f: + if ns.zip_lib: + print(PYTHON_ZIP_NAME, file=f) + if ns.include_pip: + print("packages", file=f) + else: + print("Lib", file=f) + print("Lib/site-packages", file=f) + if not ns.flat_dlls: + print("DLLs", file=f) + print(".", file=f) + print(file=f) + print("# Uncomment to run site.main() automatically", file=f) + print("#import site", file=f) + + if ns.include_appxmanifest: + log_info("Generating AppxManifest.xml in {}", ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + + with open(ns.temp / "AppxManifest.xml", "wb") as f: + f.write(get_appxmanifest(ns)) + + with open(ns.temp / "_resources.xml", "wb") as f: + f.write(get_resources_xml(ns)) + + if ns.include_pip: + pip_dir = get_pip_dir(ns) + if not (pip_dir / "pip").is_dir(): + log_info("Extracting pip to {}", pip_dir) + pip_dir.mkdir(parents=True, exist_ok=True) + extract_pip_files(ns) + + if ns.include_props: + log_info("Generating {} in {}", PYTHON_PROPS_NAME, ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + with open(ns.temp / PYTHON_PROPS_NAME, "wb") as f: + f.write(get_props(ns)) + + +def _create_zip_file(ns): + if not ns.zip: + return None + + if ns.zip.is_file(): + try: + ns.zip.unlink() + except OSError: + log_exception("Unable to remove {}", ns.zip) + sys.exit(8) + elif ns.zip.is_dir(): + log_error("Cannot create ZIP file because {} is a directory", ns.zip) + sys.exit(8) + + ns.zip.parent.mkdir(parents=True, exist_ok=True) + return zipfile.ZipFile(ns.zip, "w", zipfile.ZIP_DEFLATED) + + +def copy_files(files, ns): + if ns.copy: + ns.copy.mkdir(parents=True, exist_ok=True) + + try: + total = len(files) + except TypeError: + total = None + count = 0 + + zip_file = _create_zip_file(ns) + try: + need_compile = [] + in_catalog = [] + + for dest, src in files: + count += 1 + if count % 10 == 0: + if total: + log_info("Processed {:>4} of {} files", count, total) + else: + log_info("Processed {} files", count) + log_debug("Processing {!s}", src) + + if ( + ns.precompile + and src in PY_FILES + and src not in EXCLUDE_FROM_COMPILE + and src.parent not in DATA_DIRS + and os.path.normcase(str(dest)).startswith(os.path.normcase("Lib")) + ): + if ns.copy: + need_compile.append((dest, ns.copy / dest)) + else: + (ns.temp / "Lib" / dest).parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, ns.temp / "Lib" / dest) + need_compile.append((dest, ns.temp / "Lib" / dest)) + + if src not in EXCLUDE_FROM_CATALOG: + in_catalog.append((src.name, src)) + + if ns.copy: + log_debug("Copy {} -> {}", src, ns.copy / dest) + (ns.copy / dest).parent.mkdir(parents=True, exist_ok=True) + try: + shutil.copy2(src, ns.copy / dest) + except shutil.SameFileError: + pass + + if ns.zip: + log_debug("Zip {} into {}", src, ns.zip) + zip_file.write(src, str(dest)) + + if need_compile: + for dest, src in need_compile: + compiled = [ + _compile_one_py(src, None, dest, optimize=0), + _compile_one_py(src, None, dest, optimize=1), + _compile_one_py(src, None, dest, optimize=2), + ] + for c in compiled: + if not c: + continue + cdest = Path(dest).parent / Path(c).relative_to(src.parent) + if ns.zip: + log_debug("Zip {} into {}", c, ns.zip) + zip_file.write(c, str(cdest)) + in_catalog.append((cdest.name, cdest)) + + if ns.catalog: + # Just write out the CDF now. Compilation and signing is + # an extra step + log_info("Generating {}", ns.catalog) + ns.catalog.parent.mkdir(parents=True, exist_ok=True) + write_catalog(ns.catalog, in_catalog) + + finally: + if zip_file: + zip_file.close() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("-v", help="Increase verbosity", action="count") + parser.add_argument( + "-s", + "--source", + metavar="dir", + help="The directory containing the repository root", + type=Path, + default=None, + ) + parser.add_argument( + "-b", "--build", metavar="dir", help="Specify the build directory", type=Path + ) + parser.add_argument( + "--doc-build", + metavar="dir", + help="Specify the docs build directory", + type=Path, + default=None, + ) + parser.add_argument( + "--copy", + metavar="directory", + help="The name of the directory to copy an extracted layout to", + type=Path, + default=None, + ) + parser.add_argument( + "--zip", + metavar="file", + help="The ZIP file to write all files to", + type=Path, + default=None, + ) + parser.add_argument( + "--catalog", + metavar="file", + help="The CDF file to write catalog entries to", + type=Path, + default=None, + ) + parser.add_argument( + "--log", + metavar="file", + help="Write all operations to the specified file", + type=Path, + default=None, + ) + parser.add_argument( + "-t", + "--temp", + metavar="file", + help="A temporary working directory", + type=Path, + default=None, + ) + parser.add_argument( + "-d", "--debug", help="Include debug build", action="store_true" + ) + parser.add_argument( + "-p", + "--precompile", + help="Include .pyc files instead of .py", + action="store_true", + ) + parser.add_argument( + "-z", "--zip-lib", help="Include library in a ZIP file", action="store_true" + ) + parser.add_argument( + "--flat-dlls", help="Does not create a DLLs directory", action="store_true" + ) + parser.add_argument( + "-a", + "--include-all", + help="Include all optional components", + action="store_true", + ) + parser.add_argument( + "--include-cat", + metavar="file", + help="Specify the catalog file to include", + type=Path, + default=None, + ) + for opt, help in get_argparse_options(): + parser.add_argument(opt, help=help, action="store_true") + + ns = parser.parse_args() + update_presets(ns) + + ns.source = ns.source or (Path(__file__).resolve().parent.parent.parent) + ns.build = ns.build or Path(sys.executable).parent + ns.temp = ns.temp or Path(tempfile.mkdtemp()) + ns.doc_build = ns.doc_build or (ns.source / "Doc" / "build") + if not ns.source.is_absolute(): + ns.source = (Path.cwd() / ns.source).resolve() + if not ns.build.is_absolute(): + ns.build = (Path.cwd() / ns.build).resolve() + if not ns.temp.is_absolute(): + ns.temp = (Path.cwd() / ns.temp).resolve() + if not ns.doc_build.is_absolute(): + ns.doc_build = (Path.cwd() / ns.doc_build).resolve() + if ns.include_cat and not ns.include_cat.is_absolute(): + ns.include_cat = (Path.cwd() / ns.include_cat).resolve() + + if ns.copy and not ns.copy.is_absolute(): + ns.copy = (Path.cwd() / ns.copy).resolve() + if ns.zip and not ns.zip.is_absolute(): + ns.zip = (Path.cwd() / ns.zip).resolve() + if ns.catalog and not ns.catalog.is_absolute(): + ns.catalog = (Path.cwd() / ns.catalog).resolve() + + configure_logger(ns) + + log_info( + """OPTIONS +Source: {ns.source} +Build: {ns.build} +Temp: {ns.temp} + +Copy to: {ns.copy} +Zip to: {ns.zip} +Catalog: {ns.catalog}""", + ns=ns, + ) + + if ns.include_idle and not ns.include_tcltk: + log_warning("Assuming --include-tcltk to support --include-idle") + ns.include_tcltk = True + + try: + generate_source_files(ns) + files = list(get_layout(ns)) + copy_files(files, ns) + except KeyboardInterrupt: + log_info("Interrupted by Ctrl+C") + return 3 + except SystemExit: + raise + except: + log_exception("Unhandled error") + + if error_was_logged(): + log_error("Errors occurred.") + return 1 + + +if __name__ == "__main__": + sys.exit(int(main() or 0)) diff --git a/PC/layout/support/__init__.py b/PC/layout/support/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/PC/layout/support/appxmanifest.py b/PC/layout/support/appxmanifest.py new file mode 100644 index 000000000000..c5dda70c7ef8 --- /dev/null +++ b/PC/layout/support/appxmanifest.py @@ -0,0 +1,487 @@ +""" +File generation for APPX/MSIX manifests. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +import collections +import ctypes +import io +import os +import sys + +from pathlib import Path, PureWindowsPath +from xml.etree import ElementTree as ET + +from .constants import * + +__all__ = [] + + +def public(f): + __all__.append(f.__name__) + return f + + +APPX_DATA = dict( + Name="PythonSoftwareFoundation.Python.{}".format(VER_DOT), + Version="{}.{}.{}.0".format(VER_MAJOR, VER_MINOR, VER_FIELD3), + Publisher=os.getenv( + "APPX_DATA_PUBLISHER", "CN=4975D53F-AA7E-49A5-8B49-EA4FDC1BB66B" + ), + DisplayName="Python {}".format(VER_DOT), + Description="The Python {} runtime and console.".format(VER_DOT), + ProcessorArchitecture="x64" if IS_X64 else "x86", +) + +PYTHON_VE_DATA = dict( + DisplayName="Python {}".format(VER_DOT), + Description="Python interactive console", + Square150x150Logo="_resources/pythonx150.png", + Square44x44Logo="_resources/pythonx44.png", + BackgroundColor="transparent", +) + +PYTHONW_VE_DATA = dict( + DisplayName="Python {} (Windowed)".format(VER_DOT), + Description="Python windowed app launcher", + Square150x150Logo="_resources/pythonwx150.png", + Square44x44Logo="_resources/pythonwx44.png", + BackgroundColor="transparent", + AppListEntry="none", +) + +PIP_VE_DATA = dict( + DisplayName="pip (Python {})".format(VER_DOT), + Description="pip package manager for Python {}".format(VER_DOT), + Square150x150Logo="_resources/pythonx150.png", + Square44x44Logo="_resources/pythonx44.png", + BackgroundColor="transparent", + AppListEntry="none", +) + +IDLE_VE_DATA = dict( + DisplayName="IDLE (Python {})".format(VER_DOT), + Description="IDLE editor for Python {}".format(VER_DOT), + Square150x150Logo="_resources/pythonwx150.png", + Square44x44Logo="_resources/pythonwx44.png", + BackgroundColor="transparent", +) + +APPXMANIFEST_NS = { + "": "http://schemas.microsoft.com/appx/manifest/foundation/windows10", + "m": "http://schemas.microsoft.com/appx/manifest/foundation/windows10", + "uap": "http://schemas.microsoft.com/appx/manifest/uap/windows10", + "rescap": "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities", + "rescap4": "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/4", + "desktop4": "http://schemas.microsoft.com/appx/manifest/desktop/windows10/4", + "desktop6": "http://schemas.microsoft.com/appx/manifest/desktop/windows10/6", + "uap3": "http://schemas.microsoft.com/appx/manifest/uap/windows10/3", + "uap4": "http://schemas.microsoft.com/appx/manifest/uap/windows10/4", + "uap5": "http://schemas.microsoft.com/appx/manifest/uap/windows10/5", +} + +APPXMANIFEST_TEMPLATE = """ + + + + + Python Software Foundation + + _resources/pythonx50.png + + + + + + + + + + + + + + +""" + + +RESOURCES_XML_TEMPLATE = r""" + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + + +SCCD_FILENAME = "PC/classicAppCompat.sccd" + +REGISTRY = { + "HKCU\\Software\\Python\\PythonCore": { + VER_DOT: { + "DisplayName": APPX_DATA["DisplayName"], + "SupportUrl": "https://www.python.org/", + "SysArchitecture": "64bit" if IS_X64 else "32bit", + "SysVersion": VER_DOT, + "Version": "{}.{}.{}".format(VER_MAJOR, VER_MINOR, VER_MICRO), + "InstallPath": { + # I have no idea why the trailing spaces are needed, but they seem to be needed. + "": "[{AppVPackageRoot}][ ]", + "ExecutablePath": "[{AppVPackageRoot}]python.exe[ ]", + "WindowedExecutablePath": "[{AppVPackageRoot}]pythonw.exe[ ]", + }, + "Help": { + "Main Python Documentation": { + "_condition": lambda ns: ns.include_chm, + "": "[{{AppVPackageRoot}}]Doc\\{}[ ]".format( + PYTHON_CHM_NAME + ), + }, + "Local Python Documentation": { + "_condition": lambda ns: ns.include_html_doc, + "": "[{AppVPackageRoot}]Doc\\html\\index.html[ ]", + }, + "Online Python Documentation": { + "": "https://docs.python.org/{}".format(VER_DOT) + }, + }, + "Idle": { + "_condition": lambda ns: ns.include_idle, + "": "[{AppVPackageRoot}]Lib\\idlelib\\idle.pyw[ ]", + }, + } + } +} + + +def get_packagefamilyname(name, publisher_id): + class PACKAGE_ID(ctypes.Structure): + _fields_ = [ + ("reserved", ctypes.c_uint32), + ("processorArchitecture", ctypes.c_uint32), + ("version", ctypes.c_uint64), + ("name", ctypes.c_wchar_p), + ("publisher", ctypes.c_wchar_p), + ("resourceId", ctypes.c_wchar_p), + ("publisherId", ctypes.c_wchar_p), + ] + _pack_ = 4 + + pid = PACKAGE_ID(0, 0, 0, name, publisher_id, None, None) + result = ctypes.create_unicode_buffer(256) + result_len = ctypes.c_uint32(256) + r = ctypes.windll.kernel32.PackageFamilyNameFromId( + pid, ctypes.byref(result_len), result + ) + if r: + raise OSError(r, "failed to get package family name") + return result.value[: result_len.value] + + +def _fixup_sccd(ns, sccd, new_hash=None): + if not new_hash: + return sccd + + NS = dict(s="http://schemas.microsoft.com/appx/2016/sccd") + with open(sccd, "rb") as f: + xml = ET.parse(f) + + pfn = get_packagefamilyname(APPX_DATA["Name"], APPX_DATA["Publisher"]) + + ae = xml.find("s:AuthorizedEntities", NS) + ae.clear() + + e = ET.SubElement(ae, ET.QName(NS["s"], "AuthorizedEntity")) + e.set("AppPackageFamilyName", pfn) + e.set("CertificateSignatureHash", new_hash) + + for e in xml.findall("s:Catalog", NS): + e.text = "FFFF" + + sccd = ns.temp / sccd.name + sccd.parent.mkdir(parents=True, exist_ok=True) + with open(sccd, "wb") as f: + xml.write(f, encoding="utf-8") + + return sccd + + +@public +def get_appx_layout(ns): + if not ns.include_appxmanifest: + return + + yield "AppxManifest.xml", ns.temp / "AppxManifest.xml" + yield "_resources.xml", ns.temp / "_resources.xml" + icons = ns.source / "PC" / "icons" + yield "_resources/pythonx44.png", icons / "pythonx44.png" + yield "_resources/pythonx44$targetsize-44_altform-unplated.png", icons / "pythonx44.png" + yield "_resources/pythonx50.png", icons / "pythonx50.png" + yield "_resources/pythonx50$targetsize-50_altform-unplated.png", icons / "pythonx50.png" + yield "_resources/pythonx150.png", icons / "pythonx150.png" + yield "_resources/pythonx150$targetsize-150_altform-unplated.png", icons / "pythonx150.png" + yield "_resources/pythonwx44.png", icons / "pythonwx44.png" + yield "_resources/pythonwx44$targetsize-44_altform-unplated.png", icons / "pythonwx44.png" + yield "_resources/pythonwx150.png", icons / "pythonwx150.png" + yield "_resources/pythonwx150$targetsize-150_altform-unplated.png", icons / "pythonwx150.png" + sccd = ns.source / SCCD_FILENAME + if sccd.is_file(): + # This should only be set for side-loading purposes. + sccd = _fixup_sccd(ns, sccd, os.getenv("APPX_DATA_SHA256")) + yield sccd.name, sccd + + +def find_or_add(xml, element, attr=None, always_add=False): + if always_add: + e = None + else: + q = element + if attr: + q += "[@{}='{}']".format(*attr) + e = xml.find(q, APPXMANIFEST_NS) + if e is None: + prefix, _, name = element.partition(":") + name = ET.QName(APPXMANIFEST_NS[prefix or ""], name) + e = ET.SubElement(xml, name) + if attr: + e.set(*attr) + return e + + +def _get_app(xml, appid): + if appid: + app = xml.find( + "m:Applications/m:Application[@Id='{}']".format(appid), APPXMANIFEST_NS + ) + if app is None: + raise LookupError(appid) + else: + app = xml + return app + + +def add_visual(xml, appid, data): + app = _get_app(xml, appid) + e = find_or_add(app, "uap:VisualElements") + for i in data.items(): + e.set(*i) + return e + + +def add_alias(xml, appid, alias, subsystem="windows"): + app = _get_app(xml, appid) + e = find_or_add(app, "m:Extensions") + e = find_or_add(e, "uap5:Extension", ("Category", "windows.appExecutionAlias")) + e = find_or_add(e, "uap5:AppExecutionAlias") + e.set(ET.QName(APPXMANIFEST_NS["desktop4"], "Subsystem"), subsystem) + e = find_or_add(e, "uap5:ExecutionAlias", ("Alias", alias)) + + +def add_file_type(xml, appid, name, suffix, parameters='"%1"'): + app = _get_app(xml, appid) + e = find_or_add(app, "m:Extensions") + e = find_or_add(e, "uap3:Extension", ("Category", "windows.fileTypeAssociation")) + e = find_or_add(e, "uap3:FileTypeAssociation", ("Name", name)) + e.set("Parameters", parameters) + e = find_or_add(e, "uap:SupportedFileTypes") + if isinstance(suffix, str): + suffix = [suffix] + for s in suffix: + ET.SubElement(e, ET.QName(APPXMANIFEST_NS["uap"], "FileType")).text = s + + +def add_application( + ns, xml, appid, executable, aliases, visual_element, subsystem, file_types +): + node = xml.find("m:Applications", APPXMANIFEST_NS) + suffix = "_d.exe" if ns.debug else ".exe" + app = ET.SubElement( + node, + ET.QName(APPXMANIFEST_NS[""], "Application"), + { + "Id": appid, + "Executable": executable + suffix, + "EntryPoint": "Windows.FullTrustApplication", + ET.QName(APPXMANIFEST_NS["desktop4"], "SupportsMultipleInstances"): "true", + }, + ) + if visual_element: + add_visual(app, None, visual_element) + for alias in aliases: + add_alias(app, None, alias + suffix, subsystem) + if file_types: + add_file_type(app, None, *file_types) + return app + + +def _get_registry_entries(ns, root="", d=None): + r = root if root else PureWindowsPath("") + if d is None: + d = REGISTRY + for key, value in d.items(): + if key == "_condition": + continue + elif isinstance(value, dict): + cond = value.get("_condition") + if cond and not cond(ns): + continue + fullkey = r + for part in PureWindowsPath(key).parts: + fullkey /= part + if len(fullkey.parts) > 1: + yield str(fullkey), None, None + yield from _get_registry_entries(ns, fullkey, value) + elif len(r.parts) > 1: + yield str(r), key, value + + +def add_registry_entries(ns, xml): + e = find_or_add(xml, "m:Extensions") + e = find_or_add(e, "rescap4:Extension") + e.set("Category", "windows.classicAppCompatKeys") + e.set("EntryPoint", "Windows.FullTrustApplication") + e = ET.SubElement(e, ET.QName(APPXMANIFEST_NS["rescap4"], "ClassicAppCompatKeys")) + for name, valuename, value in _get_registry_entries(ns): + k = ET.SubElement( + e, ET.QName(APPXMANIFEST_NS["rescap4"], "ClassicAppCompatKey") + ) + k.set("Name", name) + if value: + k.set("ValueName", valuename) + k.set("Value", value) + k.set("ValueType", "REG_SZ") + + +def disable_registry_virtualization(xml): + e = find_or_add(xml, "m:Properties") + e = find_or_add(e, "desktop6:RegistryWriteVirtualization") + e.text = "disabled" + e = find_or_add(xml, "m:Capabilities") + e = find_or_add(e, "rescap:Capability", ("Name", "unvirtualizedResources")) + + +@public +def get_appxmanifest(ns): + for k, v in APPXMANIFEST_NS.items(): + ET.register_namespace(k, v) + ET.register_namespace("", APPXMANIFEST_NS["m"]) + + xml = ET.parse(io.StringIO(APPXMANIFEST_TEMPLATE)) + NS = APPXMANIFEST_NS + QN = ET.QName + + node = xml.find("m:Identity", NS) + for k in node.keys(): + value = APPX_DATA.get(k) + if value: + node.set(k, value) + + for node in xml.find("m:Properties", NS): + value = APPX_DATA.get(node.tag.rpartition("}")[2]) + if value: + node.text = value + + winver = sys.getwindowsversion()[:3] + if winver < (10, 0, 17763): + winver = 10, 0, 17763 + find_or_add(xml, "m:Dependencies/m:TargetDeviceFamily").set( + "MaxVersionTested", "{}.{}.{}.0".format(*winver) + ) + + if winver > (10, 0, 17763): + disable_registry_virtualization(xml) + + app = add_application( + ns, + xml, + "Python", + "python", + ["python", "python{}".format(VER_MAJOR), "python{}".format(VER_DOT)], + PYTHON_VE_DATA, + "console", + ("python.file", [".py"]), + ) + + add_application( + ns, + xml, + "PythonW", + "pythonw", + ["pythonw", "pythonw{}".format(VER_MAJOR), "pythonw{}".format(VER_DOT)], + PYTHONW_VE_DATA, + "windows", + ("python.windowedfile", [".pyw"]), + ) + + if ns.include_pip and ns.include_launchers: + add_application( + ns, + xml, + "Pip", + "pip", + ["pip", "pip{}".format(VER_MAJOR), "pip{}".format(VER_DOT)], + PIP_VE_DATA, + "console", + ("python.wheel", [".whl"], 'install "%1"'), + ) + + if ns.include_idle and ns.include_launchers: + add_application( + ns, + xml, + "Idle", + "idle", + ["idle", "idle{}".format(VER_MAJOR), "idle{}".format(VER_DOT)], + IDLE_VE_DATA, + "windows", + None, + ) + + if (ns.source / SCCD_FILENAME).is_file(): + add_registry_entries(ns, xml) + node = xml.find("m:Capabilities", NS) + node = ET.SubElement(node, QN(NS["uap4"], "CustomCapability")) + node.set("Name", "Microsoft.classicAppCompat_8wekyb3d8bbwe") + + buffer = io.BytesIO() + xml.write(buffer, encoding="utf-8", xml_declaration=True) + return buffer.getbuffer() + + +@public +def get_resources_xml(ns): + return RESOURCES_XML_TEMPLATE.encode("utf-8") diff --git a/PC/layout/support/catalog.py b/PC/layout/support/catalog.py new file mode 100644 index 000000000000..43121187ed18 --- /dev/null +++ b/PC/layout/support/catalog.py @@ -0,0 +1,44 @@ +""" +File generation for catalog signing non-binary contents. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +import sys + +__all__ = ["PYTHON_CAT_NAME", "PYTHON_CDF_NAME"] + + +def public(f): + __all__.append(f.__name__) + return f + + +PYTHON_CAT_NAME = "python.cat" +PYTHON_CDF_NAME = "python.cdf" + + +CATALOG_TEMPLATE = r"""[CatalogHeader] +Name={target.stem}.cat +ResultDir={target.parent} +PublicVersion=1 +CatalogVersion=2 +HashAlgorithms=SHA256 +PageHashes=false +EncodingType= + +[CatalogFiles] +""" + + +def can_sign(file): + return file.is_file() and file.stat().st_size + + +@public +def write_catalog(target, files): + with target.open("w", encoding="utf-8") as cat: + cat.write(CATALOG_TEMPLATE.format(target=target)) + cat.writelines("{}={}\n".format(n, f) for n, f in files if can_sign(f)) diff --git a/PC/layout/support/constants.py b/PC/layout/support/constants.py new file mode 100644 index 000000000000..88ea410b340e --- /dev/null +++ b/PC/layout/support/constants.py @@ -0,0 +1,28 @@ +""" +Constants for generating the layout. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import struct +import sys + +VER_MAJOR, VER_MINOR, VER_MICRO, VER_FIELD4 = struct.pack(">i", sys.hexversion) +VER_FIELD3 = VER_MICRO << 8 | VER_FIELD4 +VER_NAME = {"alpha": "a", "beta": "b", "rc": "rc"}.get( + sys.version_info.releaselevel, "" +) +VER_SERIAL = sys.version_info.serial if VER_NAME else "" +VER_DOT = "{}.{}".format(VER_MAJOR, VER_MINOR) + +PYTHON_DLL_NAME = "python{}{}.dll".format(VER_MAJOR, VER_MINOR) +PYTHON_STABLE_DLL_NAME = "python{}.dll".format(VER_MAJOR) +PYTHON_ZIP_NAME = "python{}{}.zip".format(VER_MAJOR, VER_MINOR) +PYTHON_PTH_NAME = "python{}{}._pth".format(VER_MAJOR, VER_MINOR) + +PYTHON_CHM_NAME = "python{}{}{}{}{}.chm".format( + VER_MAJOR, VER_MINOR, VER_MICRO, VER_NAME, VER_SERIAL +) + +IS_X64 = sys.maxsize > 2 ** 32 diff --git a/PC/layout/support/distutils.command.bdist_wininst.py b/PC/layout/support/distutils.command.bdist_wininst.py new file mode 100644 index 000000000000..6e9b49fe42df --- /dev/null +++ b/PC/layout/support/distutils.command.bdist_wininst.py @@ -0,0 +1,25 @@ +"""distutils.command.bdist_wininst + +Suppress the 'bdist_wininst' command, while still allowing +setuptools to import it without breaking.""" + +from distutils.core import Command +from distutils.errors import DistutilsPlatformError + + +class bdist_wininst(Command): + description = "create an executable installer for MS Windows" + + # Marker for tests that we have the unsupported bdist_wininst + _unsupported = True + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + raise DistutilsPlatformError( + "bdist_wininst is not supported in this Python distribution" + ) diff --git a/PC/layout/support/filesets.py b/PC/layout/support/filesets.py new file mode 100644 index 000000000000..47f727c05784 --- /dev/null +++ b/PC/layout/support/filesets.py @@ -0,0 +1,100 @@ +""" +File sets and globbing helper for make_layout. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import os + + +class FileStemSet: + def __init__(self, *patterns): + self._names = set() + self._prefixes = [] + self._suffixes = [] + for p in map(os.path.normcase, patterns): + if p.endswith("*"): + self._prefixes.append(p[:-1]) + elif p.startswith("*"): + self._suffixes.append(p[1:]) + else: + self._names.add(p) + + def _make_name(self, f): + return os.path.normcase(f.stem) + + def __contains__(self, f): + bn = self._make_name(f) + return ( + bn in self._names + or any(map(bn.startswith, self._prefixes)) + or any(map(bn.endswith, self._suffixes)) + ) + + +class FileNameSet(FileStemSet): + def _make_name(self, f): + return os.path.normcase(f.name) + + +class FileSuffixSet: + def __init__(self, *patterns): + self._names = set() + self._prefixes = [] + self._suffixes = [] + for p in map(os.path.normcase, patterns): + if p.startswith("*."): + self._names.add(p[1:]) + elif p.startswith("*"): + self._suffixes.append(p[1:]) + elif p.endswith("*"): + self._prefixes.append(p[:-1]) + elif p.startswith("."): + self._names.add(p) + else: + self._names.add("." + p) + + def _make_name(self, f): + return os.path.normcase(f.suffix) + + def __contains__(self, f): + bn = self._make_name(f) + return ( + bn in self._names + or any(map(bn.startswith, self._prefixes)) + or any(map(bn.endswith, self._suffixes)) + ) + + +def _rglob(root, pattern, condition): + dirs = [root] + recurse = pattern[:3] in {"**/", "**\\"} + if recurse: + pattern = pattern[3:] + + while dirs: + d = dirs.pop(0) + if recurse: + dirs.extend( + filter( + condition, (type(root)(f2) for f2 in os.scandir(d) if f2.is_dir()) + ) + ) + yield from ( + (f.relative_to(root), f) + for f in d.glob(pattern) + if f.is_file() and condition(f) + ) + + +def _return_true(f): + return True + + +def rglob(root, patterns, condition=None): + if isinstance(patterns, tuple): + for p in patterns: + yield from _rglob(root, p, condition or _return_true) + else: + yield from _rglob(root, patterns, condition or _return_true) diff --git a/PC/layout/support/logging.py b/PC/layout/support/logging.py new file mode 100644 index 000000000000..30869b949a1c --- /dev/null +++ b/PC/layout/support/logging.py @@ -0,0 +1,93 @@ +""" +Logging support for make_layout. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import logging +import sys + +__all__ = [] + +LOG = None +HAS_ERROR = False + + +def public(f): + __all__.append(f.__name__) + return f + + +@public +def configure_logger(ns): + global LOG + if LOG: + return + + LOG = logging.getLogger("make_layout") + LOG.level = logging.DEBUG + + if ns.v: + s_level = max(logging.ERROR - ns.v * 10, logging.DEBUG) + f_level = max(logging.WARNING - ns.v * 10, logging.DEBUG) + else: + s_level = logging.ERROR + f_level = logging.INFO + + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(logging.Formatter("{levelname:8s} {message}", style="{")) + handler.setLevel(s_level) + LOG.addHandler(handler) + + if ns.log: + handler = logging.FileHandler(ns.log, encoding="utf-8", delay=True) + handler.setFormatter( + logging.Formatter("[{asctime}]{levelname:8s}: {message}", style="{") + ) + handler.setLevel(f_level) + LOG.addHandler(handler) + + +class BraceMessage: + def __init__(self, fmt, *args, **kwargs): + self.fmt = fmt + self.args = args + self.kwargs = kwargs + + def __str__(self): + return self.fmt.format(*self.args, **self.kwargs) + + +@public +def log_debug(msg, *args, **kwargs): + return LOG.debug(BraceMessage(msg, *args, **kwargs)) + + +@public +def log_info(msg, *args, **kwargs): + return LOG.info(BraceMessage(msg, *args, **kwargs)) + + +@public +def log_warning(msg, *args, **kwargs): + return LOG.warning(BraceMessage(msg, *args, **kwargs)) + + +@public +def log_error(msg, *args, **kwargs): + global HAS_ERROR + HAS_ERROR = True + return LOG.error(BraceMessage(msg, *args, **kwargs)) + + +@public +def log_exception(msg, *args, **kwargs): + global HAS_ERROR + HAS_ERROR = True + return LOG.exception(BraceMessage(msg, *args, **kwargs)) + + +@public +def error_was_logged(): + return HAS_ERROR diff --git a/PC/layout/support/options.py b/PC/layout/support/options.py new file mode 100644 index 000000000000..76d9e34e1f46 --- /dev/null +++ b/PC/layout/support/options.py @@ -0,0 +1,122 @@ +""" +List of optional components. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +__all__ = [] + + +def public(f): + __all__.append(f.__name__) + return f + + +OPTIONS = { + "stable": {"help": "stable ABI stub"}, + "pip": {"help": "pip"}, + "distutils": {"help": "distutils"}, + "tcltk": {"help": "Tcl, Tk and tkinter"}, + "idle": {"help": "Idle"}, + "tests": {"help": "test suite"}, + "tools": {"help": "tools"}, + "venv": {"help": "venv"}, + "dev": {"help": "headers and libs"}, + "symbols": {"help": "symbols"}, + "bdist-wininst": {"help": "bdist_wininst support"}, + "underpth": {"help": "a python._pth file", "not-in-all": True}, + "launchers": {"help": "specific launchers"}, + "appxmanifest": {"help": "an appxmanifest"}, + "props": {"help": "a python.props file"}, + "chm": {"help": "the CHM documentation"}, + "html-doc": {"help": "the HTML documentation"}, +} + + +PRESETS = { + "appx": { + "help": "APPX package", + "options": [ + "stable", + "pip", + "distutils", + "tcltk", + "idle", + "venv", + "dev", + "launchers", + "appxmanifest", + # XXX: Disabled for now "precompile", + ], + }, + "nuget": { + "help": "nuget package", + "options": ["stable", "pip", "distutils", "dev", "props"], + }, + "default": { + "help": "development kit package", + "options": [ + "stable", + "pip", + "distutils", + "tcltk", + "idle", + "tests", + "tools", + "venv", + "dev", + "symbols", + "bdist-wininst", + "chm", + ], + }, + "embed": { + "help": "embeddable package", + "options": ["stable", "zip-lib", "flat-dlls", "underpth", "precompile"], + }, +} + + +@public +def get_argparse_options(): + for opt, info in OPTIONS.items(): + help = "When specified, includes {}".format(info["help"]) + if info.get("not-in-all"): + help = "{}. Not affected by --include-all".format(help) + + yield "--include-{}".format(opt), help + + for opt, info in PRESETS.items(): + help = "When specified, includes default options for {}".format(info["help"]) + yield "--preset-{}".format(opt), help + + +def ns_get(ns, key, default=False): + return getattr(ns, key.replace("-", "_"), default) + + +def ns_set(ns, key, value=True): + k1 = key.replace("-", "_") + k2 = "include_{}".format(k1) + if hasattr(ns, k2): + setattr(ns, k2, value) + elif hasattr(ns, k1): + setattr(ns, k1, value) + else: + raise AttributeError("no argument named '{}'".format(k1)) + + +@public +def update_presets(ns): + for preset, info in PRESETS.items(): + if ns_get(ns, "preset-{}".format(preset)): + for opt in info["options"]: + ns_set(ns, opt) + + if ns.include_all: + for opt in OPTIONS: + if OPTIONS[opt].get("not-in-all"): + continue + ns_set(ns, opt) diff --git a/PC/layout/support/pip.py b/PC/layout/support/pip.py new file mode 100644 index 000000000000..369a923ce139 --- /dev/null +++ b/PC/layout/support/pip.py @@ -0,0 +1,79 @@ +""" +Extraction and file list generation for pip. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +import os +import shutil +import subprocess +import sys + +__all__ = [] + + +def public(f): + __all__.append(f.__name__) + return f + + +@public +def get_pip_dir(ns): + if ns.copy: + if ns.zip_lib: + return ns.copy / "packages" + return ns.copy / "Lib" / "site-packages" + else: + return ns.temp / "packages" + + +@public +def extract_pip_files(ns): + dest = get_pip_dir(ns) + dest.mkdir(parents=True, exist_ok=True) + + src = ns.source / "Lib" / "ensurepip" / "_bundled" + + ns.temp.mkdir(parents=True, exist_ok=True) + wheels = [shutil.copy(whl, ns.temp) for whl in src.glob("*.whl")] + search_path = os.pathsep.join(wheels) + if os.environ.get("PYTHONPATH"): + search_path += ";" + os.environ["PYTHONPATH"] + + env = os.environ.copy() + env["PYTHONPATH"] = search_path + + output = subprocess.check_output( + [ + sys.executable, + "-m", + "pip", + "--no-color", + "install", + "pip", + "setuptools", + "--upgrade", + "--target", + str(dest), + "--no-index", + "--no-cache-dir", + "-f", + str(src), + "--only-binary", + ":all:", + ], + env=env, + ) + + try: + shutil.rmtree(dest / "bin") + except OSError: + pass + + for file in wheels: + try: + os.remove(file) + except OSError: + pass diff --git a/PC/layout/support/props.py b/PC/layout/support/props.py new file mode 100644 index 000000000000..3a047d215058 --- /dev/null +++ b/PC/layout/support/props.py @@ -0,0 +1,110 @@ +""" +Provides .props file. +""" + +import os + +from .constants import * + +__all__ = ["PYTHON_PROPS_NAME"] + + +def public(f): + __all__.append(f.__name__) + return f + + +PYTHON_PROPS_NAME = "python.props" + +PROPS_DATA = { + "PYTHON_TAG": VER_DOT, + "PYTHON_VERSION": os.getenv("PYTHON_NUSPEC_VERSION"), + "PYTHON_PLATFORM": os.getenv("PYTHON_PROPS_PLATFORM"), + "PYTHON_TARGET": "", +} + +if not PROPS_DATA["PYTHON_VERSION"]: + if VER_NAME: + PROPS_DATA["PYTHON_VERSION"] = "{}.{}-{}{}".format( + VER_DOT, VER_MICRO, VER_NAME, VER_SERIAL + ) + else: + PROPS_DATA["PYTHON_VERSION"] = "{}.{}".format(VER_DOT, VER_MICRO) + +if not PROPS_DATA["PYTHON_PLATFORM"]: + PROPS_DATA["PYTHON_PLATFORM"] = "x64" if IS_X64 else "Win32" + +PROPS_DATA["PYTHON_TARGET"] = "_GetPythonRuntimeFilesDependsOn{}{}_{}".format( + VER_MAJOR, VER_MINOR, PROPS_DATA["PYTHON_PLATFORM"] +) + +PROPS_TEMPLATE = r""" + + + $([msbuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), "python_d.exe") + $([msbuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), "python.exe") + $(PythonHome)\include + $(PythonHome)\libs + {PYTHON_TAG} + {PYTHON_VERSION} + + true + false + false + false + + {PYTHON_TARGET};$(GetPythonRuntimeFilesDependsOn) + + + + + $(PythonInclude);%(AdditionalIncludeDirectories) + MultiThreadedDLL + + + $(PythonLibs);%(AdditionalLibraryDirectories) + + + + + + + + <_PythonRuntimeExe Include="$(PythonHome)\python*.dll" /> + <_PythonRuntimeExe Include="$(PythonHome)\python*.exe" Condition="$(IncludePythonExe) == 'true'" /> + <_PythonRuntimeExe> + %(Filename)%(Extension) + + <_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.pyd" /> + <_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.dll" /> + <_PythonRuntimeDlls> + DLLs\%(Filename)%(Extension) + + <_PythonRuntimeLib Include="$(PythonHome)\Lib\**\*" Exclude="$(PythonHome)\Lib\**\*.pyc;$(PythonHome)\Lib\site-packages\**\*" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\distutils\**\*" Condition="$(IncludeDistutils) != 'true'" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\lib2to3\**\*" Condition="$(IncludeLib2To3) != 'true'" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\ensurepip\**\*" Condition="$(IncludeVEnv) != 'true'" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\venv\**\*" Condition="$(IncludeVEnv) != 'true'" /> + <_PythonRuntimeLib> + Lib\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + +""" + + +@public +def get_props_layout(ns): + if ns.include_all or ns.include_props: + yield "python.props", ns.temp / "python.props" + + +@public +def get_props(ns): + # TODO: Filter contents of props file according to included/excluded items + props = PROPS_TEMPLATE.format_map(PROPS_DATA) + return props.encode("utf-8") diff --git a/Tools/nuget/python.props b/PC/layout/support/python.props similarity index 100% rename from Tools/nuget/python.props rename to PC/layout/support/python.props diff --git a/PC/pylauncher.rc b/PC/pylauncher.rc index 3da3445f5fc4..92987af7138d 100644 --- a/PC/pylauncher.rc +++ b/PC/pylauncher.rc @@ -7,6 +7,11 @@ #include 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" @@ -14,6 +19,7 @@ 5 ICON DISCARDABLE "icons\python.ico" 6 ICON DISCARDABLE "icons\pythonw.ico" 7 ICON DISCARDABLE "icons\setup.ico" +#endif ///////////////////////////////////////////////////////////////////////////// // diff --git a/PC/python_uwp.cpp b/PC/python_uwp.cpp new file mode 100644 index 000000000000..1658d05994bb --- /dev/null +++ b/PC/python_uwp.cpp @@ -0,0 +1,226 @@ +/* Main program when embedded in a UWP application on Windows */ + +#include "Python.h" +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#ifdef PYTHONW +#ifdef _DEBUG +const wchar_t *PROGNAME = L"pythonw_d.exe"; +#else +const wchar_t *PROGNAME = L"pythonw.exe"; +#endif +#else +#ifdef _DEBUG +const wchar_t *PROGNAME = L"python_d.exe"; +#else +const wchar_t *PROGNAME = L"python.exe"; +#endif +#endif + +static void +set_user_base() +{ + wchar_t envBuffer[2048]; + try { + const auto appData = winrt::Windows::Storage::ApplicationData::Current(); + if (appData) { + const auto localCache = appData.LocalCacheFolder(); + if (localCache) { + auto path = localCache.Path(); + if (!path.empty() && + !wcscpy_s(envBuffer, path.c_str()) && + !wcscat_s(envBuffer, L"\\local-packages") + ) { + _wputenv_s(L"PYTHONUSERBASE", envBuffer); + } + } + } + } catch (...) { + } +} + +static const wchar_t * +get_argv0(const wchar_t *argv0) +{ + winrt::hstring installPath; + const wchar_t *launcherPath; + wchar_t *buffer; + size_t len; + + launcherPath = _wgetenv(L"__PYVENV_LAUNCHER__"); + if (launcherPath && launcherPath[0]) { + len = wcslen(launcherPath) + 1; + buffer = (wchar_t *)malloc(sizeof(wchar_t) * len); + if (!buffer) { + Py_FatalError("out of memory"); + return NULL; + } + if (wcscpy_s(buffer, len, launcherPath)) { + Py_FatalError("failed to copy to buffer"); + return NULL; + } + return buffer; + } + + try { + const auto package = winrt::Windows::ApplicationModel::Package::Current(); + if (package) { + const auto install = package.InstalledLocation(); + if (install) { + installPath = install.Path(); + } + } + } + catch (...) { + } + + if (!installPath.empty()) { + len = installPath.size() + wcslen(PROGNAME) + 2; + } else { + len = wcslen(argv0) + wcslen(PROGNAME) + 1; + } + + buffer = (wchar_t *)malloc(sizeof(wchar_t) * len); + if (!buffer) { + Py_FatalError("out of memory"); + return NULL; + } + + if (!installPath.empty()) { + if (wcscpy_s(buffer, len, installPath.c_str())) { + Py_FatalError("failed to copy to buffer"); + return NULL; + } + if (wcscat_s(buffer, len, L"\\")) { + Py_FatalError("failed to concatenate backslash"); + return NULL; + } + } else { + if (wcscpy_s(buffer, len, argv0)) { + Py_FatalError("failed to copy argv[0]"); + return NULL; + } + + wchar_t *name = wcsrchr(buffer, L'\\'); + if (name) { + name[1] = L'\0'; + } else { + buffer[0] = L'\0'; + } + } + + if (wcscat_s(buffer, len, PROGNAME)) { + Py_FatalError("failed to concatenate program name"); + return NULL; + } + + return buffer; +} + +static wchar_t * +get_process_name() +{ + DWORD bufferLen = MAX_PATH; + DWORD len = bufferLen; + wchar_t *r = NULL; + + while (!r) { + r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t)); + if (!r) { + Py_FatalError("out of memory"); + return NULL; + } + len = GetModuleFileNameW(NULL, r, bufferLen); + if (len == 0) { + free((void *)r); + return NULL; + } else if (len == bufferLen && + GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(r); + r = NULL; + bufferLen *= 2; + } + } + + return r; +} + +int +wmain(int argc, wchar_t **argv) +{ + const wchar_t **new_argv; + int new_argc; + const wchar_t *exeName; + + new_argc = argc; + new_argv = (const wchar_t**)malloc(sizeof(wchar_t *) * (argc + 2)); + if (new_argv == NULL) { + Py_FatalError("out of memory"); + return -1; + } + + exeName = get_process_name(); + + new_argv[0] = get_argv0(exeName ? exeName : argv[0]); + for (int i = 1; i < argc; ++i) { + new_argv[i] = argv[i]; + } + + set_user_base(); + + if (exeName) { + const wchar_t *p = wcsrchr(exeName, L'\\'); + if (p) { + const wchar_t *moduleName = NULL; + if (*p++ == L'\\') { + if (wcsnicmp(p, L"pip", 3) == 0) { + moduleName = L"pip"; + _wputenv_s(L"PIP_USER", L"true"); + } + else if (wcsnicmp(p, L"idle", 4) == 0) { + moduleName = L"idlelib"; + } + } + + if (moduleName) { + new_argc += 2; + for (int i = argc; i >= 1; --i) { + new_argv[i + 2] = new_argv[i]; + } + new_argv[1] = L"-m"; + new_argv[2] = moduleName; + } + } + } + + /* Override program_full_path from here so that + sys.executable is set correctly. */ + _Py_SetProgramFullPath(new_argv[0]); + + int result = Py_Main(new_argc, (wchar_t **)new_argv); + + free((void *)exeName); + free((void *)new_argv); + + return result; +} + +#ifdef PYTHONW + +int WINAPI wWinMain( + HINSTANCE hInstance, /* handle to current instance */ + HINSTANCE hPrevInstance, /* handle to previous instance */ + LPWSTR lpCmdLine, /* pointer to command line */ + int nCmdShow /* show state of window */ +) +{ + return wmain(__argc, __wargv); +} + +#endif diff --git a/PC/store_info.txt b/PC/store_info.txt new file mode 100644 index 000000000000..e252692b73cc --- /dev/null +++ b/PC/store_info.txt @@ -0,0 +1,146 @@ +# 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. + +## Title + +Python 3.7 + +## 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.7 interpreter and runtime. + +## Copyright Trademark Information + +(c) Python Software Foundation + +## Additional License Terms + +Visit https://docs.python.org/3.7/license.html for latest license terms. + +PSF LICENSE AGREEMENT FOR PYTHON 3.7 + +1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and + the Individual or Organization ("Licensee") accessing and otherwise using Python + 3.7 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.7 alone or in any derivative + version, provided, however, that PSF's License Agreement and PSF's notice of + copyright, i.e., "Copyright © 2001-2018 Python Software Foundation; All Rights + Reserved" are retained in Python 3.7 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.7 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.7. + +4. PSF is making Python 3.7 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.7 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.7 + FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF + MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.7, 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.7, 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/_tkinter.vcxproj b/PCbuild/_tkinter.vcxproj index 95e3cd50eca5..bd61c0d4f689 100644 --- a/PCbuild/_tkinter.vcxproj +++ b/PCbuild/_tkinter.vcxproj @@ -95,4 +95,10 @@ + + + + + + \ No newline at end of file diff --git a/PCbuild/find_msbuild.bat b/PCbuild/find_msbuild.bat index 57512a01927e..a2810f09c45e 100644 --- a/PCbuild/find_msbuild.bat +++ b/PCbuild/find_msbuild.bat @@ -29,6 +29,16 @@ @where msbuild > "%TEMP%\msbuild.loc" 2> nul && set /P MSBUILD= < "%TEMP%\msbuild.loc" & del "%TEMP%\msbuild.loc" @if exist "%MSBUILD%" set MSBUILD="%MSBUILD%" & (set _Py_MSBuild_Source=PATH) & goto :found +@rem VS 2017 and later provide vswhere.exe, which can be used +@if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto :skip_vswhere +@set _Py_MSBuild_Root= +@for /F "tokens=*" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -property installationPath -latest') DO @(set _Py_MSBuild_Root=%%i\MSBuild) +@if not defined _Py_MSBuild_Root goto :skip_vswhere +@for %%j in (Current 15.0) DO @if exist "%_Py_MSBuild_Root%\%%j\Bin\msbuild.exe" (set MSBUILD="%_Py_MSBuild_Root%\%%j\Bin\msbuild.exe") +@set _Py_MSBuild_Root= +@if defined MSBUILD @if exist %MSBUILD% (set _Py_MSBuild_Source=Visual Studio installation) & goto :found +:skip_vswhere + @rem VS 2017 sets exactly one install as the "main" install, so we may find MSBuild in there. @reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\SxS\VS7" /v 15.0 /reg:32 >nul 2>nul @if NOT ERRORLEVEL 1 @for /F "tokens=1,2*" %%i in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\SxS\VS7" /v 15.0 /reg:32') DO @( diff --git a/PCbuild/pcbuild.proj b/PCbuild/pcbuild.proj index 9e103e12103f..6bf1667e39f8 100644 --- a/PCbuild/pcbuild.proj +++ b/PCbuild/pcbuild.proj @@ -52,6 +52,8 @@ + + @@ -70,6 +72,7 @@ + diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln index 59b3861ed406..c212d9f8f32c 100644 --- a/PCbuild/pcbuild.sln +++ b/PCbuild/pcbuild.sln @@ -93,6 +93,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_queue", "_queue.vcxproj", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "liblzma", "liblzma.vcxproj", "{12728250-16EC-4DC6-94D7-E21DD88947F8}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python_uwp", "python_uwp.vcxproj", "{9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "venvlauncher", "venvlauncher.vcxproj", "{494BAC80-A60C-43A9-99E7-ACB691CE2C4D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "venvwlauncher", "venvwlauncher.vcxproj", "{FDB84CBB-2FB6-47C8-A2D6-091E0833239D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pythonw_uwp", "pythonw_uwp.vcxproj", "{AB603547-1E2A-45B3-9E09-B04596006393}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -693,6 +701,70 @@ Global {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|Win32.Build.0 = Release|Win32 {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|x64.ActiveCfg = Release|x64 {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|x64.Build.0 = Release|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|Win32.ActiveCfg = Debug|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|Win32.Build.0 = Debug|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|x64.ActiveCfg = Debug|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|x64.Build.0 = Debug|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|Win32.ActiveCfg = Release|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|Win32.Build.0 = Release|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|x64.ActiveCfg = Release|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|x64.Build.0 = Release|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|Win32.ActiveCfg = Debug|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|Win32.Build.0 = Debug|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|x64.ActiveCfg = Debug|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|x64.Build.0 = Debug|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|Win32.ActiveCfg = Release|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|Win32.Build.0 = Release|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|x64.ActiveCfg = Release|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|x64.Build.0 = Release|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|Win32.ActiveCfg = Debug|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|Win32.Build.0 = Debug|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|x64.ActiveCfg = Debug|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|x64.Build.0 = Debug|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|Win32.ActiveCfg = Release|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|Win32.Build.0 = Release|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|x64.ActiveCfg = Release|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|x64.Build.0 = Release|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|Win32.ActiveCfg = Debug|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|Win32.Build.0 = Debug|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|x64.ActiveCfg = Debug|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|x64.Build.0 = Debug|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|Win32.ActiveCfg = Release|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|Win32.Build.0 = Release|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|x64.ActiveCfg = Release|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PCbuild/python.props b/PCbuild/python.props index 09f11d3bba8c..6dbb503b3243 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -77,7 +77,8 @@ --> <_RegistryVersion>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0@ProductVersion) <_RegistryVersion Condition="$(_RegistryVersion) == ''">$(Registry:HKEY_LOCAL_MACHINE\WOW6432Node\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0@ProductVersion) - 10.0.17134.0 + 10.0.17763.0 + 10.0.17134.0 10.0.16299.0 10.0.15063.0 10.0.14393.0 diff --git a/PCbuild/python_uwp.vcxproj b/PCbuild/python_uwp.vcxproj new file mode 100644 index 000000000000..af187dd4df30 --- /dev/null +++ b/PCbuild/python_uwp.vcxproj @@ -0,0 +1,86 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF} + + + + + Application + false + Unicode + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + + %(PreprocessorDefinitions) + /EHsc /std:c++17 %(AdditionalOptions) + + + windowsapp.lib;%(AdditionalDependencies) + Console + + + + + + + + + + + + + + {cf7ac3d1-e2df-41d2-bea6-1e2556cdea26} + false + + + + + + diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index d19b5f5acf89..2ee88225b62c 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -460,4 +460,19 @@ + + + $(VCInstallDir)\Redist\MSVC\$(VCToolsRedistVersion)\ + $(VCRedistDir)x86\ + $(VCRedistDir)$(Platform)\ + + + + + + + + + + diff --git a/PCbuild/pythonw_uwp.vcxproj b/PCbuild/pythonw_uwp.vcxproj new file mode 100644 index 000000000000..79e105877fbe --- /dev/null +++ b/PCbuild/pythonw_uwp.vcxproj @@ -0,0 +1,86 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {AB603547-1E2A-45B3-9E09-B04596006393} + + + + + Application + false + Unicode + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + + PYTHONW;%(PreprocessorDefinitions) + /EHsc /std:c++17 %(AdditionalOptions) + + + windowsapp.lib;%(AdditionalDependencies) + Windows + + + + + + + + + + + + + + {cf7ac3d1-e2df-41d2-bea6-1e2556cdea26} + false + + + + + + diff --git a/PCbuild/venvlauncher.vcxproj b/PCbuild/venvlauncher.vcxproj new file mode 100644 index 000000000000..295b36304733 --- /dev/null +++ b/PCbuild/venvlauncher.vcxproj @@ -0,0 +1,85 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D} + venvlauncher + venvlauncher + false + + + + + Application + MultiByte + + + + + + ClCompile + + + + + + + + + _CONSOLE;VENV_REDIRECT;%(PreprocessorDefinitions) + MultiThreaded + + + PY_ICON;%(PreprocessorDefinitions) + + + version.lib;%(AdditionalDependencies) + Console + + + + + + + + + + + + + + + diff --git a/PCbuild/venvwlauncher.vcxproj b/PCbuild/venvwlauncher.vcxproj new file mode 100644 index 000000000000..e7ba25da41eb --- /dev/null +++ b/PCbuild/venvwlauncher.vcxproj @@ -0,0 +1,85 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D} + venvwlauncher + venvwlauncher + false + + + + + Application + MultiByte + + + + + + ClCompile + + + + + + + + + _WINDOWS;VENV_REDIRECT;%(PreprocessorDefinitions) + MultiThreaded + + + PYW_ICON;%(PreprocessorDefinitions) + + + version.lib;%(AdditionalDependencies) + Windows + + + + + + + + + + + + + + + diff --git a/Tools/msi/buildrelease.bat b/Tools/msi/buildrelease.bat index 4178981195ee..45e189b537f6 100644 --- a/Tools/msi/buildrelease.bat +++ b/Tools/msi/buildrelease.bat @@ -37,6 +37,7 @@ set BUILDX64= set TARGET=Rebuild set TESTTARGETDIR= set PGO=-m test -q --pgo +set BUILDMSI=1 set BUILDNUGET=1 set BUILDZIP=1 @@ -61,6 +62,7 @@ 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 @@ -174,10 +176,12 @@ if "%OUTDIR_PLAT%" EQU "win32" ( ) set BUILDOPTS=/p:Platform=%1 /p:BuildForRelease=true /p:DownloadUrl=%DOWNLOAD_URL% /p:DownloadUrlBase=%DOWNLOAD_URL_BASE% /p:ReleaseUri=%RELEASE_URI% -%MSBUILD% "%D%bundle\releaselocal.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=true -if errorlevel 1 exit /B -%MSBUILD% "%D%bundle\releaseweb.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=false -if errorlevel 1 exit /B +if defined BUILDMSI ( + %MSBUILD% "%D%bundle\releaselocal.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=true + if errorlevel 1 exit /B + %MSBUILD% "%D%bundle\releaseweb.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=false + if errorlevel 1 exit /B +) if defined BUILDZIP ( %MSBUILD% "%D%make_zip.proj" /t:Build %BUILDOPTS% %CERTOPTS% /p:OutputPath="%BUILD%en-us" @@ -214,6 +218,7 @@ 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 diff --git a/Tools/msi/make_appx.ps1 b/Tools/msi/make_appx.ps1 new file mode 100644 index 000000000000..b3f190e07db8 --- /dev/null +++ b/Tools/msi/make_appx.ps1 @@ -0,0 +1,71 @@ +<# +.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 new file mode 100644 index 000000000000..70741439869a --- /dev/null +++ b/Tools/msi/make_cat.ps1 @@ -0,0 +1,34 @@ +<# +.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 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]$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" +} +Sign-File -certname $certname -certsha1 $certsha1 -certfile $certfile -description $description -files @($catalog -replace 'cdf$', 'cat') diff --git a/Tools/msi/make_zip.proj b/Tools/msi/make_zip.proj index 214111734219..125a434e51f4 100644 --- a/Tools/msi/make_zip.proj +++ b/Tools/msi/make_zip.proj @@ -15,11 +15,12 @@ .zip $(OutputPath)\$(TargetName)$(TargetExt) rmdir /q/s "$(IntermediateOutputPath)\zip_$(ArchName)" - "$(PythonExe)" "$(MSBuildThisFileDirectory)\make_zip.py" - $(Arguments) -e -o "$(TargetPath)" -t "$(IntermediateOutputPath)\zip_$(ArchName)" -b "$(BuildPath.TrimEnd(`\`))" - set DOC_FILENAME=python$(PythonVersion).chm + "$(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 - $(Environment)%0D%0Aset VCREDIST_PATH=$(CRTRedist)\$(Platform) diff --git a/Tools/msi/make_zip.py b/Tools/msi/make_zip.py deleted file mode 100644 index 58f3b15ef852..000000000000 --- a/Tools/msi/make_zip.py +++ /dev/null @@ -1,250 +0,0 @@ -import argparse -import py_compile -import re -import sys -import shutil -import stat -import os -import tempfile - -from itertools import chain -from pathlib import Path -from zipfile import ZipFile, ZIP_DEFLATED - - -TKTCL_RE = re.compile(r'^(_?tk|tcl).+\.(pyd|dll)', re.IGNORECASE) -DEBUG_RE = re.compile(r'_d\.(pyd|dll|exe|pdb|lib)$', re.IGNORECASE) -PYTHON_DLL_RE = re.compile(r'python\d\d?\.dll$', re.IGNORECASE) - -DEBUG_FILES = { - '_ctypes_test', - '_testbuffer', - '_testcapi', - '_testconsole', - '_testimportmultiple', - '_testmultiphase', - 'xxlimited', - 'python3_dstub', -} - -EXCLUDE_FROM_LIBRARY = { - '__pycache__', - 'idlelib', - 'pydoc_data', - 'site-packages', - 'tkinter', - 'turtledemo', -} - -EXCLUDE_FROM_EMBEDDABLE_LIBRARY = { - 'ensurepip', - 'venv', -} - -EXCLUDE_FILE_FROM_LIBRARY = { - 'bdist_wininst.py', -} - -EXCLUDE_FILE_FROM_LIBS = { - 'liblzma', - 'python3stub', -} - -EXCLUDED_FILES = { - 'pyshellext', -} - -def is_not_debug(p): - if DEBUG_RE.search(p.name): - return False - - if TKTCL_RE.search(p.name): - return False - - return p.stem.lower() not in DEBUG_FILES and p.stem.lower() not in EXCLUDED_FILES - -def is_not_debug_or_python(p): - return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name) - -def include_in_lib(p): - name = p.name.lower() - if p.is_dir(): - if name in EXCLUDE_FROM_LIBRARY: - return False - if name == 'test' and p.parts[-2].lower() == 'lib': - return False - if name in {'test', 'tests'} and p.parts[-3].lower() == 'lib': - return False - return True - - if name in EXCLUDE_FILE_FROM_LIBRARY: - return False - - suffix = p.suffix.lower() - return suffix not in {'.pyc', '.pyo', '.exe'} - -def include_in_embeddable_lib(p): - if p.is_dir() and p.name.lower() in EXCLUDE_FROM_EMBEDDABLE_LIBRARY: - return False - - return include_in_lib(p) - -def include_in_libs(p): - if not is_not_debug(p): - return False - - return p.stem.lower() not in EXCLUDE_FILE_FROM_LIBS - -def include_in_tools(p): - if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}: - return True - - return p.suffix.lower() in {'.py', '.pyw', '.txt'} - -BASE_NAME = 'python{0.major}{0.minor}'.format(sys.version_info) - -FULL_LAYOUT = [ - ('/', '$build', 'python.exe', is_not_debug), - ('/', '$build', 'pythonw.exe', is_not_debug), - ('/', '$build', 'python{}.dll'.format(sys.version_info.major), is_not_debug), - ('/', '$build', '{}.dll'.format(BASE_NAME), is_not_debug), - ('DLLs/', '$build', '*.pyd', is_not_debug), - ('DLLs/', '$build', '*.dll', is_not_debug_or_python), - ('include/', 'include', '*.h', None), - ('include/', 'PC', 'pyconfig.h', None), - ('Lib/', 'Lib', '**/*', include_in_lib), - ('libs/', '$build', '*.lib', include_in_libs), - ('Tools/', 'Tools', '**/*', include_in_tools), -] - -EMBED_LAYOUT = [ - ('/', '$build', 'python*.exe', is_not_debug), - ('/', '$build', '*.pyd', is_not_debug), - ('/', '$build', '*.dll', is_not_debug), - ('{}.zip'.format(BASE_NAME), 'Lib', '**/*', include_in_embeddable_lib), -] - -if os.getenv('DOC_FILENAME'): - FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None)) -if os.getenv('VCREDIST_PATH'): - FULL_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None)) - EMBED_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None)) - -def copy_to_layout(target, rel_sources): - count = 0 - - if target.suffix.lower() == '.zip': - if target.exists(): - target.unlink() - - with ZipFile(str(target), 'w', ZIP_DEFLATED) as f: - with tempfile.TemporaryDirectory() as tmpdir: - for s, rel in rel_sources: - if rel.suffix.lower() == '.py': - pyc = Path(tmpdir) / rel.with_suffix('.pyc').name - try: - py_compile.compile(str(s), str(pyc), str(rel), doraise=True, optimize=2) - except py_compile.PyCompileError: - f.write(str(s), str(rel)) - else: - f.write(str(pyc), str(rel.with_suffix('.pyc'))) - else: - f.write(str(s), str(rel)) - count += 1 - - else: - for s, rel in rel_sources: - dest = target / rel - try: - dest.parent.mkdir(parents=True) - except FileExistsError: - pass - if dest.is_file(): - dest.chmod(stat.S_IWRITE) - shutil.copy(str(s), str(dest)) - if dest.is_file(): - dest.chmod(stat.S_IWRITE) - count += 1 - - return count - -def rglob(root, pattern, condition): - dirs = [root] - recurse = pattern[:3] in {'**/', '**\\'} - while dirs: - d = dirs.pop(0) - for f in d.glob(pattern[3:] if recurse else pattern): - if recurse and f.is_dir() and (not condition or condition(f)): - dirs.append(f) - elif f.is_file() and (not condition or condition(f)): - yield f, f.relative_to(root) - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path) - parser.add_argument('-o', '--out', metavar='file', help='The name of the output archive', type=Path, default=None) - parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None) - parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False) - parser.add_argument('-b', '--build', help='Specify the build directory', type=Path, default=None) - ns = parser.parse_args() - - source = ns.source or (Path(__file__).resolve().parent.parent.parent) - out = ns.out - build = ns.build or Path(sys.exec_prefix) - assert isinstance(source, Path) - assert not out or isinstance(out, Path) - assert isinstance(build, Path) - - if ns.temp: - temp = ns.temp - delete_temp = False - else: - temp = Path(tempfile.mkdtemp()) - delete_temp = True - - if out: - try: - out.parent.mkdir(parents=True) - except FileExistsError: - pass - try: - temp.mkdir(parents=True) - except FileExistsError: - pass - - layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT - - try: - for t, s, p, c in layout: - if s == '$build': - fs = build - else: - fs = source / s - files = rglob(fs, p, c) - extra_files = [] - if s == 'Lib' and p == '**/*': - extra_files.append(( - source / 'tools' / 'msi' / 'distutils.command.bdist_wininst.py', - Path('distutils') / 'command' / 'bdist_wininst.py' - )) - copied = copy_to_layout(temp / t.rstrip('/'), chain(files, extra_files)) - print('Copied {} files'.format(copied)) - - if ns.embed: - with open(str(temp / (BASE_NAME + '._pth')), 'w') as f: - print(BASE_NAME + '.zip', file=f) - print('.', file=f) - print('', file=f) - print('# Uncomment to run site.main() automatically', file=f) - print('#import site', file=f) - - if out: - total = copy_to_layout(out, rglob(temp, '**/*', None)) - print('Wrote {} files to {}'.format(total, out)) - finally: - if delete_temp: - shutil.rmtree(temp, True) - - -if __name__ == "__main__": - sys.exit(int(main() or 0)) diff --git a/Tools/msi/sdktools.psm1 b/Tools/msi/sdktools.psm1 new file mode 100644 index 000000000000..81a74d3679d7 --- /dev/null +++ b/Tools/msi/sdktools.psm1 @@ -0,0 +1,43 @@ +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 $certname) { + $certname = $env:SigningCertificate; + } + if (-not $certfile) { + $certfile = $env:SigningCertificateFile; + } + + foreach ($a in $files) { + if ($certsha1) { + SignTool sign /sha1 $certsha1 /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } elseif ($certname) { + SignTool sign /n $certname /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } elseif ($certfile) { + SignTool sign /f $certfile /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } else { + SignTool sign /a /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } + } +} + diff --git a/Tools/msi/sign_build.ps1 b/Tools/msi/sign_build.ps1 new file mode 100644 index 000000000000..6668eb33a2d1 --- /dev/null +++ b/Tools/msi/sign_build.ps1 @@ -0,0 +1,34 @@ +<# +.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"), + [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/nuget/make_pkg.proj b/Tools/nuget/make_pkg.proj index 9843bc97ccdc..e093a6d0bd76 100644 --- a/Tools/nuget/make_pkg.proj +++ b/Tools/nuget/make_pkg.proj @@ -20,25 +20,28 @@ false $(OutputName).$(NuspecVersion) .nupkg - $(IntermediateOutputPath)\nuget_$(ArchName) + $(IntermediateOutputPath)\nuget_$(ArchName)\ - rmdir /q/s "$(IntermediateOutputPath)" + rmdir /q/s "$(IntermediateOutputPath.TrimEnd(`\`))" - "$(PythonExe)" "$(MSBuildThisFileDirectory)\..\msi\make_zip.py" - $(PythonArguments) -t "$(IntermediateOutputPath)" -b "$(BuildPath.TrimEnd(`\`))" + "$(PythonExe)" "$(PySourcePath)PC\layout" + $(PythonArguments) -b "$(BuildPath.TrimEnd(`\`))" -s "$(PySourcePath.TrimEnd(`\`))" + $(PythonArguments) -t "$(IntermediateOutputPath)obj" + $(PythonArguments) --copy "$(IntermediateOutputPath)pkg" + $(PythonArguments) --include-dev --include-tools --include-pip --include-stable --include-launcher --include-props - "$(IntermediateOutputPath)\python.exe" -B -c "import sys; sys.path.append(r'$(PySourcePath)\Lib'); import ensurepip; ensurepip._main()" - "$(IntermediateOutputPath)\python.exe" -B -m pip install -U $(Packages) + "$(IntermediateOutputPath)pkg\pip.exe" -B -m pip install -U $(Packages) - "$(Nuget)" pack "$(MSBuildThisFileDirectory)\$(OutputName).nuspec" -BasePath "$(IntermediateOutputPath)" + "$(Nuget)" pack "$(MSBuildThisFileDirectory)\$(OutputName).nuspec" -BasePath "$(IntermediateOutputPath)pkg" "$(Nuget)" pack "$(MSBuildThisFileDirectory)\$(OutputName).symbols.nuspec" -BasePath "$(BuildPath.TrimEnd(`\`))" $(NugetArguments) -OutputDirectory "$(OutputPath.Trim(`\`))" $(NugetArguments) -Version "$(NuspecVersion)" $(NugetArguments) -NoPackageAnalysis -NonInteractive - set DOC_FILENAME=python$(PythonVersion).chm $(Environment)%0D%0Aset PYTHONPATH=$(PySourcePath)Lib - $(Environment)%0D%0Aset VCREDIST_PATH=$(CRTRedist)\$(Platform) + $(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 @@ -48,22 +51,7 @@ - - - - - - <_PropsContents>$([System.IO.File]::ReadAllText('python.props')) - <_PropsContents>$(_PropsContents.Replace('$$PYTHON_TAG$$', '$(MajorVersionNumber).$(MinorVersionNumber)')) - <_PropsContents>$(_PropsContents.Replace('$$PYTHON_VERSION$$', '$(NuspecVersion)')) - <_PropsContents Condition="$(Platform) == 'x86'">$(_PropsContents.Replace('$$PYTHON_PLATFORM$$', 'Win32')) - <_PropsContents Condition="$(Platform) != 'x86'">$(_PropsContents.Replace('$$PYTHON_PLATFORM$$', '$(Platform)')) - <_PropsContents>$(_PropsContents.Replace('$$PYTHON_TARGET$$', '_GetPythonRuntimeFilesDependsOn$(MajorVersionNumber)$(MinorVersionNumber)_$(Platform)')) - <_ExistingContents Condition="Exists('$(IntermediateOutputPath)\python.props')">$([System.IO.File]::ReadAllText('$(IntermediateOutputPath)\python.props')) - - + -- 2.47.3