36 处理许可证

如概述与概念手册的 “许可证” 所述,开源项目向公众开放,采用的许可证体系也各不相同。本节介绍 OpenEmbedded Build System 跟踪许可证文本变化的机制,以及在项目 生命周期中维护开源许可证合规的方法,并说明如何启用默认禁用的商业许可配方。

36 跟踪许可证变化

上游项目的许可证可能变化。为防止忽略这些变化,LIC_FILES_CHKSUM 跟踪许可证文本的校验和。配置步骤结束时会验证校验和,不匹配则构建失败。

36 指定 LIC_FILES_CHKSUM 变量

LIC_FILES_CHKSUM 保存配方源码中许可证文本的校验和,例如:

LIC_FILES_CHKSUM = "file://COPYING;md5=xxxx \
                    file://licfile1.txt;beginline=5;endline=29;md5=yyyy \
                    file://licfile2.txt;endline=50;md5=zzzz \
                    ..."

备注

  • "beginline" 和 "endline" 行号从 1 开始,且包含起止行。例如上例 licfile1.txt 包含第 5 至第 29 行。

  • 许可证检查失败时,QA 消息会显示选中的许可证文本,可据此确定所需 文本的准确起止位置。

查找 LIC_FILES_CHKSUM 列出的文件时,构建系统默认以 S 为目录。上例使用的就是默认目录。

再看以下示例:

LIC_FILES_CHKSUM = "file://src/ls.c;beginline=5;endline=16;\
                                    md5=bb14ed3c4cda583abc85401304b5cd4e"
LIC_FILES_CHKSUM = "file://${UNPACKDIR}/license.html;md5=5c94767cedb5d6987c902ac850ded2c6"

第一条语句定位 ${S}/src/ls.c,使用第 5 至第 16 行作为许可证文本。 第二条引用 UNPACKDIR 中的文件。

除非 LICENSE 设为 "CLOSED",否则所有配方都必须定义 LIC_FILES_CHKSUM。

36 语法说明

LIC_FILES_CHKSUM 列出所有包含源码许可证文本的重要文件。可以对 整个文件计算校验和,也可以用 "beginline" 和 "endline" 指定起止行, 只检查某个片段。后者适用于包含许可证头部的源文件、README 等。省略 "beginline" 时从第一行开始,省略 "endline" 时到文件最后一行结束。

"md5" 参数保存许可证文本的 MD5 校验和。文本有任何变化都会造成不匹配, 触发构建失败并通知开发者,以便审阅和处理。发生不匹配时,正确 MD5 值也会 写入构建日志,可方便地复制到配方。

LIC_FILES_CHKSUM 可以指定任意数量的文件。不过,一般每个项目只需 少量条目。许多项目用 "COPYING" 保存所有源码的许可证信息,只要该文件保持 更新,跟踪它即可。

备注

  • "md5" 参数为空或无效时,BitBake 会报告不匹配并显示正确值, 同时写入构建日志。

  • 如果整个文件仅包含许可证文本,无需使用 "beginline" 和 "endline"。

36 启用商业许可配方

OpenEmbedded 构建系统默认禁用具有商业或其他特殊许可要求的组件。这些要求 通过各配方中的 LICENSE_FLAGS 定义。例如, OpenEmbedded-Core (OE-Core) 的 meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-ugly 包含:

LICENSE_FLAGS = "commercial"

下面的例子稍复杂,变量展开后显式包含配方名称和版本:

LICENSE_FLAGS = "license_${PN}_${PV}"

可通过 LICENSE_FLAGS_DETAILS 的变量标志提供特定许可证的详细信息:

LICENSE_FLAGS_DETAILS[my-eula-license] = "For further details, see https://example.com/eula."

设置后,如果尚未接受许可证,这些信息将显示给用户。

要启用受 LICENSE_FLAGS 限制的组件并将其加入镜像,全局 LICENSE_FLAGS_ACCEPTED 必须有匹配条目。此变量通常定义在 build/conf/local.conf。例如,要启用 OpenEmbedded-Core (OE-Core) 的 meta/recipes-multimedia/gstreamer/gstreamer1.0-plugins-ugly,可以添加 "commercial_gst-plugins-ugly" 或更宽泛的 "commercial"。完整匹配规则见 “许可证标志匹配”。例如:

LICENSE_FLAGS_ACCEPTED = "commercial_gst-plugins-ugly"

同样,若另一个配方设置 LICENSE_FLAGS = "license_${PN}_${PV}",文件名 为 emgd_1.10.bb,以下配置将同时启用该软件包和 gst-plugins-ugly:

LICENSE_FLAGS_ACCEPTED = "commercial_gst-plugins-ugly license_emgd_1.10"

不必为每个软件包指定完整标志。可以仅写许可证字符串第一个或前几个下划线 之前的部分,匹配所有以该部分开头的标志。例如,以下值既匹配上述两包,也 匹配其他以 "commercial" 或 "license" 开头的包:

LICENSE_FLAGS_ACCEPTED = "commercial license"

36 许可证标志匹配

许可证标志匹配控制哪些配方参与构建。系统将配方的 LICENSE_FLAGS 字符串与 LICENSE_FLAGS_ACCEPTED 比较,匹配则纳入构建,不匹配则 排除配方。

匹配规则总体简单,但理解以下概念有助于正确使用。

配方标志与 LICENSE_FLAGS_ACCEPTED 比较之前,系统会追加展开后的 _${PN},使 LICENSE_FLAGS 值专属于该配方。例如,"foo" 配方 设置 LICENSE_FLAGS = "commercial",最终得到 "commercial_foo"。 完整匹配时,该字符串需出现在 LICENSE_FLAGS_ACCEPTED 中。

合理组合 LICENSE_FLAGS 和 LICENSE_FLAGS_ACCEPTED,可以 灵活地按许可证纳入或排除配方。例如,在接受列表中使用标志前缀可以扩大 匹配范围。

备注

使用部分字符串时,应以展开后字符串中的下划线为分界,例如 usethispart_1.3 和 usethispart_1.4 的共同前缀。

例如,LICENSE_FLAGS_ACCEPTED 只指定 "commercial",就会匹配所有 以其开头的展开后 LICENSE_FLAGS,如 "commercial_foo" 和 "commercial_bar"。这两个值由假设的 "foo"、"bar" 配方自动生成,配方均设置:

LICENSE_FLAGS = "commercial"

因此,可以逐一列出所有许可标志,只允许指定配方进入镜像;也可以使用前缀 扩大匹配范围,允许一组配方。

即使 LICENSE_FLAGS 已含 _${PN},此机制仍然有效。例如,系统 会把 "commercial_1.2_foo" 扩展成 "commercial_1.2_foo_foo",按预期匹配 LICENSE_FLAGS_ACCEPTED 中的 "commercial" 或 "commercial_1.2_foo"。

其他情况包括:

  • "foo" 配方可以使用带版本字符串 "commercial_foo_1.2",系统将其展开 为 "commercial_foo_1.2_foo"。接受列表中的 "commercial" 会匹配它, 也会匹配所有其他以 "commercial" 开头的标志。

  • 同样情况下,在 LICENSE_FLAGS_ACCEPTED 中加入 "commercial_foo", 不仅匹配 "commercial_foo_1.2",也匹配所有具有此前缀的版本。

  • 也可以在 LICENSE_FLAGS_ACCEPTED 中同时写包名和版本,例如 "commercial_foo_1.2",精确匹配带版本的配方。

36 在产品生命周期中维护开源许可证合规

使用开源软件的开发组织,需要考虑如何在产品生命周期内遵守各种许可证。 本节不提供法律意见,也不覆盖全部情况,但介绍可用于协助软件发布合规的方法。

Yocto Project 跟踪数百种开源许可证,逐一了解要求并不容易。可以先从主要 FLOSS 许可证通常涉及的三个方面着手:

  • 提供源码。

  • 提供软件许可证文本。

  • 提供编译脚本及源码修改。

此外还有超出这三方面和本节方法的要求,例如源码的分发机制。

各组织发布软件的方式不同,履行义务的方法也可能不同。这里介绍两种:

  • 利用 OpenEmbedded 提供源码、许可证列表、编译脚本和源码修改的能力。

    本节后续介绍满足上述三项要求的受支持方法。

  • 生成 软件物料清单 (SBoM),见 “创建软件物料清单”。 生成的 SPDX 输出既可用于许可证合规,暂不包括共享构建系统和层 源码,也包含用于漏洞评估的组件版本和补丁信息。

无论选择哪种方法,发布镜像、源码和构建系统前,都应审计全部产物,确保完整。

备注

创建镜像时,Yocto Project 会生成许可证清单,位于 ${DEPLOY_DIR}/licenses/${SSTATE_PKGARCH}/<image-name>-<machine>.rootfs-<datestamp>/, 可辅助审计。

36 提供源码

合规工作应在生成最终镜像之前开始。首先考虑大多数合规团队最关注的源码 提供要求,Yocto Project 有几种实现方式。

最简单的方法之一是提供构建使用的整个 DL_DIR,但存在一些问题。 最明显的是体积大,因为它包含所有构建源码,不仅是最终镜像所用源码,还包括 通常不会发布的工具链源码和其他产物。对多数企业更严重的风险是误发专有软件。 archiver 类可帮助避免其中一些问题。

使用 DL_DIR 或 archiver 前,需要先决定如何 提供源码。

一种方法是只发布源码归档包。在 Build Directory 的 build/conf/local.conf 中添加:

INHERIT += "archiver"
ARCHIVER_MODE[src] = "original"

创建镜像时,所有向镜像部署软件包的配方源码,会依据各配方 LICENSE 放入 DEPLOY_DIR/sources 子目录。发布整个目录可满足提供未修改源码的 要求,但目录可能很大。

为减小体积,可以只发布许可证要求提供源码的归档包。假设只关注 GPL 代码, 可用以下脚本筛选:

# Script to archive a subset of packages matching specific license(s)
# Source and license files are copied into sub folders of package folder
# Must be run from build folder
#!/bin/bash
src_release_dir="source-release"
mkdir -p $src_release_dir
for a in tmp/deploy/sources/*; do
   for d in $a/*; do
      # Get package name from path
      p=`basename $d`
      p=${p%-*}
      p=${p%-*}
      # Only archive GPL packages (update *GPL* regex for your license check)
      numfiles=`ls tmp/deploy/licenses/$p/*GPL* 2> /dev/null | wc -l`
      if [ $numfiles -ge 1 ]; then
         echo Archiving $p
         mkdir -p $src_release_dir/$p/source
         cp $d/* $src_release_dir/$p/source 2> /dev/null
         mkdir -p $src_release_dir/$p/license
         cp tmp/deploy/licenses/$p/* $src_release_dir/$p/license 2> /dev/null
      fi
   done
done

随后可以将 gpl_source_release 目录打包并提供给最终用户。这是满足 GPLv2 第 3a 条和 GPLv3 第 6 条要求的一个步骤。

36 提供许可证文本

许可证文本经常被忽略,也应在生成最终镜像前处理。某些许可证要求文本随 二进制文件一起提供。可以在 build/conf/local.conf 或 发行版 configuration file 中添加:

COPY_LIC_MANIFEST = "1"
COPY_LIC_DIRS = "1"
LICENSE_CREATE_PACKAGE = "1"

这些语句确保软件包生成期间收集的许可证包含在镜像中。

备注

三个变量都设为 "1" 时,镜像中会有两份相同许可证文件,分别位于 /usr/share/common-licenses 和 /usr/share/license。

原因是 COPY_LIC_DIRS 和 COPY_LIC_MANIFEST 在构建镜像时 复制许可证,但不支持为后来安装的软件包添加许可证。 LICENSE_CREATE_PACKAGE 则创建独立软件包,提供向镜像添加许可证 的升级途径。

源码 archiver 已归档包含许可证文件的原始未修改源码, 因此已满足 GPL 及其他开源许可证要求源码附带许可证信息的条件。

36 提供编译脚本和源码修改

至此,生成镜像之前需要处理的内容已完成。接下来两项要求在最终发布打包时处理。

发布构建使用的 OpenEmbedded 构建系统版本及各层,即可同时提供编译脚本和 源码修改。

如果部署团队的 BSP 层 和发行版层用于 修补、编译、打包或以任何方式修改已发布镜像中的开源软件,GPLv2 第 3 条或 GPLv3 第 1 条可能要求发布这些层。一种方式是干净检出构建时使用的 Yocto Project 及层版本。可用 git-archive 归档 Git 仓库。

为方便最终用户,开发组织可以提供自己的 meta/conf/templates/default/bblayers.conf.sample,使用户用 已发布构建系统构建时,自动在 build/conf/bblayers.conf 中包含组织的层:

# POKY_BBLAYERS_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
POKY_BBLAYERS_CONF_VERSION = "2"

BBPATH = "${TOPDIR}"
BBFILES ?= ""

BBLAYERS ?= " \
  ##OEROOT##/meta \
  ##OEROOT##/../meta-yocto/meta-poky \
  ##OEROOT##/../meta-yocto/meta-yocto-bsp \
  ##OEROOT##/../meta-mylayer \
  "

创建并提供 Metadata 层(配方、配置文件等)的归档,可满足提供控制 编译的脚本及原始源码修改的要求。

36 静态库生成可执行文件时的合规限制

如果软件包 A 通过 RDEPENDS、RRECOMMENDS 或镜像配方的 IMAGE_INSTALL 进入镜像,并依赖静态链接库配方 B(DEPENDS += "B"), B 不会出现在生成的许可证清单和源码归档包中。这是因为 license 和 archiver 假定只有通过 RDEPENDS 或 RRECOMMENDS 纳入的软件包才会进入镜像。

因此,可能无法履行软件包 B 的许可证合规义务。

这也是 Yocto Project 默认不启用静态库的部分原因。在此限制得到解决前, 如果根文件系统由静态库构建,需要手动确保交付物符合这些库的许可证。

36 复制非标准许可证

linux-firmware 等软件包包含许多不通用的许可证。通过 NO_GENERIC_LICENSE,无需将大量仅适用于特定软件包的许可证加入 通用许可证文件集合,同时避免配方使用非通用且非 CLOSED 许可证时的 QA 错误。

以下示例使用所获取源码中的 LICENSE.Abilis.txt 作为许可证:

NO_GENERIC_LICENSE[Firmware-Abilis] = "LICENSE.Abilis.txt"