blob: 14fb1cf646d3a2aeea855fea873bba0bc3a5dc41 [file] [log] [blame] [edit]
#!/bin/env python3
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This script is used to reorder the #pragma allow_unsafe_buffers and #include
# <array> in the files that have been modified by the current patch. This is
# done to allow clang format to better format the #includes as it can't reorder
# through the macros.
import sys
import os
class ReorderTarget:
def __find_line_numbers(self):
# Do we have any `#include`s above
# * `#pragma allow_unsafe_buffers` or
# * the header guard (if in a header file)?
in_opt_out = False
guard_line = None
pragma_end = None
for i, unstripped_line in enumerate(self.lines):
line = unstripped_line.strip()
if line in self.lines_to_reorder:
# If we come across a duplicate `#include`, it's
# probably an existing one, and we should leave it alone.
# The `#include`s that spanify emits should be the
# highest-up.
if self.lines_to_reorder[line] is not None:
continue
self.lines_to_reorder[line] = i
elif '#ifdef UNSAFE_BUFFERS_BUILD' in line:
in_opt_out = True
elif in_opt_out and '#endif' in line:
pragma_end = i
in_opt_out = False
elif line == self.guard_format:
guard_line = i
# If we have both a pragma and a guard, we want to insert _after_ both.
# However if we only have either pragma or guard we insert after
# whichever is present.
try:
self.insertion_point = max(pragma_end, guard_line)
except TypeError:
self.insertion_point = pragma_end or guard_line
if self.insertion_point is None:
return
self.lines_to_reorder = {
k: v
for (k, v) in self.lines_to_reorder.items()
if v is not None and v < self.insertion_point
}
def __init__(self, path):
self.lines = None
self.lines_to_reorder = {
'#include <array>': None,
'#include <cstdint>': None,
'#include "base/containers/auto_spanification_helper.h"': None,
'#include "base/containers/span.h"': None,
'#include "base/memory/raw_span.h"': None,
'#include "base/numerics/safe_conversions.h"': None,
}
self.insertion_point = None
self.guard_format = self._compute_guard_format(path)
try:
with open(path, 'r') as f:
self.lines = f.readlines()
except FileNotFoundError:
return # Skip files that were deleted.
self.__find_line_numbers()
def _compute_guard_format(self, path):
# The guard format is the path to the file with underscores instead of
# slashes and in uppercase with a trailing underscore.
guard_format = path.upper().replace('/', '_').replace('.', '_') + '_'
return f'#define {guard_format}'
def should_reorder(self):
# Deleted file.
if self.lines is None:
return False
# If there were no pragmas or header guards, then
# `git cl format` should not be confused.
if self.insertion_point is None:
return False
return bool(self.lines_to_reorder)
def reorder_pragma_and_includes(path):
target = ReorderTarget(path)
if not target.should_reorder():
return
# Entering this block means there _is_ something to reorder.
# 1. The `#pragma` line exists. We _will_ pass through it as
# we traverse the file.
# 2. `target.lines_to_reorder` is a nonempty dict.
with open(path, 'w') as f:
for (line_number, line) in enumerate(target.lines):
# Write out all lines except for the overly-high-up `#include`s
# until we pass the the `UNSAFE_BUFFERS_BUILD` macro and the HEADER
# guards (if present).
if line_number < target.insertion_point:
if line.strip() not in target.lines_to_reorder:
f.write(line)
continue
if line_number == target.insertion_point:
f.write(line)
for to_reorder in target.lines_to_reorder:
f.write("\n")
f.write(to_reorder)
f.write("\n")
continue
# We have passed the `#pragma` and any header guards (if present)
# and can mindlessly spit out every subsequent line.
assert line_number > target.insertion_point
f.write(line)
def main():
modified_files = [
f for f in os.popen("git diff --name-only HEAD~..HEAD").read().split(
"\n") if f
]
for file in modified_files:
reorder_pragma_and_includes(file)
os.system("git cl format")
if __name__ == "__main__":
sys.exit(main())