from __future__ import annotations import argparse import json import os import platform import re import shutil import struct import subprocess import sys from pathlib import Path from typing import Any def cmd_cd(path: str) -> str: return f"cd /D {path}" def cmd_set(name: str, value: str) -> str: return f"set {name}={value}" def cmd_append(name: str, value: str) -> str: op = "path " if name == "PATH" else f"set {name}=" return op + f"%{name}%;{value}" def cmd_copy(src: str, tgt: str) -> str: return f'copy /Y /B "{src}" "{tgt}"' def cmd_xcopy(src: str, tgt: str) -> str: return f'xcopy /Y /E "{src}" "{tgt}"' def cmd_mkdir(path: str) -> str: return f'mkdir "{path}"' def cmd_rmdir(path: str) -> str: return f'rmdir /S /Q "{path}"' def cmd_nmake( makefile: str | None = None, target: str = "", params: list[str] | None = None, ) -> str: return " ".join( [ "{nmake}", "-nologo", f'-f "{makefile}"' if makefile is not None else "", f'{" ".join(params)}' if params is not None else "", f'"{target}"', ] ) def cmds_cmake( target: str | tuple[str, ...] | list[str], *params: str, build_dir: str = ".", build_type: str = "Release", ) -> list[str]: if not isinstance(target, str): target = " ".join(target) return [ " ".join( [ "{cmake}", f"-DCMAKE_BUILD_TYPE={build_type}", "-DCMAKE_VERBOSE_MAKEFILE=ON", "-DCMAKE_RULE_MESSAGES:BOOL=OFF", # for NMake "-DCMAKE_C_COMPILER=cl.exe", # for Ninja "-DCMAKE_CXX_COMPILER=cl.exe", # for Ninja "-DCMAKE_C_FLAGS=-nologo", "-DCMAKE_CXX_FLAGS=-nologo", *params, '-G "{cmake_generator}"', f'-B "{build_dir}"', "-S .", ] ), f'{{cmake}} --build "{build_dir}" --clean-first --parallel --target {target}', ] def cmd_msbuild( file: str, configuration: str = "Release", target: str = "Build", plat: str = "{msbuild_arch}", ) -> str: return " ".join( [ "{msbuild}", f"{file}", f'/t:"{target}"', f'/p:Configuration="{configuration}"', f"/p:Platform={plat}", "/m", ] ) SF_PROJECTS = "https://sourceforge.net/projects" ARCHITECTURES = { "x86": {"vcvars_arch": "x86", "msbuild_arch": "Win32"}, "AMD64": {"vcvars_arch": "x86_amd64", "msbuild_arch": "x64"}, "ARM64": {"vcvars_arch": "x86_arm64", "msbuild_arch": "ARM64"}, } V = json.loads( (Path(__file__).parents[1] / ".github" / "dependencies.json").read_text() ) V["libpng-xy"] = "".join(V["libpng"].split(".")[:2]) # dependencies, listed in order of compilation DEPS: dict[str, dict[str, Any]] = { "libjpeg": { "url": f"https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/{V['jpegturbo']}/libjpeg-turbo-{V['jpegturbo']}.tar.gz", "filename": f"libjpeg-turbo-{V['jpegturbo']}.tar.gz", "license": ["README.ijg", "LICENSE.md"], "license_pattern": ( "(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n==========" ".+(libjpeg-turbo Licenses\n======================\n\n.+)$" ), "patch": { r"CMakeLists.txt": { # libjpeg-turbo does not detect MSVC x86_arm64 cross-compiler correctly 'if(MSVC_IDE AND CMAKE_GENERATOR_PLATFORM MATCHES "arm64")': "if({architecture} STREQUAL ARM64)", # noqa: E501 }, }, "build": [ *cmds_cmake( ("jpeg-static", "djpeg-static"), "-DENABLE_SHARED:BOOL=FALSE", "-DWITH_JPEG8:BOOL=TRUE", "-DWITH_CRT_DLL:BOOL=TRUE", ), cmd_copy("jpeg-static.lib", "libjpeg.lib"), cmd_copy("djpeg-static.exe", "djpeg.exe"), ], "headers": ["jconfig.h", r"src\j*.h"], "libs": ["libjpeg.lib"], "bins": ["djpeg.exe"], }, "zlib": { "url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['zlib-ng']}.tar.gz", "filename": f"zlib-ng-{V['zlib-ng']}.tar.gz", "license": "LICENSE.md", "patch": { r"CMakeLists.txt": { "set_target_properties(zlib-ng PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib-ng PROPERTIES OUTPUT_NAME zlib)", # noqa: E501 }, }, "build": [ *cmds_cmake( "zlib-ng", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON" ), ], "headers": [r"z*.h"], "libs": [r"zlib.lib"], }, "xz": { "url": f"https://github.com/tukaani-project/xz/releases/download/v{V['xz']}/FILENAME", "filename": f"xz-{V['xz']}.tar.gz", "license": "COPYING", "build": [ *cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"), cmd_mkdir(r"{inc_dir}\lzma"), cmd_copy(r"src\liblzma\api\lzma\*.h", r"{inc_dir}\lzma"), ], "headers": [r"src\liblzma\api\lzma.h"], "libs": [r"lzma.lib"], }, "libwebp": { "url": "http://downloads.webmproject.org/releases/webp/FILENAME", "filename": f"libwebp-{V['libwebp']}.tar.gz", "license": "COPYING", "patch": { r"src\enc\picture_csp_enc.c": { # link against libsharpyuv.lib '#include "sharpyuv/sharpyuv.h"': '#include "sharpyuv/sharpyuv.h"\n#pragma comment(lib, "libsharpyuv.lib")', # noqa: E501 } }, "build": [ *cmds_cmake( "webp webpmux webpdemux", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DWEBP_LINK_STATIC:BOOL=OFF", ), cmd_mkdir(r"{inc_dir}\webp"), cmd_copy(r"src\webp\*.h", r"{inc_dir}\webp"), ], "libs": [r"libsharpyuv.lib", r"libwebp*.lib"], }, "libtiff": { "url": "https://download.osgeo.org/libtiff/FILENAME", "filename": f"tiff-{V['tiff']}.tar.gz", "license": "LICENSE.md", "patch": { r"libtiff\tif_lzma.c": { # link against lzma.lib "#ifdef LZMA_SUPPORT": '#ifdef LZMA_SUPPORT\n#pragma comment(lib, "lzma.lib")', # noqa: E501 }, r"libtiff\tif_webp.c": { # link against libwebp.lib "#ifdef WEBP_SUPPORT": '#ifdef WEBP_SUPPORT\n#pragma comment(lib, "libwebp.lib")', # noqa: E501 }, }, "build": [ *cmds_cmake( "tiff", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DWebP_LIBRARY=libwebp", '-DCMAKE_C_FLAGS="-nologo -DLZMA_API_STATIC"', ) ], "headers": [r"libtiff\tiff*.h"], "libs": [r"libtiff\*.lib"], }, "libpng": { "url": f"{SF_PROJECTS}/libpng/files/libpng{V['libpng-xy']}/{V['libpng']}/" f"FILENAME/download", "filename": f"libpng-{V['libpng']}.tar.gz", "license": "LICENSE", "build": [ *cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"), cmd_copy( f"libpng{V['libpng-xy']}_static.lib", f"libpng{V['libpng-xy']}.lib" ), ], "headers": [r"png*.h"], "libs": [f"libpng{V['libpng-xy']}.lib"], }, "brotli": { "url": f"https://github.com/google/brotli/archive/refs/tags/v{V['brotli']}.tar.gz", "filename": f"brotli-{V['brotli']}.tar.gz", "license": "LICENSE", "build": [ *cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"), cmd_xcopy(r"c\include", "{inc_dir}"), ], "libs": ["*.lib"], }, "freetype": { "url": "https://download.savannah.gnu.org/releases/freetype/FILENAME", "filename": f"freetype-{V['freetype']}.tar.gz", "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], "patch": { r"builds\windows\vc2010\freetype.vcxproj": { # freetype setting is /MD for .dll and /MT for .lib, we need /MD "MultiThreaded": "MultiThreadedDLL", # noqa: E501 # freetype doesn't specify SDK version, MSBuild may guess incorrectly '': '\n $(WindowsSDKVersion)', # noqa: E501 }, r"builds\windows\vc2010\freetype.user.props": { "": "FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ;FT_CONFIG_OPTION_USE_BROTLI", # noqa: E501 "": r"{dir_harfbuzz}\src;{inc_dir}", # noqa: E501 "": "{lib_dir}", # noqa: E501 "": f"zlib.lib;libpng{V['libpng-xy']}.lib;brotlicommon.lib;brotlidec.lib", # noqa: E501 }, r"src/autofit/afshaper.c": { # link against harfbuzz.lib "#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ": '#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ\n#pragma comment(lib, "harfbuzz.lib")', # noqa: E501 }, }, "build": [ cmd_rmdir("objs"), cmd_msbuild( r"builds\windows\vc2010\freetype.vcxproj", "Release Static", "Clean" ), cmd_msbuild( r"builds\windows\vc2010\freetype.vcxproj", "Release Static", "Build" ), cmd_xcopy("include", "{inc_dir}"), ], "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"], }, "lcms2": { "url": f"{SF_PROJECTS}/lcms/files/lcms/{V['lcms2']}/FILENAME/download", "filename": f"lcms2-{V['lcms2']}.tar.gz", "license": "LICENSE", "patch": { r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": { # default is /MD for x86 and /MT for x64, we need /MD always "MultiThreaded": "MultiThreadedDLL", # noqa: E501 # retarget to default toolset (selected by vcvarsall.bat) "v143": "$(DefaultPlatformToolset)", # noqa: E501 # retarget to latest (selected by vcvarsall.bat) "10.0": "$(WindowsSDKVersion)", # noqa: E501 } }, "build": [ cmd_rmdir("Lib"), cmd_rmdir(r"Projects\VC2022\Release"), cmd_msbuild(r"Projects\VC2022\lcms2.sln", "Release", "Clean"), cmd_msbuild( r"Projects\VC2022\lcms2.sln", "Release", "lcms2_static:Rebuild" ), cmd_xcopy("include", "{inc_dir}"), ], "libs": [r"Lib\MS\*.lib"], }, "openjpeg": { "url": f"https://github.com/uclouvain/openjpeg/archive/v{V['openjpeg']}.tar.gz", "filename": f"openjpeg-{V['openjpeg']}.tar.gz", "license": "LICENSE", "build": [ *cmds_cmake( "openjp2", "-DBUILD_CODEC:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF" ), cmd_mkdir(rf"{{inc_dir}}\openjpeg-{V['openjpeg']}"), cmd_copy(r"src\lib\openjp2\*.h", rf"{{inc_dir}}\openjpeg-{V['openjpeg']}"), ], "libs": [r"bin\*.lib"], }, "libimagequant": { "url": "https://github.com/ImageOptim/libimagequant/archive/{V['libimagequant']}.tar.gz", "filename": f"libimagequant-{V['libimagequant']}.tar.gz", "license": "COPYRIGHT", "build": [ cmd_cd("imagequant-sys"), "cargo build --release", ], "headers": ["libimagequant.h"], "libs": [r"..\target\release\imagequant_sys.lib"], }, "harfbuzz": { "url": f"https://github.com/harfbuzz/harfbuzz/releases/download/{V['harfbuzz']}/FILENAME", "filename": f"harfbuzz-{V['harfbuzz']}.tar.xz", "license": "COPYING", "build": [ *cmds_cmake( "harfbuzz", "-DHB_HAVE_FREETYPE:BOOL=TRUE", '-DCMAKE_CXX_FLAGS="-nologo -d2FH4-"', ), ], "headers": [r"src\*.h"], "libs": [r"*.lib"], }, "fribidi": { "url": f"https://github.com/fribidi/fribidi/archive/v{V['fribidi']}.zip", "filename": f"fribidi-{V['fribidi']}.zip", "license": "COPYING", "build": [ cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['fribidi']}-COPYING"), cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), # generated tab.i files cannot be cross-compiled " ^&^& ".join( [ "if {architecture}==ARM64 cmd /c call {vcvarsall} x86", *cmds_cmake("fribidi-gen", "-DARCH=x86", build_dir="build_x86"), ] ), *cmds_cmake("fribidi", "-DARCH={architecture}"), ], "bins": [r"*.dll"], }, "libavif": { "url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['libavif']}.tar.gz", "filename": f"libavif-{V['libavif']}.tar.gz", "license": "LICENSE", "build": [ "rustup update", f"{sys.executable} -m pip install meson", *cmds_cmake( "avif_static", "-DBUILD_SHARED_LIBS=OFF", "-DAVIF_LIBSHARPYUV=LOCAL", "-DAVIF_LIBYUV=LOCAL", "-DAVIF_CODEC_AOM=LOCAL", "-DCONFIG_AV1_HIGHBITDEPTH=0", "-DAVIF_CODEC_AOM_DECODE=OFF", "-DAVIF_CODEC_DAV1D=LOCAL", "-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON", build_type="MinSizeRel", ), cmd_xcopy("include", "{inc_dir}"), ], "libs": ["avif.lib"], }, } # based on distutils._msvccompiler from CPython 3.7.4 def find_msvs(architecture: str) -> dict[str, str] | None: root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") if not root: print("Program Files not found") return None requires = ["-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"] if architecture == "ARM64": requires += ["-requires", "Microsoft.VisualStudio.Component.VC.Tools.ARM64"] try: vspath = ( subprocess.check_output( [ os.path.join( root, "Microsoft Visual Studio", "Installer", "vswhere.exe" ), "-latest", "-prerelease", *requires, "-property", "installationPath", "-products", "*", ] ) .decode(encoding="mbcs") .strip() ) except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): print("vswhere not found") return None if not os.path.isdir(os.path.join(vspath, "VC", "Auxiliary", "Build")): print("Visual Studio seems to be missing C compiler") return None # vs2017 msbuild = os.path.join(vspath, "MSBuild", "15.0", "Bin", "MSBuild.exe") if not os.path.isfile(msbuild): # vs2019 msbuild = os.path.join(vspath, "MSBuild", "Current", "Bin", "MSBuild.exe") if not os.path.isfile(msbuild): print("Visual Studio MSBuild not found") return None vcvarsall = os.path.join(vspath, "VC", "Auxiliary", "Build", "vcvarsall.bat") if not os.path.isfile(vcvarsall): print("Visual Studio vcvarsall not found") return None return { "vs_dir": vspath, "msbuild": f'"{msbuild}"', "vcvarsall": f'"{vcvarsall}"', "nmake": "nmake.exe", # nmake selected by vcvarsall } def download_dep(url: str, file: str) -> None: import urllib.error import urllib.request ex = None for i in range(3): try: print(f"Fetching {url} (attempt {i + 1})...") content = urllib.request.urlopen(url).read() with open(file, "wb") as f: f.write(content) break except urllib.error.URLError as e: ex = e else: raise RuntimeError(ex) def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None: import tarfile import zipfile depends_dir = prefs["depends_dir"] sources_dir = prefs["src_dir"] file = os.path.join(depends_dir, filename) if not os.path.exists(file): # First try our mirror mirror_url = ( f"https://raw.githubusercontent.com/" f"python-pillow/pillow-depends/main/{filename}" ) try: download_dep(mirror_url, file) except RuntimeError as exc: # Otherwise try upstream print(exc) download_dep(url.replace("FILENAME", filename), file) print("Extracting " + filename) sources_dir_abs = os.path.abspath(sources_dir) if filename.endswith(".zip"): with zipfile.ZipFile(file) as zf: for member in zf.namelist(): member_abspath = os.path.abspath(os.path.join(sources_dir, member)) member_prefix = os.path.commonpath([sources_dir_abs, member_abspath]) if sources_dir_abs != member_prefix: msg = "Attempted Path Traversal in Zip File" raise RuntimeError(msg) zf.extractall(sources_dir) elif filename.endswith((".tar.gz", ".tar.xz")): with tarfile.open(file, "r:xz" if filename.endswith(".xz") else "r:gz") as tgz: for member in tgz.getnames(): member_abspath = os.path.abspath(os.path.join(sources_dir, member)) member_prefix = os.path.commonpath([sources_dir_abs, member_abspath]) if sources_dir_abs != member_prefix: msg = "Attempted Path Traversal in Tar File" raise RuntimeError(msg) if sys.version_info >= (3, 12): tgz.extractall(sources_dir, filter="data") else: tgz.extractall(sources_dir) else: msg = "Unknown archive type: " + filename raise RuntimeError(msg) def write_script( name: str, lines: list[str], prefs: dict[str, str], verbose: bool ) -> None: name = os.path.join(prefs["build_dir"], name) lines = [line.format(**prefs) for line in lines] print("Writing " + name) with open(name, "w", newline="") as f: f.write(os.linesep.join(lines)) if verbose: for line in lines: print(" " + line) def get_footer(dep: dict[str, Any]) -> list[str]: return ( [cmd_copy(out, "{inc_dir}") for out in dep.get("headers", [])] + [cmd_copy(out, "{lib_dir}") for out in dep.get("libs", [])] + [cmd_copy(out, "{bin_dir}") for out in dep.get("bins", [])] ) def build_env(prefs: dict[str, str], verbose: bool) -> None: lines = [ "if defined DISTUTILS_USE_SDK goto end", cmd_set("INCLUDE", "{inc_dir}"), cmd_set("INCLIB", "{lib_dir}"), cmd_set("LIB", "{lib_dir}"), cmd_append("PATH", "{bin_dir}"), "call {vcvarsall} {vcvars_arch}", cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow cmd_set("py_vcruntime_redist", "true"), # always use /MD, never /MT ":end", "@echo on", ] write_script("build_env.cmd", lines, prefs, verbose) def build_dep(name: str, prefs: dict[str, str], verbose: bool) -> str: dep = DEPS[name] directory = dep["dir"] file = f"build_dep_{name}.cmd" license_dir = prefs["license_dir"] sources_dir = prefs["src_dir"] extract_dep(dep["url"], dep["filename"], prefs) licenses = dep["license"] if isinstance(licenses, str): licenses = [licenses] license_text = "" for license_file in licenses: with open(os.path.join(sources_dir, directory, license_file)) as f: license_text += f.read() if "license_pattern" in dep: match = re.search(dep["license_pattern"], license_text, re.DOTALL) assert match is not None license_text = "\n".join(match.groups()) assert len(license_text) > 50 with open(os.path.join(license_dir, f"{directory}.txt"), "w") as f: print(f"Writing license {directory}.txt") f.write(license_text) for patch_file, patch_list in dep.get("patch", {}).items(): patch_file = os.path.join(sources_dir, directory, patch_file.format(**prefs)) with open(patch_file) as f: text = f.read() for patch_from, patch_to in patch_list.items(): patch_from = patch_from.format(**prefs) patch_to = patch_to.format(**prefs) assert patch_from in text text = text.replace(patch_from, patch_to) with open(patch_file, "w") as f: print(f"Patching {patch_file}") f.write(text) banner = f"Building {name} ({directory})" lines = [ r'call "{build_dir}\build_env.cmd"', "@echo " + ("=" * 70), f"@echo ==== {banner:<60} ====", "@echo " + ("=" * 70), cmd_cd(os.path.join(sources_dir, directory)), *dep.get("build", []), *get_footer(dep), ] write_script(file, lines, prefs, verbose) return file def build_dep_all(disabled: list[str], prefs: dict[str, str], verbose: bool) -> None: lines = [r'call "{build_dir}\build_env.cmd"'] gha_groups = "GITHUB_ACTIONS" in os.environ for dep_name in DEPS: print() if dep_name in disabled: print(f"Skipping disabled dependency {dep_name}") continue script = build_dep(dep_name, prefs, verbose) if gha_groups: lines.append(f"@echo ::group::Running {script}") lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"') lines.append("if errorlevel 1 echo Build failed! && exit /B 1") if gha_groups: lines.append("@echo ::endgroup::") print() lines.append("@echo All Pillow dependencies built successfully!") write_script("build_dep_all.cmd", lines, prefs, verbose) def main() -> None: winbuild_dir = os.path.dirname(os.path.realpath(__file__)) parser = argparse.ArgumentParser( prog="winbuild\\build_prepare.py", description="Download and generate build scripts for Pillow dependencies.", epilog="""Arguments can also be supplied using the environment variables PILLOW_BUILD, PILLOW_DEPS, ARCHITECTURE. See winbuild\\build.rst for more information.""", ) parser.add_argument( "-v", "--verbose", action="store_true", help="print generated scripts" ) parser.add_argument( "-d", "--dir", "--build-dir", dest="build_dir", metavar="PILLOW_BUILD", default=os.environ.get("PILLOW_BUILD", os.path.join(winbuild_dir, "build")), help="build directory (default: 'winbuild\\build')", ) parser.add_argument( "--depends", dest="depends_dir", metavar="PILLOW_DEPS", default=os.environ.get("PILLOW_DEPS", os.path.join(winbuild_dir, "depends")), help="directory used to store cached dependencies " "(default: 'winbuild\\depends')", ) parser.add_argument( "--architecture", choices=ARCHITECTURES, default=os.environ.get( "ARCHITECTURE", ( "ARM64" if platform.machine() == "ARM64" else ("x86" if struct.calcsize("P") == 4 else "AMD64") ), ), help="build architecture (default: same as host Python)", ) parser.add_argument( "--nmake", dest="cmake_generator", action="store_const", const="NMake Makefiles", default="Ninja", help="build dependencies using NMake instead of Ninja", ) parser.add_argument( "--no-imagequant", action="store_true", help="skip GPL-licensed optional dependency libimagequant", ) parser.add_argument( "--no-fribidi", "--no-raqm", action="store_true", help="skip LGPL-licensed optional dependency FriBiDi", ) parser.add_argument( "--no-avif", action="store_true", help="skip optional dependency libavif", ) args = parser.parse_args() arch_prefs = ARCHITECTURES[args.architecture] print("Target architecture:", args.architecture) msvs = find_msvs(args.architecture) if msvs is None: msg = "Visual Studio not found. Please install Visual Studio 2017 or newer." raise RuntimeError(msg) print("Found Visual Studio at:", msvs["vs_dir"]) # dependency cache directory args.depends_dir = os.path.abspath(args.depends_dir) os.makedirs(args.depends_dir, exist_ok=True) print("Caching dependencies in:", args.depends_dir) args.build_dir = os.path.abspath(args.build_dir) print("Using output directory:", args.build_dir) # build directory for *.h files inc_dir = os.path.join(args.build_dir, "inc") # build directory for *.lib files lib_dir = os.path.join(args.build_dir, "lib") # build directory for *.bin files bin_dir = os.path.join(args.build_dir, "bin") # directory for storing project files sources_dir = os.path.join(args.build_dir, "src") # copy dependency licenses to this directory license_dir = os.path.join(args.build_dir, "license") shutil.rmtree(args.build_dir, ignore_errors=True) os.makedirs(args.build_dir, exist_ok=False) for path in [inc_dir, lib_dir, bin_dir, sources_dir, license_dir]: os.makedirs(path, exist_ok=True) disabled = [] if args.no_imagequant: disabled += ["libimagequant"] if args.no_fribidi: disabled += ["fribidi"] if args.no_avif or args.architecture == "ARM64": disabled += ["libavif"] prefs = { "architecture": args.architecture, **arch_prefs, # Pillow paths "winbuild_dir": winbuild_dir, # Build paths "bin_dir": bin_dir, "build_dir": args.build_dir, "depends_dir": args.depends_dir, "inc_dir": inc_dir, "lib_dir": lib_dir, "license_dir": license_dir, "src_dir": sources_dir, # Compilers / Tools **msvs, "cmake": "cmake.exe", # TODO find CMAKE automatically "cmake_generator": args.cmake_generator, # TODO find NASM automatically } for k, v in DEPS.items(): if "dir" not in v: v["dir"] = re.sub(r"\.(tar\.gz|tar\.xz|zip)", "", v["filename"]) prefs[f"dir_{k}"] = os.path.join(sources_dir, v["dir"]) print() write_script(".gitignore", ["*"], prefs, args.verbose) build_env(prefs, args.verbose) build_dep_all(disabled, prefs, args.verbose) if __name__ == "__main__": main()