From c8bc5e7084e748e7d78a1c5a61bf284e6b1c199e Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Thu, 6 Dec 2018 21:09:20 -0800 Subject: [PATCH 1/6] bpo-34977: Add Windows App Store package (GH-10245) --- .azure-pipelines/windows-appx-test.yml | 67 ++ .azure-pipelines/windows-steps.yml | 2 + .gitattributes | 1 + Doc/make.bat | 4 + Lib/test/test_pathlib.py | 2 +- .../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/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/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 | 239 +++++++ PC/store_info.txt | 146 +++++ PCbuild/_tkinter.vcxproj | 6 + PCbuild/find_msbuild.bat | 10 + PCbuild/pcbuild.proj | 4 + PCbuild/pcbuild.sln | 72 +++ PCbuild/python_uwp.vcxproj | 86 +++ PCbuild/pythoncore.vcxproj | 15 + PCbuild/pythonw_uwp.vcxproj | 86 +++ 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 +- 46 files changed, 2698 insertions(+), 284 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 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 000000000000000..5f3fe6c9457890b --- /dev/null +++ b/.azure-pipelines/windows-appx-test.yml @@ -0,0 +1,67 @@ +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' + env: + IncludeUwp: true + + - 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/.azure-pipelines/windows-steps.yml b/.azure-pipelines/windows-steps.yml index c3175841a9b857d..cba00158ad131dd 100644 --- a/.azure-pipelines/windows-steps.yml +++ b/.azure-pipelines/windows-steps.yml @@ -13,6 +13,8 @@ steps: - script: PCbuild\build.bat -e $(buildOpt) displayName: 'Build CPython' + env: + IncludeUwp: true - script: python.bat -m test.pythoninfo displayName: 'Display build info' diff --git a/.gitattributes b/.gitattributes index 4a487c3c2a14e5b..16237bb2b3ac19f 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/Doc/make.bat b/Doc/make.bat index d28dae78e86d4f8..077a1bc74069a1b 100644 --- a/Doc/make.bat +++ b/Doc/make.bat @@ -115,12 +115,16 @@ goto end :build if not exist "%BUILDDIR%" mkdir "%BUILDDIR%" +rem We ought to move NEWS to %BUILDDIR%\NEWS and point +rem Sphinx at the right location. if exist ..\Misc\NEWS ( echo.Copying Misc\NEWS to build\NEWS + if not exist build mkdir build copy ..\Misc\NEWS build\NEWS > nul ) else if exist ..\Misc\NEWS.D ( if defined BLURB ( echo.Merging Misc/NEWS with %BLURB% + if not exist build mkdir build %BLURB% merge -f build\NEWS ) else ( echo.No Misc/NEWS file and Blurb is not available. diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 876eecccfd5fe34..d3fd4bd9e6b7efd 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1521,7 +1521,7 @@ def test_resolve_common(self): # 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/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 000000000000000..8e1a4ba84880361 --- /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 000000000000000..f00475c8da3129e --- /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 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 M + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 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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 xGE;KqWvT literal 0 HcmV?d00001 diff --git a/PC/icons/pythonwx44.png b/PC/icons/pythonwx44.png new file mode 100644 index 0000000000000000000000000000000000000000..e3b32a871f90a747f46d0732b5bdd0cfddebb448 GIT binary patch literal 2232 zcmV;p2uJscP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2u(>uK~z{r?OAD1 zRb?3VN6q-7X`24lkD8k4M}M-itZ-jb$!xW9$;^aQOf46X+!xv>EwjbYa>ETn5k$o; zLO`Hcgc*${n7OH0cQHX%PIslVz+G8w%-9#1dY3ACg*M?SN7H&8hT zuuSk^kT)D%U0uIv-$GNZ-Z0@OOU>P@GfLH1R}wwJF-07lot+I=j9PmXgToJC!23Ug z<~G>yu=jikhOIaxFR%-NE21!V*$$v=22k{}Hkfz0LRtAcK=CBtvgWy{sL0C{n_6hv z0J9nB2HK1nGlp#3xX~>}W^fF4ouZ!Lb@0~-`IsO1E9{+J?t`yhz3Mi{v<2Ie5U^wq z`p^5(eQ;A#6S!iCz)uq~eAQtDERDhw^S9xV;BSDc_qaCly3rW1`UnPv?ZfEsJs20h z6KArD0qbPIwhi3|4_>w(TjDZ->ID)Q2~g21K>iq@O@hL~uF*3%qjVZ@WfxEzslF=C zW+wc0;o>+TJ3#PE&jbHfyJPX`LAQscl;5^2%`XH+Tk9tNTxjFx`&XW-(%oud>CMLK z4LF~=Tc_jt`g$ZLCc435v6wpO38s$C2D+lNvlC+?5|EfvB^&w|wJeyGxceoDYK%{g zJ32bh-qDGgKUwQ==;Ru+h= zdEzWcoV_XA=ryRSz%^a_-Sr)FUI&LpsdoO{t&~&$2NsW(>M{jDHH&DB9>FRDZ z$7XBTS8y-fT*sa%lkmLR8~kQ$x7%?x{}NUo$iN$4p1|_m=W!;dLS;W*_uKJ?m~13l zYE{vva5XaB$NSH#>pePll^151y7?@Y?o4r{PbxR2&%hOX(AwJS8)M$U496s&RaPg7 z-X@8@7HgxDF<{|Ol9RV81;QjRQ`L3yVR*#Uck4lbyR}-?5qb1vY z?&jDNERdtt#bNl$7>85Vx#w2x!}t$(E2h&wi3aL~F{=p0Z{bAVmvvpo#tNYHJDghN zEKAk%rDvEeEZ^3;7Ya9({73V?YOjvDK1ZxPaXkNNorl* z2&pCm>3FkO$3(^DM#r*3?HVmWl$Iq^<<(!6F6Vi1`mE}=Sntr3&MMW{;kYM+hli_9 zPFZ!hBS5HJF7-~{!X^*A5~VJ4S-LH^-Wn?3cLWF*$I7b@2kPVH^Go!5pFYfYLvw~B zK*$-Qy?Vte;7^ye>yJrL9#rpKA1e;m%HKHkRepDtqyO!uTUhE4Q3SZvcypgsfuDn- zOED(00E(NZG3|4kQ5fXqts?C}uB2;$CI`6Q{dj#lIHS;9Q4Q1Anxq|2TwCUd_Io2tKxkmICxA2pw z5q=V-SXV@K!A>{`^R$MtSgBZ%#+8fYDRxfG7@e+vGt3>HR8Wi28;&WWM)(y`3BR#U z8Gd7(8sX<*mqFCPg^Q&kVpnL&m~xxyn39`1zTr3tAJpmkN0>`t`x%QCetn&~hJVn~ z-PjWMJ8bnfpuNxp7D0i?^F-j%dLj9iPEr&%WjuQWCFRG#%-rGJ?N~N6@A?B;{5}m{+=Q9+G5lSuW(O zWUW?8(^VcSkZe%)fn1mNyF^+>t3-e#+0Hbja9g*eN3F||8p&KC+tsc}qCmzK2Wy{!yL2}<&xG*3ige2#67t2)&7%&gwg2ci!0<~W zFXI~px4!X|85HcoOe$@a-!Bj#? z58e%UTS*m37?_%P)Ms;qcN^J7UjHi$3`Wnt19sp`u@wvq#kPWsq?VV-iLt&Ho_6Ey zhS+j=K=YdCQ+N35>D;Hg9EwwE&!t5SEOk$3AK}u#h z8yPHkx=?sXHvbhRY^V$+t%MXMZBUFtP%-@m(oWr-yHzhYWXr=bi zbHnZ86&X&ym;tMmUxB?a>?u`-p_po6yomO%uw-2A65~aGgNp+7xcXtGu!v7)SMG6pFmkyc=L=xm9OiNE+ z+sPeC#;Rqg3<304eOJxXm70wxFd8=kCNP#iS_k29MBKRncOl<77Ge?bx}@LkvB^mv zgqDA`5uTF&LlD5{(#RU&^6VXCIR;1i&=F|N5=xF<6RNw%bV zK643*GJ*V6za{MI#@KAs;6Pj7D}|ASUo{AP^_1(viwa4AG~m?&PSXbH6!cTv@ssW! znmIop-)Pe>EY@PFt?dl@)h4e_tH^UpEy=u zR*7D|CDbZUI-%FrTK2&4RGXjfpV)+Fk&z-aB?Y59FmV{S(n5krVq;_b?~wS*r@)y~ zVD?tW0asuqkR`N~*FI9cL9PO;bdm8jx}FTEt&O)@ttCGg(59yq3vd4bJWTE!kzWnI`skllB zupWi6nqs#qFVIdB9Xa&Gs028%$SAQm^x9H)w zlj_tp!I${(>9knW)wQ+U7v@89OxBvFhD`#0rZog?MllbF6q!p>6@@@x3& zOyyy+J&*}kEN)eAU$d5O0MAq1z+}K$X zA-MUHwRAH6SJS~22GgMzTLa-d%y`&#&s+VYHj>&X~Zshx%4^R~YvKrj1MczLEK@ttjFiZ?^N{3{7G;<;fJJ~q{1v1VWhiEMnQ^|;^};CB}9 zf~Pf!3@&CS)%4qV_!EUs)USp7bGeB;HN*#xLVd)A%+JY^5D^32M7v8%RFZD`FoB_m z{iV#>z_fOQ=QW(z0juzc{mcyvtPW4a|IQxL&1Thidy~Mjo4pieG0Pf{OncI`OZ)3i zC*C5R%5QbDz^CJ`GT!VTsig(=P##|KM(AIUU$2v=RW3_U5ykLmC?lrGdp%}KS<*QW z$%y*q@{Cg2+&New|V|rDv4BPWIMG`^ZJzQd19Y3<`Sn)s*GfZ!3F`JZ?ouM7yH!mKATP7cf|{x zSG{XYT}{RT!U8m=f%EL(wbj-xhAZRFP6WRb%~#JDLMKFn-=|JWRr>!RbNV-Dx~^sk z52#VAd=vKW=G>V1^v2`(V6v(9#L<3IeEMP=X!^k65iYJ(`*|p4Nu)3^`)X#4I_^*2Gy} zR=iat)+v<=%4K!RAHsm@!LzVahgQU+%oUxKCQG{d97u`%VDM!D_*Fsm2MrFdQEr9| zjWMqUb8Guo0_i4!oiq{9!RzE<`D`vIrSjE-Ys5fzs7gM@xX}=8C8FDdxO3yei5Mk` zrg>6<7Y;zv!pkp_*TO62a$%px*uy(Plb0zgofu5fl9*pO)@v8nqre*=?A}RejSv2C zxT7c2_BGi>V;z8z$(xZu3rJ;UK&N6D#mifJ%nyrk#b?_E&I3)5H0MY`S- zr!JF%ZW@;W)jqm|e1v4Myb5n3NN%U%FRndoMV~J0VG%4ZsacuKnjcqNFG&ED8)1u6 zOVG0g$eNS$mzbG-f`(z&0hs|$PZi~6(A6k6mA(;tNm^!voyivi2uAvwXh!TODw@Wo z!d;8~vldruNr#9V@8iY4p+ua{40M7@?UxVRct5)Uvc4O6kPc4YQO`lqb8}d?8|Qno8+IguFY}*tiSvcCTY@Qg zJQ7keb4jlmgsM4gh@T~){FqlfkSlF2?Y#A?BWW+ zPz>y0rVFk*(&-fsOqEbfmZ@<`3lGFaKtNRew=PNyF!{os;^5@aTbwiOK`0MSzjkfq9!e$4Q4IF_t+;mUhy8|YOwp++hpEc7QGQzG~eM>KJS)5oA^F# z@Vn>~wNvz}iuCUN(v6t2>q!@3I1QoshJNWE?2<01^ir82n{OQ4OI~vANm(y#TT%g0 z5WT+Zph%)`pLMzbUTD6IuAU=Rur?d(>OBi+tcbMrP`o_eq!;<{sTlX1q-KhEij|yZ zu5855>(|u5ae3%`?1!Rs|DTFk+J>1Omt)3c;(==Fq&O>}t%3V5{noDL`|NdWT<_+W zUjP1s#s4*R-cz|);Q!5ObDhnW3^7}3boz&of|QPqj?=6Q#xm8}roB$5!l18d#SM*2 zc-jlSFUBJ|uK!5D+z0r1-Pto}33^hwWUX^kf%xS(kvkSm)Q-+uQe| z$C{W}R4BO9jKDGZt5`sh99lwqXYPoI>Tq9$ed`*$rqG9h=5tC{u1M7 z{=5VSJ0L8#z0d^1J|6cbu=7Uue1~)QRY&-;{ATB90V7Bt`6vARS~uRfbpFRlV5A|z z$L7p}DhJ1e@I35!Hk)@%(4B3E(8FB6nxTp?{}d&62ZXI{Np_tu-eFt#iX)x17z98M z&y|t3TKFYP&Y_3WasD;WWO(hnJVHbDVTp39n_ynrg+&JgT!V`YKMv5!mdrHN z{x5yFxh6bL--W@PgA-rL0JP>)Xrs?;e_(C2{Gf7pajg0|i+nNm@mf>tMJ&Pl6L&s+= zrZl$F=XwXm^&gI@Nq~Y550^WeAo#jG(2QS4VflB3;ieuy{cvJK57rM4LFRd<A!XU)Z}?_<&l(xq1;7=P%tm=`F14$q6f+Z~k=wx`c=~)x_j&$6B*q2P z^VmI~!)Pxv&d!)WHk1P5IHs?*fV*`iIU(m`CLT-}ey3++tV1s*R_G034 z`)ec|;9XY~deaUr7tuV1D?ThGlA43Oc$>j0oekNv@#vRwDU>FEa3e=)as_t?r{e4M zfa3}Swp@slp9dPe{u=mJh7gSNAKKr4;%_*CiG4&M4j)VJ08(jCmr^=Q|DktEnEyFC zL^^K#Q3kJ<$ggDtaV=U)Qwenc^92Td0Q-Yb2*5dfP$&5as zh}GTpK_rxtoF2zhTBsBkZ*YCsn*Ur(E~4$Ta777|6=qSa5#D}`$4+_l+GX@fuReSy zh1Q(bNeyahmPc0gfTYB9SX?Fv*VLN))^c#0^X4xRN# zm|!U&`0l~fD{pUyttf_-{qz=A_(VUyL!klbToiAR>bZvSe_vyAF@!|1z_!OIwouD{ zTDE-j+^!W&p1OzO)Nr$@nrP&atibsTvBkV}41Ns zYZxd5Wn#;Jdd|;X*vu62Z+08*e`(YJvNGM}%x?-WL;EQ*9v!(bMCi zF=s7LN#w-W8IdPMWwD`+3B%*T{{Jv19leTg_z@H$*T-h{Y&LHFkjwXkX(HXQ|IZ|` z9m^7>VqgG}jw?LPPrfdp;g}+! zw6!lFH-;haT>vVHsl;S+BGe;%*O3&i+0S$V&C4!iie(&KbK$BzlTLV?dHa~{uM6aZ z#_c#+fo~*U&b#zSgK9Ncug!kreSB6>MZ=%l2X!lC|Q5BWyNU>0ai zX4E`6sC(W&^piysU!S#HXZQWXm0F}<`<;v(_GKrRwc(+9CxUrUT`18URDEs zT+-K8&FH2>FhY^+iyXt|JZSTS=`@$r;W~kmx}OP*91|1cD#jWWV~ao&9pkEx!SOSD z}ajM}N|tYpmiIW#8vxXrGkun6XMu#ND$pewzjHDEdQ}+?RaQ%8ZM5 zl2XA&0(U6$OY8&`%MpDe5JD1ZD_*4_xqak=Foqtk(f#zpD z_9@>L^Wf{SOrTRS3Y)3Z?p|_g{RE(=HQ~+rdBT*4nF~D3Zge0HSevJ`R5p zz#1JLb&vWs>^ZEatw)x10He=gwc_S+Jco7pXY^N=tNB~x)%g%4le}a1AGE1lldu2b z@O2OPzTUV=5gP9_No%Gn@6Yq=H0Q zK3=ISAmGg?{{tsl7>H_I=*WGW_Jq+x8FHJ(BKgpC3-X|Vuex=Y48uvLr*}o8rkE;) z#+3K3kzk7$0?ig!f4U6Hn6KPj_^bQZq6v5-=E?y1gL9~47=~0HIm1iRx*|{dT(l_B1#7HMtD_!=6 ztkSCaotFpim?U{HA4gV+I~O%UD8Li%9U#+9njr{9_axO0^(dk#B{aLeH(wPlU{y@M z_M({;9IU;Gm8{HZ^`qJjrJ96iHW{(E>O~oDK0Y7 z$EZG*==<4Is}QG*t>fy-wU!GXyCkacpjB*NwF5q#TyP+7T>SdA z&&Ip4xGR+_Ek#h+J4zv1RZ-b}xk4~IDX!fgAN=6Pt_K&k<1gPvl1j}wRI#kru$NEI zXjQs`#J10;3qsL3@!kef<>6kiKaadx7A@dJTtz!^mFy=)rR+}|e#>?^c`|B7$DRA4 zC;U2yYd(R3FVJ7fh-`e~jm-Rhtwcar$*smnp8IYDO6gSSva+qHZFa4>wIQm`H=GK999Kmxe6;;pjA|LR8-XctfL+X zMe|=oU`so4D87W|isHGvx_vNoL3Ocaq2Fs^=X#*s&~d-S#sM%GD{n@eR>~`jR|xFwr3o%cz#!wT{jnmU$Ub>9{3vAFOrsl%d<^J6ypqklT zb)i2(yL<8iQ25aS5{m5Sz%Rzk-hhBtxTrLkcWi$qiL2{9iqHuMq6PzL%~R~vywZNS zc&^gITVqdIvKB*O!pZx8-?ySNmRlspiF3>9)l=5hOtc8!_bw>ql!;dT6D99G-5F)T zP9~|H3}lo{kx`aXr{_XYfQa^P{|YVJ-)4HJq@sE_xs(T-AXsoPp0MY?YNGc|)D;PN zJ?k*v*~pRe1QS`&!S&mDLj&s<3c7`YTt(>>u4a_zM(_`pPw!;Hl~G0#p%$UMxJ!q! zA#xf_P&!qY@TSt16PQTswN)3j^IJ98?g62vA!=rQI!=p&3z6&-UO*f$UZ%1|>raK| zUnzR(ipp!^5hGG!L3h8K%h9+%)?m_9T2>25N!Yc#*gKTF@Y36MpHbbj=~;AQ*?c=} zO%gT8iiO660s(k4QeYmLACBP!C6%6as-6h4F0_VNe%o$KagREzq};@fe1{EYLH3MR zkYV_hf*YSrhL~Ob`il++jo$p`U~13GaCf~sM2+u#yAn0Lw^&LHn#n4g%Lq5W^d#T- zf!UhP&4zr33#Jl2D0e$G&B-AdrV~_#48dcN3Su4}9^Q94jX#&uttCq$;U~@b#fN&6UYiueHXv|rKU40E7?M~)pl=#Hwo(ECo`K@&jb-$ z95lbc%=I9B2ZX0FX3*;yl`TH?T-U7hVw``Lg}o$p0I9gIJ*gYo)o;C1@r1n8lqIc> zxScqQIGH$!PI~W6ZL7*{*O8D;ScV51oM3H= z(SZl#;q6-lVQP}p<|?&P&d8Di1+NoU_VyDv|7P^S<-g9cIU(38+sam9?z$+0`LxSz z#7ZdF6Iir-9|EDL(TS?^0uJx)e}H=3J_D9ApI)ULi{Hsq$e$EC0I_dd=ji=zitQ}R z08Xjjx55TTsG3+vWp&YZEL1o3?ikwuu`JCSjLot(l%LDOeItUvOgN@$gPR0-dYb7{tAnZ)_x)GWr*Et>n^7@>wn z#5BU$)u%fgef{wW2JbSUNep(O?g)4k-5UW#8&*5EH@=El2=Q|o&Css`s)Y`?3_jg= zWZN4xd!rkMOOIM8dSa6&;Vx7jTYhlLF1Fjx-Z8}NlNUdaeqaia++{y-uuk#p$d_gK zO`8G#QQS+boM;SQ zJ5oy++afTGaj!sC5TOnv%gD2Ep(@i*J oCZFiH{uFN{z=r(sT)ueYY%bG(@1#t;{{#S|Agd}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 HcmV?d00001 diff --git a/PC/icons/pythonx50.png b/PC/icons/pythonx50.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc3aecd0242b9670e3633faeb10201140793f83 GIT binary patch literal 2190 zcmV;92yyp`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 HcmV?d00001 diff --git a/PC/layout/__init__.py b/PC/layout/__init__.py new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/PC/layout/__main__.py b/PC/layout/__main__.py new file mode 100644 index 000000000000000..f7aa1e6d261f4a2 --- /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 000000000000000..82d0536ca920b38 --- /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 000000000000000..e69de29bb2d1d64 diff --git a/PC/layout/support/appxmanifest.py b/PC/layout/support/appxmanifest.py new file mode 100644 index 000000000000000..c5dda70c7ef85c2 --- /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 000000000000000..43121187ed180c8 --- /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 000000000000000..88ea410b340ea53 --- /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 000000000000000..6e9b49fe42df61b --- /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 000000000000000..47f727c057844c8 --- /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 000000000000000..30869b949a1c33e --- /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 000000000000000..76d9e34e1f46a53 --- /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 000000000000000..369a923ce139fb3 --- /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 000000000000000..3a047d215058349 --- /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 3da3445f5fc4c7a..92987af7138dd38 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 000000000000000..b015abd59e66a10 --- /dev/null +++ b/PC/python_uwp.cpp @@ -0,0 +1,239 @@ +/* Main program when embedded in a UWP application on Windows */ + +#include "Python.h" +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#ifdef PYTHON_UWP_SUPPORTED +#include +#include +#else +#include +#endif + +#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() +{ +#ifdef PYTHON_UWP_SUPPORTED + 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 (...) { + } +#endif +} + +static const wchar_t * +get_argv0(const wchar_t *argv0) +{ +#ifdef PYTHON_UWP_SUPPORTED + winrt::hstring installPath; +#else + std::wstring installPath; +#endif + 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; + } + +#ifdef PYTHON_UWP_SUPPORTED + try { + const auto package = winrt::Windows::ApplicationModel::Package::Current(); + if (package) { + const auto install = package.InstalledLocation(); + if (install) { + installPath = install.Path(); + } + } + } + catch (...) { + } +#endif + + 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 000000000000000..ed40a918e2e7845 --- /dev/null +++ b/PC/store_info.txt @@ -0,0 +1,146 @@ +# Overview + +NOTE: This file requires more content. + +Since Python 3.8.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.8 + +## 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.8 interpreter and runtime. + +## Copyright Trademark Information + +(c) Python Software Foundation + +## Additional License Terms + +Visit https://docs.python.org/3.8/license.html for latest license terms. + +PSF LICENSE AGREEMENT FOR PYTHON 3.8 + +1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and + the Individual or Organization ("Licensee") accessing and otherwise using Python + 3.8 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.8 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.8 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.8 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.8. + +4. PSF is making Python 3.8 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.8 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.8 + FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF + MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.8, 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.8, 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 95e3cd50eca5af3..bd61c0d4f689e50 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 57512a01927eae0..a2810f09c45e218 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 77341b44a1ee812..befaa1fed76b007 100644 --- a/PCbuild/pcbuild.proj +++ b/PCbuild/pcbuild.proj @@ -9,6 +9,7 @@ true true true + false @@ -52,6 +53,8 @@ + + @@ -70,6 +73,7 @@ + diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln index 59b3861ed4066db..c212d9f8f32c191 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_uwp.vcxproj b/PCbuild/python_uwp.vcxproj new file mode 100644 index 000000000000000..af187dd4df30b09 --- /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 4ae2d692eee12de..78ec9a16efa7eb9 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -479,4 +479,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 000000000000000..79e105877fbeb26 --- /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/Tools/msi/buildrelease.bat b/Tools/msi/buildrelease.bat index 4178981195ee674..45e189b537f6935 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 000000000000000..e32bd76a37f135a --- /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 000000000000000..70741439869a711 --- /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 214111734219198..125a434e51f4147 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 58f3b15ef8524e3..000000000000000 --- 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 000000000000000..81a74d3679d78e9 --- /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 000000000000000..6668eb33a2d1354 --- /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 9843bc97ccdc1e3..e093a6d0bd76b0f 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')) - - + From 7d7ddbc41bd30690de8c6227cebc41c4e0f3cf88 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 10 Dec 2018 12:20:05 -0800 Subject: [PATCH 2/6] Update Windows installation section to cover different installers --- Doc/using/windows.rst | 348 +++++++++++++++++++++++++++--------------- 1 file changed, 227 insertions(+), 121 deletions(-) diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index 296d51b5a5871be..2bab988177017f4 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -12,9 +12,6 @@ This document aims to give an overview of Windows-specific behaviour you should know about when using Python on Microsoft Windows. -Installing Python -================= - Unlike most Unix systems and services, Windows does not include a system supported installation of Python. To make Python available, the CPython team has compiled Windows installers (MSI packages) with every `release @@ -24,15 +21,38 @@ core interpreter and library being used by a single user. The installer is also able to install for all users of a single machine, and a separate ZIP file is available for application-local distributions. -Supported Versions ------------------- - As specified in :pep:`11`, a Python release only supports a Windows platform while Microsoft considers the platform under extended support. This means that Python |version| supports Windows Vista and newer. If you require Windows XP support then please install Python 3.4. -Installation Steps +There are a number of different installers available for Windows, each with +certain benefits and downsides. + +:ref:`windows-full` contains all components and is the best option for +developers using Python for their projects. It may require administrative +privileges to install. + +:ref:`windows-store` is a simple installation of Python that is suitable for +running scripts and packages, and using IDLE or other development environments. +It requires Windows 10, but can be safely installed without corrupting other +programs. It also provides many convenient commands for launching Python and +its tools. + +:ref:`windows-nuget` are lightweight installations intended for continuous +integration systems. It can be used to build Python packages or run scripts, +but is not updateable and has no user interface tools. + +:ref:`windows-embeddable` is a minimal package of Python suitable for +embedding into a larger application. + + +.. _windows-full: + +The full installer +================== + +Installation steps ------------------ Four Python |version| installers are available for download - two each for the @@ -264,39 +284,199 @@ settings and replace any that have been removed or modified. "Uninstall" will remove Python entirely, with the exception of the :ref:`launcher`, which has its own entry in Programs and Features. -Other Platforms ---------------- -With ongoing development of Python, some platforms that used to be supported -earlier are no longer supported (due to the lack of users or developers). -Check :pep:`11` for details on all unsupported platforms. +.. _windows-store: -* `Windows CE `_ is still supported. -* The `Cygwin `_ installer offers to install the Python - interpreter as well (cf. `Cygwin package source - `_, `Maintainer releases - `_) +The Microsoft Store package +=========================== -See `Python for Windows `_ -for detailed information about platforms with pre-compiled installers. +.. versionadded:: 3.7.2 -.. seealso:: +.. note:: + The Microsoft Store package is currently considered unstable while its + interactions with other tools and other copies of Python are evaluated. + While Python itself is stable, this installation method may change its + behavior and capabilities during Python 3.7 releases. - `Python on XP `_ - "7 Minutes to "Hello World!"" - by Richard Dooling, 2006 +The Microsoft Store package is an easily installable Python interpreter that +is intended mainly for interactive use, for example, by students. - `Installing on Windows `_ - in "`Dive into Python: Python from novice to pro - `_" - by Mark Pilgrim, 2004, - ISBN 1-59059-356-1 +To install the package, ensure you have the latest Windows 10 updates and +search the Microsoft Store app for "Python |version|". Ensure that the app +you select is published by the Python Software Foundation, and install it. - `For Windows users `_ - in "Installing Python" - in "`A Byte of Python `_" - by Swaroop C H, 2003 +.. warning:: + Python will always be available for free on the Microsoft Store. If you + are asked to pay for it, you have not selected the correct package. + +After installation, Python may be launched by finding it in Start. +Alternatively, it will be available from any Command Prompt or PowerShell +session by typing ``python``. Further, pip and IDLE may be used by typing +``pip`` or ``idle``. IDLE can also be found in Start. + +All three commands are also available with version number suffixes, for +example, as ``python3.exe`` and ``python3.x.exe`` as well as +``python.exe`` (where ``3.x`` is the specific version you want to launch, +such as |version|). + +Virtual environments can be created with ``python -m venv`` and activated +and used as normal. + +If you have installed another version of Python and added it to your +``PATH`` variable, it will be available as ``python.exe`` rather than the +one from the Microsoft Store. To access the new installation, use +``python3.exe`` or ``python3.x.exe``. + +To remove Python, open Settings and use Apps and Features, or else find +Python in Start and right-click to select Uninstall. Uninstalling will +remove all packages you installed directly into the package, but will not +remove any virtual environments + +Known Issues +------------ + +Currently, the ``py.exe`` launcher cannot be used to start Python when it +has been installed from the Microsoft Store. + +Because of restrictions on Microsoft Store apps, Python scripts may not have +full write access to shared locations such as ``TEMP`` and the registry. +Instead, it will write to a private copy. If your scripts must modify the +shared locations, you will need to install the full installer. + + +.. _windows-nuget: + +The nuget.org packages +====================== + +.. versionadded:: 3.5.2 + +The nuget.org package is a reduced size Python environment intended for use on +continuous integration and build systems that do not have a system-wide +install of Python. While nuget is "the package manager for .NET", it also works +perfectly fine for packages containing build-time tools. + +Visit `nuget.org `_ for the most up-to-date information +on using nuget. What follows is a summary that is sufficient for Python +developers. + +The ``nuget.exe`` command line tool may be downloaded directly from +``https://aka.ms/nugetclidl``, for example, using curl or PowerShell. With the +tool, the latest version of Python for 64-bit or 32-bit machines is installed +using:: + + nuget.exe install python -ExcludeVersion -OutputDirectory . + nuget.exe install pythonx86 -ExcludeVersion -OutputDirectory . + +To select a particular version, add a ``-Version 3.x.y``. The output directory +may be changed from ``.``, and the package will be installed into a +subdirectory. By default, the subdirectory is named the same as the package, +and without the ``-ExcludeVersion`` option this name will include the specific +version installed. Inside the subdirectory is a ``tools`` directory that +contains the Python installation:: + + # Without -ExcludeVersion + > .\python.3.5.2\tools\python.exe -V + Python 3.5.2 + + # With -ExcludeVersion + > .\python\tools\python.exe -V + Python 3.5.2 + +In general, nuget packages are not upgradeable, and newer versions should be +installed side-by-side and referenced using the full path. Alternatively, +delete the package directory manually and install it again. Many CI systems +will do this automatically if they do not preserve files between builds. + +Alongside the ``tools`` directory is a ``build\\native`` directory. This +contains a MSBuild properties file ``python.props`` that can be used in a +C++ project to reference the Python install. Including the settings will +automatically use the headers and import libraries in your build. + +The package information pages on nuget.org are +`www.nuget.org/packages/python `_ +for the 64-bit version and `www.nuget.org/packages/pythonx86 +`_ for the 32-bit version. + + +.. _windows-embeddable: + +The embeddable package +====================== + +.. versionadded:: 3.5 + +The embedded distribution is a ZIP file containing a minimal Python environment. +It is intended for acting as part of another application, rather than being +directly accessed by end-users. + +When extracted, the embedded distribution is (almost) fully isolated from the +user's system, including environment variables, system registry settings, and +installed packages. The standard library is included as pre-compiled and +optimized ``.pyc`` files in a ZIP, and ``python3.dll``, ``python37.dll``, +``python.exe`` and ``pythonw.exe`` are all provided. Tcl/tk (including all +dependants, such as Idle), pip and the Python documentation are not included. + +.. note:: + + The embedded distribution does not include the `Microsoft C Runtime + `_ and it is + the responsibility of the application installer to provide this. The + runtime may have already been installed on a user's system previously or + automatically via Windows Update, and can be detected by finding + ``ucrtbase.dll`` in the system directory. + +Third-party packages should be installed by the application installer alongside +the embedded distribution. Using pip to manage dependencies as for a regular +Python installation is not supported with this distribution, though with some +care it may be possible to include and use pip for automatic updates. In +general, third-party packages should be treated as part of the application +("vendoring") so that the developer can ensure compatibility with newer +versions before providing updates to users. + +The two recommended use cases for this distribution are described below. + +Python Application +------------------ + +An application written in Python does not necessarily require users to be aware +of that fact. The embedded distribution may be used in this case to include a +private version of Python in an install package. Depending on how transparent it +should be (or conversely, how professional it should appear), there are two +options. + +Using a specialized executable as a launcher requires some coding, but provides +the most transparent experience for users. With a customized launcher, there are +no obvious indications that the program is running on Python: icons can be +customized, company and version information can be specified, and file +associations behave properly. In most cases, a custom launcher should simply be +able to call ``Py_Main`` with a hard-coded command line. + +The simpler approach is to provide a batch file or generated shortcut that +directly calls the ``python.exe`` or ``pythonw.exe`` with the required +command-line arguments. In this case, the application will appear to be Python +and not its actual name, and users may have trouble distinguishing it from other +running Python processes or file associations. + +With the latter approach, packages should be installed as directories alongside +the Python executable to ensure they are available on the path. With the +specialized launcher, packages can be located in other locations as there is an +opportunity to specify the search path before launching the application. + +Embedding Python +---------------- + +Applications written in native code often require some form of scripting +language, and the embedded Python distribution can be used for this purpose. In +general, the majority of the application is in native code, and some part will +either invoke ``python.exe`` or directly use ``python3.dll``. For either case, +extracting the embedded distribution to a subdirectory of the application +installation is sufficient to provide a loadable Python interpreter. + +As with the application use, packages can be installed to any location as there +is an opportunity to specify search paths before initializing the interpreter. +Otherwise, there is no fundamental differences between using the embedded +distribution and a regular installation. Alternative bundles @@ -441,6 +621,8 @@ appropriate version of Python. It will prefer per-user installations over system-wide ones, and orders by language version rather than using the most recently installed version. +The launcher was originally specified in :pep:`397`. + Getting started --------------- @@ -922,95 +1104,19 @@ For extension modules, consult :ref:`building-on-windows`. by Trent Apted et al, 2007 -Embedded Distribution -===================== - -.. versionadded:: 3.5 - -The embedded distribution is a ZIP file containing a minimal Python environment. -It is intended for acting as part of another application, rather than being -directly accessed by end-users. - -When extracted, the embedded distribution is (almost) fully isolated from the -user's system, including environment variables, system registry settings, and -installed packages. The standard library is included as pre-compiled and -optimized ``.pyc`` files in a ZIP, and ``python3.dll``, ``python37.dll``, -``python.exe`` and ``pythonw.exe`` are all provided. Tcl/tk (including all -dependants, such as Idle), pip and the Python documentation are not included. - -.. note:: - - The embedded distribution does not include the `Microsoft C Runtime - `_ and it is - the responsibility of the application installer to provide this. The - runtime may have already been installed on a user's system previously or - automatically via Windows Update, and can be detected by finding - ``ucrtbase.dll`` in the system directory. - -Third-party packages should be installed by the application installer alongside -the embedded distribution. Using pip to manage dependencies as for a regular -Python installation is not supported with this distribution, though with some -care it may be possible to include and use pip for automatic updates. In -general, third-party packages should be treated as part of the application -("vendoring") so that the developer can ensure compatibility with newer -versions before providing updates to users. - -The two recommended use cases for this distribution are described below. - -Python Application ------------------- - -An application written in Python does not necessarily require users to be aware -of that fact. The embedded distribution may be used in this case to include a -private version of Python in an install package. Depending on how transparent it -should be (or conversely, how professional it should appear), there are two -options. - -Using a specialized executable as a launcher requires some coding, but provides -the most transparent experience for users. With a customized launcher, there are -no obvious indications that the program is running on Python: icons can be -customized, company and version information can be specified, and file -associations behave properly. In most cases, a custom launcher should simply be -able to call ``Py_Main`` with a hard-coded command line. - -The simpler approach is to provide a batch file or generated shortcut that -directly calls the ``python.exe`` or ``pythonw.exe`` with the required -command-line arguments. In this case, the application will appear to be Python -and not its actual name, and users may have trouble distinguishing it from other -running Python processes or file associations. - -With the latter approach, packages should be installed as directories alongside -the Python executable to ensure they are available on the path. With the -specialized launcher, packages can be located in other locations as there is an -opportunity to specify the search path before launching the application. - -Embedding Python ----------------- - -Applications written in native code often require some form of scripting -language, and the embedded Python distribution can be used for this purpose. In -general, the majority of the application is in native code, and some part will -either invoke ``python.exe`` or directly use ``python3.dll``. For either case, -extracting the embedded distribution to a subdirectory of the application -installation is sufficient to provide a loadable Python interpreter. - -As with the application use, packages can be installed to any location as there -is an opportunity to specify search paths before initializing the interpreter. -Otherwise, there is no fundamental differences between using the embedded -distribution and a regular installation. - -Other resources +Other Platforms =============== -.. seealso:: - - `Python Programming On Win32 `_ - "Help for Windows Programmers" - by Mark Hammond and Andy Robinson, O'Reilly Media, 2000, - ISBN 1-56592-621-8 +With ongoing development of Python, some platforms that used to be supported +earlier are no longer supported (due to the lack of users or developers). +Check :pep:`11` for details on all unsupported platforms. - `A Python for Windows Tutorial `_ - by Amanda Birmingham, 2004 +* `Windows CE `_ is still supported. +* The `Cygwin `_ installer offers to install the Python + interpreter as well (cf. `Cygwin package source + `_, `Maintainer releases + `_) - :pep:`397` - Python launcher for Windows - The proposal for the launcher to be included in the Python distribution. +See `Python for Windows `_ +for detailed information about platforms with pre-compiled installers. From 138faabcd89b2b5db476af8fd749341137ab23f3 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 10 Dec 2018 12:27:10 -0800 Subject: [PATCH 3/6] Fixes double backslash --- Doc/using/windows.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index 2bab988177017f4..9360bdb32bb60aa 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -388,7 +388,7 @@ installed side-by-side and referenced using the full path. Alternatively, delete the package directory manually and install it again. Many CI systems will do this automatically if they do not preserve files between builds. -Alongside the ``tools`` directory is a ``build\\native`` directory. This +Alongside the ``tools`` directory is a ``build\native`` directory. This contains a MSBuild properties file ``python.props`` that can be used in a C++ project to reference the Python install. Including the settings will automatically use the headers and import libraries in your build. From 4508e854858367bc5db71802ef0e58c9541929f1 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 10 Dec 2018 12:29:52 -0800 Subject: [PATCH 4/6] Exclude UWP launchers when not generating APPX layout --- PC/layout/main.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/PC/layout/main.py b/PC/layout/main.py index 82d0536ca920b38..217b2b096e07685 100644 --- a/PC/layout/main.py +++ b/PC/layout/main.py @@ -135,12 +135,16 @@ def in_build(f, dest="", new_name=None): 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") + if ns.include_appxmanifest: + yield from in_build("python_uwp.exe", new_name="python") + yield from in_build("pythonw_uwp.exe", new_name="pythonw") + else: + yield from in_build("python.exe", new_name="python") + yield from in_build("pythonw.exe", new_name="pythonw") yield from in_build(PYTHON_DLL_NAME) - if ns.include_launchers: + if ns.include_launchers and ns.include_appxmanifest: if ns.include_pip: yield from in_build("python_uwp.exe", new_name="pip") if ns.include_idle: From b41de62433f8558b9c994a3bc624ddc33ddcfaa1 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 10 Dec 2018 13:53:39 -0800 Subject: [PATCH 5/6] Improve wording in new docs --- Doc/using/windows.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index 9360bdb32bb60aa..0165fff09cc50df 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -30,8 +30,7 @@ There are a number of different installers available for Windows, each with certain benefits and downsides. :ref:`windows-full` contains all components and is the best option for -developers using Python for their projects. It may require administrative -privileges to install. +developers using Python for any kind of project. :ref:`windows-store` is a simple installation of Python that is suitable for running scripts and packages, and using IDLE or other development environments. @@ -329,8 +328,8 @@ one from the Microsoft Store. To access the new installation, use To remove Python, open Settings and use Apps and Features, or else find Python in Start and right-click to select Uninstall. Uninstalling will -remove all packages you installed directly into the package, but will not -remove any virtual environments +remove all packages you installed directly into this Python installation, but +will not remove any virtual environments Known Issues ------------ From 8272d131dd07c6644394e8d83f051eb38d47332c Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 10 Dec 2018 16:27:49 -0800 Subject: [PATCH 6/6] Fix incorrect reference to 3.8.2 --- PC/store_info.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PC/store_info.txt b/PC/store_info.txt index ed40a918e2e7845..89f36998370fc9e 100644 --- a/PC/store_info.txt +++ b/PC/store_info.txt @@ -2,7 +2,7 @@ NOTE: This file requires more content. -Since Python 3.8.2, releases have been made through the Microsoft Store +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