-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathupdate-z3-code.py
253 lines (178 loc) · 6.22 KB
/
update-z3-code.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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# Requires Python 3.10
import argparse
import os
import shutil
import subprocess
import sys
import stat
from typing import Callable
from pathlib import Path
from os import PathLike
from utils.cli.cli_printing import print_stage_name
from utils.cli.console_color import ConsoleColor
# -----
# Utility functions
# -----
def echo_call(bin: PathLike | str, *args: str):
print(">", str(bin), *args)
def run(
bin_name: str,
*args: str,
cwd: str | Path | None = None,
echo: bool = True,
silent: bool = False
):
if echo:
echo_call(bin_name, *args)
if silent:
subprocess.check_output([bin_name] + list(args), cwd=cwd)
else:
subprocess.check_call([bin_name] + list(args), cwd=cwd)
def git(command: str, *args: str, cwd: str | Path | None = None, echo: bool = True):
run("git", command, *args, cwd=cwd, echo=echo)
def git_output(*args: str, cwd: str | None = None, echo: bool = True) -> str:
if echo:
echo_call("git", *args)
return subprocess.check_output(["git"] + list(args), cwd=cwd).decode("UTF8")
def path(root: PathLike | str, *args: PathLike | str) -> Path:
return Path(root).joinpath(*args)
def cwd_path(*args: PathLike | str) -> Path:
return path(Path.cwd(), *args)
# From: https://github.com/gitpython-developers/GitPython/blob/ea43defd777a9c0751fc44a9c6a622fc2dbd18a0/git/util.py#L101
# Windows has issues deleting readonly files that git creates
def git_rmtree(path: os.PathLike) -> None:
"""Remove the given recursively.
:note: we use shutil rmtree but adjust its behaviour to see whether files that
couldn't be deleted are read-only. Windows will not remove them in that case"""
def onerror(func: Callable, path: os.PathLike, _) -> None:
# Is the error an access error ?
os.chmod(path, stat.S_IWUSR)
try:
func(path) # Will scream if still not possible to delete.
except Exception:
raise
return shutil.rmtree(path, False, onerror)
# ----
# Main logic
# ----
Z3_REPO = "https://github.com/Z3Prover/z3.git"
Z3_TARGET_PATH = cwd_path("Sources", "CZ3")
TEMP_FOLDER_NAME = "temp"
def create_temporary_folder() -> Path:
temp_path = cwd_path(TEMP_FOLDER_NAME)
if temp_path.exists():
git_rmtree(temp_path)
os.mkdir(temp_path)
return temp_path
def clone_repo(tag_or_branch: str | None, repo: str, clone_path: str):
if tag_or_branch is None:
git("clone", repo, "--depth=1", clone_path)
else:
git("clone", repo, clone_path)
git("checkout", tag_or_branch, cwd=clone_path)
def clone_z3(tag_or_branch: str | None, base_folder: Path) -> Path:
print_stage_name("Cloning Z3...")
z3_clone_path = str(path(base_folder, "z3").absolute())
clone_repo(tag_or_branch, Z3_REPO, z3_clone_path)
return Path(z3_clone_path)
def backup_includes(from_path: Path, to_path: Path):
if to_path.exists():
shutil.rmtree(to_path)
shutil.copytree(from_path, to_path)
def initialize_z3(clone_path: Path):
run("python", "scripts/mk_make.py", cwd=clone_path)
def copy_repo_files(source_files: Path, target_path: Path):
# Backup includes
include_target_path = target_path.joinpath("include")
include_backup_path = source_files.joinpath("include")
backup_includes(include_target_path, include_backup_path)
# Erase files and copy over
shutil.rmtree(target_path)
shutil.copytree(source_files, target_path)
def copy_z3_files(clone_path: Path, target_path: Path):
print_stage_name("Copying over Z3 files...")
copy_repo_files(clone_path.joinpath("src"), target_path)
def remove_extra_z3_files(target_path: Path):
print_stage_name("Removing extra files...")
folders = [
"api/c++",
"api/dotnet",
"api/julia",
"api/java",
"api/js",
"api/ml",
"api/python",
"shell",
"test",
]
globs = [
"**/CMakeLists.txt",
"**/*.txt",
"**/*.pyg",
"**/*.cmake.in",
"**/*.h.in",
"**/*.smt",
"**/*.smt2",
"**/*.disabled",
"**/README",
"**/xor_solver.d",
]
for folder in folders:
folder_path = target_path.joinpath(folder)
git_rmtree(folder_path)
for glob in globs:
for f in target_path.glob(glob):
os.remove(f)
def update_code(z3_tag_or_branch: str | None, force: bool) -> int:
if (not force) and len(git_output("status", "--porcelain", echo=False).strip()) > 0:
print(
ConsoleColor.RED(
"Current git repo's state is not committed! Please commit and try again. (override with --force)"
)
)
return 1
# Create temp path
temp_path = create_temporary_folder()
# Clone
z3_clone_path = clone_z3(z3_tag_or_branch, temp_path)
# Initialize
initialize_z3(z3_clone_path)
# Copy files
copy_z3_files(z3_clone_path, Z3_TARGET_PATH)
remove_extra_z3_files(Z3_TARGET_PATH)
print(ConsoleColor.GREEN("Success!"))
git_status = git_output("status", "--porcelain").strip()
if len(git_status) > 0:
print(ConsoleColor.YELLOW("New unstaged changes:"))
print(git_status)
git_rmtree(temp_path)
return 0
# -----
# Entry point
# -----
def main() -> int:
def make_argparser() -> argparse.ArgumentParser:
argparser = argparse.ArgumentParser()
argparser.add_argument(
"-b",
"--z3_tag",
type=str,
help="A tag or branch to clone from the Z3 repository. If not provided, defaults to latest commit of default branch.",
)
argparser.add_argument(
"-f",
"--force",
action="store_true",
help="Whether to ignore non-committed state of the repository. By default, the script fails if the repository has changes that are not committed to avoid conflicts and unintended changes.",
)
return argparser
argparser = make_argparser()
args = argparser.parse_args()
return update_code(args.z3_tag, args.force)
if __name__ == "__main__":
try:
sys.exit(main())
except subprocess.CalledProcessError as err:
sys.exit(err.returncode)
except KeyboardInterrupt:
sys.exit(1)