sort-makefile-lines.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. #!/usr/bin/python3
  2. # Sort Makefile lines as expected by project policy.
  3. # Copyright (C) 2023-2026 Free Software Foundation, Inc.
  4. # This file is part of the GNU C Library.
  5. #
  6. # The GNU C Library is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU Lesser General Public
  8. # License as published by the Free Software Foundation; either
  9. # version 2.1 of the License, or (at your option) any later version.
  10. #
  11. # The GNU C Library is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. # Lesser General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Lesser General Public
  17. # License along with the GNU C Library; if not, see
  18. # <https://www.gnu.org/licenses/>.
  19. # The project consensus is to split Makefile variable assignment
  20. # across multiple lines with one value per line. The values are
  21. # then sorted as described below, and terminated with a special
  22. # list termination marker. This splitting makes it much easier
  23. # to add new tests to the list since they become just a single
  24. # line insertion. It also makes backports and merges easier
  25. # since the new test may not conflict due to the ordering.
  26. #
  27. # Consensus discussion:
  28. # https://inbox.sourceware.org/libc-alpha/f6406204-84f5-adb1-d00e-979ebeebbbde@redhat.com/
  29. #
  30. # To support cleaning up Makefiles we created this program to
  31. # help sort existing lists converted to the new format.
  32. #
  33. # The program takes as input the Makefile to sort correctly,
  34. # and the output file to write the correctly sorted output
  35. # (it can be the same file).
  36. #
  37. # Sorting is only carried out between two special markers:
  38. # (a) Marker start is '<variable> += \' (or '= \', or ':= \')
  39. # (b) Marker end is ' # <variable>' (whitespace matters)
  40. # With everything between (a) and (b) being sorted accordingly.
  41. #
  42. # You can use it like this:
  43. # $ scripts/sort-makefile-lines.py < elf/Makefile > elf/Makefile.tmp
  44. # $ mv elf/Makefile.tmp elf/Makefile
  45. #
  46. # The Makefile lines in the project are sorted using the
  47. # following rules:
  48. # - All lines are sorted as-if `LC_COLLATE=C sort`
  49. # - Lines that have a numeric suffix and whose leading prefix
  50. # matches exactly are sorted according the numeric suffix
  51. # in increasing numerical order.
  52. #
  53. # For example:
  54. # ~~~
  55. # tests += \
  56. # test-a \
  57. # test-b \
  58. # test-b1 \
  59. # test-b2 \
  60. # test-b10 \
  61. # test-b20 \
  62. # test-b100 \
  63. # # tests
  64. # ~~~
  65. # This example shows tests sorted alphabetically, followed
  66. # by a numeric suffix sort in increasing numeric order.
  67. #
  68. # Cleanups:
  69. # - Tests that end in "a" or "b" variants should be renamed to
  70. # end in just the numerical value. For example 'tst-mutex7robust'
  71. # should be renamed to 'tst-mutex12' (the highest numbered test)
  72. # or 'tst-robust11' (the highest numbered test) in order to get
  73. # reasonable ordering.
  74. # - Modules that end in "mod" or "mod1" should be renamed. For
  75. # example 'tst-atfork2mod' should be renamed to 'tst-mod-atfork2'
  76. # (test module for atfork2). If there are more than one module
  77. # then they should be named with a suffix that uses [0-9] first
  78. # then [A-Z] next for a total of 36 possible modules per test.
  79. # No manually listed test currently uses more than that (though
  80. # automatically generated tests may; they don't need sorting).
  81. # - Avoid including another test and instead refactor into common
  82. # code with all tests including the common code, then give the
  83. # tests unique names.
  84. #
  85. # If you have a Makefile that needs converting, then you can
  86. # quickly split the values into one-per-line, ensure the start
  87. # and end markers are in place, and then run the script to
  88. # sort the values.
  89. import sys
  90. import locale
  91. import re
  92. import functools
  93. def glibc_makefile_numeric(string1, string2):
  94. # Check if string1 has a numeric suffix.
  95. var1 = re.search(r'([0-9]+) \\$', string1)
  96. var2 = re.search(r'([0-9]+) \\$', string2)
  97. if var1 and var2:
  98. if string1[0:var1.span()[0]] == string2[0:var2.span()[0]]:
  99. # string1 and string2 both share a prefix and
  100. # have a numeric suffix that can be compared.
  101. # Sort order is based on the numeric suffix.
  102. # If the suffix is the same return 0, otherwise
  103. # > 0 for greater-than, and < 0 for less-than.
  104. # This is equivalent to the numerical difference.
  105. return int(var1.group(1)) - int(var2.group(1))
  106. # Default to strcoll.
  107. return locale.strcoll(string1, string2)
  108. def sort_lines(lines):
  109. # Use the C locale for language independent collation.
  110. locale.setlocale (locale.LC_ALL, "C")
  111. # Sort using a glibc-specific sorting function.
  112. lines = sorted(lines, key=functools.cmp_to_key(glibc_makefile_numeric))
  113. return lines
  114. def sort_makefile_lines():
  115. # Read the whole Makefile.
  116. lines = sys.stdin.readlines()
  117. # Build a list of all start markers (tuple includes name).
  118. startmarks = []
  119. for i in range(len(lines)):
  120. # Look for things like "var = \", "var := \" or "var += \"
  121. # to start the sorted list.
  122. var = re.search(r'^([-_a-zA-Z0-9]*) [\+:]?\= \\$', lines[i])
  123. if var:
  124. # Remember the index and the name.
  125. startmarks.append((i, var.group(1)))
  126. # For each start marker try to find a matching end mark
  127. # and build a block that needs sorting. The end marker
  128. # must have the matching comment name for it to be valid.
  129. rangemarks = []
  130. for sm in startmarks:
  131. # Look for things like " # var" to end the sorted list.
  132. reg = r'^ *# ' + sm[1] + r'$'
  133. for j in range(sm[0] + 1, len(lines)):
  134. if re.search(reg, lines[j]):
  135. # Remember the block to sort (inclusive).
  136. rangemarks.append((sm[0] + 1, j))
  137. break
  138. # We now have a list of all ranges that need sorting.
  139. # Sort those ranges (inclusive).
  140. for r in rangemarks:
  141. lines[r[0]:r[1]] = sort_lines(lines[r[0]:r[1]])
  142. # Output the whole list with sorted lines to stdout.
  143. [sys.stdout.write(line) for line in lines]
  144. def main(argv):
  145. sort_makefile_lines ()
  146. if __name__ == '__main__':
  147. main(sys.argv[1:])