# This class supplys common functions.
HOSTTOOLS += "xz"
SPDX_DEPLOY_DIR ??= "${DEPLOY_DIR}/spdx"
SPDX_TOPDIR ?= "${WORKDIR}/spdx_sstate_dir"
SPDX_OUTDIR ?= "${SPDX_TOPDIR}/${TARGET_SYS}/${PF}/"
SPDX_WORKDIR ?= "${WORKDIR}/spdx_temp/"
SPDX_EXCLUDE_NATIVE ??= "1"
SPDX_EXCLUDE_SDK ??= "1"
SPDX_EXCLUDE_PACKAGES ??= ""
do_spdx[dirs] = "${WORKDIR}"
LICENSELISTVERSION = "2.6"
# If ${S} isn't actually the top-level source directory, set SPDX_S to point at
# the real top-level directory.
SPDX_S ?= "${S}"
#addtask do_spdx before do_configure after do_patch
# Exclude package based on variables.
# SPDX_EXCLUDE_NATIVE ??= "1"
# SPDX_EXCLUDE_SDK ??= "1"
# SPDX_EXCLUDE_PACKAGES ??= ""
def excluded_package(d, pn):
assume_provided = (d.getVar("ASSUME_PROVIDED") or "").split()
if pn in assume_provided:
for p in d.getVar("PROVIDES").split():
if p != pn:
pn = p
break
# We just archive gcc-source for all the gcc related recipes
if d.getVar('BPN') in ['gcc', 'libgcc'] \
and not pn.startswith('gcc-source'):
bb.debug(1, 'archiver: %s is excluded, covered by gcc-source' % pn)
return True
# TARGET_SYS in ARCHIVER_ARCH will break the stamp for gcc-source in multiconfig
if pn.startswith('gcc-source'):
d.setVar('ARCHIVER_ARCH', "allarch")
# The following: do_fetch, do_unpack and do_patch tasks have been deleted,
# so avoid archiving do_spdx here.
# -native is for the host aka during the build
if pn.endswith('-native') and d.getVar("SPDX_EXCLUDE_NATIVE") == "1":
return True
# nativesdk- is for the developer SDK
if pn.startswith('nativesdk-') and d.getVar("SPDX_EXCLUDE_SDK") == "1":
return True
# packagegroups have no files to scan
if pn.startswith('packagegroup'):
return True
if pn.startswith('glibc-locale'):
return True
for p in d.getVar("SPDX_EXCLUDE_PACKAGES").split():
if p in pn:
return True
return False
def exclude_useless_paths(tarinfo):
if tarinfo.isdir():
if tarinfo.name.endswith('/temp') or tarinfo.name.endswith('/patches') or tarinfo.name.endswith('/.pc'):
return None
elif tarinfo.name == 'temp' or tarinfo.name == 'patches' or tarinfo.name == '.pc':
return None
return tarinfo
def get_tar_name(d, suffix):
"""
get the name of tarball
"""
if suffix:
filename = '%s-%s.tar.xz' % (d.getVar('PF'), suffix)
else:
filename = '%s.tar.xz' % d.getVar('PF')
return filename
def spdx_create_tarball(d, srcdir, suffix, ar_outdir):
"""
create the tarball from srcdir
"""
import tarfile, shutil
srcdir = os.path.realpath(srcdir)
bb.utils.mkdirhier(ar_outdir)
filename = get_tar_name(d, suffix)
tarname = os.path.join(ar_outdir, filename)
bb.note('Creating %s' % tarname)
tar = tarfile.open(tarname, 'w:xz')
tar.add(srcdir, arcname=os.path.basename(srcdir), filter=exclude_useless_paths)
tar.close()
shutil.rmtree(srcdir)
info = {}
info['pn'] = (d.getVar( 'PN') or "")
info['pv'] = (d.getVar( 'PKGV') or "")
info['pr'] = (d.getVar( 'PR') or "")
if d.getVar('SAVE_SPDX_ACHIVE'):
manifest_dir = (d.getVar('SPDX_DEPLOY_DIR') or "")
if not os.path.exists( manifest_dir ):
bb.utils.mkdirhier( manifest_dir )
info['outfile'] = os.path.join(manifest_dir, filename)
create_manifest(info,tarname)
return tarname
def get_tarball_name(d, srcdir, suffix, ar_outdir):
"""
get the tarball file path
"""
import tarfile, shutil
srcdir = os.path.realpath(srcdir)
filename = get_tar_name(d, suffix)
tarname = os.path.join(ar_outdir, filename)
return tarname
# Run do_unpack and do_patch
def spdx_get_src(d):
import shutil
spdx_workdir = d.getVar('SPDX_WORKDIR')
spdx_sysroot_native = d.getVar('STAGING_DIR_NATIVE')
pn = d.getVar('PN')
# The kernel class functions require it to be on work-shared, so we dont change WORKDIR
if not is_work_shared(d):
# Change the WORKDIR to make do_unpack do_patch run in another dir.
d.setVar('WORKDIR', spdx_workdir)
# Restore the original path to recipe's native sysroot (it's relative to WORKDIR).
d.setVar('STAGING_DIR_NATIVE', spdx_sysroot_native)
# The changed 'WORKDIR' also caused 'B' changed, create dir 'B' for the
# possibly requiring of the following tasks (such as some recipes's
# do_patch required 'B' existed).
bb.utils.mkdirhier(d.getVar('B'))
bb.build.exec_func('do_unpack', d)
# Copy source of kernel to spdx_workdir
if is_work_shared(d):
d.setVar('WORKDIR', spdx_workdir)
d.setVar('STAGING_DIR_NATIVE', spdx_sysroot_native)
src_dir = spdx_workdir + "/" + d.getVar('PN')+ "-" + d.getVar('PV') + "-" + d.getVar('PR')
bb.utils.mkdirhier(src_dir)
if bb.data.inherits_class('kernel',d):
share_src = d.getVar('STAGING_KERNEL_DIR')
if pn.startswith('gcc-source'):
gcc_source_path = d.getVar('TMPDIR') + "/work-shared"
gcc_pv = d.getVar('PV')
gcc_pr = d.getVar('PR')
share_src = gcc_source_path + "/gcc-" + gcc_pv + "-" + gcc_pr + "/gcc-" + gcc_pv + "/"
cmd_copy_share = "cp -rf " + share_src + "/* " + src_dir + "/"
cmd_copy_kernel_result = os.popen(cmd_copy_share).read()
bb.note("cmd_copy_kernel_result = " + cmd_copy_kernel_result)
git_path = src_dir + "/.git"
if os.path.exists(git_path):
remove_dir_tree(git_path)
# Make sure gcc and kernel sources are patched only once
if not (d.getVar('SRC_URI') == "" or is_work_shared(d)):
if bb.data.inherits_class('dos2unix', d):
d.setVar('WORKDIR', spdx_workdir)
bb.build.exec_func('do_convert_crlf_to_lf', d)
bb.build.exec_func('do_patch', d)
# Some userland has no source.
if not os.path.exists( spdx_workdir ):
bb.utils.mkdirhier(spdx_workdir)
def create_manifest(info,sstatefile):
import shutil
shutil.copyfile(sstatefile,info['outfile'])
def get_cached_spdx( sstatefile ):
import subprocess
if not os.path.exists( sstatefile ):
return None
try:
output = subprocess.check_output(['grep', "PackageVerificationCode", sstatefile])
except subprocess.CalledProcessError as e:
bb.error("Index creation command '%s' failed with return code %d:\n%s" % (e.cmd, e.returncode, e.output))
return None
cached_spdx_info=output.decode('utf-8').split(': ')
return cached_spdx_info[1]
#Find InfoInLicenseFile and fill into PackageLicenseInfoInLicenseFile.
def find_infoinlicensefile(sstatefile):
import subprocess
import linecache
import re
info_in_license_file = ""
line_nums = []
key_words = ["NOTICE", "README", "readme", "COPYING", "LICENSE"]
for key_word in key_words:
search_cmd = "grep -n 'FileName: .*" + key_word + "' " + sstatefile
search_output = subprocess.Popen(search_cmd, shell=True, stdout=subprocess.PIPE).communicate()[0]
bb.note("Search result: " + str(search_output))
if search_output:
bb.note("Found " + key_word +" file.")
for line in search_output.decode('utf-8').splitlines():
num = line.split(":")[0]
line_nums.append(num)
else:
bb.note("No license info files found.")
for line_num in line_nums:
line_spdx = linecache.getline(sstatefile, int(line_num))
file_path = line_spdx.split(": ")[1]
base_file_name = os.path.basename(file_path)
if base_file_name.startswith("NOTICE"):
bb.note("Found NOTICE file " + base_file_name)
elif base_file_name.startswith("readme"):
bb.note("Found readme file " + base_file_name)
elif base_file_name.startswith("README"):
bb.note("Found README file " + base_file_name)
elif base_file_name.find("COPYING")>=0:
bb.note("Found COPYING file " + base_file_name)
elif base_file_name.find("LICENSE")>=0:
bb.note("Found LICENSE file: " + base_file_name)
else:
continue
linecache.clearcache()
line_no = int(line_num) + 1
line_spdx = linecache.getline(sstatefile, line_no)
while not re.match(r'[a-zA-Z]',line_spdx) is None:
if not line_spdx.startswith("LicenseInfoInFile"):
line_no = line_no + 1
linecache.clearcache()
line_spdx = linecache.getline(sstatefile, int(line_no))
continue
license = line_spdx.split(": ")[1]
license = license.split("\n")[0]
bb.note("file path = " + file_path)
file_path = file_path.split("\n")[0]
bb.note("file path = " + file_path)
path_list = file_path.split('/')
if len(file_path.split('/')) < 3:
file_path_simple = file_path.split('/',1)[1]
elif len(file_path.split('/')) < 4:
file_path_simple = file_path.split('/',2)[2]
elif len(file_path.split('/')) < 5:
file_path_simple = file_path.split('/',3)[3]
else:
file_path_simple = file_path.split('/',4)[4]
#license_in_file = file_path + ": " + license
license_in_file = "%s%s%s%s" % ("PackageLicenseInfoInLicenseFile: ",file_path_simple,": ",license)
license_in_file.replace('\n', '').replace('\r', '')
info_in_license_file = info_in_license_file + license_in_file + "\n"
line_no = line_no + 1
linecache.clearcache()
line_spdx = linecache.getline(sstatefile, int(line_no))
linecache.clearcache()
return info_in_license_file
## Add necessary information into spdx file
def write_cached_spdx( info,sstatefile, ver_code ):
import subprocess
import re
infoinlicensefile=""
def sed_replace(dest_sed_cmd,key_word,replace_info):
dest_sed_cmd = dest_sed_cmd + "-e 's#^" + key_word + ".*#" + \
key_word + replace_info + "#' "
return dest_sed_cmd
def sed_replace_aline(dest_sed_cmd,origin_line,dest_line):
dest_sed_cmd = dest_sed_cmd + "-e 's#^" + origin_line + ".*#" + \
dest_line + "#' "
return dest_sed_cmd
def sed_insert(dest_sed_cmd,key_word,new_line):
dest_sed_cmd = dest_sed_cmd + "-e '/^" + key_word \
+ r"/a\\" + new_line + "' "
return dest_sed_cmd
def sed_insert_front(dest_sed_cmd,key_word,new_line):
dest_sed_cmd = dest_sed_cmd + "-e '/^" + key_word \
+ r"/i\\" + new_line + "' "
return dest_sed_cmd
## Delet ^M in doc format
subprocess.call("sed -i -e 's#\r##g' %s" % sstatefile, shell=True)
## Document level information
subprocess.call("sed -i '/SPDXID: SPDXRef-DOCUMENT/d' %s" % sstatefile, shell=True)
subprocess.call("sed -i '/DocumentName: \/srv\/fossology\/repository\/report/d' %s" % sstatefile, shell=True)
subprocess.call("sed -i '/DocumentNamespace: http:/i SPDXID: SPDXRef-DOCUMENT' %s" % sstatefile, shell=True)
sed_cmd = r"sed -i "
spdx_DocumentComment = "SPDX for " + info['pn'] + " version " \
+ info['pv'] + ""
sed_cmd = sed_insert(sed_cmd,"SPDXID: SPDXRef-DOCUMENT","DocumentName: " + info['pn']+"-"+info['pv'])
## Creator information
sed_cmd = sed_replace(sed_cmd,"Creator: Tool: ",info['creator']['Tool'])
## Package level information
sed_cmd = sed_replace_aline(sed_cmd, "SPDXVersion: SPDX-2.2", "SPDXVersion: SPDX-2.3")
sed_cmd = sed_replace(sed_cmd, "PackageName: ", info['pn'])
sed_cmd = sed_replace_aline(sed_cmd, "SPDXID: SPDXRef-", "SPDXID: SPDXRef-" + info['pkg_spdx_id'])
sed_cmd = sed_replace(sed_cmd, "Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-", info['pkg_spdx_id'])
sed_cmd = sed_insert(sed_cmd, "PackageName: ", "PackageVersion: " + info['pv'])
sed_cmd = sed_replace(sed_cmd, "PackageDownloadLocation: ",info['package_download_location'])
sed_cmd = sed_insert(sed_cmd, "PackageDownloadLocation: ", "PackageHomePage: " + info['package_homepage'])
sed_cmd = sed_insert(sed_cmd, "PackageDownloadLocation: ", "PackageSummary: " + "" + info['package_summary'] + "")
sed_cmd = sed_replace(sed_cmd, "PackageVerificationCode: ",ver_code)
sed_cmd = sed_insert(sed_cmd, "PackageVerificationCode: ", "PackageDescription: " +
"" + info['pn'] + " version " + info['pv'] + "")
sed_cmd = sed_insert(sed_cmd, "PackageVerificationCode: ", "PackageComment: \\nModificationRecord: " + info['modified'] + "\\n" + "")
for contain in info['package_contains'].split( ):
sed_cmd = sed_insert(sed_cmd, "PackageComment:"," \\n\\n## Relationships\\nRelationship: " + info['pn'] + " CONTAINS " + contain)
for static_link in info['package_static_link'].split( ):
sed_cmd = sed_insert(sed_cmd, "PackageComment:"," \\n\\n## Relationships\\nRelationship: " + info['pn'] + " STATIC_LINK " + static_link)
sed_cmd = sed_insert(sed_cmd, "PackageVerificationCode: ", "BuiltDate: " + info['build_time'])
sed_cmd = sed_insert(sed_cmd, "PackageVerificationCode: ", "ReleaseDate: " + info['release_date'])
sed_cmd = sed_insert(sed_cmd, "PackageVerificationCode: ", "PrimaryPackagePurpose: " + info['purpose'])
depends = info['depends_on']
for depend in re.split(r'\s*[,\s\n\r]\s*', depends):
sed_cmd = sed_insert_front(sed_cmd, "PackageCopyrightText: ", "Relationship: SPDXRef-" + info['pn'] + " DEPENDS_ON SPDXRef-" + depend)
bb.note("sed_cmd = " + sed_cmd)
sed_cmd = sed_cmd + sstatefile
subprocess.call("%s" % sed_cmd, shell=True)
infoinlicensefile = find_infoinlicensefile(sstatefile)
for oneline_infoinlicensefile in infoinlicensefile.splitlines():
bb.note("find_infoinlicensefile: " + oneline_infoinlicensefile)
sed_cmd = r"sed -i -e 's#\r$##' "
sed_cmd = sed_insert(sed_cmd, "ModificationRecord: ", oneline_infoinlicensefile)
sed_cmd = sed_cmd + sstatefile
subprocess.call("%s" % sed_cmd, shell=True)
with open(sstatefile, encoding="utf-8", mode="a") as file:
file.write(info['external_refs'])
def is_work_shared(d):
pn = d.getVar('PN')
return bb.data.inherits_class('kernel', d) or pn.startswith('gcc-source')
def remove_dir_tree(dir_name):
import shutil
try:
shutil.rmtree(dir_name)
except:
pass
def remove_file(file_name):
try:
os.remove(file_name)
except OSError as e:
pass
def list_files(dir ):
for root, subFolders, files in os.walk(dir):
for f in files:
rel_root = os.path.relpath(root, dir)
yield rel_root, f
return
def hash_file(file_name):
"""
Return the hex string representation of the SHA1 checksum of the filename
"""
try:
import hashlib
except ImportError:
return None
sha1 = hashlib.sha1()
with open( file_name, "rb" ) as f:
for line in f:
sha1.update(line)
return sha1.hexdigest()
def hash_string(data):
import hashlib
sha1 = hashlib.sha1()
sha1.update(data.encode('utf-8'))
return sha1.hexdigest()
def get_ver_code(dirname):
chksums = []
for f_dir, f in list_files(dirname):
try:
stats = os.stat(os.path.join(dirname,f_dir,f))
except OSError as e:
bb.note( "Stat failed" + str(e) + "\n")
continue
chksums.append(hash_file(os.path.join(dirname,f_dir,f)))
ver_code_string = ''.join(chksums).lower()
ver_code = hash_string(ver_code_string)
return ver_code
python do_spdx_creat_tarball(){
import shutil
spdx_outdir = d.getVar('SPDX_OUTDIR')
spdx_workdir = d.getVar('SPDX_WORKDIR')
spdx_temp_dir = os.path.join(spdx_workdir, "temp")
temp_dir = os.path.join(d.getVar('WORKDIR'), "temp")
bb.utils.mkdirhier(spdx_workdir)
spdx_get_src(d)
if os.path.isdir(spdx_temp_dir):
for f_dir, f in list_files(spdx_temp_dir):
temp_file = os.path.join(spdx_temp_dir,f_dir,f)
shutil.copy(temp_file, temp_dir)
bb.note("Creat tarball for " + spdx_outdir)
tar_file = spdx_create_tarball(d, d.getVar('WORKDIR'), 'patched', spdx_outdir)
}
# For scancode-tk.bbclass, just
python do_spdx_get_src(){
import shutil
spdx_outdir = d.getVar('SPDX_OUTDIR')
spdx_workdir = d.getVar('SPDX_WORKDIR')
spdx_temp_dir = os.path.join(spdx_workdir, "temp")
temp_dir = os.path.join(d.getVar('WORKDIR'), "temp")
bb.utils.mkdirhier(spdx_workdir)
spdx_get_src(d)
if os.path.isdir(spdx_temp_dir):
for f_dir, f in list_files(spdx_temp_dir):
temp_file = os.path.join(spdx_temp_dir,f_dir,f)
shutil.copy(temp_file, temp_dir)
bb.note("temp_dir = " + spdx_temp_dir)
}
#For SPDX2.3
def get_external_refs(d):
from oe.cve_check import get_patched_cves
external_refs = "##------------------------- \n"
external_refs += "## Security Information \n"
external_refs += "##------------------------- \n"
external_refs += "\"externalRefs\" : ["
unpatched_cves = []
nvd_link = "https://nvd.nist.gov/vuln/detail/"
with bb.utils.fileslocked([d.getVar("CVE_CHECK_DB_FILE_LOCK")], shared=True):
if os.path.exists(d.getVar("CVE_CHECK_DB_FILE")):
try:
patched_cves = get_patched_cves(d)
except FileNotFoundError:
bb.fatal("Failure in searching patches")
ignored, patched, unpatched, status = check_cves(d, patched_cves)
if patched or unpatched or (d.getVar("CVE_CHECK_COVERAGE") == "1" and status):
cve_data = get_cve_info(d, patched + unpatched + ignored)
#cve_write_data(d, patched, unpatched, ignored, cve_data, status)
else:
bb.note("No CVE database found, skipping CVE check")
return " "
if not patched+unpatched+ignored:
return " "
for cve in sorted(cve_data):
is_patched = cve in patched
is_ignored = cve in ignored
status = "unpatched"
if is_ignored:
status = "ignored"
elif is_patched:
status = "fix"
else:
# default value of status is Unpatched
unpatched_cves.append(cve)
external_refs += "{\n"
external_refs += "\"referenceCategory\" : \"SECURITY\",\n"
external_refs += "\"referenceLocator\" : \"https://nvd.nist.gov/vuln/detail/%s\",\n" % cve
external_refs += "\"referenceType\" : \"%s\"\n" % status
external_refs += "},"
external_refs += "]"
#bb.warn("external_refs = " + external_refs)
return external_refs
def get_pkgpurpose(d):
section = d.getVar("SECTION")
if section in "libs":
return "LIBRARY"
else:
return "APPLICATION "
def get_build_date(d):
from datetime import datetime, timezone
build_time = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
return build_time
def get_depends_on(d):
import re
depends = re.split(r'\s*[\s]\s*',d.getVar("DEPENDS"))
depends_spdx = ""
for depend in depends:
bb.note("depend = " + depend)
if depend.endswith("-native"):
bb.note("Don't show *-native in depends relationship.\n")
else:
depends_spdx += depend + ","
depends_spdx = depends_spdx.strip(',')
return depends_spdx
def get_spdxid_pkg(d):
if d.getVar("PROVIDES"):
pid = d.getVar("PROVIDES")
else:
pid = d.getVar("PN")
bb.note("SPDX ID of pkg = " + pid)
return pid