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"