blob: b438ef6286fcb8bc40f6aa6e9e2d20cb008357bf [file] [log] [blame]
# Copyright 2023 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Recursively copy all files from --source-dir to the dest-dir therein.
If a meta/manifest.json is found, the arch target will be merged into that file
(rather than replacing it).
"""
import argparse
import ast
import filecmp
import os
import shutil
import json
import subprocess
import sys
import difflib
import re
GENERATED_CONTANTS_TEMPLATE = """# AUTO-GENERATED - DO NOT EDIT!
# This file is re-generated in //scripts/sdk/bazel/merge_bazel_wrapper.py
# The following list of CPU names use Fuchsia conventions.
constants = struct(host_cpus = ["x64"], target_cpus = {target_cpu})
"""
WHITELIST_DIFF_LISTS = []
PATTERN = r"fuchsia_select\((.*?)\)"
def merge_fuchsia_select(source_file: str, dest_file: str) -> bool:
if not os.path.basename(source_file).endswith(".bazel"):
return False
with open(source_file, "r", encoding="utf-8") as f:
source = f.read()
# If file does not contain fuchsia_select, then nothing to merge
if "fuchsia_select" not in source:
return False
with open(dest_file, "r", encoding="utf-8") as f:
dest = f.read()
source_list = re.findall(PATTERN, source.replace("\n", ""))
dest_list = re.findall(PATTERN, dest.replace("\n", ""))
merged_map = {}
for select_source, select_dest in zip(source_list, dest_list):
dict_source = ast.literal_eval(select_source)
dict_dest = ast.literal_eval(select_dest)
dict_dest.update(dict_source)
merged_dict_str = str(dict_dest).strip("{}")
merged_map[select_dest.strip("{}").strip()] = merged_dict_str
for src in merged_map:
dest = dest.replace(src, merged_map[src])
with open(dest_file, "w") as f:
f.write(dest)
return True
def merge_new_manifest(manifest_json, new_manifest):
if manifest_json is None:
manifest_json = new_manifest
else:
manifest_json["arch"]["target"].extend(new_manifest["arch"]["target"])
return manifest_json
def print_file_difference(source_file, dest_file, relpath):
print("=" * 40)
print(relpath)
print("=" * 40)
with open(source_file) as sf, open(dest_file) as df:
# Only print the difference if the files is utf-8 encoded
try:
for line in difflib.unified_diff(sf.readlines(), df.readlines()):
print(line)
except UnicodeDecodeError:
return
def format_dir(buildifier_path: str, dir: str) -> None:
cmd = [buildifier_path, "-r", dir]
subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def sorting(item):
if isinstance(item, dict):
return sorted((key, sorting(values)) for key, values in item.items())
if isinstance(item, list):
return sorted(sorting(x) for x in item)
return item
def validate_same_json(source_file, dest_file):
if not os.path.basename(source_file).endswith(".json"):
return False
with open(source_file, "r+") as sf, open(dest_file, "r+") as df:
source = sorting(json.load(sf))
dest = sorting(json.load(df))
return source == dest
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--source-dir")
parser.add_argument("--dest-dir")
parser.add_argument("--buildifier-path", default=None)
args = parser.parse_args()
manifest_json = None
if args.buildifier_path:
for dir in [args.source_dir, args.dest_dir]:
format_dir(args.buildifier_path, dir)
for directory in os.listdir(args.source_dir):
directory = os.path.join(args.source_dir, directory)
for root, _, files in os.walk(directory):
for f in files:
source_file = os.path.join(root, f)
relpath = os.path.relpath(source_file, directory)
dest_file = os.path.join(args.dest_dir, relpath)
if relpath == "meta/manifest.json":
with open(source_file, "r") as manifest_file:
new_manifest = json.load(manifest_file)
manifest_json = merge_new_manifest(
manifest_json, new_manifest
)
continue
# Skip generated_constants.bzl file as we have to update the targets section with merged targets.
if relpath == "generated_constants.bzl":
continue
if os.path.exists(dest_file):
if filecmp.cmp(source_file, dest_file):
continue
if validate_same_json(source_file, dest_file):
continue
if not merge_fuchsia_select(source_file, dest_file):
print_file_difference(source_file, dest_file, relpath)
continue
if not os.path.exists(os.path.dirname(dest_file)):
os.makedirs(os.path.dirname(dest_file))
shutil.copy(source_file, dest_file)
# Write meta/manifest.json
dest_file = os.path.join(args.dest_dir, "meta/manifest.json")
if not os.path.exists(os.path.dirname(dest_file)):
os.makedirs(os.path.dirname(dest_file))
with open(dest_file, "w") as f:
json.dump(manifest_json, f, indent=2)
# Write generated_constants.bzl
dest_file = os.path.join(args.dest_dir, "generated_constants.bzl")
with open(dest_file, "w") as f:
f.write(
GENERATED_CONTANTS_TEMPLATE.format(
target_cpu=manifest_json["arch"]["target"]
)
)
if args.buildifier_path:
format_dir(args.buildifier_path, args.source_dir)
if __name__ == "__main__":
main()