-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
Expand file tree
/
Copy pathprepare_lief.py
More file actions
310 lines (273 loc) Β· 12.4 KB
/
prepare_lief.py
File metadata and controls
310 lines (273 loc) Β· 12.4 KB
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
#!/usr/bin/env python3
import argparse
import re
import shutil
import sys
import tempfile
import zipfile
from pathlib import Path
def ensure_dir(p: Path):
p.mkdir(parents=True, exist_ok=True)
def extract_zip(prefix: str, zip_path: Path, dst_third_party: Path):
"""Extract a third-party archive into dst_third_party/prefix.
Typical structure inside the archive is a single top-level directory like
"mbedtls-<ver>/". We extract under dst_third_party, then rename that
directory to a stable name dst_third_party/prefix.
"""
ensure_dir(dst_third_party)
with zipfile.ZipFile(zip_path, 'r') as zf:
# Determine the top-level directory inside the archive
names = zf.namelist()
top_candidates = {n.split('/', 1)[0] for n in names if '/' in n}
top_dir = next((n for n in sorted(top_candidates) if n.startswith(f"{prefix}-")), None)
if top_dir is None: # There is no versioned top-level dir, create one
top_dir = prefix
extract_parent_dir = dst_third_party / prefix
if not extract_parent_dir.exists():
extract_parent_dir.mkdir()
else:
extract_parent_dir = dst_third_party
print(f"Extracting {zip_path} -> {extract_parent_dir} (top_dir: {top_dir})")
zf.extractall(extract_parent_dir)
extracted_dir = dst_third_party / top_dir
target_dir = dst_third_party / prefix
if extracted_dir.exists() and extracted_dir != target_dir:
if target_dir.exists():
shutil.rmtree(target_dir)
extracted_dir.rename(target_dir)
def find_single_subdir(root: Path) -> Path:
entries = [p for p in root.iterdir() if p.is_dir()]
if len(entries) == 1:
return entries[0]
# Try to pick the most likely one by name length (zip usually extracts into a single dir)
if entries:
return sorted(entries, key=lambda p: len(p.name))[0]
return root
def cmake_configure_in(template: str, values: dict) -> str:
# Handle @VAR@ replacement and #cmakedefine VAR @VAR@
def repl_at(m):
var = m.group(1)
return str(values.get(var, ""))
# Replace @VAR@
s = re.sub(r"@([A-Za-z0-9_]+)@", repl_at, template)
# Process #cmakedefine lines
out_lines = []
for line in s.splitlines():
m = re.match(r"\s*#cmakedefine\s+([A-Za-z0-9_]+)\s+(\S+)", line)
if m:
name = m.group(1)
var_token = m.group(2)
# var_token was already replaced above (may still be a token if not found)
# try to look it up from values if it's still in @VAR@ format
val = 0
if var_token.startswith("@") and var_token.endswith("@"):
key = var_token.strip("@")
val = int(values.get(key, 0) or 0)
else:
try:
val = int(var_token)
except Exception:
val = 0
if val:
out_lines.append(f"#define {name} {val}")
else:
out_lines.append(f"/* #undef {name} */")
else:
out_lines.append(line)
return "\n".join(out_lines) + "\n"
def write_file(path: Path, content: str):
ensure_dir(path.parent)
with open(path, 'w', encoding='utf-8') as f:
f.write(content)
def parse_version_tuple(version: str):
m = re.match(r"^(\d+)\.(\d+)\.(\d+)$", version)
if not m:
return (0, 0, 0)
return tuple(int(x) for x in m.groups())
def main():
ap = argparse.ArgumentParser(description='Prepare LIEF third-party and configured headers for checked-in sources')
ap.add_argument('--source-dir', required=True, help='Path to the unzipped LIEF source directory (e.g. /tmp/LIEF-<ver>/)')
ap.add_argument('--lief-dir', required=True, help='Target path to deps/LIEF (the vendored destination in this repo)')
ap.add_argument('--version', help='LIEF version (X.Y.Z). If omitted, try to infer a fallback (0.17.0).')
ap.add_argument('--commit', default='', help='Commit hash to embed into version.h (optional)')
ap.add_argument('--tag', default='', help='Git tag to embed into version.h (optional)')
args = ap.parse_args()
source_dir = Path(args.source_dir).resolve()
lief_dir = Path(args.lief_dir).resolve()
if not source_dir.exists():
print(f"source-dir does not exist: {source_dir}", file=sys.stderr)
return 1
if not lief_dir.exists():
ensure_dir(lief_dir)
# 1) Copy only the relevant files from source_dir to lief_dir
included_dirs = ['src', 'include', 'config', 'README.md', 'LICENSE']
for item in included_dirs:
src = source_dir / item
dst = lief_dir / item
if not src.exists():
print(f"Warning: expected item missing: {src}", file=sys.stderr)
continue
print(f"Copying {src} -> {dst}")
if src.is_dir():
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(src, dst)
else:
shutil.copy2(src, dst)
# 2) Extract selected third-party zips into third-party/<base>
# Keep this generic via a small allowlist so we can extend later if needed.
# For now, we only extract 'mbedtls'.
src_third_party = source_dir / 'third-party'
dst_third_party = lief_dir / 'third-party'
ensure_dir(dst_third_party)
# Use a temporary third party directory, then move needed files to lief_dir
tmp_third_party = Path(tempfile.mkdtemp())
# For example, src_third_party/mbedtls-<ver>.zip contains mbedtls-<ver>/,
# we extract that to tmp_third_party/mbedtls, and in the extracted directory, we
# only want to copy the 'library' folder, so tmp_third_party/mbedtls/library ->
# dst_third_party/mbedtls/library. Any other files under the extracted mbedtls
# directory will be ignored.
libs_to_extract = {
'mbedtls': ['library', 'include', 'README.md', 'LICENSE'], # #include "mbedtls/private_access.h"
'spdlog': ['include', 'src', 'LICENSE', 'README.md'], # #include "spdlog/fmt/..."
'frozen': ['include', 'LICENSE', 'README.rst'], # #include <frozen/map.h>
'expected': ['include', 'README.md', 'COPYING'], # #include <LIEF/third-party/internal/expected.hpp>
'utfcpp': ['source', 'README.md', 'LICENSE'], # #include <internal/utfcpp/utf8/unchecked.h> | #include "utf8/unchecked.h"
'tcb-span': ['span.hpp'], # #include <LIEF/third-party/internal/span.hpp>
}
extracted_libs = []
with tempfile.TemporaryDirectory() as _tmp_dir:
tmp_third_party = Path(_tmp_dir)
zip_files = sorted(src_third_party.glob('*.zip'))
for lib, included in libs_to_extract.items():
# Find the matching zip file that starts with lib name
zip_path = next((z for z in zip_files if z.name.startswith(f"{lib}-")), None)
if zip_path is None:
print(f"Warning: No archive found for '{lib}' in {src_third_party}", file=sys.stderr)
continue
# Extract archive into a tmp location under tmp_third_party/{lib}
extract_zip(lib, zip_path, tmp_third_party)
extracted_libs.append(lib)
# Move the allow-listed directories from each lib in tmp_third_party to dst_third_party
src = tmp_third_party / lib
dst = dst_third_party / lib
for subpath in included:
src_item = src / subpath
dst_item = dst / subpath
if not src_item.exists():
print(f"Warning: expected item missing: {src_item}", file=sys.stderr)
continue
ensure_dir(dst_item.parent)
if dst_item.exists():
if dst_item.is_dir():
shutil.rmtree(dst_item)
else:
dst_item.unlink()
print(f"Moving {src_item} -> {dst_item}")
shutil.move(str(src_item), str(dst_item))
# 2) Place internal headers expected by the project layout under src/
# - third-party/expected/include/tl/expected.hpp
# -> include/LIEF/third-party/internal/expected.hpp
# - third-party/tcb-span/span.hpp
# -> include/LIEF/third-party/internal/span.hpp
# - third-party/utfcpp/source/utf8
# -> include/LIEF/third-party/internal/utfcpp/utf8
internal_mappings = [
['third-party/expected/include/tl/expected.hpp', 'include/LIEF/third-party/internal/expected.hpp'],
['third-party/tcb-span/span.hpp', 'include/LIEF/third-party/internal/span.hpp'],
['third-party/utfcpp/source/utf8', 'include/LIEF/third-party/internal/utfcpp/utf8'],
]
for src_rel, dst_rel in internal_mappings:
src_path = lief_dir / src_rel
dst_path = lief_dir / dst_rel
if not src_path.exists():
print(f"Warning: expected internal header missing: {src_path}", file=sys.stderr)
continue
print(f"Copying internal header {src_path} -> {dst_path}")
ensure_dir(dst_path.parent)
if dst_path.exists():
if dst_path.is_dir():
shutil.rmtree(dst_path)
else:
dst_path.unlink()
if src_path.is_dir():
shutil.copytree(src_path, dst_path)
else:
shutil.copy2(src_path, dst_path)
# 3) Generate configured headers into src/LIEF/
# Prefer templates from target include/, fall back to source include/ if missing.
configs = [
['include/LIEF/config.h.in', 'include/LIEF/config.h'],
['include/LIEF/version.h.in', 'include/LIEF/version.h'],
['src/compiler_support.h.in', 'src/compiler_support.h'],
]
# The CMakeLists.txt usually contains the default version values like this. Parse them.
# set(LIEF_VERSION_MAJOR 0)
# set(LIEF_VERSION_MINOR 17)
# set(LIEF_VERSION_PATCH 0)
cmakelists_path = source_dir / 'CMakeLists.txt'
major_ver = minor_ver = patch_ver = 0
if cmakelists_path.exists():
with open(cmakelists_path, 'r', encoding='utf-8') as f:
cmake_contents = f.read()
m_major = re.search(r'set\s*\(\s*LIEF_VERSION_MAJOR\s+(\d+)\s*\)', cmake_contents)
m_minor = re.search(r'set\s*\(\s*LIEF_VERSION_MINOR\s+(\d+)\s*\)', cmake_contents)
m_patch = re.search(r'set\s*\(\s*LIEF_VERSION_PATCH\s+(\d+)\s*\)', cmake_contents)
if m_major:
major_ver = int(m_major.group(1))
if m_minor:
minor_ver = int(m_minor.group(1))
if m_patch:
patch_ver = int(m_patch.group(1))
vals = {
'PROJECT_NAME': 'LIEF',
'LIEF_VERSION_MAJOR': major_ver,
'LIEF_VERSION_MINOR': minor_ver,
'LIEF_VERSION_PATCH': patch_ver,
'LIEF_COMMIT_HASH': args.commit,
'LIEF_IS_TAGGED': 0 if not args.tag else 1,
'LIEF_GIT_TAG': args.tag or '',
'LIEF_PE_SUPPORT': 1,
'LIEF_ELF_SUPPORT': 1,
'LIEF_MACHO_SUPPORT': 1,
'LIEF_COFF_SUPPORT': 1,
'LIEF_OAT_SUPPORT': 0,
'LIEF_DEX_SUPPORT': 0,
'LIEF_VDEX_SUPPORT': 0,
'LIEF_ART_SUPPORT': 0,
'LIEF_DEBUG_INFO_SUPPORT': 0,
'LIEF_OBJC_SUPPORT': 0,
'LIEF_DYLD_SHARED_CACHE_SUPPORT': 0,
'LIEF_ASM_SUPPORT': 0,
'LIEF_EXTENDED': 0,
'ENABLE_JSON_SUPPORT': 0,
'LIEF_JSON_SUPPORT': 0,
'LIEF_LOGGING_SUPPORT': 0,
'LIEF_LOGGING_DEBUG_SUPPORT': 0,
'LIEF_FROZEN_ENABLED': 1,
'LIEF_EXTERNAL_EXPECTED': 0,
'LIEF_EXTERNAL_UTF8CPP': 0,
'LIEF_EXTERNAL_MBEDTLS': 0,
'LIEF_EXTERNAL_FROZEN': 0,
'LIEF_EXTERNAL_SPAN': 0,
'LIEF_NLOHMANN_JSON_EXTERNAL': 0,
'LIEF_SUPPORT_CXX11': 1,
'LIEF_SUPPORT_CXX14': 1,
'LIEF_SUPPORT_CXX17': 1,
}
for cfg_in_rel, cfg_out_rel in configs:
cfg_in_path = lief_dir / cfg_in_rel
cfg_out_path = lief_dir / cfg_out_rel
if not cfg_in_path.exists():
print(f"Warning: config template missing: {cfg_in_path}", file=sys.stderr)
continue
with open(cfg_in_path, 'r', encoding='utf-8') as f:
template = f.read()
configured = cmake_configure_in(template, vals)
ensure_dir(cfg_out_path.parent)
write_file(cfg_out_path, configured)
extracted_summary = ', '.join(sorted(set(extracted_libs))) if extracted_libs else 'none'
print(f'LIEF preparation complete: third-party extracted from {source_dir} into {lief_dir} [{extracted_summary}] and headers configured into src/')
return 0
if __name__ == '__main__':
sys.exit(main())