from collections import defaultdict
import argparse
import hashlib
import os
import os.path
import sys
import gzip
import shutil
import glob
import re
import subprocess
sys.path.append(
os.path.dirname(os.path.dirname(os.path.dirname(
os.path.abspath(__file__)))))
from scripts.util import build_utils
from scripts.util.file_utils import write_json_file, read_json_file
xml_escape_table = {
"&": "&",
'"': """,
"'": "'",
">": ">",
"<": "<",
}
def move_static_library_notices(options):
files = []
dest = os.path.join(options.notice_root_dir, 'libs')
os.makedirs(dest, exist_ok=True)
static_dir = os.path.join(options.notice_root_dir, "static")
static_subdir = os.path.join(options.static_library_notice_dir, "libs")
if os.path.exists(static_dir):
files = build_utils.get_all_files(static_dir)
files.sort()
if os.path.exists(static_subdir):
other_files = build_utils.get_all_files(static_subdir)
other_files.sort()
files.extend(other_files)
if files:
for file in files:
file_name = os.path.basename(file)
if not file_name.startswith("lib"):
dest_file = os.path.join(dest, f"lib{file_name}")
else:
dest_file = os.path.join(dest, file_name)
shutil.copyfile(file, dest_file)
if os.path.isfile("{}.json".format(dest_file)):
os.makedirs(os.path.dirname("{}.json".format(dest_file)), exist_ok=True)
shutil.copyfile("{}.json".format(file), "{}.json".format(dest_file))
if os.path.exists(static_dir):
shutil.rmtree(static_dir)
if os.path.exists(static_subdir):
shutil.rmtree(static_subdir)
def copy_static_library_notices(options, depfiles: list):
valid_notices = []
basenames = []
files = build_utils.get_all_files(options.static_library_notice_dir)
files.sort()
for file in files:
if os.stat(file).st_size == 0:
continue
if not options.lite_product:
if not file.endswith('.a.txt'):
continue
elif not file.endswith('.txt'):
continue
notice_file_name = os.path.basename(file)
if options.lite_product:
if not notice_file_name.startswith("lib"):
file_dir = os.path.dirname(file)
lib_file = os.path.join(file_dir, f"lib{notice_file_name}")
os.rename(file, lib_file)
file = lib_file
if file not in basenames:
basenames.append(notice_file_name)
valid_notices.append(file)
depfiles.append(file)
for file in valid_notices:
if options.image_name == "system":
if options.target_cpu == "arm64" or options.target_cpu == "x64":
install_dir = "system/lib64"
elif options.target_cpu == "arm":
install_dir = "system/lib"
else:
continue
elif options.image_name == "sdk":
install_dir = "toolchains/lib"
elif options.image_name == "ndk":
install_dir = "sysroot/usr/lib"
else:
continue
dest = os.path.join(options.notice_root_dir, install_dir,
os.path.basename(file))
os.makedirs(os.path.dirname(dest), exist_ok=True)
shutil.copyfile(file, dest)
if os.path.isfile("{}.json".format(file)):
os.makedirs(os.path.dirname("{}.json".format(dest)), exist_ok=True)
shutil.copyfile("{}.json".format(file), "{}.json".format(dest))
def write_file(file: str, string: str):
print(string, file=file)
def compute_hash(file: str):
sha256 = hashlib.sha256()
with open(file, 'rb') as file_fd:
for line in file_fd:
sha256.update(line)
return sha256.hexdigest()
def get_entity(text: str):
return "".join(xml_escape_table.get(c, c) for c in text)
def generate_txt_notice_files(file_hash: str, input_dir: str, output_filename: str,
notice_title: str):
with open(output_filename, "w") as output_file:
write_file(output_file, notice_title)
for value in file_hash:
write_file(output_file, '=' * 60)
write_file(output_file, "Notices for file(s):")
for filename in value:
write_file(
output_file, '/{}'.format(
re.sub('.txt.*', '',
os.path.relpath(filename, input_dir))))
write_file(output_file, '-' * 60)
write_file(output_file, "Notices for software(s):")
software_list = []
for filename in value:
json_filename = '{}.json'.format(filename)
contents = read_json_file(json_filename)
if contents is not None and contents not in software_list:
software_list.append(contents)
software_dict = {}
for contents_value in software_list:
if len(contents_value) > 0:
for val in contents_value:
if val.get('Software'):
software_name = val.get('Software').strip()
if software_name not in software_dict:
software_dict[software_name] = {"_version": "", "_path": []}
else:
write_file(output_file, "Software: ")
if val.get('Version'):
version = val.get('Version').strip()
software_dict[software_name]["_version"] = version
else:
write_file(output_file, "Version: ")
if val.get('Path'):
notice_source_path = val.get('Path').strip()
software_dict[software_name]["_path"].append(notice_source_path)
for software, software_value in software_dict.items():
write_file(output_file, f"Software: {software}")
write_file(output_file, f"Version: {software_value.get('_version')}")
if software_value.get("_path"):
for path in software_value.get("_path"):
write_file(output_file, f"Path: {path}")
write_file(output_file, '-' * 60)
with open(value[0], errors='ignore') as temp_file_hd:
write_file(output_file, temp_file_hd.read())
def generate_xml_notice_files(files_with_same_hash: dict, input_dir: str,
output_filename: str):
id_table = {}
for file_key in files_with_same_hash.keys():
for filename in files_with_same_hash[file_key]:
id_table[filename] = file_key
with open(output_filename, "w") as output_file:
write_file(output_file, '<?xml version="1.0" encoding="utf-8"?>')
write_file(output_file, "<licenses>")
sorted_filenames = sorted(id_table.keys())
for filename in sorted_filenames:
stripped_filename = re.sub('.txt.*', '',
os.path.relpath(filename, input_dir))
write_file(
output_file, '<file-name content_id="%s">%s</file-name>' %
(id_table.get(filename), stripped_filename))
write_file(output_file, '')
write_file(output_file, '')
processed_file_keys = []
for filename in sorted_filenames:
file_key = id_table.get(filename)
if file_key in processed_file_keys:
continue
processed_file_keys.append(file_key)
with open(filename, errors='ignore') as temp_file_hd:
write_file(
output_file,
'<file-content content_id="{}"><![CDATA[{}]]></file-content>'
.format(file_key, get_entity(temp_file_hd.read())))
write_file(output_file, '')
write_file(output_file, "</licenses>")
def compress_file_to_gz(src_file_name: str, gz_file_name: str):
with open(src_file_name, mode='rb') as src_file_fd:
with gzip.open(gz_file_name, mode='wb') as gz_file_fd:
gz_file_fd.writelines(src_file_fd)
def handle_zipfile_notices(zip_file: str):
notice_file = '{}.txt'.format(zip_file[:-4])
with build_utils.temp_dir() as tmp_dir:
build_utils.extract_all(zip_file, tmp_dir, no_clobber=False)
files = build_utils.get_all_files(tmp_dir)
contents = []
for file in files:
with open(file, 'r') as fd:
data = fd.read()
if data not in contents:
contents.append(data)
with open(notice_file, 'w') as merged_notice:
merged_notice.write('\n\n'.join(contents))
return notice_file
def do_merge_notice(args, zipfiles: str, txt_files: str):
notice_dir = args.notice_root_dir
notice_txt = args.output_notice_txt
notice_gz = args.output_notice_gz
notice_title = args.notice_title
if not notice_txt.endswith('.txt'):
raise Exception(
'Error: input variable output_notice_txt must ends with .txt')
if not notice_gz.endswith('.xml.gz'):
raise Exception(
'Error: input variable output_notice_gz must ends with .xml.gz')
notice_xml = notice_gz.replace('.gz', '')
files_with_same_hash = defaultdict(list)
for file in zipfiles:
txt_files.append(handle_zipfile_notices(file))
for file in txt_files:
if os.stat(file).st_size == 0:
continue
file_hash = compute_hash(file)
files_with_same_hash[file_hash].append(file)
file_sets = [
sorted(files_with_same_hash[hash])
for hash in sorted(files_with_same_hash.keys())
]
if file_sets is not None:
generate_txt_notice_files(file_sets, notice_dir, notice_txt,
notice_title)
if files_with_same_hash is not None:
generate_xml_notice_files(files_with_same_hash, notice_dir, notice_xml)
compress_file_to_gz(notice_xml, args.output_notice_gz)
if args.notice_module_info:
module_install_info_list = []
module_install_info = {}
module_install_info['type'] = 'notice'
module_install_info['source'] = args.output_notice_txt
module_install_info['install_enable'] = True
module_install_info['dest'] = [
os.path.join(args.notice_install_dir,
os.path.basename(args.output_notice_txt))
]
module_install_info_list.append(module_install_info)
write_json_file(args.notice_module_info, module_install_info_list)
if args.lite_product:
current_dir_cmd = ['pwd']
process = subprocess.Popen(current_dir_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate(timeout=600)
current_dir = stdout.decode().strip()
dest = f"{current_dir}/system/etc/NOTICE.txt"
if os.path.isfile(notice_txt):
os.makedirs(os.path.dirname(dest), exist_ok=True)
shutil.copyfile(notice_txt, dest)
def parse_args():
"""Parses command-line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument('--image-name')
parser.add_argument('--collected-notice-zipfile',
action='append',
help='zipfile stors collected notice files')
parser.add_argument('--notice-root-dir', help='where notice files store')
parser.add_argument('--output-notice-txt', help='output notice.txt')
parser.add_argument('--output-notice-gz', help='output notice.txt')
parser.add_argument('--notice-title', help='title of notice.txt')
parser.add_argument('--static-library-notice-dir',
help='path to static library notice files')
parser.add_argument('--target-cpu', help='cpu arch')
parser.add_argument('--depfile', help='depfile')
parser.add_argument('--notice-module-info',
help='module info file for notice target')
parser.add_argument('--notice-install-dir',
help='install directories of notice file')
parser.add_argument('--lite-product', help='', default="")
return parser.parse_args()
def main():
"""Main function to merge and generate notice files."""
args = parse_args()
notice_dir = args.notice_root_dir
depfiles = []
if args.collected_notice_zipfile:
for zip_file in args.collected_notice_zipfile:
build_utils.extract_all(zip_file, notice_dir, no_clobber=False)
else:
depfiles += build_utils.get_all_files(notice_dir)
if args.lite_product:
move_static_library_notices(args)
if args.static_library_notice_dir:
copy_static_library_notices(args, depfiles)
zipfiles = glob.glob('{}/**/*.zip'.format(notice_dir), recursive=True)
txt_files = glob.glob('{}/**/*.txt'.format(notice_dir), recursive=True)
txt_files += glob.glob('{}/**/*.txt.?'.format(notice_dir), recursive=True)
outputs = [args.output_notice_txt, args.output_notice_gz]
if args.notice_module_info:
outputs.append(args.notice_module_info)
build_utils.call_and_write_depfile_if_stale(
lambda: do_merge_notice(args, zipfiles, txt_files),
args,
depfile_deps=depfiles,
input_paths=depfiles,
input_strings=args.notice_title + args.target_cpu,
output_paths=(outputs))
if __name__ == "__main__":
main()