import os
import subprocess
import time
import argparse
import shlex
import signal
import sys
import re
import tempfile
import shutil
import json
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
def get_target(target):
if target.startswith("//"):
return target[2:]
else:
return target
def execute_build_command(command, use_shell=False):
process = subprocess.Popen(
command,
shell=use_shell,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
print(output.strip())
return_code = process.poll()
print(f"build finish,return code is: {return_code}")
return return_code == 0
def monitor_file_and_stop(shell_script_path, target_file, shell_args=None,
use_shell=False):
if isinstance(shell_args, str):
command = [shell_script_path] + shlex.split(shell_args)
elif shell_args:
command = [shell_script_path] + shell_args
else:
command = [shell_script_path]
command += ["--build-only-gn", "--no-prebuilt-sdk"]
execute_build_command(command, use_shell=use_shell)
file_detected = False
while True:
if os.path.exists(target_file):
file_detected = True
break
time.sleep(0.5)
if file_detected:
if os.stat(target_file).st_size == 0:
print("No changes to the unittest detected, skipping compilation directly.")
sys.exit()
command.remove("--build-only-gn")
command.remove("--no-prebuilt-sdk")
command.extend(["--build-target", "precise_module_build"])
return execute_build_command(command, use_shell=use_shell)
return False
def process_changes():
change_info = read_json("change_info.json")
openharmony_fields = [v["name"] for v in change_info.values() if "name" in v]
change_files = []
file_operations = {
"added": lambda x: x,
"rename": lambda x: [item for pair in x for item in pair],
"modified": lambda x: x,
"deleted": lambda x: x
}
gn_files, c_files, h_files, other_files = [], [], [], []
file_type_map = {
"h": h_files,
"hh": h_files,
"hpp": h_files,
"gn": gn_files,
"gni": gn_files,
"c": c_files,
"cpp": c_files,
"cc": c_files,
"cxx": c_files,
"js": c_files,
"ets": c_files,
"in": h_files,
"cl": h_files,
"cppm": h_files,
"cuh": h_files,
"def": h_files,
"gch": h_files,
"glsl": h_files,
"h++": h_files,
"hhu": h_files,
"hlsl": h_files,
"i": h_files,
"icc": h_files,
"impl": h_files,
"inl": h_files,
"ipp": h_files,
"ixx": h_files,
"mpp": h_files,
"mxx": h_files,
"pch": h_files,
"protp": h_files,
"swig": h_files,
"template": h_files,
"tcc": h_files,
"thrift": h_files,
"tpp": h_files,
"wgsl": h_files,
}
for key, value in change_info.items():
changed_files = value.get("changed_file_list", {})
for operation, processor in file_operations.items():
if operation not in changed_files:
print(f"unknown file operation: {operation}")
continue
for modified_file in processor(changed_files[operation]):
target_list = file_type_map.get(get_file_extension(modified_file), other_files)
if target_list == gn_files:
patch_file_content = generate_patch(os.path.join(ROOT_PATH, key), value.get("name"))
print(patch_file_content)
review_content = parse_patch_for_buildgn(patch_file_content)
print("-"*100)
print(review_content)
print(ROOT_PATH)
print("-"*100)
if target_list is not None:
target_list.append("//" + os.path.join(key, modified_file))
modified_files = {
"h_file": h_files,
"c_file": c_files,
"gn_file": gn_files,
"other_file": other_files,
"gn_module":[]
}
file_path = os.path.abspath(__file__)
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(file_path)))))
write_file = os.path.join(root_path, "modify_files.json")
with open(write_file, 'w') as json_file:
json.dump(modified_files, json_file, indent=4)
return (
[os.path.join(self.ace_root, f) for f in change_files],
openharmony_fields
)
def parse_patch_for_buildgn(content):
"""
解析patch文件,提取BUILD.gn文件的修改行号
特别注意:
1. 只提取以'+'开头的行(新增行)
2. 对于添加空行的情况,也要正确识别
"""
buildgn_diffs = re.findall(
r'diff --git a/(.*?BUILD\.gn.*?) b/(.*?BUILD\.gn.*?)\n(.*?)(?=\ndiff --git|\Z)',
content,
re.DOTALL
)
if not buildgn_diffs:
return None
results = []
for old_file, new_file, diff_content in buildgn_diffs:
changed_lines = []
current_line_num = 0
lines = diff_content.split('\n')
for line in lines:
if line.startswith('@@'):
match = re.search(r'\+(\d+)(?:,(\d+))?', line)
if match:
current_line_num = int(match.group(1))
continue
if line.startswith('+') and not line.startswith('++'):
changed_lines.append(str(current_line_num))
current_line_num += 1
continue
if line.startswith(' ') or line.startswith('-'):
if not line.startswith('---') and not line.startswith('--'):
current_line_num += 1
if changed_lines:
results.append(f"{new_file} {','.join(changed_lines)}")
return results
def generate_patch(part_path, part_name):
original_cwd = os.getcwd()
patch_file = ""
patch_content = ""
try:
os.chdir(part_path)
repo_name = part_name
patch_file = f"{repo_name}.patch"
cmd = f"git format-patch HEAD^ --stdout"
try:
result = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True,
check=True
)
if result.stdout.strip():
patch_content = result.stdout
with open(patch_file, 'w', encoding='utf-8') as pf:
pf.write(result.stdout)
else:
print(f" nothing change,no patch file")
except subprocess.CalledProcessError as e:
print(f" git diff command shibai: {e}")
print(f" generate patch error: {e.stderr}")
except Exception as e:
print(f" generate patch error: {e}")
finally:
os.chdir(original_cwd)
return patch_content
def get_file_extension(filename):
if '.' in filename:
return filename.split(".")[-1]
else:
return None
def read_json(path):
try:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
return {}
if __name__ == "__main__":
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False
)
parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
help='display help information and exit')
parser.add_argument('-s', '--script', required=True,
help='the path to the shell script to be executed')
parser.add_argument('-f', '--file', required=True,
help='the path of the target file to be monitored')
parser.add_argument('--use-shell', action='store_true',
help='execute commands using the shell (handle complex commands)')
parser.add_argument('shell_args', nargs=argparse.REMAINDER,
help='parameters passed to the shell script (after --)')
args = parser.parse_args()
process_changes()
shell_args = []
if args.shell_args:
try:
sep_index = args.shell_args.index("--")
shell_args = args.shell_args[sep_index + 1:]
except ValueError:
shell_args = args.shell_args
success = monitor_file_and_stop(
shell_script_path=args.script,
target_file=args.file,
shell_args=shell_args,
use_shell=args.use_shell,
)
exit(0 if success else 1)