| #!/usr/bin/env python3.8 |
| # |
| # Copyright 2022 Google LLC |
| # |
| # This program is free software; you can redistribute it and/or |
| # modify it under the terms of the GNU General Public License |
| # version 2 as published by the Free Software Foundation. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| |
| # This script is used to automatically generate package |
| # information for SBOM of COS image bundled dependencies. |
| |
| import json |
| import os |
| import subprocess |
| import sys |
| from sbom_info_lib import download_url |
| from sbom_info_lib import go_dep |
| from sbom_info_lib import licenses |
| from chromite.lib import osutils |
| from chromite.lib import portage_util |
| |
| |
| SBOM_INFO_FILE_NAME = "sbom-pkg-info.json" |
| SPDX_NOASSERTION = "NOASSERTION" |
| |
| |
| class SbomPackageInfo: |
| def __init__(self): |
| self.download_url = "" |
| self.licenses = "" |
| self.go_dep = [] |
| # Format: [{ |
| # "license_name": <license_name>, |
| # "file_names": [file_name], |
| # "license_txt": <license_txt> |
| # },{......}] |
| self.other_license_list = [] |
| self.private = False |
| self.err = "" |
| |
| def write_to_build_info(self, build_info_dir): |
| content = { |
| "download-url": self.download_url, |
| "licenses": self.licenses, |
| "go-dep": self.go_dep, |
| "other_licenses": self.other_license_list, |
| "private": self.private, |
| "err": self.err, |
| } |
| json_content = json.dumps(content, indent=4) |
| osutils.WriteFile( |
| f"{build_info_dir}/{SBOM_INFO_FILE_NAME}", json_content, makedirs=True |
| ) |
| |
| |
| class SBOMPkgInfoError(Exception): |
| def __init__(self, msg): |
| super().__init__(msg) |
| |
| |
| def read_build_info(build_info_dir): |
| with open(os.path.join(build_info_dir, "repository"), "r") as f: |
| repository = f.read().strip() |
| with open(os.path.join(build_info_dir, "CATEGORY"), "r") as f: |
| category = f.read().strip() |
| with open(os.path.join(build_info_dir, "PF"), "r") as f: |
| pf = f.read().strip() |
| license_path = os.path.join(build_info_dir, "LICENSE") |
| license = "" |
| if os.path.exists(license_path): |
| with open(license_path, "r") as f: |
| license = f.read().strip() |
| return repository, category, pf, license |
| |
| |
| def build_env_var(build_dir, var): |
| env_file = os.path.join(build_dir, "temp", "environment") |
| result = subprocess.run( |
| ["bash", "-c", f"source {env_file}; echo -n ${var}"], |
| capture_output=True, check=True, text=True) |
| return result.stdout |
| |
| |
| def get_src_path(sysroot, fullname): |
| # Package source code has been fetched by gen-package-licenses.sh. |
| tmpdir = portage_util.PortageqEnvvar("PORTAGE_TMPDIR", sysroot=sysroot) |
| build_dir = os.path.join(tmpdir, "portage", fullname) |
| # For cros-workon ebuilds, source is sometimes not copied to the build dir |
| # as a performance optimization. Use the "S" ebuild variable to get the |
| # source dir. Fallback to work dir if source dir isn't available |
| src_dir = build_env_var(build_dir, "S") |
| work_dir = os.path.join(build_dir, "work") |
| |
| if os.path.exists(src_dir): |
| return src_dir |
| if os.path.exists(work_dir): |
| return work_dir |
| |
| raise AssertionError( |
| "Unpack of %s didn't create %s or %s. Version mismatch" % (fullname, src_dir, work_dir) |
| ) |
| |
| |
| def main(): |
| sbom_pkg_info = SbomPackageInfo() |
| package_dir = os.getenv("PORTAGE_BUILDDIR") |
| sysroot = "/".join(package_dir.split("/")[:3]) |
| build_info_dir = os.path.join(package_dir, "build-info") |
| try: |
| package_name = os.path.basename(package_dir) |
| ebuild = os.path.join(build_info_dir, package_name + ".ebuild") |
| repository, category, pf, license = read_build_info(build_info_dir) |
| if "private" in repository: |
| sbom_pkg_info.private = True |
| sbom_pkg_info.download_url = download_url.get_download_url( |
| ebuild, repository, category, pf, license |
| ) |
| src_path = get_src_path(sysroot, os.path.join(category, pf)) |
| ( |
| sbom_pkg_info.licenses, |
| sbom_pkg_info.other_license_list, |
| ) = licenses.get_licenses(build_info_dir, src_path, package_name) |
| sbom_pkg_info.go_dep = go_dep.get_go_dep(src_path) |
| |
| # Since download location is not required by the EO, failure to |
| # find download location shouldn't be fatal. |
| if not sbom_pkg_info.download_url and not sbom_pkg_info.private: |
| sbom_pkg_info.download_url = SPDX_NOASSERTION |
| if not sbom_pkg_info.licenses: |
| sbom_pkg_info.licenses = "NONE" |
| except Exception as e: |
| sbom_pkg_info.err = repr(e) |
| finally: |
| sbom_pkg_info.write_to_build_info(build_info_dir) |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main()) |