PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /usr/local/nagios/venv/lib/python3.13/site-packages/setuptools
Viewing File: /usr/local/nagios/venv/lib/python3.13/site-packages/setuptools/msvc.py
""" Environment info about Microsoft Compilers. >>> getfixture('windows_only') >>> ei = EnvironmentInfo('amd64') """ from __future__ import annotations import contextlib import itertools import json import os import os.path import platform from typing import TYPE_CHECKING, TypedDict from more_itertools import unique_everseen import distutils.errors if TYPE_CHECKING: from typing_extensions import LiteralString, NotRequired # https://github.com/python/mypy/issues/8166 if not TYPE_CHECKING and platform.system() == 'Windows': import winreg from os import environ else: # Mock winreg and environ so the module can be imported on this platform. class winreg: HKEY_USERS = None HKEY_CURRENT_USER = None HKEY_LOCAL_MACHINE = None HKEY_CLASSES_ROOT = None environ: dict[str, str] = dict() class PlatformInfo: """ Current and Target Architectures information. Parameters ---------- arch: str Target architecture. """ current_cpu = environ.get('processor_architecture', '').lower() def __init__(self, arch) -> None: self.arch = arch.lower().replace('x64', 'amd64') @property def target_cpu(self): """ Return Target CPU architecture. Return ------ str Target CPU """ return self.arch[self.arch.find('_') + 1 :] def target_is_x86(self): """ Return True if target CPU is x86 32 bits.. Return ------ bool CPU is x86 32 bits """ return self.target_cpu == 'x86' def current_is_x86(self): """ Return True if current CPU is x86 32 bits.. Return ------ bool CPU is x86 32 bits """ return self.current_cpu == 'x86' def current_dir(self, hidex86=False, x64=False) -> str: """ Current platform specific subfolder. Parameters ---------- hidex86: bool return '' and not '\x86' if architecture is x86. x64: bool return '\x64' and not '\amd64' if architecture is amd64. Return ------ str subfolder: '\target', or '' (see hidex86 parameter) """ return ( '' if (self.current_cpu == 'x86' and hidex86) else r'\x64' if (self.current_cpu == 'amd64' and x64) else rf'\{self.current_cpu}' ) def target_dir(self, hidex86=False, x64=False) -> str: r""" Target platform specific subfolder. Parameters ---------- hidex86: bool return '' and not '\x86' if architecture is x86. x64: bool return '\x64' and not '\amd64' if architecture is amd64. Return ------ str subfolder: '\current', or '' (see hidex86 parameter) """ return ( '' if (self.target_cpu == 'x86' and hidex86) else r'\x64' if (self.target_cpu == 'amd64' and x64) else rf'\{self.target_cpu}' ) def cross_dir(self, forcex86=False): r""" Cross platform specific subfolder. Parameters ---------- forcex86: bool Use 'x86' as current architecture even if current architecture is not x86. Return ------ str subfolder: '' if target architecture is current architecture, '\current_target' if not. """ current = 'x86' if forcex86 else self.current_cpu return ( '' if self.target_cpu == current else self.target_dir().replace('\\', f'\\{current}_') ) class RegistryInfo: """ Microsoft Visual Studio related registry information. Parameters ---------- platform_info: PlatformInfo "PlatformInfo" instance. """ HKEYS = ( winreg.HKEY_USERS, winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CLASSES_ROOT, ) def __init__(self, platform_info) -> None: self.pi = platform_info @property def visualstudio(self) -> str: """ Microsoft Visual Studio root registry key. Return ------ str Registry key """ return 'VisualStudio' @property def sxs(self): """ Microsoft Visual Studio SxS registry key. Return ------ str Registry key """ return os.path.join(self.visualstudio, 'SxS') @property def vc(self): """ Microsoft Visual C++ VC7 registry key. Return ------ str Registry key """ return os.path.join(self.sxs, 'VC7') @property def vs(self): """ Microsoft Visual Studio VS7 registry key. Return ------ str Registry key """ return os.path.join(self.sxs, 'VS7') @property def vc_for_python(self) -> str: """ Microsoft Visual C++ for Python registry key. Return ------ str Registry key """ return r'DevDiv\VCForPython' @property def microsoft_sdk(self) -> str: """ Microsoft SDK registry key. Return ------ str Registry key """ return 'Microsoft SDKs' @property def windows_sdk(self): """ Microsoft Windows/Platform SDK registry key. Return ------ str Registry key """ return os.path.join(self.microsoft_sdk, 'Windows') @property def netfx_sdk(self): """ Microsoft .NET Framework SDK registry key. Return ------ str Registry key """ return os.path.join(self.microsoft_sdk, 'NETFXSDK') @property def windows_kits_roots(self) -> str: """ Microsoft Windows Kits Roots registry key. Return ------ str Registry key """ return r'Windows Kits\Installed Roots' def microsoft(self, key, x86=False): """ Return key in Microsoft software registry. Parameters ---------- key: str Registry key path where look. x86: str Force x86 software registry. Return ------ str Registry key """ node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node' return os.path.join('Software', node64, 'Microsoft', key) def lookup(self, key, name): """ Look for values in registry in Microsoft software registry. Parameters ---------- key: str Registry key path where look. name: str Value name to find. Return ------ str value """ key_read = winreg.KEY_READ openkey = winreg.OpenKey closekey = winreg.CloseKey ms = self.microsoft for hkey in self.HKEYS: bkey = None try: bkey = openkey(hkey, ms(key), 0, key_read) except OSError: if not self.pi.current_is_x86(): try: bkey = openkey(hkey, ms(key, True), 0, key_read) except OSError: continue else: continue try: return winreg.QueryValueEx(bkey, name)[0] except OSError: pass finally: if bkey: closekey(bkey) return None class SystemInfo: """ Microsoft Windows and Visual Studio related system information. Parameters ---------- registry_info: RegistryInfo "RegistryInfo" instance. vc_ver: float Required Microsoft Visual C++ version. """ # Variables and properties in this class use originals CamelCase variables # names from Microsoft source files for more easy comparison. WinDir = environ.get('WinDir', '') ProgramFiles = environ.get('ProgramFiles', '') ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles) def __init__(self, registry_info, vc_ver=None) -> None: self.ri = registry_info self.pi = self.ri.pi self.known_vs_paths = self.find_programdata_vs_vers() # Except for VS15+, VC version is aligned with VS version self.vs_ver = self.vc_ver = vc_ver or self._find_latest_available_vs_ver() def _find_latest_available_vs_ver(self): """ Find the latest VC version Return ------ float version """ reg_vc_vers = self.find_reg_vs_vers() if not (reg_vc_vers or self.known_vs_paths): raise distutils.errors.DistutilsPlatformError( 'No Microsoft Visual C++ version found' ) vc_vers = set(reg_vc_vers) vc_vers.update(self.known_vs_paths) return sorted(vc_vers)[-1] def find_reg_vs_vers(self): """ Find Microsoft Visual Studio versions available in registry. Return ------ list of float Versions """ ms = self.ri.microsoft vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs) vs_vers = [] for hkey, key in itertools.product(self.ri.HKEYS, vckeys): try: bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ) except OSError: continue with bkey: subkeys, values, _ = winreg.QueryInfoKey(bkey) for i in range(values): with contextlib.suppress(ValueError): ver = float(winreg.EnumValue(bkey, i)[0]) if ver not in vs_vers: vs_vers.append(ver) for i in range(subkeys): with contextlib.suppress(ValueError): ver = float(winreg.EnumKey(bkey, i)) if ver not in vs_vers: vs_vers.append(ver) return sorted(vs_vers) def find_programdata_vs_vers(self) -> dict[float, str]: r""" Find Visual studio 2017+ versions from information in "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances". Return ------ dict float version as key, path as value. """ vs_versions: dict[float, str] = {} instances_dir = r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances' try: hashed_names = os.listdir(instances_dir) except OSError: # Directory not exists with all Visual Studio versions return vs_versions for name in hashed_names: try: # Get VS installation path from "state.json" file state_path = os.path.join(instances_dir, name, 'state.json') with open(state_path, 'rt', encoding='utf-8') as state_file: state = json.load(state_file) vs_path = state['installationPath'] # Raises OSError if this VS installation does not contain VC os.listdir(os.path.join(vs_path, r'VC\Tools\MSVC')) # Store version and path vs_versions[self._as_float_version(state['installationVersion'])] = ( vs_path ) except (OSError, KeyError): # Skip if "state.json" file is missing or bad format continue return vs_versions @staticmethod def _as_float_version(version): """ Return a string version as a simplified float version (major.minor) Parameters ---------- version: str Version. Return ------ float version """ return float('.'.join(version.split('.')[:2])) @property def VSInstallDir(self): """ Microsoft Visual Studio directory. Return ------ str path """ # Default path default = os.path.join( self.ProgramFilesx86, f'Microsoft Visual Studio {self.vs_ver:0.1f}' ) # Try to get path from registry, if fail use default path return self.ri.lookup(self.ri.vs, f'{self.vs_ver:0.1f}') or default @property def VCInstallDir(self): """ Microsoft Visual C++ directory. Return ------ str path """ path = self._guess_vc() or self._guess_vc_legacy() if not os.path.isdir(path): msg = 'Microsoft Visual C++ directory not found' raise distutils.errors.DistutilsPlatformError(msg) return path def _guess_vc(self): """ Locate Visual C++ for VS2017+. Return ------ str path """ if self.vs_ver <= 14.0: return '' try: # First search in known VS paths vs_dir = self.known_vs_paths[self.vs_ver] except KeyError: # Else, search with path from registry vs_dir = self.VSInstallDir guess_vc = os.path.join(vs_dir, r'VC\Tools\MSVC') # Subdir with VC exact version as name try: # Update the VC version with real one instead of VS version vc_ver = os.listdir(guess_vc)[-1] self.vc_ver = self._as_float_version(vc_ver) return os.path.join(guess_vc, vc_ver) except (OSError, IndexError): return '' def _guess_vc_legacy(self): """ Locate Visual C++ for versions prior to 2017. Return ------ str path """ default = os.path.join( self.ProgramFilesx86, rf'Microsoft Visual Studio {self.vs_ver:0.1f}\VC', ) # Try to get "VC++ for Python" path from registry as default path reg_path = os.path.join(self.ri.vc_for_python, f'{self.vs_ver:0.1f}') python_vc = self.ri.lookup(reg_path, 'installdir') default_vc = os.path.join(python_vc, 'VC') if python_vc else default # Try to get path from registry, if fail use default path return self.ri.lookup(self.ri.vc, f'{self.vs_ver:0.1f}') or default_vc @property def WindowsSdkVersion(self) -> tuple[LiteralString, ...]: """ Microsoft Windows SDK versions for specified MSVC++ version. Return ------ tuple of str versions """ if self.vs_ver <= 9.0: return '7.0', '6.1', '6.0a' elif self.vs_ver == 10.0: return '7.1', '7.0a' elif self.vs_ver == 11.0: return '8.0', '8.0a' elif self.vs_ver == 12.0: return '8.1', '8.1a' elif self.vs_ver >= 14.0: return '10.0', '8.1' return () @property def WindowsSdkLastVersion(self): """ Microsoft Windows SDK last version. Return ------ str version """ return self._use_last_dir_name(os.path.join(self.WindowsSdkDir, 'lib')) @property def WindowsSdkDir(self) -> str | None: # noqa: C901 # is too complex (12) # FIXME """ Microsoft Windows SDK directory. Return ------ str path """ sdkdir: str | None = '' for ver in self.WindowsSdkVersion: # Try to get it from registry loc = os.path.join(self.ri.windows_sdk, f'v{ver}') sdkdir = self.ri.lookup(loc, 'installationfolder') if sdkdir: break if not sdkdir or not os.path.isdir(sdkdir): # Try to get "VC++ for Python" version from registry path = os.path.join(self.ri.vc_for_python, f'{self.vc_ver:0.1f}') install_base = self.ri.lookup(path, 'installdir') if install_base: sdkdir = os.path.join(install_base, 'WinSDK') if not sdkdir or not os.path.isdir(sdkdir): # If fail, use default new path for ver in self.WindowsSdkVersion: intver = ver[: ver.rfind('.')] path = rf'Microsoft SDKs\Windows Kits\{intver}' d = os.path.join(self.ProgramFiles, path) if os.path.isdir(d): sdkdir = d if not sdkdir or not os.path.isdir(sdkdir): # If fail, use default old path for ver in self.WindowsSdkVersion: path = rf'Microsoft SDKs\Windows\v{ver}' d = os.path.join(self.ProgramFiles, path) if os.path.isdir(d): sdkdir = d if not sdkdir: # If fail, use Platform SDK sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK') return sdkdir @property def WindowsSDKExecutablePath(self): """ Microsoft Windows SDK executable directory. Return ------ str path """ # Find WinSDK NetFx Tools registry dir name if self.vs_ver <= 11.0: netfxver = 35 arch = '' else: netfxver = 40 hidex86 = True if self.vs_ver <= 12.0 else False arch = self.pi.current_dir(x64=True, hidex86=hidex86).replace('\\', '-') fx = f'WinSDK-NetFx{netfxver}Tools{arch}' # list all possibles registry paths regpaths = [] if self.vs_ver >= 14.0: for ver in self.NetFxSdkVersion: regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)] for ver in self.WindowsSdkVersion: regpaths += [os.path.join(self.ri.windows_sdk, f'v{ver}A', fx)] # Return installation folder from the more recent path for path in regpaths: execpath = self.ri.lookup(path, 'installationfolder') if execpath: return execpath return None @property def FSharpInstallDir(self): """ Microsoft Visual F# directory. Return ------ str path """ path = os.path.join(self.ri.visualstudio, rf'{self.vs_ver:0.1f}\Setup\F#') return self.ri.lookup(path, 'productdir') or '' @property def UniversalCRTSdkDir(self): """ Microsoft Universal CRT SDK directory. Return ------ str path """ # Set Kit Roots versions for specified MSVC++ version vers = ('10', '81') if self.vs_ver >= 14.0 else () # Find path of the more recent Kit for ver in vers: sdkdir = self.ri.lookup(self.ri.windows_kits_roots, f'kitsroot{ver}') if sdkdir: return sdkdir or '' return None @property def UniversalCRTSdkLastVersion(self): """ Microsoft Universal C Runtime SDK last version. Return ------ str version """ return self._use_last_dir_name(os.path.join(self.UniversalCRTSdkDir, 'lib')) @property def NetFxSdkVersion(self): """ Microsoft .NET Framework SDK versions. Return ------ tuple of str versions """ # Set FxSdk versions for specified VS version return ( ('4.7.2', '4.7.1', '4.7', '4.6.2', '4.6.1', '4.6', '4.5.2', '4.5.1', '4.5') if self.vs_ver >= 14.0 else () ) @property def NetFxSdkDir(self): """ Microsoft .NET Framework SDK directory. Return ------ str path """ sdkdir = '' for ver in self.NetFxSdkVersion: loc = os.path.join(self.ri.netfx_sdk, ver) sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder') if sdkdir: break return sdkdir @property def FrameworkDir32(self): """ Microsoft .NET Framework 32bit directory. Return ------ str path """ # Default path guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework') # Try to get path from registry, if fail use default path return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw @property def FrameworkDir64(self): """ Microsoft .NET Framework 64bit directory. Return ------ str path """ # Default path guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64') # Try to get path from registry, if fail use default path return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw @property def FrameworkVersion32(self) -> tuple[str, ...]: """ Microsoft .NET Framework 32bit versions. Return ------ tuple of str versions """ return self._find_dot_net_versions(32) @property def FrameworkVersion64(self) -> tuple[str, ...]: """ Microsoft .NET Framework 64bit versions. Return ------ tuple of str versions """ return self._find_dot_net_versions(64) def _find_dot_net_versions(self, bits) -> tuple[str, ...]: """ Find Microsoft .NET Framework versions. Parameters ---------- bits: int Platform number of bits: 32 or 64. Return ------ tuple of str versions """ # Find actual .NET version in registry reg_ver = self.ri.lookup(self.ri.vc, f'frameworkver{bits}') dot_net_dir = getattr(self, f'FrameworkDir{bits}') ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or '' # Set .NET versions for specified MSVC++ version if self.vs_ver >= 12.0: return ver, 'v4.0' elif self.vs_ver >= 10.0: return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5' elif self.vs_ver == 9.0: return 'v3.5', 'v2.0.50727' elif self.vs_ver == 8.0: return 'v3.0', 'v2.0.50727' return () @staticmethod def _use_last_dir_name(path, prefix=''): """ Return name of the last dir in path or '' if no dir found. Parameters ---------- path: str Use dirs in this path prefix: str Use only dirs starting by this prefix Return ------ str name """ matching_dirs = ( dir_name for dir_name in reversed(os.listdir(path)) if os.path.isdir(os.path.join(path, dir_name)) and dir_name.startswith(prefix) ) return next(matching_dirs, None) or '' class _EnvironmentDict(TypedDict): include: str lib: str libpath: str path: str py_vcruntime_redist: NotRequired[str | None] class EnvironmentInfo: """ Return environment variables for specified Microsoft Visual C++ version and platform : Lib, Include, Path and libpath. This function is compatible with Microsoft Visual C++ 9.0 to 14.X. Script created by analysing Microsoft environment configuration files like "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ... Parameters ---------- arch: str Target architecture. vc_ver: float Required Microsoft Visual C++ version. If not set, autodetect the last version. vc_min_ver: float Minimum Microsoft Visual C++ version. """ # Variables and properties in this class use originals CamelCase variables # names from Microsoft source files for more easy comparison. def __init__(self, arch, vc_ver=None, vc_min_ver=0) -> None: self.pi = PlatformInfo(arch) self.ri = RegistryInfo(self.pi) self.si = SystemInfo(self.ri, vc_ver) if self.vc_ver < vc_min_ver: err = 'No suitable Microsoft Visual C++ version found' raise distutils.errors.DistutilsPlatformError(err) @property def vs_ver(self): """ Microsoft Visual Studio. Return ------ float version """ return self.si.vs_ver @property def vc_ver(self): """ Microsoft Visual C++ version. Return ------ float version """ return self.si.vc_ver @property def VSTools(self): """ Microsoft Visual Studio Tools. Return ------ list of str paths """ paths = [r'Common7\IDE', r'Common7\Tools'] if self.vs_ver >= 14.0: arch_subdir = self.pi.current_dir(hidex86=True, x64=True) paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow'] paths += [r'Team Tools\Performance Tools'] paths += [rf'Team Tools\Performance Tools{arch_subdir}'] return [os.path.join(self.si.VSInstallDir, path) for path in paths] @property def VCIncludes(self): """ Microsoft Visual C++ & Microsoft Foundation Class Includes. Return ------ list of str paths """ return [ os.path.join(self.si.VCInstallDir, 'Include'), os.path.join(self.si.VCInstallDir, r'ATLMFC\Include'), ] @property def VCLibraries(self): """ Microsoft Visual C++ & Microsoft Foundation Class Libraries. Return ------ list of str paths """ if self.vs_ver >= 15.0: arch_subdir = self.pi.target_dir(x64=True) else: arch_subdir = self.pi.target_dir(hidex86=True) paths = [f'Lib{arch_subdir}', rf'ATLMFC\Lib{arch_subdir}'] if self.vs_ver >= 14.0: paths += [rf'Lib\store{arch_subdir}'] return [os.path.join(self.si.VCInstallDir, path) for path in paths] @property def VCStoreRefs(self): """ Microsoft Visual C++ store references Libraries. Return ------ list of str paths """ if self.vs_ver < 14.0: return [] return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')] @property def VCTools(self): """ Microsoft Visual C++ Tools. Return ------ list of str paths When host CPU is ARM, the tools should be found for ARM. >>> getfixture('windows_only') >>> mp = getfixture('monkeypatch') >>> mp.setattr(PlatformInfo, 'current_cpu', 'arm64') >>> ei = EnvironmentInfo(arch='irrelevant') >>> paths = ei.VCTools >>> any('HostARM64' in path for path in paths) True """ si = self.si tools = [os.path.join(si.VCInstallDir, 'VCPackages')] forcex86 = True if self.vs_ver <= 10.0 else False arch_subdir = self.pi.cross_dir(forcex86) if arch_subdir: tools += [os.path.join(si.VCInstallDir, f'Bin{arch_subdir}')] if self.vs_ver == 14.0: path = f'Bin{self.pi.current_dir(hidex86=True)}' tools += [os.path.join(si.VCInstallDir, path)] elif self.vs_ver >= 15.0: host_id = self.pi.current_cpu.replace('amd64', 'x64').upper() host_dir = os.path.join('bin', f'Host{host_id}%s') tools += [ os.path.join(si.VCInstallDir, host_dir % self.pi.target_dir(x64=True)) ] if self.pi.current_cpu != self.pi.target_cpu: tools += [ os.path.join( si.VCInstallDir, host_dir % self.pi.current_dir(x64=True) ) ] else: tools += [os.path.join(si.VCInstallDir, 'Bin')] return tools @property def OSLibraries(self): """ Microsoft Windows SDK Libraries. Return ------ list of str paths """ if self.vs_ver <= 10.0: arch_subdir = self.pi.target_dir(hidex86=True, x64=True) return [os.path.join(self.si.WindowsSdkDir, f'Lib{arch_subdir}')] else: arch_subdir = self.pi.target_dir(x64=True) lib = os.path.join(self.si.WindowsSdkDir, 'lib') libver = self._sdk_subdir return [os.path.join(lib, f'{libver}um{arch_subdir}')] @property def OSIncludes(self): """ Microsoft Windows SDK Include. Return ------ list of str paths """ include = os.path.join(self.si.WindowsSdkDir, 'include') if self.vs_ver <= 10.0: return [include, os.path.join(include, 'gl')] else: if self.vs_ver >= 14.0: sdkver = self._sdk_subdir else: sdkver = '' return [ os.path.join(include, f'{sdkver}shared'), os.path.join(include, f'{sdkver}um'), os.path.join(include, f'{sdkver}winrt'), ] @property def OSLibpath(self): """ Microsoft Windows SDK Libraries Paths. Return ------ list of str paths """ ref = os.path.join(self.si.WindowsSdkDir, 'References') libpath = [] if self.vs_ver <= 9.0: libpath += self.OSLibraries if self.vs_ver >= 11.0: libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')] if self.vs_ver >= 14.0: libpath += [ ref, os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'), os.path.join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'), os.path.join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'), os.path.join( ref, 'Windows.Networking.Connectivity.WwanContract', '1.0.0.0' ), os.path.join( self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs', f'{self.vs_ver:0.1f}', 'References', 'CommonConfiguration', 'neutral', ), ] return libpath @property def SdkTools(self): """ Microsoft Windows SDK Tools. Return ------ list of str paths """ return list(self._sdk_tools()) def _sdk_tools(self): """ Microsoft Windows SDK Tools paths generator. Return ------ generator of str paths """ if self.vs_ver < 15.0: bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86' yield os.path.join(self.si.WindowsSdkDir, bin_dir) if not self.pi.current_is_x86(): arch_subdir = self.pi.current_dir(x64=True) path = f'Bin{arch_subdir}' yield os.path.join(self.si.WindowsSdkDir, path) if self.vs_ver in (10.0, 11.0): if self.pi.target_is_x86(): arch_subdir = '' else: arch_subdir = self.pi.current_dir(hidex86=True, x64=True) path = rf'Bin\NETFX 4.0 Tools{arch_subdir}' yield os.path.join(self.si.WindowsSdkDir, path) elif self.vs_ver >= 15.0: path = os.path.join(self.si.WindowsSdkDir, 'Bin') arch_subdir = self.pi.current_dir(x64=True) sdkver = self.si.WindowsSdkLastVersion yield os.path.join(path, f'{sdkver}{arch_subdir}') if self.si.WindowsSDKExecutablePath: yield self.si.WindowsSDKExecutablePath @property def _sdk_subdir(self): """ Microsoft Windows SDK version subdir. Return ------ str subdir """ ucrtver = self.si.WindowsSdkLastVersion return (f'{ucrtver}\\') if ucrtver else '' @property def SdkSetup(self): """ Microsoft Windows SDK Setup. Return ------ list of str paths """ if self.vs_ver > 9.0: return [] return [os.path.join(self.si.WindowsSdkDir, 'Setup')] @property def FxTools(self): """ Microsoft .NET Framework Tools. Return ------ list of str paths """ pi = self.pi si = self.si if self.vs_ver <= 10.0: include32 = True include64 = not pi.target_is_x86() and not pi.current_is_x86() else: include32 = pi.target_is_x86() or pi.current_is_x86() include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64' tools = [] if include32: tools += [ os.path.join(si.FrameworkDir32, ver) for ver in si.FrameworkVersion32 ] if include64: tools += [ os.path.join(si.FrameworkDir64, ver) for ver in si.FrameworkVersion64 ] return tools @property def NetFxSDKLibraries(self): """ Microsoft .Net Framework SDK Libraries. Return ------ list of str paths """ if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: return [] arch_subdir = self.pi.target_dir(x64=True) return [os.path.join(self.si.NetFxSdkDir, rf'lib\um{arch_subdir}')] @property def NetFxSDKIncludes(self): """ Microsoft .Net Framework SDK Includes. Return ------ list of str paths """ if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: return [] return [os.path.join(self.si.NetFxSdkDir, r'include\um')] @property def VsTDb(self): """ Microsoft Visual Studio Team System Database. Return ------ list of str paths """ return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')] @property def MSBuild(self): """ Microsoft Build Engine. Return ------ list of str paths """ if self.vs_ver < 12.0: return [] elif self.vs_ver < 15.0: base_path = self.si.ProgramFilesx86 arch_subdir = self.pi.current_dir(hidex86=True) else: base_path = self.si.VSInstallDir arch_subdir = '' path = rf'MSBuild\{self.vs_ver:0.1f}\bin{arch_subdir}' build = [os.path.join(base_path, path)] if self.vs_ver >= 15.0: # Add Roslyn C# & Visual Basic Compiler build += [os.path.join(base_path, path, 'Roslyn')] return build @property def HTMLHelpWorkshop(self): """ Microsoft HTML Help Workshop. Return ------ list of str paths """ if self.vs_ver < 11.0: return [] return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')] @property def UCRTLibraries(self): """ Microsoft Universal C Runtime SDK Libraries. Return ------ list of str paths """ if self.vs_ver < 14.0: return [] arch_subdir = self.pi.target_dir(x64=True) lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib') ucrtver = self._ucrt_subdir return [os.path.join(lib, f'{ucrtver}ucrt{arch_subdir}')] @property def UCRTIncludes(self): """ Microsoft Universal C Runtime SDK Include. Return ------ list of str paths """ if self.vs_ver < 14.0: return [] include = os.path.join(self.si.UniversalCRTSdkDir, 'include') return [os.path.join(include, f'{self._ucrt_subdir}ucrt')] @property def _ucrt_subdir(self): """ Microsoft Universal C Runtime SDK version subdir. Return ------ str subdir """ ucrtver = self.si.UniversalCRTSdkLastVersion return (f'{ucrtver}\\') if ucrtver else '' @property def FSharp(self): """ Microsoft Visual F#. Return ------ list of str paths """ if 11.0 > self.vs_ver > 12.0: return [] return [self.si.FSharpInstallDir] @property def VCRuntimeRedist(self) -> str | None: """ Microsoft Visual C++ runtime redistributable dll. Returns the first suitable path found or None. """ vcruntime = f'vcruntime{self.vc_ver}0.dll' arch_subdir = self.pi.target_dir(x64=True).strip('\\') # Installation prefixes candidates prefixes = [] tools_path = self.si.VCInstallDir redist_path = os.path.dirname(tools_path.replace(r'\Tools', r'\Redist')) if os.path.isdir(redist_path): # Redist version may not be exactly the same as tools redist_path = os.path.join(redist_path, os.listdir(redist_path)[-1]) prefixes += [redist_path, os.path.join(redist_path, 'onecore')] prefixes += [os.path.join(tools_path, 'redist')] # VS14 legacy path # CRT directory crt_dirs = ( f'Microsoft.VC{self.vc_ver * 10}.CRT', # Sometime store in directory with VS version instead of VC f'Microsoft.VC{int(self.vs_ver) * 10}.CRT', ) # vcruntime path candidate_paths = ( os.path.join(prefix, arch_subdir, crt_dir, vcruntime) for (prefix, crt_dir) in itertools.product(prefixes, crt_dirs) ) return next(filter(os.path.isfile, candidate_paths), None) # type: ignore[arg-type] #python/mypy#12682 def return_env(self, exists: bool = True) -> _EnvironmentDict: """ Return environment dict. Parameters ---------- exists: bool It True, only return existing paths. Return ------ dict environment """ env = _EnvironmentDict( include=self._build_paths( 'include', [ self.VCIncludes, self.OSIncludes, self.UCRTIncludes, self.NetFxSDKIncludes, ], exists, ), lib=self._build_paths( 'lib', [ self.VCLibraries, self.OSLibraries, self.FxTools, self.UCRTLibraries, self.NetFxSDKLibraries, ], exists, ), libpath=self._build_paths( 'libpath', [self.VCLibraries, self.FxTools, self.VCStoreRefs, self.OSLibpath], exists, ), path=self._build_paths( 'path', [ self.VCTools, self.VSTools, self.VsTDb, self.SdkTools, self.SdkSetup, self.FxTools, self.MSBuild, self.HTMLHelpWorkshop, self.FSharp, ], exists, ), ) if self.vs_ver >= 14 and self.VCRuntimeRedist: env['py_vcruntime_redist'] = self.VCRuntimeRedist return env def _build_paths(self, name, spec_path_lists, exists): """ Given an environment variable name and specified paths, return a pathsep-separated string of paths containing unique, extant, directories from those paths and from the environment variable. Raise an error if no paths are resolved. Parameters ---------- name: str Environment variable name spec_path_lists: list of str Paths exists: bool It True, only return existing paths. Return ------ str Pathsep-separated paths """ # flatten spec_path_lists spec_paths = itertools.chain.from_iterable(spec_path_lists) env_paths = environ.get(name, '').split(os.pathsep) paths = itertools.chain(spec_paths, env_paths) extant_paths = list(filter(os.path.isdir, paths)) if exists else paths if not extant_paths: msg = f"{name.upper()} environment variable is empty" raise distutils.errors.DistutilsPlatformError(msg) unique_paths = unique_everseen(extant_paths) return os.pathsep.join(unique_paths)