HarmonyOS开发:开源协议与合规——开源项目治理

举报
Jack20 发表于 2026/06/28 21:19:27 2026/06/28
【摘要】 HarmonyOS开发:开源协议与合规——开源项目治理📌 核心要点:开源协议不是摆设,选错协议或者忽视合规,轻则项目被下架,重则吃官司。搞清楚MIT、Apache、GPL的区别,是每个开发者的必修课。 背景与动机你开发了一个HarmonyOS应用,用了十几个三方库。应用上线了,用户量起来了,突然有一天收到一封律师函——你用的某个库是GPL协议的,你的应用必须开源,但你没开源。这不是吓唬人...

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协议。这不是随便选的,而是经过深思熟虑:

  1. Apache 2.0对商业友好:企业可以闭源使用,不用担心传染性。
  2. 专利授权条款:贡献者自动授予专利许可,避免专利纠纷。
  3. 生态兼容性: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在开源合规方面的变化:

  1. Ohpm合规扫描:Ohpm仓库新增了自动合规扫描,上传的HAR包会自动检测依赖协议。如果包含GPL依赖,会发出警告。

  2. SPDX标识:官方推荐在源文件头使用SPDX标识代替传统版权声明:

// SPDX-License-Identifier: Apache-2.0
  1. 合规报告生成:DevEco Studio 5.x新增了合规报告生成功能,一键输出项目的协议合规报告。

  2. 依赖白名单:企业可以在oh-package.json5中配置协议白名单,不在白名单内的依赖无法安装:

{
  "licenseConfig": {
    "allowedLicenses": ["MIT", "Apache-2.0", "BSD-3-Clause"]
  }
}

总结

开源协议不是小事。你以为随便选一个就行,等到出问题就晚了。花半天时间搞清楚协议的区别,比花半年时间处理法律纠纷划算得多。

维度 评价
学习难度 ⭐⭐ 法律术语有点绕,但核心概念不多
使用频率 ⭐⭐⭐⭐ 每次引入新依赖都要考虑
重要程度 ⭐⭐⭐⭐⭐ 出问题就是大问题

几个关键建议:

  • 默认Apache 2.0:如果你不知道选什么,选Apache 2.0。它是OpenHarmony生态的标准协议,商业友好,有专利保护。
  • 远离GPL:除非你的项目本身就是开源的,否则别碰GPL协议的库。找替代品。
  • 自动化检查:把协议检查集成到CI里,每次提交自动扫描。别指望手动检查。
  • 保留声明:用别人的代码,保留版权声明。这是底线。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。