blob: c4ee1aeb1209b9df45112789c720f2cee8d5e526 [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001import os
2import re
3import shutil
4import subprocess
5import tempfile
6
7
8def clone_repo(url, treeish):
9 """Clones a git repo at the given URL into a temp folder and checks out the
10 given tree-ish (either branch or tag).
11
12 The current working directory will be set to the repository folder.
13
14 Keyword argument:
15 url -- The URL of the git repo
16 treeish -- The tree-ish to check out (branch or tag)
17 """
18 os.chdir(tempfile.gettempdir())
19
20 repo = os.path.basename(url)
21 dest = os.path.join(os.getcwd(), repo).removesuffix(".git")
22
23 # Clone Git repository into current directory or update it
24 if not os.path.exists(dest):
25 subprocess.run(["git", "clone", url, dest])
26 os.chdir(dest)
27 else:
28 os.chdir(dest)
29 subprocess.run(["git", "fetch", "origin", treeish])
30
31 # Get list of heads
32 # Example output of "git ls-remote --heads":
33 # From https://gitlab.com/libeigen/eigen.git
34 # 77c66e368c7e355f8be299659f57b0ffcaedb505 refs/heads/3.4
35 # 3e006bfd31e4389e8c5718c30409cddb65a73b04 refs/heads/master
36 ls_out = subprocess.check_output(["git", "ls-remote",
37 "--heads"]).decode().rstrip()
38 heads = [x.split()[1] for x in ls_out.split("\n")[1:]]
39
40 if f"refs/heads/{treeish}" in heads:
41 # Checking out the remote branch avoids needing to handle syncing a
42 # preexisting local one
43 subprocess.run(["git", "checkout", f"origin/{treeish}"])
44 else:
45 subprocess.run(["git", "checkout", treeish])
46
47
48def get_repo_root():
49 """Returns the Git repository root as an absolute path.
50
51 An empty string is returned if no repository root was found.
52 """
53 current_dir = os.path.abspath(os.getcwd())
54 while current_dir != os.path.dirname(current_dir):
55 if os.path.exists(current_dir + os.sep + ".git"):
56 return current_dir
57 current_dir = os.path.dirname(current_dir)
58 return ""
59
60
61def setup_upstream_repo(url, treeish):
62 """Clones the given upstream repository, then returns the root of the
63 destination Git repository as well as the cloned upstream Git repository.
64
65 The current working directory will be set to the cloned upstream repository
66 folder.
67
68 Keyword arguments:
69 url -- The URL of the git repo
70 treeish -- The tree-ish to check out (branch or tag)
71
72 Returns:
73 root -- root directory of destination Git repository
74 repo -- root directory of cloned upstream Git repository
75 """
76 root = get_repo_root()
77 clone_repo(url, treeish)
78 return root, os.getcwd()
79
80
81def walk_if(top, pred):
82 """Walks the current directory, then returns a list of files for which the
83 given predicate is true.
84
85 Keyword arguments:
86 top -- the top directory to walk
87 pred -- a function that takes a directory path and a filename, then returns
88 True if the file should be included in the output list
89 """
90 return [
91 os.path.join(dp, f)
92 for dp, dn, fn in os.walk(top)
93 for f in fn
94 if pred(dp, f)
95 ]
96
97
98def copy_to(files, root):
99 """Copies list of files to root by appending the relative paths of the files
100 to root.
101
102 The leading directories of root will be created if they don't yet exist.
103
104 Keyword arguments:
105 files -- list of files to copy
106 root -- destination
107
108 Returns:
109 The list of files in their destination.
110 """
111 if not os.path.exists(root):
112 os.makedirs(root)
113
114 dest_files = []
115 for f in files:
116 dest_file = os.path.join(root, f)
117
118 # Rename .cc file to .cpp
119 if dest_file.endswith(".cc"):
120 dest_file = os.path.splitext(dest_file)[0] + ".cpp"
121
122 # Make leading directory
123 dest_dir = os.path.dirname(dest_file)
124 if not os.path.exists(dest_dir):
125 os.makedirs(dest_dir)
126
127 shutil.copyfile(f, dest_file)
128 dest_files.append(dest_file)
129 return dest_files
130
131
132def walk_cwd_and_copy_if(pred, root):
133 """Walks the current directory, generates a list of files for which the
134 given predicate is true, then copies that list to root by appending the
135 relative paths of the files to root.
136
137 The leading directories of root will be created if they don't yet exist.
138
139 Keyword arguments:
140 pred -- a function that takes a directory path and a filename, then returns
141 True if the file should be included in the output list
142 root -- destination
143
144 Returns:
145 The list of files in their destination.
146 """
147 files = walk_if(".", pred)
148 files = copy_to(files, root)
149 return files
150
151
152def comment_out_invalid_includes(filename, include_roots):
153 """Comment out #include directives that include a nonexistent file
154
155 Keyword arguments:
156 filename -- file to search for includes
157 include_roots -- list of search paths for includes
158 """
159 # Read header
160 with open(filename) as f:
161 old_contents = f.read()
162
163 new_contents = ""
164 pos = 0
165 for match in re.finditer(r"#include \"([^\"]+)\"", old_contents):
166 include = match.group(1)
167
168 # Write contents from before this match
169 new_contents += old_contents[pos:match.span()[0]]
170
171 # Comment out #include if the file doesn't exist in current directory or
172 # include root
173 if not os.path.exists(os.path.join(
174 os.path.dirname(filename), include)) and not any(
175 os.path.exists(os.path.join(include_root, include))
176 for include_root in include_roots):
177 new_contents += "// "
178
179 new_contents += match.group()
180 pos = match.span()[1]
181
182 # Write rest of file if it wasn't all processed
183 if pos < len(old_contents):
184 new_contents += old_contents[pos:]
185
186 # Write modified file back out
187 if old_contents != new_contents:
188 with open(filename, "w") as f:
189 f.write(new_contents)
190
191
192def apply_patches(root, patches):
193 """Apply list of patches to the destination Git repository.
194
195 Keyword arguments:
196 root -- the root directory of the destination Git repository
197 patches -- list of patch files relative to the root
198 """
199 os.chdir(root)
200 for patch in patches:
201 subprocess.check_output(["git", "apply", patch])