From 1d122dcf12781462a5bd8bf72a305787d45762fc Mon Sep 17 00:00:00 2001 From: ma91n Date: Tue, 4 Jun 2024 13:28:16 +0900 Subject: [PATCH] protoc plugin --- ...20\343\202\222\344\275\234\343\202\213.md" | 1 + ...56\344\275\234\343\202\212\346\226\271.md" | 172 ++++++++++++++++++ source/images/20240604a/overview.jpg | Bin 0 -> 14953 bytes source/images/20240604a/thumbnail.jpg | Bin 0 -> 10294 bytes 4 files changed, 173 insertions(+) create mode 100644 "source/_posts/20240604a_proto\343\203\225\343\202\241\343\202\244\343\203\253\343\201\213\343\202\211\343\202\263\343\203\274\343\203\211\343\202\222\350\207\252\345\213\225\347\224\237\346\210\220\343\201\231\343\202\213protoc\343\201\256\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\343\201\256\344\275\234\343\202\212\346\226\271.md" create mode 100644 source/images/20240604a/overview.jpg create mode 100644 source/images/20240604a/thumbnail.jpg diff --git "a/source/_posts/2022/20220624a_grpc-gateway\343\201\247gRPC\343\201\250REST\344\270\241\345\257\276\345\277\234\343\201\256\343\202\265\343\203\274\343\203\220\343\202\222\344\275\234\343\202\213.md" "b/source/_posts/2022/20220624a_grpc-gateway\343\201\247gRPC\343\201\250REST\344\270\241\345\257\276\345\277\234\343\201\256\343\202\265\343\203\274\343\203\220\343\202\222\344\275\234\343\202\213.md" index de26019a9d8f..0568ff7c1e49 100644 --- "a/source/_posts/2022/20220624a_grpc-gateway\343\201\247gRPC\343\201\250REST\344\270\241\345\257\276\345\277\234\343\201\256\343\202\265\343\203\274\343\203\220\343\202\222\344\275\234\343\202\213.md" +++ "b/source/_posts/2022/20220624a_grpc-gateway\343\201\247gRPC\343\201\250REST\344\270\241\345\257\276\345\277\234\343\201\256\343\202\265\343\203\274\343\203\220\343\202\222\344\275\234\343\202\213.md" @@ -6,6 +6,7 @@ tag: - gRPC - grpc-gateway - IDL + - ProtocolBuffers category: - Programming thumbnail: /images/20220624a/thumbnail.png diff --git "a/source/_posts/20240604a_proto\343\203\225\343\202\241\343\202\244\343\203\253\343\201\213\343\202\211\343\202\263\343\203\274\343\203\211\343\202\222\350\207\252\345\213\225\347\224\237\346\210\220\343\201\231\343\202\213protoc\343\201\256\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\343\201\256\344\275\234\343\202\212\346\226\271.md" "b/source/_posts/20240604a_proto\343\203\225\343\202\241\343\202\244\343\203\253\343\201\213\343\202\211\343\202\263\343\203\274\343\203\211\343\202\222\350\207\252\345\213\225\347\224\237\346\210\220\343\201\231\343\202\213protoc\343\201\256\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\343\201\256\344\275\234\343\202\212\346\226\271.md" new file mode 100644 index 000000000000..5ba3c4aaf731 --- /dev/null +++ "b/source/_posts/20240604a_proto\343\203\225\343\202\241\343\202\244\343\203\253\343\201\213\343\202\211\343\202\263\343\203\274\343\203\211\343\202\222\350\207\252\345\213\225\347\224\237\346\210\220\343\201\231\343\202\213protoc\343\201\256\343\203\227\343\203\251\343\202\260\343\202\244\343\203\263\343\201\256\344\275\234\343\202\212\346\226\271.md" @@ -0,0 +1,172 @@ +--- +title: "protoファイルからコードを自動生成するprotocのプラグインの作り方" +date: 2024/06/04 00:00:00 +postid: a +tag: + - gRPC + - ProtocolBuffers +category: + - Programming +thumbnail: /images/20240604a/thumbnail.jpg +author: 関靖秀 +lede: "protocのプラグインについて深掘りしました。Protocol Buffers概観" +--- +# はじめに + +関です。以前、[grpc-gatewayでgRPCとREST両対応のサーバを作る](/articles/20220624a/)を書きました。その記事でほんの少し触れていたprotocのプラグインについて深掘りします。 + +内容は以下です。 + +- Protocol Buffers概観 +- proto compiler(protoc)とそのプラグインがコードを生成する仕組み +- protocのプラグインをgolangで実装する方法 + +# Protocol Buffers概観 + +Protocol Buffersは、構造化データをシリアライズするための拡張可能な機構です。言語やプラットフォームに対して中立であるため、拡張機能が対応する範囲で様々なプログラミング言語やプラットフォームに対応できます。また、自分で拡張機能を用意することで対応範囲を広げることもできます。 + +イメージとしては、JSONに近く、Protocol Buffersで定義した構造化データはJSONと相互に変換可能なのですが、シリアライズ結果はJSONに比べてコンパクトな代わりに、データの解釈には事前にシリアライズやデシリアライズの方法を知っておく必要があるのが大きな違いです。 + +用途として最も有名なものはgRPCにおけるIDL(Interface Definition Language)でしょう。これ以外にも、レコードライクで型付きの構造化データをシリアライズし、言語やプラットフォームに依存しない形で利用可能にしたいというようなユースケースにはフィットします。 + +Protocol Buffersを利用する際の基本的なワークフローは以下です。 + +- データ構造を定義する.protoファイルを作成する +- 作成した.protoファイルを入力にして、proto compiler(protoc)を起動、コードを生成する +- 生成されたコードを使ってプロジェクトのコードを実装 + +先ほど触れた、シリアライズやデシリアライズの方法が、proto compilerが生成するコードの中に含まれているイメージです。 + +# proto compiler(protoc)とそのプラグインがコードを生成する仕組み + +## 全体の流れ + +overview.jpg + +まず、簡単な例を使って、全体の流れを説明します。`protoc-gen-myplugin`というプラグインを使ってコードを生成したいと仮定します。また、コンパイルの対象ファイルは`example1.proto`, `example2.proto`の2つであるとして、コンパイル結果の出力先ディレクトリは`out`ディレクトリであるとします。この時、protocの呼び出しは以下のように行います。 + +```sh +protoc --myplugin_out=out example1.proto example2.proto +``` + +上記のコマンドが実行されると、protocはコンパイル対象が`example1.proto`, `example2.proto`の2つであること、オプションの`myplugin_out`から利用プラグインが`protoc-gen-myplugin`であることと、そのプラグインの出力先ディレクトリが`out`であることを把握します。(※プラグインは、`protoc-gen-${NAME}`という名前でPATH上に配置されている必要があり、protocがプラグインを使う時には`-${NAME}_out`というオプションで出力先ディレクトリを指定する必要があります。)その後、コンパイル対象ファイルとその依存先ファイルを解析し、その結果を[CodeGeneratorRequest](https://github.com/protocolbuffers/protobuf/blob/v27.0/src/google/protobuf/compiler/plugin.proto#L43)に詰めます。次に、利用プラグインを呼び出した上で、その標準入力に解析結果であるCodeGeneratorRequestをシリアライズしたバイト列を書き込みます。プラグインは書き込まれたCodeGeneratorRequestをデシリアライズの上、自身の処理を実行し、実行結果を[CodeGeratorResponse](https://github.com/protocolbuffers/protobuf/blob/v27.0/src/google/protobuf/compiler/plugin.proto#L83)に詰め、シリアライズしたバイト列を標準出力に書き込みます。protocはプラグインの実行結果であるCodeGeratorResponseを受け取ったら、そこに書かれている指示を元にファイルを生成します。 + +次に、プラグインに対して何らかのパラメータを渡すケースを見てみます。下記はいずれも同じ意味になります。 + +```sh +# ${NAME}_opt オプションを使って渡す。 +protoc --myplugin_out=gen --myplugin_opt=param1=foo1,param2=foo2 proto/example1.proto + +# ${NAME}_out オプションに含めて渡す。 +protoc --myplugin_out=param1=foo1,param2=foo2:gen proto/example1.proto +``` + +ここで渡すパラメータは、コマンドライン引数ではなく、`CodeGeneratorRequest`の`pamrameter`フィールドに、`param1=foo1,param2=foo2`という文字列が設定されて渡されます。このため、実際に利用する際にはこの文字列を解析する必要があります。 + +## protocのプラグインに対する要件 + +以上を踏まえると、以下のような要件を満たすように実装すればprotocのプラグインとして動作させられることがわかります。 + +- 標準入力からバイト列を読み取り、それを[CodeGeneratorRequest](https://github.com/protocolbuffers/protobuf/blob/v27.0/src/google/protobuf/compiler/plugin.proto#L43)として解釈できること +- 解釈したCodeGeneratorを元に、自身の処理結果を[CodeGeratorResponse](https://github.com/protocolbuffers/protobuf/blob/v27.0/src/google/protobuf/compiler/plugin.proto#L83)に詰め、シリアライズしたバイト列を標準出力に書き込むこと +- PATH上に`protoc-gen-${NAME}`というファイル名で配置されていること + +以上から、protocのプラグインは言語に依存せず実装ができ、GoのプラグインはGoで、C++のプラグインはC++でといった実装が可能な仕組みになっています。 + +# protocのプラグインをGoで実装する方法 + +## ナイーブな実装 + +[CodeGeneratorRequest](https://github.com/protocolbuffers/protobuf/blob/v27.0/src/google/protobuf/compiler/plugin.proto#L43)や[CodeGeratorResponse](https://github.com/protocolbuffers/protobuf/blob/v27.0/src/google/protobuf/compiler/plugin.proto#L83)のコンパイル結果は、Goだと`google.golang.org/protobuf/types/pluginpb`パッケージ([公式ドキュメント](https://pkg.go.dev/google.golang.org/protobuf/types/pluginpb))に含まれています。 + +この方針での実装方法については他記事の[protocプラグインの書き方](https://qiita.com/yugui/items/87d00d77dee159e74886)が詳しいのでそちらを参照していただけたらと思います。 + +## プラグイン実装用のライブラリを使った実装 + +上記の方針に則っても良いのですが、実際にコード生成をしようとすると、依存パッケージをimportしたり、protoファイルのsnake_caseからGoファイルで利用するCamelCaseへの変換が必要だったりと、全てを自分でやるのはいささか面倒です。 + +Goの場合、プラグイン実装に便利な,`google.golang.org/protobuf/compiler/protogen`というライブラリ([公式ドキュメント](https://pkg.go.dev/google.golang.org/protobuf/compiler/protogen))が整備されています。このライブラリは、標準のプラグインである`protoc-gen-go`や`protoc-gen-go-grpc`の[実装](https://github.com/golang/protobuf/blob/master/protoc-gen-go/main.go)、`protoc-gen-grpc`の[実装](https://github.com/grpc-ecosystem/grpc-gateway/blob/main/protoc-gen-grpc-gateway/main.go)や`protoc-gen-connect-go`の[実装](https://github.com/connectrpc/connect-go/blob/main/cmd/protoc-gen-connect-go/main.go)でも利用されている実績あるものです。今回はこちらを使って典型的なプラグインの構造を説明します。 + + +### プラグイン実装のアウトライン + +```go +package main + +import ( + "flag" + "log" + + "google.golang.org/protobuf/compiler/protogen" +) + +func main() { + // CodeGeneratorRequestからパラメータを読み出すための変数. + flags := flag.NewFlagSet("", flag.ContinueOnError) + param1 := flags.String("param1", "default", "") + param2 := flags.String("param2", "default", "") + + opt := protogen.Options{ + // ParamFuncは, opt.Runした際にCodeGeneratorRequestのparameterごとに呼び出される. + // これにより, 前段で宣言していた変数に値がセットされる. + ParamFunc: flags.Set, + } + + opt.Run(func(plugin *protogen.Plugin) error { + // ここの処理が実行されるタイミングでは、paramに値がセット済み. + // 行末コメントは以下コマンド実行時の標準エラー出力. + // protoc --myplugin_out=gen --myplugin_opt=param1=foo1,param2=foo2,module=github.com/sayshu-7s/protoc-gen-myplugin/gen proto/example1.proto + log.Print(*param1) // foo1 + log.Print(*param2) // foo2 + + for _, f := range plugin.Files { + // protoファイルごとの処理 + // 引数で渡したファイル以外に, その依存先のprotoファイルもFilesには含まれる. + // トポロジカルソートされているため, あるファイルが依存している先は必ずそのファイルより先に現れる. + + if f.Generate { + // 引数で指定したファイルが生成対象とされ, f.Generate == trueとなっている. + outFile := plugin.NewGeneratedFile(f.GeneratedFilenamePrefix+".myplugin.go", f.GoImportPath) + if _, err := outFile.Write([]byte(`// Code generated by protoc-gen-myplugin. DO NOT EDIT. + package ` + f.GoPackageName + "\n")); err != nil { + return err + } + // TODO: 生成先ファイルへの書き込み. + } + } + + return nil + }) +} +``` + +CodeGeneratorRequestに含まれているプラグインに渡されるパラメータは、main関数冒頭のように, `flag.FlagSet`を使うことで変数に読み出すことができます。 + +実際にコードを生成する処理は、`opt.Run()`に渡した関数の中で指定します。ここで渡されてくる`plugin`には、生の`CodeGeneratorRequest`や、そこから読み出した情報を元にGoのコードの生成に便利な機能を追加した様々な構造体が含まれています。ファイルの生成は、`plugin.NewGeneratedFile()`ででき、生成した`outFile`に対して, Writeすると、あとはpluginがCodeGeneratorResponseに変換してよしなにやってくれる仕組みになっています。 + +TODOの箇所では、`template`パッケージなどを使って必要なコードを生成し、書き込むと良いでしょう。ライブラリの機能を使うことで、importなどが楽に行えます。 + +### 実装例 + +簡単な実装例を[protoc-gen-myplugin](https://github.com/sayshu-7s/protoc-gen-myplugin)として解説コメント付きで実装しました。このプラグインは、protoファイルに含まれるMessageから、その名前をPrintするだけの簡単な関数が書かれたコードを生成し、その際に渡されてきたCodeGeneratorRequestをJSONとして生成する機能があります。 + +`proto`ディレクトリにサンプルのprotoファイルを、`gen`ディレクトリにコンパイル結果を配置しています。中身を確認するとprotocが行なっている解析がどのようなものかのイメージが掴みやすくなると思います。 + +また、Makefileに記載してある`make install`でプラグインのインストールが、`make gen`でコードの生成ができます。コマンドのインストール先が`PATH`に含まれる必要があることに注意してください。`proto`ファイルに自前のprotoファイルを作成し、`CondeGeneratorRequest`に何が入るのか確認してみるとプラグインを自作する際に便利かもしれません。 + +# おわりに + +protocのプラグインを全体像と、Goの実践的な実装で利用されるライブラリを紹介しました。 + +ナイーブな実装について解説する記事はあったものの、より実践的なライブラリについて触れられている記事はあまりなかったのでこの記事を書きました。プラグイン実装する際の参考になれたら幸いです。 + +# 参考 + +- [Protocol Buffers公式](https://protobuf.dev) +- protocが利用するMessageの定義情報と、そのコンパイル結果を含むgolangライブラリ + - [plugin.proto](https://github.com/protocolbuffers/protobuf/blob/v27.0/src/google/protobuf/compiler/plugin.proto) + - [google.golang.org/protobuf/types/pluginpb](https://pkg.go.dev/google.golang.org/protobuf/types/pluginpb)(plugin.protoのコンパイル結果を含むgolangライブラリの公式ドキュメント) +- プラグイン実装に利用可能なより実践的なgolangライブラリ + - [google.golang.org/protobuf/compiler/protogen](https://pkg.go.dev/google.golang.org/protobuf/compiler/protogen) +- [protocプラグインの書き方](https://qiita.com/yugui/items/87d00d77dee159e74886) + diff --git a/source/images/20240604a/overview.jpg b/source/images/20240604a/overview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..63717e2f3d6a6465e0f92f152ed1b3e20e0d3e03 GIT binary patch literal 14953 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 literal 0 HcmV?d00001 diff --git a/source/images/20240604a/thumbnail.jpg b/source/images/20240604a/thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3f4b727a19e534db4d58cbe5f6b363cb81478ae6 GIT binary patch literal 10294 zcmc(F2~ZQ;wsr#|a+qXR5TeYAkfVSoQ&2=e#0ZEW2sw%}9*iiF2q9rmMgf5%96^XO z&nOT;2njPPVt_zU5J`f_3<)x{5R#Yk?tQP`U+>=f-+lF0{av-Ht9B>5_gdY(*7tpD z^FHy$fro&=|L=p>21xG#9t#{55>NvKr3Hkf1$bQo`2Zjw{I{h6e?J5Sg?0(=7TF{E zi`ZU3P(VmXaF>v<@UC6_UqV7EtL0HVgN3n7735n?$e>~01`tw;+QS9eeE+jsAW zsKX<)(NCYp7|f|})8A)i=jPd~Ki1YaHo>1;0)WteM*43K{TF|v`Thv*+9kA0?F+f~Mfd9aRqyZSf@dl1Ly0Qd6%idyP_ABwby{fE;Y zE@6&#XQYBX@OTJ#ha}9VR%6vOeoA#jTEyoQHyLrMRa{N*9JTw7Uvo*EmnXbyONE>ZBToW6HU(FOMxh+Etwss`rd^X_^%CZ&WJE`^$YW-0 z9f9qd91`U7-84eBYs?fLwiR+BHC(U1s6K1=M}I(Mpx%`NV>OVD@_@lwjwKK1e8e)~ z0eRMte>4b0NLXWj@BpzuCS^w%+9ky~i4cPQ{O0)Ihc1Z>4F8jf!KJ0Xam1)atJT4THoqRU zYsh2#+WWNjgCf*nXXg?oj#4r`T7${DB!Do*Tzw1HZ6A+yiJ07LaR~6hep9Erc6q-! z@#;cuWyyyt*G%OFUUkl9|D0k@a-PVoY!;$-}UW^ddvSesj~F>eK{SmO?C zLQQ9oO>dseD=;desJPLe-Z!~-?gUBm&Co4R2L?IDuBj<&WP-FE)=Bj~aQ(b}N-8dV2ln3>|epmxC*^x_9>n5TSx(QY1X* zr3%N;F=v}cZrIytQuWqz^&`$0k~6L*c7Ll)oL+W8-eRr9tJNWO#GV=I-8dOa%EBCE z8p2b^yE%4c*XEQ&x*>uKO1ae))qZzB&2e=GXPUU5SY7Uqxti1)f^?0|Jk=PRk?*vZ zQQv}&>*+d$4+?(z@+V1;Wz3P8p&LhHg6ZUV6L>rZX&i5+T#0sey^`YqdISbm8b_O1 ziNte`XLJ`~OsIIv=7tt(NT8>)J1gYtfl=b=8+p18zRoRgz+ATeOt-6rIuB^q=K;R3 z1XZOv*2veFY=~td^(vUgysEz@B|Z1Vt`baQh9>6pm@F4DP3n*;^)BtCt3VbT zn$4FVQfJ5dGfRR{cWweZ^3kheaZM@v3!X0dC~O`+@Ya>e`4 zoLA30{L8Mied9IHa6T)aecQ;jJ6S1f6kx&P=y1rSD!RP4J2VrkMI1uP+7n-~zGg7u znXhs#4jK04#5UIsE>)v+m)wJFE|Ez-c^*#z&4^#S9%EZ0Y)W_lf_fkuU_n0Pxpv8u zJ5P~gQar$2mYEwNfRM!O0q5vAF;Lfi0A&wNaGF>0?VcQy8CzOIMDYM+4!qc~U?U2A zsr5TyMFL|wQm=PWrB+}g*T9+B;Opx0?3tUC4-be<2ZMQl)!sh~eJ4zeZ*l6gS<)V4Jb_)-2sDk7gK##ORs^zI0e zpge$ik~@gw%$NStxqnhNGQxin4d@OE6i>E(R1g!FjN8L8s+<7fUD=}#32)ZSUAr^O z%I0X?L#*lL+-m<<^U~+^Y7h>?M`!W|iwXV@4c^yaxI+lZUvW%NHjW2K_iiLXYB)!# zb?KCN9P1J+3NK3zK#F2}Occ`CDSgbzM`q#Mr#M%`cX0IHt_Z2N(63T#sic+bkrQCe zW!BDfFR{_zng>xj*9W+2pTRRonX`KO7t>bx=;2ZAZjcw znKeG8)mSlCu#o|R-mN2M22~gZ!oZ5gQ<9p|m%DDKnGyF-PBrG~XevFfLdmVjmo;uU zZkwVHM&cN{Ae(ZMAE2zAm*G5M;sRu5w917o!g-$B?!^Pxeq=wX9b2+**p_IR)C=xq z0S1Kik+g>gysVk(3XzYE>j zJGK^DJB!`Js);8eDtn7NReB1vo-&e^wmpZ_JHGlaU2kd)*Rw#?M|;GpXVQC>DKZAO z2Tsar~1GaT~*c7V+J;m3C# z#j@Fvz^TI6S};EZ_W^-P7UTg)gV*}WUu^&E`IuXzL98-bxjWB%WU-1XhzS~8MjFSWq$=$tyZ3LgQM18FbjAX7M~7FqxmqBtiAgw25)I6OGIWuU;E!^7@pm z-qC`l*7RdW(`|{qi8O(uOLDUPb?>4d$ZT@7zDjdDo7gapriBU*co;x+@bp>fLX2-2 zZ^&0a8N-;=&S#mB$GGjgv&TI9NrjE8U-VwS%2RGX)|h!pITq|>B|;Nvd3)0*d$32r z2TUA*e!~f*llOzi%#4C7XTwG)4>_)Mb2wL%3uW<3yP0v>SU{c>yEmaeJSoWeZ1cA1 zF(P-WLnmyC};J4;2tB?|qh@6#Ih zd$arK8%5QV;EhfuE~=VnRkSdQl_w7}*(cJGOGL+#>K9{iajNPjF;#P&b|bKQLxNB1 zYm=a@2`kaMCefcebU3If(t!}4#qOi7;}iIq47E_D^rJKTJ0b8^0p`ennKBbD1L`xG zGwtsEl$b+cD9Zbmaa=KJSbzCuW3x%>W212L(#nB(3w7Gjwe1!-9OUPlT_Zbqte)T2 z$EW39oHMM&uZO@#Y#D>M7{%Sy13*}CyEqJmQPY^`G4zkcb!{BbwGY*cG& zzFk32Nb6lsAF-B=W?=jKxKmxr0~y-<*J=QBG%pCi1{4Zbv3Mab2l^h|$YT$*n3t?ui*{<=o zeW*n1LcR5m2^Hg?5!d|k4pFacT9becVydGx<}-5poIh42If!I{hV{TijNG27=l$>) zLSkH4t8;Jf+zv@i#7fqVV6=X}d9%OX>j-W)_^{wGk+t-=<)u<6i~w8Ql(E@>CFO~; zg@-@LNI=`S9+Hel^)-+!5{+>j$nKWvYuAje8{^>VzBlB(zgQkr<$O?V;!xH^IBPX0 z*)!dfaS#b?r(!b3vv9U?t5o~kKp&+P#CsiKs9?+)4r=@=DI2)>_|DY1A>`u-gpZ_I z^+NaLev6~94d2d5i3P}R&Y=@d+swGGDw%$>U=*?$bqjpJN_7f+wusa^dm0i?(znX* zEWB{#I%}r;NqWo>Nd=?If_56UzF{q=4}q!fw(tbbz3Q(M=(t`kv?*Tv8d%a3R++Id zMjG{`(T-2_7R>oKP_3zkeS5Cg#S1+~jzO${*juxxQ)^>)y!q?12Bp|aI*)&&zioD* zr6QT<_Wg{mj`qF9xULZy4%>zC0sYxxjxB>`P-dSpUV85Bj{vW|!%GCHIC}Cz)PC6| z_pB(6qjx^K5nk`DCfw38c!O~FeB}qr8i!xcUA%9TcWRt+n;tXH14ge&bxA$S69%8% z^7WsBN{t27`G*Q_pQd?duGq6}9<3YsO&H ziLF&;(aIcOP_gL>Bb6{~OZL{A=d{N4c|?ES}aJ0^hNIc!EicC566DRrMhZlnIeQFSJ{j4zBBkcW&ls z58foQu#LMbcJO7hTcPp9!=(;ErB(+TNT}i#U%kS9iI3x@%=Dq(Bm=w5utP9m++rGb z0JfEe{kXoi=b%8ki6nPqHE6SgGInOWH&2ee&2a%$R0|kTF>t(t78Zt)24jC;4k8AZ zqLnX^h5iM&oh&ss$Wl1@iG^S#e zL7lcq5t8QBc-dZ($$;5`t;jSBi)#;3i?te1y9kGsJX;$-dwn0an|UEJVnRzQ1T*YL z3{^tmbt7vI$cv=G_2eVmean?bdQfLVcCRCKC8+31B6DctJu}?nPYq$MXn*x!LW;i? z118}y?w@&ut2#ih8~iOWNOed;-63)Gd}G9^wYV6EM*Cy(4<(9B=aS{iyOrlY(DbVU zdeo<;taeJqGuXQH!*f)6NmuOV0uPW7q2`g^sGKh1w(syM%xocNH(v+PWb=txzD%Tf zHnp=^<>4!ecY*&EXOCRVcD>?U?;s8BE6s_Umyg*AS0W)BPrn`WoEO+{z4XD6g8KeG zzK_rb$m|AIc)W&R0r~^MEi! zRA;YT9Yj%f&nhW)ARglD@I8>d=+W-1uR3h~+#)Z+Lw8tD|4p|sbZF;LAXax6xpIB%OgZq_jUw zmLy_dxX|pAQ#E(j3nL6x6?u~IWn=B7Ztw0D&*LFS*Y!?JFO?)zA2kxQ4;(*C?DObD zm&Ced_3Pn$;VBEObE9-Vyz5?hXn?jelMkXR_xn)jB`JCsEB$1Z%KOe=#}c>}UUT=i zDxc`~31kn=_b6vx)ga_exKFfMd`&923#UNu;->3k*4k7*b=zO+D!P|0d38JnPn>S! z0g=n<>nHmiEQRG9E#+1ggN$_TbCkCO!^d@-G#Z=hT4Hr`>?RJZdQy9=^?U06OE+yeDw%8<~45uf2==6OzIqnj4&GI0hPFp3;=a<^Bra8`P6t3 zW<7A*5wq`6I<-6Ra26w_YPy1wkMS5-pNw^X+OoIhiSc8@9q5H&Y5O3LB}J!^kiMum zLTjGdf!=%DaEwmtgWox#<$(}|^`|bYhRBCrDkB{958FkBlgp@y;}d3zFKrH8=M!Q% zbcq#p@8RQKJpwPJUM7{ZpU*^X{+SVd2N1XG2@_{U?8d;{ck;3FbJ_iTKC?D)@6unu ztxO~CGHa$&N(_x>rqmQn^d3s6T>r!oEexdPDL0E3)~l)!hcwPAPq^G2>4imMwK@L5 z(C)cHFqAbt;lXR9p0&`!YWSoW=x9txf{oFV8v(k)+WR(sYll$l;M%bP_cdX)Ek7?gvSO7;}Xq+ z7pKziTAI0Xm8?4MAM3lv#O>zzvhL%5S;=r^E4Cxz0}D>8o_U5OIEYz|R)ticzKDGZ zpj;P}T2C+0xJOnY-eSO2FygA}EZ_JwsQj!VY%f>L23ds;z4DqC^5$036uuLFLLj1~ zpO29`pTh%|8~6Tt*wC@*h(+<<7vGMQ%?O`%GH7qVB9i@cn<=tLVfFSlf3eUCG4C_+XS*otb?Vqa+a5wrR2vs%LXrVr|B+&sP z5d+0d>7l@uTmN580TSN1JhLZGg8<_x_=uSnj0zSCHe)pg5bcr+QkP7>%i+({DhHOea;9>7G zU)vpFnxspBGo^01gD7Y9pyR%1Cp6wcgTpO3#3x;r<@8ilI~fHku- z)4q2GSN(iwGVukx_SKp@u>dQj5C{0A>%`GJjLLvH9kXy zieZ(F6}N=+%L0Ro|NQpc7PUvK_Htlpw%pJF(SmTOR!k7p@HyUCd@Kvl8;x@P* zC;_)03A*i_9{6BwVc}G912GYupJ|&Xdd)WN{9D_^Gk761jw>3z4V@iwucu*8(Uy+t zrHe^Gud?-@2USjoe}YyMgqwyi=7oITS~Y%py0>&Pz&O<82qJKW@yyV)MgiSCbL~3{ z32Lk?QjPj2CgawjiK_eZejr#qRY@@U&jBp|tA|abK|w$t)hUm+3eAzdbn&PHZ#DTmQi^1%e+!?Ny%J;Lh!TOD=4vTdu3CFrsrbh|TwFJ?Ym>6j`7VtOx%G@J{Z z_Q%oxn5=X8UWBTg&n9*|@Vw&&v9%CZTaPMDtLs68XtTUNn18YvlBB zb^V`uRrR*Fr(!!VQbYJ4B_U)#wrr=&*|!a^B%ZO&14L!qqPy^_=xgb=z|D;306^Jy z0FZh)@Ec^4k2tJu@4$!C{ntijabIye68y~KXf5~e=6pW-E-oGaU9`l}Qhbld8Oxqu zB^4pdQH^UaUYvTTee&%|^_aBPCzkCqQKPDVZ0QKRwB(p)aSjhV{(Cw8t0ezl4Gmq| zBf?R`O3-odLU(e*aU-``5D@=iwAD2RJAhw33>tI$$cudNRngKGBCtG1UF87-*7_nZ zIA!H+*xWX*^t{c_sVaR2_dH)-ybR3$oY~;(Rq2XT@U0~VWalDB4{_!;wKyz}_L)Gc4|dg1;_6zQKP^l?g_wSj$}!_}A^3P#Gi_&_f$9iSht7d<<@} zh5M1b)j|~UVyDf+OjvwLJREJ`A*>!M%BF6h(l*qa&hndq47$Vu<;gz#4L z-*4-!V6GN>yeqq=pTid1yprTPWZ2^Ay4ZX%tqgaQ9K%;d;1*@&9IK}!rCEGg8YAkh zN^=St2&Dw4E_@VXnTt$YLjEA_!*hQrC_#A4-t{AkLPNs)ZMEvAaPec5SR%aYlP8=( z5woh9x7f#`L~4PdC7(epYIg;5$DTopshC*s(it|itM51HarIJ2S6O%M_0r}6(HZ1u zA`&%)h{e~Mx!*LIj14l(g}Bqiz`H9KTSh|^T853PkX>FU>S{e4)EB?`Em-7Uub2dL zTpp=Yv#FNULbmpT7XO{9n8A;gKv?-4KQtwmp~_dV-)=Il^8nflsnnc{{2RSMQ#yme zyE9ioJ65Mteaqm|GUncO@0ZsW#s(WrE`&W@yuY#4m~7}^YTMM9xarigia8ZnjabMU zB>(&hOV18aFy%plJ$*5>Y_)jheF7**jGA$Y@~(nOzMge^gXUQd}Cq3 z67sAT+28@^290!+CXP_QvPxb!|8$~<{Q~IZ zC;rQ(+k-s;Zgz16SYPAr(I<4zj1cXZI0#y~E&H;U_OOwI=@~*!_trEiOTPf8pLD$} z;BHZJUBBUp2HO1X+n7I#ni`^t@?btSxj4U?pCTMSBdLo85Az)hkrQa2L~m{sy#}Kg zP~gO)ZJ0SF$xM3+FE>7~jsQ#3#;xfQ_H8CX-3bI$Mekt!7nldgV3p2mLWH{Uva zw=eC28FI&AuaP~J@hrw>Bo~6Qq+wOiT9h{YKEAHv)<7dE^Mg2re~6m!>g`baVt}q= zESHp_Z=3(Jg%IU9utNCXD)F@J#QSDUSXE>EVvr_nO+}Nr?b`)gz59PMbD1^BX-=uo z_dXmLkmTnbt)jgVmblN`_GO~EGc&PQF1XZoDjE0cPFntv{3G-ukuQWDo%#C;2);W) zINwQ*66X>Rm==zZKUyYZ@OC~i(KGjx+7TViXU`JL%OmY&ECHE20!B00zTGvkVZ9q= zL)?R)_NCObwGG3ltBV|$f@|ZS{5M~DeHoklGNH*Pq`VK0FmTE=CFIh@N+ywM5=Cu~ zg+7|`8EYLkid?tCmB8xYR->Ds317u`$}KcNZ8z4_80}+5Somuvs5PfeoL&kW5b5uk+NeB@UtIajQn&;ArpJa? zG&V%row{uzKDA8u?g_Fm#SW>{T}pbFtU7Q)QgNwKLD+*{=&)XX14UNZMJe!eFYaEE zdq9?#Ov(sBK+L>e(cqJf-{@(j%l1J-sl?*{(jCF`xsuM8RBnHd)Xp&*O9-yx+VYos zu_zU|nEJtm$!W?|yBRYqQ9qf>!Ec$kXZ~%!!hh^r_@ADCj&3CKfG4;P#P&&KG=3}V zG{V~TBf0)5=`|17LyjgSt9N46*sdHk@_q|BbdAdp@o-T?V-4=QAL$4o%cZq;NG)48 z*V2u@i6Njln);#H!}fdLKJ8=sJ_+LX@|#F+Ir2Q9)_R+NecpQ8bacReAVOrdCYFFt z+Ehd+VU^GwQZcxC(^(5;5XBC)9D$0o<7Hxy|mu=&HlFVq4!MMk@ z_S+kQD#K5dbDWKc@>&=#0*- zk5NV+{4~?YR01y$shUl;NnILfnWZAz4`~U%1nCzXg&9&fGI8|Ql32a^9pzrU+)?7y z-EoCuo?s^PN5|@{*CDoIY!JV_^UwythIB3&bh>NDexq_uRlG;Vn?V>oEK2OUop^)S zp?ccla?OpFZC$RC?nQH~GS^5|OM;NZgl#NRb!(gv7|NXHXfCzhFSDb2ks*1Y~iQpm4sXWVd~W2QdEl zs|&2er|DY%^i}g(;{o*-^Vp)FN zJ2Ews3wr~a`_h9;5EvcW(Zv^Rx#pW!V7@K1=@t`}*5|Iizi(v(oO`c~3eWtr{OJoR zD!SQtdlff37vLVf`{T;yH}lsqx=+p-w2SZgD0MBxQg+~f0f8Qf*a@zo*8Z2VnV~UuHy|{WpF<%=lH3q{umPUPln71^qb<*>niGM5gO<3=rV>l7s=YIomKFJUO literal 0 HcmV?d00001