本文为 AtomGit 码动四季·开源同行征稿活动参与文章
难度等级:⭐⭐(2/5)|前置知识:无(概念篇,条款原文均附官方链接)
01 号文章那次开源化体检里,CI 打包用了半天,CONTRIBUTING 用了一小时,LICENSE 卡了我大半天。反复推翻自己好几次,返工的根因是三个概念从一开始就理解错了。
不是查资料难——GPL-3.0 全文才 674 行,MIT 只有 23 行。难的是每次以为选好了,就冒出一个新的具体问题:Tauri 是 MIT/Apache 双许可,我打包出来的 exe 算谁的?git2 绑定的是 Apache/MIT 双许可,我能用吗?别人 fork 我的代码做个商业版扫描工具,我管得着吗?这些问题,抄来的 LICENSE 回答不了。
这篇就把我这个 Rust 项目的决策过程完整摊开:怎么把一个模糊的"选个开源协议"问题,拆成四个可回答的具体问题,以及最后为什么落在了 Apache-2.0 上。给后面要开源的读者省下大半天——按本文的决策路径,第二个仓库 10 分钟就能出结论。
本文要点:四问决策法 · 许可证宽严光谱 · 传染性/专利授权/无许可证三个概念正解 · 依赖审计两条扫描命令 · CLA 与 DCO 选型 · 3 个真实踩坑
适用版本:Apache-2.0 / GPL-3.0 / MIT / DCO 1.1(均为 2026 年现行版本,条款以官方原文为准)
一、先把问题拆对:选许可证其实是回答四个问题
我卡住的根源,是一直试图直接回答"哪个许可证好"——这是个无法回答的问题,因为"好"取决于你要什么。真正可回答的是下面四个问题,答案组合起来,许可证基本就自动浮出来了:
我的四个答案分别是:接受他人闭源使用(工具类代码,传播比控制重要);在意专利条款( Rust 生态里专利风险虽低,但条款白拿不要钱);仓库主体是软件不是内容;单人维护、个人项目。这组答案直接指向 Apache-2.0——后面第三节展开。
二、前置认知:宽严光谱与三个被说错的概念
2.1 宽严光谱:许可证不是散点,是一条连续的光谱
从左到右,你保留的权利越少、使用者拿到的自由越多;反过来越往右,"衍生品必须同协议开源"的义务越重。所有主流许可证都能在这条光谱上找到位置,选型就是在光谱上选一个和你态度匹配的点。
2.2 三个高频概念的正解
传染性(copyleft):GPL 的"传染"有明确触发条件——分发(distribute)。你用 GPL 库写了内部系统自己跑,不分发、不提供网络服务,不触发开源义务;一旦把软件分发给别人(卖、送、发包),整个衍生作品必须以 GPL 开源。AGPL 多堵了一个"网络服务"的洞:SaaS 化提供也视同分发。网上流传的"碰了 GPL 代码就完蛋",绝大多数场景是把"分发"这个前提弄丢了。
专利授权:这是 Apache-2.0 独有的显式条款——贡献者授予你专利使用权,并且有专利报复条款(你发起专利诉讼则授权终止)。MIT/GPL 默认没有专利条款,理论上存在"代码开源但专利暗雷"的风险敞口。对企业级项目,这一条常常是选 Apache-2.0 的决定性理由;对个人项目,它是"条款白拿不要钱"的增量保障。
"无许可证"的真实含义:没有 LICENSE 文件的仓库,默认保留所有权利——你有权看,无权复用。很多人以为"公开在 GitHub 上 = 随便用",这是错的,而且是最常见的合规事故来源。
2.3 流传最广的四个错误说法
| 错误说法 | 实际情况 |
|---|---|
| “用了 GPL 的库,我的整个项目必须开源” | 只有分发(或 AGPL 的网络服务)才触发;不分发不受影响 |
| “MIT 协议的代码可以随便用” | 须保留版权与许可声明;且不含专利授权,商标也不在授权范围 |
| “开源 = 放弃版权” | 版权始终在你手里,许可证只是授权他人有限使用 |
| “依赖是 MIT/Apache 双许可就随便选一个挂” | 你分发的是衍生作品,声明必须覆盖全部依赖的许可证义务,逐个核对 |
三、本仓库的真实决策:为什么是 Apache-2.0
3.1 决策过程复盘
我的仓库构成:Rust 检查器内核(约 3700 行,依赖 git2/rayon/serde)、Tauri 2 桌面壳 + Vue 3 前端(约 2600 行,依赖 element-plus/pinia/vue)、CI 打包脚本。逐个问题的推理过程:
问题 1(商用态度):这是一个通用开发工具,我希望它被尽量多的人用——包括被公司拿去内部用、被 fork 出改进版。限制商用等于亲手压缩传播面。所以答案是:接受闭源使用。这一条直接排除了 GPL 系和 NC 系,落点在宽松光谱。
问题 2(专利):宽松光谱里剩 MIT 和 Apache-2.0。仓库虽然没有值得专利保护的资产,但 Apache-2.0 的专利授权条款是"防下游"的:万一未来有贡献者贡献了涉及专利的实现,显式授权条款能避免"贡献者事后用专利讹用户"的经典纠纷。条款白拿不要钱,选它。
问题 3(单一客体):和文档仓库不同,这个仓库客体单一——全是软件代码,一个许可证罩得住,不需要双许可。唯一的"内容"是 README 和 docs,我按软件项目惯例统一归入 Apache-2.0(docs 里如果未来出现成规模的技术文章,再单独声明)。
最终落点:
| 客体 | 许可证 | 理由 |
|---|---|---|
| 全部源码(crates/、ui/、CI 脚本) | Apache-2.0 | 宽松 + 显式专利授权;与 Tauri 生态主流协议一致 |
| README 与 docs 文档 | Apache-2.0 | 软件项目惯例,随仓库统一声明 |
这个落点让我能具体地回答开头那些问题:公司拿我的工具内部使用——允许;fork 出商业版——允许(需保留版权声明与 LICENSE,修改处按 Apache-2.0 标注);有人拿我的代码申请专利反制用户——Apache-2.0 的专利授权与报复条款兜底;把我的工具改名当成自己的作品发布且不保留声明——不允许,这是违约。
顺带核对过依赖侧的义务:Tauri 是 MIT/Apache 双许可(我按下游惯例可任选口径),git2/git2-rs 是 MIT/Apache 双许可,前端 vue/element-plus 均为 MIT——宽松依赖树,Apache-2.0 的声明罩得住,无 copyleft 污染。这个核对我的做法是把cargo license和license-checker的输出过一遍(见第四节)。
3.2 单许可的声明方式
单许可比双许可简单,但声明位置依然有讲究:LICENSE 是法律文件的锚点,README 只做导航。我的做法是根目录放 LICENSE 全文,README 加一段声明,另外在Cargo.toml里同步元数据:
LICENSE # Apache-2.0 全文 README.md 中的声明段: ## 许可证 本项目基于 Apache License 2.0 开源发布。 Cargo.toml(workspace.package): license = "Apache-2.0"Cargo.toml里的license字段不是可选项——crates.io 和各类许可证识别工具都以它为准,README 里写了但元数据没写,工具照样识别不出。
四、依赖审计:你的仓库里藏着别人的协议
选好自己的许可证只完成了一半。另一半是搞清楚:你引用的东西,允许你这样引用吗?
4.1 扫依赖树
Rust 和前端两侧各有一条现成的扫描命令:
# Rust 依赖的许可证清单(cargo-edit 提供)cargoinstallcargo-license&&cargolicense--workspace# npm 依赖的许可证清单npx license-checker--summary实际跑cargo license的输出长这样——每个依赖的名称、版本、许可证一目了然:
图 4:
cargo license --workspace实际扫描输出,重点关注 License 列的宽松/严格属性
扫描结果里要盯的不是"有没有 GPL"——前面说过,不分发就不触发——而是两个真正的风险位:
- 你计划以宽松协议(MIT/Apache)开源的代码,直接链接/改编了 GPL 代码——你的 Apache 声明罩不住这段衍生代码,分发时违反的是 GPL。Rust 生态整体宽松(git2/rayon/serde/thiserror 全部 MIT 或 Apache 双许可),我的依赖树扫描结果全绿;前端 element-plus/pinia/vue 也全部 MIT。
- AGPL 组件混进了服务端链路——只要你的服务对外提供网络访问,开源义务就触发,和是否分发无关。本项目是纯本地桌面应用,无服务端链路,这一位天然安全——但同类项目引入任何联网同步功能前,必须重审这一位。
我的依赖树里这次没扫出污染,但上一轮内容仓库审计时真实扫出过一个:早期文章里复用的一段工具函数,溯源后发现改编自一个 GPL-3.0 的开源项目。处理方式二选一:重写这段函数(我选了这个,函数不长),或者这段代码单独声明 GPL 并从宽松协议范围中排除。拖着不处理是最差的选项——合规债务只会长利息。
4.2 图标与素材:软件仓库特有的审计位
桌面应用有一类内容仓库没有的客体:图标、演示 GIF、UI 素材。我的三条原则:
- 自绘图标与截图——版权归自己,随仓库协议分发,无额外义务;
- 引用第三方图标库——查清它的协议(多数图标库是 MIT/CC0,也有要求署名的 CC-BY),按其要求在 README 致谢段标注;
- 演示 GIF 里若出现第三方软件界面——标注来源与版本,避免"代言"误会。
五、CLA 与 DCO:贡献者的授权方式
仓库开放 PR 之后,"外部贡献的代码我用什么权利"就有了现实意义。两种机制:
| 维度 | CLA(贡献者许可协议) | DCO(开发者原始声明) |
|---|---|---|
| 形式 | 签一份法律文件,首次贡献前完成 | 每次 commit 加一行Signed-off-by |
| 给维护者的权利 | 宽泛授权(可再许可、可变更协议) | 确认"这是我写的,我有权按仓库协议贡献" |
| 贡献者成本 | 高(要读合同、可能走法务) | 极低(git commit -s顺手的事) |
| 典型使用者 | 大型基金会项目、商业开源 | Linux 内核及多数个人/社区项目 |
个人项目的选择几乎没有悬念:DCO。理由是 CLA 的核心价值——给维护者"将来可以变更许可证、可以再商业许可"的灵活性——对一个不打算变更协议的个人仓库没有意义,而它给贡献者增加的摩擦是实打实的。CLA 适合的场景是公司主导、可能商业转化的项目。
DCO 的启用成本也极低:CONTRIBUTING.md 里声明"提交即代表遵循 DCO 1.1",然后 CI 里加一道 sign-off 检查:
# CI 中校验所有 commit 均含 Signed-off-bygitlog--format="%b"origin/main..HEAD|grep-q"Signed-off-by"\||{echo"FAIL: commit 缺少 Signed-off-by(git commit -s 即可)";exit1;}六、三个真实踩坑
1. 先写代码后想协议,依赖审计被迫返工
现象:开源决策时回溯依赖树,发现有个阶段为图方便引入过一个实验性 crate,它的传递依赖里有 GPLv2 项——而彼时代码已经引用了它的 API。
根因:开发期没有"引入依赖先查协议"的习惯,合规债务被推迟到开源决策点集中爆发。
解决:换用宽松协议的等价 crate 并重写适配层;此后 CONTRIBUTING.md 明确"新增依赖必须附带许可证核对"。
教训:引入合规是编码时一分钟的事,事后换依赖是半天的事——这笔成本只会涨不会跌。
2. 把"免费用"等同于"随便用"
现象:选型前默认 MIT 最"无负担",细读条款才发现版权声明必须保留、作者不承担担保责任、商标不在授权范围。
根因:把"宽松"误读为"没有义务",只看了传播自由,没看约束条款。
解决:逐条读完主流许可证原文再决策,关键义务写进 README 的声明段。
教训:"不得用作者名字做宣传"正是 BSD-3 存在的理由——许可证的每个差异条款背后都有真实场景,这些不是废话。
3. 元数据与 LICENSE 文件不同步
现象:LICENSE 文件放好了,但Cargo.toml的license字段是初始模板里的占位值——许可证识别工具读出来的是占位协议,crates.io 侧的元数据校验也会拒绝发布。
根因:以为"声明"就是放一个 LICENSE 文件,忽略了包管理器生态有自己的元数据校验链。
解决:把"三处一致"写进发布检查单:LICENSE 文件、README 声明段、Cargo.toml/package.json的 license 字段,三处必须指向同一协议。
教训:每个包管理生态都有自己的许可证元数据位,声明要覆盖全部锚点——只改一处等于没改。
七、效果与边界
| 指标 | 决策前 | 决策后 |
|---|---|---|
| 选型决策耗时 | 大半天(反复摇摆) | 复用本决策树,第二个项目 10 分钟出结论 |
| 依赖许可证盲区 | 全部未知 | cargo license+license-checker全量扫描,宽松依赖树确认无污染 |
| 转载/商用问题 | 无从回答 | 声明段一句话可查 |
| 贡献授权 | 未定义 | DCO 1.1,CI 强制校验 |
适用边界:本文的决策样本是通用工具型个人软件仓库。两类场景请另行评估:内容为主的仓库(文章、文档、知识库)应当拆"内容 + 代码"双许可,CC 系列条款与本文路径不同;企业代码开源的合规权重完全不同——专利风险、竞业与商业秘密审查、CLA(甚至贡献者协议必须有法务参与)会成为主问题。请把本文当"个人工具项目参考",不要当模板。
八、总结
回头看我卡住的大半天,问题出在一开始就把"选协议"当成一道选择题来做。拆成四个具体问题之后,它其实是一道填空题:商用态度、专利诉求、copyleft 底线、客体构成,四个答案一填,协议自己就浮出来了。
几个我付过学费的认知:传染性以"分发"为触发条件,不弄清这个前提,GPL 恐惧和 GPL 大意都会出错;工具类代码优先选带专利授权的 Apache-2.0,条款白拿不要钱;依赖审计要覆盖 Rust/npm 两个生态的元数据锚点,LICENSE 文件、README、包元数据三处一致才算声明完成;个人项目用 DCO 不用 CLA,给贡献者的摩擦降到最低。
最后的决策清单,拿去直接用:
- 回答四个问题:商用态度 / 专利诉求 / copyleft 底线 / 仓库客体构成
- 在宽严光谱上落点;内容为主的仓库拆"内容 + 代码"双许可
- 扫描依赖(
cargo license/license-checker),处理宽松代码链 GPL 和 AGPL 混入两个风险位 - 图标、GIF 等素材查清来源协议,按要求致谢
- 个人项目配 DCO 1.1,CI 加 sign-off 校验
- LICENSE 文件、README 声明段、包管理器元数据三处一致
九、常见问题
Q1:Tauri 这类 MIT/Apache 双许可的框架,我分发衍生作品时要遵守哪个?
双许可的意思是你可以任选其一作为自己的合规口径——两个都满足义务即可(保留版权声明、附许可证副本)。我统一按 Apache-2.0 口径管理:LICENSE 文件里除了自己项目的声明,保留一份依赖协议清单(cargo license 输出),这样任何人审查时都能一次性看到整棵树的授权链。
Q2:MIT 和 Apache-2.0 到底怎么选?
一句话:个人小工具选 MIT,企业级或可能有专利纠纷的选 Apache-2.0。差别就两点——Apache-2.0 有显式专利授权 + 报复条款(约多 50 行法律文本),MIT 更短更没有心智负担。本文仓库选 Apache-2.0 的核心理由是"防下游专利纠纷"的那份增量保障,以及与 Tauri 生态主流口径一致。
Q3:同一仓库能放多个许可证吗?会不会冲突?
可以,前提是客体不重叠。冲突只发生在"同一个文件同时被两个协议声明"。如果要拆,按目录划界并写清楚对照表。有一个实操细节:仓库根目录的README.md本身算内容还是配置?软件仓库的惯例是随仓库主协议走(它本质是软件的使用说明),内容仓库才需要把 README 归入内容协议——两种做法都成立,关键是写明。
你的仓库现在挂的是什么许可证?如果答案是"没挂"或者"不知道",现在就去补——这可能是开源合规里性价比最高的十分钟。
真实性声明
本文决策过程来自 repo-balance 仓库 2026-09 开源改造的真实选型记录;依赖审计结果来自
cargo license --workspace与license-checker实际扫描输出(git2/rayon/serde/thiserror/vue/element-plus 等依赖协议均已核对);文中 GPL 污染案例为此前仓库审计中的真实事件,涉事代码已重写。许可证条款表述以各协议官方原文为准,本文不构成法律意见,商业决策请咨询专业法务。案例仓库:atomgit.com/dickeryang/repo-balance。
参考资源
- choosealicense.com(GitHub 官方许可证指南)
- GNU GPL-3.0 全文
- Apache-2.0 全文
- DCO 1.1 声明原文
- Tauri 许可证说明
专栏导航
- 上一篇:给一个 Rust 桌面应用做开源化体检
- 下一篇:03 从 commit 到发版全自动:semantic-release 发布流水线
如果本文对你有帮助,欢迎点赞、收藏、转发。有任何问题或建议,请在评论区留言交流。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。