From 4cb3e593f5aeac3c6f8eddc65193de0f7e7d2cbe Mon Sep 17 00:00:00 2001 From: simensan Date: Sat, 22 Oct 2022 18:55:58 +0200 Subject: [PATCH] Feature/support for unity licensing server linux (#468) * Initial support for adding a UNITY_LICENSING_SERVER parameter to build parameters * Test to figure out what the working directory is of current bash script * Outputting current directory and using $ACTION_FOLDER * Add resources folder to mounted docker volumes. Used by activation script to copy over template file for unity licensing server * use awk instead of sed due to http characters breaking syntax * mkdir for unity config * Add -p flag to mkdir so parents are also created if missing * Initial work on returning floating license when using licensing server * Checking licensing server first for now, since serial is always set * Parse and save acquired floating license for use for returning after build * Clean up duplicate commands in activate.sh * Fixed running string as command, use it as input instead * Fixed cloud runner tests failing when using a ssh remote. * Clean up of test files and unnecessary logging * Moved process of generating services-config.json file from platform specific activate scripts to typescript * Fixed path --- dist/index.js | Bin 21896227 -> 21898504 bytes dist/index.js.map | Bin 16269222 -> 16283017 bytes dist/platforms/ubuntu/steps/activate.sh | 15 +++ dist/platforms/ubuntu/steps/return_license.sh | 9 +- .../services-config.json.template | 7 ++ src/model/build-parameters.test.ts | 100 ++++++++++++------ src/model/build-parameters.ts | 19 ++-- src/model/cloud-runner/cloud-runner.test.ts | 2 + src/model/docker.ts | 1 + src/model/image-environment-factory.ts | 1 + src/model/input-readers/git-repo.test.ts | 16 +++ src/model/input-readers/git-repo.ts | 2 +- src/model/input.ts | 4 + src/model/platform-setup.ts | 18 ++++ src/model/platform-setup/setup-mac.ts | 1 + 15 files changed, 154 insertions(+), 41 deletions(-) create mode 100644 dist/unity-config/services-config.json.template diff --git a/dist/index.js b/dist/index.js index a33732a60a8639bd31fc5c91abd69a23b5f93bb0..1ccffaab8a965a0fc393dcb25c17611df5a0864d 100644 GIT binary patch delta 2457 zcmaLX3wVun9KiACc+V~l8)F+AwlmCj*v{G9w;5x0!LSTngdN*Ej^=F6IdhFBic+i& z{iW0*DoUgrl#oi1(p{vRu8K&y(dGNwCDuHh=lOh}=lswAegE(Kf8TB0jvb~Y2X~mZ zT!>d^7HBQQE1V9`>^w)2?sPkxlM8g$4Bcf;S0NXgM0k_t`gu25T6qI48LDc(!RXDk zOi&p)Jp;WPE!|a8+-`q$cC6N7+iA-%gUUD>6zLt>ZM3Qk+!d~>lLOlEEm*BRsI~H* z>=vp<&I*lF)p=U5x3Zbh8l+SYO8R&Z0;#`mu{;W?@?^4{l%6#r=uE~sI~XS zCu*mITILPO%oU?ePL2d>J#fF)=D*rREpZV2{=G0Asi+T&ab%XfVzSm1|gG zsEjF_8r23Pn1sDBVtC8morryIHZh33X^^?uD&)3{}ZvDvR zHR=zaSExdKj%c;lImx8@N{jRDF8egyqr2Q!d9!twCCy~6`;@q^_$!#{a(MJ?N2y*g+gTJZD@8Tj z;)H)Uq4t)2_MO_%5ViTB=C7(#G=I|-ueafl*0#}mH}J3)xj5yJ1|$67j{pRsDNJaF z<_JPCTA(FD&)iZFzu4ca0Ca{hK`j}C}JG|Y%WM|47G#G(u05DyEwq8k#B2rFzz zLNdCe2YR9xdZQ2eA_b{PLqDXWKL#KJ12G7h7>q1rBL}${f}t3OJPgMOj6^=J!6+19 zG{#^ouElk@9yeedZp3&@z(f?nj!7s&F?5t*GN!$(v!3_^8Fat9& z3$rl?b8$1~VLom_B^KaTEW{$*hTE|iOK=D7#8TXayRi(*QH6VOFT7ZRmADTIt8hP7 z;{iN~HFyXQqZ*H3E!N>ttj7jChR5*)HewT=#8cRer?CZF@eH=%Sv-gB@L>mbVi%sr z3)qbpu?Ksx4=-UqUdAhU6$kJdUdKTk!W%e@H}MvZ;BCBvckv$H#|QWjNAVGk;W$3V z34DT2@fkkH7dVM8@fE(tH~1Fc;S|2d4>*l8_z`Du4nN^Me#Qm-f?x3)e#alUh)ehr zf8p->uy4#FS^O_AcF+1|oiX3LuMvr;MA7O4uFSHusYh%r5|Nldl-)B|S zIOD9kvok%W>7PEYI8a_3aKLF52aIw%BWPTTwCCm-S8}}>DZa5;CBbP^CYP3_rUWOI zmN8_OSW|a7lkD9yjN!?4**c>#!QOm0tlDW-ojhh2>@nO9E9$7rZLhC2>KxXQ{4lQ_ zwa*B}lqc332!jia&=_tsK{z7N6p?6#C`3cI9>k(K;?M$Kv_w1-&QTO^|$ z+M@$Hq7zb(3Lnyt4nG12A_JMoLT7YAS9C*n^guRp&=a}HLof73AM`~&`k_Atpa26g z2!m0GAt=I748w3-hY`3QH((@gL@`ETG;YGp7=y961tl1VTX7q1#~m1t3AhszF$s5} z6nEnuOvb&q5BFmVrs4riLm3{#bUcI^co;J=3yp?PvL1);~CW8Sv-g5@d94NOW1;! zu@&2}9k1Y3?7(Yy9Xnwogg3AYyRiqgcoTb3hqtf~Z{r=ji}$b}@8bg;z(IV7LpY3& za0Ewj498KA6ZjaP;8T2tllUB8;7fdkQ)s}~_y*tNJA98H@FRZ0&p3@=a0b8PH~fx2 za2Drq9)IEj{=(n5h<|VimvIIE;y+wnnpp3sfZ|jPB}{QCjg-cUTWO+%D-lXlB~odo zL@CiqjN(yZmF7yE(n9enEtPmBL20G5R@x|uN|Mr6Nmkk^?UfEnN2QaJqNFN5B~3|J z{7OIxDj7n2Xs}%*8d8m*PEV@z?pMyLIR{wR62x?QbJb&NeCB-ge0U0DAnGOqGSsyL@ZB4 zFjnjo%Ttu5qKF`1H*}vqp9T5QPu`RaVwX{p<)4PLCU%ad!fDYFuhSBB! z2y7?6lH-mze%OSE34SE5hzi>@?v3Cezo#o|p7x`wyAtwVlzM4!u74Ly1CU=e)wyKzvlS!$6o;c z0`V7wzhL}@;4c(^VfYKjUj+Ul@fU?Z2mYe*7lXf8{Kerf9)Ah=OT=Fi{*v+60)Hv^ zOT}MH{I%ND>MHF_OTS9mbBl9|{JGVchMxy!=o9eM{yt6EAD7+bj0>rpP&Thkn{E>- zX{*0pEVKSy8;YOu06hsmBktE)1alsl<t_);x}YCf)wrQN^k!E|`37DJ1c7f}SwUr4c(zD$!K zxMZ2u8b41i)8gp2myBe(f0-7qJ`AG^57tJ}p;Qq{pB~e~$ylyA>AEARdhBwogKDJk zQV~dNmTSF98&MlX*DRz&D%;W^z3)G|gy z(Jd=9hfIizx8_eyq8rdubk$zD%+52Rb+y_B7okjXX;D>`$X7Fk`BAH)s=BDUq*z?( zsuoo>6&2-`)vhT8rA5_K%PVJ8WzwR7dlM*?v{s>J{&|>KfV4H;IB;(aElF{P(IV1x zpX3%{11igBj4H3GEOwcdY`I5^qEkLu6Ys&qaZMNBqeXjGQgjdRw1xL*S#mzHwi76b zP|0WorXJ0XhEm>2ts7PEYjDu4l~|8l(Ss|smYx;8xl+^Y&epEdVm(unS7~y4pYq?A zNTXJ1a^sh;(gvs|H?L23(0i-UhMa(43?17CQ|?=cY`#93NCO4gpPGpR$)K)EuTVL`|ORS>2T9jOyM3;_xJBo@PP!oqAm3PAfS|MknwezvPhreAL zNb5(|hET7yS_kx}Vy$*DU3I)Rj50SmBWU+p?J|5tTk+K6L3EqD%2QGCpteq^VUKAe@x10S9#q{aqZi)d*K5P^ zQ@oy0_QUJ7?sz-AUh9n?pzO?4j8X0PJ~bt!QwoYIi)Og0F{_N2Cq1qusVz}(Ov^O^ zyi5zDosVmCP{WfaMLdn)pv{%f%d})Vxk0;5J;%`v8?|e!=e3)F(zBk>rcsBv6hjX^ zp^f0Dfb8sS?33VntQSVh^^s0TN=;cw^_(Fk#jdidlCmpDxhk)ARf--xdWZ~0uqo7| zUh6A+0}3Hg?Xr4p7;QV&kVwGFhC3mc>W}Er6cGwk+xtnalgyGri=WiGtEbPN)biC+ z_op!VATfz#lvd|71t+uS z<49bk@J^GBMs|0>M^o%yxFZ~;4EML5?x!f4_ng+w{TfQIJ*VY)yhd)aUWaee`ltj{ zA4KamVRv&I09YsW6#u*hwTqwE%GI~+&w~bVzJeDpZ!s@m+gq=L)YG~btO`!O2m=1y z3sx;$qQi?;|M+#zi`ICNIh0;_5!8(9!)vB`YV(rS=c`@T<;R=#YFl3PpuNq11tD^`cPzoJ#qQ>VbTZG8oViK{yOiq%?&SFP%cU)2g! zD$mi|uWHlPD=)4~Hd`P$v>A|GvRUh;Qt>)KJuTj%0mW{?8s|3fYQl;k)Ud^Ba`U+P z0hG7ZS|E=7YXWG=R%{_IhWY~NtF4+QF^c3Ot$&g;b$_Aqec(BBwrM?q8K!U3IxC#Q zyJXWgEmk4zs%^j)KW+nFx2cbK4O^7?9uV{pD)evd-@L47h9Wz+zoxZoj#kJoT92je zuW34}Z@`cJQF~px03Y?pQ?UVq%ci(yQ~tNuatl8OTgEhF>FekXH?SX1$Qu0yhPwRc z4`OJdeor`E{f0KADT)wBhu+YlEsF5hq4MWp^i0lu(RQsp#`)xSts8!hZr6IWb5n!9 zMa9!xk_LD&f55{fQ|Fjm`_Ol^OVk8KU9&WR9(xCvM6y4Wlejm8K6{6Gh=S*?iKABU zVgXxI$sBx)qV5An8d1#)K>(`XWxiq6yP70g``*>sv@lyPREH6{nmpO6xB%{<#nt93 zmgf2$TE09(F-4<_JzAna)3KyI;KR=D&@M#fVeg@z1-rpYPu!^m;X_gQ@wk92IZ`TG zdLXd_KU%9-{!VdK6<3y20DCt|n(mr|Z}iGeEyZFMQ9!No(>t|RsJ{K0Rk1XG(7i#F zxr+f^6!1nIUA+sK=C&U`2=iEhukKP1Odz$yd)Eaq>Dt`D{T?$hdD{Uj|Hx@)#PPgUjgEiD1GuP!f{BJw1}Lk=)+K4P`e zB5;DivIBGPE!I?u*2QeL&O*P{1^~HnQ zF#H64s!0jNhGV)$+9549GPf8T9?X{6O8uvNf-mcAB#`5fmM7;}v5B&aKnpbFkab$R z_KR|v8P6ks7!@7{HDYW#=P*ucO=UTD zSc`FE{9t+R4{0vz7f1N?_2dz4B;9$0Pi1(XiYEEfw9m8@>-mGvG!4&tR_Dal{;({F z!awIN+3Sa8Ey6PLa`JN8XE0D@2AcFaqr)YiYlBfHfAXpXdi4y*{1Y%|)$F#f27%xuQ_Xe9FE83L5t%gPouE zeyR0AL4NM=mDWQ&kH*i|uNWDA{S}xDK9Oi&qutm0>~%z%hwza#Wta!^ylk`P_k0ab zqn(37==LMEp|tC3a3qRL{uN{qYr#=lZG=$QZ?slawUAm-|;N|6m(7zXDF>ZV}K5};KL78kUu0jr1+TV1mc{mJjB#ds|}szrpBy2`Gs zo|Yk6w^sbL{74l?0WB_qS5uJ0qMQ@of}8#OQB;k=r_!z;agvnh!vO9n&D_@z>oCXD zcgSEq4>bbuW+`Q=I|dkdNtW7rK*%@_IRJ~2UJBmrNR)&H%XUK9q)Ag;#pRVn)vnC( zSjLNvYXyME?&F#k-DGMGwLPIZk!8pUO^ZVoZ!CcY;6wfPoW$g#K1oF6Yo!x2rT?r%SVPSKQk?8_KP+`X2*;xt|xkk(+E%!*8Hyj+UcM8RS>Uk!9mi($M#!t!_ORmOCf5~syVKry8 zJR}{tBfW)0kzEv$>3svxLtOz7%$DE4rd;$JWC#1dYY0=q+T(YmM=E@}=r_d;#*$Zw zp6*>4qBzL3vy6Yx^+jlG>shUz!op|2FaiQIrgyH&=*qi(MXngAj@=9WL=fe@T^m7X zzA%DT)ZtCy0c1#7@xI7vDpOEKwX3Syt|^5k`Uz9q>|GtL^b-zueKf2Yq{g54iHN3Z zjmju3FD@$0o>E>s-BsyXJBlfBGFi2U(Wz}K%pp@V(Z*Na65d=$!DCU39!CqlgX|un zV}B{=2%uH|B16eN0Lf^2q&Z$$P!7Rz5X}yR%=o9~;&%1Trvxx!ARX`*VJfwEI)?L` zzxc4p%v}9Nf#{r9^$)1mg*C!SLxa#7r=Gtd$nb+`(rretqCh=niAburO!&*WO{A6n zB2$gXr+?4{#1b|^5UmatZB;{cpXm|2Xktx?w`A5C)k-!2ve!7$LNG!9P>v2FVmjD27IhU_6hG7N*$V<}G^Nq_?6kqD2M#`s=BCnx);08vjc8k zY6Z>N>6RkK;cZ!pNRtXQQ#IpRfn>)03PvQ*;S{WOCeSDQ361$hYMlxJZQR$;dkmi= z0%%w&cH8^?abU717Rs?G+LS6J)4V5Dj8PKW3tI|F3-l+QEy&eUL{q zJykK9oT;}VD)4pYTY4hBX$UE#;t5K0ie8EaGj?S@z1k_-bI|}sj!n{!Lrx4<9T21# z9e6yQGSd__O_bWn4zh-5s%!sp5H0*l7<3{{nEFp!P`+zQdN2)07rMgAj_V)+o|`T% zrXM~;*)7}B1L;#IsEe%{WwHF4Y_yiwTI8x?KJ_ipv0U$LEuN-__j}Mhf6B`di8Lbv z|JfZ!PeW*74%T*khR9QW7LT;?r(L7L3$VPNyMeL+6p;yBWtU~*H9S*{l-*zjfh-A$-@5TEcPT6P9GZV(Qe_+j0le=xfnVbLYuP`2h>P&@~?>%rGscs3U4JoE~qT8a8*{9xT@Uk2{8<6JygxzK>gIVgV3c=$Ge#^9YjZN zhJ~^#I|wPje6EAYYMk|02O*6>@?4wasc%Oy5YM-E6cbor5mfD(Q32BcC7ciI1cr@; zmi;@4UX6~50BK(f%6}c{`F~ck`#>)d@3E|!-~;gKLfYL|kB`Tsxw(M;m6hd{ERH+T z9cY_@Y*TiyY({KG(X1-rlKC+GN|sK3q_?KSoj^sPbB&|bEu6q$Z|mkc&e278(%haF zVy-)u9z?J86qD4J!PYlu>})-Xrt|^@my(nw*>4OAtIvA}_qU#3kjrZWD)`ad0qFw!&HU|z6 zH%pWiMa(ihFhC4Ii_HD}JU|Rp0+=Bn7o`J*wDP)Vph(A0(IAl0g9Al~SBA`xp{^O_ zm2>27?>b0GlPhGD#c&Kyrbh;W(POH1|0q3()(i%2J}^j3Rg-qO-+?f?VlZ%sP57*Q z*(!^T`k7RIKiGpR`BUGE)pAIzjawZ`1CIiC+6A5;A{J1GOGK5b>BS*p9(5cduIATG zcunzB$T)j0dgWMa1kjnGqKjgmm}40>43r@Kbc4g6r4#35`&ie(D*uWRpjj!yaUy5E zKX^B#LvJ?ERDj!A>xP51vT`}-&~Q^M&~(NDsGSA;*mnBdz#3PsvWe} zJOBQX;CZ~d9x_T~HOV%!=(lE+xECucYBcEZRqbIzSiir)pIX&i7epgQTddddyCsh? z7F^eo(EusSohKY`2ql&!fPrgC+ed>tU>4G51IHRT3RZnw8?TtAihO9jcdbu{YI%&} zR6Li~u$qs;slM=V5beIDER3!n0}+J1gc&uR9Gn(Hd&YoSV^#t@RRER$3%jA!Sds6} z91Kf3)!jIc5i$S7%*^hwIC}W-A~t8irW}p=ep51yBSa*N%46S0nmUM2vRd8tfv3v0d~58U+MEFS>qTNevy zwxQx>q5I<1 zn(BI`cpo2_wuxz8&w&(PB6e7b$4bQ4c-H3vCm*{?>~hyK9Upj6?)WS{#9CHyrKZT0 zE+|DhZ?D2q;1oW2OF{s8e9sb7k{aOQR#VWdreK3E@xmsMn%hRuj_VlMX4{ob5 z3Js)#SBnAO_)(3sHu>5}DxW2SRZEO09ScNuK*zkiyf##D9I_!VP()GqY^)6K7i%gO ztbihz^Whlj@M}Xer4E(*7&Qm75=+h&GpX$4TPOQ2{^rZBKOT`aV^)J zWlR79p)6^-E)p)^L9wv$^dd`$*y1{XfuVcRbs|M7R4wAebf%3G8|K{s)X22+j_X7g z^Vg?t#W`i+^;ncAufyJ8Mfa5J#RZKz@znJ!l6&Jv@ z!DRg=$S!#qMwUDrNblSP7&EsucOk*_`6emwxEV|VH_oO*#WxGh8h{iZ+;X#!jv-&% zEKGyD&9{`LQvX{Z&Em;oj%~p$qC_PGtmIQ1ej;^|@6PxWB=uRw|8n zlfQH;vUYvq%tP{)gric*Sq?&tlZj2H?0WY5kzZ$~eEjQ1GmuxLxSyopra#0*$$&v64;JAC;Lor^Hn{MR+yw-5n70S_>%~iiv{+fbM0Dwjg>Tz%oARIjWmlI}mY2rxSug50WNr|4T=Rt@hSS7iaLn&g4C zeyNC0;Od;%j&4a;=RBl+v{V#I&m&oQXi-H4L}F5KMSYivZWz_PWsIp_S|+5)=)abU z3*^~e2q&_%UoNE2!VvsiVbscqeg=$}oCXxtSqV zmXr;RbXBpt3y-qPT`ZX>xr^!i+Pheg_RU=)JB$Zpw!Yv#k&uLE)lVN09}T#h+4w1U zGvCnrUI>Bb*NR99CoVwcFNnnEvKz7-27|7}%0a zIYrTwDFUuTq8etV#icb>@Hxmqxw@@f|L=74S`yMc1s!ccuMiJg_7ea}oaO_T*?AwI`WN*_1iLb@wv6dF)=Mju+l1 zE>z8WQu{n=EAY>1t*NQgj?xkE%R6VV$ z$hD9eX0IHmROu_Is2*f;=2{`Gl14nv6|Py!6&5@KG{m&F)sx`j@f1(ZAB5b8pE|jp zT<)jZ2bFF&p0+(`k?pV+H;0B~v}H66Q2gsboPF~j5<}439S@014DO{<6k8;$3Rp?5 zFhKBs4Ud-5IYl#yt|>2rOg^W$e8xm{;7@`k!c|83}(%Q2Wv zKLTwFKMnjF7+-!`|2NUnFGF%v34~|%8;YmoNA0h8HouN}6dV_qy5>=okNFgn;CLG5 zJ7}l3O7L4J`m3jwkIA`Qr&y$T;@7R~aBkoV`1R~MaLv{$EB`s4pxYnAVG#+j zJjNuYUhGd={)5F$Y^Zd4(6s* zTwMk%Q@VB?oIkW{SGm@0R>q49VLK~GCs1y^h|ciA z0vWmPRvO^}X=7x5dYfz7`pVLKUx3ioCmDGGyEI*= z^C@eejCsmJCz#ckh<>h8cuFcAtl82tPg%1q+l*2UWHbE1PfQS_f$HK3#v)>JU9zLE+5j^~84NqSYg zNhGAY7odOwzNosUs;lTfEWgi?{{4VYb2c$6J!F$emRzJwqPU4U;8JHWv0{Ga+&cj9 zi1(L;DN3Ti!soFWqn;N*d~~xv_4fTFy~^e8vAI!;`I9?S*ko(t^9uXDAdDtQN93!PXYV(WADnVA+pM7Fv;uEhxxRLada42@oWMs^vtSpMy7@M(ohP?z> zPkBiQi)*&4So)H<=$!nGRYMg0`VxaR{$9L8xTCDuFuD z>z7{^&L%6#36vWTIZP7fE=`xd!u$?OCet0Sh{Ps~_crHBqHl>=Ulr1r^YT|k=O(ka zqqkobE#;|8LBB~1V0Gal>c3g!G_GgCX3-;pCvR*;v3tf$upihgvfFT?5Mzr=;RjRXnevlPdr>v z`c|u=w8kUjNktvu0VskwG0&e|aOYMbW3a?*YiczBB#1Y$O(ghZ=!Kr17*dvHx@u5i zCy^BHW#bw3nqYScR(*M9qtUPN?3ceLE@3=T)CrW~z-xR&^?zMl`2SP3(~IZJL(^^U z&EGLia?LJ+VUJw944~*YM564K5QAp`_l~J7{bSXXU0G5#+pNUDrwjm4{)QL{P;Yrd zWJv=T5GsrRwnRt|yeVQWROc-~;;tN9DlSor&|R``MUA^V?h@_uI&?yNvNp8GMc;f= zbjb2<)NN{^u%^2-gd3fGO85P{=(vDO_Zc&INWXz&2KRHf>MbktsY{u8$&s?vz#lAB zT4ip8{$$y;4i zS#c_t5*y3@P?BK5W68%yRI~dXAw5TBXF8g-Nl|W9bx9cl1C^JlY*w8@WDF?l+I66- z8bY27S$^ibBGr}w$jY+?%2?oQVqhP{xgo9gGPaFtD|8ZNtHwUI3>AbNhgL@5Yh zTes&guLwiu8 zlD+XGy|ra_dKaJr1aI3Tbnl8h@g2m_dqlF5{K-<*KkT(EiQWp{6N%w=0ep#ij}Ljj zTTGh6l)Wc9;pc((SnBk`d!k)qcHHE1GUm7>KBlwpi8OC;JQsoyYd`;)wi&HA8S^&!fYS=>atF6NWA#A7#_-PuOwA zGWBdn=?8ct!I36Qy1EC`yaOyts5`*(`}_e|;`wMBm;8)x-49|O+O$Eixy3(=G-a3w zn~b(pe-Noi2&2uxyTjQHn!Y>;Ypuq$#8bo}5gafTWWq&H2Jdd6a$j%=^V7(+7_D;t zDBHc7kTdsO^@jUpk=_O#BX&3bafoq8^kEnfu;=*X!y>^`w>tl@7<_I!g(c%)2)y_t z!;4J=cKwqrOxfWC9x8$K>=CgVtBh?0uK$czndJo<8dxPAnyQqa2djp|zCu4(am^a# z>EA=7Crx3LoyDBbMVUux&l5Pu$y5Z|3!?fTu$Psk5ZB6$1+ePV6di*QPHkP|~?#5Xw&yFLtU!#LopaM84cm^YkR%Rd#U&HCfcNusB z^`yuP#u-sL;kN$~6aLvZtQup8j{cUXkH>)CB%=% z8Z6fzhRKklkVdi@iu7Hv41YcK`nF%t-lM?Xto6@(ZeNx%q4e7^2txNBgFFnir^r{f^GtEq+1jT%wYKVx!y}4I$ibeoiG=}U zBgvZk6gCXAR$nyTbV6Awd5+k}l7Vf1PM(0l(fKD`DnGwd`zqm-=+LA&sOyv%u544- zwB_%oL@r9PRZZw=3!Gg~!?Xmm7E6zxhT?)vYacre5qk7@VwN(aVSA9rm*+VuxuIzn%#c_Cc*(Pmjwf}_4=7o+86ilE01cw>S2YbtfP^X`v zC*etPFO*;zPnE6O(Z@Ne?Ys-u_&1wWL?ezPbPvN09iHs&mpK*NJO`DY+Ruz|n^ZXcb2u{>Ao_)U!T>94=+FVm{?MXt!9(q=k> zJpINZi!;B8taHS$SkVB#?OA8pUl`dES1i?|QzsZ%qO7L0RJo%xj*F3lQi&__*h|?g zbgmc~$Toufah-$(s8Ct+3 zm@Z9TJ&m1Dhw0K04PMT%^sQ5maeFyO(;MTRQBhE*_OTqQAr@x0v(VyFS5X<_U4T6w z5uvxY&Rmman9G4RB~!A5nSz?nu2&(Lkq>Xt8M+h>$x`@P6rtO!T@D*02CGOK|Av}r z_Xa(U=0)i_=R$`OVnUi?jq03_w$LIXqp-U?qAsoHexHOSJB|*PvV74t>;-d=Ak!IOWPeNqMzlP~V;bi~D^9vS zNzcJ7U@r6Mnh%yO3Bx10!cuuC7>5RqjES&%lOec#{ zTkA4x(1g~yjGMlwwcf$=i1UhkZ?&(_iC=w&v1Hb&faVC7r0k5h%xMin=XQF6J%A@pApj4i(+r8j z&goCir z?87r>*t4~)2*`mBl9^$xgwFuO} zZvU|)iqoAWHXkx0Uu@}dJr?Q9hk`vAN1u2H7)hzDB}JLDKi^)VlZM$i|xMNb2Gk*?cXFBy5u5wpukzon?7^CG+{hhRaV70rl&`=yt+ zV<2VS2iC!wYuLH)v1qc~E6HjPyxmowDBDU@ule2dOVul8Dwv+@X0aMQy90e#epvn# zS*M2tT>!V9ygbu^cR_cMbKYBZ6^IMMlZ92U1t3sC^&A?$zd@ItltC2HLl3ql08&;n zUU3g*hW;>0WD;J`lV@n_I%hIrO3XQ^-<}^&kM`81=@fHepY+tbs*pPlT9r&adod@v zXq+>#`RtNvst?|sqJ)Nb^wJx-MeN!JY0%~)h`*xs)-OVp%ng?G)^{r>uZ#16c%FBw&k-?u+}vpMiO zstKYw{q;!OR$SF{?plKA)&7WjVD%!Avd1~Y4Btk@IVunX;g8vCfQ8}FJ_5A{ih$PA z81_$9lCvH1I%^PSB~s(_Cn~XFX_l9q$Db%45j?d2=t0B#c|cQDL`%`RKMWI0O%Npy z@|fBr8aqgj{xfs38u{IGlKuxvI;J74tv!)fOfwvRuq1am_hO*3pSL3tk39{AmtiL! z9;_$*$%;&aJzfu}Blgb$4?PK^@$VvJq*qTusp4{HNZRkr=tP!EWnIGe2#r)fvoFz8 zm4}Af4&X|2a32J#oWYt(Z{ZD>=q)|01ra19k76 z>9a|sdpPwPqD%jhF++6CSFvYClX02#uO0&M+sOW#Afk4UGsbCMvr#P@gTER5s zWNdmTb3b}VZ}6w5hv~~b`Zjqu&V1ZN;YH&DgDw9c1y7W6Q11n85xxI{%NS;`&gD!aNXoNg_DXt*VgK&Uah?wwQM=G}9& zV$xYM4xjST0NMJ{dWe4+@P#DPEoEh#kSxQ5&(68JsFWU%mlttl1@Txr1TE->PG9^! zq6=CDf}784V?!yJ9vTS-%%|z7wTL5nY9#mw7NJ+cEQzHdqZE&A_6grO8l?Ri zS34TnK;g|#2oe!OmygifdwjD-V(JpCL1fbFTUJC9H^#i+dAjW{y~Hi>`O^zyFiCE> zyEQb?bg&;*kJnNRr%T4_rgu16wkHWMcCC(e9RldBv4E}x8AotMr8IM`F!uqnM^k-6 z8-M@Vu42z`NC+a!zdg|1$!NNL90-`z$!JKXMMie9#|nmUVjLDCV3$noF9jdYPp(U` zpRH03&tZqV*O%8gJQkbphN20VVXh@|&>Im-U#!5+-f_S{Ko5$ryTe6rY)|G&4qpbi z|2IfM%o`3&iO0r5=1429(&K_<3^2yb#|rg9zK4a}xnOD$awk}BCjVGO&CMCxURfKk z5)vlL%jCA&RHVnUaDm@GEz+ayx8{>Q-!dnIlTmB36vxydll5?4o{ghHa(tn=lUW1; z6;v{=dEouF9769?{;ptv;#@vJ5>7vtg1G)S%*%PMQ8A^5X22G6XL?Eu#rKBvVJWZCrJM3?HTneK z$>ab57hVmK3~vkw)D0?l1O)Srt1Y3#vsasPGjEH7SbAj{j9C(9>6=hD3;oZ`(xv+@ z3yl$>4L20Yiw>8}wgf6Hu0K2*B6B|fLhu$qZO-n-uJED94qxdRU~qlCWTGUr{j)N+2cqYEn952_sAqf9rdJT2kkMo$%RV zKB7Pd+s=8Gyb`bJ)OkJ*;|xWX#@T`l$-k+Nw+cXn(4Xh)$^?U&5K+*K1>j^sSrf@e zKGBri#Xa88S<-(;j@TY+D|O+(gRF?U1W^kL8SpyoI8K7V3_ z(MoNfCUIw2pz>6*1SgsWgRPtHz{rQGE>5i;SMwH$Qt{PAv1 zK7Jbr=X9GC-GLo^>oeFG1vf-G1sLYvQ7&Jd9PlM!^<(^>qg#Xxgy7ti=hGEJzSlJdB92s|W542~kT|p5|Fh z3%PrYy0UO#cU`kw7R>c6s-cSGNmseAjLDL`XgKn++W64(5VWyX zahED1f}VIp?`#d&`(R#J4+b{mZ`e=f0pUMuCH_0}FCB`M3V(>aqlNt;?O=#X(D0XE z6x%oO&3&Ge_dlP0iW*jtpjJD&@XD@$cPo; zKE?kZECw@vX6Z9}3u(b8@y%a;hIc*p*PP+uU5$uP1VzIA!)-*5o(k~~8(J5bnu&-e zGX8N@Bi07-pr!Lekd)&ARj8|Z=Dq+jYe^9P#C?wc+4WPpnR)sGyg8yt;!hAw{t?8*PS$>neAH~!%!*Z&>8 z_i3{0TWmTC6Gv$??_(6fa*ns(f?(U`U~!#PN1Cx81g+QGU?_cf-m=f|E#MG%x211e z)-uc-6+CWa(y6n!6tZFjBJJ8Yny`!@f4jcex2jXy!L>2lUa!G@HUzh3r!%7>Q(CGqBqS53BwY zklz+N6muPm%h<5bj_<&KbsvMN-nkcaxqb&$3J=;!VMU@#@^4fY18|yf&M}^9WX9LPhr?}v! zC}Ym<{uCmTTlQLsczhZ0RdIjb4p!G+4~39lj9?O!T)MK z*^7S2$Ka(1Tvs2hz?1v0=ghgh^kv2kaKg)9AsE)v8RC>xnlY>zHuS zuv|9p?pL;cID*N&lJ*IXkh~kLW!*)+TA8D$>j9;%O7vLecOTH%^(T?wp%Fr-4_Hm| zV9={*Ijyno!C+5)0M+NlgL{IW`u>nU@QFc^GFaYfW+L7%DxYx0AIXeCiSXGF83Z4Y$7sH9m$y z)A|SZkKgA&Wj=V*5}tMTFcM2wd;!t5#QM%A&S_2EY??z6U$V`9 z+b{LBb9qf5OE4|_k`107MLulXcfQ22`!CNF{QaoT5xUj=>7HzWC+WxK%2G#jE~;-b z5hiEh$wV|F|86G2z5;F}E(57{!LJ04e@y&$oPZX`XL@}ELe`!Oqp)R0TYBZBo}uR0 zCt=|ghNIcMLK8i?DJ2&=dI~lGyxxJ#Oz*FX@3Ea0oW>^NguEZ1Yd?5eH|1VT*$z#F zjmpK}fmCo7-imjBhegF3p%6mz+5^}E^A3Y^>h=$dJ+ZgI&L5#WT>VeI3x)g>bc8EG zvrH|q1!M;zf56VRIxt5)?fwy1>%AZJ2R&X<%W`)G*;MwUKEj=v({>@eE>0bpA9M_0fbYzPdOtEz_#7}dx# zdY7LeZ^L7$7tnDwZ8-}TXHqjmQu@YUThK4f3^r|-ZZ1{ph+5ZRKtl^X1Vl;)pY zd;~U<6AgoYYi{)NU^vG5Lyqq=FVK|4(JybXOj@!VQG83ZC@88dn!(=tBLj@i;pQ1r zHG^Yv3iH@jUylVCO09Z+2fN9EhKxS+NT4D8r9KKY+;@?kOZFuNidICS(_LV2b2iA3 z4hWvO<;q~ArDgL8f9>j$Vu8E6GffexTk?}l?*!vMiRyj0Ir2oXA-%641Inh(A%F>g zog89F4K;s#63^=E;SfXmI^b(N$_O=LEK@E^K;+ZGD?-tTiSExeDpYrQm?5Lin(9;w z-^vS1j}6n~)s1U?!VT#*c5S$ED+{Tj3SGt1%EbgDJ3D)VArGAqhV+*j9AQkx58mQw zbA%y1rg}yixfFb-(Z)7wOS{%O%*yjBGK@pMyCb=Vmyz9)-OAL{k%rV@Bt`K=<*!eV zqlr<5w9YP#GP+3&7ezk%8CLS4v^UCV{X1KPuPEn8>E#aNvPROjJq}~IB(CZxIHRjII4_~HX<4oof&OI-ZU-Y8(>dG8{t;4@!z5i8CuM`NEc)y21Z%G zZwGuj)$oj8ZW1~;=@_UDt;iK}Jl41sKQ(cNY1F*s9^Be4oVa3QPnHo9t}tTg(Pc={~SFlnw&=AKE0?yfn8<|P?PP4v&M zG)rYpNJg>7kpfwVaLk`#35n$Pt%;cl&xT$zq!oJLx; zz<*6udbEWR<8Rl7!NgGAc`~zQW5a|zQft9drNy6p9g^ytkc{| z#!uI27t$4Zsi8p=4CR>33;*3#EPDB@TFr z-N84)TZ&YPDI?R25DW}=!pG7Bizb6(5(+t#3kyoN-Ek;)Y&nmtzTPwryoLRx*IgLwczE)(kOl7Ip-X^KyV|EKd@gAHxnJQ7T+7g0!axt+J> zFjYMA^RZaU8Hi0%xJ7G0A{S3g5t(DIj|BGa-O)%15_%8m`KUx{IJgJVZMh%=Y#j5? zTw|JIN?9y+bsqY|lBBZzxSPnG5C=yEOZn{8e_)eU}s~nFN99s0l5!?*%%Vo074He7Y4g|=*y!0y?{Kt z#*Y&Y?qeaI1n3(wlt7Yw?)3aQ{D-nS9%z>TZ0V-`t#Hon zhW5gZbX+Ni{gLP?Om%pOjKb#@ki+`8NvPSApW@*l>eC#e4+T=o9V>$ZGVLyHS=1xY zQin@@p8V&dW!ht|-;{sjRyhyP;f>klZjsmY!7d?zHe7B<|Lm>(j5I6Aok_p?PIDDc zALJ@3t)ABC6l`J4Xd1s&OQp-Y>e`95c z3_HVW3%UI$ukhn=w&WnbxYNQv=Kk9fbZel+zyZ9<7xIMx7B_8UL91sD>M{@uVq%xQ zL8@m~R~)o%phDVBUlz=$?YDu3d#e6AtkijCc+V)q>46)0exYh^P4~9~W*OdHNL`-A zgL&u*VbhjOR}6(jY~4^J0(qVt%38j?Lk)S0UtaUu;;Hj65C!Z?^M1}l|!*C-J+1V2D`QZ@#FoCwR^R-@4^omqr zJyu{iLQI344BV2IT$|*sHYspwQAsJjV!^=N*vu|9mQgXH6&)!s5|{1OTxzivpwlf_1&#~QLV%kgB%k|QO)h@>-H z*c7J`7BO|wrX2Zqbjf!7iXl_05Z%_TJAP_Bct{`S#z$y@&3qA6OfXD?X1sQ!?2JwwZ?$ZbnV@)XrFS z(j*Z2H1<78q#v#TowKsuz)wgFp&gU3yybV!%9>VIg-TX21gBd$NJ)tnlJNCp_mS&v zE^N#%E_jP2{agKT$eqecJ9e7%GIS55rA5Y7tmH)>qMK*PeFTf;u$-!j(vs@TLIDt* z`VbebLyj3q6DDKhv&BqPHpt;7A%H1+V+z3B=Gng&P_$eb_hSv_(SP zHR6%CbSigX@l^C$E;Gk?>#WJ78`Q;e^bVpQrW)pD!tAD9f2GkbQ$^(d9gkK?r0i;L zy%HP`F9@reyg%ri>*XJeSd>?SeIpYGP(>Z1^&Kqx^XSOo}JG0!C7iZLzD5PAQQaA<}a@eZ!S5bnaz*J)7eTR$RgIFo2YnUR5Kcb@`E%D@IIO2q6&VP9BR z$CVos>9!v}z)4D8t=TH`E3>zC+G^OTUZ)4xU6xvT;x6wh2P)x(!MrqUVr)sPZ`V3i zu$;u6!M1d&0yyWD3hpgtkUvOaxb@s3q9A|NCrpxHUnuhxuEkW~ZJxTX>l!9B%Q;ttvgr&!OxL2|kI-mTsAOovy2 z<}!MR?WPR)#aan$P)BVw7_$M{Mx;FUzhA`?-_&Zu@LVVTs(CV=-1}q_rOgD2UsY{H z@lvhmR+m6qs!=(wQG2{Jji+WN-yNhnD$)OXUo`<`%lEu+nC3<`0p5$MS~jYK6f8nD0; zFx$Mo%@kNZS^?^yzHfms^mo)59P5)7!t(9>oCYgRaU*pEja>w3GBXr>fIN8<*bSBm z!BH)mjxB;WF!?r+JZ_hTcjpYp=fjMBd>gBYez?te7YlXE?bc5`)!hN9 z9GjtEwAhe-l_M7$eerYmVkUvlFXnpQ487J^FWI2nnk!H~C%UOxA_#UHW zVkKK>WEYo~6nb$4Z!R~a&+ZS)jcWW%yUUn^pTl<<_o_kNem9ewjdvTf<(`x%zIX~P zJqfKhv`VooTP<6`h47O`p|ytew6va+Xl1P-C6KSzGC01eWpK#eX<#iYtEDbfRhtXb zk;*w0@(Pihs%gOf`NZO8b3hox6#9i2SV+mBdG{F6s2JZrYlN7Al-!8zo zt`@{%B^mM~fV~N5>y{N@cBT&nMmvsViGMc%w@oq8nhh8BEnXUpID3V+8A0bKBu$FP zu@F)s)om@-r2gNibZ*~>ZP|03A^jzIS>Cw;C%&occw3qj`Q3F!fl404I-k;_sw&#J z(MbB!gm`7URkHV*N^()wN- zjD!D!xdX`lkI+ZaQ+%4n*|rGtar`M(dr7`Xp7j)>=vQ7m-Brm5qTp$SF7pB5uD`7c zppTwrgO8cdA4;HKp9ZDnjaQJ;I)Hi|0Jp(B%l2o$(qHzBLK|?c^MjTv?`_EO5FimM zHJRDb&uM2ieVB3m1Tcon3D53lv9fH7=o!!Qk~;AcOs!sfmT~v5&l>U`t)b6xN}ElF zGk8$>3>Tk59(c~UT&1Oxwuvo+kRgZ3&Za9j8KupsZnKe+QG?5B=42y&s=Nk0x2CER zmqry;x^nq@$0@lgC6|g`#Bg>#4<_sIi^g70!uirmd{jlpE~WSTQw*W@ZJUx_FDCccu$-&mDG%0y};zMv#JGUAw8dY?9tC4M;4NMJI)i%~a zHc`XTZAMI^8kB;q!Wk5dyL$RpR+ig3Vb!CBuQ7D@yk^8%HZoto#=Q%E-Dt-LqP)=v zuQ29yBi}QM7D5Dzs_IJVr(uP6Jzd%wx}evsgA*TVze6Q%9jp7og(#Y|zXA74EFgcn z^bKQ}T3X?68ZDZEfs`TSt?LcUoJh)65(0UU0@ardd$J&2I)0k@y5S6zs~e$UWbcEj z(8=+jhKv4=-hKQgYx?;4;=dc6F$H)YsD4_~7k}qD;^e%ym`Re)jv<5l_8&HC@UVfC zM)e;#w*N@d-!|ftc?AtADRz}rp-b}iIqWOx*RR^T`Yqmzs33`6e2Zlgcuu7Fx4Dt% z?dVU-X*dy=yv@7~e|YR|Hud9Y#McWX=i5eXBuDv{q4iYtNR=!iXR6q4brW{x+j*aG zD+TYOW<+(91_{=OFzR|S+@!B~$1Z|wVYKcY-fea^|94r)ZGDj8b*<-Udj4GtNHBDl ztpx61g|U?*n5ORFb2ooLK)hI*zXP=V*bXDkswiQn(b0OIw9}Bmp*c66gXpoHd_K26 zAaIssNBtgOsoci==I$;L^wy-9&7H* zI>zj=i`4D0!Hnl1`qn!!h`PPUrghu^=UDWf(b3wVk_VPjHQM|h^Lj_#GcHriGU9V3 zSh?7EMM|}J8_# zDaOC`V?*~$ssGqKb~ndXC%+f>#Rti&-sI*B+HbV-jnFrvs2IukKYK=bSvhCxv!7Sl zwEc#3vZ~$BJLTQ|e7^W*Kd-NtPYfd!;FJTk{*{#DhTRPO&y@`Mghj_!ePX0}3QcV( zmYl)5PgrQW=MxCz8RlodFihIW!Z3M~49}QVQZ}W0RuxCfKK%vu3sYKm1}Z$j0=npT z4kgl+N1+Lo#hSs!1WK+1Wg_zQ0Y+s%9Wd%Jq*Q1I3><9|B*& zJOXoIk%u9DuvqtCdR@f6?fsEL&ksVP%gpXD_ zrPi4NsC^AkduK9}{QF!y)Dw6f0|^?1XrCoT=SU41P*m)i3`1j&^suSsH*0dJ>>F$d zyWyqZSiM{K4V$jX-UWL*8?d|1EFF#ouBB0olg!&W(~WpQ?+l z%w&soa4uq_GoJh!jFE~keA)o`Om8p-TlN)99VAMU1v!3#XM}dK;i%E4X|fZ33?u)2vIEnUcyb&!_F{ZbZr8fur_~82 zH+@d94ZzY9Op^b8g7rCPPcYrnPZ~1k_>-Iy_9ar+v*{$KeSFf8Oh@u5qpfs5;M)(R z?GSDK87jYuQ*382_Y{|uL=SmCJ;gNl=ypxIQS>>ju={ClvFgCNCK_P+!scDelW(!voS=j`x^Y| z!5^Tr@r>>C=?}&&jV2Kt%(dq4KP$%?^sATAJPQsuhzT2tEIp_tuK3g`y*b#fXrZ4| z8jhU$nW-=A?vi|?Hdr<}9Td8=XdmmrZ4<|nzrf7IeV3uzK_8vz_hdJ2iz&PA$4OfL=p-tBf|| z+fOu21u}9_B|hZgLn+Qlne9DpjSTV!it`aZoNt!h{R}eu$7P(i#y8xyN(sqGOD?SOpR`-fwhS_Ojt&9Iw{o6ay&QuQ^cJ6g)YTevxP%_2} zw++PEpk^|hH$6~W_ehI#w`I@go81}S_bIS=_o66FEniv65+&v(AB%GKQ7FL8s7W4J z$_<*}&jJZy3p-J%!)e~DwAO(|CplAljY#_z)5TpGyEJ860|iw84T*M|#zG$Gr&W{E zTR_zz9rJBJY^#JDjjt|ktdBF zaZag)sgHBsWl40CR*)i`i49$$S_+m+X|no#V+)>Dt^r z4?sw70jM!)WpnKDEl@ijcd3Pw#ZXboPWz`8&H*Z6@VDvFG&uz~Uii2Qz(FZaWqCHm zIjV`&aLP=DX9`yjv6Y7-!NgRjc|ose2XBFusm^52bM5n~7#d?mPS#q&B(ix+XJ^km z`7NDNQ3&wF)32J-5iqj<03bRxYviG!XcTQ|=^U;)07JvZXL4i;^icJNa4hs3`8*_e_L!a7UP*I?dW`C8 z6!FX>NAB>?+t71G`(DSHt%ljjP`8ifedF-_*hqE6`1qBq((X)T zT=W>_Re$W@I)|=>AM7T5Kp-MPhwwPr(gx&VT?J^wy_^ zs3iHZ$?3uvxLl-n11&x4-r5HA&Tc=lty534!Jyz_V|r0lH7);2=<7W@QPtKN<5}hH zZJm}kYFlSJT9>%jK|i+jSjA4y+=JVB;LYS@={#K3(*vV6}yp!3wZ*WHgR-FU)#uG1=53`(J{XrhP3I`3&c6ynh&CGUMK7Z}7ZS&e= zUA&gOj7Kl>a-8v=BfccZDQ)Pn6#JcL0e+HWVY7-{G$eAJiJq18$aR|DuSm}D zOu0YTYvX*L>kR`8AT^?|y3pIC3LvuS-V}xh7Dl`CoDu%G)YQiGArfeIs@J$=&HjBW zYV5Va8Kc!M1T9JkQr5FR8A8p^aJt(E>F5*fLG-LSbkMi$y>Z9H4o>%-^*n)nJ2c8* zik><70p#}t&$bR;;0f;NO#w1HIy<^skEXdDovl4fKibi$?1t6i`LUz-;(_n58LEN7 zG^vw!+Y&-_UndV_9Yt@*BD_aVc5+HJcMF8`2ZYAh9x-?)qQmQMwDBz{HXy%z73>a&HrC{S00vCmBt^H9ruj_74Q`? zM*){y$bC;mKtKg-1lhs`kPkysQgOk_5~T8!sF1rMh*{z;DAO{IqcdZrrI}96l%r;@63TD4YQXIq%bw`2H(NKN?GF^6bcjWQQur|<7z{QDH&rP0@Ze6vwuLV_Bq)N z*>KkP^yd+ln8a=}{-bwnwa!UO+fdzy^W`}f9rYQaZ@89zzCMJ4%v>(3q*Ye^Yy7`5 zyBdR|v9jeOND3f78$n4@uBzA*NXK(hGqdMLr661;4>j(X_q9x^hfS%1IBk@)Do7TQ zG33i~{f3Mu410*6QN+I2yu?ELQv((h?t#gP+0YCHhAJL)G*}bXdsx!iMq{Bz$os?6 zD7CeA)LPord5Lx;OJY)qRBcdNLoj0-$>tHPVIJ(4&}C5~iz}7;f;?p^p_xzw>shql zgJ#(>GYpf!2NfON6X6LlyfP{*>d*@^>dp(&V`Iqzg%pVT)K3+ym7;Mx&4j<_z=W`L zn^eO4>B(jPVDRDBP&Il1dGKf>g-Gq?HSg9U{N42~UI_jrts6|kg64{QZOPl){92Bd zUX)0vTi)XiKQN8_`!z$lBA2vTgoD&}+ZVaa!PG-%!*78D#A zLw&JwZjGVG*-Yfz7*OlsV@X~EO&d#6biZ>f2q3MbpH}odYXXI$0ou7Bm%O18=M{ks z{C*2t@SP+vk#dy&$tR5ZtGFXU;UGO1hk@xD2h{0SHdBOZ5d5- zR9~|qlwbmGJf-@Q8*F$J>k~u6tj(;tj*IVa#o#EHC9i*CqmyVih2;f^GwLnIPa)~x ziJzBEA*tP0@2g;e|Na!Fj`+o#w>vlWa4_nrji)wV4J|*p8F7y0Q%M>lbX`1nzJdL`l_ugXbG?G#^E^cismfVeL#HKK^>3D&-w&UTEGA!1V zj4g2NDgF_wH^W;Di6g1+;_~p^N^Fla{>T-;Jg2pAinqLljZS-!7EjW6n=@C%lf2l< z&)>&Wu;Y>67>qyp3)PNad>na2`r-U+79#8C520FRQI->adE1(YB&`MdM!e{UvcKPO^q z$f9%z>{ySi|Jqv2NP;lHcvmwEi-9dCIGpj$Bq&%c=RLax;j1Se$vPKdEa8>OB-N<< z@-}*j^U3tG{GzgRAb*F|Ckw?FDOig?yx(hdQ`0n(MvDPyJoCfSXa;^OPowdQ$B?E( z)vceTMvbIYPHDD9_jHmbXeRR$XJHHlM;f0y8@mSYQu?oM;@Me{vGF^D)`5)$qB*2v zRE=@dbNu)Mc#wI_|NXq-v$13w%BwfhQ`|oJlIF7IjoG{_ot;e~;@jCY3*(SF2PXh? zWNdh~XAV_33|7`$Y%I?1=UR%*b2<6446u40ndtDI3~<+R8ERL^piGqYWd`al-P`I1P0wme+{LBJECHU6xr8aVtw|A8^Z+ZLJp1!?)@^kEG@r z&%Ppc9?9GJc$UX5|M|>3-h!{st&ssw#sQzd-)Urc}eTsjJo4Dk7>cjIHoL zr&xk=P(xWZkLr%1$pofjsEfZ`cT}5T!hblm`(f{@-s}N3`ag4PhasgOty@8BLZFzJeB3vS2@(N z!NOaJiSr@a;~DoOOSF7`&~^5``2ngZsGH=*@uV`CkX9UqSN4eP%vmW($}#KHx!mEB zTnIQ=?)h=9S#Eb%C4jZGkLbMsbRnmTT|fbj5|B$?xMrkDaA33IA!-*;a6NGXA1=)n zVgR)H`#;UN>H>`%fB7Zk$!4(r4sMBDm(~|?z-r$`l-68yb!LsA=+*r~o2Js_8w`kg zgAh?|J!j50Otx9a|J_pT|56|#IxYkK!B4DMjR-4q7E$tDMxAmq#L-bXF7q>ii(JIZ z%RmZqdL}j##s#>Dw;N9Hk`L0^NZL0a3)f!i{Q{wJT*&A0RV7~H&3x=me3Wu^tdG5s zkH#CcEt~zd%qVgb-v^hti=NA&fV1R?TV6j0Uj!^#ZYsX)O&r5#mt(897Z3Fq#{n(% zxZo<9ufS%^J>no)<5pmU_`FZMv=fg-;{Z9h3W|45@?C*Gh~PS$MQ{QB8oCUjHbY`y z(_eJ+qMPttjqf(QfI=M#)t!C2ijN9(vdAck{WQhNqWY``8OHw5*se!5U`Jxsduhb28Ct z^|g*-dLi~=Odic{Jim|51aL9k+-nUIk}VI{QVG2@YPJXd-YlV^iRiZiGG`AwCd zR!*WxLCapDG<+kM4HSbPk_?bzrilmH8J<|T+t;6L{H!?{)d$`(Po*MT#Q;_*hE9knNMue$zti1 zcKrqfsr`NvdEBS+hc;7$Ie*q>Y^~fIJ-;{1H5}KTT7;_pn$0xOoWD_Nz5K9hxL^Lr zQW|B>zo}HyB0k(Nx$;1p~tX4FvY zX36QPsUua)Bt+1YltL$^WeN3rfNq7EZEf_|zDmQ*?HIg;{2TYu=)@~qz!7?HA(Q4A zDKt$g{tREd_5bYOxQ|i5qmJ{5H_wo*dI3gPw{FD=#ge$Uwqi@;(-J3?hw#}(+3IlG zxsB~Kn9o1G4GrT&%-G-DhV7G?_!*ZLxeDLySOqva8@?im#4W{~?I1Ob97WsFlLzK- zk4iyoD!4mWdI7-|x$nSaeqc5y+y=7bXfMilsPnvD`^q|Zb(egLmvk`cA)4=`NQZ;@ z<4SVZ2U8vznHR3N)4t=*orG8l3q@Peco%3@ewwk1YAAQ0sj7k81j`D6cc7us(T zNwUNRQBGXM)4O%qs}zm6rlufja5v}xq$jL1yTOz4_qw|qM~(iypm%=_rd>=-6kbIL zW<vs3_HP5Nj2Q*ocFupfS zTHz12g*b8)N9ZY7Yfh;K1I078u-dRlvCO8E)lj_hUg2_3E(W=wBpjp(cGCo9R*GD@ z6{*@3tdd>}7sQg)0f#Wpc|8rz^=mG&V*Rx6Wf+teV(TF^k7uBsxT&~(2<=7=rDD|v z!1plq(i(OclDguBqK#N{nBp+KKRry{%wyB`Nd1$BDfNW+z&yWrga#R^B4dC%2)Cow z<9Ybe`p2_rl%-`*WU~$J#GRx1&e-u7jv=0k%-xPTMuSxs<*#<{xH!w@^=xj|nPd0e z;lDmc{mh*UIgW}fn>UvN1ASMq#{grMb6 z9Mz!RI?<7-mim<%h?#iNyVg<*DYueyLjT9g3sHL#-v^nU#F$zzm^}AZ*OJ}z6R91K zu!FT|Ft_bqElo4GE$(Ffa|&&XQiX;Ly5%AUodRiO?eEf46l^Z3`c!=@4j#1rbu_&J zas9D65E>ALc~xf>)6K>R^+kCi-L=d3g5q}w=mG=)U4cNL8}JCw9ry*%1Lz3^0l`2B z&X1`GlQ14DqJz%XDqFaj6}i~>djV}P;1IN)(Wwr>J3 z5eNe&0pUOd5D82MqJU^12ABd&1*QS9KpYScOb4C-ehJI~5`aWtCXfUq11Uf%kOn*n zqyw{n*}xoNE|39a0$IR3ARDj&Ilz42DIgbE04xNa1{MK}fjrpcE(r zUIn%QTY+uBc3=mv6W9gp23`ZofjvM4Pzme>s(^jK>%e~C4d6}S08kAa1P%d*fg`|C z;23ZmI04iEwZKW>6i^5J8h8tM8+ZqJ7x)cu8u%@62KXIt7B~m|9{2gMso#H5uHNaO zqVjkj2Wq~`^_Rt=_H@w7bI78^;fq5Zc9*LwI#Pv&rvvSO-!6hm3q9Z1l^AIuN{y-G z*I;+j*tadkL)R5{%f$aT?dz0{dXzuGZ;p7*2=Ig6>;V@moQTG%fGuQOa5rtfJK8npjFH(1g&K z333gZR-jSQ)&h+a?JLl9B=sUqqD#hzyo{9G)XWSiJ1?V4GEM*Lwg<(`3hG2sqaYiq z9Uo{%KP=L?QDI8C8|4p^RAjwa<4GH=irgsCyGTbp7i)rOqWGt7UZIs6U9ePQjzODjWgLS(L_?iPi7h34Y;Rqr9MkEfzqGuq5XD&o>Z|EowfVmo(Fj?)3l@a$DDPe_<=FemHOGaOw)^&J-q8q6+eUr zP~+to^5_1t2VoS=$veErld_fu*;D)Q8U%ZgdlA5FTR+&98W(8-lvc8eFaqw4M|J#UpsiisZaq5n zOl+CZhBRGuUUalb<3kOs%3bMB5!Tsrm8Q+IvFqHa*gM>T#;?*ep~OiyZRnFA%&N>1 ztuJg|WNXoq<}bbNUjks=QgEAq+byVbGhM4nSY30irYc2pq|3CLVKF=9vU9pbAg&l7Xt2AZU;Kjp7%68xQ0b6 zjcFd^M@`mi8c@!9O$Un%%DNKhMW@$m`is6iDPaSa@tkI^6D3Xva;5PbG>7Q$&r7^$ zQcKyM2E7mJ=(S>t8HApA-;Nf!h@rTW+eXZ%UN(^7#b1GH z8n{u@iV{}Gc+mXjMfOy>5zFoIl)M<%N;Iu?+h1Y|i??9*A8o!@T;^52R{^RES4 z@$TwYklVUPa&sEAP1BXOZbSPO+ptto+chl|HZp6w#>v-!x2^J0(}pJ|XD6rYbM@Id z(j?lo9T?1bQ9c4I2GX}7-44wJ%JF;TNxOGwCeyVqun7F2!%oec%47LX%|vSd0%Xn{ z1CMI*+Xt}C*`*mpf9}#Gdt^wJ{)>*5qN1Yc?tt^2l>Z?BGwZ=BEcsC|kZrrM(0_jj zu+alBsf68_$D8iWylCrgkkr@_=e?*bN#{ltEp)c@e7B|^d7QZFLGfRJlDmDRiKF)h zU-P5&A8A_B4C_l?;)9B&4!Eo$+dW`X8+HfjsO)2n1Lf_}G^0O1n&r=2iCxZZ2W@h)HM7PO4wCH0jvF;XnMo}mF-aFWhT9g29`B%Zkiz=F4q6t?X z4~SnQ*M~S!xi{EM#a@lg^1T`@jq^e+@kEXHYC0%o#iLc*)HKrG(t&ox$Gv;OkKg)X z6_<6Uer}Up$aNoZ-1if7J+fwyq1~)cHTG2g32LG6R4uXPmDt}euz=J z#iI+=`Lt5CIMW9;9sU$ZW9(N$PRk;&JhkSisP#Sokso=LYYaaf=O@#9Hv@T-vh zSX7=%#(qsGS!Z7}SBs@w8(@XGm1p|_Dqe#62Y@rYK;sXf8h*LcM+Y>~%GZYnAXhLb z4{Cf6L*^P;2f;Cqi1s!g)aa`XIkHrfQ0>+HQVfi1o-Wno&@XSJ=72+>NA7k0A@t9~ zE)(y#oYm)=M0(Jzz=dw_1xbwlTr*l3#Es8Um_I~*p=odWWk(CX0HN}Sn9Yr?>FO7n z)|DQc9M-f|9v2@5t733pJgn)Wj6m`%wxQN~u!X-41FS9dkmd3%Q0uZIn6igUa1BZy zrE{UA2El5&dqmTa!jIygch-I7N#l+R-jf{RM~#kxZucDpyW|hoj)JCk$LMNOi(|kx zmO&}UfXnZUK~u|)0fhUHVZK+60ilN-Jnl|SzXXzw&MP*s%28i}Co!vxzoE0C17Bk1 z6<=b~-I@gZQ=hLio#~fDjSVdD$XAeaf(2HSAZ;p1UZEE)SIIFn^qn9zojDFsVt+!@ zRBK>r3CSs=^wOk3%y4pYlXFv3q_O(!)DaWtr`QD^l#jAB_Kok|sq6%%@o}g7u9Uc@ zk1f^i3IXp`rje;}8IXL$T;O6%uX0~nRfY*|DZ@;^FM~GWaZ(ei;?|Ssr$ddsnR(lA zx758~QGd#c2(zUh&Va|1p9IrzJ_Yc^oYHi+Gx0z=bREKV&G*odbncJbsaD2iTbgTi z)Q*NWELYKvCWUIca!R9u9CACO@wCd!jwFlku|n-fUUs4Y(RA$hQ#~o^At1oZ+2?yS zxXdXSf~F4y-&@}UjlS0xdC>XqL5=xtLA5CGG{*69`wD}CP?1xtqT5r8>?ouw826i} z(LC!e8&6}x0R#m{8iNW>#AlL4f+{c=VO(uqsc!5 z6H2#&6JPlmqrCgGrj4T!?b1>qKimGI(J1os#;;hUMm67er;mRDEp7e5!3{iwSpu;t?8#aU@>TjHT|^G~LPXI>?!Sj9Bca4vDE9Q#&?%35Epj12>{ABp~|(ne#Mmr-h|{5pj7q9%Mh!4_XAybx_A=+ z{;uhLHD#r*QBjg_kTrST0@H7E3Sx&Xl8WnECrZ5qmiKT;p}!NV)=tYD*&!n=&67H_Ru^RMXo>|lLJ7CM*%>u{>Y{U| zg0^8^G_j@Zpp1@ff^By(;)fsTI#cnIiEQH4uWH@ZrgQava3a^U(877jZ4S$>bYltR z%F%lozhz!QzK%nO4%eq-W+&(BLkE(Dr?xgVxetsUdS6q^DWl5AL(jF*v|oJu2_MPg zPfW6BzoNbhtNf-B##Z(x;I;5iaKu*8Fp;+4QPB6HMluw>KT)aTPY8}`l?oo9Auhl7 z0f^@#NC|S6kzr z&w)40Mbe)`2pAbiFkOER2Evch3l!=90@%ZkU%mied-g)pj}A0_U(M$HvPDw7LqkQ?scVbDw`sy zmzTN!dDq85HLQ}yncvqWKeZHT z{%D=6&&f5_s!5yGk~`UqDG#UL)sidKdkp^pnyX16wvvbPVU%r^5AWMbHN*!kUFigd zX%_a+Z9(4;2W)U9SUXLrMI!KUlAYv7?z@9RsnibQVacL=FU4iiX}#=7uJ%%h$z`FY z3P;J4^!CzR3c42rv#oLs6;(J(4m9@ZT{qe}3T}ri2k9*N)Lmh4ZR|XM(VB)hqP=Q; zY;%;ts`jx~G|yVEj=prlEMdjCQSnWv(p4I$++~a-49pWpU==<1>!Lq(ival=9Ur#; zp{|)*YF(sQ`uac!^!tVq*g?}`2U?R>A{rC={?x_;LkpmTdlK9%&gYIJm5 zA-irhBwLE{!9VZHP+l6VWG7-lJoJDpD-s&i(@vr4XkV0T>?;M)Dvx`1l;H_(gr#am zJA9=M^kF7=1H3mH;p&kn(NFr8a%aUzrjBa-2hu)Hsz#EFdTeIua4Dt<#j4OA#?nO@ zM$Ao+)0IZRwv3y=iQFZ>1-U8ip1wU>CFkgS<|e0%rs)*yYoq);^lgSWo#-NI6zy;d zy%7ks>@AnO(3Ra)OcFo|1;CBPUr3RFmzQ*uwL%7t2@;}!(EuRcjX31tB z0UZA&H?#&xT>x6m?x32KI0UZJ{0PaOX0@9Hy#GAdRv4XubT|Ttr;y(;t+miA>?kS{ z1bZz_kfeCWU5rop*1|Wim@t5G*FqCtt zK5)AjsfRLfsdq11y7T~UtNa)&;3o?Ko1GOQj>^E##X!XH1lgrhQ4pk2ro~fDhy~8_ zRClTx2U3!{$dL-1F*GArQQGAum)3){PZ7g7X%Kh6SFh3^x3(o`kXk$td^v%@EM{d?gXlEoTRlhwz(`07&PhR8HHb4B+++qQZ2d_ zCq)>Fcv1Z(AWC+NcFEacOK&xiTGQ7}z=7Z!byMrpv(lhcDsJe0P0?Dn@fX}^K~t$Y z{gMZ(d{!f{0?TFq%Rj4b$7w=q;7t`lV_@!(G4&~_jpXHK^x^mI*(N(XGaKsHoHkHS zHr0bmn`KQmSOLz^P$a$hj!B0#p=nL>H08yf*cv?NjrREGb&lZURKGK@xl3oM zuSpB-LUvuC-I*rkZk|-f%whjoQ2o_K8b$9sfH{yq?*c5SK&UJGyGnIvcO)>=vYV7= zvJTWVznj!nVY1KtLuarUZMp;F zP(-51-C=aIe!5P)>+=L~DIW(go$M}UQf`c(iKdqvXh;t!X!&mo?P%w-hpOcxFf=Zu z?*WOls|PSa6lJ1gYJS>7YD^jX%k3z$-dPyb<4~b{PjKkGM=QOkyy5wpO4+x-waR)+ z6DYoyl%tSfvFTkxZ)vRgT?xwhScs+arwSFe#GLv{EhzLr2pmM3OKKX`7wejE7XCFz z5PK2=)m8{pK`b=H@{v|yfIA0BSot8yg?@g!2yRcozaTm7XkmY-IXV3R9hR#@eS$qH z&GM0lshW#zhxFu;`rg@T^wZne%c@dUZJmmbiu?E#RULZjv&W{U=mo{TqPE{_`&1=l zrl%)o(4yEA9t?KlSk)9h00hha&zC#&S3Xo7BHbSVoMm0%BKy3)PZ)TPKlprXUB#coQqp@X2@u;==nRfVpKZGU`_v`WG8DTBdH zww!?xw7oT)Z#xD_gfed%LH3aCm^GJq4VPM|inys^=W<$X#e-Iy7if%tr ztfnbLVHvWlIX4&#{`pX7COpujnMRr2DoJW$l2blZ5E%+{Sv9Ge(&a!l!=XAbiiIUZ zyuIU72*pC^YSolDvz|FESrd6SD-zmK*kvj2JB=Ay2%U#dGAdDtJ2wG&j5;?VNk5z4h`2j(#K+qte5s54mR*b zw_-2KJa7%G`p(9$$GbBlVy%_IQ8SHP(lA>7gnAv#c;| zy6e)hwCpFGldf=7PsJqoX@=~tj;>vMkPP+@OQ8Wv4}}9yeM=6a($Arb4qYwRSe_{j zq9K`B`f-_3M_Qg-sG+NwXf~<_QVvjNRtcEa?c@efa=E6_Jx{6|i(1BYj`#U7hSI zk2P|tJX+>T*DDo%o-3WH^!QsI^bUq_^C#$SEomVTJmD0A8GVce5w7+B1l)UH^wAAWIUk7^gnAn zaQtm|AP0}2M10^!?)UfcP@}n(pb3~F*WeEWCjh=!c`wmk5G94fxxq+ueFAhOhIHqN z(EK?jpx;cO$ww0*uh_@MUOxCO+(|b{ny*xg2!hi2+a#%*Kxh{#PAIeyQW1OgbEJBd zJ_(#VKwTq(HHs&5OFqn&&NYMW zXoy_yMw2_~5{P-g!08bBp!HCyb!(-!aA>IM=5!EQ-9(6Ob{Mz{_}po!g*+P~wk5WL zHAcOOp|N_45$Fi8cuyGtM}d@Ozz9L)g$*4$5Q5!|8IS_brHE=MzovsZQWvCQ-r@LQ z2F!6brf)5SMUA0#pjK~zAsG?fj`H6EmVW6AiwZDsSFFR{53ab;)wiIa_t~3)C=M!D zQNr6$5r#LpWZ3!@pg#IGDnL~?=wjw>UEY!Un3Ta*wC^2oKVERqoQm$hgNB*sl}5s& z+is>5PqXr1N$@`Ss+rPbYB>>nUe<&Bh>EPx>c-ds@fVkBOm0k6{O~@d>Vrm6!vO{ou26;FoiZ`i}iv zX|tl$6tph3r61>lj8jJG3|{S;^Q0kc8wfSH6`hy|%EVr~8$Fo^`eCTooDa0NJasRC z5<6pWFP*^8N6rUunc}w12RR`k!;88v0M(dV-m(B=xw`;5Cx8<}Qwl&KYZjvJ163p> z+yZb-*8&hx+k30rseS<_{j@cFP6mxy78M#OBIsxV2339+sDJr8NP+S@SFFi$kyKv^ z)i6~^Uj(NP%dGDfp+ZCnoGFPX5XS-CUP~d1!3xY9V>yc@%`zJVLv3CR$@_G%)R4L^ z{Tkh`2NNiNcELsw;)9pKFkvCiKwh~-N)?#yMSe@6*0XI6iEl?qOVP@WN$cz=>(_I( z3jZ{cvK{@n6y|%xXjEfpC3pr{y1JaYhmB6tGO3d$xmThtv@M$!+t94f3TW88 z;&CgmLdF$$qKp+#=9u}Sc}IG01%|}z|Hl;oeHDDLqZTUxadykGr*QU4V7S?h-@Z`t zGCAOC7D@ra>EFCistXIkrAP{!Qhk-sRE8c7-E&=s~z+hmMQ z&Cbk7*JtFidCImgWfuXf?-hZygnlnYIY1@vgIE{D%qh09_!QJML#XP3QUW{)$$(>a zVrF(Ow;712m0lCFVC#0xOCP4sR?10|$(U{wLxWdINseftOLA5gl+~O#I8+BgI_Jx^yhK*%o~=W z&&60rq>eNg(E{%gSQ*RGKw)u_geN53a|d*YX&9kWM(RK-BCkYboR&EE&B z8?;Vx#)8c)#+YN?lLE1~Dn?YPLU}QS_2T!yI)c`MxB}nDuh?V2uov&a7&@?C^0CTI z$)XGEAtX<(1@_!n3rI$+gUyw;4*Yhur$Q)2`zfKc?&L&5QFgln!r@WJd;t07n$q-op(LXxuyC zmOvkFlyqLazRKz;tISJc4MoyTi*#SghWaq2_0JZ2pLSfHcCMl^N$%eZ7%>tey_#3rzSpEf}W!`$F(-z<5(4AAZy0g+ytrC2mP zXEUa90>zXHPd7^rudW~vRhejy%)DG6s_4&#f79>}UbSwAwgUPkAApd*{Q&yUBm9F7 zgSNm&m$C&f1oY_*6neSCm0P7iE06=BjYtaH`X5V&(C1sFh(KPBK&eeaPsv0kVrlUr zK|!}T^4%uY!Z@02lluGOQ=o)s+zFs4sb$}uQgTX4W?n`v?c4^4e7H-h@$%SO?v-T4 zK_?IczM5RU;9&5-?Zh=X@dhbJkEPe-ScRhgGqA?q;NNbM(UuVoHhLqa@ruyMhfYoIu16TbMzJAqAYc1a!m84B3sR9I<_phy9+ z9+_!a=3Tp9wLt#`5F-Ee{CyqtDLGL&iu;4^eh7y2A7HV158$spFLdVbj&vK}FQ-|rpE0-F* z2OX_T|ICXppm?SH{XH;WcvNTz{`R9-gUugfWivm zzt2!vS;vHZRZwBvJ^+aq46VQ`xz7m9f>L<)P9MNmhAls8J_DHTK6Jb7Z^h%)Bf^VT{D^6~v;ZJH%5MNF82@Lx5Q|0>V3$Lu^uW0*mU!%`i0%%IJP90Vt0EfDB4 z68wAS;a9122M$Z#Li#EH2_cr36DPIl+Ok#WHm#}RFo?(P2zX%z)joo?_-FjZ%sedz z!SO8ETbWYL_}k64Z+2>~APm!IH-{dXnYk@fpu*;akhC9kn|YM`ns*d^Ej}h`UzWO; zjzY~V+$zbe7Q`GwPaTg*AvIXycM&qXS9119eXddL*NLM=$0Ygn4FwBT(T5w6&`7tu zT!fV=`(;%@*kO1a-7Ufb(w9<%J;SU|a$26A?jDuA6%Db?_$*<6klQzq;nG){P7DNYY;N6G(6)DNL)Y%d!4JRs8msO8H9%DS z4X{1oYsu@S?jvfp%*)NpNl8wFVI%OjiY(_*pc!mPd?WdKi|@5tW@l9E7PF{ZSzs7z zdC;71l&aA$^;?^qzGZ167^E=K+-bnK7(I`46W+a4BZ6$clLCBr{a!(J#!ek5`8yHT z^DSmv{GH_UigrSNl-%saJo{v&h{ZzH)`2gB8Me^N6OFLpO0@Gh+UH?kIF7O3IxYpi zqNh$LBv--4UsCviA0C97&Q`hOGU?`VN#iEecrhqYq!~LR2u(Pu@C~1gRu5NhR0efn zTA397if(@?lYGQhtT|YD-$`IW{zYcjJNIJpxs|%MW3C4 zsXG0P6eiZsRIk!brziC|gL1~gb)8VIRqqa+Tes`op|vs?Ks&K< zuSjgu7(&u=xJ;Xj-0TU7nW@4`5XF3|H55H8H6uC)aiW*cg3eo=#W-4>mwX%)iLZzu zQLh>}LPAV{%s40Yq%Y1%-Ngd6Y1@-IV(ZjweG23Nj6+ddwCP8K&r8}Mv+F}l;dTCr zE$5{gLwKlW|AZ(n{uAbDoY5T;6J4jC@Ao;fC zHlrkMn=yIGX*tF&qF}>~h?9(5O!Nvx5CApuc!gUVRf#y7_JibG!}w?*zRG@#6Bncq zXYQ+uJ~w%Ia&B@Q+5Q0AvhEL39dCR9AcS_GCB_CJ3Im%PjeJ>(9Q;A5AqYhLCk6;E z8b|M5ffotf$b-r*NWr2(@gFP(M7toZAv1$ZP5x2x70xB&e^F8=_5|iA>XUKy;&YUQ z8p?}mBeu9G)#=EMwAN?oGlu7M%P>JaN;1O0*w!n$sM062gp04#5-vgyxN=eQdL`6i zFF^qZI{DD#OOk`oJ$fbQj0W;!%Sy}^0J$WEybQbZmtYP&y~I{{c{vo;mY1cPMqu{I zOv_6b_HZU%(!9&~yi5E#ei@uXz5-Fu`HCb(nBW&BwQbp>UCUl=jB{->UZ0XDy05$} z-KpY=F9?g7pojXkB!G$3h3KlO;Bq5BZCv0#Tso7ErhM$*Gl{_U-RrEW?WXAgo2iFB@Ca zyrP_E)ee0ubF(S&?!ehztyk9yAi_AuG8Z~wt@WVJ74RQ% zPV3|ftQF_DY_5P)E+8Ed4JQ_stH`)vmp})#T0fes*7_=YXq(mADDrJrY}hYDT!kH7 zx+|?#f;f3&cI!R3ZOl>Zx9(x<)qB`A7zWl_cN+2#`;}tXMUCz2VVOC4TC^x1lMc;`NIaiOQO!8q*+{PT#r-|*fs?^{>mjh41^|3!|2rod8mP|XhHo}&$SjaAaV|=`jyhnyzH5-aq zFjYkh|AstCeXkr5Cu3q<>EJWyr<>YuM+D5Lh}U&}(f~0A4bN+^L5QGFuFHGl-JSt~ zM}^#TK~$5yvR#f>wzS|G=I1LRER+X@=0cPNEN+R1a>GvRKy{ykMe%qzzQNoDSBv=* z3kEz3PlesH8X|X z@m{IxeSqkywYJggNOzhQ1~bJ*TcR}a8u~>{en+H(GyT3y6KXoA(i*tXt7w8Z@y!e< z-|rD38cZajvjt}|^W!{QZAT6pr>s~+-ZpdAwxa+$Z4K%dd<;QhOPV<-@IbJZ3yreV zwoxLshP2cM5#SM}%p-q%KLlO>4G5O^4l!`4E6#|EFawGyrp#VjuM)*@A%Uz}CJSh!8J6Nh8h~Ge~trj8glUvjX#x zBPWRKfTMO;rT&>WHgnQ;du=xmM8F^?fL4Ujo8U2ujxCr|soiSx;&vq$ZD(V<0WYPG zHKZplT7N2bLqtSvSFKmI95N)B)gfPZEs~C1we>}@5Z^&^q?D1DmKN4zP}PJpV_^^K z<%W5qaQ)XbmnS}k)1W(&%Re^PkZO8BY#B$VuEkNcIsY_*hP1^4b1_w+au2QR%fskV zsYJG?){|y=VnF{UTFt6w>rNCq2WVL1hU7#I{Q+L1YSM*%^VcF{EkN6ts>H5M575@4 z6(8_?Zn#@J@&htSviPr&HwKC>>hi8r!Xk3<)Bi>$HgqJu$08AlU>Zd#fZ!D^)M- z6s+x~6z1H5J73OI)3jg+rL%KpD6~`@3|!C+E;RJ`SFo{>%kwTeQgnz`OU=8lMv9sU zubLa8?Wr{PAF{thqaQs&403q}&H&jUa^$5d~GAEo6rgc-I zKsa!7QyAc74BRvvw}VUwH?j!_lo~9W;zE~nK+9_7I38+sI7AbE)p23$l2S5--v@qO z8Z#>jUdYc-P)AyAb>;-AVkRM1?jCd=_b9yViB z;Ekm*ckg@Cr_B+mRn`Cza1-v{cUHZTIn40M$0l>Qsg%?ZgH{`2k~f!wpk*l*ZOmQ^jEyL zH`Uv!Bx+|zrfg0}8ZtAaF_mo$ zb|Z;_3tG}1RtkrCB4^Upq6wXBuT3G< zc<4Kz9#`tsK|9Zg=6`em9Qe!%BfFAma!0HNSadkm=!9QicEnH=?CYcrrnpX+f$_1M z@~hgya+XK6e67UJpf_H`Y7^q+u#7}0?>|~;u#foNlCBVD=64KkyKdU?^vTY1ZkAKRD7Im_A!IPEL~Ex)z$gAA zoS)-E7rH@4a1?xLW;E!+gl91y-Lj*j&4VS%Ow`&^nFYco2S*^yX?Y^l)Yr;yBW$(w zMRy1Q76pKO$&*#nGY#-=M_Bpz$(#am60`q|h zUOlz3^zKarV50F>bmcK}9XIrZy266^ZclBQ5=wo#7f_VV3#hc7mJ5B}S38%w z^#fup_64L6->#;W{lMHg-m#+H9GoE54@O}0{GtF_|mpup2itEqM%bq zv~UVXXX~>Nf?D~-7zoFo*b4%xK!_`U=T3!yd_dA2JS}#{~02>Eu=0vY5 z)g(n_ss=E08JRZ(nkR0VW61TB&Mio z>M*Ekramg=$#It=F~vT_xLG!s7QI-hA!#h)_^nd3i$$JE0DY7Kfx*9|Uqh|wvz0(0 z(BN`rO#y5m@l>5nvi4hle92eIB$dh-)0VnF`#QFaqv@z3)Jy zQa*_E9hMrUsbWFDDddyz9Le^_Rgg0;Rw9>9q{sP>#3GoxEK5S-#_i8>XhhOT$h318 z#St*vk!X+8E>lOLqXQ$sh#NX0P2uIQP82i>I+@kHYlgH(V>L+`#%t#ml#1!|NVy2I zi2NMeW@n}YUvorCGL8A*8uBl9^>L>7cHsQ4nVGAC9hjplHG2az&Dgt{3t+G&Jc`7y4cQi2HwDRh#Y!MQZrJ%d0t_GFqu~%e%BSQV*QW^~e zUF>v6L#3A&INODYty%bwM=5-mJ4QJNtB|OaYOiQ{8bs^xzoAK@go;k2!8m&>;u2B{ z(*fX=uh+{;7o1F$_4Rr$a{Zta61D_|poU|0gnOBOY{@>u$as)A)#-a~Ww7 zBY{yg7LCpH!U+pk#)4A!kA;D7YAloy{NqVZKf8%No;m=3DFmM<)|>F{v*Ob4sO zuQ26244zKzbWFI~bhz5y=}=n}T`$;C@lO{W#DD{++^K~l1-yx0Rfb;U-&xE3>cKt9 zDUJoxv@I*y!E1d3>rvCW@Z0dJjh?IRU{cbWQr7A|HZ*?;j#Tn5gtXYb63KS(I>>bJ z7>F@x9`J}2>$&qVV34ulfML%2yf+W7TsHiFm=9x!&tcrDTs`Ht~?BO}O5YT6J z5{G)(k){ZX>QkWYs_3c{3$*R4?`!>5fT2Mt~k zdBBfsvCQuRpN844;6c{@GZ64w1|f9%gA48!FPRAq*-m(cncz-4!(d@mGkc7)a-jI- z;C{-~mIL58(HYr|V$Q3UYbPs%`sYb9f#gx8nYJR!6(E6g^UIwmZUtmp&lNBV-dTa> z`Is0ci~TDgZ>y2T^%WQk7s(nh6S?>+LEvoe#I1y7&%dh4YGKe;mepX6wq#X^-udKC z=8H)_!8%%_E_9(%Po(raU&et0^9zC0e16>CLckQqUIie&5XV;q6=_@X$-#tE6x}Fo z6j&P{!?>jgGxUj@2fz3(q=Ivj;(>(oYU;5Ho(y9is*W;n`j$Op8iM_YCmHv7E#HNk zhiLJv1IC#n6Bx5Tm#b;6&)l=bsHS;+~ z>_d4XzA_=a z7z~H$sw}yq;^g7%IgxE@m7zya*b-y}@`{RNOy0eU{S0RkiQN&^h|ILK%yG0fF$A0E zA`kh<7Vzkr^I#HJ^nnJ*@2+ow01E$698GLGd5Ot0<~tldod3PXnwoFbE~HiK;I3Q! z7$*aiZ-rgZ2%9d;O2gFTxDE25W)hOEkqmFGMiLRuUx6tVaLJdl{z4jU#x^KtAFEJc z&0-)r7>EN>Mc@+#se!v1oF~})|7(FAMc7Wp2CzWg9^1jeITrwr_7t&5cA@ughoc*@ z!NkhZt?giNtV}U>57EOLTf8VACs!|mHNzSo7smM&NXtKPt%lO_H#>xmAx<8KX5y+y zTK63oB%dS8?*MZ<+wrg_a&nBPPO!3p?O>6L2^HP$$5oK@-q?aI*$Kp5=7sYI`B0sE zJJEVjVPSN2M#Emk)$TYU;B0U)jxaq1?aTD^-Lgx&LGPZ=pm9SHwkP+Gva}_z0%#6k zUEgj5*9~W+9zB3nH$nE*$FBb~kUM>$t?~Z@Ab-#dQbTOh{oeqoammG5P&EC4t~;NY zqUM!@Oo$hW+tBO#M(43!KR#b%N4?K$CF5z^4m5bY945F+I}?iuB|f;3m57BZxry?@%#XGoz|pHe zeh%8h3ChjHk;yPqpTMowTP=r*D+wY*m|btG^CMgeNk3|fj5~4SKy4%)z*m)gS?dXG zI`nB>Zza%;Wia#Pe2tu7&`VoFE6hYTmFYwZHMP2=o$>1P*F@yQv~plCpO72h583`^ zg*@XjoFaTU?!WD5=@qd9^Qwd1{;{La%V97YbNQRm&MS!LD7>MqOS78c6dgW7qUa}> zV|*NG{FOjk+Ikb3l_}7I?SS$CIf@dmq8L;n9nGJGVx{7mleJf&Ch>^`)lMcehwA8m z*3MU|VI#_)!Yi;$-=D@N83VU#1%&0TiuZAFun@@nuAl-tAs2svC8C;*Gue2n2vQEG zr{9Go-7Xz_rp9+_(x@-F(5hddI>T*&<59(SM8&}%9Hp`ts+oE8ovvxSDDuix z`#_Jjcc41_b`8^Fvx7f0x(<7l9}B+2LhrqfujfYJh8Iel&&ciJjOVG!MKHW$W$xpv z-?V%nBTgOaBiF|9)!Js2y8PgG?6SST_2rZLjNtT93Rf|5+(d*O1KX$4-o~>(wCzn^ zfPYxj-gN<-p zmY1&l7C?iuKfUO3INa&+Q=mF7UX<@G9_{FZ+n^udotuI|Wm6Wb>4JF2tw8yn>H(Kc7$^IxFRwEuGqVk~HO@0D1PN|*0J_2WB%Sl7m;b(Gld zgf3>#-hqer5y#bfM7dWbWCSTWkmH}=e{5nfHZ#h6`2cKT%%4WvgFi4}_x%ZVoWX=6 zxeR#7RN3(XHXmPZCAQK#;B|84M-ec&@F5~k*a9%_w!4Xi_MjO>*x6;+W0Bj?Gu>4< zHogwWA$KC2%#7>5Jkn09)M)-!=hRf@0VC(_zrd$hA7_t3>&Mtp#Q5R`O+-=j6YNy- za5<21-)CYNJtgjM^*y+ElMPmOp0JHT)bSv8l! ze@{HIat;u647kln8Lnih-c)X`90UBDi9jl zaT^HDa9**hYS>h&qK3g5exmuAaNC`DJ}teg|PK9s8Bs{ z^kS|a0qHnV0Fv~XtsG>MZ15ur(UD}2P0)-wvTQL$IXl&iNBGoHfu{Lgg}rPz0GF4N z`NTj6xv60<-9`4G{SLCf5+;!EBF9pIBU)kCAX7`equi3t%mW)b?a>kBl;nw z$_WA;3y8C##BF8eu5u?tTgqvMVYtY+Zp004_1~T4E|t0*oDA* zrLx9+A}AH>pn4qlz)921x+8p&y&7idhIwK=U!{}edg9gyW2l3{Nob@cJ96-nTPSm5 zPBqudxJJg7ajc6V52g$*GS6EMtqhF-EYx#vxy>uy)uc^6#=7SAG`{kos)g%QZ9i;? zavxx8ZuEQ-uB{vAhk>)q=6=uk0o%BUxv5??pcq!E=1bqzkl71j5ZA+M%HaxJUgf8( zB9&kAuX^~y-Nou0i)%B+!VmDKa0Uz&6O?QQ$p%kc8>h4pIZ_tlO7Q}wWOpy9wF7I( zeAG1D+I}FS3P;fEBxl)mnOhK|by5+_!)V~4k$sfT_+ka2p9%vMEjaOQTR5--*KHxs z#Z{6U*|L=@dM)-vP7-Uc?3~ys;j$6<_Fr|!LW~xxG){~4`dkJU9n=EzMUftnj z-vm@W_13^MItGF=Ka0mqKKlU48U5?R$-XlX3OZj3aU~FAukyi;B6Xkv{@PzB>Q=61 zM_Y7gioaeG<@oF7%{a$h3c{$^#Ku@~C|?lJ+RQo;nsJ&kf@NfMi8J6g`36P_?q)dj zJu*`EwwOe{LhzlVQ=7>+2yRV?JV8k_2@i#vBQ8`96=xHP|7iMsut(5YP1+HPVH^tu zvLuH=%j>#b4l`zH=_VO1q$!jXCikUJ!axE)i(-7KQA9Z4&_JH#LcPMl3^OQyw&X<% z!eu9Fat((WzaI{ebL{7fa9lp*b@H6ZLBYCLlW^6Lk&p1&5h3>%w;XxWw~e56VTN(I z27(WhZ=abtns(jasG%JvGt~5MB-TLOhvXnWVC$2=BU5)Mb%+LawU3e=MSkvpC@|HW zT2Q0K(fc^66<10mQ)11+8kO-tT%K{IHe5rT(^T3Uqh$*Y0l`{v!oEKxMa#43k7yZQ zYjOABl^85kyBI)+S9MQ}fQh)g5Fh3!?{0L2wm3T$e4cBxs3(UA@u8!~v5<0HZT03r z70Gp~7wM>ceclIOUF*Ts{6igJiLsUbBJjn5x;F^+#&O?rEz^e(I=)BtaEO$WI;RfH zPR^cy%wmMpa`oHeCy$~`k>)z*DxNgZ=5kt zZ0K_gNooLu;|4G;7()#JBL+&9A~-a*0lsp%lMMh`Rl;Jot)^?3FCVwwY0AHLNcsUzK#RA{yOzbH+m5# zcU35CXcIZH(!GRyxx=LF@GJ@63Aw(>#r=rwwAyy ze)m;N&_U9B8?f^`3H*lJd(hIz>=ur}yS*F+SDO3g23#GOfR=fo#kQ7huT(h0a}f!w4axMT=?hnR(#=DS9TXo71MgV^XwbAk zFS!`Y>K3W#kFB^3h*J!2w*#o3wi86yUKWRHS^Tc4VUOb|kja{nn=_QM9PcBXqv8&7 z2j72aiwH?j-==zBIa2ZA*=kxn|D>(u6zn8L113=&u;eW|$l|E1U5OA}1s$-qtTpqc zQ%^dA3D)X}@B+SDif^9UpNPF6l*7hWM<_LQ5`n9qcLbVLsn+oj_+tVs7Dz zwPBdb%}&_w-_{8vfdd^878@4mD&7Uth&G5G=8Tuf&X}$0GCU73s}R~z=ax0C=`5!x zVWTmn8MYMA1&v1Uo?}PlR^Vf%`()Y2UcS3nL;1tQos}DDKI(#%S7N#W1F_bKkzhr(n^r0L6GX+8?XyH3R`d1#dO> zCCva}o9DwKZ0-rum<O08_wy$4_uHDr>Rh20|jR#>yjw9|EE01A!?MXU#C=Fq&udVj$K-IefnASXvJP zLU1eWOGpxVjpcgjuK@j;;m^d?7*~7#4{uF4PpR z!Z5<_nG5Z3n}moV(`AQ0HGxe&ytogisTf=W%vEsZWsOSRvWa2rF_PLuV2i&j8ux_r zzRzks7|JUo8s9Z}njVdj{Y=DWyc#H&%De+{1LIYQAP%8nwUxny9rQZ1cr+$8`&J$f z#vTnq_iQ}JlYG-)(qdabhUTZqI`H8`izFT0u-}Bt?ve~#C72G%a7c&xyX@}^xGLy& zAVNvf!N738Q2?z^$CNgN;zj^tVcZuY%HaYpXNnmaXhY!{;5|-*=BsIIC_)B|<$P^M zB&Vh!h4t?Xo>fab*oiOt;f9+myu~F-IQ~~OgFX@Gza8#l2l0zLu4q_pPc1U#F_k(# zktqior^4H-5m^{9|3b(Dj>vrv_o58R0!cKwj4P>^Wy#%9ys)j*L$QTBjsg9DRFdJT z7$FwLcl{_}Ir7ndA1!-XN>iw%2s^_X#G2*if3tznWIX8nlioV>mW3z5(c&X6bW$&$ z01v_e6;zO_2yWcxP*czI@I#w6Cp-+gdgl>2;$kIbR{~chdr-hcaLy`bIeS&=)2WFN zFIDW^DC#i@7*fT~4YP^L$xBaC&T2N=%$(3}aKXb_;)uf%knO@`@y28_sV4z`QCK%O<<2SWRPD9LhTtSY>=h+M#o1h;th)rMc$p*gs>PvE}JeuSrHe>n**_G<;!m<*HE!5y0`(H zhpjr}+MMGP7pZKMaBJki7+e{;OA9*Zy^M-{z+pqt8r)r4CE&(tRI+(vs1>c54oAiD z=`iVR&LcSrGPRn&k8dgL_of_3Q{IHYs54&nwl}Y8)$D=ORBsuev`j|u^=~ue8fwGE zeF)?P;|Mk)1&AQtCAysy7Ed&$jDkdy?H;*_md{ktZ#miw60M*}8LK zgPnVVtKZY-qJ8Cx%`w>0+4dO{-?FCT;D-3&*BmLhE3TTmG#3n!FS&i5eB6U-&BFkV z7i&6D&!$LB3QfJ{LJc21L`=TzWR()YvU(n%YCgBX2wJ3^%>#;`+82uZzH&A?Qp6mT z)6SRskbXK$UUR=2=Yv@V#eVKankUd9emMo~PW<9G745u(J0z76EPzq?#}V8QU;gew zjJfM-6*XTV52pBCv)yR>0;m~{A3a3ch!_>qGRL=>hnCo3jtjk50G!~U5U3;WR59)X zjx04zf678oT9s9iD8B$G`fMR^2TKEKDIPl)U>?Rt!KJlxF#gX9#U3i-5n? zF8IaWG|qHzkxgGJ(_NQu^eBUT&Fzr6ai6Abire>6E(}mrF_Ogy3zQ> zn5s{!%Putd00flOGO!k9Ex~}kSqu)ux1GN43&+ai#lWPpo2S5wmcSD}eF?~sZLLq1 z2%-}=8B1fg+0xA=Fw7EOtnyf1>l^Fkzb^DqhlD5*9*kV(r4}==9E~V(8(ffs@z+r9 zD>e)@ z9(jIbZ$A85O-%ZMC-MCtyb&($2O)Opqi{QVvKq7A{tf;y-$JPbn2C^6wn;u(1D*oS zELqc)n5@OG6$YGQ0 zDpO2WFaEcs%idwz(9*lpyWT+$&M;X+YA{flw9v@4WWobcQ_3Da`Pq- z7QVZ1wxx0h(@T9;zQGbB)zbWpsDI!+-~juO*`BC2N?gq8Mc2D1&UA6*mX?;S2c;-M zs-ltd^`LW}CAW!38`5n6^WZj3Lo7+%AWyXr!}Fr&?*kKzka*Rod|y8xfK5`q!Z3)S z=bL2b*TZDQ{{#+ay1E&d#N)5LqClDtApLl*EX7}Y186ljWi`z4-U0~~JO$UmHrpb9 zMtd^xOS1!~R;xTpyfGB*hG-10uj_z%T zpC9iA(t;h>`N5->oOZ(LgvYv6uoLzjbAXdOfs`The|KHJ3$K3Ki(*}N$+m{Wuz0_! z{4kdAlaC-y&Tfa;IIv5;MHYKejZ5G95ZZ#Y7bEc54Q|AZHrWjU<#iJ|4q3b5S2(j< z9#7pqk_(m5Km7<}*6zU|3-@5}Wa!5zZ2K|7Qt)e>_@@bZlpy4wWeLjRA5S$?!yuW; z_sTLJlCyL1u%iU1h#>^i%@VL&9udZIP`qm>MwCKB_JH}W+$-#+PvoA8mkk_6VLK*^ ze|zOqsca8GD!i80aMV6KbfXRe6zvXzR!k|=%DDizc14i8VZ(>*>K3KgF{*aA<=#>Z z9H;}0PuOb}NC2bILs$ZF`>fGQt9IWb3_KCya`=!@wN+%B>9W|{>%#o1vx|Zjm+X`S ze8zkZG(-GrO7U2>Y5s1Qj^@8?Gz@CyV=)u%tWs?6r-LUTltK{aKfIA2>)BEDqvJoP@*p!W0 z&DuUrt-c2Pahi40a04!`js~y%S>sIYUf^PRmLGS&mb-~%Z%1>5g!m^h(M2%3f3;2N z@g4LOvq<2}PB}aXd6u?SxD?NQBRjYMcsa_&V4=0n zOc^cw&UETDykpf|U;WO2Iv*cGPMgcpK-@Zc2B2u01Q%pXawJqs>Szcgm_TQ@%porsMhf?~_ptC8ParNhp zpRu4DHtVA4#?M%agw-*Ic-YWiFx;g}ZzJ>ikCh3u<`+z+T8$dNLU%CNC|w(iL3R2PGU*!(QNCeYeEh3C_3WZ(+{&T|hz-wTL=6$|>dIY)@UPFvPyH!YY z{Noy?%Nn-Rb+Gq_;{I|D0cmy}qLm%eIHe3XaeogNZ&n6E^^4cd#}R4az?{w3AzS+F zfD6XyH}t`0S|kpd7fklQVZzgX1Bc!nqpL~HuP<~G1_t7>e*@pIIu@H8Y{CsB?eB6n z$-m?C-rwPqz%PGN|ABSmU)}z|`tz@MP4D7wz!t?jAA08oc5Ly>n|{8bl*8uLO{hdj zNXGTnHvwP%%I_xM1QRwtmR5d@tn{J%Ev0jBnsW<+sZIsVQDiDNRDMO>mK&RX;X${L#Jlbw|p?4U6sj0Xh)XBAXvB<`EFlpmg~s?@cnFYgCr8m+VJDw7!rT@pk1Jgx@X>q5vk+#lw91$o4VG7zr^X;d38L_1PzHao}(&; zhKPRsu#sta&;MWjV|sE5KH=Y}^vn!#V#2y-&}F`P2E=;$Om0y1+)*4zo;M!F#aWCf zn)qCf!0PP!RO8`@w+MvJ#;xx7u;w{vecy9X$B)lJy(wb={d@_|=BTc44u-tIM3xg$OsnOOv`egWgbqZ2;tJOdIje<*(eRI95lS0v){T!ifIp9yfH zVf$t60-K2oj@_0~-~u~CoB|>h?mC?Fp>`I5b!d`BU}ySiLtuc?X}*164a&3(w6p5g zvmYJ0dB27g06}h@%00>W&7DrEaF`%J?ywB>q%VeGGD)svO+Q!$Hl#P*n*s0a0uf}p z6zdrO%Sz<2rCr74i}lVTF=X=^YdUL#uJ3&k>qk4S0_%!!NIzwkF%B>|9j&p-OX}3= ztfcFVvkq)eDc@kz1G<(CU9k={MCpp?KGp{rK&Wb`0LVB{J9THVIt}V6$OZdw)0rM^sO5kZQHo$;6$pqRiX zckE`Kc^FD_^<#uw^HSJ2KYKe!KB%Ht>|5fxWiARfW{iWqm2tUrROSP35}UEt*;Y>N z^N3ZMa!ro#8knB*jJaKiVIxLgneK>ipE#KooOt$j@B_>9_E8NjmRt7)iE}2n>Pozo zpKpen{K}I)6Te^fgh{#jl7Nhna>5g_xvro1>p^tARHou>yL5j*#cOyF5F#t>0Z$^l zJEV=sNY|z*niRc8S>I%QC&GZ5qOyIJk6hAMqZ`$%zgJ<4&JjGRP}N-)m(PPva)P%9Ovc9y$exTv$P5SAVg-C(547K z)!RC#m2QPZ_vm<6Yy4ERa*^uvTjnO@z(3C7D6o|Zdi$#ea?LGFiHQ!O_P9n@C!aib2w=Epg;}kV0f1q?)oOT??#8si$qR1C(LRq*gXG>F zc~~d50V9xEgA?W*37cv?dL5Rp~FMqCe`e9@^KnJ2j8&gWutIejrvwTalQj z$1!p{B2_J|=dLMn^R3VVwkoi(TpfIl&=@JPeg(Bo$4YS!?wIDxf@@N{GQPBw7$PmA zSxo4Q@ud}0k?RqCVjw&6qxLhPRM|bXDEM+?C zi$})gNWdRw!z`Bj_>Q0SB1$FjhhNdoO#-$#|8a6^;#MbONJ|6TC56 ztR`y{7(U~rRh*#0!Wzcc9nxr6g9*7~vnD3QH^|JI$PY=wByBWIu@N9Ps0*2Y@?Mh) z`rZFtiRcbRVS*RBm!u}D0hWpU zqjq`}%2*ivMfke-QHAevV<-Lo*C8; zI*Z%y!P)RRdtzExhl~ewxa|`3*(2&n`R#e_ulm!YXt$cGw(|Z0?1o%22Y7<&1@npL zVm3Nww=@vnrjTz)GlFTqZ*`p?9n>B!Y6VgU@ z;|lrg2^k5N?0sohL`_9?S%1tt^Vvw)kmC(-fNnjXg~0!*eO^+lxyqF1nyVGsT(Ncw z2HiEo%6P9{sn7E1=gBz%t9ZvMgx`#Fy7W=%{Vr4rn; z8zv^oj+UwoSIi{J-Il7Y`OvnN8qK^*#JVMl{fJ<2o0 zQnMd4KQx8O9zBClZbTL5F5DEh=iC&!-e}H@LE4OGFdFInaS=BlPmTAK{VD8whQ?|R zCiMBADHMCD+1$T0o;KPo2z2k$sm*Yiv*}2LuAXUzzr0d}&q47th?Ex8D$~f44rKav zY;2IEZs?CETaTD3-cfmzvh|Ix#vapZBYoyA(>p>f&XbSB43r|0t=|$mu{xO(lIfKv zJJFdsan-01Mlh(avOy>adt#n{)QO32GOP9ohtU6Yq8>)*?(CfaY0#PKx9+UsELO)l zMMXumj>^(hEHgT*sgk)0vB$Mj>$s(^i|TJJx)ZvnK(Vho4D?Oj;+ zj0q$5yg`<=tf^CES00!;Y0Ff%39_-Mzv;yQS>Dx2QTmZQ(GBL1>sPE7-<1?yc%c)=I8l*T-0y(i`%#UwyqdO9b^MN|XL%*T<_5 zgK8e&JnJPb`_RYsYrpiN->3J1Q!`24>;soq?&(9;gSfsZi|q#`eVGqAeXVA|PWLVM z8c)q@p?Yb-2~u+@rf*VHSPF*sW8X79yuKgJwKF}@kE)pmZvE-B3e&B1f2gqnalF)@ zDQ`FRcz>&@q2e~6Tpu(WKs8LxnFA=p9?uN}*h9<%YU*3nagm$eNhLCw%&HD#X(X-@ z8^a!UPiG^lO6P%;%xZ@%q+VocnEB1mb8HSbvN?!R=UEvG5NY%@2Ar;h+9<>^DZ=|H ze-Cptl_Wh4&AHOZxIqxPSp{AgL|g1$IyA_!%=tu(AtJJ|PcJh$)L`c-*h_!1o4RnY zHOTma45kn4Ui)>hlNu9-Ff#TV?}xT@2cUVC_GIr<$s)VX5lrpwDi zX@j#Me>pM~8gde_m)sjl2V;s{UFr>^Y7g3HQnx-~8g!f^D5*a1T&rXSO4E9c>JH|ld)f%&9b{A&`_niPS5RFzwG6(Joz9u7>7%8X zwx_c!y5F20C}+}XXviCd-)}acp)b<-2&g3THV-(4p=*oE7(Ye(EnT=BPfQj^?J0 zO`FKI-Q!F!XNnD&j?~3zLiU6Vk0`CEohcYkSw{U*dpsv!qnCNw^+{`JyzrUGo063+ zZCah?-s+W@AKo9&zHIyD5*L}UJ9$t+d-&+%0!ztM@%Y% zs&)=cNBJ7bk8{)zxqK+5x~bc+xvHrZMiE)0uk4=-=V+oTny)qHv1{31^X4&3_SZf0 zR3L*lCmcDHZUCRJ&SEcFIXx=4OvSr@dkS9seq9eLzVT!Uob7O0XDXgiJ1)A6{qf@G z;NNX}O@0c=YUl#h3d>%6Kb~H|_+SCa70wGNrT0RJsB{68a;9S`&WQ`zjuICl!y!q8 ze6)}O{$ybT9{Dy|9es`G3q2y%d^b==K*Oxxndcmv6-=~pH1q`ad1wWhL*c&hBbpP3Ide?LR4ysI z{t|}qIEYu1O)DXE6S(7>m7Gv3tzt8>Rl?T_={QT^uQkMcMR%}@qV3<3)mRH)}aRzpxXcHRmWuL4zQwGo}NuY6d*%Gm7X zBu9vAbUJ=^XFli{?UZzIDO(m!WElg92b?p7d&p!~A?(iLHMGbmIJc}pK68JK>b!dL zrXVQ_O|1G(=1a=W%@V^p4?>=@w2*^s1r5nnot5c>;k(hj?qAEOnFYywy}A|(_ZVNA zD5OJO;p(veKXJo#a6$h&Q;&EfQEzmYFno4~6c;TeT{#NdHGg8g3R&H{C{(i6GsyGT ztI=|0J$-MiUQ(wbY;O9p+E%f>yyY!^8yM;64eA+LyFpFUf7mpNR3BMbq+eI9XcZy+IQamUHoQ(M}c|}BdO<%W3&T(|FSA241yn!hdst{4Bj?dWz9pU6JL@goIiFf zvbm3w-^t67jWZ{QB2~zM^~suyfDdbnhBI(HJp4Br9doQCK<@sH*N+2%wM0W zn6iz7MyI_E-`V0|aanJ%u$>U|qFcxptbmO>S?%nvgI!;ZegCj)#~scJ>vqEJ8waqG zXNo-Ki=7PT{iGXJWZ#<0IP!!MdM#|1Gs*ow3q7TW{#qf)i@TV&#wP6GE@))tc1-$t z7sFszG)7Zyo!u-)ErW<6Y<%y!>}J;`YNrXH=;1RmBYje4g8s|h9{C|!Z)t)v9@tH# zT*)kX>VJ0Ac?0tcEkm$e&q5!0bPw~;PGnOUdtrc#*xs=9fEe7V_xPOWh^l z8~p5y3n7VlkQ8egV6i5|TzOPSk?^ysVQjfR`UibxOQ8)5V`8%eyvqY)+-LZ;f$t)n zU3=FNx6)_A=$t9mDL_sv<@%A^??QoNCYNIB`<_a*Vm6kvBFNIV_aF%EoC&WIv=X84$IPwJulXjqpf3tR?pf*xMj9P+4-RwF7ERZYYO?{JAH_P z%z1gmk)hW(UUB5!2Z{53^~S*YcMXdAd)~S{38aWE{_q9(L>y zZvCXR&4f^~C-bb2*bZ#Ns%;-3IC(&@?VGlFsNK>;JW+m!-oUdjFn|xe#SnWY5wJ(k{6hFMrnpUltOh)8kZq}(-J#59hKqxP>i{X)9 zE=H^S&{`&H(HV{(RZqaroti@0-X~PEC~K$D0(`xdIb6uKj(0TL2=3IiM5|JVtr z)s%AYgz9LQ((5GjW9sZo<}J%l!oQicZ=Y02cG@d{O*~Y(3QoIxclUbo_Lr)eojc4#v|A&q{q;}NX=WU9PqR>&#_}7^ zQm3h;^Fg3n6IXIy2|dF*_Uw7;3{3ADuL}+(-{~`Sra64p-^|Ta9}Cj@u&>yZjqh~t zuT-AMI$|3isD@YCv5uv94{B>l@3Ryd8Zy^k-uzmHx%bG)&X64=O2fFjTKRayXY22c zKEon6s@3V*Qs-+G;At7lx>AMf(*=QNHRGH#x2Tp*R6}--U=h-J$WLEG(PQ-=?s2P- zE}(MXv`kk{CT(>2WGGWoPS=uS!x^m1Z&WV{85XCci5| zi6chKiKB&4)?JPL&of{8-NMy<#xVr0v(IyMt=fgXvFH}uBMWu_ms7)Kno~*X)e?6h z@?WgzAa9bN&eI_2k((tfN4C|!X_h(rp*3V&^}V*nkmAWw7AA8I-tchZ zXf4~s>iK#pOXJ?6e_<8zCo8=1l|Ej|iLXg2=U=}I*desrk{>KjUO@8HviZ^~;#W*H zOuDx(sI>BR@svgvl_P4DQG3Flcu3(zh7D&zydxcTd0f8y3`4O7*CWw+d`D*+y>z+p z@Z}0!Q4aYTJI1PO~& zMLzg9HVB3wO_*sS9}^|1apRhrZE{?D8!!qsaC;V!wDeu9?G;+a2&$mzp> zMz3y&TpwlnFMx2e!2pQn1V~Q^<+^I* zF!!pk_hn6JpOrUJu5FF!EzX{akd`-CZOlCfV{b44Ub_jeUc7sqXQlR85?y~@xMeM1 z*w#ZgD9&;j(iQ~fx2T&?Y{~#u+tLrSy**74Q?048;HIi?SAG9YWXz08A8p?8xK-;_ zzp5s#=AQgj4YZRty9N7Y_DFNX*IT!s^K$#%7^z-o2^UaiaEV|3Zw!dJwMvIwcW!^t z->kzRc+O#L8uHJ2$@W{YMz?>1zYHrrN?eW8s7juE?TDvu&rz)X6O7YYl2!BRw^@}h zU7Qvm@7^w3soHn=y-Z?`o)Kp?y|IGP|vi0>85fIuBpFDleRLPk|T^3*taLNC0&~BB%!*2K7M$&=4en zMxZfh0v-WPK{N0ucnmxanu8YL3D6R>0-l32D}6c!CJ5mtOqZH4WI~Y1e?HS@CtYpYyq!< z*TLVwR`7T52G|DP1aE=uU;${OZmN^* yfQ#Tea0z@5E`uMyzrc^+-{2?kGx!Bu0aw8_a2?zLH^Hyq7WnO*N%w9CPx(JMsO!uC diff --git a/dist/platforms/ubuntu/steps/activate.sh b/dist/platforms/ubuntu/steps/activate.sh index 8802de9c..81eb2ffc 100755 --- a/dist/platforms/ubuntu/steps/activate.sh +++ b/dist/platforms/ubuntu/steps/activate.sh @@ -74,6 +74,21 @@ elif [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then # Store the exit code from the verify command UNITY_EXIT_CODE=$? +elif [[ -n "$UNITY_LICENSING_SERVER" ]]; then + # + # Custom Unity License Server + # + echo "Adding licensing server config" + + /opt/unity/Editor/Data/Resources/Licensing/Client/Unity.Licensing.Client --acquire-floating > license.txt #is this accessible in a env variable? + PARSEDFILE=$(grep -oP '\".*?\"' < license.txt | tr -d '"') + export FLOATING_LICENSE + FLOATING_LICENSE=$(sed -n 2p <<< "$PARSEDFILE") + FLOATING_LICENSE_TIMEOUT=$(sed -n 4p <<< "$PARSEDFILE") + + echo "Acquired floating license: \"$FLOATING_LICENSE\" with timeout $FLOATING_LICENSE_TIMEOUT" + # Store the exit code from the verify command + UNITY_EXIT_CODE=$? else # # NO LICENSE ACTIVATION STRATEGY MATCHED diff --git a/dist/platforms/ubuntu/steps/return_license.sh b/dist/platforms/ubuntu/steps/return_license.sh index c5bb721f..f0f68b58 100755 --- a/dist/platforms/ubuntu/steps/return_license.sh +++ b/dist/platforms/ubuntu/steps/return_license.sh @@ -4,7 +4,14 @@ echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory." pushd "$ACTIVATE_LICENSE_PATH" -if [[ -n "$UNITY_SERIAL" ]]; then + +if [[ -n "$UNITY_LICENSING_SERVER" ]]; then # + # + # Return any floating license used. + # + echo "Returning floating license: \"$FLOATING_LICENSE\"" + /opt/unity/Editor/Data/Resources/Licensing/Client/Unity.Licensing.Client --return-floating "$FLOATING_LICENSE" +elif [[ -n "$UNITY_SERIAL" ]]; then # # PROFESSIONAL (SERIAL) LICENSE MODE # diff --git a/dist/unity-config/services-config.json.template b/dist/unity-config/services-config.json.template new file mode 100644 index 00000000..5a868f1b --- /dev/null +++ b/dist/unity-config/services-config.json.template @@ -0,0 +1,7 @@ +{ + "licensingServiceBaseUrl": "%URL%", + "enableEntitlementLicensing": true, + "enableFloatingApi": true, + "clientConnectTimeoutSec": 5, + "clientHandshakeTimeoutSec": 10 +} diff --git a/src/model/build-parameters.test.ts b/src/model/build-parameters.test.ts index 1cb37ae5..c41befba 100644 --- a/src/model/build-parameters.test.ts +++ b/src/model/build-parameters.test.ts @@ -5,21 +5,17 @@ import BuildParameters from './build-parameters'; import Input from './input'; import Platform from './platform'; -// Todo - Don't use process.env directly, that's what the input model class is for. const testLicense = '\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \nm0Db8UK+ktnOLJBtHybkfetpcKo=o/pUbSQAukz7+ZYAWhnA0AJbIlyyCPL7bKVEM2lVqbrXt7cyey+umkCXamuOgsWPVUKBMkXtMH8L\n5etLmD0getWIhTGhzOnDCk+gtIPfL4jMo9tkEuOCROQAXCci23VFscKcrkB+3X6h4wEOtA2APhOY\nB+wvC794o8/82ffjP79aVAi57rp3Wmzx+9pe9yMwoJuljAy2sc2tIMgdQGWVmOGBpQm3JqsidyzI\nJWG2kjnc7pDXK9pwYzXoKiqUqqrut90d+kQqRyv7MSZXR50HFqD/LI69h68b7P8Bjo3bPXOhNXGR\n9YCoemH6EkfCJxp2gIjzjWW+l2Hj2EsFQi8YXw=='; -process.env.UNITY_LICENSE = testLicense; - -const determineVersion = jest.spyOn(Versioning, 'determineBuildVersion').mockImplementation(async () => '1.3.37'); -const determineUnityVersion = jest - .spyOn(UnityVersioning, 'determineUnityVersion') - .mockImplementation(() => '2019.2.11f1'); -const determineSdkManagerParameters = jest - .spyOn(AndroidVersioning, 'determineSdkManagerParameters') - .mockImplementation(() => 'platforms;android-30'); afterEach(() => { jest.clearAllMocks(); + jest.restoreAllMocks(); +}); + +beforeEach(() => { + jest.spyOn(Versioning, 'determineBuildVersion').mockImplementation(async () => '1.3.37'); + process.env.UNITY_LICENSE = testLicense; // Todo - Don't use process.env directly, that's what the input model class is for. }); describe('BuildParameters', () => { @@ -29,48 +25,54 @@ describe('BuildParameters', () => { }); it('determines the version only once', async () => { + jest.spyOn(Versioning, 'determineBuildVersion').mockImplementation(async () => '1.3.37'); await BuildParameters.create(); - expect(determineVersion).toHaveBeenCalledTimes(1); + await expect(Versioning.determineBuildVersion).toHaveBeenCalledTimes(1); }); it('determines the unity version only once', async () => { + jest.spyOn(UnityVersioning, 'determineUnityVersion').mockImplementation(() => '2019.2.11f1'); await BuildParameters.create(); - expect(determineUnityVersion).toHaveBeenCalledTimes(1); + await expect(UnityVersioning.determineUnityVersion).toHaveBeenCalledTimes(1); }); it('returns the android version code with provided input', async () => { const mockValue = '42'; jest.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidVersionCode: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual( + expect.objectContaining({ androidVersionCode: mockValue }), + ); }); it('returns the android version code from version by default', async () => { const mockValue = ''; jest.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidVersionCode: 1003037 })); + await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidVersionCode: 1003037 })); }); it('determines the android sdk manager parameters only once', async () => { + jest.spyOn(AndroidVersioning, 'determineSdkManagerParameters').mockImplementation(() => 'platforms;android-30'); await BuildParameters.create(); - expect(determineSdkManagerParameters).toHaveBeenCalledTimes(1); + await expect(AndroidVersioning.determineSdkManagerParameters).toHaveBeenCalledTimes(1); }); it('returns the targetPlatform', async () => { const mockValue = 'somePlatform'; jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ targetPlatform: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ targetPlatform: mockValue })); }); it('returns the project path', async () => { const mockValue = 'path/to/project'; + jest.spyOn(UnityVersioning, 'determineUnityVersion').mockImplementation(() => '2019.2.11f1'); jest.spyOn(Input, 'projectPath', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ projectPath: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ projectPath: mockValue })); }); it('returns the build name', async () => { const mockValue = 'someBuildName'; jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildName: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildName: mockValue })); }); it('returns the build path', async () => { @@ -79,13 +81,18 @@ describe('BuildParameters', () => { const expectedBuildPath = `${mockPath}/${mockPlatform}`; jest.spyOn(Input, 'buildsPath', 'get').mockReturnValue(mockPath); jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockPlatform); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildPath: expectedBuildPath })); + await expect(BuildParameters.create()).resolves.toEqual( + expect.objectContaining({ buildPath: expectedBuildPath }), + ); }); it('returns the build file', async () => { const mockValue = 'someBuildName'; + const mockPlatform = 'somePlatform'; + jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: mockValue })); + jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockPlatform); + await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: mockValue })); }); test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])( @@ -93,7 +100,7 @@ describe('BuildParameters', () => { async (targetPlatform) => { jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform); - expect(BuildParameters.create()).resolves.toEqual( + await expect(BuildParameters.create()).resolves.toEqual( expect.objectContaining({ buildFile: `${targetPlatform}.exe` }), ); }, @@ -103,7 +110,7 @@ describe('BuildParameters', () => { jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(false); - expect(BuildParameters.create()).resolves.toEqual( + await expect(BuildParameters.create()).resolves.toEqual( expect.objectContaining({ buildFile: `${targetPlatform}.apk` }), ); }); @@ -112,7 +119,7 @@ describe('BuildParameters', () => { jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(true); - expect(BuildParameters.create()).resolves.toEqual( + await expect(BuildParameters.create()).resolves.toEqual( expect.objectContaining({ buildFile: `${targetPlatform}.aab` }), ); }); @@ -120,51 +127,82 @@ describe('BuildParameters', () => { it('returns the build method', async () => { const mockValue = 'Namespace.ClassName.BuildMethod'; jest.spyOn(Input, 'buildMethod', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildMethod: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildMethod: mockValue })); }); it('returns the android keystore name', async () => { const mockValue = 'keystore.keystore'; jest.spyOn(Input, 'androidKeystoreName', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidKeystoreName: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual( + expect.objectContaining({ androidKeystoreName: mockValue }), + ); }); it('returns the android keystore base64-encoded content', async () => { const mockValue = 'secret'; jest.spyOn(Input, 'androidKeystoreBase64', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidKeystoreBase64: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual( + expect.objectContaining({ androidKeystoreBase64: mockValue }), + ); }); it('returns the android keystore pass', async () => { const mockValue = 'secret'; jest.spyOn(Input, 'androidKeystorePass', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidKeystorePass: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual( + expect.objectContaining({ androidKeystorePass: mockValue }), + ); }); it('returns the android keyalias name', async () => { const mockValue = 'secret'; jest.spyOn(Input, 'androidKeyaliasName', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidKeyaliasName: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual( + expect.objectContaining({ androidKeyaliasName: mockValue }), + ); }); it('returns the android keyalias pass', async () => { const mockValue = 'secret'; jest.spyOn(Input, 'androidKeyaliasPass', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidKeyaliasPass: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual( + expect.objectContaining({ androidKeyaliasPass: mockValue }), + ); }); it('returns the android target sdk version', async () => { const mockValue = 'AndroidApiLevelAuto'; jest.spyOn(Input, 'androidTargetSdkVersion', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual( + await expect(BuildParameters.create()).resolves.toEqual( expect.objectContaining({ androidTargetSdkVersion: mockValue }), ); }); + it('returns the unity licensing server address', async () => { + const mockValue = 'http://example.com'; + jest.spyOn(Input, 'unityLicensingServer', 'get').mockReturnValue(mockValue); + await expect(BuildParameters.create()).resolves.toEqual( + expect.objectContaining({ unityLicensingServer: mockValue }), + ); + }); + + it('throws error when no unity license provider provided', async () => { + delete process.env.UNITY_LICENSE; // Need to delete this as it is set for every test currently + await expect(BuildParameters.create()).rejects.toThrowError(); + }); + + it('return serial when no license server is provided', async () => { + const mockValue = '123'; + delete process.env.UNITY_LICENSE; // Need to delete this as it is set for every test currently + process.env.UNITY_SERIAL = mockValue; + await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ unitySerial: mockValue })); + delete process.env.UNITY_SERIAL; + }); + it('returns the custom parameters', async () => { const mockValue = '-profile SomeProfile -someBoolean -someValue exampleValue'; jest.spyOn(Input, 'customParameters', 'get').mockReturnValue(mockValue); - expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ customParameters: mockValue })); + await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ customParameters: mockValue })); }); }); }); diff --git a/src/model/build-parameters.ts b/src/model/build-parameters.ts index 4dcb3854..27c1aad9 100644 --- a/src/model/build-parameters.ts +++ b/src/model/build-parameters.ts @@ -14,6 +14,7 @@ class BuildParameters { public editorVersion!: string; public customImage!: string; public unitySerial!: string; + public unityLicensingServer!: string; public runnerTempPath: string | undefined; public targetPlatform!: string; public projectPath!: string; @@ -76,24 +77,26 @@ class BuildParameters { // Todo - Don't use process.env directly, that's what the input model class is for. // --- let unitySerial = ''; - if (!process.env.UNITY_SERIAL && Input.githubInputEnabled) { - // No serial was present, so it is a personal license that we need to convert - if (!process.env.UNITY_LICENSE) { - throw new Error(`Missing Unity License File and no Serial was found. If this + if (Input.unityLicensingServer === '') { + if (!process.env.UNITY_SERIAL && Input.githubInputEnabled) { + // No serial was present, so it is a personal license that we need to convert + if (!process.env.UNITY_LICENSE) { + throw new Error(`Missing Unity License File and no Serial was found. If this is a personal license, make sure to follow the activation steps and set the UNITY_LICENSE GitHub secret or enter a Unity serial number inside the UNITY_SERIAL GitHub secret.`); + } + unitySerial = this.getSerialFromLicenseFile(process.env.UNITY_LICENSE); + } else { + unitySerial = process.env.UNITY_SERIAL!; } - unitySerial = this.getSerialFromLicenseFile(process.env.UNITY_LICENSE); - } else { - unitySerial = process.env.UNITY_SERIAL!; } return { editorVersion, customImage: Input.customImage, unitySerial, - + unityLicensingServer: Input.unityLicensingServer, runnerTempPath: process.env.RUNNER_TEMP, targetPlatform: Input.targetPlatform, projectPath: Input.projectPath, diff --git a/src/model/cloud-runner/cloud-runner.test.ts b/src/model/cloud-runner/cloud-runner.test.ts index b8e51ed3..4f7dc265 100644 --- a/src/model/cloud-runner/cloud-runner.test.ts +++ b/src/model/cloud-runner/cloud-runner.test.ts @@ -91,6 +91,7 @@ describe('Cloud Runner', () => { delete Cli.options; }, 1000000); } + it('Local cloud runner returns commands', async () => { // Build parameters Cli.options = { @@ -119,6 +120,7 @@ describe('Cloud Runner', () => { Input.githubInputEnabled = true; delete Cli.options; }, 1000000); + it('Test cloud runner returns commands', async () => { // Build parameters Cli.options = { diff --git a/src/model/docker.ts b/src/model/docker.ts index 497b3f05..ef991357 100644 --- a/src/model/docker.ts +++ b/src/model/docker.ts @@ -38,6 +38,7 @@ class Docker { --volume "${actionFolder}/default-build-script:/UnityBuilderAction:z" \ --volume "${actionFolder}/platforms/ubuntu/steps:/steps:z" \ --volume "${actionFolder}/platforms/ubuntu/entrypoint.sh:/entrypoint.sh:z" \ + --volume "${actionFolder}/unity-config:/usr/share/unity3d/config/:z" \ ${sshAgent ? `--volume ${sshAgent}:/ssh-agent` : ''} \ ${sshAgent ? '--volume /home/runner/.ssh/known_hosts:/root/.ssh/known_hosts:ro' : ''} \ ${image} \ diff --git a/src/model/image-environment-factory.ts b/src/model/image-environment-factory.ts index 15513fda..581b7bff 100644 --- a/src/model/image-environment-factory.ts +++ b/src/model/image-environment-factory.ts @@ -31,6 +31,7 @@ class ImageEnvironmentFactory { { name: 'UNITY_EMAIL', value: process.env.UNITY_EMAIL }, { name: 'UNITY_PASSWORD', value: process.env.UNITY_PASSWORD }, { name: 'UNITY_SERIAL', value: parameters.unitySerial }, + { name: 'UNITY_LICENSING_SERVER', value: parameters.unityLicensingServer }, { name: 'UNITY_VERSION', value: parameters.editorVersion }, { name: 'USYM_UPLOAD_AUTH_TOKEN', value: process.env.USYM_UPLOAD_AUTH_TOKEN }, { name: 'PROJECT_PATH', value: parameters.projectPath }, diff --git a/src/model/input-readers/git-repo.test.ts b/src/model/input-readers/git-repo.test.ts index 1e96db60..11d05dd9 100644 --- a/src/model/input-readers/git-repo.test.ts +++ b/src/model/input-readers/git-repo.test.ts @@ -1,8 +1,24 @@ import { GitRepoReader } from './git-repo'; +import { CloudRunnerSystem } from '../cloud-runner/services/cloud-runner-system'; +import Input from '../input'; describe(`git repo tests`, () => { it(`Branch value parsed from CLI to not contain illegal characters`, async () => { expect(await GitRepoReader.GetBranch()).not.toContain(`\n`); expect(await GitRepoReader.GetBranch()).not.toContain(` `); }); + + it(`returns valid branch name when using https`, async () => { + const mockValue = 'https://github.com/example/example.git'; + await jest.spyOn(CloudRunnerSystem, 'Run').mockReturnValue(Promise.resolve(mockValue)); + await jest.spyOn(Input, 'cloudRunnerCluster', 'get').mockReturnValue('not-local'); + expect(await GitRepoReader.GetRemote()).toEqual(`example/example`); + }); + + it(`returns valid branch name when using ssh`, async () => { + const mockValue = 'git@github.com:example/example.git'; + await jest.spyOn(CloudRunnerSystem, 'Run').mockReturnValue(Promise.resolve(mockValue)); + await jest.spyOn(Input, 'cloudRunnerCluster', 'get').mockReturnValue('not-local'); + expect(await GitRepoReader.GetRemote()).toEqual(`example/example`); + }); }); diff --git a/src/model/input-readers/git-repo.ts b/src/model/input-readers/git-repo.ts index 3372089e..1db63010 100644 --- a/src/model/input-readers/git-repo.ts +++ b/src/model/input-readers/git-repo.ts @@ -14,7 +14,7 @@ export class GitRepoReader { CloudRunnerLogger.log(`value ${value}`); assert(value.includes('github.com')); - return value.split('github.com/')[1].split('.git')[0]; + return value.split('github.com')[1].split('.git')[0].slice(1); } public static async GetBranch() { diff --git a/src/model/input.ts b/src/model/input.ts index d3bb8116..6589f4d2 100644 --- a/src/model/input.ts +++ b/src/model/input.ts @@ -117,6 +117,10 @@ class Input { return Input.getInput('buildsPath') || 'build'; } + static get unityLicensingServer() { + return Input.getInput('unityLicensingServer') || ''; + } + static get buildMethod() { return Input.getInput('buildMethod') || ''; // Processed in docker file } diff --git a/src/model/platform-setup.ts b/src/model/platform-setup.ts index 7ef9d938..ced5b753 100644 --- a/src/model/platform-setup.ts +++ b/src/model/platform-setup.ts @@ -1,9 +1,13 @@ +import fs from 'fs'; +import * as core from '@actions/core'; import { BuildParameters } from '.'; import { SetupMac, SetupWindows } from './platform-setup/'; import ValidateWindows from './platform-validation/validate-windows'; class PlatformSetup { static async setup(buildParameters: BuildParameters, actionFolder: string) { + PlatformSetup.SetupShared(buildParameters, actionFolder); + switch (process.platform) { case 'win32': ValidateWindows.validate(buildParameters); @@ -16,6 +20,20 @@ class PlatformSetup { // Add other baseOS's here } } + + private static SetupShared(buildParameters: BuildParameters, actionFolder: string) { + const servicesConfigPath = `${actionFolder}/unity-config/services-config.json`; + const servicesConfigPathTemplate = `${servicesConfigPath}.template`; + if (!fs.existsSync(servicesConfigPathTemplate)) { + core.error(`Missing services config ${servicesConfigPathTemplate}`); + + return; + } + + let servicesConfig = fs.readFileSync(servicesConfigPathTemplate).toString(); + servicesConfig = servicesConfig.replace('%URL%', buildParameters.unityLicensingServer); + fs.writeFileSync(servicesConfigPath, servicesConfig); + } } export default PlatformSetup; diff --git a/src/model/platform-setup/setup-mac.ts b/src/model/platform-setup/setup-mac.ts index beb55195..10f7cfd3 100644 --- a/src/model/platform-setup/setup-mac.ts +++ b/src/model/platform-setup/setup-mac.ts @@ -52,6 +52,7 @@ class SetupMac { process.env.ACTION_FOLDER = actionFolder; process.env.UNITY_VERSION = buildParameters.editorVersion; process.env.UNITY_SERIAL = buildParameters.unitySerial; + process.env.UNITY_LICENSING_SERVER = buildParameters.unityLicensingServer; process.env.PROJECT_PATH = buildParameters.projectPath; process.env.BUILD_TARGET = buildParameters.targetPlatform; process.env.BUILD_NAME = buildParameters.buildName;