一份误删的授权文件,让我重新理解了开源协议常识
那张单子其实是份误法务发来的一张截图:客户在应用商店的合规审查里,把我们的删的授权识安装包标成了“授权不明”。截图右下角有一行红字,文件附着一个我从未打开过的让重文件路径——LICENSES/。点进去是新理空的。那一刻我才意识到,解开项目根目录里那个叫 LICENSE 的源协议常文本文件,在三个月前的份误一次目录整理中被顺手删掉了,而仓库的删的授权识提交记录里,没人再把它加回来。文件
这不是让重法律事故,但足够让一次交付卡住。新理复盘下来,解开问题不在“选错了协议”,源协议常而在“选协议这件事被当成了复制粘贴”。份误下面按时间顺序,把当时每一步做了什么、判断依据是什么写清楚,顺带说清开源协议常识相关工具该怎么挑。
第一天下午:先确认仓库里到底缺了什么
我做的第一件事不是去网上搜“哪个协议最宽松”,而是打开仓库根目录,逐项核对四样东西:根目录是否有 LICENSE 文件;package.json(或 pom.xml、Cargo.toml)里的 license 字段写的是什么;README 末尾是否声明了授权;源码文件头部有没有零散的版权注释。结果很典型:字段写的是 MIT,文件却不存在,两处对不上。
这一步的价值在于把“印象”变成“清单”。很多团队以为自己用了某个协议,实际只是某个模板脚手架自带的字段。核对时以仓库里的实际文件为准,不以记忆为准。如果项目还引用了第三方组件,顺手把依赖清单导出一份,后面要用。
第二天上午:用工具查依赖,但先弄清它查的是什么
接下来才轮到工具。这里最容易失手:把扫描工具的输出直接当成法律结论。我当时的做法是先分清三类工具的职责边界。
第一类是依赖清单生成器,比如各语言生态自带的锁文件与依赖树命令。它只回答“引用了谁、什么版本”,不判断授权。第二类是许可证扫描器,常见的有开源的扫描工具和商业 SaaS 两种形态,它们读取依赖包的元数据与许可证文本,输出一份“组件—协议”对照表。第三类是合规平台,能给出义务清单和冲突提示,但结论仍需人复核。
选择时的判断点很具体:项目是否允许把依赖信息上传到外部服务(涉及代码与依赖清单的保密要求);扫描器是否支持你所用语言的锁文件格式;它的协议数据库更新频率能否覆盖你依赖的冷门包;输出格式能不能直接进 CI。前两点决定能不能用,后两点决定好不好用。至于“哪个最准”,任何工具都会在冷门包和双协议包上出错,所以务必保留人工抽查环节。
第二天傍晚:把扫描结果翻译成能执行的动作
扫描结果出来,列表里出现了一个 GPL 系协议的直接依赖。这时候不该做的是立刻换库,也不该做的是装作没看见。正确的动作是回到开源协议常识本身,问三个问题:这个库是作为独立进程调用,还是被链接进我们的代码;我们分发的是源码还是二进制;这个依赖是否只在构建期使用、不进最终产物。
这三个问题决定了义务范围。构建期工具通常不构成分发义务;被静态链接进产物的库则要谨慎。判断拿不准时,指向可核对的来源:协议原文、项目官方 FAQ、以及 SPDX 的许可证标识列表。不要依赖二手博客的“一句话总结”,那些总结往往省略了版本差异——同一个协议的不同版本,义务可能完全不同。
我当时的处理是把该依赖替换为一个协议更宽松的等价实现,并在提交信息里写明替换原因。这不是唯一解,但对交付时间紧的项目,是成本可控的一种。另一个反例来自同组同事:他选择在 README 里加一句“本项目仅供学习”,以为能绕开义务。这种声明不改变授权关系,属于典型的无效操作。
第三天:把协议文件补回去,并让它不再丢
最后一步是补文件。根目录新建 LICENSE,内容用协议官方发布的原文,不改字、不加自己的补充条款;同步修正元数据字段,使两者一致;README 里写明项目名称、年份与版权人。如果项目包含多个子包,各自目录下也要有对应文件。
更关键的是防复发。我们在 CI 里加了一条检查:根目录缺少 LICENSE 或元数据字段与之不一致时,构建直接失败。检查脚本很短,逻辑就是读文件、比对字段、非零退出。另有一条约定:目录整理类提交必须单独成一次 PR,避免协议文件混在大量移动操作里被误删而看不出来。
回头看,整件事的转折点不是找到了多好的工具,而是在第一天下午停下来核对了清单。工具能省时间,但选择工具之前,先想清楚你要它回答哪个问题:是“我引用了谁”,还是“我分发时要履行什么”。前者靠依赖树,后者靠扫描器加人工判断。把这两件事混在一起,才是那天空白目录真正的来源。