123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- #
- # Copyright (c) 2025, RT-Thread Development Team
- #
- # SPDX-License-Identifier: Apache-2.0
- #
- # Change Logs:
- # Date Author Notes
- # 2025-05-15 supperthomas add PR status show
- # 2025-05-22 hydevcode 替换bsp_building.yml的判断文件修改机制,并将PR status show合并进bsp_building.yml
- import subprocess
- import sys
- import os
- import re
- import argparse
- import locale
- from typing import List, Dict
- import json
- from typing import List
- class FileDiff:
- def __init__(self, path: str, status: str, size_change: int = 0, old_size: int = 0, new_size: int = 0):
- self.path = path
- self.status = status # A (added), M (modified), D (deleted), R (renamed)
- self.size_change = size_change
- self.old_size = old_size
- self.new_size = new_size
-
- def __str__(self):
- if self.status == 'A':
- return f"Added {self.path}: {self.size_change} bytes"
- elif self.status == 'D':
- return f"Deleted {self.path}: was {self.old_size} bytes"
- elif self.status == 'M' or self.status == 'R':
- return f"Modified {self.path}: {self.size_change} bytes change"
- else:
- return f"{self.status} {self.path}"
-
- class GitDiffAnalyzer:
- def __init__(self, target_branch: str):
- self.target_branch = target_branch
- self.encoding = locale.getpreferredencoding()
- def get_diff_files(self) -> List[FileDiff]:
- """获取当前分支与目标分支之间的差异文件"""
- # 找到当前分支和目标分支的最近共同祖先
- merge_base = self.get_merge_base()
- if not merge_base:
- print("No common ancestor found between current branch and target branch")
- sys.exit(1)
-
- # 获取差异文件列表
- diff_cmd = f"git diff --name-status {merge_base} HEAD"
- print(diff_cmd)
- try:
- output = subprocess.check_output(diff_cmd.split(), stderr=subprocess.STDOUT)
- output = output.decode(self.encoding).strip()
- print(output)
- except subprocess.CalledProcessError as e:
- print(f"Error executing git diff: {e.output.decode(self.encoding)}")
- sys.exit(1)
-
- if not output:
- print("No differences between current branch and target branch")
- sys.exit(0)
-
- # 处理可能的换行符问题
- output = output.replace('\r\n', '\n')
- lines = output.split('\n')
-
- file_diffs = []
- for line in lines:
- line = line.strip()
- if not line:
- continue
-
- parts = line.split('\t')
- if len(parts) < 2:
- # 可能是重命名文件,格式为 "RXXX\told_path\tnew_path"
- match = re.match(r'R(\d+)\t(.+)\t(.+)', line)
- if match:
- status = 'R'
- old_path = match.group(2)
- new_path = match.group(3)
- # 计算重命名文件的修改大小
- old_size = self.get_file_size(old_path, self.target_branch)
- new_size = self.get_file_size(new_path, 'HEAD')
- size_change = new_size - old_size if old_size > 0 else new_size
- file_diffs.append(FileDiff(new_path, status, size_change, old_size, new_size))
- else:
- status = parts[0][0] # 取状态码的第一个字符
- path = parts[1]
-
- if status == 'A':
- # 新增文件,计算大小
- size = self.get_file_size(path, 'HEAD')
- file_diffs.append(FileDiff(path, status, size, 0, size))
- elif status == 'D':
- # 删除文件,计算原大小
- size = self.get_file_size(path, self.target_branch)
- file_diffs.append(FileDiff(path, status, 0, size, 0))
- elif status == 'M':
- # 修改文件,计算大小变化
- old_size = self.get_file_size(path, self.target_branch)
- new_size = self.get_file_size(path, 'HEAD')
- size_change = new_size - old_size
- file_diffs.append(FileDiff(path, status, size_change, old_size, new_size))
-
- return file_diffs
- def get_merge_base(self) -> str:
- """获取当前分支和目标分支的最近共同祖先"""
- try:
- cmd = f"git merge-base {self.target_branch} HEAD"
- print(cmd)
- output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
- return output.decode(self.encoding).strip()
- except subprocess.CalledProcessError as e:
- print(f"Error executing git merge-base: {e.output.decode(self.encoding)}")
- return None
- def get_file_size(self, path: str, ref: str) -> int:
- """获取指定分支上文件的大小"""
- try:
- # 使用 git cat-file 来获取文件内容,然后计算其大小
- cmd = f"git cat-file blob {ref}:{path}"
- output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
- return len(output)
- except subprocess.CalledProcessError:
- # 如果文件不存在或无法获取,返回0
- return 0
- def format_size(size: int) -> str:
- """将字节大小转换为人类可读的格式"""
- if size >= 0:
- if size < 1024:
- return f"{size} bytes"
- elif size < 1024 * 1024:
- return f"{size / 1024:.1f} KB"
- elif size < 1024 * 1024 * 1024:
- return f"{size / (1024 * 1024):.1f} MB"
- else:
- return f"{size / (1024 * 1024 * 1024):.1f} GB"
- else:
- temp_size=abs(size)
- if temp_size < 1024:
- return f"-{temp_size} bytes"
- elif temp_size < 1024 * 1024:
- return f"-{temp_size / 1024:.1f} KB"
- elif temp_size < 1024 * 1024 * 1024:
- return f"-{temp_size / (1024 * 1024):.1f} MB"
- else:
- return f"-{temp_size / (1024 * 1024 * 1024):.1f} GB"
- def is_bsp(path):
- return os.path.isfile(os.path.join(path, "rtconfig.h"))
- def filter_bsp_config(file_diffs: List[FileDiff], config_path: str):
- # 读取原始JSON配置
- with open(config_path, 'r', encoding='utf-8') as f:
- config = json.load(f)
- # 获取所有修改的文件路径(统一使用Linux风格路径)
- modified_paths = [diff.path.replace('\\', '/') for diff in file_diffs]
- print(modified_paths)
- if not modified_paths:
- print("master分支运行")
- return
- bsp_paths = set()
- bsp_in_but_not_bsp_paths = set()
- all_print_paths = set()
- for modified_path in modified_paths:
- parts = modified_path.strip(os.sep).split('/')
- if not parts:
- continue
- first_level = parts[0]
- first_level_path = os.path.join(os.getcwd(), first_level)
- #处理bsp路径的逻辑
- if first_level == "bsp":
- temp_path=os.path.join(os.getcwd(), modified_path)
- # 判断是否是文件
- if not os.path.isdir(modified_path):
- temp_path = os.path.dirname(temp_path)
- #循环搜索每一级是否有rtconfig.h
- while temp_path !=first_level_path:
- if is_bsp(temp_path):
- bsp_paths.add(temp_path)
-
- break
- temp_path=os.path.dirname(temp_path)
- if temp_path ==first_level_path:
- bsp_in_but_not_bsp_paths.add(parts[1])
- else:
- #非bsp路径逻辑
- all_print_paths.add(first_level_path)
- # 变成相对路径
- bsp_list = set()
- for path in sorted(bsp_paths):
- current_working_dir = os.path.join(os.getcwd(), "bsp/")
- if path.startswith(current_working_dir):
- bsp_list.add(path[len(current_working_dir):].lstrip(os.sep))
- else:
- bsp_list.add(path) # 如果 first_level_path 不以 current_working_dir 开头,则保持不变
- # 处理修改了bsp外的文件的情况
- filtered_bsp = [
- path for path in bsp_list
- if path.split('/')[0] not in bsp_in_but_not_bsp_paths
- ]
- merged_result = filtered_bsp + list(bsp_in_but_not_bsp_paths)
- filtered_legs = []
- for leg in config["legs"]:
- matched_paths = [
- path for path in leg.get("SUB_RTT_BSP", [])
- if any(keyword in path for keyword in merged_result)
- ]
- if matched_paths:
- filtered_legs.append({**leg, "SUB_RTT_BSP": matched_paths})
- # 生成新的配置
- new_config = {"legs": filtered_legs}
- # 判断有没有修改到bsp外的文件,有的话则编译全部
- if not all_print_paths:
- print(new_config)
- file_name = ".github/ALL_BSP_COMPILE_TEMP.json"
- # 将 new_config 写入文件
- with open(file_name, "w", encoding="utf-8") as file:
- json.dump(new_config, file, ensure_ascii=False, indent=4)
- def main():
- # 源文件路径
- source_file = ".github/ALL_BSP_COMPILE.json" # 替换为你的文件路径
- # 目标文件路径
- target_file = ".github/ALL_BSP_COMPILE_TEMP.json" # 替换为你的目标文件路径
- # 读取源文件并过滤掉带有 // 的行
- with open(source_file, "r", encoding="utf-8") as infile, open(target_file, "w", encoding="utf-8") as outfile:
- for line in infile:
- if not line.lstrip().startswith("//"):
- outfile.write(line)
-
- parser = argparse.ArgumentParser(description='Compare current branch with target branch and show file differences.')
- parser.add_argument('target_branch', help='Target branch to compare against (e.g., master)')
- args = parser.parse_args()
-
- analyzer = GitDiffAnalyzer(args.target_branch)
- file_diffs = analyzer.get_diff_files()
- # 生成报告
- generate_report(file_diffs, args.target_branch)
- filter_bsp_config(file_diffs,".github/ALL_BSP_COMPILE_TEMP.json")
-
- def add_summary(text):
- """
- add summary to github action.
- """
- if "GITHUB_STEP_SUMMARY" in os.environ:
- summary_path = os.environ["GITHUB_STEP_SUMMARY"] # 获取摘要文件路径
- # 将 text 写入摘要文件(追加模式)
- with open(summary_path, "a") as f: # "a" 表示追加模式
- f.write(text + "\n") # 写入文本并换行
- else:
- print("Environment variable $GITHUB_STEP_SUMMARY is not set.")
- def summarize_diff(label, count, size=None):
- """格式化输出变更摘要"""
- line = f" {label:<12} {count:>3} files"
- if size is not None:
- line += f" ({format_size(size)})"
- add_summary(line)
- def generate_report(file_diffs: List[FileDiff], target_branch: str):
- """生成差异报告"""
- add_summary(f"# 📊 **Comparison between {target_branch} and Current Branch**\n")
-
- # 分类统计
- added_files = [f for f in file_diffs if f.status.startswith('A')]
- removed_files = [f for f in file_diffs if f.status.startswith('D')]
- modified_files = [f for f in file_diffs if f.status.startswith('M')]
- renamed_files = [f for f in file_diffs if f.status.startswith('R')]
- copied_files = [f for f in file_diffs if f.status.startswith('C')]
- unmerged_files = [f for f in file_diffs if f.status.startswith('U')]
- type_changed_files = [f for f in file_diffs if f.status.startswith('T')]
-
- # 计算总变化量
- total_added = sum(f.size_change for f in added_files)
- total_removed = sum(f.old_size for f in removed_files)
- total_modified = sum(f.size_change for f in modified_files)
- total_copied = sum(f.size_change for f in copied_files)
- total_renamed = sum(f.old_size for f in renamed_files)
- total_type_changed = sum(f.size_change for f in type_changed_files)
- total_size_change = sum(f.size_change for f in file_diffs)
- # === 汇总输出 ===
- summarize_diff("Total:", len(file_diffs))
- summarize_diff("Added:", len(added_files), total_added)
- summarize_diff("Removed:", len(removed_files), total_removed)
- summarize_diff("Modified:", len(modified_files), total_modified)
- summarize_diff("Renamed:", len(renamed_files), total_renamed)
- summarize_diff("Copied:", len(copied_files), total_copied)
- summarize_diff("Type Changed:", len(type_changed_files), total_type_changed)
- summarize_diff("Unmerged:", len(unmerged_files))
- if total_size_change > 0:
- change_desc = f"📈 **Increase of {format_size(total_size_change)}**"
- elif total_size_change < 0:
- change_desc = f"📉 **Reduction of {format_size(abs(total_size_change))}**"
- else:
- change_desc = "➖ **No net size change**"
- add_summary(f"\n### 📦 **Total Size Change:** {change_desc} (Excluding removed files)")
-
- # 显示详细差异文件内容
- add_summary("\n## 📂 **Detailed File Changes**\n")
- for diff in file_diffs:
- add_summary(f"📄 {diff.path} — **[{diff.status}]**")
- # 文件状态处理
- if diff.status.startswith('A'):
- add_summary(f"📦 Size: {format_size(diff.new_size)}")
- elif diff.status.startswith(('M', 'R')): # 修改或重命名
- add_summary(f"📏 Original size: {format_size(diff.old_size)}")
- add_summary(f"📐 New size: {format_size(diff.new_size)}")
- delta = diff.size_change
- if delta > 0:
- change_str = f"📈 Increased by {format_size(delta)}"
- elif delta < 0:
- change_str = f"📉 Reduced by {format_size(abs(delta))}"
- else:
- change_str = "➖ No size change"
- add_summary(f"📊 Size change: {change_str}")
- elif diff.status.startswith('D'):
- add_summary(f"🗑️ Original size: {format_size(diff.old_size)}")
- elif diff.status.startswith('C'):
- add_summary(f"📎 Copied from size: {format_size(diff.old_size)} → {format_size(diff.new_size)}")
- elif diff.status.startswith('T'):
- add_summary("⚙️ File type changed")
- elif diff.status.startswith('U'):
- add_summary("⚠️ Unmerged conflict detected")
- else:
- add_summary("❓ Unknown change type")
- add_summary("\n\n")
- if __name__ == "__main__":
- main()
|