windows: parse build configuration with argparse

This commit is contained in:
nulano 2023-02-13 03:16:04 +00:00
parent 57260d4924
commit eeb7c7c647
No known key found for this signature in database
GPG Key ID: B650CDF63B705766
3 changed files with 149 additions and 92 deletions

View File

@ -88,7 +88,7 @@ jobs:
- name: Prepare build - name: Prepare build
if: steps.build-cache.outputs.cache-hit != 'true' if: steps.build-cache.outputs.cache-hit != 'true'
run: | run: |
& python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation --srcdir & python.exe winbuild\build_prepare.py -v --python $env:pythonLocation
shell: pwsh shell: pwsh
- name: Build dependencies / libjpeg-turbo - name: Build dependencies / libjpeg-turbo

View File

@ -38,42 +38,50 @@ Visual Studio is found automatically with ``vswhere.exe``.
Build configuration Build configuration
------------------- -------------------
The following environment variables, if set, will override the default Run ``build_prepare.py`` to configure the build::
behaviour of ``build_prepare.py``:
* ``PYTHON`` + ``EXECUTABLE`` point to the target version of Python. usage: winbuild\build_prepare.py [-h] [-v] [-d PILLOW_BUILD]
If ``PYTHON`` is unset, the version of Python used to run [--depends PILLOW_DEPS]
``build_prepare.py`` will be used. If only ``PYTHON`` is set, [--architecture {x86,x64,ARM64}]
``EXECUTABLE`` defaults to ``python.exe``. [--python PYTHON] [--executable EXECUTABLE]
* ``ARCHITECTURE`` is used to select a ``x86``, ``x64`` or ``ARM64`` build. [--nmake] [--no-imagequant] [--no-fribidi]
By default, uses same architecture as the version of Python used to run ``build_prepare.py``.
* ``PILLOW_BUILD`` can be used to override the ``winbuild\build`` directory
path, used to store generated build scripts and compiled libraries.
**Warning:** This directory is wiped when ``build_prepare.py`` is run.
* ``PILLOW_DEPS`` points to the directory used to store downloaded
dependencies. By default ``winbuild\depends`` is used.
``build_prepare.py`` also supports the following command line parameters: Download dependencies and generate build scripts for Pillow.
* ``-v`` will print generated scripts. options:
* ``--nmake`` will use NMake instead of Ninja for CMake dependencies -h, --help show this help message and exit
* ``--no-imagequant`` will skip GPL-licensed ``libimagequant`` optional dependency -v, --verbose print generated scripts
* ``--no-fribidi`` or ``--no-raqm`` will skip optional LGPL-licensed dependency FriBiDi -d PILLOW_BUILD, --dir PILLOW_BUILD, --build-dir PILLOW_BUILD
(required for Raqm text shaping). build directory (default: 'winbuild\build')
* ``--python=<path>`` and ``--executable=<exe>`` override ``PYTHON`` and ``EXECUTABLE``. --depends PILLOW_DEPS
* ``--architecture=<arch>`` overrides ``ARCHITECTURE``. directory used to store cached dependencies (default:
* ``--dir=<path>`` and ``--depends=<path>`` override ``PILLOW_BUILD`` 'winbuild\depends')
and ``PILLOW_DEPS``. --architecture {x86,x64,ARM64}
build architecture (default: same as host python)
--python PYTHON Python install directory (default: use host python)
--executable EXECUTABLE
Python executable (default: use host python)
--nmake build dependencies using NMake instead of Ninja
--no-imagequant skip GPL-licensed optional dependency libimagequant
--no-fribidi, --no-raqm
skip LGPL-licensed optional dependency FriBiDi
Arguments can also be supplied using the environment variables PILLOW_BUILD,
PILLOW_DEPS, ARCHITECTURE, PYTHON, EXECUTABLE. See winbuild\build.rst for more
information.
**Warning:** The build directory is wiped when ``build_prepare.py`` is run.
Dependencies Dependencies
------------ ------------
Dependencies will be automatically downloaded by ``build_prepare.py``. Dependencies will be automatically downloaded by ``build_prepare.py``.
By default, downloaded dependencies are stored in ``winbuild\depends``; By default, downloaded dependencies are stored in ``winbuild\depends``;
set the ``PILLOW_DEPS`` environment variable to override this location. use the ``--depends`` argument or ``PILLOW_DEPS`` environment variable
to override this location.
To build all dependencies, run ``winbuild\build\build_dep_all.cmd``, To build all dependencies, run ``winbuild\build\build_dep_all.cmd``,
or run the individual scripts to build each dependency separately. or run the individual scripts in order to build each dependency separately.
Building Pillow Building Pillow
--------------- ---------------
@ -106,7 +114,7 @@ The following is a simplified version of the script used on AppVeyor:
set PYTHON=C:\Python38\bin set PYTHON=C:\Python38\bin
cd /D C:\Pillow\winbuild cd /D C:\Pillow\winbuild
C:\Python37\bin\python.exe build_prepare.py -v --depends=C:\pillow-depends C:\Python37\bin\python.exe build_prepare.py -v --depends C:\pillow-depends
build\build_dep_all.cmd build\build_dep_all.cmd
build\build_pillow.cmd install build\build_pillow.cmd install
cd .. cd ..

View File

@ -1,3 +1,4 @@
import argparse
import os import os
import platform import platform
import re import re
@ -434,7 +435,7 @@ def extract_dep(url, filename):
import urllib.request import urllib.request
import zipfile import zipfile
file = os.path.join(depends_dir, filename) file = os.path.join(args.depends_dir, filename)
if not os.path.exists(file): if not os.path.exists(file):
ex = None ex = None
for i in range(3): for i in range(3):
@ -475,12 +476,12 @@ def extract_dep(url, filename):
def write_script(name, lines): def write_script(name, lines):
name = os.path.join(build_dir, name) name = os.path.join(args.build_dir, name)
lines = [line.format(**prefs) for line in lines] lines = [line.format(**prefs) for line in lines]
print("Writing " + name) print("Writing " + name)
with open(name, "w", newline="") as f: with open(name, "w", newline="") as f:
f.write(os.linesep.join(lines)) f.write(os.linesep.join(lines))
if verbose: if args.verbose:
for line in lines: for line in lines:
print(" " + line) print(" " + line)
@ -549,11 +550,14 @@ def build_dep(name):
def build_dep_all(): def build_dep_all():
lines = ["@echo on"] lines = ["@echo on"]
for dep_name in deps: for dep_name in deps:
print()
if dep_name in disabled: if dep_name in disabled:
print(f"Skipping disabled dependency {dep_name}")
continue continue
script = build_dep(dep_name) script = build_dep(dep_name)
lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"') lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"')
lines.append("if errorlevel 1 echo Build failed! && exit /B 1") lines.append("if errorlevel 1 echo Build failed! && exit /B 1")
print()
lines.append("@echo All Pillow dependencies built successfully!") lines.append("@echo All Pillow dependencies built successfully!")
write_script("build_dep_all.cmd", lines) write_script("build_dep_all.cmd", lines)
@ -572,59 +576,90 @@ def build_pillow():
if __name__ == "__main__": if __name__ == "__main__":
# winbuild directory
winbuild_dir = os.path.dirname(os.path.realpath(__file__)) winbuild_dir = os.path.dirname(os.path.realpath(__file__))
pillow_dir = os.path.realpath(os.path.join(winbuild_dir, ".."))
verbose = False parser = argparse.ArgumentParser(
disabled = [] prog="winbuild\\build_prepare.py",
depends_dir = os.environ.get("PILLOW_DEPS", os.path.join(winbuild_dir, "depends")) description="Download dependencies and generate build scripts for Pillow.",
python_dir = os.environ.get("PYTHON") epilog="""Arguments can also be supplied using the environment variables
python_exe = os.environ.get("EXECUTABLE", "python.exe") PILLOW_BUILD, PILLOW_DEPS, ARCHITECTURE, PYTHON, EXECUTABLE.
architecture = os.environ.get( See winbuild\\build.rst for more information.""",
"ARCHITECTURE",
"ARM64"
if platform.machine() == "ARM64"
else ("x86" if struct.calcsize("P") == 4 else "x64"),
) )
build_dir = os.environ.get("PILLOW_BUILD", os.path.join(winbuild_dir, "build")) parser.add_argument(
cmake_generator = "Ninja" "-v", "--verbose", action="store_true", help="print generated scripts"
sources_dir = "" )
for arg in sys.argv[1:]: parser.add_argument(
if arg == "-v": "-d",
verbose = True "--dir",
elif arg == "--nmake": "--build-dir",
cmake_generator = "NMake Makefiles" dest="build_dir",
elif arg == "--no-imagequant": metavar="PILLOW_BUILD",
disabled += ["libimagequant"] default=os.environ.get("PILLOW_BUILD", os.path.join(winbuild_dir, "build")),
elif arg == "--no-raqm" or arg == "--no-fribidi": help="build directory (default: 'winbuild\\build')",
disabled += ["fribidi"] )
elif arg.startswith("--depends="): parser.add_argument(
depends_dir = arg[10:] "--depends",
elif arg.startswith("--python="): dest="depends_dir",
python_dir = arg[9:] metavar="PILLOW_DEPS",
elif arg.startswith("--executable="): default=os.environ.get("PILLOW_DEPS", os.path.join(winbuild_dir, "depends")),
python_exe = arg[13:] help="directory used to store cached dependencies (default: 'winbuild\\depends')", # noqa: E501
elif arg.startswith("--architecture="): )
architecture = arg[15:] parser.add_argument(
elif arg.startswith("--dir="): "--architecture",
build_dir = arg[6:] choices=architectures,
elif arg == "--srcdir": default=os.environ.get(
sources_dir = os.path.sep + "src" "ARCHITECTURE",
else: (
msg = "Unknown parameter: " + arg "ARM64"
raise ValueError(msg) if platform.machine() == "ARM64"
else ("x86" if struct.calcsize("P") == 4 else "x64")
),
),
help="build architecture (default: same as host python)",
)
parser.add_argument(
"--python",
dest="python_dir",
metavar="PYTHON",
default=os.environ.get("PYTHON"),
help="Python install directory (default: use host python)",
)
parser.add_argument(
"--executable",
dest="python_exe",
metavar="EXECUTABLE",
default=os.environ.get("EXECUTABLE", "python.exe"),
help="Python executable (default: use 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",
)
args = parser.parse_args()
# dependency cache directory arch_prefs = architectures[args.architecture]
os.makedirs(depends_dir, exist_ok=True) print("Target Architecture:", args.architecture)
print("Caching dependencies in:", depends_dir)
if python_dir is None: if args.python_dir is None:
python_dir = os.path.dirname(os.path.realpath(sys.executable)) args.python_dir = os.path.dirname(os.path.realpath(sys.executable))
python_exe = os.path.basename(sys.executable) args.python_exe = os.path.basename(sys.executable)
print("Target Python:", os.path.join(python_dir, python_exe)) print("Target Python:", os.path.join(args.python_dir, args.python_exe))
arch_prefs = architectures[architecture]
print("Target Architecture:", architecture)
msvs = find_msvs() msvs = find_msvs()
if msvs is None: if msvs is None:
@ -632,35 +667,47 @@ if __name__ == "__main__":
raise RuntimeError(msg) raise RuntimeError(msg)
print("Found Visual Studio at:", msvs["vs_dir"]) print("Found Visual Studio at:", msvs["vs_dir"])
print("Using output directory:", build_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 # build directory for *.h files
inc_dir = os.path.join(build_dir, "inc") inc_dir = os.path.join(args.build_dir, "inc")
# build directory for *.lib files # build directory for *.lib files
lib_dir = os.path.join(build_dir, "lib") lib_dir = os.path.join(args.build_dir, "lib")
# build directory for *.bin files # build directory for *.bin files
bin_dir = os.path.join(build_dir, "bin") bin_dir = os.path.join(args.build_dir, "bin")
# directory for storing project files # directory for storing project files
sources_dir = build_dir + sources_dir sources_dir = os.path.join(args.build_dir, "src")
# copy dependency licenses to this directory # copy dependency licenses to this directory
license_dir = os.path.join(build_dir, "license") license_dir = os.path.join(args.build_dir, "license")
shutil.rmtree(build_dir, ignore_errors=True) shutil.rmtree(args.build_dir, ignore_errors=True)
os.makedirs(build_dir, exist_ok=False) os.makedirs(args.build_dir, exist_ok=False)
for path in [inc_dir, lib_dir, bin_dir, sources_dir, license_dir]: for path in [inc_dir, lib_dir, bin_dir, sources_dir, license_dir]:
os.makedirs(path, exist_ok=True) os.makedirs(path, exist_ok=True)
disabled = []
if args.no_imagequant:
disabled += ["libimagequant"]
if args.no_fribidi:
disabled += ["fribidi"]
prefs = { prefs = {
# Python paths / preferences # Python paths / preferences
"python_dir": python_dir, "python_dir": args.python_dir,
"python_exe": python_exe, "python_exe": args.python_exe,
"architecture": architecture, "architecture": args.architecture,
**arch_prefs, **arch_prefs,
# Pillow paths # Pillow paths
"pillow_dir": os.path.realpath(os.path.join(winbuild_dir, "..")), "pillow_dir": pillow_dir,
"winbuild_dir": winbuild_dir, "winbuild_dir": winbuild_dir,
# Build paths # Build paths
"build_dir": build_dir, "build_dir": args.build_dir,
"inc_dir": inc_dir, "inc_dir": inc_dir,
"lib_dir": lib_dir, "lib_dir": lib_dir,
"bin_dir": bin_dir, "bin_dir": bin_dir,
@ -669,7 +716,7 @@ if __name__ == "__main__":
# Compilers / Tools # Compilers / Tools
**msvs, **msvs,
"cmake": "cmake.exe", # TODO find CMAKE automatically "cmake": "cmake.exe", # TODO find CMAKE automatically
"cmake_generator": cmake_generator, "cmake_generator": args.cmake_generator,
# TODO find NASM automatically # TODO find NASM automatically
# script header # script header
"header": sum([header, msvs["header"], ["@echo on"]], []), "header": sum([header, msvs["header"], ["@echo on"]], []),
@ -682,4 +729,6 @@ if __name__ == "__main__":
write_script(".gitignore", ["*"]) write_script(".gitignore", ["*"])
build_dep_all() build_dep_all()
if args.verbose:
print()
build_pillow() build_pillow()