-
Notifications
You must be signed in to change notification settings - Fork 0
/
list-make-prerequisites.py
executable file
·175 lines (136 loc) · 5.28 KB
/
list-make-prerequisites.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/usr/bin/env python3
#
# Author: Luca Colagrande
from anytree import Node, RenderTree
import argparse
import hashlib
from pathlib import Path
import re
import subprocess
import sys
# Parse arguments from the command line
def parse_args():
parser = argparse.ArgumentParser(
description="List prerequisites for a specified Makefile target.")
parser.add_argument(
'target',
help="The make target whose prerequisites you want to list")
parser.add_argument(
'-r',
'--recursive',
action='store_true',
help="Recursively list all prerequisites")
parser.add_argument(
'--hash',
action='store_true',
help="Generate a hash from the contents of all prerequisites")
parser.add_argument(
'-d',
'--debug',
action='store_true',
help="Enable debug output to show intermediate steps")
return parser.parse_args()
# Function to parse all rules from 'make -pq' output
def _parse_makefile():
try:
# Run 'make -pq' and capture its output
result = subprocess.run(
['make', '-pq'],
capture_output=True,
text=True)
# Initialize an empty dictionary to store the targets and their prerequisites
targets = {}
# Split the output by lines
make_output = result.stdout.splitlines()
# Regex to capture target and its prerequisites
target_pattern = re.compile(r'^([^\s]+)\s*:\s*([^\|]*)')
# Parse the make output
for line in make_output:
match = target_pattern.match(line)
if match:
target = match.group(1)
prerequisites = match.group(2).split()
targets[target] = prerequisites
return targets
except Exception as e:
print(f"An error occurred: {e}")
sys.exit(1)
# Internal function used for recursion. Takes additional arguments to keep track of state.
def _get_prerequisites_recursive(target, targets, recursive=False):
# Get the prerequisites for the target
prerequisites = targets.get(target.name, [])
# In non-recursive mode, attach prerequisites to root and that's it
if not recursive:
[Node(prereq, parent=target) for prereq in prerequisites]
# In recursive mode, if we are at a leaf node, we return the leaf node
# otherwise we attach the current prerequisite node to the parent
else:
if prerequisites:
for prereq in prerequisites:
# If recursive, get prerequisites for each prerequisite
if recursive:
_get_prerequisites_recursive(
Node(prereq, parent=target),
targets,
recursive=True)
else:
return target
# Function to list prerequisites, optionally recursively
def list_prerequisites(target, recursive=False, debug=False):
# Parse the makefile
targets = _parse_makefile()
# Get the prerequisites for the user-provided target, if it exists
if target in targets:
# Call the function with the user-provided target and recursive/debug flags
root = Node(target)
_get_prerequisites_recursive(
root,
targets,
recursive=recursive)
# Print tree structure for debugging
if debug:
for pre, fill, node in RenderTree(root):
print(f"{pre}{node.name}")
# Print a newline to separate debugging output from regular output
print("")
# Find all leaf prerequisites
leaf_prerequisites = [node.name for node in root.descendants if node.is_leaf]
# Remove repetitions and sort for consistency
leaf_prerequisites = sorted(list(set(leaf_prerequisites)))
# Return prerequisites
return leaf_prerequisites
else:
print(f"Target '{args.target}' not found in the Makefile.")
sys.exit(1)
def hash_files(file_list):
# Create a new SHA-256 hash object
hasher = hashlib.sha256()
# If any prerequisite is a directory, replace it with its (recursive) contents
expanded_file_list = []
for file_name in file_list:
file = Path(file_name)
if file.is_dir():
expanded_file_list.extend([str(f) for f in file.rglob('*') if f.is_file()])
else:
expanded_file_list.append(file_name)
# Hash file contents
for file_name in expanded_file_list:
try:
with open(file_name, 'rb') as f:
# Read the file content in chunks to avoid memory issues with large files
while chunk := f.read(8192): # 8192 bytes per chunk
hasher.update(chunk) # Update the hash with each chunk
except FileNotFoundError:
print(f"File '{file_name}' not found.", file=sys.stderr)
# Return the final hexadecimal hash value
return hasher.hexdigest()
if __name__ == "__main__":
args = parse_args()
prerequisites = list_prerequisites(args.target, recursive=args.recursive, debug=args.debug)
if args.hash:
# Print a hash of the prerequisites' contents
hash_value = hash_files(prerequisites)
print(hash_value)
else:
# Print the list of prerequisites
print('\n'.join(prerequisites))