众所周知,在github上参与开源项目的一般流程如下:

  1. 将开源项目fork到自己的名下。
  2. 在本地开发环境中clone自己在上一步中fork的项目。
  3. 本地完成开发测试和代码提交,再push到自己名下的仓库中。
  4. 从自己名下的这个项目中,对原始项目发起一个pull request。
  5. 发起的pull request被上游merge后,自己的代码就进入开源项目中了。

虽然具体workflow的细节上可能有些差异,但总体流程大概就是这样。

但是在参与Golang的开源项目中,由于gopath的限制,这个流程就会有问题。举个例子,有一个项目为github.com/userA/project,fork之后,有一个新的仓库,路径为github.com/userB/project。而这个project中大概率会有内部package引用,比如在main.go中,import了github.com/userA/project/moduleA,这样在go get自己的仓库时,仍然会引用到原始的仓库路径。将会导致诸如构建失败等一些问题。

为什么基于c/c++的开源项目就没有这个问题呢,因为c/c++的构建更原始更简单。include只有两种形式,要么是在标准路径中或项目子目录中去查找,要么在非标准路径下,但能通过编译器的选项来告诉编译器去哪里找,而代码本身则是非常干净的,不会有硬编码的特定路径在源码里面。而Golang的import,是一个完整的url,这样可以让go get之类的工具自动化的进行一些操作。

上面可以解释Golang项目在引用外部包的时候使用完整url的做法,但是在引用项目内部包时,仍然使用完整url的做法就多少让人觉得有点不能理解。如果import能支持相对路径,就不会有前面提到的问题了。Golang的开发者们当然对这个问题有自己的考虑,参考这个issue:https://github.com/golang/go/issues/3515

不能简单的说Golang的这种构建方式不对,因为它的出现必然是有自己的优点和解决了一些问题的,所以从实用的角度出发,我们只能将其称之为“特点”,并去适应它。

解决办法有几个,但都看上去有些丑陋。比如:

在本地开发的时候,使用的路径仍然是原始项目的路径,将自己名下的仓库作为一个remote upstream,push的时候将修改push到自己名下的仓库中,然后再向原始仓库提交pull request。

对于一般的开源项目,这种“临时”的方法就够用了。但在下面的几个场景种,这种方法显然会很令人不适:

  1. 原始项目已经不维护了,你想自己长期维护一个fork。
  2. 原始项目仍然在积极维护,但是你仍然想自己长期维护一个fork。

在上述情况下,其实我们是把原始项目和自己的fork当作两个相对独立的项目,我们希望在构建的时候能直接go build就行了,不希望有其他额外的操作。如果有第三个人,他想fork我们这个fork并进行并列开发,按上面的方法将会非常麻烦。

对于这种情况,我选择将项目内所有源码中的import路径,修改为我自己名下的路径。虽然这种做法很丑陋,也是不得已而为之了。