Merge "lakitu: create package information file in the image"
diff --git a/overlay-lakitu/scripts/board_specific_setup.sh b/overlay-lakitu/scripts/board_specific_setup.sh
index cafdd0f..3e001d2 100644
--- a/overlay-lakitu/scripts/board_specific_setup.sh
+++ b/overlay-lakitu/scripts/board_specific_setup.sh
@@ -1,3 +1,5 @@
+#!/bin/bash
+
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -12,6 +14,14 @@
# Don't install symbol table for kdump kernel.
INSTALL_MASK+=" /boot/kdump/System.map-*"
+TEMP_PACKAGE_LIST=""
+
+cleanup_temp_package_list() {
+ if [[ -e "${TEMP_PACKAGE_LIST}" ]]; then
+ rm -f "${TEMP_PACKAGE_LIST}"
+ fi
+}
+
# build_image script calls board_setup on the pristine base image.
board_make_image_bootable() {
local -r image="$1"
@@ -23,6 +33,49 @@
fi
}
+create_package_info() {
+ trap cleanup_temp_package_list EXIT
+ TEMP_PACKAGE_LIST="$(mktemp -t package-list.XXXXXXXXXX)"
+
+ # The "emerge" command below generates the list of packages that
+ # virtual/target-os depends on. Its results look like
+ #
+ # ...
+ # [binary R ] app-arch/gzip-1.9 to /build/lakitu/
+ # [binary R ] dev-libs/popt-1.16-r2 to /build/lakitu/
+ # [binary R ] app-emulation/docker-credential-helpers-0.6.3-r1 to /build/lakitu/
+ # ...
+ #
+ # This command line is similar to what ListInstalledPackage function (in
+ # chromite/licensing/licenses_lib.py) does.
+ #
+ # The following "grep" command filters out extra messages to leave the package
+ # list only.
+ #
+ # And the "sed" command extracts the category name, the package name, and the
+ # version from each line. With that, the example above is converted to
+ #
+ # ...
+ # app-arch/gzip-1.9
+ # dev-libs/popt-1.16-r2
+ # app-emulation/docker-credential-helpers-0.6.3-r1
+ # ...
+ "emerge-${BOARD}" \
+ --with-bdeps=n --with-bdeps-auto=n --usepkgonly --emptytree --pretend \
+ --color=n virtual/target-os | \
+ grep --color=never "^\[" | \
+ sed -E 's/\[[^]]+R[^]]+\] (.+) to \/build\/.*/\1/' \
+ > "${TEMP_PACKAGE_LIST}"
+
+ local -r script_root="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")"
+ sudo "${script_root}/create_pkg_info.py" \
+ --input="${TEMP_PACKAGE_LIST}" \
+ --output="${root_fs_dir}"/etc/cos-package-info.json
+
+ cleanup_temp_package_list
+ trap - EXIT
+}
+
write_toolchain_path() {
local -r cros_overlay="/mnt/host/source/src/third_party/chromiumos-overlay"
local -r sdk_ver_file="${cros_overlay}/chromeos/binhost/host/sdk_version.conf"
@@ -37,13 +90,14 @@
sudo tee "${BUILD_DIR}/toolchain_path" > /dev/null
}
-# Moves the given rootfs_file to the given artifact location. The directory
-# containing the rootfs_file is deleted if it becomes empty after this move.
-# If the rootfs_file doesn't exist, put an empty file at the given artifact
-# location.
-export_image_artifact() {
- local rootfs_file="$1"
- local artifact="$2"
+# Moves the given rootfs file (a relative path to "root_fs_dir") to the given
+# artifact location (a relative path to "BUILD_DIR"). The directory containing
+# the rootfs file is deleted if it becomes empty after this move. If the
+# rootfs file doesn't exist, this function puts an empty file at the given
+# artifact location.
+move_for_artifact() {
+ local rootfs_file="${root_fs_dir}/$1"
+ local artifact="${BUILD_DIR}/$2"
if [[ ! -f "${rootfs_file}" ]]; then
touch "${artifact}"
return
@@ -55,64 +109,28 @@
fi
}
-write_toolchain_env() {
- # Create toolchain_env file in BUILD_DIR so that it can be exported
- # as an artifact.
- local artifact="${BUILD_DIR}/toolchain_env"
-
- # File from which kernel compiler information will be copied
- # This file is deleted after copying content to artifact
- local toolchain_env_file="${root_fs_dir}/etc/toolchain_env"
-
- # Copy kernel compiler info to BUILD artifact
- if [[ -f "${toolchain_env_file}" ]]; then
- cp "${toolchain_env_file}" "${artifact}"
- # Remove toolchain_env from image
- sudo rm "${toolchain_env_file}"
- else
- touch "${artifact}"
- fi
+# Creates toolchain_env file in BUILD_DIR so that it can be exported as an
+# artifact.
+export_toolchain_env() {
+ # File that has kernel compiler information.
+ move_for_artifact "etc/toolchain_env" "toolchain_env"
}
-write_kernel_info() {
- # Create kernel_info file in BUILD_DIR so that it can be exported
- # as an artifact.
- local build_artifact="${BUILD_DIR}/kernel_info"
-
- # File from which kernel information will be copied.
- # This file is deleted after copying content to artifact.
- local kernel_info_file="${root_fs_dir}/etc/kernel_info"
-
- # Copy kernel_info to BUILD artifact.
- if [[ -f "${kernel_info_file}" ]]; then
- cp "${kernel_info_file}" "${build_artifact}"
- # Remove kernel_info file from image.
- sudo rm "${kernel_info_file}"
- else
- touch "${build_artifact}"
- fi
+# Creates kernel_info file in BUILD_DIR so that it can be exported as an
+# artifact.
+export_kernel_info() {
+ # File with kernel information.
+ move_for_artifact "etc/kernel_info" "kernel_info"
}
-write_kernel_commit() {
- # Create kernel_commit file in BUILD_DIR so that it can be exported
- # as an artifact.
- local build_artifact="${BUILD_DIR}/kernel_commit"
-
- # File from which kernel commit will be copied.
- # This file is deleted after copying content to artifact.
- local kernel_commit_file="${root_fs_dir}/etc/kernel_commit"
-
- # Copy kernel_commit to BUILD artifact.
- if [[ -f "${kernel_commit_file}" ]]; then
- cp "${kernel_commit_file}" "${build_artifact}"
- # Remove kernel_commit file from image.
- sudo rm "${kernel_commit_file}"
- else
- touch "${build_artifact}"
- fi
+# Creates kernel_commit file in BUILD_DIR so that it can be exported as an
+# artifact.
+export_kernel_commit() {
+ # File with kernel commit ID.
+ move_for_artifact "etc/kernel_commit" "kernel_commit"
}
-# Export default GPU driver version file as an artifact.
+# Exports default GPU driver version file as an artifact.
export_gpu_default_version() {
local -r script_root="$1"
local -r default_driver_file="${script_root}/gpu_default_version"
@@ -126,16 +144,15 @@
# end of building base image.
board_finalize_base_image() {
local -r script_root="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")"
+ create_package_info
write_toolchain_path
- export_image_artifact \
- "${root_fs_dir}/opt/google/src/kernel-src.tar.gz" \
- "${BUILD_DIR}/kernel-src.tar.gz"
- export_image_artifact \
- "${root_fs_dir}/opt/google/src/kernel-headers.tgz" \
- "${BUILD_DIR}/kernel-headers.tgz"
- write_toolchain_env
- write_kernel_info
- write_kernel_commit
+ move_for_artifact "opt/google/src/kernel-src.tar.gz" \
+ "kernel-src.tar.gz"
+ move_for_artifact "opt/google/src/kernel-headers.tgz" \
+ "kernel-headers.tgz"
+ export_toolchain_env
+ export_kernel_info
+ export_kernel_commit
cp "${BOARD_ROOT}/usr/lib/debug/boot/vmlinux" "${BUILD_DIR}/vmlinux"
export_gpu_default_version "${script_root}"
diff --git a/overlay-lakitu/scripts/create_pkg_info.py b/overlay-lakitu/scripts/create_pkg_info.py
new file mode 100755
index 0000000..423545a
--- /dev/null
+++ b/overlay-lakitu/scripts/create_pkg_info.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Create the package information file in JSON format.
+
+To get the package information, use the library at
+https://cos.googlesource.com/cos/tools/+/refs/heads/master/src/pkg/cos/pkg_info.go
+instead of accessing the JSON file directly.
+
+For information on the JSON format, see http://go/cos-package-list-design .
+"""
+
+from __future__ import print_function
+
+import collections
+import getopt
+import json
+import re
+import sys
+
+
+# The following regular expresssions are used for extracting the category name,
+# the package name, the package version without the revision, and the package
+# revision from a string that looks like
+#
+# category_name/package_name-1.2.3.4_beta1_rc2-r1
+#
+# These regular expressions follow the Gentoo package manager specification at
+# https://www.gentoo.org/proj/en/qa/pms.xml . They are similar to what SplitPV
+# function (in chromite/lib/portage_util.py) uses.
+
+CATEGORY_RE = r'(?P<category>\w[\w\+\.\-]*)'
+PACKAGE_RE = r'(?P<package>\w[\w+-]*)'
+VERSION_NUMLETTER_RE = r'\d+(\.\d+)*[a-z]?'
+VERSION_SUFFIX_RE = r'_(pre|p|beta|alpha|rc)\d*'
+VERSION_NO_REV_RE = r'(?P<version_no_rev>%s(%s)*)' % (VERSION_NUMLETTER_RE,
+ VERSION_SUFFIX_RE)
+REVISION_RE = r'-r(?P<revision>\d+)'
+CPV_RE = r'^%s\/%s-%s(%s)?$' % (CATEGORY_RE, PACKAGE_RE,
+ VERSION_NO_REV_RE, REVISION_RE)
+
+
+def PrintHelp():
+ print('usage: create_pkg_list.py --input=<input file> --output=<output file>')
+
+
+def CreateList(input_lines):
+ cpv_re = re.compile(CPV_RE, re.VERBOSE)
+ package_list = []
+ for line in input_lines:
+ match = cpv_re.match(line.strip())
+ if match is not None:
+ package_list.append(match.groupdict())
+ return package_list
+
+
+def WriteJson(package_list, output_file):
+ installed_packages = []
+ for p in package_list:
+ package_info = collections.OrderedDict(
+ [('category', p['category']),
+ ('name', p['package']),
+ ('version', p['version_no_rev'])
+ ]
+ )
+ if 'revision' in p and p['revision'] is not None:
+ package_info['revision'] = p['revision']
+
+ installed_packages.append(package_info)
+
+ result = {}
+ result['installedPackages'] = installed_packages
+ json.dump(result, output_file, indent=4)
+
+ return 0
+
+
+def main(argv):
+ input_fn = ''
+ output_fn = ''
+
+ try:
+ opts, args_left = getopt.getopt(argv, '', ['help', 'input=', 'output='])
+ except getopt.GetoptError:
+ PrintHelp()
+ return -1
+ if len(args_left) != 0:
+ PrintHelp()
+ return -1
+
+ for opt, arg in opts:
+ if opt == '--help':
+ PrintHelp()
+ elif opt == '--input':
+ input_fn = arg
+ elif opt == '--output':
+ output_fn = arg
+
+ if input_fn == '' or output_fn == '':
+ PrintHelp()
+ return -1
+
+ input_lines = []
+ try:
+ with open(input_fn) as input_file:
+ input_lines = input_file.readlines()
+ except OSError:
+ print('error: Failed to open input file: %s' % input_fn)
+ return -1
+ if len(input_lines) == 0:
+ print('error: No input lines')
+ return -1
+
+ package_list = CreateList(input_lines)
+ if len(package_list) == 0:
+ print('error: Empty package list')
+ return -1
+
+ try:
+ with open(output_fn, 'w') as output_file:
+ ret = WriteJson(package_list, output_file)
+ if ret != 0:
+ print('error: Failed to write package list')
+ return ret
+ except OSError:
+ print('error: Failed to open input file: %s' % input_fn)
+ return -1
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/overlay-lakitu/scripts/create_pkg_info_unittest.py b/overlay-lakitu/scripts/create_pkg_info_unittest.py
new file mode 100755
index 0000000..25204b9
--- /dev/null
+++ b/overlay-lakitu/scripts/create_pkg_info_unittest.py
@@ -0,0 +1,160 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for create_pkg_info."""
+
+from __future__ import print_function
+
+import io
+import unittest
+
+import create_pkg_info
+
+
+class CreatePkgInfoTest(unittest.TestCase):
+ """All the tests."""
+
+ INPUT_LINES = [
+ 'app-arch/gzip-1.9',
+ 'dev-libs/popt-1.16-r2',
+ 'app-emulation/docker-credential-helpers-0.6.3-r1',
+ '_not.real-category1+/_not-real_package1-12.34.56.78',
+ ' _not.real-category1+/_not-real_package2-12.34.56.78-r26 ',
+ ' _not.real-category1+/_not-real_package3-12.34.56.78_rc3 ',
+ ' _not.real-category1+/_not-real_package4-12.34.56.78_rc3-r26 ',
+ ' _not.real-category1+/_not-real_package5-12.34.56.78_pre2_rc3-r26 ',
+ '_not.real-category2+/_not-real_package1-12.34.56.78q',
+ '_not.real-category2+/_not-real_package2-12.34.56.78q-r26',
+ '_not.real-category2+/_not-real_package3-12.34.56.78q_rc3',
+ '_not.real-category2+/_not-real_package4-12.34.56.78q_rc3-r26',
+ '_not.real-category2+/_not-real_package5-12.34.56.78q_pre2_rc3-r26'
+ ]
+
+ EXPECTED_LIST = [
+ {'category': 'app-arch', 'package': 'gzip', 'version_no_rev': '1.9',
+ 'revision': None},
+ {'category': 'dev-libs', 'package': 'popt', 'version_no_rev': '1.16',
+ 'revision': '2'},
+ {'category': 'app-emulation', 'package': 'docker-credential-helpers',
+ 'version_no_rev': '0.6.3', 'revision': '1'},
+ {'category': '_not.real-category1+', 'package': '_not-real_package1',
+ 'version_no_rev': '12.34.56.78', 'revision': None},
+ {'category': '_not.real-category1+', 'package': '_not-real_package2',
+ 'version_no_rev': '12.34.56.78', 'revision': '26'},
+ {'category': '_not.real-category1+', 'package': '_not-real_package3',
+ 'version_no_rev': '12.34.56.78_rc3', 'revision': None},
+ {'category': '_not.real-category1+', 'package': '_not-real_package4',
+ 'version_no_rev': '12.34.56.78_rc3', 'revision': '26'},
+ {'category': '_not.real-category1+', 'package': '_not-real_package5',
+ 'version_no_rev': '12.34.56.78_pre2_rc3', 'revision': '26'},
+ {'category': '_not.real-category2+', 'package': '_not-real_package1',
+ 'version_no_rev': '12.34.56.78q', 'revision': None},
+ {'category': '_not.real-category2+', 'package': '_not-real_package2',
+ 'version_no_rev': '12.34.56.78q', 'revision': '26'},
+ {'category': '_not.real-category2+', 'package': '_not-real_package3',
+ 'version_no_rev': '12.34.56.78q_rc3', 'revision': None},
+ {'category': '_not.real-category2+', 'package': '_not-real_package4',
+ 'version_no_rev': '12.34.56.78q_rc3', 'revision': '26'},
+ {'category': '_not.real-category2+', 'package': '_not-real_package5',
+ 'version_no_rev': '12.34.56.78q_pre2_rc3', 'revision': '26'}
+ ]
+
+ EXPECTED_JSON = """{
+ "installedPackages": [
+ {
+ "category": "app-arch",
+ "name": "gzip",
+ "version": "1.9"
+ },
+ {
+ "category": "dev-libs",
+ "name": "popt",
+ "version": "1.16",
+ "revision": "2"
+ },
+ {
+ "category": "app-emulation",
+ "name": "docker-credential-helpers",
+ "version": "0.6.3",
+ "revision": "1"
+ },
+ {
+ "category": "_not.real-category1+",
+ "name": "_not-real_package1",
+ "version": "12.34.56.78"
+ },
+ {
+ "category": "_not.real-category1+",
+ "name": "_not-real_package2",
+ "version": "12.34.56.78",
+ "revision": "26"
+ },
+ {
+ "category": "_not.real-category1+",
+ "name": "_not-real_package3",
+ "version": "12.34.56.78_rc3"
+ },
+ {
+ "category": "_not.real-category1+",
+ "name": "_not-real_package4",
+ "version": "12.34.56.78_rc3",
+ "revision": "26"
+ },
+ {
+ "category": "_not.real-category1+",
+ "name": "_not-real_package5",
+ "version": "12.34.56.78_pre2_rc3",
+ "revision": "26"
+ },
+ {
+ "category": "_not.real-category2+",
+ "name": "_not-real_package1",
+ "version": "12.34.56.78q"
+ },
+ {
+ "category": "_not.real-category2+",
+ "name": "_not-real_package2",
+ "version": "12.34.56.78q",
+ "revision": "26"
+ },
+ {
+ "category": "_not.real-category2+",
+ "name": "_not-real_package3",
+ "version": "12.34.56.78q_rc3"
+ },
+ {
+ "category": "_not.real-category2+",
+ "name": "_not-real_package4",
+ "version": "12.34.56.78q_rc3",
+ "revision": "26"
+ },
+ {
+ "category": "_not.real-category2+",
+ "name": "_not-real_package5",
+ "version": "12.34.56.78q_pre2_rc3",
+ "revision": "26"
+ }
+ ]
+}"""
+
+ def __init__(self, *args, **kwargs):
+ unittest.TestCase.__init__(self, *args, **kwargs)
+ self.maxDiff = None
+
+ def testCreateList(self):
+ self.assertEqual(
+ create_pkg_info.CreateList(self.INPUT_LINES),
+ self.EXPECTED_LIST)
+
+ def testWriteJson(self):
+ mock_file = io.StringIO()
+ create_pkg_info.WriteJson(self.EXPECTED_LIST, mock_file)
+ self.assertEqual(mock_file.getvalue(), self.EXPECTED_JSON)
+ mock_file.close()
+
+
+if __name__ == '__main__':
+ unittest.main()