#!/usr/bin/env python
import distutils
from distutils.command.build import build as _build
from setuptools.command.develop import develop as _develop
from setuptools.command.bdist_wheel import bdist_wheel as _bdist_wheel
from packaging.specifiers import SpecifierSet
from packaging.version import Version
from pyproject_parser import PyProject
from setuptools import Distribution
from setuptools import setup, Command
import os
import sys
# Disable SourceLink during the build until it can read repo-format v1, #1613
os.environ["EnableSourceControlManagerQueries"] = "false"
NET46_SUPPORT_OPTION = "--net46-support"
NET46_SUPPORT = NET46_SUPPORT_OPTION in sys.argv
if NET46_SUPPORT:
sys.argv.remove(NET46_SUPPORT_OPTION)
WINDOWS_PLATFORM_TAG = "win32.win_amd64"
class DotnetLib:
def __init__(self, name, path, **kwargs):
self.name = name
self.path = path
self.args = kwargs
class build_dotnet(Command):
"""Build command for dotnet-cli based builds"""
description = "Build DLLs with dotnet-cli"
user_options = [
("dotnet-config=", None, "dotnet build configuration"),
(
"inplace",
"i",
"ignore build-lib and put compiled extensions into the source "
+ "directory alongside your pure Python modules",
),
]
def initialize_options(self):
self.dotnet_config = None
self.build_lib = None
self.inplace = False
def finalize_options(self):
if self.dotnet_config is None:
self.dotnet_config = "release"
build = self.distribution.get_command_obj("build")
build.ensure_finalized()
if self.inplace:
self.build_lib = "."
else:
self.build_lib = build.build_lib
def run(self):
dotnet_modules = self.distribution.dotnet_libs
for lib in dotnet_modules:
output = os.path.join(
os.path.abspath(self.build_lib), lib.args.pop("output")
)
rename = lib.args.pop("rename", {})
opts = sum(
[
["--" + name.replace("_", "-"), value]
for name, value in lib.args.items()
],
[],
)
opts.extend(["--configuration", self.dotnet_config])
opts.extend(["--output", output])
self.announce("Running dotnet build...", level=distutils.log.INFO)
self.spawn(["dotnet", "build", lib.path] + opts)
for k, v in rename.items():
source = os.path.join(output, k)
dest = os.path.join(output, v)
if os.path.isfile(source):
try:
os.remove(dest)
except OSError:
pass
self.move_file(src=source, dst=dest, level=distutils.log.INFO)
else:
self.warn(
"Can't find file to rename: {}, current dir: {}".format(
source, os.getcwd()
)
)
# Add build_dotnet to the build tasks:
class build(_build):
sub_commands = _build.sub_commands + [("build_dotnet", None)]
class develop(_develop):
def install_for_development(self):
# Build extensions in-place
self.reinitialize_command("build_dotnet", inplace=1)
self.run_command("build_dotnet")
return super().install_for_development()
class bdist_wheel(_bdist_wheel):
def get_tag(self):
if NET46_SUPPORT:
platform_tag = WINDOWS_PLATFORM_TAG
else:
platform_tag = "any"
abi_tag = "none"
python_tag = self._get_python_tag()
return python_tag, abi_tag, platform_tag
def _get_python_tag(self) -> str:
pyproject = PyProject.load("pyproject.toml")
project = pyproject.project or {}
requires_python = project.get("requires-python")
if not requires_python:
raise RuntimeError("project.requires-python is required")
specifiers = SpecifierSet(str(requires_python))
return ".".join(
f"cp3{minor}"
for minor in range(0, 100)
if specifiers.contains(Version(f"3.{minor}"), prereleases=True)
)
# Monkey-patch Distribution s.t. it supports the dotnet_libs attribute
Distribution.dotnet_libs = None
cmdclass = {
"build": build,
"build_dotnet": build_dotnet,
"develop": develop,
"bdist_wheel": bdist_wheel,
}
if NET46_SUPPORT:
csproj = "src/compat/Python.Runtime.Compat.csproj"
else:
csproj = "src/runtime/Python.Runtime.csproj"
dotnet_libs = [DotnetLib("python-runtime", csproj, output="pythonnet/runtime")]
setup(
cmdclass=cmdclass,
dotnet_libs=dotnet_libs,
)