Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/scripts/wdev.py @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 ''' | |
| 2 Finds locations of Windows command-line development tools. | |
| 3 ''' | |
| 4 | |
| 5 import os | |
| 6 import platform | |
| 7 import glob | |
| 8 import re | |
| 9 import subprocess | |
| 10 import sys | |
| 11 import sysconfig | |
| 12 import textwrap | |
| 13 | |
| 14 | |
| 15 class WindowsVS: | |
| 16 r''' | |
| 17 Windows only. Finds locations of Visual Studio command-line tools. Assumes | |
| 18 VS2019-style paths. | |
| 19 | |
| 20 Members and example values:: | |
| 21 | |
| 22 .year: 2019 | |
| 23 .grade: Community | |
| 24 .version: 14.28.29910 | |
| 25 .directory: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community | |
| 26 .vcvars: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat | |
| 27 .cl: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\bin\Hostx64\x64\cl.exe | |
| 28 .link: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\bin\Hostx64\x64\link.exe | |
| 29 .csc: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\Roslyn\csc.exe | |
| 30 .msbuild: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe | |
| 31 .devenv: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\devenv.com | |
| 32 | |
| 33 `.csc` is C# compiler; will be None if not found. | |
| 34 ''' | |
| 35 def __init__( self, year=None, grade=None, version=None, cpu=None, verbose=False): | |
| 36 ''' | |
| 37 Args: | |
| 38 year: | |
| 39 None or, for example, `2019`. If None we use environment | |
| 40 variable WDEV_VS_YEAR if set. | |
| 41 grade: | |
| 42 None or, for example, one of: | |
| 43 | |
| 44 * `Community` | |
| 45 * `Professional` | |
| 46 * `Enterprise` | |
| 47 | |
| 48 If None we use environment variable WDEV_VS_GRADE if set. | |
| 49 version: | |
| 50 None or, for example: `14.28.29910`. If None we use environment | |
| 51 variable WDEV_VS_VERSION if set. | |
| 52 cpu: | |
| 53 None or a `WindowsCpu` instance. | |
| 54 ''' | |
| 55 def default(value, name): | |
| 56 if value is None: | |
| 57 name2 = f'WDEV_VS_{name.upper()}' | |
| 58 value = os.environ.get(name2) | |
| 59 if value is not None: | |
| 60 _log(f'Setting {name} from environment variable {name2}: {value!r}') | |
| 61 return value | |
| 62 try: | |
| 63 year = default(year, 'year') | |
| 64 grade = default(grade, 'grade') | |
| 65 version = default(version, 'version') | |
| 66 | |
| 67 if not cpu: | |
| 68 cpu = WindowsCpu() | |
| 69 | |
| 70 # Find `directory`. | |
| 71 # | |
| 72 pattern = f'C:\\Program Files*\\Microsoft Visual Studio\\{year if year else "2*"}\\{grade if grade else "*"}' | |
| 73 directories = glob.glob( pattern) | |
| 74 if verbose: | |
| 75 _log( f'Matches for: {pattern=}') | |
| 76 _log( f'{directories=}') | |
| 77 assert directories, f'No match found for: {pattern}' | |
| 78 directories.sort() | |
| 79 directory = directories[-1] | |
| 80 | |
| 81 # Find `devenv`. | |
| 82 # | |
| 83 devenv = f'{directory}\\Common7\\IDE\\devenv.com' | |
| 84 assert os.path.isfile( devenv), f'Does not exist: {devenv}' | |
| 85 | |
| 86 # Extract `year` and `grade` from `directory`. | |
| 87 # | |
| 88 # We use r'...' for regex strings because an extra level of escaping is | |
| 89 # required for backslashes. | |
| 90 # | |
| 91 regex = rf'^C:\\Program Files.*\\Microsoft Visual Studio\\([^\\]+)\\([^\\]+)' | |
| 92 m = re.match( regex, directory) | |
| 93 assert m, f'No match: {regex=} {directory=}' | |
| 94 year2 = m.group(1) | |
| 95 grade2 = m.group(2) | |
| 96 if year: | |
| 97 assert year2 == year | |
| 98 else: | |
| 99 year = year2 | |
| 100 if grade: | |
| 101 assert grade2 == grade | |
| 102 else: | |
| 103 grade = grade2 | |
| 104 | |
| 105 # Find vcvars.bat. | |
| 106 # | |
| 107 vcvars = f'{directory}\\VC\\Auxiliary\\Build\\vcvars{cpu.bits}.bat' | |
| 108 assert os.path.isfile( vcvars), f'No match for: {vcvars}' | |
| 109 | |
| 110 # Find cl.exe. | |
| 111 # | |
| 112 cl_pattern = f'{directory}\\VC\\Tools\\MSVC\\{version if version else "*"}\\bin\\Host{cpu.windows_name}\\{cpu.windows_name}\\cl.exe' | |
| 113 cl_s = glob.glob( cl_pattern) | |
| 114 assert cl_s, f'No match for: {cl_pattern}' | |
| 115 cl_s.sort() | |
| 116 cl = cl_s[ -1] | |
| 117 | |
| 118 # Extract `version` from cl.exe's path. | |
| 119 # | |
| 120 m = re.search( rf'\\VC\\Tools\\MSVC\\([^\\]+)\\bin\\Host{cpu.windows_name}\\{cpu.windows_name}\\cl.exe$', cl) | |
| 121 assert m | |
| 122 version2 = m.group(1) | |
| 123 if version: | |
| 124 assert version2 == version | |
| 125 else: | |
| 126 version = version2 | |
| 127 assert version | |
| 128 | |
| 129 # Find link.exe. | |
| 130 # | |
| 131 link_pattern = f'{directory}\\VC\\Tools\\MSVC\\{version}\\bin\\Host{cpu.windows_name}\\{cpu.windows_name}\\link.exe' | |
| 132 link_s = glob.glob( link_pattern) | |
| 133 assert link_s, f'No match for: {link_pattern}' | |
| 134 link_s.sort() | |
| 135 link = link_s[ -1] | |
| 136 | |
| 137 # Find csc.exe. | |
| 138 # | |
| 139 csc = None | |
| 140 for dirpath, dirnames, filenames in os.walk(directory): | |
| 141 for filename in filenames: | |
| 142 if filename == 'csc.exe': | |
| 143 csc = os.path.join(dirpath, filename) | |
| 144 #_log(f'{csc=}') | |
| 145 #break | |
| 146 | |
| 147 # Find MSBuild.exe. | |
| 148 # | |
| 149 msbuild = None | |
| 150 for dirpath, dirnames, filenames in os.walk(directory): | |
| 151 for filename in filenames: | |
| 152 if filename == 'MSBuild.exe': | |
| 153 msbuild = os.path.join(dirpath, filename) | |
| 154 #_log(f'{csc=}') | |
| 155 #break | |
| 156 | |
| 157 self.cl = cl | |
| 158 self.devenv = devenv | |
| 159 self.directory = directory | |
| 160 self.grade = grade | |
| 161 self.link = link | |
| 162 self.csc = csc | |
| 163 self.msbuild = msbuild | |
| 164 self.vcvars = vcvars | |
| 165 self.version = version | |
| 166 self.year = year | |
| 167 except Exception as e: | |
| 168 raise Exception( f'Unable to find Visual Studio') from e | |
| 169 | |
| 170 def description_ml( self, indent=''): | |
| 171 ''' | |
| 172 Return multiline description of `self`. | |
| 173 ''' | |
| 174 ret = textwrap.dedent(f''' | |
| 175 year: {self.year} | |
| 176 grade: {self.grade} | |
| 177 version: {self.version} | |
| 178 directory: {self.directory} | |
| 179 vcvars: {self.vcvars} | |
| 180 cl: {self.cl} | |
| 181 link: {self.link} | |
| 182 csc: {self.csc} | |
| 183 msbuild: {self.msbuild} | |
| 184 devenv: {self.devenv} | |
| 185 ''') | |
| 186 return textwrap.indent( ret, indent) | |
| 187 | |
| 188 def __str__( self): | |
| 189 return ' '.join( self._description()) | |
| 190 | |
| 191 | |
| 192 class WindowsCpu: | |
| 193 ''' | |
| 194 For Windows only. Paths and names that depend on cpu. | |
| 195 | |
| 196 Members: | |
| 197 .bits | |
| 198 32 or 64. | |
| 199 .windows_subdir | |
| 200 Empty string or `x64/`. | |
| 201 .windows_name | |
| 202 `x86` or `x64`. | |
| 203 .windows_config | |
| 204 `x64` or `Win32`, e.g. for use in `/Build Release|x64`. | |
| 205 .windows_suffix | |
| 206 `64` or empty string. | |
| 207 ''' | |
| 208 def __init__(self, name=None): | |
| 209 if not name: | |
| 210 name = _cpu_name() | |
| 211 self.name = name | |
| 212 if name == 'x32': | |
| 213 self.bits = 32 | |
| 214 self.windows_subdir = '' | |
| 215 self.windows_name = 'x86' | |
| 216 self.windows_config = 'Win32' | |
| 217 self.windows_suffix = '' | |
| 218 elif name == 'x64': | |
| 219 self.bits = 64 | |
| 220 self.windows_subdir = 'x64/' | |
| 221 self.windows_name = 'x64' | |
| 222 self.windows_config = 'x64' | |
| 223 self.windows_suffix = '64' | |
| 224 else: | |
| 225 assert 0, f'Unrecognised cpu name: {name}' | |
| 226 | |
| 227 def __str__(self): | |
| 228 return self.name | |
| 229 | |
| 230 | |
| 231 class WindowsPython: | |
| 232 ''' | |
| 233 Windows only. Information about installed Python with specific word size | |
| 234 and version. Defaults to the currently-running Python. | |
| 235 | |
| 236 Members: | |
| 237 | |
| 238 .path: | |
| 239 Path of python binary. | |
| 240 .version: | |
| 241 `{major}.{minor}`, e.g. `3.9` or `3.11`. Same as `version` passed | |
| 242 to `__init__()` if not None, otherwise the inferred version. | |
| 243 .include: | |
| 244 Python include path. | |
| 245 .cpu: | |
| 246 A `WindowsCpu` instance, same as `cpu` passed to `__init__()` if | |
| 247 not None, otherwise the inferred cpu. | |
| 248 .libs: | |
| 249 Python libs directory. | |
| 250 | |
| 251 We parse the output from `py -0p` to find all available python | |
| 252 installations. | |
| 253 ''' | |
| 254 | |
| 255 def __init__( self, cpu=None, version=None, verbose=True): | |
| 256 ''' | |
| 257 Args: | |
| 258 | |
| 259 cpu: | |
| 260 A WindowsCpu instance. If None, we use whatever we are running | |
| 261 on. | |
| 262 version: | |
| 263 Two-digit Python version as a string such as `3.8`. If None we | |
| 264 use current Python's version. | |
| 265 verbose: | |
| 266 If true we show diagnostics. | |
| 267 ''' | |
| 268 if cpu is None: | |
| 269 cpu = WindowsCpu(_cpu_name()) | |
| 270 if version is None: | |
| 271 version = '.'.join(platform.python_version().split('.')[:2]) | |
| 272 _log(f'Looking for Python {version=} {cpu.bits=}.') | |
| 273 | |
| 274 if '.'.join(platform.python_version().split('.')[:2]) == version: | |
| 275 # Current python matches, so use it directly. This avoids problems | |
| 276 # on Github where experimental python-3.13 is not available via | |
| 277 # `py`. | |
| 278 _log(f'{cpu=} {version=}: using {sys.executable=}.') | |
| 279 self.path = sys.executable | |
| 280 self.version = version | |
| 281 self.cpu = cpu | |
| 282 self.include = sysconfig.get_path('include') | |
| 283 | |
| 284 else: | |
| 285 command = 'py -0p' | |
| 286 if verbose: | |
| 287 _log(f'{cpu=} {version=}: Running: {command}') | |
| 288 text = subprocess.check_output( command, shell=True, text=True) | |
| 289 for line in text.split('\n'): | |
| 290 #_log( f' {line}') | |
| 291 if m := re.match( '^ *-V:([0-9.]+)(-32)? ([*])? +(.+)$', line): | |
| 292 version2 = m.group(1) | |
| 293 bits = 32 if m.group(2) else 64 | |
| 294 current = m.group(3) | |
| 295 path = m.group(4).strip() | |
| 296 elif m := re.match( '^ *-([0-9.]+)-((32)|(64)) +(.+)$', line): | |
| 297 version2 = m.group(1) | |
| 298 bits = int(m.group(2)) | |
| 299 path = m.group(5).strip() | |
| 300 else: | |
| 301 if verbose: | |
| 302 _log( f'No match for {line=}') | |
| 303 continue | |
| 304 if verbose: | |
| 305 _log( f'{version2=} {bits=} {path=} from {line=}.') | |
| 306 if bits != cpu.bits or version2 != version: | |
| 307 continue | |
| 308 root = os.path.dirname(path) | |
| 309 if not os.path.exists(path): | |
| 310 # Sometimes it seems that the specified .../python.exe does not exist, | |
| 311 # and we have to change it to .../python<version>.exe. | |
| 312 # | |
| 313 assert path.endswith('.exe'), f'path={path!r}' | |
| 314 path2 = f'{path[:-4]}{version}.exe' | |
| 315 _log( f'Python {path!r} does not exist; changed to: {path2!r}') | |
| 316 assert os.path.exists( path2) | |
| 317 path = path2 | |
| 318 | |
| 319 self.path = path | |
| 320 self.version = version | |
| 321 self.cpu = cpu | |
| 322 command = f'{self.path} -c "import sysconfig; print(sysconfig.get_path(\'include\'))"' | |
| 323 _log(f'Finding Python include path by running {command=}.') | |
| 324 self.include = subprocess.check_output(command, shell=True, text=True).strip() | |
| 325 _log(f'Python include path is {self.include=}.') | |
| 326 #_log( f'pipcl.py:WindowsPython():\n{self.description_ml(" ")}') | |
| 327 break | |
| 328 else: | |
| 329 _log(f'Failed to find python matching cpu={cpu}.') | |
| 330 _log(f'Output from {command!r} was:\n{text}') | |
| 331 raise Exception( f'Failed to find python matching cpu={cpu} {version=}.') | |
| 332 | |
| 333 # Oddly there doesn't seem to be a | |
| 334 # `sysconfig.get_path('libs')`, but it seems to be next | |
| 335 # to `includes`: | |
| 336 self.libs = os.path.abspath(f'{self.include}/../libs') | |
| 337 | |
| 338 _log( f'WindowsPython:\n{self.description_ml(" ")}') | |
| 339 | |
| 340 def description_ml(self, indent=''): | |
| 341 ret = textwrap.dedent(f''' | |
| 342 path: {self.path} | |
| 343 version: {self.version} | |
| 344 cpu: {self.cpu} | |
| 345 include: {self.include} | |
| 346 libs: {self.libs} | |
| 347 ''') | |
| 348 return textwrap.indent( ret, indent) | |
| 349 | |
| 350 def __repr__(self): | |
| 351 return f'path={self.path!r} version={self.version!r} cpu={self.cpu!r} include={self.include!r} libs={self.libs!r}' | |
| 352 | |
| 353 | |
| 354 # Internal helpers. | |
| 355 # | |
| 356 | |
| 357 def _cpu_name(): | |
| 358 ''' | |
| 359 Returns `x32` or `x64` depending on Python build. | |
| 360 ''' | |
| 361 #log(f'sys.maxsize={hex(sys.maxsize)}') | |
| 362 return f'x{32 if sys.maxsize == 2**31 - 1 else 64}' | |
| 363 | |
| 364 | |
| 365 | |
| 366 def _log(text=''): | |
| 367 ''' | |
| 368 Logs lines with prefix. | |
| 369 ''' | |
| 370 for line in text.split('\n'): | |
| 371 print(f'{__file__}: {line}') | |
| 372 sys.stdout.flush() |
