view windows-dev/Configure.py @ 654:0d6673d06c2c

Add support for using "tomllib" (in Python's stdlib since 3.11) and "tomli" TOML packages. They are preferred if they are found to be installed. But note that the declared dependency for the "toml" extra nevertheless is the "toml" package. Because it is available for all supported Python versions. So use Python 3.11+ or install "tomli" manually if you want to use the alternate packages.
author Franz Glasner <fzglas.hg@dom66.de>
date Thu, 19 May 2022 22:10:59 +0200
parents 8efdb6357428
children
line wrap: on
line source

#
#
# Important Triplets:
#
# clang-cl (clang-cl /clang:-dumpmachine)
#
#   x86_64-pc-windows-msvc
#   i386-pc-windows-msvc
#
# clang on FreeBSD (clang -dumpmachine):
#
#   x86_64-unknown-freebsd12.2
#   i386-unknown-freebsd12.2
#
# NOTE: gcc also knows "-dumpmachine"
#
#

from __future__ import print_function, absolute_import

import argparse
import collections
import datetime
import copy
import getopt
import os
import sys

import ninja_syntax

tool = build = host = None

#
# Global build variables (ordered because they must be written ordered
# -- and with simple attribute access
#
class BuildVars(collections.OrderedDict):

    def __getattr__(self, n):
        try:
            return self[n]
        except KeyError:
            raise AttributeError(n)

    def __setattr__(self, n, v):
        # private v
        if n.startswith("_OrderedDict__"):
            return super(BuildVars, self).__setattr__(n, v)
        self[n] = v


def make_obj_name(name, newext):
    bn = os.path.basename(name)
    if not bn:
        return bn
    root, ext = os.path.splitext(bn)
    return root + newext

options = argparse.Namespace(
    user_includes = [],
    sys_includes = [],
    sys_libpath = [],
    user_libpath = [],
    link_with_python = None,
    python_limited_api = None,
)

gbv = BuildVars()
gbv.intdir = "build/_builddir-test/tmp"
gbv.srcdir = "."
gbv.builddir = "$intdir"
gbv.outdir = "$intdir/out"

opts, args = getopt.getopt(
    sys.argv[1:],
    "B:H:t:I:L:",
    ["build=",
     "host=",
     "tool=",
     "include=",
     "libpath=",
     "sys-include=",
     "sys-libpath=",
     "CC=",
     "LINK=",
     "intdir=",              # intermediate files
     "builddir=",            # Ninja builddir
     "outdir=",              # The built objects
     "link-with-python=",    # link with libpython
     "python-limited-api=",  # Use Py_LIMITED_API
     ])
for opt, val in opts:
    if opt in ("-t", "--tool"):
        if tool is None:
            tool = argparse.Namespace(local=False, msvc=False, clang=False)
        if val == "msvc":
            tool.msvc = True
            tool.compile_only = "/c"
            tool.define_format = "/D {}"
            tool.include_format = "/I {}"
            tool.lib_format = "{}"
            tool.libpath_format = "/libpath:{}"
            tool.dependencies = "msvc"
        elif val == "clang-cl":
            tool.msvc = tool.clang = True
            tool.compile_only = "/c"
            tool.define_format = "/D {}"
            tool.include_format = "/I {}"
            tool.lib_format = "{}"
            tool.libpath_format = "/libpath:{}"
            tool.dependencies = "msvc"
        elif val == "clang":
            tool.clang = True
            tool.compile_only = "-c"
            tool.define_format = "-D{}"
            tool.include_format = "-I{}"
            tool.lib_format = "-l{}"
            tool.libpath_format = "-L{}"
            tool.dependencies = "gcc"
        elif val in ("local", "posix"):
            tool.local = True
            tool.compile_only = "-c"
            tool.define_format = "-D{}"
            tool.include_format = "-I{}"
            tool.lib_format = "-l{}"
            tool.libpath_format = "-L{}"
            tool.dependencies = "gcc"
        else:
            raise getopt.GetoptError("unknown tool value: {}".format(val), opt)
    elif opt in ("-B", "--build"):
        build = argparse.Namespace(type=None,
                                   posix=False, windows=False,
                                   pathmod=None)
        if val == "windows":
            # build on Windows with clang-cl
            build.windows = True
            build.type = "windows"
        elif val == "posix":
            build.posix = True
            build.type = "posix"
        else:
            raise getopt.GetoptError("unknwon build value: {}".format(val),
                                     opt)
    elif opt in ("-H", "--host"):
        if host is None:
            host = argparse.Namespace(windows=False)
        if val == "windows":
            host.type = "windows"
            host.windows = True
            host.posix = False
            host.objext = ".obj"
            host.pydext = ".pyd"
        elif val == "posix":
            host.type = "posix"
            host.windows = False
            host.posix = True
            host.objext = ".o"
            host.pydext = ".so"
        else:
            raise getopt.GetoptError("unknown host value: {}".format(val),
                                     opt)
    elif opt in ("-I", "--include"):
        options.user_includes.append(val)
    elif opt in ("-L", "--libpath"):
        options.user_libpath.append(val)
    elif opt == "--sys-include":
        options.sys_includes.append(val)
    elif opt == "--sys-libpath":
        options.sys_libpath.append(val)
    elif opt == "--CC":
        gbv.cc = val
    elif opt == "--LINK":
        gbv.link = val
    elif opt == "--intdir":
        gbv.intdir = val
    elif opt == "--builddir":
        gbv.builddir = val
    elif opt == "--outdir":
        gbv.outdir = val
    elif opt == "--link-with-python":
        options.link_with_python = val
    elif opt == "--python-limited-api":
        if val.lower().startswith("0x"):
            options.python_limited_api = val
        else:
            # Here we are Python 3.7+ when using the limited API
            options.python_limited_api = "0x03070000"
    else:
        raise getopt.GetoptError("Unhandled option `{}'".format(opt), opt)

if tool is None:
    print("ERROR: no tool given", file=sys.stderr)
    sys.exit(1)

if build.windows and host.posix:
    print("ERROR: cross-compiling on Windows not supported", file=sys.stderr)
    sys.exit(1)

if build.windows:
    import ntpath as pathmod
else:
    import posixpath as pathmod
build.pathmod = pathmod

if tool.msvc:
    if tool.clang:
        if not getattr(gbv, "cc", None):
            gbv.cc = "clang-cl"
        if not getattr(gbv, "link", None):
            gbv.link = "lld-link"
    else:
        gbv.cc = "cl"
        gbv.link = "link"
elif tool.clang:
    gbv.cc = "clang"
    gbv.link = "clang"   # link C through the compiler
elif tool.local:
    gbv.cc = "cc"
    gbv.link = "cc"       # link through the compiler
else:
    raise RuntimeError("tool condition is not handled")

speedups_sources = [
    "$srcdir/configmix/_speedups.c",
]

ccflags = []
ccwarnings = []
ldflags = []

defines = [
#    "PY_SSIZE_T_CLEAN",   # in the source
    "HAVE_THREADS",
]
if options.python_limited_api:
    defines.append("Py_LIMITED_API={}".format(options.python_limited_api))

# XXX TBD: handle debug/release build _DEBUG/NDEBUG

includes = []
includes.extend(options.sys_includes)
includes.extend(options.user_includes)


libpath = []
libpath.extend(options.sys_libpath)
libpath.extend(options.user_libpath)

libs = []
if host.windows:
    if tool.msvc:
        # automatically included via #pragma
        # libs.append("python3.lib")
        pass
else:
    if options.link_with_python:
        libs.append(options.link_with_python)

if host.windows:
    defines.append("WIN32")
    # XXX TBD Handle arch -> WIN64
    defines.append("WIN64")
    defines.append("_WINDOWS")
    # for a user dll
    defines.append("_USRDLL")
    defines.append("_WINDLL")

    defines.append("WIN32_LEAN_AND_MEAN")
    # WinXP: no extravagant Windows features
    defines.append("_WIN32_WINNT=0x0501")

    if tool.msvc:
        # XXX TBD warnings

        defines.append("_CRT_SECURE_NO_WARNINGS")

        ccflags.append("/Zi")
        ccflags.append("/MD")   # link to dll runtime
        #ccflags.append("/EHsc")   # no C++ here
        ccflags.append("/Gy")   # enable function level linking
        ccflags.append("/GS")   # enable the stack protector -fstack-protector
        ccflags.append("/O2")
        ccflags.append("/W3")

        # XXX TBD machine
        if tool.clang:
            ccflags.append("--target=x86_64-pc-windows-msvc")
        else:
            ccflags.append("-m64")

        if tool.clang:  # or tool.gcc
            ccflags.append("-fno-strict-aliasing")

        ldflags.append("/dll")
        ldflags.append("/debug")    # PDB output
        # 32-bit: -> 5.01   64-bit: 5.02
        ldflags.append("/subsystem:windows,5.02")
        ldflags.append("/incremental:no")
        #
        ldflags.append("/manifest:NO")


    if tool.clang:
        ccflags.append("-fms-compatibility-version=16.00")

        ccwarnings.append("-Wno-nonportable-include-path")
        ccwarnings.append("-Wno-microsoft-template")
        ccwarnings.append("-Wno-pragma-pack")
elif host.posix:
    defines.append("PIC")

    ccwarnings.extend(["-Wall", "-Wextra", "-pedantic"])

    ccflags.append("-g")
    ccflags.append("-fPIC")
    ccflags.append("-fvisibility=hidden")
    ccflags.append("-pthread")

    if tool.clang:  # || tool.gcc
        ccflags.append("-ffunction-sections")
        ccflags.append("-fdata-sections")
        ccflags.append("-fno-strict-aliasing")
        ccflags.append("-fstack-protector")
        ccflags.append("-faddrsig")    # use with --icf=all/safe when linking

    ldflags.append("-shared")
    ldflags.append("-Wl,-z,relro,-z,now")
    ldflags.append("-Wl,--build-id=sha1")
    # XXX TBD only when building in debug code
    if options.link_with_python:
        ldflags.append("-Wl,-z,defs")

    if tool.clang: # || tool.gcc
        ldflags.append("-Wl,--gc-sections")

    if tool.clang:
        ldflags.append("-Wl,--icf=safe")

    if options.python_limited_api:
        host.pydext = ".abi3" + host.pydext

defines.append("NDEBUG")

gbv.cppdefines = [tool.define_format.format(d) for d in defines]
gbv.includes = [tool.include_format.format(pathmod.normpath(i))
                for i in includes]
gbv.ccflags = ccflags
gbv.ccwarnings = ccwarnings
gbv.ldflags = ldflags
gbv.ldlibpath = [tool.libpath_format.format(pathmod.normpath(l))
                 for l in libpath]
gbv.ldlibs = [tool.lib_format.format(l) for l in libs]

n = ninja_syntax.Writer(sys.stdout)

n.comment('This file is used to build test Python extensions.')
n.comment(
    'It is generated by {} at {}Z.'.format(
        os.path.basename(__file__),
        datetime.datetime.utcnow().isoformat()))
n.newline()
n.comment('Created using command: {!r}'.format(sys.argv))
n.newline()
for k, v in gbv.items():
    n.variable(k, v)
n.newline()
if tool.msvc:
    # Note: this includes clang-cl
    n.rule("compile-pyextension-unit",
           "$cc /nologo /showIncludes /c $cppdefines $ccwarnings $includes $ccflags /Fd$intdir/$intsubdir/ /Fo$out $in",
           deps=tool.dependencies)
else:
    n.rule("compile-pyextension-unit",
           "$cc -MD -MF $intdir/_deps -MT $out $cppdefines $ccwarnings $includes $ccflags -c -o $out $in",
           deps=tool.dependencies,
           depfile="$intdir/_deps")
n.newline()
if tool.msvc:
    # XXX TBD: in "release" builds use /pdbaltpath:$out.pdb
    n.rule("link-pyextension", "$link /nologo $ldflags $ldlibpath /implib:$out.lib /pdb:$out.pdb /pdbaltpath:$pdbaltpath /out:$out $in $ldlibs")
else:
    n.rule("link-pyextension", "$link $cppdefines $ccwarnings $ccflags $ldflags -o $out $in $ldlibpath $ldlibs")
n.newline()

n.comment("_speedups")
for f in speedups_sources:
    n.build(pathmod.normpath("$intdir/$intsubdir/"+make_obj_name(f, host.objext)),
            "compile-pyextension-unit",
            inputs=pathmod.normpath(f),
            variables={"intsubdir": "speedups"})
n.newline()
linkinputs = [pathmod.normpath("$intdir/$intsubdir/"+make_obj_name(f, host.objext))
              for f in speedups_sources]
linkoutput = "$outdir/_speedups" + host.pydext
linkvariables = {"intsubdir": "speedups"}
if tool.msvc:
    linkvariables["pdbaltpath"] = os.path.basename(linkoutput) + ".pdb"
    implicit_outputs = [
        pathmod.normpath(linkoutput + ".pdb"),
        pathmod.normpath(linkoutput + ".lib")]
    if not tool.clang:
        implicit_outputs.append(pathmod.normpath(linkoutput + ".exp"))
else:
    implicit_outputs = None
n.build(linkoutput,
        "link-pyextension",
        inputs=linkinputs,
        implicit_outputs=implicit_outputs,
        variables=linkvariables)
n.newline()