makeutils.py 3.6 KB

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