- 投稿日:2021-06-20T20:34:29+09:00
【Go入門】A tour of Go 【Basics】
A tour of Go Go入門用の公式チュートリアル https://tour.golang.org ブラウザ上で実行できるのでGoインストール等は不要。 Offlineで行う方法もWelcome編に記載されている : https://go-tour-jp.appspot.com/welcome/3 この記事では各チュートリアルで個人的に理解しづらかったものや、普段使用する言語と違った部分をメモとして残します。 Basics packages, variables & functions パッケージについて 外部パッケージはimportで宣言 外部パッケージ内で使用している変数や関数などの名前は、そのパッケージでエクスポートされている場合に使用できる。エクスポートされている名前は先頭が大文字になる。 exported-names.go package main import ( "fmt" "math" ) func main() { fmt.Println(math.Pi) } math.piだと宣言されていないのでエラーになる。 関数について 基本形 func (arg1,arg2...) (return_value) {処理} 引数や戻り値を変数宣言する際は、型宣言は変数の後ろ functions.go package main import "fmt" func add(x int, y int) (sum int) { return x + y } func main() { fmt.Println(add(42, 13)) } 同じ型の場合はまとめて宣言可能 func add(x, y int) (sum int) { return x + y } 複数の戻り値も可能 multiple-result.go func swap(x, y string) (string, string) { return y, x } 戻り値に変数名を与えると、returnの後ろに指定しなくても戻り値を暗黙で指定できる (naked return statement) 短いコードでは有効だが、それ以外では可読性が下がる naked-return.go func add(x, y int) (sum int) { sum = x + y return } 変数について 型宣言するJavascriptと理解するとよい var : 変数の宣言 const : 定数の宣言 (後述) ゼロ値 変数に初期値を与えずに宣言するとゼロ値が与えられる 数値型(int,floatなど): 0 bool型: false string型: "" (空文字列( empty string )) 初期値の与え方 pythonのようにまとめて代入できる variables-with-initializers.go package main import "fmt" var i, j int func main() { var c, python, java = true, false, "no!" fmt.Println(i, j, c, python, java) } 暗黙的な型宣言 関数の中では varの代わりに := を用いて型宣言せずに変数宣言ができる short-variable-declarations.go func main() { c, python, java := true, false, "no!" fmt.Println(c, python, java) } 型変換 変数vを型Tに変換する : T(v) GoはC言語と異なり、型変換は明示的に変換しないとエラーになる type-conversion.go package main import ( "fmt" ) func main() { var x, y int = 3, 4 f := float64(x) + 0.5 y += 1 fmt.Println(x, y, f) } 定数 character, string, boolean, numericのみ 定数は暗黙的宣言 (:=) はできない 数値の定数は高精度な値であり、型が指定されない場合、状況に応じて必要な型を取る numeric-constants.go package main import "fmt" const ( // Create a huge number by shifting a 1 bit left 100 places. // In other words, the binary number that is 1 followed by 100 zeroes. Big = 1 << 100 // Shift it right again 99 places, so we end up with 1<<1, or 2. Small = Big >> 99 ) func needInt(x int) int { return x*10 + 1 } func needFloat(x float64) float64 { return x * 0.1 } func main() { fmt.Println(needInt(Small)) -> 0.2 fmt.Println(needFloat(Big)) -> 1.2676506002282295e+29 } SmallもBigもintかfloatか決まっていないが、それぞれ関数で要求されているデータ型にマッチするように型を取っている。
- 投稿日:2021-06-20T11:41:40+09:00
Goの外部パッケージが$GOPATH/srcではなく$GOPATH/pkg/modにインストールされる
Goの外部パッケージをsrc配下にインストールするには 環境はMacBook (Retina, 12-inch, 2017) OSはBigSurバージョン11.4 Goはgo1.16.3です。 以下のコマンドを叩くとsrc配下にインストールされるようになります。 $export GO111MODULE=off 例えばその後 $go get github.com/markcheno/go-talib を叩くと以前はGOPATH/pkg/mod/github.comにインストールされていましたが、GOPATH/srcにgithub.comディレクトリが作成され、正しい場所にインストールされるようになります。 参考 リンク
- 投稿日:2021-06-20T00:54:57+09:00
GoとGinとLambdaでSlack botを作成する
社内でgoの勉強会みたいなのをやっておりまして、それの事前調査的な意味でのやってみた系blog記事となります。 やりたいこと こんな感じです。 goで何かつくる 何かフレームワーク使う(gin) Lambdaとapi gateway使う deployはsam使う slack botとして動かす 環境 普通のmac(m1じゃない) まずはhello world なんか公式っぽいライブラリがあります。 https://github.com/awslabs/aws-lambda-go-api-proxy これ使えばgoとlambdaとginとsamが一気にやれる見込みです。 ひとまずhelloworldしてみましょう。 とは言ってもREADME通りにやるだけ。 まずは当然これ。 git clone https://github.com/awslabs/aws-lambda-go-api-proxy あとはこんな感じで一気に。 ただ、makeの時にbuildのオプションがないとLambdaでexec format error: PathError nullみたいなエラーになったので下記にならってMakefileを修正しています。(GOBUILD=GOOS=linux GOARCH=amd64 $(GOCMD) buildみたいに) https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-package.html $ cd aws-lambda-go-api-proxy $ go get github.com/aws/aws-lambda-go/events $ go get github.com/aws/aws-lambda-go/lambda $ go get github.com/awslabs/aws-lambda-go-api-proxy/... $ make $ export YOUR_DEPLOYMENT_BUCKET=hoge $ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket YOUR_DEPLOYMENT_BUCKET export YOUR_STACK_NAME=hoge $ aws cloudformation deploy --template-file output-sam.yaml --stack-name $YOUR_STACK_NAME --capabilities "CAPABILITY_IAM" Waiting for changeset to be created.. Waiting for stack create/update to complete Successfully created/updated stack - hoge これであっさりとdeployが始まったっぽいです。 cloud formationのstackが作られて、deployが進むわけですが、進捗は下記のコマンドで確認できます。 $ aws cloudformation describe-stacks --stack-name $YOUR_STACK_NAME --query 'Stacks[]' "StackStatus": "CREATE_COMPLETE", みたいに出力されていれば正常終了しています。 で、"OutputValue" という所にapi gatewayのendpointが出力されています。 これをcurlで叩いてみますね。 $ curl -XGET https://xxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/pets -H 'application/json' | jq '.[0,1,2]' % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 1225 100 1225 0 0 2097 0 --:--:-- --:--:-- --:--:-- 2097 { "id": "b4a89cf0-6136-4507-af13-023df7cb5e39", "breed": "Dalmatian", "name": "Cody", "dateOfBirth": "2008-07-02T11:20:25Z" } { "id": "f7a7a1d4-e499-4e16-9130-27de6176587f", "breed": "Norwegian Elkhound", "name": "Buster", "dateOfBirth": "2012-03-27T15:53:16Z" } { "id": "84852a28-b48d-49b1-9465-075ab8dd7305", "breed": "Jack Russell Terrier", "name": "Charlie", "dateOfBirth": "2012-07-30T20:47:20Z" } あっさり成功。 GET /pets/:id でペットを取得できたり、POST /petsでペットを登録できたり、 get /petsでペットのリストを取得できたりと、ペットを大量に飼育しているご家庭におすすめのサービスです。 なお、GET /pets 内ではgetRamdomPetっていうメソッドが呼ばれていて、登録してなくてもランダムでペットが生まれてきます。 いや、/pet/:idでもランダムでした。現状だと createPetは意味ないのかな。 curl -XGET https://xxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/pets/1 -H 'application/json' {"id":"1","breed":"Beagle","name":"Buster","dateOfBirth":"2021-01-09T03:33:19Z"}% slack bot めんどくさいのでこのexampleをそのまま使ったれ。 前提として、botを動かすためのslackのワークスペースおよび、botを登録する権限は必要です。 とりあえずbotを作る まずは深く考えずにbotを作ります。 まだ作成していない場合には、Slack アプリを作成します。作成してある場合には、既存のアプリを選択してください。 From app an manifestって何だろう?なんかmanifest使えるっぽいですね。 とりあえず、これで適当なやつを作っておいてみよう。 _metadata: major_version: 1 minor_version: 1 display_information: name: "俺の亀" description: An example app background_color: "#da3a79" features: bot_user: display_name: "orekame" settings: interactivity: is_enabled: true request_url: https://example.com/slack/message_action oauth_config: scopes: bot: - channels:read - chat:write - chat:write.public redirect_urls: - https://example.com/slack/auth OK。 とりあえずさっくりとはできた。細かい設定はまた後で。 ※なおredirect_urlsはいらなかった incoming webhookの利用 まずは下記をやります。 「機能」ページで「Incoming Webhook をアクティブにする」をオンにします。 アプリの投稿先となるチャンネルを選択して「許可する」をクリックします。 で、 3. Incoming Webhook URL を使用して Slack に投稿します。 $ curl -X POST -H 'Content-type: application/json' --data '{"text":"Hello, World!"}' https://hooks.slack.com/services/xxx/xxx/xxx みたいにやると外部からslackへの投稿は確認できました。 Lambdaからのレスポンスはこれを使おう。 Event Subscriptions 以前はOutgoings Webhooksってのがあったけど廃止されたみたいです。けっこうslack周りはupdate早いんすよね。。 ご参考: https://qiita.com/risto24/items/342256f6ed6cb504059a 先程のapiを再利用しつつ、 https://xxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/ みたいに登録しておきます。 ただ、challengeってレスポンスを返す仕様である必要があるみたいなので、main.go に追記をしておきます。(雑ですまんせん。) main.go type Challenge struct { Challenge string `json:"challenge"` } ... r.POST("/", postChallenge) ... func postChallenge(c *gin.Context) { json := Challenge{} err := c.BindJSON(&json) if err != nil { return } c.JSON(http.StatusAccepted, json) } あと、sam.yamlのEndpointも追加が必要でした。 sam.yaml Events: GetResource: Type: Api Properties: Path: /{proxy+} Method: any PostResource: Type: Api Properties: Path: / Method: post そんなわけでエンドポイントは登録できました。 channelに投稿されたメッセージを拾いたいので message:channels をsubscribeしています。 メッセージを拾う messageはこんな感じみたいです。 typeに event_callback って入ってきて、 event.typeが message って入ってるのを拾えばいいっぽい? if payload.Type == "url_verification" { c.JSON(http.StatusOK, gin.H{"challenge": payload.Challenge, "message": "ok"}) } else if payload.Type == "event_callback" { fmt.Println(payload.Event.Text) fmt.Println(payload.Event.Type) こんな感じでメッセージは拾えました。 あとはincoming webhookに投げるだけかな。 slackにレスポンスを返す。 最終的にこんな感じになりました。とりあえず動くだけのレベルのやつ。 /pets は消しました。 main.go package main import ( "context" "encoding/json" "fmt" "log" "net/http" "net/url" "strings" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ginadapter "github.com/awslabs/aws-lambda-go-api-proxy/gin" "github.com/gin-gonic/gin" ) var ginLambda *ginadapter.GinLambda type Event struct { Type string `json:"type"` Text string `json:"text"` } type Payload struct { Challenge string `json:"challenge"` Type string `json:"type"` Event Event `json:"event"` } type ResponseBody struct { Text string `json:"text"` } // Handler is the main entry point for Lambda. Receives a proxy request and // returns a proxy response func Handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { if ginLambda == nil { // stdout and stderr are sent to AWS CloudWatch Logs log.Printf("Gin cold start") r := gin.Default() r.POST("/", postChallenge) ginLambda = ginadapter.New(r) } return ginLambda.ProxyWithContext(ctx, req) } func main() { lambda.Start(Handler) } func postChallenge(c *gin.Context) { payload := Payload{} err := c.ShouldBindJSON(&payload) if err != nil { return } fmt.Println(payload.Type) if payload.Type == "url_verification" { c.JSON(http.StatusOK, gin.H{"challenge": payload.Challenge, "message": "ok"}) } else if payload.Type == "event_callback" && strings.Contains(payload.Event.Text, "Hello") { fmt.Println(payload.Event.Text) fmt.Println(payload.Event.Type) webhookURL := "https://hooks.slack.com/services/xxxx/xxxx/xxxx" p, err := json.Marshal(ResponseBody{Text: "ha?"}) if err != nil { fmt.Println(err) } resp, err := http.PostForm(webhookURL, url.Values{"payload": {string(p)}}) if err != nil { fmt.Println(err) } defer resp.Body.Close() c.JSON(http.StatusOK, gin.H{"message": "ok"}) } else { fmt.Println(payload.Type) c.JSON(http.StatusOK, gin.H{"message": "ok"}) } } sam.yaml AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Example Lambda Gin Resources: SampleFunction: Type: AWS::Serverless::Function Properties: Handler: main CodeUri: main.zip Runtime: go1.x MemorySize: 128 Policies: AWSLambdaBasicExecutionRole Timeout: 3 Events: PostResource: Type: Api Properties: Path: / Method: post Outputs: SampleGinApi: Description: URL for application Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' Export: Name: SampleGinApi 一度、 strings.Contains(payload.Event.Text, "Hello") とか絞り込みをやらないで無限ループとかやらかしたりはしましたが・・とりあえずの所まではやれました。 あとは自分なりにカスタマイズしていきましょう。 あとlocalでのテスト term1 % sam local start-api -t sam.yaml Mounting SampleFunction at http://127.0.0.1:3000/ [POST] You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template 2021-06-20 01:02:47 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit) Invoking main (go1.x) Decompressing /xxx/sample/main.zip Failed to download a new amazon/aws-sam-cli-emulation-image-go1.x:rapid-1.0.0 image. Invoking with the already downloaded image. Mounting /private/var/folders/_f/nyppwytj6cb3_8ggkpwpqj1m0000gp/T/tmpcyochpbp as /var/task:ro,delegated inside runtime container START RequestId: 270fc1a5-c05f-1d91-d42e-ff60ef331920 Version: $LATEST Gin cold start [GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] POST / --> main.postChallenge (3 handlers) test test [GIN] 2021/06/19 - 16:02:55 | 200 | 129µs | | POST / END RequestId: 270fc1a5-c05f-1d91-d42e-ff60ef331920 REPORT RequestId: 270fc1a5-c05f-1d91-d42e-ff60ef331920 Init Duration: 190.28 ms Duration: 7.55 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 24 MB 2021-06-20 01:02:55 127.0.0.1 - - [20/Jun/2021 01:02:55] "POST / HTTP/1.1" 200 - term2 % curl -XPOST localhost:3000 -H 'application/json' -d '{"type":"test"}' {"message":"ok"}%
- 投稿日:2021-06-20T00:12:01+09:00
PythonからGo、GoからPythonを呼び出し合ってみる
内容 PythonからGoを、GoからPythonを呼び出してみようという試みです。 前回の記事にも書きましたが、PythonとGoはCを介して連携ができますので、cgo使えばやれそうです! cgoとは 概要 GoからCを呼ぶためのツールで、CライブラリにアクセスするGoコードを作るために使われたりします1。 まずはシンプルに試してみます。 main.go package main /* #include <stdio.h> void my_print(void) { printf("Hello, World!\n"); } */ import "C" func main() { C.my_print() } 結果がこちらです。 # ビルド $ go build -o main # 実行 $ ./main Hello, World! 仕組み cgoは実に面白い仕組みになっています。import "C"がcgoを使う合図になっていますが、その上のコメントはただのコメントではありません。import "C"のすぐ上に書いたコメントはCのコードとして認識され、それに従いcgoはCの関数を呼び出すためのコードを自動生成します。 Go言語からC言語の関数を呼び出す 基本的な流れは、 import "C"を行う import "C"の直前のコメント(preamble)2に呼び出したい関数や変数の宣言を書く go buildを実行 です。 go build実行時、import "C"を使用したGoファイルがひとつでもあれば配下のCファイルのコンパイル等を開始します3。 cgoを伴うgo buildでは、 Go/C間のギャップ(型など)を吸収するためのグルーコード生成 Go/Cコードのコンパイル 各種ライブラリのリンク 等を行なったのちに成果物であるバイナリを吐き出します。 一連の流れについてはこちらの記事に記載の図が圧倒的に分かりやすかったです。 グルーコードの生成、Cコードのコンパイル、ライブラリとのリンク等、裏側で行われる処理が可視化されています。 cgoを使ったCとGoのリンクの裏側 (1) ちなみになんでGoの世界なのにCファイルをコンパイルできるのかと申しますと、裏でCコンパイラが普通に呼び出されているようです。 PythonからGo、GoからPythonを呼び出し合ってみる ここからが本題です。 先に言っておきますと、今回は怠慢極まれり、コンテナ等使わずローカルで実行しています。 (本記事の目的がPythonとGoの連携を「ばっくり」理解するためだからです) 環境差異によっては実行できない場合もありますのであらかじめご留意ください。 とはいえ大枠のソースコードやビルドの流れは一緒だと思います。 自分の環境は下記のような感じです。 MacBook Pro (16-inch, 2019) macOS11.2.3 Go1.16.2 Python3.9.5 PythonからGoを呼ぶ場合 Pythonから呼び出されるGoコードがこちらです。 export.go package main import ( "C" "fmt" ) //export my_print func my_print() { fmt.Println("Hello, World!") } func main() {} ポイントは、 パッケージはmainである必要がある main関数は実行されないが宣言する必要がある エクスポートする関数に//export funcnameを付与(exportとスラッシュの間は空けない) です。 呼び出し側であるPythonコードです。 こちらはやたらシンプルになりました。 host.py from ctypes import * import ctypes lib = cdll.LoadLibrary("./export.so") lib.my_print() ビルドと実行をやってみましょう。 go buildの際にc-sharedをつけることでC ABIを持った共有ライブラリが作成できます。 # ビルド $ go build -buildmode=c-shared -o export.so # 確認 $ ls export.go export.so # 実行(export.soをPythonコードのディレクトリに移し替えてください) $ python3 host.py Hello, World! Pythonを実行し、Goで記述した「Hello, World!」が確認できました。 前回の記事でも書いたようにctypesはC ABIを持った共有ライブラリを読み込めるモジュールです。 なのでc-sharedな共有ライブラリを作って食わせてやることでうまく呼び出せた次第です。 GoからPythonを呼ぶ場合 今度はGoからPythonを呼びます。 PythonからGoを呼ぶ場合同様、Cを介さなくては相互にやり取りできないので、cgoを使います。 まずは完成コードです。 main.go package main // #cgo CFLAGS: -I/Library/Frameworks/Python.framework/Versions/3.9/include/python3.9 // #cgo LDFLAGS: -L/Library/Frameworks/Python.framework/Versions/3.9/lib -lpython3.9 // #include <Python.h> import "C" func main() { // 最後にPythonインタプリタ終了 defer C.Py_Finalize() // Pythonインタプリタの初期化 C.Py_Initialize() // GoのstringをCのcharに型変換(変換しないとPyRun_SimpleStringに型合ってないよって怒られる) // cannot use "print(\"Hello, World!\")" (type string) as type *_Ctype_char in argument to _Cfunc_PyRun_SimpleString pyCodeStr := `print("Hello, World!")` pyCodeChar := C.CString(pyCodeStr) // Pythonコードを文字列として受け取ってインタプリタ上で実行 C.PyRun_SimpleString(pyCodeChar) } 実行結果はこちらです。 # ビルド $ go build -o main # 実行 $ ./main Hello, World! Python(CPython)にはPython/C APIがあるので、C上でPythonインタプリタを動かせます。 今回の場合、Goからcgoを介してCにアクセスし、CからPython/C APIを使ってPythonインタプリタを動かしているイメージです。 Pythonコード自体は文字列としてインタプリタに渡し実行しています。 かなり短いですがprint("Hello, World!")がPythonコードに該当する部分です。 確かにGoの上でもPythonが動きました! これでPythonからGo、GoからPythonを呼び出す試みはすべて達成されました。 やったね! ここからはただの余談です。 余談:cgoのコメントについて #include <Python.h>の上に、 // #cgo CFLAGS: -I/Library/Frameworks/Python.framework/Versions/3.9/include/python3.9 // #cgo LDFLAGS: -L/Library/Frameworks/Python.framework/Versions/3.9/lib -lpython3.9 とのコメントがあるかと思います。 コメントを取り除いて実行してみると、 fatal error: 'Python.h' file not found のようにPython.hが見つからんと怒られます。 cgoでは、Cコンパイラに、使用するCライブラリ(及びそのヘッダファイル)の位置を教えてあげる必要があります。 ヘッダファイルPython.hやその実体であるCライブラリはPythonを落としたディレクトリに存在しますので、その位置を伝える必要があるというわけです。 誤解を恐れずばっくり言うと、 #cgo CFLAGS:でヘッダファイルが存在する位置等をコンパイラに伝えることができます #cgo LDFLAGS:でCライブラリが存在する位置等をコンパイラに伝えることができます。-LでCライブラリのディレクトリを指定し、-lで使用するライブラリを指定します。Cライブラリはlibfoo.dylibみたいなファイル名になるのですが4、先頭のlibと拡張子を除いたライブラリ名を指定してあげる必要があります LDFLAGS、CFLAGSについて 今回のPythonはpython.orgの公式インストーラから入れたものなので、 # Pythonのバージョンは落としたPythonのバージョンで読み替える # ヘッダファイル /Library/Frameworks/Python.framework/Versions/3.9/include/python3.9 # Cライブラリ /Library/Frameworks/Python.framework/Versions/3.9/lib あたりに(Macの場合だと)ヘッダファイルやCライブラリがいます。 以上のように、cgoでは使用するCライブラリやヘッダファイルの位置について、コメントを介してCコンパイラに渡しています。 余談:pkg-config 「Embedding Python in Go」では、 // #cgo pkg-config: python3 // #include <Python.h> import "C" といった形で、LDFLAGSやCFLAGSを用いずスマートにcgoのコメントが書けています。 知見が無なので調査するとpkg-configとは、*.pcファイルを元に、ビルドの際に必要な情報を返すツールだそうです。 Cライブラリをリンク等する際に必要な情報を*.pc形式にまとめておくからみんな利用してくれよな、みたいな世界観? PythonのCライブラリ自体も*.pcを準備しているので、「Embedding Python in Go」ではソイツを読み込んで利用しているわけですね。 同じようにやってみましょう。 pkg-configは、 $ brew install pkg-config で入れられました。 自分のMac環境(何度も言いますがpython.orgの公式インストーラから入れたPython)の場合、 # Pythonのバージョンは落としたPythonのバージョンで読み替える $ cd /Library/Frameworks/Python.framework/Versions/3.9/lib/pkgconfig/ $ ls python-3.9-embed.pc python3-embed.pc tcl.pc python-3.9.pc python3.pc tk.pc あたりにPythonの*.pcファイル群がいました。 複数ある*.pcファイルの中からpython-3.9-embed.pcをピックアップしpkg-configコマンドを試してみます5。 # pkg-configはPKG_CONFIG_PATH環境変数で指定したディレクトリを探すため実行前には事前に設定 # cgoで使う前にも設定する $ export PKG_CONFIG_PATH=/Library/Frameworks/Python.framework/Versions/3.9/lib/pkgconfig/ # Cライブラリの位置を出力 $ pkg-config --libs python-3.9-embed -L/Library/Frameworks/Python.framework/Versions/3.9/lib -lpython3.9 # ヘッダファイルの位置を出力 $ pkg-config --cflags python-3.9-embed -I/Library/Frameworks/Python.framework/Versions/3.9/include/python3.9 先ほどLDFLAGSやCFLAGSで指定したのと全く同じ内容が出力されています! cgoではcgo pkg-config: fooとコメントを書くことで、pkg-configがfoo.pcから取得できるディレクトリ等の情報がCコンパイラに渡されているようです。 まとめ 全く実用的なお話ではないですが、PythonからGo、GoからPythonを呼び出し合うことができました。 次はDatadogがgo-pythonなるパッケージを作ってるっぽいので使ってみたいです。 参考リンク cgo https://hnakamur.github.io/blog/2019/12/29/cgo-and-unsafe/ https://dave.cheney.net/tag/cgo https://speakerdeck.com/rajeshr/cgo-go-under-the-hood https://www.slideshare.net/AllThingsOpen/hidden-dragons-of-cgo pkg-config Wikipediaがかなり分かりやすいです https://ja.wikipedia.org/wiki/Pkg-config Goにとって唯一のFFIツールというわけでなくSWIG等も利用できるらしいです。C++連携だとSWIGの方が良いとの声も ↩ import "C"とコメントの間に改行があると動作しません ↩ このあたり公式にまとまっています ↩ 共有ライブラリの拡張子はOSによって異なります。*.dylibの他に*.soや*.dllなどがあります ↩ ちなみになぜpython-3.9-embed.pcを選んだのか、何故python-3.9.pcやpython3.pcではないのかと申しますと、後者の場合はLibsのフィールドが空だったため ↩