HarmonyOS开发:开源协议与合规——开源项目治理
HarmonyOS开发:开源协议与合规——开源项目治理
📌 核心要点:开源协议不是摆设,选错协议或者忽视合规,轻则项目被下架,重则吃官司。搞清楚MIT、Apache、GPL的区别,是每个开发者的必修课。
背景与动机
你开发了一个HarmonyOS应用,用了十几个三方库。应用上线了,用户量起来了,突然有一天收到一封律师函——你用的某个库是GPL协议的,你的应用必须开源,但你没开源。
这不是吓唬人,这种事在开源圈子里真实发生过。
或者另一种情况:你写了一个很棒的HAR库想开源,但不知道该选什么协议。选MIT吧,怕别人拿去闭源赚钱;选GPL吧,怕别人嫌传染性太强不敢用;选Apache吧,又觉得太正式了。
开源协议这事,你不能凭感觉。每个协议都有明确的法律效力,选错了后果很严重。这篇文章,我把主流开源协议的核心区别、鸿蒙项目的协议选择、依赖合规检查方法讲清楚。
核心原理
主流开源协议对比
先上一张图,把几个最常见的协议放在传染性强弱的轴线上:
graph LR
A[无传染性] --> B[MIT]
B --> C[BSD]
C --> D[Apache 2.0]
D --> E[LGPL]
E --> F[GPL]
F --> G[AGPL]
A2[可闭源使用] --> B2[MIT ✅]
A2 --> C2[BSD ✅]
A2 --> D2[Apache ✅]
A2 --> E2[LGPL ⚠️ 有限制]
A2 --> F2[GPL ❌ 必须开源]
A2 --> G2[AGPL ❌ 网络服务也要开源]
classDef permissive fill:#4CAF50,stroke:#2E7D32,color:#fff
classDef weak fill:#FF9800,stroke:#E65100,color:#fff
classDef strong fill:#F44336,stroke:#C62828,color:#fff
class B,C,D permissive
class E weak
class F,G strong
协议核心条款详解
| 协议 | 能否闭源使用 | 是否需要开源衍生作品 | 专利授权 | 商标使用 | 典型项目 |
|---|---|---|---|---|---|
| MIT | ✅ 可以 | ❌ 不需要 | ❌ 无 | ❌ 无 | jQuery、Rails |
| BSD | ✅ 可以 | ❌ 不需要 | ❌ 无 | ❌ 无 | FreeBSD、Django |
| Apache 2.0 | ✅ 可以 | ❌ 不需要 | ✅ 有 | ❌ 无 | Android、Kubernetes |
| LGPL | ⚠️ 有条件 | ⚠️ 修改LGPL库本身需开源 | ❌ 无 | ❌ 无 | GNU C Library |
| GPL | ❌ 不行 | ✅ 必须开源 | ❌ 无 | ❌ 无 | Linux内核、GCC |
| AGPL | ❌ 不行 | ✅ 必须开源(含网络服务) | ❌ 无 | ❌ 无 | MongoDB(早期) |
传染性是什么?
"传染性"是理解开源协议最关键的概念:
- MIT/BSD/Apache:没有传染性。你用了这些协议的库,你的项目想开源就开源,想闭源就闭源,随便。
- LGPL:弱传染性。你动态链接LGPL库,你的项目可以闭源;但你修改了LGPL库的源码,修改部分必须开源。
- GPL:强传染性。你的项目只要包含了GPL代码(哪怕是链接),整个项目就必须以GPL协议开源。这就是所谓的"病毒式传播"。
- AGPL:最强传染性。即使你只是通过网络提供服务(不分发软件),也必须开源。
鸿蒙生态的协议偏好
OpenHarmony项目本身使用Apache 2.0协议。这不是随便选的,而是经过深思熟虑:
- Apache 2.0对商业友好:企业可以闭源使用,不用担心传染性。
- 专利授权条款:贡献者自动授予专利许可,避免专利纠纷。
- 生态兼容性:MIT/BSD/Apache的库可以自由组合,不会产生协议冲突。
graph TB
A[OpenHarmony 生态] --> B[Apache 2.0 主协议]
B --> C[系统核心代码]
B --> D[官方SDK与工具]
E[三方库] --> F[MIT - 轻量工具库]
E --> G[Apache 2.0 - 功能库]
E --> H[LGPL - 特定场景]
I[⚠️ 需要避免] --> J[GPL - 传染性风险]
I --> K[AGPL - 强传染性]
classDef main fill:#4CAF50,stroke:#2E7D32,color:#fff
classDef third fill:#2196F3,stroke:#1565C0,color:#fff
classDef warn fill:#F44336,stroke:#C62828,color:#fff
class A,B,C,D main
class E,F,G,H third
class I,J,K warn
代码实战
基础用法:依赖合规检查脚本
手动检查每个依赖的协议?几十个库你查到猴年马月。写个脚本自动扫描。
// LicenseChecker.ets - 依赖协议合规检查工具
import * as fs from 'fs';
import * as path from 'path';
// 协议风险等级
enum LicenseRisk {
SAFE = 'SAFE', // 可安全使用
CAUTION = 'CAUTION', // 需要注意
DANGEROUS = 'DANGEROUS', // 有风险
UNKNOWN = 'UNKNOWN', // 未识别
}
// 协议分类
const LICENSE_CATEGORIES: Record<string, LicenseRisk> = {
'MIT': LicenseRisk.SAFE,
'BSD-2-Clause': LicenseRisk.SAFE,
'BSD-3-Clause': LicenseRisk.SAFE,
'Apache-2.0': LicenseRisk.SAFE,
'ISC': LicenseRisk.SAFE,
'0BSD': LicenseRisk.SAFE,
'LGPL-2.1': LicenseRisk.CAUTION,
'LGPL-3.0': LicenseRisk.CAUTION,
'MPL-2.0': LicenseRisk.CAUTION,
'GPL-2.0': LicenseRisk.DANGEROUS,
'GPL-3.0': LicenseRisk.DANGEROUS,
'AGPL-3.0': LicenseRisk.DANGEROUS,
'SSPL': LicenseRisk.DANGEROUS,
};
// 检查结果
interface CheckResult {
packageName: string;
version: string;
license: string;
risk: LicenseRisk;
path: string;
}
export class LicenseChecker {
private projectRoot: string;
constructor(projectRoot: string) {
this.projectRoot = projectRoot;
}
// 执行完整检查
async check(): Promise<CheckResult[]> {
const results: CheckResult[] = [];
// 1. 检查oh-package.json5中的依赖
const ohPkgPath = path.join(this.projectRoot, 'oh-package.json5');
if (fs.existsSync(ohPkgPath)) {
const deps = this.parseDependencies(ohPkgPath);
for (const dep of deps) {
const result = await this.checkPackage(dep.name, dep.version);
results.push(result);
}
}
// 2. 检查子模块的依赖
const modules = this.findModules();
for (const mod of modules) {
const modPkgPath = path.join(mod, 'oh-package.json5');
if (fs.existsSync(modPkgPath)) {
const deps = this.parseDependencies(modPkgPath);
for (const dep of deps) {
const result = await this.checkPackage(dep.name, dep.version);
// 去重
if (!results.find(r => r.packageName === result.packageName)) {
results.push(result);
}
}
}
}
return results;
}
// 解析依赖声明
private parseDependencies(pkgPath: string): Array<{ name: string; version: string }> {
const content = fs.readFileSync(pkgPath, 'utf-8');
// 简单解析JSON5(生产环境建议用json5库)
const jsonStr = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
const pkg = JSON.parse(jsonStr);
const deps: Array<{ name: string; version: string }> = [];
const allDeps = {
...pkg.dependencies,
...pkg.devDependencies,
...pkg.peerDependencies,
};
for (const [name, version] of Object.entries(allDeps)) {
deps.push({ name, version: version as string });
}
return deps;
}
// 检查单个包的协议
private async checkPackage(name: string, version: string): Promise<CheckResult> {
// 在node_modules中查找包的LICENSE文件
const possiblePaths = [
path.join(this.projectRoot, 'node_modules', name, 'LICENSE'),
path.join(this.projectRoot, 'node_modules', name, 'license'),
path.join(this.projectRoot, 'node_modules', name, 'LICENSE.md'),
path.join(this.projectRoot, 'node_modules', name, 'LICENSE.txt'),
];
let license = 'UNKNOWN';
for (const p of possiblePaths) {
if (fs.existsSync(p)) {
license = this.detectLicense(fs.readFileSync(p, 'utf-8'));
break;
}
}
// 也检查oh-package.json5中的license字段
const pkgPath = path.join(this.projectRoot, 'node_modules', name, 'oh-package.json5');
if (fs.existsSync(pkgPath) && license === 'UNKNOWN') {
const content = fs.readFileSync(pkgPath, 'utf-8');
const jsonStr = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
try {
const pkg = JSON.parse(jsonStr);
if (pkg.license) license = pkg.license;
} catch { /* 忽略解析错误 */ }
}
return {
packageName: name,
version,
license,
risk: LICENSE_CATEGORIES[license] ?? LicenseRisk.UNKNOWN,
path: path.join('node_modules', name),
};
}
// 从LICENSE文件内容检测协议类型
private detectLicense(content: string): string {
const upper = content.toUpperCase();
if (upper.includes('MIT LICENSE')) return 'MIT';
if (upper.includes('APACHE LICENSE') && upper.includes('VERSION 2.0')) return 'Apache-2.0';
if (upper.includes('BSD 2-CLAUSE')) return 'BSD-2-Clause';
if (upper.includes('BSD 3-CLAUSE')) return 'BSD-3-Clause';
if (upper.includes('GNU GENERAL PUBLIC LICENSE') && upper.includes('VERSION 2')) return 'GPL-2.0';
if (upper.includes('GNU GENERAL PUBLIC LICENSE') && upper.includes('VERSION 3')) return 'GPL-3.0';
if (upper.includes('GNU LESSER GENERAL PUBLIC LICENSE')) return 'LGPL-3.0';
if (upper.includes('ISC LICENSE')) return 'ISC';
return 'UNKNOWN';
}
// 查找项目中的所有模块
private findModules(): string[] {
const modules: string[] = [];
const entriesPath = path.join(this.projectRoot, 'entry');
// 简单实现:查找包含oh-package.json5的目录
const scanDir = (dir: string) => {
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
if (item.isDirectory() && !item.name.startsWith('.') && item.name !== 'node_modules') {
const subDir = path.join(dir, item.name);
if (fs.existsSync(path.join(subDir, 'oh-package.json5'))) {
modules.push(subDir);
}
scanDir(subDir);
}
}
};
scanDir(this.projectRoot);
return modules;
}
// 生成检查报告
generateReport(results: CheckResult[]): string {
const lines: string[] = [];
lines.push('===== 开源协议合规检查报告 =====\n');
// 按风险等级分组
const safe = results.filter(r => r.risk === LicenseRisk.SAFE);
const caution = results.filter(r => r.risk === LicenseRisk.CAUTION);
const dangerous = results.filter(r => r.risk === LicenseRisk.DANGEROUS);
const unknown = results.filter(r => r.risk === LicenseRisk.UNKNOWN);
lines.push(`总计: ${results.length} 个依赖`);
lines.push(`✅ 安全: ${safe.length} 个`);
lines.push(`⚠️ 注意: ${caution.length} 个`);
lines.push(`❌ 危险: ${dangerous.length} 个`);
lines.push(`❓ 未知: ${unknown.length} 个\n`);
if (dangerous.length > 0) {
lines.push('--- ❌ 危险依赖(必须处理)---');
for (const r of dangerous) {
lines.push(` ${r.packageName}@${r.version} [${r.license}]`);
}
lines.push('');
}
if (caution.length > 0) {
lines.push('--- ⚠️ 需要注意的依赖 ---');
for (const r of caution) {
lines.push(` ${r.packageName}@${r.version} [${r.license}]`);
}
lines.push('');
}
if (unknown.length > 0) {
lines.push('--- ❓ 协议未识别的依赖 ---');
for (const r of unknown) {
lines.push(` ${r.packageName}@${r.version} - 请手动检查`);
}
lines.push('');
}
return lines.join('\n');
}
}
进阶用法:协议兼容性矩阵
不是所有协议都能自由组合。比如你用了GPL的库,你的项目就不能用MIT协议发布。写个工具检查协议兼容性。
// LicenseCompatibility.ets - 协议兼容性检查
// 协议兼容性矩阵:行是项目协议,列是依赖协议
// ✅ 兼容 ⚠️ 有条件 ❌ 不兼容
const COMPATIBILITY_MATRIX: Record<string, Record<string, string>> = {
'MIT': {
'MIT': '✅',
'BSD-2-Clause': '✅',
'BSD-3-Clause': '✅',
'Apache-2.0': '✅',
'LGPL-2.1': '✅',
'LGPL-3.0': '✅',
'GPL-2.0': '❌', // MIT项目不能包含GPL依赖
'GPL-3.0': '❌',
'AGPL-3.0': '❌',
},
'Apache-2.0': {
'MIT': '✅',
'BSD-2-Clause': '✅',
'BSD-3-Clause': '✅',
'Apache-2.0': '✅',
'LGPL-2.1': '✅',
'LGPL-3.0': '✅',
'GPL-2.0': '❌',
'GPL-3.0': '❌',
'AGPL-3.0': '❌',
},
'GPL-3.0': {
'MIT': '✅', // GPL项目可以用MIT依赖
'BSD-2-Clause': '✅',
'BSD-3-Clause': '✅',
'Apache-2.0': '✅',
'LGPL-2.1': '⚠️', // LGPL-2.1和GPL-3.0有兼容性问题
'LGPL-3.0': '✅',
'GPL-2.0': '⚠️', // GPL-2.0和GPL-3.0不完全兼容
'GPL-3.0': '✅',
'AGPL-3.0': '❌', // AGPL比GPL更严格
},
};
export class LicenseCompatibilityChecker {
// 检查项目协议与依赖协议的兼容性
checkCompatibility(
projectLicense: string,
dependencies: Array<{ name: string; license: string }>
): Array<{ name: string; license: string; compatible: string; suggestion: string }> {
const results: Array<{ name: string; license: string; compatible: string; suggestion: string }> = [];
const matrix = COMPATIBILITY_MATRIX[projectLicense];
if (!matrix) {
return dependencies.map(d => ({
name: d.name,
license: d.license,
compatible: '❓',
suggestion: `项目协议 ${projectLicense} 未在兼容性矩阵中,请手动检查`,
}));
}
for (const dep of dependencies) {
const compatible = matrix[dep.license] ?? '❓';
let suggestion = '';
switch (compatible) {
case '✅':
suggestion = '兼容,可正常使用';
break;
case '⚠️':
suggestion = '有兼容性风险,建议咨询法律意见';
break;
case '❌':
suggestion = '不兼容!必须替换此依赖或修改项目协议';
break;
default:
suggestion = '未识别的协议组合,请手动检查';
}
results.push({
name: dep.name,
license: dep.license,
compatible,
suggestion,
});
}
return results;
}
// 推荐项目协议
recommendLicense(
dependencies: Array<{ license: string }>,
isCommercial: boolean
): string[] {
const depLicenses = new Set(dependencies.map(d => d.license));
const hasGPL = [...depLicenses].some(l => l.startsWith('GPL') || l.startsWith('AGPL'));
const hasLGPL = [...depLicenses].some(l => l.startsWith('LGPL'));
const recommendations: string[] = [];
if (hasGPL) {
// 有GPL依赖,项目必须用GPL
recommendations.push('GPL-3.0(因包含GPL依赖,必须使用GPL协议)');
return recommendations;
}
if (isCommercial) {
// 商业项目,优先Apache 2.0
recommendations.push('Apache 2.0(商业友好,含专利授权条款)');
recommendations.push('MIT(最宽松,但无专利保护)');
} else {
// 开源项目
recommendations.push('Apache 2.0(与OpenHarmony生态一致)');
recommendations.push('MIT(简单宽松,适合工具库)');
}
if (hasLGPL) {
recommendations.push('⚠️ 注意:包含LGPL依赖,动态链接可闭源,修改LGPL库本身需开源');
}
return recommendations;
}
}
完整示例:自动化合规检查流水线
把协议检查集成到CI流程里,每次提交自动检查。
// CompliancePipeline.ets - 合规检查流水线
import { LicenseChecker, CheckResult } from './LicenseChecker';
import { LicenseCompatibilityChecker } from './LicenseCompatibility';
import * as fs from 'fs';
import * as path from 'path';
// 合规报告
interface ComplianceReport {
timestamp: string;
projectLicense: string;
totalDependencies: number;
safeCount: number;
cautionCount: number;
dangerousCount: number;
unknownCount: number;
issues: string[];
passed: boolean;
}
export class CompliancePipeline {
private projectRoot: string;
private projectLicense: string;
private checker: LicenseChecker;
private compatChecker: LicenseCompatibilityChecker;
constructor(projectRoot: string, projectLicense: string) {
this.projectRoot = projectRoot;
this.projectLicense = projectLicense;
this.checker = new LicenseChecker(projectRoot);
this.compatChecker = new LicenseCompatibilityChecker();
}
// 执行完整合规检查
async run(): Promise<ComplianceReport> {
console.info('开始开源协议合规检查...\n');
const issues: string[] = [];
// 阶段1:扫描所有依赖的协议
console.info('阶段1:扫描依赖协议...');
const checkResults = await this.checker.check();
console.info(` 发现 ${checkResults.length} 个依赖\n`);
// 阶段2:风险分类
const dangerous = checkResults.filter(r => r.risk === 'DANGEROUS');
const caution = checkResults.filter(r => r.risk === 'CAUTION');
const unknown = checkResults.filter(r => r.risk === 'UNKNOWN');
if (dangerous.length > 0) {
const msg = `发现 ${dangerous.length} 个危险依赖: ${dangerous.map(d => `${d.packageName}[${d.license}]`).join(', ')}`;
issues.push(msg);
console.error(` ❌ ${msg}`);
}
if (caution.length > 0) {
const msg = `发现 ${caution.length} 个需注意的依赖: ${caution.map(d => `${d.packageName}[${d.license}]`).join(', ')}`;
issues.push(msg);
console.warn(` ⚠️ ${msg}`);
}
// 阶段3:协议兼容性检查
console.info('\n阶段2:协议兼容性检查...');
const deps = checkResults.map(r => ({ name: r.packageName, license: r.license }));
const compatResults = this.compatChecker.checkCompatibility(this.projectLicense, deps);
const incompatibles = compatResults.filter(r => r.compatible === '❌');
if (incompatibles.length > 0) {
const msg = `发现 ${incompatibles.length} 个不兼容依赖: ${incompatibles.map(d => `${d.name}[${d.license}]`).join(', ')}`;
issues.push(msg);
console.error(` ❌ ${msg}`);
}
// 阶段4:检查NOTICE文件
console.info('\n阶段3:检查NOTICE声明...');
const noticeCheck = this.checkNoticeFile();
if (!noticeCheck.passed) {
issues.push(noticeCheck.message);
console.warn(` ⚠️ ${noticeCheck.message}`);
}
// 阶段5:检查源文件头
console.info('\n阶段4:检查源文件协议头...');
const headerCheck = this.checkSourceHeaders();
if (headerCheck.missingCount > 0) {
const msg = `${headerCheck.missingCount} 个源文件缺少协议头声明`;
issues.push(msg);
console.warn(` ⚠️ ${msg}`);
}
// 生成报告
const report: ComplianceReport = {
timestamp: new Date().toISOString(),
projectLicense: this.projectLicense,
totalDependencies: checkResults.length,
safeCount: checkResults.filter(r => r.risk === 'SAFE').length,
cautionCount: caution.length,
dangerousCount: dangerous.length,
unknownCount: unknown.length,
issues,
passed: dangerous.length === 0 && incompatibles.length === 0,
};
// 输出详细报告
console.info('\n' + this.checker.generateReport(checkResults));
this.saveReport(report);
return report;
}
// 检查NOTICE文件
private checkNoticeFile(): { passed: boolean; message: string } {
const noticePath = path.join(this.projectRoot, 'NOTICE');
if (!fs.existsSync(noticePath)) {
return {
passed: false,
message: '缺少NOTICE文件(Apache 2.0项目必须包含NOTICE文件)',
};
}
const content = fs.readFileSync(noticePath, 'utf-8');
if (content.trim().length < 10) {
return {
passed: false,
message: 'NOTICE文件内容为空或过短,请补充第三方依赖声明',
};
}
return { passed: true, message: 'NOTICE文件检查通过' };
}
// 检查源文件协议头
private checkSourceHeaders(): { total: number; missingCount: number } {
let total = 0;
let missingCount = 0;
const srcDir = path.join(this.projectRoot, 'entry', 'src', 'main', 'ets');
if (!fs.existsSync(srcDir)) return { total: 0, missingCount: 0 };
const scanDir = (dir: string) => {
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
const fullPath = path.join(dir, item.name);
if (item.isDirectory()) {
scanDir(fullPath);
} else if (item.name.endsWith('.ets') || item.name.endsWith('.ts')) {
total++;
const content = fs.readFileSync(fullPath, 'utf-8');
if (!content.includes('Copyright') && !content.includes('SPDX-License-Identifier')) {
missingCount++;
}
}
}
};
scanDir(srcDir);
return { total, missingCount };
}
// 保存报告
private saveReport(report: ComplianceReport): void {
const reportPath = path.join(this.projectRoot, 'compliance-report.json');
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
console.info(`\n合规报告已保存到: ${reportPath}`);
}
}
踩坑与注意事项
坑1:GPL的"隐式链接"问题
你没用GPL的代码,但你的应用通过IPC调用了GPL的服务。这算不算"链接"?
法律上,这取决于IPC的调用方式。如果只是进程间通信(比如启动一个GPL的命令行工具),通常不算链接。但如果是深度集成(比如共享内存、加载GPL的动态库),就有风险。
建议:拿不准的时候,别用。找替代库。
坑2:LGPL的动态链接要求
LGPL允许你闭源使用,但前提是你必须以动态链接的方式使用。HarmonyOS的HAR是静态链接的,这意味着你把LGPL的代码编译进了你的HAR包里——这违反了LGPL的要求。
解法:如果必须用LGPL库,用HSP(动态共享库)代替HAR。HSP是运行时加载的,满足LGPL的动态链接要求。
坑3:协议版本混淆
GPL-2.0和GPL-3.0是两个不同的协议,它们之间甚至不完全兼容。你看到"GPL"就以为都一样,这是大错。
关键区别:
- GPL-3.0增加了专利授权条款
- GPL-3.0增加了反DRM条款
- GPL-2.0和GPL-3.0的混合使用有兼容性问题
坑4:忘记保留协议声明
你用了MIT库的代码,但在自己的项目里删掉了MIT的版权声明。MIT协议虽然宽松,但要求保留版权声明和协议文本。
解法:在项目的NOTICE文件中列出所有三方库的协议声明。
坑5:双协议库的选择
有些库同时提供两种协议(比如MySQL有GPL和商业协议)。你选了GPL版本,结果你的项目也被传染了。
解法:如果项目是商业闭源的,购买商业授权。如果项目是开源的,确认你的协议与GPL兼容。
坑6:自己写协议
有些开发者觉得现有协议都不合适,自己写了一个。这在法律上是有效的,但没人看得懂你的协议,别人不敢用你的库。
建议:除非你是大公司有专业法务团队,否则别自己写协议。从MIT、Apache 2.0、GPL中选一个,比你自己写的靠谱一万倍。
HarmonyOS 6适配说明
HarmonyOS 6在开源合规方面的变化:
-
Ohpm合规扫描:Ohpm仓库新增了自动合规扫描,上传的HAR包会自动检测依赖协议。如果包含GPL依赖,会发出警告。
-
SPDX标识:官方推荐在源文件头使用SPDX标识代替传统版权声明:
// SPDX-License-Identifier: Apache-2.0
-
合规报告生成:DevEco Studio 5.x新增了合规报告生成功能,一键输出项目的协议合规报告。
-
依赖白名单:企业可以在
oh-package.json5中配置协议白名单,不在白名单内的依赖无法安装:
{
"licenseConfig": {
"allowedLicenses": ["MIT", "Apache-2.0", "BSD-3-Clause"]
}
}
总结
开源协议不是小事。你以为随便选一个就行,等到出问题就晚了。花半天时间搞清楚协议的区别,比花半年时间处理法律纠纷划算得多。
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐ 法律术语有点绕,但核心概念不多 |
| 使用频率 | ⭐⭐⭐⭐ 每次引入新依赖都要考虑 |
| 重要程度 | ⭐⭐⭐⭐⭐ 出问题就是大问题 |
几个关键建议:
- 默认Apache 2.0:如果你不知道选什么,选Apache 2.0。它是OpenHarmony生态的标准协议,商业友好,有专利保护。
- 远离GPL:除非你的项目本身就是开源的,否则别碰GPL协议的库。找替代品。
- 自动化检查:把协议检查集成到CI里,每次提交自动扫描。别指望手动检查。
- 保留声明:用别人的代码,保留版权声明。这是底线。
- 点赞
- 收藏
- 关注作者
评论(0)