背景
もともと、
の問題に対応するため、パッチを作った(つもりだった)。
症状
別環境で試すことになったので、もう一度 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が採用されないんだよね…