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