Last active
September 16, 2024 15:50
-
-
Save jcormier/4af3e1a33cfc4d20690350cfa9e134a1 to your computer and use it in GitHub Desktop.
Compare two uncompiled device tree files and output a unified diff ignoring phandle changes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #! /bin/bash | |
| # SPDX-License-Identifier: GPL-2.0-or-later | |
| # This script uses the bash <(...) extension. | |
| # If you want to change this to work with a generic /bin/sh, make sure | |
| # you fix that. | |
| # Change to script dir | |
| cd $(dirname $0) | |
| DTC=dtc | |
| source_and_sort () { | |
| DT="$1" | |
| if [ -d "$DT" ]; then | |
| IFORMAT=fs | |
| elif [ -f "$DT" ]; then | |
| case "$DT" in | |
| *.dts) | |
| IFORMAT=dts | |
| ;; | |
| *.dtb) | |
| IFORMAT=dtb | |
| ;; | |
| esac | |
| fi | |
| if [ -z "$IFORMAT" ]; then | |
| echo "Unrecognized format for $DT" >&2 | |
| exit 2 | |
| fi | |
| $DTC -I $IFORMAT -O dts -qq -f -s -o - "$DT" | |
| } | |
| if [ $# != 2 ]; then | |
| echo "Usage: dtdiff <device tree> <device tree>" >&2 | |
| exit 1 | |
| fi | |
| #diff -u <(source_and_sort "$1") <(source_and_sort "$2") | |
| ./dtdiff.py <(source_and_sort "$1") <(source_and_sort "$2") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/env python3 | |
| """ | |
| Compare two uncompiled device tree files and output a unified diff ignoring phandle changes | |
| Creates a map of phandles to node paths in each file | |
| Compares the two files and finds phandle changes, replacing each phandle with the node path | |
| Outputs a unified diff of the two files with phandle changes applied | |
| Note: Its possible a non-phandle value changes in a way that exactly matches a phandle change. | |
| This script assumes that is very unlikely. | |
| Ex: | |
| `'mii-rt = <0xeb>' --> 'mii-rt = <0xe8>'` is a phandle change | |
| - if 0xeb is in from_phandle_dict | |
| - if 0xe8 is in to_phandle_dict | |
| - the paths are the same | |
| Ex: | |
| `'prus = <0xe8 0xe9>;' --> 'prus = <0xe5 0xe6>;'` | |
| Has two phandle changes. In this case, 0xe8 -> 0xe5 and 0xe9 -> 0xe6 | |
| """ | |
| import difflib | |
| import sys | |
| import pprint | |
| import re | |
| def create_phandle_dict(lines): | |
| """ | |
| Create a dictionary of phandles from device tree lines | |
| """ | |
| capture_phandle = re.compile(r'phandle = <(0x[0-9a-f]+)>;') | |
| phandle_dict = {} | |
| current_path = [] | |
| for line in lines: | |
| # Look for path lines | |
| if '{' in line: | |
| node_name = line.split()[0] | |
| current_path.append(node_name) | |
| if '}' in line: | |
| current_path.pop() | |
| # Look for phandle lines | |
| m = capture_phandle.search(line) | |
| if m: | |
| phandle = m.group(1) | |
| node_path = "/".join(current_path).replace("//", "/") | |
| phandle_dict[phandle] = "&{" + node_path + "}" | |
| return phandle_dict | |
| def is_phandle_change(a_byte, b_byte, from_phandle_dict, to_phandle_dict): | |
| """ | |
| Return true if diff between two lines is a phandle change | |
| """ | |
| return ( | |
| a_byte != b_byte | |
| and a_byte in from_phandle_dict | |
| and b_byte in to_phandle_dict | |
| and from_phandle_dict[a_byte] == to_phandle_dict[b_byte] | |
| ) | |
| def get_phandle_changes_for_line(from_lines, to_lines, a_line_num, b_line_num, from_phandle_dict, to_phandle_dict): | |
| """ | |
| Return a list of phandle changes for a single line | |
| A line may have multiple phandle changes: | |
| Ex: `'prus = <0xe8 0xe9>;' --> 'prus = <0xe5 0xe6>;'` | |
| Return: List of changes to make to a_line and b_line: | |
| ((from_line_number, phandle, node_name), (to_line_number, phandle, node_name)) | |
| """ | |
| phandle_changes = [] | |
| a_line = from_lines[a_line_num].strip() | |
| b_line = to_lines[b_line_num].strip() | |
| # print(f"{tag:7} a[{a_line_num}] --> b[{b_line_num}] {a_line!r:>8} --> {b_line!r}") | |
| # Get list of hex values from each line | |
| byte_regex = re.compile(r'0x[0-9a-f]+') | |
| a_bytes = byte_regex.findall(a_line) | |
| b_bytes = byte_regex.findall(b_line) | |
| # Compare each corresponding byte to see if it's a phandle change | |
| for a_byte, b_byte in zip(a_bytes, b_bytes): | |
| if is_phandle_change(a_byte, b_byte, from_phandle_dict, to_phandle_dict): | |
| phandle_node = from_phandle_dict[a_byte] | |
| # print(f"Phandle change: {a_byte} -> {b_byte} in node {phandle_node}") | |
| change = ((a_line_num, a_byte, phandle_node), (b_line_num, b_byte, phandle_node)) | |
| phandle_changes.append(change) | |
| return phandle_changes | |
| def get_phandle_changes(from_lines, to_lines, from_phandle_dict, to_phandle_dict): | |
| """ | |
| Create a list of phandle changes | |
| Return: List of changes to make to from_lines and to_lines: | |
| ((from_line_number, phandle, node_name), (to_line_number, phandle, node_name)) | |
| """ | |
| phandle_changes = [] | |
| s = difflib.SequenceMatcher(None, from_lines, to_lines) | |
| # Maps lines to diff tags: 'equal', 'delete', 'insert', 'replace' | |
| for tag, i1, i2, j1, j2 in s.get_opcodes(): | |
| # Only handle replace operations | |
| if tag != 'replace': | |
| continue | |
| # Handle differences one line at a time | |
| # TODO Handle changes in file a that don't exactly line up with changes in file b | |
| # Ex: | |
| # - mboxes = <0x9b 0xd8>; | |
| # - timers = <0xd9>; | |
| # - watchdog-timers = <0xda>; <--- This line is missing in the other file | |
| # - memory-region = <0xdb>; | |
| # + mboxes = <0x9b 0xd6>; | |
| # + timers = <0xd7>; | |
| # + memory-region = <0xd8>; | |
| for a_line_num, b_line_num in zip(range(i1, i2), range(j1, j2)): | |
| temp = get_phandle_changes_for_line(from_lines, to_lines, a_line_num, b_line_num, from_phandle_dict, to_phandle_dict) | |
| phandle_changes.extend(temp) | |
| return phandle_changes | |
| def apply_phandle_changes(from_lines, to_lines, phandle_changes): | |
| """ Apply list of phandle changes to from_lines and to_lines """ | |
| def apply_change(lines, change): | |
| """ Apply a phandle change to a list of lines """ | |
| line_num, byte, phandle_node = change | |
| # print(f"Changing {lines[line_num]!r} phandle {byte} to {phandle_node}") | |
| lines[line_num] = lines[line_num].replace(byte, phandle_node) | |
| for change in phandle_changes: | |
| from_change, to_change = change | |
| apply_change(from_lines, from_change) | |
| apply_change(to_lines, to_change) | |
| def main(): | |
| # Check for 2 arguments | |
| if len(sys.argv) != 3: | |
| sys.stderr.write(f"Usage: {sys.argv[0]} <file1> <file2>\n") | |
| file1 = sys.argv[1] | |
| file2 = sys.argv[2] | |
| with open(file1, encoding='utf-8') as fromf: | |
| from_lines = fromf.readlines() | |
| with open(file2, encoding='utf-8') as tof: | |
| to_lines = tof.readlines() | |
| from_phandle_dict = create_phandle_dict(from_lines) | |
| to_phandle_dict = create_phandle_dict(to_lines) | |
| # pprint.pprint(from_phandle_dict) | |
| # pprint.pprint(to_phandle_dict) | |
| phandle_changes = get_phandle_changes(from_lines, to_lines, from_phandle_dict, to_phandle_dict) | |
| # pprint.pprint(phandle_changes) | |
| apply_phandle_changes(from_lines, to_lines, phandle_changes) | |
| # Output unified diff | |
| diff = difflib.unified_diff(from_lines, to_lines, file1, file2) | |
| sys.stdout.writelines(diff) | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment