gdrive の import ができなくなった。
背景
もともと、
の問題に対応するため、パッチを作った(つもりだった)。
症状
別環境で試すことになったので、もう一度 build してみると、なんと動かなくなっているんだよね。 (すなわち issue の状態)
調査
バックアップ
とりあえず、現行のうまく動くバイナリ(?)をバックアップした。(これは大正解)
オリジナルのレポジトリの upstream merge してbuild
オリジナルのレポジトリを upstream merge して(これが余分だった) build してみてもNG。
Merge remote-tracking branch 'upstream/master' · tomofumi-nakano/gdrive@eef35d2 · GitHub
以前の環境で上記の merge を反映させたソースで build
以前の環境で最新のレポジトリに反映させて build を試みるも次のようなエラーが。
handlers_drive.go:12:2: cannot find package "github.com/prasmussen/gdrive/auth" in any of: /usr/local/Cellar/go/1.8.1/libexec/src/github.com/prasmussen/gdrive/auth (from $GOROOT) /Users/t-nakano/src/github.com/prasmussen/gdrive/auth (from $GOPATH) gdrive.go:7:2: cannot find package "github.com/prasmussen/gdrive/cli" in any of: /usr/local/Cellar/go/1.8.1/libexec/src/github.com/prasmussen/gdrive/cli (from $GOROOT) /Users/t-nakano/src/github.com/prasmussen/gdrive/cli (from $GOPATH) compare.go:5:2: cannot find package "github.com/prasmussen/gdrive/drive" in any of: /usr/local/Cellar/go/1.8.1/libexec/src/github.com/prasmussen/gdrive/drive (from $GOROOT) /Users/t-nakano/src/github.com/prasmussen/gdrive/drive (from $GOPATH)
うまくいかなったので、
go get github.com/tomofumi-nakano/gdrive go get golang.org/x/net/context golang.org/x/oauth2 golang.org/x/oauth2/google google.golang.org/api/sheets/v4
ということをやってしまった。すると、build は通ったが、issue の症状となってしまう。
fix時の状態に戻して build してみる。
fix時の状態に戻して build してみる。それでも issue の症状になってしまう。
ところが、バックアップしたバイナリではうまく行った。
- gdrive のソースが悪いわけではない。となると外部ソース関係(ライブラリなど)が怪しい。
- 外部ソースは現行のバイナリの中に含まれている。
ということがわかる。
gdb でやってみる。
このコマンドは基本的には、google の api と通信しているだけなので、その通信内容の違いを見れば、何が違うのかが分かる。 違いが判明すれば、どの部分に問題があるのかが分かるだろう。 当初は wireshark なども検討した。ところが、https で通信していれば、中身を見ることはできないことに気がついた。
というわけで、gdb で見ることにした。
懸念点としては、デバッグ用の文字列が埋め込まれているかどうかであるが…
まず gdb で run すると次のようなありがたいメッセージが
During startup program terminated with signal ?, Unknown signal.
c - Unknown ending signal when using debugger gdb - Stack Overflow
はい、すみません、インストール後、次のようなありがたいメッセージを頂いておりました。
gdb requires special privileges to access Mach ports. You will need to codesign the binary. For instructions, see: https://sourceware.org/gdb/wiki/BuildingOnDarwin On 10.12 (Sierra) or later with SIP, you need to run this: echo "set startup-with-shell off" >> ~/.gdbinit
一つ目は sudo しただけだったけど、二つ目は無視していました。
sudo gdb gdrive.ok ... (gdb) run import --mime text/csv file.csv ... warning: unhandled dyld version (15) Imported xxxx with mime type: 'application/vnd.google-apps.spreadsheet' ...
おおうまく行った。やはり ok バージョンは問題ない。
次にプログラムが埋め込まれているかどうか。
(gdb) list warning: Source file is more recent than executable. 11 const Version = "2.1.0" 12 13 const DefaultMaxFiles = 30 14 const DefaultMaxChanges = 100 ....
おーっ、埋め込まれている模様!!!
warning: Source file is more recent than executable.
は多少気になるがとりあえずOK。
(gdb) list drive/upload.go:178 warning: Source file is more recent than executable. 173 dstFile.Parents = args.Parents 174 175 // Chunk size option 176 chunkSize := googleapi.ChunkSize(int(args.ChunkSize)) 177 178 // Wrap file in progress reader 179 progressReader := getProgressReader(srcFile, args.Progress, srcFileInfo.Size()) 180 181 // Wrap reader in timeout reader 182 reader, ctx := getTimeoutReaderContext(progressReader, args.Timeout)
おやおや?
ここで修正されているはずの内容が修正されていませんね…。どういうことなのでしょうか…
strings
で各バイナリの違いを見てみる
strings ~/bin/gdrive.ok> ok.txt strings ~/bin/gdrive.ng> ng.txt vim -d ok.txt ng.txt
多すぎて判明せず。とりあえず、別のバイナリと思って良さそう。
もう一度原因を見てみる
// Media specifies the media to upload in one or more chunks. The chunk // size may be controlled by supplying a MediaOption generated by // googleapi.ChunkSize. The chunk size defaults to // googleapi.DefaultUploadChunkSize.The Content-Type header used in the // upload request will be determined by sniffing the contents of r, // unless a MediaOption generated by googleapi.ContentType is // supplied. // At most one of Media and ResumableMedia may be set. func (c *FilesCreateCall) Media(r io.Reader, options ...googleapi.MediaOption) *FilesCreateCall { opts := googleapi.ProcessMediaOptions(options) chunkSize := opts.ChunkSize if !opts.ForceEmptyContentType { r, c.mediaType_ = gensupport.DetermineContentType(r, opts.ContentType) } c.media_, c.mediaBuffer_ = gensupport.PrepareUpload(r, chunkSize) return c }
ここで、DetermineContentType
をするために、 opts.ContentType
を渡している。
これで、変換をするわけだが、それが、
// DetermineContentType determines the content type of the supplied reader. // If the content type is already known, it can be specified via ctype. // Otherwise, the content of media will be sniffed to determine the content type. // If media implements googleapi.ContentTyper (deprecated), this will be used // instead of sniffing the content. // After calling DetectContentType the caller must not perform further reads on // media, but rather read from the Reader that is returned. func DetermineContentType(media io.Reader, ctype string) (io.Reader, string) { // Note: callers could avoid calling DetectContentType if ctype != "", // but doing the check inside this function reduces the amount of // generated code. if ctype != "" { return media, ctype } // For backwards compatability, allow clients to set content // type by providing a ContentTyper for media. if typer, ok := media.(googleapi.ContentTyper); ok { return media, typer.ContentType() } sniffer := newContentSniffer(media) if ctype, ok := sniffer.ContentType(); ok { return sniffer, ctype } // If content type could not be sniffed, reads from sniffer will eventually fail with an error. return sniffer, "" }
により、単に、c.mediaType_
に opts.ContentType
が入ることになる。
とりあえず、デバッグしてみる
$ sudo gdb gdrive.ok ... (gdb) break upload.go:187 ... (gdb) run import --mime text/csv file.csv ... Thread 3 hit Breakpoint 1, github.com/prasmussen/gdrive/drive.(*Drive).uploadFile (self=0xc42002e1e8, args=..., ~r1=0x0, ~r2=0, ~r3=...) at /Users/t-nakano/src/github.com/prasmussen/gdrive/drive/upload.go:187 ... (gdb) p args.Mime $2 = 0xc4203485d0 "application/vnd.google-apps.spreadsheet"
上記は正常な方。
そして、次は、修正されている方…。
(gdb) list upload.go:187 182 reader, ctx := getTimeoutReaderContext(progressReader, args.Timeout) 183 184 fmt.Fprintf(args.Out, "Uploading %s\n", args.Path) 185 started := time.Now() 186 187 f, err := self.service.Files.Create(dstFile).Fields("id", "name", "size", "md5Checksum", "webContentLink").Context(ctx).Media(reader, chunkSize).Do() 188 if err != nil { 189 if isTimeoutError(err) { 190 return nil, 0, fmt.Errorf("Failed to upload file: timeout, no data was transferred for %v", args.Timeout) 191 }
修正されていない!
いったいどうなっているのか…。なぜ修正されていない…。
もう一度はじめのエラーを思い出してみる。
go get
とかで、色々取得していたが…。
そして upload.go
を見つける。
見つかった。とりあえず、github.com/prasmussen/
を消してbuild してみる。
go build -gcflags "-N -l" handlers_drive.go:12:2: cannot find package "github.com/prasmussen/gdrive/auth" in any of: /usr/local/Cellar/go/1.8.1/libexec/src/github.com/prasmussen/gdrive/auth (from $GOROOT) /Users/t-nakano/src/github.com/prasmussen/gdrive/auth (from $GOPATH) gdrive.go:7:2: cannot find package "github.com/prasmussen/gdrive/cli" in any of: /usr/local/Cellar/go/1.8.1/libexec/src/github.com/prasmussen/gdrive/cli (from $GOROOT) /Users/t-nakano/src/github.com/prasmussen/gdrive/cli (from $GOPATH) compare.go:5:2: cannot find package "github.com/prasmussen/gdrive/drive" in any of: /usr/local/Cellar/go/1.8.1/libexec/src/github.com/prasmussen/gdrive/drive (from $GOROOT) /Users/t-nakano/src/github.com/prasmussen/gdrive/drive (from $GOPATH) make: *** [gdrive] Error 1
きた、これだ。handlers_drive.go:12:
に何が書いてあるんだっけ。
12 "github.com/prasmussen/gdrive/auth" 13 "github.com/prasmussen/gdrive/cli" 14 "github.com/prasmussen/gdrive/drive"
ぐふっ。
そして golang の import のショボさについて知ることになる…
- 相対パスによる local package はNG(理由はよくわらないが)
- github 上の push してから
go get
により、使うのが良いとされる。 - github で fork した場合(今回の自分)、対象なるパッケージ名も
github.com/<user_name>
となるはず。 - そこを修正していなかった!
あらためて、import 先を修正。すると、
(gdb) list upload.go:187 182 progressReader := getProgressReader(srcFile, args.Progress, srcFileInfo.Size()) 183 184 // Wrap reader in timeout reader 185 reader, ctx := getTimeoutReaderContext(progressReader, args.Timeout) 186 187 fmt.Fprintf(args.Out, "Uploading %s\n", args.Path) 188 started := time.Now() 189 190 f, err := self.service.Files.Create(dstFile).Fields("id", "name", "size", "md5Checksum", "webContentLink").Context(ctx).Media(reader, chunkSize, contentType).Do() 191 if err != nil {
おー!。思った通りに反映されている!
うーん、去年の11月にPR送ったのに、こういった話があってか、うまくできんといわれて、PRが採用されないんだよね…