|
11 | 11 | import urllib.request |
12 | 12 | import zipfile |
13 | 13 |
|
14 | | -SCRIPT_PATH = pathlib.Path(__file__).resolve().parent |
| 14 | +_SCRIPT_PATH = pathlib.Path(__file__).resolve().parent |
| 15 | +_NINJA_URL = "https://github.com/ninja-build/ninja/releases/download/v1.12.1/{}" |
| 16 | +_CMAKE_VERSION = "4.0.1" |
| 17 | +_CMAKE_URL = ( |
| 18 | + "https://github.com/Kitware/CMake/releases/download/" |
| 19 | + f"v{_CMAKE_VERSION}/cmake-{_CMAKE_VERSION}" + "-{}.{}" |
| 20 | +) |
15 | 21 |
|
16 | | -NINJA_URL = 'https://github.com/ninja-build/ninja/releases/download/v1.10.2/{}' |
17 | | -CMAKE_URL = 'https://cmake.org/files/v3.22/{}' |
18 | 22 |
|
19 | | - |
20 | | -def parse_args(): |
| 23 | +def _parse_args() -> argparse.Namespace: |
21 | 24 | """Parse command-line arguments.""" |
22 | 25 | parser = argparse.ArgumentParser() |
23 | 26 | parser.add_argument( |
24 | | - '--cfg', |
25 | | - choices=[ |
26 | | - 'Debug', |
27 | | - 'RelWithDebInfo', |
28 | | - 'Release'], |
29 | | - default='Release', |
30 | | - const='Release', |
31 | | - nargs='?', |
32 | | - help='CMake configuration') |
| 27 | + "--cfg", |
| 28 | + choices=["Debug", "RelWithDebInfo", "Release"], |
| 29 | + default="Release", |
| 30 | + const="Release", |
| 31 | + nargs="?", |
| 32 | + help="CMake configuration", |
| 33 | + ) |
33 | 34 | parser.add_argument( |
34 | | - '--arch', |
| 35 | + "--arch", |
35 | 36 | type=int, |
36 | 37 | choices=(32, 64), |
37 | 38 | default=64, |
38 | 39 | const=64, |
39 | | - nargs='?', |
40 | | - help='Target architecture') |
| 40 | + nargs="?", |
| 41 | + help="Target architecture", |
| 42 | + ) |
41 | 43 | parser.add_argument( |
42 | | - '--paland', |
43 | | - help='Compile with Paland\'s printf conformance suite', |
44 | | - action='store_true') |
| 44 | + "--paland", |
| 45 | + help="Compile with Paland's printf conformance suite", |
| 46 | + action="store_true", |
| 47 | + ) |
45 | 48 | parser.add_argument( |
46 | | - '--download', |
47 | | - help='Download CMake and Ninja, don\'t use local copies', |
48 | | - action='store_true') |
49 | | - parser.add_argument('--ubsan', action='store_true', help='Clang UB sanitizer') |
50 | | - parser.add_argument('--asan', action='store_true', help='Clang addr sanitizer') |
51 | | - parser.add_argument('-v', '--verbose', action='store_true', help='verbose') |
| 49 | + "--download", |
| 50 | + help="Download CMake and Ninja, don't use local copies", |
| 51 | + action="store_true", |
| 52 | + ) |
| 53 | + parser.add_argument("--ubsan", action="store_true", help="Clang UB sanitizer") |
| 54 | + parser.add_argument("--asan", action="store_true", help="Clang addr sanitizer") |
| 55 | + parser.add_argument("-v", "--verbose", action="store_true", help="verbose") |
52 | 56 | return parser.parse_args() |
53 | 57 |
|
54 | 58 |
|
55 | | -def download_file(url, local_path, verbose): |
| 59 | +def download_file(url: str, local_path: pathlib.Path, verbose: bool) -> None: |
56 | 60 | """Download a file from url to local_path.""" |
57 | 61 | if verbose: |
58 | | - print(f'Downloading:\n Remote: {url}\n Local: {local_path}') |
59 | | - with urllib.request.urlopen(url) as rsp, open(local_path, 'wb') as file: |
| 62 | + print(f"Downloading:\n Remote: {url}\n Local: {local_path}") |
| 63 | + with urllib.request.urlopen(url) as rsp, open(local_path, "wb") as file: |
60 | 64 | shutil.copyfileobj(rsp, file) |
61 | 65 |
|
62 | 66 |
|
63 | | -def get_cmake(download, verbose): |
| 67 | +def _get_cmake(download: bool, verbose: bool) -> pathlib.Path: |
64 | 68 | """Return the path to system CMake, or download and unpack a local copy.""" |
65 | 69 | if not download: |
66 | | - cmake = shutil.which('cmake') |
| 70 | + cmake = shutil.which("cmake") |
67 | 71 | if cmake: |
68 | | - return cmake |
| 72 | + return pathlib.Path(cmake) |
69 | 73 |
|
70 | 74 | plat = { |
71 | | - 'darwin': 'macos-universal', |
72 | | - 'linux': 'linux-x86_64', |
73 | | - 'win32': 'win64-x86_64'}[ |
74 | | - sys.platform] |
75 | | - cmake_prefix = f'cmake-3.22.2-{plat}' |
76 | | - cmake_local_dir = SCRIPT_PATH / 'external' / 'cmake' |
77 | | - cmake_file = f'{cmake_prefix}.tar.gz' |
78 | | - cmake_local_tgz = cmake_local_dir / cmake_file |
79 | | - cmake_local_exe = cmake_local_dir / cmake_prefix / \ |
80 | | - ('CMake.app/Contents' if sys.platform == 'darwin' else '') / 'bin' / 'cmake' |
| 75 | + "darwin": "macos-universal", |
| 76 | + "linux": "linux-x86_64", |
| 77 | + "win32": "windows-x86_64", |
| 78 | + }[sys.platform] |
| 79 | + |
| 80 | + suffix = "zip" if sys.platform == "win32" else "tar.gz" |
| 81 | + |
| 82 | + cmake_prefix = f"cmake-{_CMAKE_VERSION}-{plat}" |
| 83 | + cmake_local_dir = _SCRIPT_PATH / "external/cmake" |
| 84 | + cmake_file = f"{cmake_prefix}.{suffix}" |
| 85 | + cmake_local_archive = cmake_local_dir / cmake_file |
| 86 | + cmake_local_exe = ( |
| 87 | + cmake_local_dir |
| 88 | + / cmake_prefix |
| 89 | + / ("CMake.app/Contents" if sys.platform == "darwin" else "") |
| 90 | + / "bin/cmake" |
| 91 | + ) |
81 | 92 |
|
82 | 93 | if not cmake_local_exe.exists(): |
83 | | - if not cmake_local_tgz.exists(): |
| 94 | + if not cmake_local_archive.exists(): |
84 | 95 | cmake_local_dir.mkdir(parents=True, exist_ok=True) |
85 | | - download_file( |
86 | | - CMAKE_URL.format(cmake_file), |
87 | | - cmake_local_tgz, |
88 | | - verbose) |
89 | | - with tarfile.open(cmake_local_tgz, 'r') as tar: |
90 | | - for member in tar.getmembers(): |
91 | | - member_path = pathlib.Path(cmake_local_dir / member.name).resolve() |
92 | | - if not cmake_local_dir in member_path.parents: |
93 | | - raise ValueError('Tar file contents move upwards past sandbox root') |
94 | | - tar.extractall(path=cmake_local_dir) |
| 96 | + download_file(_CMAKE_URL.format(plat, suffix), cmake_local_archive, verbose) |
| 97 | + |
| 98 | + match suffix: |
| 99 | + case "tar.gz": |
| 100 | + with tarfile.open(cmake_local_archive, "r") as tar: |
| 101 | + for member in tar.getmembers(): |
| 102 | + member_path = pathlib.Path( |
| 103 | + cmake_local_dir / member.name |
| 104 | + ).resolve() |
| 105 | + if cmake_local_dir not in member_path.parents: |
| 106 | + raise ValueError( |
| 107 | + "Tar file contents move upwards past sandbox root" |
| 108 | + ) |
| 109 | + |
| 110 | + tar.extractall(path=cmake_local_dir) |
| 111 | + |
| 112 | + case "zip": |
| 113 | + with zipfile.ZipFile(cmake_local_archive, "r") as zip_file: |
| 114 | + zip_file.extractall(cmake_local_dir) |
95 | 115 |
|
96 | 116 | return cmake_local_exe |
97 | 117 |
|
98 | 118 |
|
99 | | -def get_ninja(download, verbose): |
| 119 | +def _get_ninja(download: bool, verbose: bool) -> pathlib.Path: |
100 | 120 | """Return the path to system Ninja, or download and unpack a local copy.""" |
101 | 121 | if not download: |
102 | | - ninja = shutil.which('ninja') |
| 122 | + ninja = shutil.which("ninja") |
103 | 123 | if ninja: |
104 | | - return ninja |
| 124 | + return pathlib.Path(ninja) |
105 | 125 |
|
106 | | - ninja_local_dir = SCRIPT_PATH / 'external' / 'ninja' |
107 | | - plat = {'darwin': 'mac', 'linux': 'linux', 'win32': 'win'}[sys.platform] |
108 | | - ninja_file = f'ninja-{plat}.zip' |
| 126 | + ninja_local_dir = _SCRIPT_PATH / "external/ninja" |
| 127 | + plat = {"darwin": "mac", "linux": "linux", "win32": "win"}[sys.platform] |
| 128 | + ninja_file = f"ninja-{plat}.zip" |
109 | 129 | ninja_local_zip = ninja_local_dir / ninja_file |
110 | | - ninja_local_exe = ninja_local_dir / f'ninja{".exe" if plat == "win" else ""}' |
| 130 | + ninja_local_exe = ninja_local_dir / f"ninja{'.exe' if plat == 'win' else ''}" |
111 | 131 |
|
112 | 132 | if not ninja_local_exe.exists(): |
113 | 133 | if not ninja_local_zip.exists(): |
114 | 134 | ninja_local_dir.mkdir(parents=True, exist_ok=True) |
115 | | - download_file( |
116 | | - NINJA_URL.format(ninja_file), |
117 | | - ninja_local_zip, |
118 | | - verbose) |
119 | | - with zipfile.ZipFile(ninja_local_zip, 'r') as zip_file: |
| 135 | + download_file(_NINJA_URL.format(ninja_file), ninja_local_zip, verbose) |
| 136 | + |
| 137 | + with zipfile.ZipFile(ninja_local_zip, "r") as zip_file: |
120 | 138 | zip_file.extractall(ninja_local_dir) |
121 | | - os.chmod(ninja_local_exe, os.stat( |
122 | | - ninja_local_exe).st_mode | stat.S_IEXEC) |
| 139 | + |
| 140 | + os.chmod(ninja_local_exe, os.stat(ninja_local_exe).st_mode | stat.S_IEXEC) |
123 | 141 |
|
124 | 142 | return ninja_local_exe |
125 | 143 |
|
126 | 144 |
|
127 | | -def configure_cmake(cmake_exe, ninja, args): |
| 145 | +def _configure_cmake( |
| 146 | + cmake_exe: pathlib.Path, ninja: pathlib.Path, args: argparse.Namespace |
| 147 | +) -> bool: |
128 | 148 | """Prepare CMake for building nanoprintf tests under 'build/ninja/<cfg>'.""" |
129 | | - build_path = SCRIPT_PATH / 'build' / 'ninja' / args.cfg |
130 | | - if (build_path / 'CMakeFiles').exists(): |
| 149 | + build_path = _SCRIPT_PATH / "build/ninja" / args.cfg |
| 150 | + if (build_path / "CMakeFiles").exists(): |
131 | 151 | return True |
132 | 152 |
|
133 | 153 | sys.stdout.flush() |
134 | 154 | build_path.mkdir(parents=True) |
135 | 155 |
|
136 | | - cmake_args = [cmake_exe, |
137 | | - SCRIPT_PATH, |
138 | | - '-G', |
139 | | - 'Ninja', |
140 | | - '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON', |
141 | | - f'-DCMAKE_MAKE_PROGRAM={ninja}', |
142 | | - f'-DCMAKE_BUILD_TYPE={args.cfg}', |
143 | | - f'-DNPF_PALAND={"ON" if args.paland else "OFF"}', |
144 | | - f'-DNPF_32BIT={"ON" if args.arch == 32 else "OFF"}', |
145 | | - f'-DNPF_CLANG_ASAN={"ON" if args.asan else "OFF"}', |
146 | | - f'-DNPF_CLANG_UBSAN={"ON" if args.ubsan else "OFF"}'] |
| 156 | + cmake_args = [ |
| 157 | + cmake_exe, |
| 158 | + _SCRIPT_PATH, |
| 159 | + "-G", |
| 160 | + "Ninja", |
| 161 | + "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", |
| 162 | + f"-DCMAKE_MAKE_PROGRAM={ninja}", |
| 163 | + f"-DCMAKE_BUILD_TYPE={args.cfg}", |
| 164 | + f"-DNPF_PALAND={'ON' if args.paland else 'OFF'}", |
| 165 | + f"-DNPF_32BIT={'ON' if args.arch == 32 else 'OFF'}", |
| 166 | + f"-DNPF_CLANG_ASAN={'ON' if args.asan else 'OFF'}", |
| 167 | + f"-DNPF_CLANG_UBSAN={'ON' if args.ubsan else 'OFF'}", |
| 168 | + ] |
| 169 | + |
147 | 170 | try: |
148 | | - return subprocess.run( |
149 | | - cmake_args, |
150 | | - cwd=build_path, |
151 | | - check=True).returncode == 0 |
| 171 | + return subprocess.run(cmake_args, cwd=build_path, check=True).returncode == 0 |
152 | 172 | except subprocess.CalledProcessError as cpe: |
153 | 173 | return cpe.returncode == 0 |
154 | 174 |
|
155 | 175 |
|
156 | | -def build_cmake(cmake_exe, args): |
| 176 | +def _build_cmake(cmake_exe: pathlib.Path, args: argparse.Namespace) -> bool: |
157 | 177 | """Run CMake in build mode to compile and run the nanoprintf test suite.""" |
158 | 178 | sys.stdout.flush() |
159 | | - build_path = SCRIPT_PATH / 'build' / 'ninja' / args.cfg |
160 | | - cmake_args = [cmake_exe, '--build', build_path] + \ |
161 | | - (['--', '-v'] if args.verbose else []) |
| 179 | + build_path = _SCRIPT_PATH / "build/ninja" / args.cfg |
| 180 | + cmake_args = [cmake_exe, "--build", build_path] + ( |
| 181 | + ["--", "-v"] if args.verbose else [] |
| 182 | + ) |
| 183 | + |
162 | 184 | try: |
163 | 185 | return subprocess.run(cmake_args, check=True).returncode == 0 |
164 | 186 | except subprocess.CalledProcessError as cpe: |
165 | 187 | return cpe.returncode == 0 |
166 | 188 |
|
167 | 189 |
|
168 | | -def main(): |
| 190 | +def main() -> int: |
169 | 191 | """Parse args, find or get tools, configure CMake, build and run tests.""" |
170 | | - args = parse_args() |
| 192 | + args = _parse_args() |
171 | 193 |
|
172 | | - cmake = get_cmake(args.download, args.verbose) |
| 194 | + cmake = _get_cmake(args.download, args.verbose) |
173 | 195 | if args.verbose: |
174 | | - print(f'Found CMake at {cmake}') |
| 196 | + print(f"Found CMake at {cmake}") |
175 | 197 |
|
176 | | - ninja = get_ninja(args.download, args.verbose) |
| 198 | + ninja = _get_ninja(args.download, args.verbose) |
177 | 199 | if args.verbose: |
178 | | - print(f'Found ninja at {ninja}') |
| 200 | + print(f"Found ninja at {ninja}") |
179 | 201 |
|
180 | | - built_ok = configure_cmake(cmake, ninja, args) and build_cmake(cmake, args) |
| 202 | + built_ok = _configure_cmake(cmake, ninja, args) and _build_cmake(cmake, args) |
181 | 203 | return int(not built_ok) # 0 is success |
182 | 204 |
|
183 | 205 |
|
184 | | -if __name__ == '__main__': |
| 206 | +if __name__ == "__main__": |
185 | 207 | sys.exit(main()) |
0 commit comments