首页 / 节气时间

开源协议自查:一次误用 GPL 的复盘

开源协议自查:一次误用 GPL 的复盘

先给一条能当场核对的开源判断标准:把你准备引入的那个代码目录打开,看根目录有没有 LICENSE、协议COPYING 或 LICENSE.md,自查再打开你打算发布的次误那个仓库,看它的开源许可证字段和这份文件是不是同一份文本。两处对不上,协议就先停下,自查别急着提交。次误这条标准不解决所有问题,开源但它能拦住大部分“凭印象以为可以随便用”的协议失手。

下面按时间顺序复盘一次真实的自查误用。项目是次误个内部工具,后来要对外开源,开源问题是协议在上线前一天才被发现的。

第一天下午:把一个工具函数拷进项目

当时需要一段解析配置文件的自查代码,同事从某个开源项目里复制了一个文件,改了几行,直接放进我们的 src/utils 目录。文件头没有版权声明,他以为“没写就是没关系”。这一步的关键动作是:复制之后立刻在提交信息里注明来源和原项目地址,并在仓库里新建一个 THIRD_PARTY 目录专门存放外来代码。这条习惯本身不解决授权问题,但它让问题变得可见——后来我们能顺着提交记录找到这段代码的出处,而不是靠回忆。

如果当时连来源都记不清,下一步就只能靠文件特征去搜:函数名、注释风格、报错文案,用代码搜索平台反查。这一步很费时间,所以宁可在复制的那一刻就留下记录。

第三天:发现原项目是 GPL-3.0

顺着地址打开原项目,根目录有 LICENSE 文件,开头写明 GNU General Public License v3.0。这时要做的是三件事,按顺序来:

第一,确认这份许可证约束的是整个项目还是部分文件。有些项目是双许可,或者某个子目录单独用 MIT,需要看目录级的 LICENSE 和文件头注释。第二,确认我们打算怎么发布:如果只是内部使用、不对外分发,GPL 的义务触发点和对外分发不同;一旦对外提供二进制或源码,义务就来了。第三,把结论写下来,哪怕只有一行“此文件来自 X 项目,GPL-3.0,对外发布前需处理”,放进仓库的说明文档里。写下来是为了让下一个接手的人不用重新查一遍。

这里有个容易混淆的点:GPL 的“传染”针对的是衍生作品的法律认定,不是“只要在同一个仓库里就自动全部感染”。哪些算衍生、哪些算独立程序,实务上争议不少。所以自查时不要把结论下得太满,拿不准就按更保守的方式处理,或者去问熟悉开源合规的律师。

上线前一天:把文件挪出去还是换掉

留给我们的选项其实只有几个,按成本从低到高排:

一是替换实现。那段解析逻辑不复杂,重写一版,行为对齐测试用例,外来文件删掉。这是最干净的做法。二是把这段代码拆成独立进程,通过命令行或网络接口调用,隔离程度更高,但架构改动大,且是否构成“独立程序”仍需具体判断。三是整体按 GPL 发布,这对一个想用 MIT 对外授权的内部工具来说不可接受。最后我们选了第一条,重写加补测试,花了大半天。

反例也值得说一句:另一个项目把 Apache-2.0 的代码并进 MIT 项目,以为只要保留版权声明就行。Apache-2.0 还带有专利授权条款和 NOTICE 文件的要求,只留一行版权声明并不够。不同协议的义务不一样,不能套同一个模板。

收尾时留下的一张核对表

事情处理完,我们把自查动作固化成一张表,放在仓库根目录,新人引入外部代码时照着填:

来源地址、原项目许可证名称与版本、许可证文件路径、引入的文件范围、是否修改过、修改说明、计划的分发方式(仅内部/对外源码/对外二进制)、结论与处理人。表格不长,但每一项都能对应到一个可核对的文件或提交记录。

另外补两条经验。其一,许可证名称要写全,MIT 和 MIT-0 不同,BSD-2-Clause 和 BSD-3-Clause 不同,GPL-2.0-only 和 GPL-2.0-or-later 也不同,简写成“开源协议”没有意义。其二,依赖树也要查,不只是手工复制的文件;用包管理器的话,导出一份依赖清单,逐个看许可证字段,遇到 UNKNOWN 或自定义条款的单独标出来。这类清单工具能省时间,但工具给的结论只是初筛,最终判断还得人看许可证原文。

回到开头那条标准:两处许可证文本对不上就先停。这次失手之所以能在上线前被发现,不是因为谁记得住协议条款,而是因为有人真的去打开了那个 LICENSE 文件。开源协议常识的自查,说到底就是把“我以为”换成“我看过”,并且把看过的结果写下来,让下一个人能复核。

作者:干支纪年