From 50a05abfc7fa6efb01f9695709b89ffd8a96f547 Mon Sep 17 00:00:00 2001 From: Shardul Mahadik Date: Tue, 2 Apr 2019 13:41:15 -0700 Subject: [PATCH] Add Transport UDF plugin to create platform SourceSets and generate wrappers --- gradle/wrapper/gradle-wrapper.jar | Bin 54706 -> 55741 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 2 +- gradlew.bat | 2 +- settings.gradle | 1 + .../transport/processor/Constants.java | 2 +- .../processor/TransportProcessor.java | 10 +- .../codegen/AbstractTestWrapperGenerator.java | 8 +- .../codegen/TestHiveWrapperGenerator.java | 2 +- .../codegen/TestPrestoWrapperGenerator.java | 4 +- .../codegen/TestSparkWrapperGenerator.java | 2 +- .../linkedin/transport/codegen/TestUtils.java | 4 +- ...operties.json => sample-udf-metadata.json} | 0 .../hive/sources/udfs/hive/OverloadedUDF.java | 0 .../hive/sources/udfs/hive/SimpleUDF.java | 0 ...facebook.presto.metadata.SqlScalarFunction | 0 .../sources/udfs/presto/OverloadedUDFInt.java | 0 .../udfs/presto/OverloadedUDFString.java | 0 .../presto/sources/udfs/presto/SimpleUDF.java | 0 .../sources/udfs/spark/OverloadedUDF.scala | 0 .../spark/sources/udfs/spark/SimpleUDF.scala | 0 transportable-udfs-examples/gradlew | 2 +- transportable-udfs-examples/gradlew.bat | 2 +- .../build.gradle | 65 +---- transportable-udfs-plugin/build.gradle | 36 +++ .../linkedin/transport/plugin/Defaults.java | 100 ++++++++ .../plugin/DependencyConfiguration.java | 28 +++ .../plugin/DependencyConfigurationName.java | 13 + .../linkedin/transport/plugin/Language.java | 22 ++ .../plugin/PlatformConfiguration.java | 51 ++++ .../transport/plugin/SourceSetUtils.java | 101 ++++++++ .../transport/plugin/TransportPlugin.java | 225 ++++++++++++++++++ .../plugin/tasks/GenerateWrappersTask.java | 95 ++++++++ 33 files changed, 706 insertions(+), 74 deletions(-) rename transportable-udfs-codegen/src/test/resources/inputs/{sample-udf-properties.json => sample-udf-metadata.json} (100%) rename transportable-udfs-codegen/src/test/resources/outputs/{sample-udf-properties => sample-udf-metadata}/hive/sources/udfs/hive/OverloadedUDF.java (100%) rename transportable-udfs-codegen/src/test/resources/outputs/{sample-udf-properties => sample-udf-metadata}/hive/sources/udfs/hive/SimpleUDF.java (100%) rename transportable-udfs-codegen/src/test/resources/outputs/{sample-udf-properties => sample-udf-metadata}/presto/resources/META-INF/services/com.facebook.presto.metadata.SqlScalarFunction (100%) rename transportable-udfs-codegen/src/test/resources/outputs/{sample-udf-properties => sample-udf-metadata}/presto/sources/udfs/presto/OverloadedUDFInt.java (100%) rename transportable-udfs-codegen/src/test/resources/outputs/{sample-udf-properties => sample-udf-metadata}/presto/sources/udfs/presto/OverloadedUDFString.java (100%) rename transportable-udfs-codegen/src/test/resources/outputs/{sample-udf-properties => sample-udf-metadata}/presto/sources/udfs/presto/SimpleUDF.java (100%) rename transportable-udfs-codegen/src/test/resources/outputs/{sample-udf-properties => sample-udf-metadata}/spark/sources/udfs/spark/OverloadedUDF.scala (100%) rename transportable-udfs-codegen/src/test/resources/outputs/{sample-udf-properties => sample-udf-metadata}/spark/sources/udfs/spark/SimpleUDF.scala (100%) create mode 100644 transportable-udfs-plugin/build.gradle create mode 100644 transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java create mode 100644 transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfiguration.java create mode 100644 transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfigurationName.java create mode 100644 transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Language.java create mode 100644 transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/PlatformConfiguration.java create mode 100644 transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/SourceSetUtils.java create mode 100644 transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/TransportPlugin.java create mode 100644 transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/tasks/GenerateWrappersTask.java diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ef663e64ce97074bb3a6f99ed8dac084fee9e9bf..457aad0d98108420a977756b7145c93c8910b076 100644 GIT binary patch delta 49846 zcmZ6xQ*b3*v@V)X$L`p+z0$F5+qRvoj%~YR+ji2iZ97@9lk?wu9!~9DHD}d$9947F z_%s_Te|9(ij0ZsQHr~!9@Mu5>XmE$ESTkS!2?+*vp2YHl6R4wsrh)zy zn_+5*4{0J86d43SWsGQERF~y)R{+uxPJlXmKQQoz?6{}Qh!|vd zThT6S4R!i}TD>eC3`wCO6tK~_eL3Jl_|6^r*woZ4n)-C0D;g5gqMrI-M1BYq8?6g% z59c9c+)k0YLEx#;@8f9@x*cFvKSjqC9EdQD^4mf)l&2p>nj8zKNUBqCz9!n}cFo!< zgXjLOK`PO5WcuQ`;2L|sn=yG)t#BZIa#fy1&-Ed2Y~h)pTlZN04H#(GiIJvn?m9T$ z(Q)8xkxioeODEJMqIi_Jb+@YchCZpoe`b{e&f!cbi)NedNf z{_rb;hsG7Sh(S=`AW%a#*JI%e$Lfg_?YRy=9?Qnf$KEpV!Q+?4YbTrcno3f=a6cpe zzxxK8Rdq4(go;LE&5&sQ=DW(xThJqwc9{)@h&D@bqndMSbESMP{BNb%8fhA%%6*vF z3a+pfoF}_Mw4im*f`L8)91pd0>ajZ2RqDzYL3)YZe`RHkN;tM&7<zKd`EzKpM!P zQ@$5N;BC_OY)$W6aSPL7XG`p#gdw#vp@5PQv6-yp2I^A5xgS-=I-+-tW)8e5C-{bR zCfSXM8SF#uam%f*oDAO%EgLmM8WLzL<^ncW>Fw&JdMsx}5iWB5(mLL33z@$4*7rWY65F~VM<8zOQxfE)JkBEHM|0hI zx5@v~0b4|*83Z@tm!VF@4_l_O&XRZqcg%0Gz|qL!mrWj0OQ;GhJS`C~HP$ctoV3Yu z9R=iPE8gI+MFVpetFcKLSj~SHQ6)tq$5a`OUZMp%&lANv=g;8|aELZs#M_)|ikzf3 zAyF_c)v+;(E#bPygdFUh+RtB)hZJnB;XXx+0r!822U;?+%ij1n3>EjH_|Q-Kiy#L{ z%HJjO1juW@Q@{Kea6%b;m=lQc*BvmT7aX)3(Ef{-@PQ%F?|o+si2j9)GD@usK`z{j zH{S%GFbtVJB!hm$UR?@e4Z!4AIKY_aRw*42@JX50CzBFbb%CRtz>c3|C|r3*9y?7u z13tquADc}v%O{*>saSkkjMECwoKc-lAxb<~8vzk-GZISC^4XN*Tu&wN3C|MbVH2%S zqX^6Pc1=Y0_Y4T7E=y0odHeHqn*?^P3Bp$Xex9YM&o0Q6!zQwW^GVk!%zq*DW4c-v z73SliL1Nik4pz-C%^ra<@=sO~_4U143%z}3)I)Wk$p9{?6%pGXAF z{xZ%nB682O{>>;_AFc@02s1Icg^x?dCYCnph|UJKomdb3upSB}Pm#JO z1Xezlf3W{7mo6%{bpZPp*#B&QdAdUo4;&0kHc0`RAEEqx}{oI}7dis~|$4kI1Y`1&(tTeJd;{!)NCA z^JaU7L2vIjgdFNdJu+{22vTT}H0L!d{R&R`dGcJ!vF z0^9KZLlo(+4`3k2lx6aUe~)9e)Yi`JM3P23ss?W~)#^J~c-M435bUS#teqF>*K_s{Ahx!7Iym#N z&maGDZqUxS1Ysu!iU>USIxJ&1~8+!J5-Ycg8^suDH?tn4FxbX@W5M zVx;gRPsA}rAxp8mJn+hrK9Z^W|rxwZ9t2 zirRgJ_9q8gFX2EM2oK7iEf~e0E_emBLBbomqcn9xo4KPhbrV*H12o)4M*L8MkpUf{ z47?nm@J9+LO#mx0Ytvt>VTk(k%8zFdKSJKV%wQCDZAd;azO*@jDl+y4fZJA*4}!1l zK7sU?Q54SIJnO$-?BGC3edN8o+ZgT-_%DuNnHK=sca8a%NX~;C$GzOo@AAV}rX^n) zj_>NsZ>6bLwR?i4u6)zCnuwba=07+phLPCH7z6UYbII2J6oRPbO5Ma$Qio6~1tnF0BXJdbR>y4xlmhFO}_Hv12?KG*L5lQfzMnYe`}2q zA`fMM(3xRXvlf8vKx05s~!1Q5qH)DRXTXM^A4!z>n%2r zVAaV>uJzvtm409m59cu`d8xYwu*`D2MkZBykZqwL&#f;}tG`LY%yr#Jg&#TVv(9hh zL4LKUDKYtsTF$~4*2a72c@~_7$}aDHoqkfZ&>hiqQ?$5emx8o&l`BJ`=Tihk`aJ`` zogzu`krP-`0y&h)H1?4pClV;w$|F36^+>5KZ(zQkd;lb9R-kBRyspc^Z|DxjJ)!#f zc8eNIoYfk<=WZ5C)clnn)X=4(5xCUo$ zIi8c{{1Mw9kjtAEyipHGaAk)cB*alFU?~}w!r>;9Usou)>%tv>W(9l7CUU1-A_V|2 zJ)eqvM}abHQgCUvi7xV2M=hbU>$mPPzh9Y)4;0MACDevgb509*%O{8a6q?qSUN@!g zTojFbIK4HXm~7dLvlz`15o4Ueo4dEs_30(Mo8jnCijU){7byR+1{}brVlDm7zNKR0 zkGWn&)2)$qd~evdR2nu>Q9;RaGWtP{FHbq5=6;p;eL+9=?X8~v+3 zl*{s1I~xM&0_`hKYhIQTgT9tSmN``O1xQegFV!;Z@SFn_R~) z?Q+{BqeLE76KWK$xonx_t+AW9XABZ(Se7xHvvd+sgQfB=Np~HYou75Bqfd#jV$D^- z6+moB$bl=0V4*85clG4U+0)fZtkaaKz+u_*J=yuEoc;m|`>M-$=jP@kW!&RQDMIh* z$1=ILOD=-yz+yQJ1(@=75-N^@KkbZwe}nt`YsLGzvvMbj&C7CW^4E8vIEVS^S+}7C zDaO%P!iKGtF7(;M8*2A;kXT8Wgn8lk&`fQYmA#} zEPj^A4J@_3u0PaYGuyM`HQA%pFM>_pwOc<k46VQ{}<2TpO4*bc`@qIe?RVE%|`P)`>t1%XRkB{lokHfp{W2uLzJGv*>g zNuCl{Jl~BM$%E^>>#{fnn|z`ICluP2DmyOHbh|s(ZRer6FVzuUQ|BfHsf#MQbou&p zk2dzC60s=xZokp`SuPd!SN7s!!}69K8RSyncDw#i68^U_3=uRhEz3n=kxM_6+29U2 zT2y#~`t<@Ht58%(vl3Ur224s~G{cT?wvT-I#mDR_IZ7u)mo1a=85O6~;j)Fh00cu<2;*KH|7=^&1_rUW3!l#!#o5WviZrz-~Q#JOA5` z_DtJZei}`N*3@(6+L)oyodjY-_g_2jGDST{u$9f) zA~8B}i9*8&Cx^dKzrLAI{#bTJv+dM8s(W`RBOy3#nc~EWGU3MB2c5>^qH|d8K59}@ z<;Ys5nnQ6*wVX_S3i!LR$`f9t^2bR&0^Nd^$d+>cNV%>8KyiWfyM|h+xoL4?No4B? zgWBzcjc)hsuBqfqG!2(CHAJ+>dCGG`k=8;wg}Zf%_dJ5uHLy*(mda zW7AQi7+hiP(jsl+x5*L^&)ST9q17|300_4;G`cXEy6J1Az~ zV%{_XSEYk7LnVuV>#u3e?-L4NsJ}uRPY-1o);(&!1+rZiDohI$j0X>52XKNuKke6N z9^YX9yyjNxnl*TkA5bHxOEs@Fj#9e*0=$+On|;a7d^=>#lSpQ*k3HaP9LboZ$k{nm zh|d@_R+v9VVF7dIRSF|n&q~@c?qYR^()#61`L%lDOZ-ww??&xD1cGUq75Qm+!LO_9 zPKLVpic2Sb)T=)HZJJC--In=8R(7=dW2tDT!^tawomMtROtnE3Ir3pWH&BMdCgOJC~@Y7CDjJg;80n0ooR&bklG3r%adN z7m_Zy@0J>MHqb(sg5MiT;l!%X9b@K~V2C-v^K*(J zp^sgC_K=PzMv@pm0$Zyp5~4wKORKRBHjj-*jzhbXLAA5H#@)xSh|<`+p0TI8<(x)V z7dVWlB}z2fFk|TVGD2_P5sG*V8nN2^n7?~7YsR{~dYrg;Xtnnu=;l;<51^Wx{{XKT z%xbXYFQLl0a?6I*m(cXnP7PtCuLb^M5NZ;eJU46BYU1a)C+sd|hGN<&VnZ#!}Mx)Qr>$a&tv;Ts5vfT%}XCi3r*sEFB{JtY0g$rW!$G<5N0k-bK zc7+Mq#2-L*VPWCWBC~exjOx}>w0gwfSd~~t@7*PN3Y`DD<#qEBI4>Woe6jKsEUsL{ z8i?rj<*HAEFQqLXGM~1PwLUyi9HlZm|p7(>Zi(naiOV6b4~yUZybY zB9l0x+B@{m;#5dmsd)L#W)<1g^1%W7 z8GV#O{GAn)aO^PCVZNJ%%LM!NrHv0pz{x-k!(qyBtW2FN*v>GRJ>rfZB9P}`p^eJd z0kbm`c2Mubd~Rwod$<6fv4lEIMn!Huo=_cStpH)PGorN-ySp)*geMBS0WGg6$cjAl ztCXFwN9Dk639qa$E)_FnRgkpjz=fGciWSlMh;*u+CyFmjd(fwG6>Hnz)9b<1>2EEE zuVRePPyEr!!7$_OON1ua72taV*QOR4tf+O{DpsvM?6ZYaFhrQaNT1m?Z-qNj>P<96 z5vrTTE^g@lo(Wvn_8qDidj^EW>Ee%=v-+->Oe-8klcXO#iL}jVR5zy51V+k;%rPSw zlE`ckghp@FK3fv-Rpi4nyB1!;JPtW|CQN-9DYE8A{Z8v#fM=NQFJROQ#S;_^O4^!= zr(eD0Mbvr?Js@3wW{)jdNvjP~jm^}c+9atQ)CG68ig1%8=oE?OovNeXm+}GW7D0A2 zIkm^dR3@@_VEe9JdjM6idG6Z<@?Kk7%@~eo(e2VTcir-6$7Y#$WO&6Uco7=^A}N^* zgLO)iCN*J^i-!?YewQLD2`6JS`O=Bv{~H&{zV40{*7-Yo<^2|-ZlEvdBwrzY zsDja%;$T)J$rBS{sDJsK3wpg{9Rs-fY`oL+{L{=AMA9sj(Pov`#J2i_ zWeRdV8LFp|O1*mRVU}lfVfYY7n}AU8L3lsI^n5l|DG+Li)~H z3jPji3E}(Y@157{ott;}&y(-3Cl9dLJqvFB;FFEN2awH^RzOcB3G}>qMYmaD%^3dRGX*6k2pVqm2igedr3Z^+5b7W;#JmNNSHN^cI6N9zTFFdYET%rn zX>OabK8H)o8Ib<4#BatK6AEHIOt17jD^|c}or7lfSjX&a`D3i&4>inXMO%xWqwkp> zhn-k7vUcoI9!j_wT*zgWpzA7wQk}_w^I?Xzm%caKaTh}hU#sPMV~It%OAz)W7K8Pe zT-&y=eLtH_%Or!3c`<4C=D+3wy-ID3qcXFJd}Af`N?=ln38Q3O?8wv~V&-JngSyS^ zeLNTij-3Vh?JvO7li6TGmEs4(P8q0r2W^{j#FUcE6Fk(~L^p^9R||!_>hS zkrt|DJ7CU+vxJ-W;D+161}QcY@}ZnPY~T0q#GY$r$UYT07|G+Cno zEZ*s;+s`nkAwi;87;HQdHV|tMDQI>1nn$hiGVuYy7XyIci&zWu&|O3GKUwL9GFPAOG)>yrl*4%$yK789%}VDVYKvFfp?%`X&FVlSlW z0hy|aGkat=tbPJSM0PkGF;&3h1YlCZe>tr6d4TkO?hHO7@`kzK>H|Pf{>1U=uoklG z-P!5^-|_dva>$WU4axeP40&U)vbC+c+_qm$H&D5UG*4t*R=b!rHZS!7t<1%HR8anr z7xbr}e*vN!?HB>z?}Tve$Dcv#0`FN+l+HyS-1i{c!>~5SF1r;=yMyMUJ>Cicvr)Q?%Ha3 zqrqxh%q`rfpjqFgW}BXD)=#kj2@mJ!H&^GHle$uzEs>JcV;?#rnjwP_Y9-S!^{=%*ciP#IJ+~a=>1^N%&tt8&hT~U%Z z+a7aw^28si#F$6xgO9}Pjxe1dAi03{A6M#7Qf`m7*j3tnm)3lz4Q&Y>k3boM5xb_mvjaQ+%*f^X3(Q>QnOH!A;>@(psg~G^ zu1HH~mhho}5l!cN?!Qq{{%J+Rt4~Byz~n*Zzt-aQuV4}}8SC2Kkb_D|;Pj)LfID8? ziAlL8LAft4M(shwAE&F@@)v?jso^$7v56R-)%U0Mo0Q0&n3CtY5>}4H+7*EUnu+AUa82#61TtXlCMbiq2z~#W`(;`4n^z6{i z=>knbd2p{G_6+F2EqH&V4R+D=Vt#WxC0sYEz+10Ys-SI%wNU`&Do9*rhtd2uJ*njU#p-u zq!EF^((&NXe?{V2`q{|5nV&@e6Mf?^gXz8gOC&;*a)>yA8}b{1D17kb?$H5@$ue;0 z(lq2vLy+t9@L~mqf~301fk zyt5o#|E+&4uJrBl!vKQ+07y;#^9B?%WgCN=+loJ7U}zab)qr3pW1qn3T1+42pdR?R0ZR zn0u6$;w0D$r>{Gmq{BH`$|o3|>oZ))hp_E*C%-dqn{wvP4u;R;S&PZyW@|R zBfeex*R7j3(9)zfdEHLKcA${;hHVrd`X_=CbD)Lo9^cj@UYH%)fI1vKdZZl_Uo+P)G zz4QL$y)%(C!~cQ(hr>81l7Xwb0KYbM!shT!8?shKiMGGkvpWz#-L?_s)8z>)gy@nQ z&bq~9+CCrNK=0BMYZk2<;W^yvrrnP4pz&;t^g?>!$KU}Gjy?IecWE%*{P)D$wYxvz z_)mv%V7(Q^7zmX?Bp4GQBMcePFGv~;I*K%C=6u8XsJM!<(Lvf0Q257VnJ!_{gccLw z^S%jf*GtO|LbIGje&uhI2V5m-Bx zXOI@AFgEBQ$*ACUD>rtkqwlNJ?@>fjdUU79WS_Qk<8|wY;js%1=YKjJM}8Zp_5mS~ zGBKn2f#0imEAt`UU#s?T004(~e`;|H=0-+(aC;)$Txu-6H=No2ssZNO|1dZW_t$lZ zIImAg<)?r6eXH##vz^GhHsVz5ZbUOqG)2U z9*{ARIr(u}H)V%k8~_i*yUa?vw{9+&IGL`S^wXIT%O5tuyaa zymkN1_lKu5@Q^A6m8KjxC55RENlJ$QK?TTFk_m4%IPv8&znEy%s6=Etm|> zuXb}H;iQC3tvazLLR`SZzR6S2o7g(2t0DzGA>epXAGcu`eE@Yu*PTOMPQ&$cs10SX zem_xEOE#I|GG4iQA}X%w9b;e7M`e6QN@~2h4c^WG#QW<9J}? zBNRK^5}1>2>*i94WAo!+CnqX4Fz^(#k=zv&01a|_WdrOVKY&Gp za5>|?*-~gmrV-j#e>o_|>a3EbiuV*=8UuSRU+BKXvrWK@M-zVqA?d#Zlj}lz4~THT zB7ih6t)bt$y|UwHft$rt;u)N{xg%`2xx+5FsBJ2PG`tEyA)V;rS}-g!h6zz}dJVM_ zh4h+fgFvKvQF`n1!Ap|qF<3;V5j)ow>al10P~%$rm=+4u5o{MO##>%z8jLa*q3!A1 z^t!f$9@Dptkw{h&j_E1V%nLP=*;x$e6^Df7UWQ4kWvUJS0v;SB^0VuKO5kp*`7+kg^Kdtg0Jo6xsMyKip z(=~QT_>3UV)hW+2@12`YW`B{{S{Y|(G7eYI4oTBY?-*mbsBD%W^G;|)a6?sXAgodc zumh!u7pTjGSvh5CT()q#DbK}%C<6(*O@zQ)S zy$-v7Xvk&1k5uBD!z7Z5#bLP+t}uHYhdppKCC4d^p^Nf%t7o|bXK1!a&(J>LHa(1A zV~B5E4L3U4u7_t!)4N^GFDfMsxMFM5b8f3su(zgOok*a!Y91pztz;T1uAgA!O$RD! zErY%We)pg0T|`1fXN?Q3Zu{Y`IbH&CqP48n2Ak7+a*Zwuj zu$EzVzYTY*C`Ji0wwa0BdlBkc%Z`Yd=_ajNC_QWi=~`Tx9iorRFRpoQSZ(FYy;k!M zrJ82A%P{pNFFX(ETA1;4ToK@u_JGQa1oWa!r1+$vrzVCyuGi%dHP8Y$_<<#Cj%l5q z-+$m^bPkW(XV=9{ETTEihe}&5|2Q}n@{B6pIGMMEJl32jYY)5D@(p9%w|kw?8Q{id zHrznZY1^|hIR*?fL+p-{2-t)8lAfGi(F6r6@;u~v8iAadpb!*9r zLJayHtQ^7jh47AuS6J~hqb}O5k-%N?_gnFe5YVEA_6Z|vi7#)ZCgC{b3rvx*6rRzg z!zdM(&u@K{6`$VuisJ@&RQFZhLwS25d(fNE%@u;^DB7e0pB3^mlYuVUGM~kX%O}qe zDrHs-SwoxZ!?h^doFQJK8F>SN=$LO_`wow9-Ev8qu=4t5h?tG}~e8K5T>JqcnixmNO9!$s1dQyPef?_vUgP)kh;S`ob+*Kot z>?TAx?ih^44$A|?4?uFxT}<1z9a^uq$Hzfs;e( zPwiM((H9x0a}s`f2_<2#=L713p>=39lkmCcm3x6C-SK~WV6h}fA*>r*nXv;hoO7{fnZ2{7KUv5L3(kV=bO?{7N9~Fkg8a&_A=%!j4@~LX8{y(%flg;K* zM|2G7vW3yUGyNEPWR%-n!ENByuEva7i6>%fpV>RZ&bZ4x(rS4_nrW(JjQrs6+;_s( z@@Z`Gt+V@%EPylxORY2^Q_qI^5N|ag-be-!ivAuc+nm0ZMk~E_%DT^Jh6*@-U2!Uc zrVH`Cb%|Bt{7=_)Fw91niwuB99dB{qkA3yq8%*B3PTY}eNKcaA4B{%dPo!T{;!?GN z6S?B;fjU1CIP(6*5*XtQ>PRR5Flevk3yIz8r()h%ZU!zWIYKx3Sd=AbUt2%86+0f~ zF5yIPp_;e>;)LZaw9OJiK5`p7uNv*$19KZq^(TC{#eZznZG8nImPK7}ji~t(=;&nN z?lLh|&##glvyED36i;NF33YU5{8;@LY-uL$u;+o-t2cQ@moYPsEF=w=c<%mYf!W@) zN8wq1H3$^%iuPg**Am+4;=M``_skvfh4kPJDu#2X2Jut_L1--5Lo^uA<0*0fo${A9)mKr3=e;E7BbBKac78I*>$HU# z&I4xtm1rkleNF?#Te0?!V(gyi+9AhM*VKLuv->}R{~044k`fptXiN<6Q#R`^_dEoJ*s ztE_GNfOj+*SjMhk@y2m2rdP!%C68FRV4oa%2S2a%rkMWyhu}TJc`8%<^?6qM!Va5p zFE18)1)9H)8=O-*-uZ@(-dCtpTAc}DNDHWiiOH(3c9LB4BS9C8>dmLT~ncaNQ3u5q0X+207Qdh&DJL48uZmR*Q|0dlYn< zYuG=)KEX9sk}j4DtjrKK29a;c5Eb^=2RE){tvt1-pLa)b{R8u?J;gZ%l57M<8{wvb zmYzs6@S+{}9T!$FA)EYBTHe(VnIk+!!-;_|xW|tAgYa#CchhkFL$Elyg3~xYaC_%K zaQ>Bh9`ghLLGk7(8~tF?`5{j2qLaeh?9DASBybUg`&G2(_MtUs0MdX15EExCT%cFm zhFy6Qh`sBx?Fb#adLr@6G5kfs@$LuK`&d3AdKCa}0f@k~FNY+LChv0k{HwQk_$n>d z;}pqcx*wIh!m+GjCs=QML2i9cbX51Bi`kc%Q9)!WtTp*Q{`#=C4w`Maso7#@gwp>q z7ZTaJf;xC*1m#2Sh5Xjb6k=ZYY3(*-Z)-$@Xj?4HC;4bAa?$8vb@MD$#V&voMvYph zmGOgN6Pc(S@IR)HhT1&}b~VLjxXOHzy0H0@Sm3JO@ibrmvz_Pg@J+sI)H#?r4KC5O zPQ`VTmH_o2q0=fLe4*Buj|t(Gctnd6Hk~D2Pl^>)+I_8?5cc?^RXOtzlXbL*?eFO6 zaq$3xoUkq_;cc(i_;)%$%K?z|6#y)>ZsG2#xt7KX72j7L(`S8(A5@6pHkKM!HFWse zsk=zV5|FFVlG}O`z4E6hrYV#?4-q;bZ)CH5*ZXg;qYADZff2vCrKRr(i7)oFa5#MM ze$#5UNYZ=gf~RVu>PpY6_wudETw|izw>!aQFeKs!dm%fEs7?1R zZ<7#^b08!A3~yx=Iq(gezQZfRR^cG(5)udec%gJwBgbk4I?a9G3DDq zUFo7Yv#ho`BWdKovTqOupz_B!T4J5-{jS2-Y~isYlJLuOSLzk0s#E(fC^Loe45RQ6>Ky77H6=H28<@&cU+Z!psP8Gn1{^-U=H z6u8OPlqPv5TfojH6ATx+X!5wf2{5HFNJzKk553G10XKCuwk_c{Ddi7xb0u@m$mO75 zif>yU``J}n#0z8WX$%dxzyel{RsOVE@Y!gd-2~By-QY{O*<*=tNlsX?g#Ne%gBot+BF-I+_k4r2`T&7fMfEK5&mM zSnQ1Yg8nDaP&+vn>@|5YuNvJLz;(MOy%3x^AzsSxiKyd9E2PB4%s$=!ij_oz{M4rMN~?2B#4a z>-(TJI>-pkD24<#TU2AS6*!y#Vn4rh$HD@c2JyCMl%kchK6HnG|BFFj=)q_+kpJhq z+0rGkaL@u(mzD&m8oS0otKcLE7vT^M&;yh|5>Ns~ll}w`Oa9WQWE%TtJKaSB+icCC zZqr)ZQDKQjy2B4S>F~@QxHlUC2TUC%6#?_3=#JVYSM}&k zs5*f(acONY=w6Id$F%y#$JYASBRVkybem0JK1P?SR_f9!bm;PEaHXAX^4{kd_KKb< zQ5u~u2xpoHCB>W6Q`&&;@e6eQS`d^M--2B8=3z;SZ|VTvmMYVt#vMSnONxIwwM2Mt z%HH~r0nWzW-aL<;!Csq+uOlmyYh}3l&i21tb71P~(&P3w<2J1=bi?m|B)=Fh_rrN- zFmemvMmHBJ;p|LIu>B^TtXgO`_e#K4q!(z=I?=alK!7dRn#mZDSHrFj>CLPCTQF1n z4E*7hFv}Dq9573&8ZqR-(oHaG_yIe~!fPtB!p+SFz*K9e+gaki*3sNtiOX4AqYdm# zq$$0|x64u|oE5e^iV!+}nb_rzV!inJ= zME3$#_~^&R1IE`M{iFM{dd+Ukd0F80IGa-0?|txi7sjWsj}g?8M))4#n%dOEQ(LSW ziw2D_9oLcsx3BnZ6lBhexn^9bVQ`~7OwC%c&%2z{CcGeC#+{3T zA0@vqU*qkQYjsqjXxh(Ia5tJGz3%Z2gi7Xq{Q8gm{2ka4^4Qw7FKL%px_cn0tZO9Y zVS1KOk$s$~%FN0;iu(s}h0Q+wrG=^D2|XDoWWWpdSi-_vZAhFlAiHg1v%-_H8rQ3> z*|0>OhY>6z+fp*>YQTFA=oklH1c-q-wfD-!;WRX;MdxO%7jmLU;Qg(%5sGGA+_jBu zZGl^CDgwaGbg=2hWN|714Z{I7@_qg^ac-5t zHCL?^el6FyQq;hP{ix9jf*_)OMv*-##`9|@ychfYHIe#+2}E~}ZBPdq@+#_|05F*C z|1!CXC4TJ^uzTG0ZKcvHzo63ZnAhilCePisS~)1)vox5K_X^=4O4R~WL;e~}SqwG* zi3q&ZJTmM87ji}_o`OGNTBH#gP#8y0BwT=f{TC<{dSNc7D6MARYFO_ zYYg6kX}dU6f9Yjajh)!E{+Ri;`(DK}RQ&8Q;WU5n{-d4Wnt^{i>n<>qmpNxPKeab2 zBHl!2_Df|$Q#&?Ym5dCv$S_tF0p(i~RV!tg$w^-Fm16Cr_*W(DaF92x;)ZO?B}0cP zyqOOx=m>FZ>rfH6H-Y}uGmu6$^gfVK(_}cLsvgfGX=XkXNXTx%)^7$Mj=7atzIE1U zK4n}nk=~gYaor}Op#Fci+g$IG?Ha91rmTGm%WZK*7I{7#Nz=;njm*RaUl(PL-pzMK~uVh~Afgpvk$f43zfbZ{ou`eVie~q#V=BHrs z%{&F|6|w0SQN;&YkMdFFi*-nCLY$4-7mGpZox%%u(E%AehhGcIM)}>N?@v+5G!*-- zwuTCH;8Ntcz!Bcw##y0NEF4^Xio<@ET2F|B+AHT5PJzsEgV5fFT2Ii8su$Ky@!i&k zi`BJ~7r>zUj_K6xFz_qIW}jq`Xi;aWDy)y`X+m4k0G~{Yr z*#&1|e`VKMBV6p9g&7L7>tS^=mdQ897s60uazW@=3t2o|uRO~(4o z4O~R%?#z|&5t9O@N=aI@{;o5~Vk|e6Vt_y@k+ir-t*RfZsaq-gwalVJN=d%ph>jVi zkqDIP^)JAB?cERlGRN9+);9@LLquRLpng=$e>?D@JK2pZ@*N{Up$_R(d9M7 z#5*+FI;Q-C{#zS{AoaNYhj8&YTT0$ZWx|eF2>3Tv;sR8v7Hu!}LW!?iaWM!eL;D?S zp#F*h>+U5&CL#< zJ0g~4BMe{K-eHHz4=R6XNQ)65szfpJi(bkO8pKD&cOv^#Jh$x8GsXO;8i>WJ?ol0) zdUm7~dh>yVWfR(Xw72|0;Vo^P=njMSJI6r`#Oquo5s`KID^J`@!|7vmmmEpNi+D_>^?bQRs)}-z3;1*a$96t06B&O8mgb5u7z>|5B`zHsNNFxXOQ?W zjnF@6SPKiT8!W=3yI~~YWiw0iLo7qDjwza+x z659aXyMwVayfvHgXat#J)||bu_7iH(*bjGOp~uNq~p{B zb&k=2fVThW8G>w^UK#6d4(DOb1766|ZppVUFQo`z@mE6aA6>Dt9RG#CsR6fU;%m0; zqrd1gv1md>j5nEqwq6Y{KjF3EZ9VqqYX8*@Q~y(~4jCXx4*4Nd<^+kuh(4(Z%_Six zS3=kDO)GXb`vG+a6c}iT_-ca5Ov40++9r*i@pVr79C<*ds0l7wq3~C-*nX$yXPiW6 z=Sw73&FfN>VWTkaI`bYxtI7Q!#P+P-7IQ95qSu@}*kNfGz0)6e89M0RlnLIb5^UgB zqGp@oqI`==+}Dgy+HcxSthSN;>`COT`-gz)wudAtwJ7ij^eLJdYYPh;5Wmq~@`iw} z;YN+qU1-{eF6`s{IRA?V4k*F~>S) zh8%XoB!EnI(=EDpL+aGijIMtox*g*%%d%2!JE0G#0`%^gyelh z{cl^0=mmr83-21lfiM>gRVQ<@EC}4ZIppEKHJEmE9AN~hwg4YK<8~k!>vmu{^}WnX zUG8(p1JMIG+xw^tgNFAMpGE}7i`Kj&8w=xMe4RSu&SWECoTYWZqW&* zq{W<88|rhdrYY1%nu}mnc*LD&(^q}o4Dl1$R<;eH{8G&wX=&%hak{ec&HZZfJ{JJyV zCS4JNqL}qDwpp=u()F;7drGKzwqkbS*#4;PD6>0Twq)YT$aS9iorZZLKQet$hRo)M ze2&Gt9~SNU%yTQ>NvW%3(4@>5G)lA`0$`ObLuAgBQwvJEU`EL@FMTJVK;wkiD1hU` znKYHl(tyGQ`~o@Gid;ndpv|wK6jTW>ud)qEVmxoo9AlH89VZniTl#3wh9l{3skeyZ zM3%@D-!x0x2K8|uA9*zd$z=v|a_j`R&<4_eHI#ugP4WQsMl#_z9)39wcr zm&<)eUh9XvBuT#`i5iL!RC;wx&y`;^W%lxwxhnpHi2lwJ3c-mLt_LY5bowj3m;U@k zCw(VTnPEi|^$r26@Ifys1uttO3!MUs+k}%-M=hy0^)I*;wjm3?9*X^&6y6nA zxnJnrJD;F16AVeps_QZUvA)KE1mLU$mspS2Sysw^%HfNvAjE3yEjy3G$!_c$+WDX- zIDnQbq@P#xAW=q3d767C=~?mqqxIr>`c|-a7S5RC*pT&ZWWt{&_ujk5WF!N7m%#w0SeNL!& z!BL2g#@c~bSXrhi)0o~&mSqB>oYwXt7n91SihmC^;$WHmI7E1n94t}PjLbZ?=N0<^Z881e+0 zx+hD1MO#qO&ajW`3$S8SCd8!{5?%okvB~8LmG(qX#_f62kv|J=@&FpvkzbGL@di3# z2e0P=w}AV_7P9jdlPVes6pOW!DmTwGbBRh^Z8|wKBb#tazjNGYV|l{ifjd-HFn%|W0+uu_FqzCQtRS9vdb#vd249K(`IXvs{swz0V?Uu|f zgzPMvkqkNu<1MB*IZx=wKd>lJ+NApThp<)BGuKl zmz82WRc}t{?^G;RBOw@u3zD>-m*$(ZBEZI zGpU9i!5Qu!!~gvkLZjoG&>718fUV5$*|(=&<=;+@aWoH;BL(UTQPwO4Msm+a|3(_+PsuGm zMj2U)W9oz_Qgf+9cuM}VCEgk{slqq9a(maR}{a@k2ilR{l@eO zn!Xn3zvKNzMa={Gdhz=W%O$W%QL+m%v0}m*P*m^m0?7a5GkaG0 z>;5fT7*QalThIfZM<6|P6wUpEV3nVCAzx)%yekY z2AnF;UObrlpJG-Z%20_6j%-$aIV_pmFw7eDTFT^3BT|z;d_x$L|5Ss}zf&N$)4eT+ zEr)Jn)FA`A69vka{vwzW-349H5n00x==U78EsO{ynI^kQ?u{s+nG~g&$$xVa2Aiav zF4|)fqO))ySgM0vh_TypOt5jSJQ)@1498eh!{eb6EBpsRc{D*IBC<>Z<>uL`F2>BJ zDkj#w8aZsmnaFDTRxhRspr@qY!N+o_aY^shMS>_>37~qH-&}Fzin8Iq|NP&<-%U5? zlkHE+l=+8ng!jL}UxhdD{8t2#pjDR=Jrn!Bbr5Oe*V~D>f4p}^qP+k@XIU*Lkzmvpv8Zk z;jesDesCMJYe6g3WD~G*Z?wO5@X6>3Jne5TJvBEIwcz(m0!5Azz|+Zm-Mc@*CIj`Y z1vfkL6fYn1sZpz;<;I2maIEJy{T?m`QLw?Vi<7de+cxsqpAfi^J_W9(DyZU7cvkBy z0@YLO`)4hd*1~e_Wd`a_3coS_NKvh4a7S3^(yH-BSGw03GFox_^646;@@39FS=G$Q zpu#W{=cB!#V;Ui20O8t6AORlo@C^8ZRr7D!_Z*UQ@c zFdiY)q|^rs%^~7AL835_c*rXfdptN~Zs|P)2MOdGwREyMZW39&WsKyhmuk3}AW?`a ztwh8_tvJKf+iM@}Rs(*tr_?-$1W9O3Zy_Rkb9#TclGqDSz%cVJD=S^YSxbQe$X+1~?}d_5L}*Wli{+Tc<~Xb7a9 zCYvH$O{bxBx%M07zf>bb3*x=~e#@7fmKJ1C0ryU2I)2K!&glPm z``jn<^|qSG4@Dz!Q)AkaGSqo>+s_xgS+|Gj<;LX zye8LdjX05Ana#=qjJ$G1y6K!hL=HCJ^zf(C!xkd%c}xBt;z2yFT|Al3otm8o?%*0( zOQ>0u{KbWvPFDmVK@$f?RglCZ!8Y7}AOT(`$2a!n3`xh5*=X)G|xUl_RGX~2T{ew{Pmz+=Wlvnp;0ZWAtk z`Lx(AUe&_uMDk8hWNR3?@R!j$I*wbtd0F}%$Z}^4XUOB?@<-ClLk3}R6{hSX9>I&X z|7P{oRBw$$`!MgV+rnpIAXNe8+!+flPV_dZk)+(-@E2Quba{bXZYI`_E3hhJTkg`g zg#rHo?BGbQ?4kZ73^WtyWat3qhFGd7Jcd%3>bS$v24szfXoC!u)T^iv%={Z*9V`l# ztG$}`b4xgyNbZR41<|5mFvb+I$=($53gXsb`$oi4$dqKzqN(i2HwgbbfXMG>3ebcU+=X0e;c50L+!siyojLC zQLuArtIgjhn2gp=F4h4IQK>;S*6N@=39C~iMJ0N}A{>9ZVkN4GdnHGz6cZ*Ts(UEM z)3gZ4y!qwF3=hg`&DI5O7`0XfP-`U6@`ID)`hw%}V1J^NV-vizbhVN!&iMLAt1UHZ zjHklBU^6$93BfacCfga|6M=cgEGWqI{QxOQV5A`G#kFFY@l*hXZ3X6&e^)vii0%m8 zX%mGdfN7^9!)TLFyT*|Mj?#I2DMF-QR(mPyk-?);KqiAY5G}BxbrfcnN+-SxO{^HZaE2Tm@OvS`hGNO=l7% z!0u2=bt2Dy^#!0=|4jQ~73&f>}R7&3|k=gJ>)gP_jXB%nUPBFsqR0tr5zpLGF-y^VuObpH*wLs=uzJZGK2~$PR z^nMnhV+HsW?k!`Q(2qy_OY`C%EXVW-*tU4V@u}X0d&&2gO<4r9E*zTFT62iZlz&Sg zk2KIc=_uM!3z(ofDZQRbq8Z+xfBIPEQXoIBXDHtpSW+)xn5Rt6N>!yKGI*bMO6k+R zX$$5mF)ImgcLkw+a=D^BVP9cl*%;JS$Ma!2&<4=de@7X~Xbxgwx)2NGzhI3kQ|^03 zE}}6>)z&%pQ*ID$N4GK6moG=Lm~Jn1=Te%z)?ng z6Vu77sPx`&wLSTSNgz2ww)1B4&Lm)=YusJIucr>r>bhYMVY-}uW_2UV2HG&_fz~H^_5eUOElV z!!RY7Ei9jNL7ltoCNuLd&#Lg=5ofconpTI6f^D1vq)I#5)y>3z3IRf?cagi}r|u#uFgD$Qj&pVbv{>|G&*3iB z$zBbS3b2oGJqd?HT0HnngBfQY3m9=gsnjw@X%LCAJAt(;$>|J_zVFrE%zt*}ek!oJvG7Zg0(Z|Hl{qZjw0J{p31QvJ3cL53qls)eF0>P6gPRjOPMOR^BJ}X4s z-uQl|sV()i&4H%*L)JZqxh->v8Nx8n`1=l)@qb3d07=IO2jUL{+8YT3de0N^vmP#X z4jlZVoyrf+(0}c&T}=Me9-dH`lzq!A-%%8O@HD8#Y@!d_H_RQwlD21$0E)wCK6v@R zg-!;Iy&_*KBd4*C+@F{aL8;reA{SO-^hS-p(GTGhm6OLQ91_aUngM~6Sr)?OM( zwvl+R>bWUcJ<*Hb8HM800e(>4Ab4xIXHeYAy`Rnx`w!?34C3F+9xDRbc@Me&D*s^y z;Btqd7=X+M6>mt+xwQsL)sGi~j&&m_=R)E5w7)p%jASe|>gr|eB+{0aSG5t51TNfcQG@rV>$w{9k|>?MpW zA&St7wY_xjqMou8X^kablU5_I&wT$!J)hW5!@5C$fZQN}faw3%5SfkP0HRbjm40xf z-`ept1k{B12IP=Ql)Fr8OBP}B<-zf&z$C~6J}V>@qXsK7Y*=wVtJh-LoLRMS-sdxE zvZfbO*kcV{&vJFLm)ACe_yihw4p*(mA5*QzUsIiI@6X+U56~SEFEoC*JV;XD5+P<^ z`;J^aMxbowAz=BNqRb8wKqCd{Jmemv39y;qLX0{`7tLTMav0Fx38@#ZD778~BHEpB zWGRQZK>u=|4y>LZ7HK$KK?~_D9;*R#y1%jC(tx4?FQ>P)i;YP#Atyq+;Qm6HixQM6 zGZTs|dy{?^0F(F-| zc@l9=u}$d`Fm9IUbI^Q?fpOQIW}LlS-vO(wi4?w~XK2D|i=qu2iMpcm zB4$$9mgKR*P>s|Z@DJ+^49VPuvb!ilmPCwcyxe1s;EcaXD@9sP$k-e>ievzcD#1!Q z1#{$Tb#<5LY7H@afloILR_V&nUvi<5tU_Oa0ecxn0eyN_wucUKe7ziR` z3)wA`uao&!pQA@$2r65$M?wj0xs#VrXysdAS@4F7DyGsyz?_zp2&B26P$N0wiJM@C zF{jWVrA^7m$bdD=8_i6s=A+h@QzVObOnttG6xzM0iAXc3M}Cv_D$lw3=EbPm8l*Q* zJIrP4l*Z8Cir&ON#kfJSI($tL*l7Qb@C-Fodnj`htQR%CozIE^$?=OtiZP|r3WW{BlNYwx-#YJ$wq!+)_Ss5O|(;i z^TP|hs?KpyOq3Tj9@~$oy{eNc=;?PDdWk~sUl@&zxARvWVka>hz%@=Tg<@|bAmSFw_zqUJ$5q@oGJeUy{u6ap zXC8;LoSIMIPr2oR@qWEESF1?>uD5yhAkq5Y?AgggkQz${oi{BX4%Kj5xIqiVJ&Dmh zxi7fc!!hoQnx0n78&gT&V}yfl%pOOfPxfJ*sk52U?AU6MLkW#OY*4Z79`Dd?ZE-x~ zPD^M)fN&}5uajY*VK8WeP;#&czFiW;7i%nMsps7^O!A(0BCmkwrrrEp9}lcCM5gzB zp?*;28?==`&a))RnRh)To?Gh@sdY2LH-1aUUw_i_3VoM$-_1Pf|Muk4G(Zi6G~21| z(9$ZM!lOTeb%J7>UsCV1wxd(ZwL!9TowA z>*4XudnChv?|EkSmlOLmEX3#030`)*R~`TYQ9%u3($>oLGRuIo`;3G9mvQ?53Ic}9 zM9kzZH*lo5jPvI3Zbu*ZC|-|WBtQ(pO?sC*4U|!Cgqx(kq&FUH8Xf&l zG@hGmJ`%fiO0WE7DBvjB@(*C?X$fqJY4efgY8|>xK`x?QraYRZnc}(gxR)S6(6!8G za)smqoxzCV;1t+=K6jK z_8D6v!ibvP$qVdIW8G3iCI+v;>O5jtPS$FAE$Snp>3!7Pg*85hNU@B?+ zdSR|>gGsA>MX;2~Ov$P7Wwmi=Irdt1BzS63ZQZD zSlPLpVOtJnq?g*8DhO!XefwqGh+Z(o_>lApW~9I3%bfK%ysR&m3zd*6p9m<=p?EXL(uu06r@z&4rPGhy{jF)(5E zf!shZ8)$#$NscF?8ThNgJygG7KNi{~fzU$K@K=%uyZ{LKB=EkS{0R$(<_*e@61Js`Px+Bo>W)C!V z;~nm=C-4e9j$wQ??09nn&)#`W;P#>nJa+qHhRcq-U4`czhRig5nx<-j($z!h9x120 zJsT(R3R(pG!_bLk=)b?0_7NNcfP!+}D3bzY1TfJs9^(DgCn#DXt3z(`0)sy{M|=IQ zqH}w%zPjRa{r<%b@B{KJ_3YFHTaH^?6fLLCR&SpQtT9$^c@R-u(pNhwjW2~XigKjP zsDGcVq+HzLCn6!egTZ-(QjLd>2E2KNEJ8KKnad$hOMHruRQ(nWe$YLz!!+T#4$CJsHW`kzhrsfbw(RiUn481s z{7fu7JkfdAH@!mU29>IZVUkKY8)BI4PpKLEN5vCy><9a`P8qN8xtX zwtU34wXQH?=xT(RuN7n`pD(V^PS!a~4N&B$?;BZX#Xu|e+2NBgZ%XC_8t3!Auu(wi zDwFfWt_wKETj}rvt{ejd8f$qy@*S`~ACx!4v##dRjfl!r5@ycM;fXy& zh6U0_{cRt?;%f!gOASPC$jZ~Clo^OB-{h{8DBFY2YHP|Cyv^!7lsX~HV-hHRM&ifV5z~2c_rxY{4M~?g!$u*Zbn=Y! z6$j<8BPmb-e8#Mg1VkwXOq|t{JFaQd-svj~#+sYrmzY}Y!rz1lF!ZDeQ7hxk1-fLlQyv4r@`bny}2Tn^%Mch86_ot)Ayx5sF6G~I>%6pg3_|&D;DiD_5+s$=00zCDc zV}?=UYaJ_!+RHPJoTaN@oqg-tSp!eDT#r6k(MB{=s=V}4`G<7aj*H6NLYeM=2NiS` zoZa~VN0Q)Evsms~^GPTjgBtjXUq!@0HaH9p(d;DGo9xJcHVWA@Rw~zR8Rq(+8bK1I zWllbTsSjKOyKw57=6^+GLl@e%yb*fNjZPwK=g@;$D=qZSjlnVbFt;L(j+I=8{Bi_Q z!bq^T=yf0Kg(IaC%o!4-s-{+dRBGpk;CN%AsO%7iof>m+q75>;J z@^e-TUORo*rW>;7$yaA!4cf6CoKDUQSs>K!2Hj{Oc4{q7A-3ROwFY9627?%i01~Y> zY~^o)SPRI1k(6m)jdR$ANLQL%9CTYkxYnIR?m`@n2+s9p`3W+KT=izz zVJU)`sBx#n1D`u`p(gR!cEpUy<*M+md%rs39t^AnV|7I8rCqwiTz+xl5L_9G$l$`8 z!V`dpuuU6`vyj)`9C29jaL2R<0|p(|v71y)ROm`)LO2z364foWP&OvY?nnN<2n{E_ zA|tHEFW#>(NaMD&vu$xkWIe1q<4WR9TNCCq5|?+8H^-d?D>Wty)`k)XNvQBl${eUI z5fDCTT$->|vy(^GLFbFR_bW~B?yii8JGwoj=am^gF;pVi17Me!%{qlb22^YiBE&F!b6a^dDQ}7RJ zmFz^PO=}S0lo)vgABMSo ztSHXr6c-8y7lTyi1ABa|1~N{m@#Cch9?QYU*>NgJs+dGIlE@fL^Yb*tmi)+QeS@C7C>y2=mC#pwbgTw};T zeBa}V;HHklf#Z$?8tk_CsHJWgYT{T7y%x7!i=l#5Wr_#qX|37d)-EMpwyW9UITnO< zL6$?x*Zw=$pdU?k6rp3jQA%Z*W^w&DMGHyHhfa)VmhZ*L^OQ9Kz*<3nrDfu=@jJWP z3n|Z{T)7k>!zKzLxZi3&GW%XJ#Z&eaCkUh>3FD%`+eEW29!jVn2r8tZf+jD=GX7yL zxtE7}Btw~SWzh*I zW;{2Cic2j$s>C52a3$2}jIN>pC8Qb(;oNKXCZxY=yO9%@?b$?DER!2N1McI1vlzR1@yG=4WzN9D z1YL~^Yi;vS*x1FSpsKXy8dS6p*c= zMgTP?rI+=DRi@~Z$yJK%cK#4dam~wv5H7p-3s=#`F1lfFB{S0y``ZljJ9Y2pVvcZ! zJe~N!%wtH06F_6!{NK)dIiRs#?Z-E}NZ>J(28{O5A0k4C=c3ds1QrZPNi|9+NR~zg zM-=(5U~)+Z)5DwWO@RC$GOjeb-ZgadoGRPiF1~LCQkuPGY2gvg#~9T)jxW5QwvM^p zUGC1eg$#joAO0 zw?I8@{WCdiT?JiVBP-e^#sGs9)jqK+1RSI_4br2BnYc7~(7iocBGzn~k`wCF75RNA7GyCTr9k5d`A{~gx_7$cS&LPMMs8! z=-=|QmE{<7=jE78`WLzrmTY%5U~vf}@{oPK3V>1p?+P;BwZ_Nh=}1TYi4Op)3m zxwmtHY)i@YG=obG{09nWh@ z%BqDq88@UfWG0OZbmHaN#^VXbC5U7*XzK)aPGSes z)+0RTk_LIO-r!1AX|Td_-!{!DQK|cz`D|l4(`Kk_v8$~bfH${0r$4_kmUd&AFJ9Fo zIkg-ATQG{rmn~lJs^1mo0(3D805F4l`54jZ+%f48lXB85ko-=wdwdFl;PRQlK$#*L zZrV_QTLQ;@iEq2`kHXftM@{41aa1itrHC(!UoEI_pu1qcoj|`Xf`tGZ>855R}Ym&(8gEW&*v58g^Xg~c;&0Y?Ig9}n9n5BQU< z=zPuoSCENB5A@W!T>fJ>IH>0Ke!CoA%>@V)3z`DkR`}R2xWDZQ-(%=^hTq`ONyhMC z|H5O&_5C`v2d!Rc`g-N~3Y3inIU7GvjAbhtL1H=xavg!Y53~)4 z$|?7RO{>3v4HZa2$iAJv%u?if! zcaZ;kkoDVpZx}xr5YrzkkRpK~9w%Yl7%rj21{uK3T8J;A08UQqwq0~yN3uEMqV%@T z{qp-mVUINkQ5sTP&E;sa>p3_rd%M~e=$YMh#@SBNE?%0Zg*DKJF0&5wUa2VX+n~i{ zKk*OAg){eI7(@r9@Ci39(wDhW(GlzvxD9cT0qJt4ohlhOxqjTNVwfKKfc?&@l-pHM z8Y-Y`#wy-{PonU9=pj??C)z%=K%8iCJXPfyyY|xNf{CYyY^|#tN+Ow?`};a;25aE6 z?6$^5QX%;~kZ*M1OgHgB^qj=byWIeRwwcSe3$g8+2Apj@3{K;KscOPR$r?DCZA6(*GS- z_#`wSOGVQaNf~`Jp(u+vO((R$JXh<0&|DC!u_32&LxXluXJ!dC9DLqL`cF|BeVQG@ zwd=>9j+dg28+=+nW%cpH1h}qOv=0p0l$L2D&a{n!@sP~>EXS_;?)5L(qi2B67iiD; z3u&Y^JK4S$`man&5xis#`(L(Dr+MgGVLR{uT9F$_+6aN9T5JJK42<+h*M2j)%`r%G z5pfT2)xON?@F0Y~GWOB1USuJijV@9!DN%u78Ufb(CYvKjM9!kBlI6v}vrAD6nI4BR`#fF82xkq)S9P4wAtF!$bxg{!1EoFxnubS>UYX|d~Yzu;Zlo{oK z=OM&B6XJZ7x#TN$A_5I4I)aMhxk2N_BT4@T;qSU+^$EJOr|`gBi*ISFj4=(1p&3PU zA-SlG@^TAWro!U}_2JNcTlV}z8k)*A4N<}I)PM7r{fGwk*{>3s^$W&ASeLr7@Yrlj z>EbB*CgJ7d=`3_pf&C^LEU15_&@5d6^@C3R5NlMNOdaQ|vdTxp^)$Kdb3pUwn5D;- z0cQ=9s}^r(4e72`W8`Bkv;?jCAaQYX;^)dA%bZma;Ku&P4eD^*nqF z-s(ffd5zp06ZP@G9l3Xkl?V#a$%w%3Zq7IbS+J^Z?1~X>wa_9M=M4Sywl7FC$k(Rl z*0<3`7={1Xn6tz4=KUOW?Kw`*wSrXTlCWG`lIgH4HqAQ1JqU4#ZomBwXFR5;HOwyy zXf?{(MJ?rXC|6>zh`m?)%o}R}f>e-Ah9Qv|kT&|w{@Mm7!?2*&yG{Pj4Ea01y~uOx z+n^bF6>k`L^=HR&lsx`N+-*A7oG~b@* zfGYG-*6?b9w&1}J+%0(mUWkye(+-cYQ8MqRkUmJVK4q^ihk=#@v_AbvK{&D{xPuej z{Ci%9%{)T&_QaU^gr(fYWUB~>^*z2ZHAprFRUq{tx(OK*ULbn?Sjc}-Z+X5Q|IP!A z+iQU$h+c0E&;8AFGNkg>`8)sNl(p>qGp+opPBarR`fQku>ZO#Qdv#mb{ zc|11-UKo(r4(~P=n{MLrc7LXt7P)Eu7%&C)j zlcBv%61&{uiY}%V%f|kPeLSo{Jj&PHL9cJhg^!L5yW}c99WD4GxePmaWpy9rxWw=X zT}ynxs@86x>t21YbVioA*Fp+>UX9A3$4d zF~WjJC?*$+RrnT+{~3jwhFA9MHoDbj^GK4usrj1LfO+X2K+`uml@&A7o{9}`!*dhq zT2H9QE#NN-jzuC4M?nltCW)^T<)Pg#m`+>{9PKoedms=p=V!(=tvj4}K7zo9!)d9= zEwj%AbAr#Q4DXPRNm7Gk)61DQjQX_Y8XNEoZ`F1Us_83%&|I7FgZq&VY)f>y;$CdM14TE&r^LXuVF_#;v4w?3hc*a zcy-~=w!XuJDrZhWGz%_*l7*sizMN)wOlGS84*39`U&_SdU z*fax0h#!iY7=a8aAaS!@SK*v{5r0Y8LaV%kYdKbhJiT6^rf49+BwW@yj8B7}QIr=A zu*~z)pmg#OuJQ-p7lbEZwz(&GO5Rl%*5X#WBKnK&tanG$j9ZL2Q=4rqsgWRL6 zf*3X_iPeUY#{0ePkeSmy2J8qKs!{$yzLAb16`gx6&7&eC9X&XaY*iLw$SE7k>pd@j z*{(VpCbC1WkTn{fhZ+&*N33jaixM>pIBYmADnvnCP|oXzWhlwIFB_au;AL2UP`A%wkF_)he?fww; zz^;)Ohi1;}xCwm)d$XQ1kDt<63z%IYbIYrJf$hLMxe#Eggb^GfmG37-&{Hx45a>XS zisPl&f!0u@OAi>f$Sz?oJm2(-d6K>%{F}ZR?9LeqxGu@M{PMe@eNl`g{vux&*nFZb zAjxfx=uxDWtT3liV}&|N9Ri-wycxzB{Iof`gSs{fBd|$2CL}e@}PbX;)7Ve~L6Sf=vw0=bKo6BQ(@;GLi9HVTXG|xS z(MLenS+x8UVS~nDLq9S+wcKmG*$RVIWpi7J4`(ReH$iO%&`;C5SAZF%vQMotP2hI3 zoZLt8DF@FsVewpaLE8I3<8iXQ0nAd(@V+eDtnE+l4;Rk@aCGk&U7Ih3bbJ{hAGC^2 zVG70gACV=rXZ~i#6^1m36__)dN;mzPr1lXoNcjuX$^1kpz?GdJQQ5DDH45-&#_nG*jE z*3O0nqWV=c<7EBIkw9MJSYl4R%U6oPgP~$oeqm3wV8XjBS7iltZX&F}qG3cdAyYL- z7uNlGRBTW#ZIGvC^EXD72%~f(I|Vf8a8UsjIxHh5FNBLuVFs{t8(G#6?;WSm{c6G6 zGn0MXkw5Uk{KFqJ1K=Us|EqQd!^1njew@GzC?Fv2|H+25HxA%jaa|PoYYDZ2C}N-` zHL$4wmgSdM??${{1~F*5roEj8Sp#Y3sGvQH6$jC{j81OMO!E(~nt)=y;G1*4`DD9G zh>M4cL(d>Unw04?f0O6^G}D$Zi-!~N^?nWo@nGj5>}@1q^tqg~nKBuli#buwN9Z`64_7w8VbL*r#?(ZnvR-(%j92S&I=|>$O={ zK$YC4yg0A1C|nKm+(}(`UIbsNc97&Dmg-+0>{k ziwc&l5s{0pt%^P=5U|Eu6Um&ar-xpc%&XLJ*^RSakcpl~2it7LieRtD zPMM(<2@i-=vs`ap6q_18BxkhhPU^Vh?W;)e>>^{@W1zxygs#=uGK`IQEGkr(3Jt`* z>XC-JM9J(8_1DilY8j8^OfK3x^n_%+;TT4Plw2_i^*Alv;q5XSaT9GbwaIuKHc6S_ zc3esJcSJ|3=+YO*#?@ObW43HF*Y(Uht>k83b^u^lABvYrwv=S}&W7C%4KNFt@u@={ zhP0VDdX@8ws0%Nk)4JoDU&j81aovXZDOJ<*s^6P|B49hXGhCd5L*%qFyn1QQqD^yc z!%btS4b`tK4qrGgj8~SK$BJ6ngVwHk#l5;u#40z(T4YA~t$+@TUQ(v)l(Fl@CBvqq z(+OC&Su>4AfFAPHuNf8a0D~)e7h+T15mU?fQ3}dnKm`fA7pLLzDqQy7&{o>at;yvh%6$Oq za>s4>eUoXt-{A7dh4Zk+@HYA4xBEVivI3+%1Bc09gPHOzq*#OD?7I#h=|E=0^1Gl) z6UFOr9x8_c!PgjX3DdpaFF1Y`%!HqQ>dRhxVu7v%DTocwLlD*6n}hrmUW#up#gbot zZ!rY9(7jqC>?AtkFynLxw%yOLC1-e`WSy9VSt1mY`_VN8f(u5>6PZY$kx}#!wVkA|su?5K|ENoQrd|m zLa|0-9ADHHY(j7%(a$UNdi4U#Y$te#UASjbH2X*2xCE&jWs=s`Ao~iKJH34jv1O? zKBTZpv&U_nD_pTzCA^uXtkxSby+nmJ3sLt=8P0--x-)^dO=qu!nCQ2ffOHdhis{e8 zm8F^;>mtgA0Kcn|;xX|I42x!yaBO6|(OyigKj(H3h1;dr(gtH-?{6o7?^VZ(F#Ns+ zk#O*c{7pjXqH7~EvnRj@ztns-y?1X01D(+aw2T_M$h6JMN02=Vz)@Xoh>VxR%Yg%N zSnHCf!nLDT{(!|c{sA<_{hAm^I%gs-GRVBv#3|X(-S`nC?ehP1brwK%G}+rHxVvj` zclQ9n-Q9u*cjw~n7k9Vd1b26L5AGTQ1j~0fyWj4g_f}06HT>uq=AP;4>3+_0TAN2E zi86if(;Cv#%56~Zusr$11G;+iNm#8J%jL8u?eN(hOWCS#r*`oO_4-{8)8(o7tPXbP zOfCWB7k~V(*a)K83{3EE#4!aE1D@S67m5CC zm;zUApkV_bF)$_MrRqJ=uLHiUr3FHAA#ma1-}Kh<+d*@&JDPLBb*<~zo;@pn1q;AV z`f3GbjjocX9@khE1}HqbyHh;gDcio9IJZmGUl|~IJ(zm%yLxc#o^F3$Tn4-n_$$bT z%|JFrr@WE|qXAJ-NWo%Q0dY5pKg_~$vQr`C07{~4;l^+hu+4plB15oZaWKZo{h|Gz z%Te)J#@1Qk!bG(~sDnJ{g0TgN`q({E6mt-ax&6k1g%Ng-qCL9^QGVcbbbQ%xKzZYEqL+6ZuRtaKO}L$9yW03tW!vftNag8;4{6tm{B2ll;h>Iz4JGOunNU=M zmW++0D+ioCYO{r?FgfHJ{5oG$jy#b%khaTAeBYn1qN$WnaFux0Hf(C_=~r+*A8i!m z@+AM(`30~d!Sdm16EL`&MY>si*77zfh>+&hV0WyL+LIa+GKzy2(wD=0emuzAVD;(a zjLFcb?sytS2L4u!cKhP1bf22UL|O)+5@Ys*5L?d{dfAq7nn|d|VwR;Hoy{jdja%Ch zj<%}LU$xi?1H1{G`xn2jsKHg>c_z2+LZ+Y^pBv@!?QYxA4g*9Rv&y(G<7vT(VxpE> zIr4ee)%d-EyF(=~o8xFlQYt3Q$t|>U$wqu)8IAMIf}R6N7G9Y^Bi`Bj2GPDu^sU6} zrk+1tDB9X-UY(h(;(J6Ll}E+ERHP1@{R1whKTzTMEq9r;^d+{7yl;!V9JX1UY;D&N zc$tnUV%=z334jZR&6Hx|-TOYtFtd#adA5vMrb37_GkgI8Q=0)IwkK39%dRH*li*d* zfbRuy-}gBsdu|#>)dsA77feaPGTMs#B8<+MwJ%(=?w7)OgCq6ZyF^^} zqC;qeSSuU0G;7!4&NTfc(dA8SU(yS`^rGnF(o@hu0*zBHxM9VKcliyix2;`#i@v*u^e)_9Zyx5WTU!k?ml7C|1H7O&dt+ zA=<73zyn;EYO9Y}7^j1*_0@t6e+>xjvz@sj zUC6RxEY?E#w(yPnAf3;f{3k*rv~rHIO0xZzy6YlyiY!u1P3Fn4)*5utZ#Rzum*4R- z2$DGz+JMr|4D8C5FEVYNA!iS5RtKA^Mpxij3H*RqoYNjBPV9M`cDvUh z6Hfu&KPtRFWlIJI~oJM%S5`*Renz1)9H4t@R?l8JqO=OOUi6EA(?o05*dTIig?m`mPiXc9{4 z3_ujwg`LbF9@6-{msZJo~wr7dD_BmZ@(O z3rj#ie~K)p*yUDijR;iJ+dswOCq-o67yQAUC|E>=LR0kOofK)Qoh50=e$D{tq!4RZ z0=_Gm^AcAiicf?hQXN5bj+l+RxJ!}l$^horLhY=c&d&{Klnf+-E8s1t>-zhmHlB3| zbr&CSzMRR9v{kiCH{VcL89!Niz#mA~K9TMBj!_1TYw>9jzCo(|fnvy%7~IY*trhUq zOZS#@u=1fVE=o&>C}W8FxW0_+E$NFI zXbw?5r7fIm8+;*c?AtHP7^=cz38QnH@g>&b^&+3wJPtGepyH14H3IFB9pUH)%*KWl zX)AJfly7*425|1w{=iTTr1DU`g~;x1KZ^Q<03zwDyZiQm&9n&a%2`p45dr}B?%Z-s z9}>46^%vY%KkUkqiMB{0bT>os^k7gGzO`@h`aeqGskZjQ#tUkt2;&``Wv%7&N=)&0 zUpnGD4>D>e3&$z4qjQyh!{gxJAxI4cb!oHRKS5FKkv99|df~>k9K4l%GbUNnOHEQq zh$+M(-H_N`hj@V*N<1adBLC)ixV!lMh1ll#01QIC{BS_U^2hD)7c#L}yYvow)$iEYnca1&#H zH~%P#9x{OA2PT|!v(Ys1K^A&E@iJ@O+3%?CBDcHS41pW zpqsu&9mVk@YZLn#JRx$Ze7n&H=8cjqot57(+e2s}9mq_}P`J$-9WpbBT5*8T4hZ+P z{>jmX(zMw-yGDp~n|vJP)V6* z^**dQYRr$>kN1l5s9r)2QQSfB*!e&KA1(u`Fc6X^znMrU!i2@V zm7U&j6m4M+isvp+Ug1C|5Bf;?3=5t6vYJLNGu!_kPcIho~A6^L_IgHh)IM&+;ns*6hgW9X5TS9smoIBHx^;P8oZ%tnlDLZ@-`<1%xBD)VmpqtM^>Zuvp zyExvmNGHVgsWz~RQh4q|O-N8FVkx}4ar7OABg@>C)l#Z0Glm;SbPhNakI+HNke;Hn0Ia)Vt6}vfA+psVt=1&((kj$Y% zANW9&CuMou2BYPZ;)C_$)yo9K&S%U4sNH&9?y*RHop5#(-?V46BO4ghxB@B0jl+Gc zB4_b*rXen}7hutjpb(!YqD*Dg)N&sKN5aIqXa;+4i3DcFa%h-#(FgIE$oGDp=hei} zo#vuZRS4;i8=G)RJe@wK4 zLb?J-lqd{P``a)3T`tWk^AXm-cljf3!UwT^LXCuFnq|=LF%2DvGvjC|vX)*)1B40( zGy8oYkJxGWGgM|6bJT>Sz(vcXKRxa{<$Ni4bm#Z_>oki{7m!8n1DHXBn zN(9w@MtEO0+0^^ET%Y|*J*(Ptb`%T`W^f0*jFbyBK_i+zDMsRaweWekJM?}vpB_q| z1i;i9hv$OPFD2n6b=hWuuaoZb?fD_rkfS(j*zOM~F>N)jUP+O#j9|k{MMXC`6kE(J za@&xTGrH^qO_$9cvlV~VDo?;_MmFQ`mfwYu&VkMawzCHUp2j*oo#Gz6U! zk(aL1X}sJo+B7|F`2aPQi*chjgxzFyT@APoG^Xb=(|(AKyufR+csi_`P4^~)CN)81 z#--Y;7;@Z0EdE3M+Kr6u7Hiz1duAEAHyo(5%<{ql=Nkn9Rcwp+HMBEekpolBkBaa*BmB-5u4a6(R|NQuRXrW62G zb_*#V3d#Ub;Zz6e=TPIh15|&s4boAMsDd9TpHs+M*}Nn7d4rqJ5z7^iTm4&9&mF1| zj-;I1-RMuvstyROS|yI-{GpX z_+OpKy`UTfki`rwqheT4HOEmwrh|EF@;%`59GXPyPo9%?*<`eosp=}3Uns_GR%;qs zZ)#7~jU1DoP18-shc&+L(r^{zj+CXAm8By?i$3V*D(he16^Up`afS`$jOjiP4t!{W z{i*mwxb#3(m@C?^#%|3MzlN@GT1D?jUhw&t=c{g^%SKv83##n1ylI?T*F;nYnx+AO zdTtDCJ$UoU^g1k5H^|*vs_Zo`v;F&g{>f;~m~GMRg!7LfdSmE8F#h1elZQ_I>2eGl_`XvW^RG{L77K*NJS%VFM;*Bf#lA$=mXB3#Y?5N{E|5Su$=P@hmA@-W39 z!rp*ids=9Xx#H2cR_~fsW2G|lAFbhvzC|n&^q*{BkBzVd9?kL@9GTfW{n52UnZkVZ z4f7NpAhS;YmE4eO@y})Of}scTZ=jB<2@1&SMGaI+#Q>=NQ2?!Pz1@2qj5#LhKz>$6 zi~24(t~`+Y5#L%a8kUL%`j2jPs*^{j^(As)+%Co=GX;1Am5~2ck)xX~str&SICVKO z<@0lL;_~(B=PR{8ufU18uw3@Q_gIcdQO07H!aaNjqy|0?%n%v`*3>@R9sZh~>QHY) zIN~MT`f|7W+EDWf*<~wLjV|JnY4q!aI57X=*J+W+= zj*kpFtP6!fWS&-YclR?}B{-O`L^UHl#C5WWFgf09y7;VIEg&qrll;QlK3nJD%eVq( zd(=uF%CZs4LN1D^_i5-8c95t52a7)6_8|9?>Nc{O1&7m=2r}G|TXg)HKv7?lJngD6I z27OZj*|tPua$)Yq?lX(RG_$;-X1<#WvsEeR_&`d8*vJVuzA?k{)8c~=X)OoP-ErNjY)9Hae~yGn1(u6AN4c}#AOfvzKBFa|Dh;Dd?(i%2g}fj!@l>KD%Yg)R#b z$ND5tJbQo~-Tpn63pi3Q8A_c7@UMEg^@_5F85XHjw?xCThDFu zGUc(RXEZa_pyN|P#4M+^3IDcy$XLpSbT=pu@7t~cfYnqu!6{SEmEDG!Bl>xp0GMYf%;3hTPyqTiaWyB0tGjVQ6`%GkXd{;?LeS>xwR$?Dx; zmq|L(Fc+LR#fIL4RQu|3wU*;D72QN+T!}8tZoCN zkynI_{BApLeKgQg;2Ge{JiP7j3+ ziP}}5i8!RjA`%04t=RLxX%`xXAZqw3s9&|>KOTZEamVXjMbPIv`BVPWH&H-XBX+2IY@R$8Xp$Wf%@zP*bggAm0hu7IP_eam9+KolPUXyl40}^6`zkj8bd_oef4JvL;?eoBF6v*zaHXlR9z@37^n2R@y^2Q8B9b6y$txGP9^R+SQgL zO}c96*JWr6gX@cEuM`n-8#qf$CNWc0yy{cE>Olq89JM)zg!|;1GDog6Z7b8bG;Q{{ z))|qQ5B~Fn#s&j}g$3!;M8F&-wU9PJ;b9G0A`$)@9{BUbfKK&s6_Dgh&^=MQkL43A z>P<*EjWld{M;NJe3htnZ99dkqYImuJk=QUBQsM`q+~LJa8ELUAp*DE#xb*& zn_`J*uo5ZLhRgPP^YlqqsXf&dpxYP52)>zfAbxQsw2n(BreU-${YQ{=j7mvi3;rPI zhsJa1>G0i9z^7;OlN2QPWC-vlh;^Y!=3;38d2DF$I6v*}c#8*B4vYl(hQ;r+`84Wnb0hH2HZJ!&b_t?%;oB0W+> zW;O~lIzLZ11FjQQ^A6j1%!*akST=^8`q>^O6by8%0dt(5B#`}Es=&{ufG@($*{S#a z^3yKo-TML}2*Uv8w=NKA#mG_^`O`H`qPz zN6C5j06vmLg2!ggs**Und~FFc`T45DsL%yz^Q>{Y$}^ccuScb%EQ^@yU<;ccQ*6ae zpBA}rWQ-_W=Zq1#ydlAf>XlmB4_ck%b+FbRIK93hmBl6veDsA{KbFN44TKT>1{pW< zzyb2Lr4bNP!dgqV#G%?wMSE6aD{O}~t8a(301Ub1+sy|AQmj&_eewyW6B5D5ou%x` z)ASjnwhMH`PcAHFATLUH}oJ3OuC{8fL@ zfp>;Cls;gJHTse7CtV1~4IKxGIe|FLZ_>!`7(n8~?s2@V5+-I=?X28M#Okm{;rE@S zsZQnsHae&t6fH=E1qKErPb3B9j@cMHJ7;TK8R-osyvbywb9;F;nXT3f8rkeXoTacf z)gGt{f#Jz=rPL*$OlWx}N)S(nr=vCTh_>{h#iGUyQ;e`E-xz95P9FD`fCGj)rk3S>b|~d za&|A*Vdq*t1|c&G&EC+yl{r7LBmF7eQT+XgB($8rhd|`{sM2o?Qj8z@5cbacoDZ`u z5-ta0YDPU$N)F?WE)Eo$ba=e(s&;cOK4OXI#OfD3QyNaZnRj)96(>(kn(eh<=*2#p>|ND^JZ7eIEYeB_iXxP@aqr67=W|J5gTSrthc1dx_I6 z$|M_BEHbgHJGU`4>?3k!RE=i&RHoo}XO)lYab}e^RsI_HDSiNecQdGU<5<;eNwPzY zqZ1xToq9lyRhr|~jH+}qsJbyO*Qsli&1(Dc>Utg#>YthwGaZf5b#o|TdbbG1nh>O_B7lnMQ5c{$*?28Ge=z>!w&^USjqui14qXL9UFwu zxyf&o$Xp`Qw=;zv@?R>J%`#+QPA$PY-cgVMjNbPr(oFMwDFIE?7&l%*lL^oouq4n8;W* zH5vQh${)naZn7;O_!1p4xxuoj8_4=?x$>LgW=msTwecWeiPQCLsY+{|2Db7P(Ez>j zQX#l+k+orSL%owKEl(ricc8Me?rO21G+yCc89|_a(Wo_iqhw&jB`%|0LTg*k8y>R6 z+Be$|KQ+ZoE*3B9jI38G?cl;Hh}Jbi$V#ftU&NDm`EZdX-MNC@(yLQt`SfW6pOBQB4^#fXjuIPLoVL-BMVnkZ%Qy zz1L4h3`KtYz?{#L;fbX_A#F_!<(18{F_0E1HuyI7R!xvmRh;t3l>5zyFGajeU&5v7 z-5S$IXI9D5bQ2}ir+=zMqS*8ItE}8q8r{cBC=g%*j*@oS$*ZDmiRA=?K4wI794O)Z zqNJFyDVQH6aJ`>GqJuNplrzS3F{8h7BC~@&#V!Q8s*7c&T7fc!{f|#%phGf;^+old zu3_P=SEn#*wK!XqYQL_W_wx;mLePqaJAl&|;NVH6Sy2W-wuaQ$!RbuCSQFSGwnyM? zCT5QU46}t4no0`-;c<}B(WW7@sMtk9+1V_x5n)s~&Sy=(MI!1X@dy*!9C<>|DHc_K zi~3+ojkePlZvN?L#j(j*o6y0G@2x-O7CqD+zNN`GXGQr(oa9*dI*2 z3pT=S61U$Of)qY+ZBHU{zqlXdu-|p=^p%c^XFzhUN_1RChvGUQ$jm`9Muqm9M`3~4 zv0i|y7!-8zUARE%c2)sJF}Jj%ZWhu_uX@=dGRsx^o2%NZE$rGIujl~Ygy^+Q1h~(5&Es*UTR?eKXJGXD_u7tjR-0_MoLJRGbhuB6Sfr{0WdYDkZjiFFuzAFiyb@ z@wdcKF@}DRwEE%QqNpT*d5)ae`<=B&9b+Ig);pf9>oRk`7^)5mxadY;rm+FmpqFMGfy z#*|7AOGFv9SByN^SHULMMXE_xV2|&VtS9gBeQB{?XVA7KL_L~av$eP)+A$Ye4={Ra zBhG*wgXa{Z;`Q?5J8X!q`Vj0zn?^PJzOGXzDL0Me28|}J6VPWpKJ(zKm|gEh=muRg z-YpR&-SY%sIPi|*54p*#;`oT0!1%ML;<&DY4PC*xm)DZ+=F>Tgk_9yk;HxTP$ez0`P z4d6b23EjvY%mKF+BUaiuOefAI9u4Os_<-~~U(aSF=oa-M-HvEB4p;nU_sgKsNI%`+ zP^(Ec$i4biNCVP}61A>U5Z4@TwKK^5OGSs7l}V<7fQgrngd>dsA>#0a5x`o}k^X8e zd{w9T`(vHcPx8>0&&8q_^+puv@|ZBXx>1Ez9U`|9iR_GA@&ahll#%YC=tP6;bQWrO zkJ#1A`=y}*ba7U1S$g&26O!p?ho!wMoY_pY($t~#cPRNb^!*WJ5v%nAq(LRC@jMxPd38Z zX`j2)+_WvqU+^&gy0UtF`X8I*>6lFU-wb}ZzjKr;oJgP)Fg zhm_Nzf~kCgHxka8eNWZBUgH>be$kAyuJk$xZjjzh<*5z~2|h_!$t_7i1dHBpyO%~h zIpXmGaOevtPBVsfU4WtW=3=pI6N3(K-HnLFf<&)nbhlvmBMuC%wzTV10bTuW>u47) zRfVQ5`w7A!e!eq|K+r=iqkSQ0O$>Ot=(yCLS)%*Hq&VNGe0|nuv-4;6I*nhom)ldI zMkqSBo>^`lY#}ayEW_(1rKLisep9YC2T#ec**d?r++hrd3na&iK&F>F@qe)_V#Svx zHVP0==P(JGV{X zOxs$>P6G!S09DT-=z<^bWfeCiW%%44a=YxrzJ-oJDeUK3PW`5b0}MNX+;(di7Tr&P zh(Z!MK&*-62DEM3``Xr8zHIn)z9RJv=Mxd#YX%2|IX}Und>A<~#HVVby!^!Ax!$*V z6T3f{a3HYd-;XGNv?_3AeUgFKQ2k>VW2vg1#ZFR1XEEnSL;y;c^-P)nqa~hfCski5 zo)s&*k7)zWEAfO!b>M(Gr@A!L8w_(COQ|?bk zI=(t=e;n%aiqt~FUlO4|sH|UX-)>rMV(is@dm(?-?_Hr4?GR*Kxc|0BbK>1~TK(WL z_OxRw1h&mS;@P*`5mYrroCbk%xT_G0m`UuuV|TB4)9Q;tn^YNcj%910{aeJc^ZH5= z?nYaVo&oS-&xTCWAOB*AxO#iMF6{N;_b|5rR^iMBx8)b1L64_|*hxPEh{>m{2zBE4&`Y^8VjK3Q-cT`J+1a|kO>DZg~tVh+gAJq9b*OuYz zxfSM->}6t|1rPZ;k(5Go@>p>zkeT(!?gG78>y`jG*S~f#(e)GprLjfSdvt4Sae&r7 zTxMvrkyusOGvF@=ts}dF$#NDztH?o@3h}cWz9LM5>n9Z#VD7+zAPw0s4wWkrKjD&} zGz#7U3=|c=Xd589geFGv+lOeig)Agx`8y6qsuvk#Ci6Qg>PaXu)pWF$qMXF_wxZw{ z6)OS1U|u@QHe&kxg{_#>d*#2jRm|!FHIqv9G+sX*&e6T8xM3}yrjxl!7UiI8FM8PK zob$i!3Ne&0C018AbfL_L&B>yjEG10ZY`EK)n=vcYtk{c(N0w46_udQ08KKLGW1Q9^ z(>n^S|APGj(X;d`$!fK9Wt^AfbYJ^?Z{%A& zZMrYv#ZX+jBgQ_tKwvHlcj?&kBQ59TpvkC9bD7`J>|U0(f1nA?a(wB|SET1Squ5Z^ z!3K+H;Xw4**n*s@P<_d=Sz`vi3CkA%dg&oyXk7nyhUS1%xY*nQRb;}+ooG?`s)?V7 zPUH6$`7>2&Z%rJSbA;@{^wngh@!|Y5y9zjHnyg$ob>iWvP1afKmDh)0=A22~8pIVh z`%4~C9pX7K>0{zs+KpY2rV2R;K|&K#y`k2f(a+oJcgC@Kb#u_9(KRkCWfG(SRdMF2 z4fmIz`T(t*K2`A79qL2*-SSCO!~PbYxTB7Mqpk#tlDlmAN$UI=-pQYj%d&`|@{Fol zZFn$@48Pbho~{`f6>{o!{993?j-&h-O0H5CyyzI+7tmh_ z!$9{{tfWs<8Xu)O?VuK_Oqo^+W_^~Z1}Zv-u8hIQ>4bWN7($z6tS2>Z;+`b`ZKSLx z2kelG{{4W1g5L4r2k4g&3z7lejS#v8`Z{Lv?Zi$PogEnk4{*;9ohV0uL%OH*Xjs?& zN);~p3m-ash6}3lgu-l%ef1sR8>A`i>X3J;BrQBSH<)U~EL?IBD1e zj%8jJkFueb#5yfk-ZR_7y)=1+oYtH7f^y7pSTcNW znhnNXr9F2KCwdEo1&P^-6J+xfn99(`S+qJ%cx{W}dWuo6n&UgPHSA|5oPoziKp+q`W*AW8 za8OY%WFu3P$+B6V`L(n3T*LsX-|A;5yep2qa^Z&fb8L72W2*yLe+4GbjGyqz(Yoa5Z($4*Ix<}k z4E!naKU41bM+*SCowtvW9KKt0w4WD&L@^?WVuNC49R0O){)^g} z&c5+ifp47HJRm{dxhl+kFy^()N@MZ%Lohxa!-{M-AC-E^rva0bgL0`a)7~1KN!Fg- zq2hwqq(i&pTT~6lX()cb%&BV}x19g)CwLBKyA+J-vBc;Z0_ZH|Y$j zV{31vHtE7tn#V|wG+L7_%5NDx!z_n?G&9ALmzn!32mzwY4ST#X#WgtCDN3;)Z>}Cu zj?AmgYmgOMc^+mfn|rgq^RJHE98H@vnxb*J*06;h zPObw^L!541?zGU9P;Xb}E|YOLFsps#!NcX&ZI^8-&H}*uOuqsMIwM~-9DU0?uesg7 z{?6}+bW&pmAUe^&pf$KTc>Tu=6Mocz&?sn*6$~`XL|QZa1+69RGMQm<9V7?cZY^(8RaS?D%F-~3KKAzlbDn&SMzPGR=FYI z;xQAAoV2=nclfs-hjR0>veJ%xEhuD{T398ICN>_Xd~+YRj+oe=hC7Ad@OnPHFa^4$ z(cPjw8ZD7VMA22k1_GuEJ~A~$et~q2HE$)xXiFS>f<3(%Ey&gRBR=h_6Z|2jEAKHx}n1P>XJ0!(U=!Lvl=tXAkQIIpgdm6M{ZS~ zlsfqDDpQd@-XTZYndIwDzwVmtl$nz%Dme|TXJzd(uw9$c75N1v0$XeunpM_T(P}J< z9N9BDu_*xCU))WW|s8kATaZGty@Z_9c895RoOyax}r`SGO1K+6;mWqmySmH|Wf8-FVf0v~zDKM;m1 zCssS9iwt(@`h$;BzpjUFzx$0K()KV4=D_6g`pf5j#T#}g zn9Um=%riO)lSO6{NzK<#^ftO4)gz|bV9u;QAiiI$dY4^O8Vz5yuO>rn=rdOI!oKU(B|{cTG8%dnujilny>G&t3=5m6J!zvlF_z0jCloS-c}W z->=;xSSBx3p=T`>JJ30^*T~gNiMsuS?S*bk$w_R!Dpr}%LnNstys8@C)qg3CDn(0A zWm`GXKh%~(@V1r_?;$6AkFB*BS1oOYpa1m$JxreH3&Q+Z5+reI%_UwJUjL{^o>=esImR#DNYjyKd( zfTwV@$e5Tv8?vz4*Da}y(8Lyq?HAied$LcgQmif*r=hqdT9T|z7{7e2XE5a`mutm( zv9gM0@_y7&r>a)tb>HJqm&o<;V8m>%6Av>i=0j zed6afjqkq|4ji(k)~tHzvM*ein!d?K1pF3x!&nKyI}eQ!^uR*d;T*W;lg8hP6?;K3 zF8mPp1(Q^VsK>-3iw5H}Q9l~=gad0VH5e_?AnV*u<1zfJ*h8LTjHCzBT7{_>xiqYO@22fSFfA^b;liQNb+gjzq1jomKEE<~8gmjJ2azIEimtLsRL${Q;%FA>SIPY&{t3k8+IJV1r zVwKsDe{RUWhew$eSh@|tb&kX@vI2Yvc*V_e1U_(GeGp;nz1QGSu_#Z_DFI;EY<&iP-3vfLzbXX3-d&|)8+ND@j-U;XkoL#O{%!w#4A@A6wGH0Y z$SJtCYXv#znxh0a6R*C>DymPosvBXg1etRg7BG61Qr|S(G)-^|Z=(^JTEcm`tGp|; zGO!2JlJ;s-CGYGX*FVyt?p2H=G%-v|^S~ZB-;XGlNJ%5IkW^(-hyMAxOtu?IEdz@yceK8M{qe3?-DiGmEAjVT5@wn z7Cuc`FCk`0cO9IAdRtt&6F$39zT$UTS{rLY6tZ;8@sDOw_lW8pzoAk z+w4Z6q?JKRFIWJQZWMHlv?*@#=hN4{rv;BTKs5ok0bpr;K#qEviZm(mz%TGbEjc9z zp^>8$cO2*~>Aj3{l#aSxS7j~q+w)GXrMq2`FaUUT-3c29O_8SWqfKdB*TLnf!Do{+ zQpq94tTj6hP~?oc7C{Z83}VdQv9;0ZymZnN@UkAx@C6|^>QHs!?J|Qzl*TuyF>8G8 z1`V!2vera><3l>3mcEP&=N=uTGs`;9s$v^$hgCTXUMLQ!dDCVl;_g zBm$rxG>XBXHbZV2k}$s~3u<^i1TOXymlwi+P2#7s!WQ(R@`znP{HB3JSg5?0Qkqxl zz$MEUZ#qod3b>KAW8qf&l+vjl&$NzI+LsGA>h^>11c)vzhT?_gl3lCsK-k1cYkmA# zp`T_56EWLGpG)431;}voIQI|`7CUYA@&Pom!njCsZBkcEeBv`PJQsjb zCmm)bQ)K0}539RENxP(E*3aR7;^z&yz6lz?4Fl=EsLf0AGmZ>X-aS7;v=Dx|2-cb( zv=6^3xIm3)%xa6`G>V8fcn`c1x>zLw2u0{=4AT9G^8NgIY4bq<<=a+Wd@u>2U0_5QO8}=PjazaT%$FRM&+y9> zBLg4S$UNr1j^VO>uADACKMM75;tsSHLTc+I-TfJ_!Q@b=-^;YHB3b`qzV6O{?=!}* z9&mw&Y}FG^p2jcW$e^6>*>g)NJ!+;1F@V1{DbCDekcMR;VM?mS0F$)-6_y}P?bRW&9?xA&4D&@(DuKNB{Yzf2`h0J78nB! z3q&45C0v?n`$Pab13AzUIsONNev*ax|E%T^Eg{CKJZBNe6V9Fp42<+0*8=zdxz-_U zLeBuXf_%{L$3PqFJ1!IV-y9QRVNlpcH7G^F2N4Vm^xyrTPZZw27@*-W3t{|uFoP%P z#6m$Q#`u2_FtEsvf1#dXI>N|?ZP4sCNbrLT21fS|QXu~i1PwGLLjzKep%Omr;vQCj zpmoqj_709>0>Rioc}8SlH|Ux;eA@YC0(yUeL;WY{$_xT2{L``Bh3G0&Pc83Ex$!dAbsD%HmfA}wd zzIPy-(tkiCAjBdz;k!~#@4#ryzo6w9BjNvQr2O+Dr|SXh#)Jvqm7I9jN|GT6AOzBk z^MIdQ0%^@Cfnnox;IdZ0hH+BDf0-={fWACcP)NRy0xv7z^|%<}e+_j(UmpLvcA;!Q z03J}+k^&e#feQK82QzO18@$N@D3VJET$rGS{FkL8@DFI8oYUWZ|1xw0v4rou3*R+b z>HHUroFpXtuU!=gpm_(~dV(Nw;K3vg!8>!PchDx#e~K3PX^N8YzeYBoNO>Q{!G6Gi zDRIJgRw3^iaS!-=BspIK;KylH!godi@4&f`zhHa-CD3{fmGE7D_d75s{4dB9ObGk~ zYVloC+dFVK?k}hmO#mDRwfHV$=pDF|_-`;38k9stMfl%54A6bc{SG`#{x2w-i2C4~Ga8&iMDy->CrK!CQHM zVWe!dzgOv<9_$^+RtN$`{zCtd%lseArUV3$0Ym4p3E$0l{2v4iY^Vi@xIhM613~{B z9Q}`*?*TzVkpFsQ02LOn!5{j8uss5QWBUD5;(vbv1~xtjdS;k`=L`dD`bim-WIupo kf?3pR3`-0eoQAA}X}h$iH^~KfJ>jpa1{> delta 48759 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 b!Hp6G26zECdH+%NdO annotations, RoundEnvironment private void processImpl(RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { - generateUDFPropertiesFile(); + generateUDFMetadataFile(); } else { processElements(roundEnv.getRootElements()); } @@ -178,18 +178,18 @@ private boolean typeElementOverridesTopLevelStdUDFMethods(TypeElement typeElemen } /** - * Generates the UDF properties resource file in a pretty-printed JSON format + * Generates the UDF metadata resource file in a pretty-printed JSON format */ - private void generateUDFPropertiesFile() { + private void generateUDFMetadataFile() { Filer filer = processingEnv.getFiler(); try { FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", Constants.UDF_RESOURCE_FILE_PATH); try (Writer writer = fileObject.openWriter()) { _transportUdfMetadata.toJson(writer); } - debug("Wrote Transport UDF properties file to: " + fileObject.toUri()); + debug("Wrote Transport UDF metadata file to: " + fileObject.toUri()); } catch (IOException e) { - fatalError(String.format("Unable to create UDF properties resource file: %s", e)); + fatalError(String.format("Unable to create UDF metadata resource file: %s", e)); } } diff --git a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/AbstractTestWrapperGenerator.java b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/AbstractTestWrapperGenerator.java index de3387ec..0a411441 100644 --- a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/AbstractTestWrapperGenerator.java +++ b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/AbstractTestWrapperGenerator.java @@ -44,14 +44,14 @@ void teardown() { _resourcesOutputDir.delete(); } - void testWrapperGenerator(String udfPropertiesFileResource, String expectedSourcesOutputFolderResource) { - testWrapperGenerator(udfPropertiesFileResource, expectedSourcesOutputFolderResource, null); + void testWrapperGenerator(String udfMetadataFileResource, String expectedSourcesOutputFolderResource) { + testWrapperGenerator(udfMetadataFileResource, expectedSourcesOutputFolderResource, null); } - void testWrapperGenerator(String udfPropertiesFileResource, String expectedSourcesOutputFolderResource, + void testWrapperGenerator(String udfMetadataFileResource, String expectedSourcesOutputFolderResource, String expectedResourcesOutputFolderResource) { WrapperGeneratorContext context = - new WrapperGeneratorContext(TestUtils.getUDFPropertiesFromResource(udfPropertiesFileResource), + new WrapperGeneratorContext(TestUtils.getUDFMetadataFromResource(udfMetadataFileResource), _sourcesOutputDir, _resourcesOutputDir); getWrapperGenerator().generateWrappers(context); diff --git a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestHiveWrapperGenerator.java b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestHiveWrapperGenerator.java index 6d659af3..2416ae7d 100644 --- a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestHiveWrapperGenerator.java +++ b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestHiveWrapperGenerator.java @@ -17,6 +17,6 @@ WrapperGenerator getWrapperGenerator() { @Test public void testHiveWrapperGenerator() { - testWrapperGenerator("inputs/sample-udf-properties.json", "outputs/sample-udf-properties/hive/sources"); + testWrapperGenerator("inputs/sample-udf-metadata.json", "outputs/sample-udf-metadata/hive/sources"); } } diff --git a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestPrestoWrapperGenerator.java b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestPrestoWrapperGenerator.java index 5316b398..3c2fafbf 100644 --- a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestPrestoWrapperGenerator.java +++ b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestPrestoWrapperGenerator.java @@ -17,7 +17,7 @@ WrapperGenerator getWrapperGenerator() { @Test public void testPrestoWrapperGenerator() { - testWrapperGenerator("inputs/sample-udf-properties.json", "outputs/sample-udf-properties/presto/sources", - "outputs/sample-udf-properties/presto/resources"); + testWrapperGenerator("inputs/sample-udf-metadata.json", "outputs/sample-udf-metadata/presto/sources", + "outputs/sample-udf-metadata/presto/resources"); } } diff --git a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestSparkWrapperGenerator.java b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestSparkWrapperGenerator.java index 8627a3cf..02bc0d6a 100644 --- a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestSparkWrapperGenerator.java +++ b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestSparkWrapperGenerator.java @@ -17,6 +17,6 @@ WrapperGenerator getWrapperGenerator() { @Test public void testSparkWrapperGenerator() { - testWrapperGenerator("inputs/sample-udf-properties.json", "outputs/sample-udf-properties/spark/sources"); + testWrapperGenerator("inputs/sample-udf-metadata.json", "outputs/sample-udf-metadata/spark/sources"); } } diff --git a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestUtils.java b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestUtils.java index 1a41ae03..0879b4c3 100644 --- a/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestUtils.java +++ b/transportable-udfs-codegen/src/test/java/com/linkedin/transport/codegen/TestUtils.java @@ -21,12 +21,12 @@ class TestUtils { private TestUtils() { } - static TransportUDFMetadata getUDFPropertiesFromResource(String resource) { + static TransportUDFMetadata getUDFMetadataFromResource(String resource) { try (InputStreamReader reader = new InputStreamReader( Thread.currentThread().getContextClassLoader().getResourceAsStream(resource))) { return TransportUDFMetadata.fromJson(reader); } catch (IOException e) { - throw new RuntimeException("Could not read UDF properties from resource: " + resource, e); + throw new RuntimeException("Could not read UDF metadata from resource: " + resource, e); } } diff --git a/transportable-udfs-codegen/src/test/resources/inputs/sample-udf-properties.json b/transportable-udfs-codegen/src/test/resources/inputs/sample-udf-metadata.json similarity index 100% rename from transportable-udfs-codegen/src/test/resources/inputs/sample-udf-properties.json rename to transportable-udfs-codegen/src/test/resources/inputs/sample-udf-metadata.json diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/hive/sources/udfs/hive/OverloadedUDF.java b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/hive/sources/udfs/hive/OverloadedUDF.java similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/hive/sources/udfs/hive/OverloadedUDF.java rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/hive/sources/udfs/hive/OverloadedUDF.java diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/hive/sources/udfs/hive/SimpleUDF.java b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/hive/sources/udfs/hive/SimpleUDF.java similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/hive/sources/udfs/hive/SimpleUDF.java rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/hive/sources/udfs/hive/SimpleUDF.java diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/resources/META-INF/services/com.facebook.presto.metadata.SqlScalarFunction b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/resources/META-INF/services/com.facebook.presto.metadata.SqlScalarFunction similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/resources/META-INF/services/com.facebook.presto.metadata.SqlScalarFunction rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/resources/META-INF/services/com.facebook.presto.metadata.SqlScalarFunction diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/OverloadedUDFInt.java b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/OverloadedUDFInt.java similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/OverloadedUDFInt.java rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/OverloadedUDFInt.java diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/OverloadedUDFString.java b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/OverloadedUDFString.java similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/OverloadedUDFString.java rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/OverloadedUDFString.java diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/SimpleUDF.java b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/SimpleUDF.java similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/presto/sources/udfs/presto/SimpleUDF.java rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/presto/sources/udfs/presto/SimpleUDF.java diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/spark/sources/udfs/spark/OverloadedUDF.scala b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/spark/sources/udfs/spark/OverloadedUDF.scala similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/spark/sources/udfs/spark/OverloadedUDF.scala rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/spark/sources/udfs/spark/OverloadedUDF.scala diff --git a/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/spark/sources/udfs/spark/SimpleUDF.scala b/transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/spark/sources/udfs/spark/SimpleUDF.scala similarity index 100% rename from transportable-udfs-codegen/src/test/resources/outputs/sample-udf-properties/spark/sources/udfs/spark/SimpleUDF.scala rename to transportable-udfs-codegen/src/test/resources/outputs/sample-udf-metadata/spark/sources/udfs/spark/SimpleUDF.scala diff --git a/transportable-udfs-examples/gradlew b/transportable-udfs-examples/gradlew index cccdd3d5..af6708ff 100755 --- a/transportable-udfs-examples/gradlew +++ b/transportable-udfs-examples/gradlew @@ -28,7 +28,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/transportable-udfs-examples/gradlew.bat b/transportable-udfs-examples/gradlew.bat index e95643d6..0f8d5937 100644 --- a/transportable-udfs-examples/gradlew.bat +++ b/transportable-udfs-examples/gradlew.bat @@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle b/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle index d545b24a..0ff2825e 100644 --- a/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle +++ b/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle @@ -1,67 +1,28 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath('com.linkedin.transport:transportable-udfs-plugin') + } +} + apply plugin: 'java' +apply plugin: 'com.linkedin.transport.plugin' dependencies { - compile('com.linkedin.transport:transportable-udfs-api') // TODO: Reference all external dependencies from a single gradle file compile('com.google.guava:guava:24.1-jre') compile('org.apache.commons:commons-io:1.3.2') - testCompile('com.linkedin.transport:transportable-udfs-test-api') } -// ============================================================================ -// transportable-udfs-plugin cannot be applied to this module as the plugin is also -// built in this project. In projects using the Transportable UDF framework, the -// following code will be applied by the plugin -// ============================================================================ - -dependencies { - annotationProcessor('com.linkedin.transport:transportable-udfs-annotation-processor') -} +licenseHive.enabled = false +licensePresto.enabled = false +licenseSpark.enabled = false // TODO: Add a debugPlatform flag to allow debugging specific test methods in IntelliJ // for a particular platform other than default -configurations { - platformTestRuntime { extendsFrom testRuntime } - hiveTestRuntime { extendsFrom platformTestRuntime } - prestoTestRuntime { extendsFrom platformTestRuntime } - sparkTestRuntime { extendsFrom platformTestRuntime } -} - -dependencies { - testRuntimeOnly('com.linkedin.transport:transportable-udfs-test-generic') - platformTestRuntime sourceSets.main.output, sourceSets.test.output - hiveTestRuntime('com.linkedin.transport:transportable-udfs-test-hive') - prestoTestRuntime('com.linkedin.transport:transportable-udfs-test-presto') - sparkTestRuntime('com.linkedin.transport:transportable-udfs-test-spark') -} - -task hiveTest(type: Test, dependsOn: test) { - group 'Verification' - description 'Runs the Hive tests.' - testClassesDirs = sourceSets.test.output.classesDirs - classpath = configurations.hiveTestRuntime - useTestNG() -} - -task prestoTest(type: Test, dependsOn: test) { - group 'Verification' - description 'Runs the Presto tests.' - testClassesDirs = sourceSets.test.output.classesDirs - classpath = configurations.prestoTestRuntime - useTestNG() -} - -task sparkTest(type: Test, dependsOn: test) { - group 'Verification' - description 'Runs the Spark tests.' - testClassesDirs = sourceSets.test.output.classesDirs - classpath = configurations.sparkTestRuntime - useTestNG() -} - -check.dependsOn(hiveTest, prestoTest, sparkTest) - prestoTest { exclude '**/TestArrayFillFunctionFailsOnPresto.class' exclude '**/TestStructCreateByIndexFunctionFailsOnPresto.class' diff --git a/transportable-udfs-plugin/build.gradle b/transportable-udfs-plugin/build.gradle new file mode 100644 index 00000000..30f61b85 --- /dev/null +++ b/transportable-udfs-plugin/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'java' + id 'java-gradle-plugin' +} + +gradlePlugin { + plugins { + simplePlugin { + id = 'com.linkedin.transport.plugin' + implementationClass = 'com.linkedin.transport.plugin.TransportPlugin' + } + } +} + +dependencies { + compile project(':transportable-udfs-api') + compile project(':transportable-udfs-codegen') + compile ('com.google.guava:guava:24.1-jre') + compile ('com.google.code.gson:gson:2.8.5') + testCompile('org.spockframework:spock-core:1.1-groovy-2.4') { + exclude group: 'org.codehaus.groovy' + } +} + +def writeVersionInfo = { file -> + ant.propertyfile(file: file) { + entry(key: "transport-version", value: version) + entry(key: "hive-version", value: '1.2.2') + entry(key: "presto-version", value: '0.203') + entry(key: "spark-version", value: '2.3.0') + } +} + +processResources.doLast { + writeVersionInfo(new File(sourceSets.main.output.resourcesDir, "version-info.properties")) +} \ No newline at end of file diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java new file mode 100644 index 00000000..4272e9ec --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java @@ -0,0 +1,100 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +import com.google.common.collect.ImmutableList; +import com.linkedin.transport.codegen.HiveWrapperGenerator; +import com.linkedin.transport.codegen.PrestoWrapperGenerator; +import com.linkedin.transport.codegen.SparkWrapperGenerator; +import java.io.IOException; +import java.util.List; +import java.util.Properties; + +import static com.linkedin.transport.plugin.DependencyConfigurationName.*; + + +/** + * Stores default configurations for the Transport UDF plugin + */ +class Defaults { + + private Defaults() { + } + + // The versions of the Transport and supported platforms to apply corresponding versions of the platform dependencies + private static final Properties DEFAULT_VERSIONS; + + static { + DEFAULT_VERSIONS = new Properties(); + try { + DEFAULT_VERSIONS.load( + Thread.currentThread().getContextClassLoader().getResourceAsStream("version-info.properties")); + } catch (IOException e) { + throw new RuntimeException("Error loading version-info.properties", e); + } + } + + static final List MAIN_SOURCE_SET_DEPENDENCIES = ImmutableList.of( + getDependencyConfiguration(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-api", "transport"), + getDependencyConfiguration(ANNOTATION_PROCESSOR, "com.linkedin.transport:transportable-udfs-annotation-processor", + "transport") + ); + + static final List TEST_SOURCE_SET_DEPENDENCIES = ImmutableList.of( + getDependencyConfiguration(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-test-api", "transport"), + getDependencyConfiguration(RUNTIME_ONLY, "com.linkedin.transport:transportable-udfs-test-generic", "transport") + ); + + static final List DEFAULT_PLATFORMS = ImmutableList.of( + new PlatformConfiguration( + "presto", + Language.JAVA, + PrestoWrapperGenerator.class, + ImmutableList.of( + getDependencyConfiguration(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-presto", + "transport"), + getDependencyConfiguration(COMPILE_ONLY, "com.facebook.presto:presto-main", "presto") + ), + ImmutableList.of( + getDependencyConfiguration(RUNTIME_ONLY, "com.linkedin.transport:transportable-udfs-test-presto", + "transport") + ) + ), + new PlatformConfiguration( + "hive", + Language.JAVA, + HiveWrapperGenerator.class, + ImmutableList.of( + getDependencyConfiguration(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-hive", "transport"), + getDependencyConfiguration(COMPILE_ONLY, "org.apache.hive:hive-exec", "hive") + ), + ImmutableList.of( + getDependencyConfiguration(RUNTIME_ONLY, "com.linkedin.transport:transportable-udfs-test-hive", + "transport") + ) + ), + new PlatformConfiguration( + "spark", + Language.SCALA, + SparkWrapperGenerator.class, + ImmutableList.of( + getDependencyConfiguration(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-spark", + "transport"), + getDependencyConfiguration(COMPILE_ONLY, "org.apache.spark:spark-sql_2.11", "spark") + ), + ImmutableList.of( + getDependencyConfiguration(RUNTIME_ONLY, "com.linkedin.transport:transportable-udfs-test-spark", + "transport") + ) + ) + ); + + private static DependencyConfiguration getDependencyConfiguration(DependencyConfigurationName configurationName, + String moduleCoordinate, String platform) { + return new DependencyConfiguration(configurationName, + moduleCoordinate + ":" + DEFAULT_VERSIONS.getProperty(platform + "-version")); + } +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfiguration.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfiguration.java new file mode 100644 index 00000000..d5f9dd97 --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfiguration.java @@ -0,0 +1,28 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +/** + * Represents a dependency to be applied to a certain dependency configuration (e.g. implementation, compileOnly, etc.) + * In the future can expand to incorporate exclude rules, dependency substitutions, etc. + */ +public class DependencyConfiguration { + private DependencyConfigurationName _configurationName; + private String _dependencyString; + + public DependencyConfiguration(DependencyConfigurationName configurationName, String dependencyString) { + _configurationName = configurationName; + _dependencyString = dependencyString; + } + + public DependencyConfigurationName getConfigurationName() { + return _configurationName; + } + + public String getDependencyString() { + return _dependencyString; + } +} \ No newline at end of file diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfigurationName.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfigurationName.java new file mode 100644 index 00000000..5708aa3a --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/DependencyConfigurationName.java @@ -0,0 +1,13 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +public enum DependencyConfigurationName { + ANNOTATION_PROCESSOR, + COMPILE_ONLY, + IMPLEMENTATION, + RUNTIME_ONLY +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Language.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Language.java new file mode 100644 index 00000000..c232521f --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Language.java @@ -0,0 +1,22 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +public enum Language { + JAVA("Java"), + SCALA("Scala"); + + private String _language; + + Language(String language) { + this._language = language; + } + + @Override + public String toString() { + return _language; + } +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/PlatformConfiguration.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/PlatformConfiguration.java new file mode 100644 index 00000000..930f50e4 --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/PlatformConfiguration.java @@ -0,0 +1,51 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +import com.linkedin.transport.codegen.WrapperGenerator; +import java.util.List; + + +/** + * Represents the information required to configure a given platform inside the {@link TransportPlugin} + */ +public class PlatformConfiguration { + + private final String _name; + private final Language _language; + private final Class _wrapperGeneratorClass; + private final List _defaultWrapperDependencies; + private final List _defaultTestDependencies; + + public PlatformConfiguration(String name, Language language, Class wrapperGeneratorClass, + List defaultWrapperDependencies, List defaultTestDependencies) { + _name = name; + _language = language; + _wrapperGeneratorClass = wrapperGeneratorClass; + _defaultWrapperDependencies = defaultWrapperDependencies; + _defaultTestDependencies = defaultTestDependencies; + } + + public String getName() { + return _name; + } + + public Language getLanguage() { + return _language; + } + + public Class getWrapperGeneratorClass() { + return _wrapperGeneratorClass; + } + + public List getDefaultWrapperDependencies() { + return _defaultWrapperDependencies; + } + + public List getDefaultTestDependencies() { + return _defaultTestDependencies; + } +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/SourceSetUtils.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/SourceSetUtils.java new file mode 100644 index 00000000..9a0d66b3 --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/SourceSetUtils.java @@ -0,0 +1,101 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +import java.util.Collection; +import org.codehaus.groovy.runtime.InvokerHelper; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.SourceDirectorySet; +import org.gradle.api.plugins.Convention; +import org.gradle.api.tasks.ScalaSourceSet; +import org.gradle.api.tasks.SourceSet; + + +/** + * Utility class to help manipulate a {@link SourceSet} + */ +class SourceSetUtils { + + private SourceSetUtils() { + } + + /** + * Returns the {@link SourceDirectorySet} for a given {@link SourceSet} depending on the language of the sources + */ + static SourceDirectorySet getSourceDirectorySet(SourceSet sourceSet, Language language) { + switch (language) { + case JAVA: + return sourceSet.getJava(); + case SCALA: + Convention sourceSetConvention = (Convention) InvokerHelper.getProperty(sourceSet, "convention"); + ScalaSourceSet scalaSourceSet = sourceSetConvention.getPlugin(ScalaSourceSet.class); + return scalaSourceSet.getScala(); + default: + throw new UnsupportedOperationException("Language " + language + " not supported"); + } + } + + private static Configuration getConfigurationForSourceSet(Project project, SourceSet sourceSet, + DependencyConfigurationName configurationName) { + return project.getConfigurations().getByName(getConfigurationNameForSourceSet(sourceSet, configurationName)); + } + + private static String getConfigurationNameForSourceSet(SourceSet sourceSet, + DependencyConfigurationName configurationName) { + final String configName; + switch (configurationName) { + case ANNOTATION_PROCESSOR: + configName = sourceSet.getAnnotationProcessorConfigurationName(); + break; + case IMPLEMENTATION: + configName = sourceSet.getImplementationConfigurationName(); + break; + case COMPILE_ONLY: + configName = sourceSet.getCompileOnlyConfigurationName(); + break; + case RUNTIME_ONLY: + configName = sourceSet.getRuntimeOnlyConfigurationName(); + break; + default: + throw new UnsupportedOperationException("Configuration " + configurationName + " not supported"); + } + return configName; + } + + /** + * Adds the provided dependency to the given {@link Configuration} + */ + static void addDependencyToConfiguration(Project project, Configuration configuration, Object dependency) { + configuration.withDependencies(dependencySet -> dependencySet.add(project.getDependencies().create(dependency))); + } + + /** + * Adds the provided dependencies to the given {@link Configuration} + */ + static void addDependenciesToConfiguration(Project project, Configuration configuration, + Collection dependencies) { + dependencies.forEach( + dependency -> addDependencyToConfiguration(project, configuration, dependency.getDependencyString())); + } + + /** + * Adds the provided dependency to the appropriate configurations of the given {@link SourceSet} + */ + static void addDependencyToSourceSet(Project project, SourceSet sourceSet, DependencyConfiguration dependency) { + addDependencyToConfiguration(project, + SourceSetUtils.getConfigurationForSourceSet(project, sourceSet, dependency.getConfigurationName()), + dependency.getDependencyString()); + } + + /** + * Adds the provided dependencies to the appropriate configurations of the given {@link SourceSet} + */ + static void addDependenciesToSourceSet(Project project, SourceSet sourceSet, + Collection dependencies) { + dependencies.forEach(dependency -> addDependencyToSourceSet(project, sourceSet, dependency)); + } +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/TransportPlugin.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/TransportPlugin.java new file mode 100644 index 00000000..a8c4a647 --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/TransportPlugin.java @@ -0,0 +1,225 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin; + +import com.google.common.collect.ImmutableList; +import com.linkedin.transport.plugin.tasks.GenerateWrappersTask; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.plugins.scala.ScalaPlugin; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.testing.Test; +import org.gradle.language.base.plugins.LifecycleBasePlugin; + + +/** + * A {@link Plugin} to be applied to a Transport UDF module which: + *
    + *
  1. Configures default dependencies for the main and test source sets
  2. + *
  3. Applies the Transport UDF annotation processor
  4. + *
  5. Creates a SourceSet for UDF wrapper generation
  6. + *
  7. Configures default dependencies for the platform wrappers
  8. + *
  9. Configures wrapper code generation tasks
  10. + *
  11. TODO: Configures tasks to package wrappers with appropriate shading rules
  12. + *
  13. Configures tasks to run UDF tests for the platform using the Unified Testing Framework
  14. + *
+ */ +public class TransportPlugin implements Plugin { + + public void apply(Project project) { + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { + project.getPlugins().apply(ScalaPlugin.class); + + JavaPluginConvention javaConvention = project.getConvention().getPlugin(JavaPluginConvention.class); + SourceSet mainSourceSet = javaConvention.getSourceSets().getByName("main"); + SourceSet testSourceSet = javaConvention.getSourceSets().getByName("test"); + + configureBaseSourceSets(project, mainSourceSet, testSourceSet); + Defaults.DEFAULT_PLATFORMS.forEach(config -> configurePlatform(project, config, mainSourceSet, testSourceSet)); + }); + } + + /** + * Configures default dependencies for the main and test source sets + */ + private void configureBaseSourceSets(Project project, SourceSet mainSourceSet, SourceSet testSourceSet) { + SourceSetUtils.addDependenciesToSourceSet(project, mainSourceSet, Defaults.MAIN_SOURCE_SET_DEPENDENCIES); + SourceSetUtils.addDependenciesToSourceSet(project, testSourceSet, Defaults.TEST_SOURCE_SET_DEPENDENCIES); + } + + /** + * Configures SourceSets, dependencies and tasks related to each Transport UDF platform + */ + private void configurePlatform(Project project, PlatformConfiguration platformConfiguration, + SourceSet mainSourceSet, SourceSet testSourceSet) { + SourceSet sourceSet = configureSourceSet(project, platformConfiguration, mainSourceSet); + TaskProvider generateWrappersTask = + configureGenerateWrappersTask(project, platformConfiguration, mainSourceSet, sourceSet); + // TODO: shade and package into Jar + // Add Transport tasks to build task dependencies + project.getTasks().named(LifecycleBasePlugin.BUILD_TASK_NAME).configure(task -> { + // TODO: Replace this task with the shaded jar tasks once we create them + task.dependsOn(sourceSet.getClassesTaskName()); + }); + + TaskProvider testTask = configureTestTask(project, platformConfiguration, mainSourceSet, testSourceSet); + project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(task -> task.dependsOn(testTask)); + } + + /** + * Creates and configures a {@link SourceSet} for a given platform, sets up the source directories and + * configurations and configures the default dependencies required for compilation and runtime of the wrapper + * SourceSet + */ + private SourceSet configureSourceSet(Project project, PlatformConfiguration platformConfiguration, + SourceSet mainSourceSet) { + JavaPluginConvention javaConvention = project.getConvention().getPlugin(JavaPluginConvention.class); + Path platformBaseDir = + project.getBuildDir().toPath().resolve(Paths.get("generatedWrappers", platformConfiguration.getName())); + Path wrapperSourceOutputDir = platformBaseDir.resolve("sources"); + Path wrapperResourceOutputDir = platformBaseDir.resolve("resources"); + + return javaConvention.getSourceSets().create(platformConfiguration.getName(), sourceSet -> { + /* + Creates a SourceSet and set the source directories. E.g. + + presto { + java.srcDirs = ["${buildDir}/generatedWrappers/sources"] + resources.srcDirs = ["${buildDir}/generatedWrappers/resources"] + compileClasspath += sourceSets.main.output + } + */ + SourceSetUtils.getSourceDirectorySet(sourceSet, platformConfiguration.getLanguage()).setSrcDirs( + ImmutableList.of(wrapperSourceOutputDir)); + sourceSet.getResources().setSrcDirs(ImmutableList.of(wrapperResourceOutputDir)); + sourceSet.setCompileClasspath(project.files(sourceSet.getCompileClasspath(), mainSourceSet.getOutput())); + + /* + Sets up the configuration for the wrapper SourceSet. E.g. + + configurations { + prestoImplementation.extendsFrom mainImplementation + prestoRuntimeOnly.extendsFrom mainRuntimeOnly + } + */ + project.getConfigurations().getByName(sourceSet.getImplementationConfigurationName()) + .extendsFrom(project.getConfigurations().getByName(mainSourceSet.getImplementationConfigurationName())); + project.getConfigurations().getByName(sourceSet.getRuntimeOnlyConfigurationName()).extendsFrom( + project.getConfigurations().getByName(mainSourceSet.getRuntimeOnlyConfigurationName())); + + /* + Adds default dependency config for Presto + + dependencies { + prestoImplementation 'com.linkedin.transport:transportable-udfs-presto:$version' + prestoCompileOnly 'com.facebook.presto:presto-main:$version' + } + */ + SourceSetUtils.addDependenciesToSourceSet(project, sourceSet, + platformConfiguration.getDefaultWrapperDependencies()); + }); + } + + /** + * Creates and configures a task to generate UDF wrappers for a given platform + */ + private TaskProvider configureGenerateWrappersTask(Project project, + PlatformConfiguration platformConfiguration, SourceSet inputSourceSet, SourceSet outputSourceSet) { + + /* + Example generateWrapper task for Presto + + task generatePrestoWrappers { + generatorClass = 'com.linkedin.transport.codegen.PrestoWrapperGenerator' + inputClassesDirs = sourceSets.main.output.classesDirs + sourcesOutputDir = sourceSets.presto.java.srcDirs[0] + resourcesOutputDir = sourceSets.presto.resources.srcDirs[0] + dependsOn classes + } + + prestoClasses.dependsOn(generatePrestoWrappers) + */ + String taskName = outputSourceSet.getTaskName("generate", "Wrappers"); + File sourcesOutputDir = + SourceSetUtils.getSourceDirectorySet(outputSourceSet, platformConfiguration.getLanguage()) + .getSrcDirs().iterator().next(); + File resourcesOutputDir = outputSourceSet.getResources().getSrcDirs().iterator().next(); + + TaskProvider + generateWrappersTask = project.getTasks().register(taskName, GenerateWrappersTask.class, task -> { + task.setDescription("Generates Transport UDF wrappers for " + platformConfiguration.getName()); + task.getGeneratorClass().set(platformConfiguration.getWrapperGeneratorClass().getName()); + task.getInputClassesDirs().set(inputSourceSet.getOutput().getClassesDirs()); + task.getSourcesOutputDir().set(sourcesOutputDir); + task.getResourcesOutputDir().set(resourcesOutputDir); + task.dependsOn(project.getTasks().named(inputSourceSet.getClassesTaskName())); + }); + + project.getTasks().named(outputSourceSet.getCompileTaskName(platformConfiguration.getLanguage().toString())) + .configure(task -> task.dependsOn(generateWrappersTask)); + + return generateWrappersTask; + } + + /** + * Creates and configures a task to run tests written using the Unified Testing Framework against a given platform + */ + private TaskProvider configureTestTask(Project project, PlatformConfiguration platformConfiguration, + SourceSet mainSourceSet, SourceSet testSourceSet) { + + /* + Configures the classpath configuration to run platform-specific tests. E.g. for Presto, + + configurations { + prestoTestClasspath { + extendsFrom testImplementation + } + } + + dependencies { + prestoTestClasspath sourceSets.main.output, sourceSets.test.output + prestoTestClasspath 'com.linkedin.transport:transportable-udfs-test-presto' + } + */ + Configuration testClasspath = + project.getConfigurations().create(platformConfiguration.getName() + "TestClasspath", config -> + config.extendsFrom( + project.getConfigurations().getByName(testSourceSet.getImplementationConfigurationName())) + ); + SourceSetUtils.addDependencyToConfiguration(project, testClasspath, mainSourceSet.getOutput()); + SourceSetUtils.addDependencyToConfiguration(project, testClasspath, testSourceSet.getOutput()); + SourceSetUtils.addDependenciesToConfiguration(project, testClasspath, + platformConfiguration.getDefaultTestDependencies()); + + /* + Creates the test task for the platform. E.g. For Presto, + + task prestoTest(type: Test, dependsOn: test) { + group 'Verification' + description 'Runs the Presto tests.' + testClassesDirs = sourceSets.test.output.classesDirs + classpath = configurations.prestoTestClasspath + useTestNG() + } + */ + + return project.getTasks().register(platformConfiguration.getName() + "Test", Test.class, task -> { + task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); + task.setDescription("Runs Transport UDF tests on " + platformConfiguration.getName()); + task.setTestClassesDirs(testSourceSet.getOutput().getClassesDirs()); + task.setClasspath(testClasspath); + task.useTestNG(); + task.mustRunAfter(project.getTasks().named("test")); + }); + } +} diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/tasks/GenerateWrappersTask.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/tasks/GenerateWrappersTask.java new file mode 100644 index 00000000..3e93e9b1 --- /dev/null +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/tasks/GenerateWrappersTask.java @@ -0,0 +1,95 @@ +/** + * Copyright 2019 LinkedIn Corporation. All rights reserved. + * Licensed under the BSD-2 Clause license. + * See LICENSE in the project root for license information. + */ +package com.linkedin.transport.plugin.tasks; + +import com.linkedin.transport.codegen.WrapperGenerator; +import com.linkedin.transport.codegen.WrapperGeneratorContext; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; +import java.util.stream.StreamSupport; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; + + +/** + * A Gradle task which generates Transport UDF wrappers given a UDF metadata file and wrapper generator class + */ +public class GenerateWrappersTask extends DefaultTask { + + private final Property _generatorClass; + + private final Property _inputClassesDirs; + + private final DirectoryProperty _sourcesOutputDir; + + private final DirectoryProperty _resourcesOutputDir; + + public GenerateWrappersTask() { + _generatorClass = getProject().getObjects().property(String.class); + _inputClassesDirs = getProject().getObjects().property(FileCollection.class); + _sourcesOutputDir = getProject().getObjects().directoryProperty(); + _resourcesOutputDir = getProject().getObjects().directoryProperty(); + } + + @Input + public Property getGeneratorClass() { + return _generatorClass; + } + + @InputFiles + public Property getInputClassesDirs() { + return _inputClassesDirs; + } + + @OutputDirectory + public DirectoryProperty getSourcesOutputDir() { + return _sourcesOutputDir; + } + + @OutputDirectory + public DirectoryProperty getResourcesOutputDir() { + return _resourcesOutputDir; + } + + @TaskAction + public void generateWrappers() { + // TODO: Use Gradle worker API to generate wrappers concurrently + WrapperGenerator generator; + try { + generator = (WrapperGenerator) Class.forName(_generatorClass.get()).getConstructor().newInstance(); + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Could not create object of class: " + _generatorClass.get(), e); + } + generator.generateWrappers( + new WrapperGeneratorContext(getUDFMetadataFile(_inputClassesDirs.get()), _sourcesOutputDir.getAsFile().get(), + _resourcesOutputDir.getAsFile().get())); + } + + /** + * Finds the location for the UDF metadata file generated by the annotation processor in the classes directories of + * the input sources + */ + private File getUDFMetadataFile(FileCollection inputClassesDirs) { + Optional udfMetadataFile = StreamSupport.stream(inputClassesDirs.spliterator(), true) + .map(folder -> folder.toPath().resolve("META-INF/transport-udfs/metadata.json").toFile()) + .filter(File::exists) + .findFirst(); + + if (udfMetadataFile.isPresent()) { + return udfMetadataFile.get(); + } else { + throw new RuntimeException( + "Could not find UDF metadata file in the input directories: " + inputClassesDirs.getFiles().toString()); + } + } +}