makeutils.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from io import open
  2. from os.path import isdir
  3. import re
  4. def filterLines(lines, regex):
  5. '''Filters each line of the given line iterator using the given regular
  6. expression string. For each match, a tuple containing the text matching
  7. each capture group from the regular expression is yielded.
  8. '''
  9. matcher = re.compile(regex)
  10. for line in lines:
  11. if line.endswith('\n'):
  12. line = line[ : -1]
  13. match = matcher.match(line)
  14. if match:
  15. yield match.groups()
  16. def filterFile(filePath, regex):
  17. '''Filters each line of the given text file using the given regular
  18. expression string. For each match, a tuple containing the text matching
  19. each capture group from the regular expression is yielded.
  20. '''
  21. with open(filePath, 'r', encoding='utf-8') as inp:
  22. for groups in filterLines(inp, regex):
  23. yield groups
  24. def joinContinuedLines(lines):
  25. '''Iterates through the given lines, replacing lines that are continued
  26. using a trailing backslash with a single line.
  27. '''
  28. buf = ''
  29. for line in lines:
  30. if line.endswith('\\\n'):
  31. buf += line[ : -2]
  32. elif line.endswith('\\'):
  33. buf += line[ : -1]
  34. else:
  35. yield buf + line
  36. buf = ''
  37. if buf:
  38. raise ValueError('Continuation on last line')
  39. _reEval = re.compile('(\$\(|\))')
  40. def evalMakeExpr(expr, makeVars):
  41. '''Evaluates variable references in an expression.
  42. Raises ValueError if there is a syntax error in the expression.
  43. Raises KeyError if the expression references a non-existing variable.
  44. '''
  45. stack = [ [] ]
  46. for part in _reEval.split(expr):
  47. if part == '$(':
  48. stack.append([])
  49. elif part == ')' and len(stack) != 1:
  50. name = ''.join(stack.pop())
  51. if name.startswith('addprefix '):
  52. prefix, args = name[len('addprefix') : ].split(',')
  53. prefix = prefix.strip()
  54. value = ' '.join(prefix + arg for arg in args.split())
  55. elif name.startswith('addsuffix '):
  56. suffix, args = name[len('addsuffix') : ].split(',')
  57. suffix = suffix.strip()
  58. value = ' '.join(arg + suffix for arg in args.split())
  59. elif name.startswith('shell '):
  60. # Unsupported; assume result is never used.
  61. value = '?'
  62. elif name.isdigit():
  63. # This is a function argument; assume the evaluated result is
  64. # never used.
  65. value = '?'
  66. else:
  67. value = makeVars[name]
  68. stack[-1].append(value)
  69. else:
  70. stack[-1].append(part)
  71. if len(stack) != 1:
  72. raise ValueError('Open without close in "%s"' % expr)
  73. return ''.join(stack.pop())
  74. def extractMakeVariables(filePath, makeVars = None):
  75. '''Extract all variable definitions from the given Makefile.
  76. The optional makeVars argument is a dictionary containing the already
  77. defined variables. These variables will be included in the output; the
  78. given dictionary is not modified.
  79. Returns a dictionary that maps each variable name to its value.
  80. '''
  81. makeVars = {} if makeVars is None else dict(makeVars)
  82. with open(filePath, 'r', encoding='utf-8') as inp:
  83. for name, assign, value in filterLines(
  84. joinContinuedLines(inp),
  85. r'[ ]*([A-Za-z0-9_]+)[ ]*([+:]?=)(.*)'
  86. ):
  87. if assign == '=':
  88. makeVars[name] = value.strip()
  89. elif assign == ':=':
  90. makeVars[name] = evalMakeExpr(value, makeVars).strip()
  91. elif assign == '+=':
  92. # Note: Make will or will not evaluate the added expression
  93. # depending on how the variable was originally defined,
  94. # but we don't store that information.
  95. makeVars[name] = makeVars[name] + ' ' + value.strip()
  96. else:
  97. assert False, assign
  98. return makeVars
  99. def parseBool(valueStr):
  100. '''Parses a string containing a boolean value.
  101. Accepted values are "true" and "false"; anything else raises ValueError.
  102. '''
  103. if valueStr == 'true':
  104. return True
  105. elif valueStr == 'false':
  106. return False
  107. else:
  108. raise ValueError('Invalid boolean "%s"' % valueStr)