refactoring: tidy up library and reuse more logic (#116)

pull/117/head
David Hewitt 2 years ago committed by GitHub
parent 1d7ee9fc23
commit 1e37cc88a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      CHANGELOG.md
  2. 2
      examples/namespace_package/Cargo.lock
  3. 3
      setup.cfg
  4. 4
      setup.py
  5. 2
      setuptools_rust/__init__.py
  6. 98
      setuptools_rust/build.py
  7. 108
      setuptools_rust/check.py
  8. 43
      setuptools_rust/clean.py
  9. 55
      setuptools_rust/command.py
  10. 16
      setuptools_rust/extension.py
  11. 6
      setuptools_rust/setuptools_ext.py
  12. 91
      setuptools_rust/test.py
  13. 9
      setuptools_rust/tomlgen.py
  14. 24
      setuptools_rust/utils.py

@ -1,12 +1,17 @@
# Changelog
## Unreleased
### Packaging
- Bump minimum Python version to Python 3.6.
### Changed
- Respect `PYO3_PYTHON` and `PYTHON_SYS_EXECUTABLE` environment variables if set. [#96](https://github.com/PyO3/setuptools-rust/pull/96)
- Add runtime dependency on setuptools >= 46.1. [#102](https://github.com/PyO3/setuptools-rust/pull/102)
- Append to, rather than replace, existing RUSTFLAGS when building. [#103](https://github.com/PyO3/setuptools-rust/pull/103)
- Append to, rather than replace, existing `RUSTFLAGS` when building. [#103](https://github.com/PyO3/setuptools-rust/pull/103)
### Fixed
- Set executable bit on shared library. [#110](https://github.com/PyO3/setuptools-rust/pull/110)
- Don't require optional wheel dependency. [#111](https://github.com/PyO3/setuptools-rust/pull/111)
- Don't require optional `wheel` dependency. [#111](https://github.com/PyO3/setuptools-rust/pull/111)
## 0.11.6 (2020-12-13)

@ -109,7 +109,7 @@ dependencies = [
]
[[package]]
name = "namespace_package_bar"
name = "namespace_package_rust"
version = "0.1.0"
dependencies = [
"pyo3",

@ -4,7 +4,7 @@ version = attr: setuptools_rust.__version__
author = Nikolay Kim
author_email = fafhrd91@gmail.com
license = MIT
description = Setuptools rust extension plugin
description = Setuptools Rust extension plugin
keywords = distutils, setuptools, rust
url = https://github.com/PyO3/setuptools-rust
long_description = file: README.md
@ -29,6 +29,7 @@ packages = setuptools_rust
zip_safe = True
install_requires = setuptools>=46.1; semantic_version>=2.6.0; toml>=0.9.0
setup_requires = setuptools>=46.1; setuptools_scm[toml]>=3.4.3
python_requires = >=3.6
[options.entry_points]
distutils.commands =

@ -3,6 +3,4 @@
from setuptools import setup
if __name__ == "__main__":
setup(
python_requires='>=3.5'
)
setup()

@ -1,5 +1,3 @@
from __future__ import print_function, absolute_import
from .build import build_rust
from .check import check_rust
from .clean import clean_rust

@ -1,28 +1,25 @@
from __future__ import print_function, absolute_import
import glob
import json
import os
import shutil
import sys
import subprocess
from distutils.cmd import Command
from distutils.errors import (
CompileError,
DistutilsExecError,
DistutilsFileError,
DistutilsPlatformError,
DistutilsSetupError,
)
from subprocess import check_output
from .command import RustCommand
from .extension import RustExtension
from .utils import (
Binding, Strip, cpython_feature, get_rust_version, get_rust_target_info
Binding, Strip, rust_features, get_rust_target_info
)
class build_rust(Command):
""" Command for building rust crates via cargo. """
class build_rust(RustCommand):
""" Command for building Rust crates via cargo. """
description = "build Rust extensions (compile/link to build directory)"
@ -33,9 +30,9 @@ class build_rust(Command):
"ignore build-lib and put compiled extensions into the source "
+ "directory alongside your pure Python modules",
),
("debug", "d", "Force debug to true for all rust extensions "),
("release", "r", "Force debug to false for all rust extensions "),
("qbuild", None, "Force enable quiet option for all rust extensions "),
("debug", "d", "Force debug to true for all Rust extensions "),
("release", "r", "Force debug to false for all Rust extensions "),
("qbuild", None, "Force enable quiet option for all Rust extensions "),
(
"build-temp",
"t",
@ -45,7 +42,7 @@ class build_rust(Command):
boolean_options = ["inplace", "debug", "release", "qbuild"]
def initialize_options(self):
self.extensions = ()
super().initialize_options()
self.inplace = None
self.debug = None
self.release = None
@ -54,11 +51,7 @@ class build_rust(Command):
self.plat_name = None
def finalize_options(self):
self.extensions = [
ext
for ext in self.distribution.rust_extensions
if isinstance(ext, RustExtension)
]
super().finalize_options()
# Inherit settings from the `build_ext` command
self.set_undefined_options(
@ -68,7 +61,7 @@ class build_rust(Command):
("inplace", "inplace"),
)
def build_extension(self, ext):
def run_for_extension(self, ext: RustExtension):
executable = ext.binding == Binding.Exec
rust_target_info = get_rust_target_info()
@ -94,7 +87,6 @@ class build_rust(Command):
# we'll target a 32-bit Rust build.
# Automatic target detection can be overridden via the CARGO_BUILD_TARGET
# environment variable.
# TODO: include --target for all platforms so env vars can't break the build
target_triple = None
target_args = []
if os.getenv("CARGO_BUILD_TARGET"):
@ -116,17 +108,16 @@ class build_rust(Command):
"--format-version",
"1",
]
# The decoding is needed for python 3.5 compatibility
metadata = json.loads(check_output(metadata_command).decode("utf-8"))
metadata = json.loads(check_output(metadata_command))
target_dir = metadata["target_directory"]
if not os.path.exists(ext.path):
raise DistutilsFileError(
"Can not find rust extension project file: %s" % ext.path
f"can't find Rust extension project file: {ext.path}"
)
features = set(ext.features)
features.update(cpython_feature(binding=ext.binding))
features.update(rust_features(binding=ext.binding))
debug_build = ext.debug if ext.debug is not None else self.inplace
debug_build = self.debug if self.debug is not None else debug_build
@ -190,24 +181,19 @@ class build_rust(Command):
# Execute cargo
try:
output = subprocess.check_output(args, env=env)
output = subprocess.check_output(args, env=env, encoding="latin-1")
except subprocess.CalledProcessError as e:
output = e.output
if isinstance(output, bytes):
output = e.output.decode("latin-1").strip()
raise CompileError(
"cargo failed with code: %d\n%s" % (e.returncode, output)
f"cargo failed with code: {e.returncode}\n{e.output}"
)
except OSError:
raise DistutilsExecError(
"Unable to execute 'cargo' - this package "
"requires rust to be installed and cargo to be on the PATH"
"requires Rust to be installed and cargo to be on the PATH"
)
if not quiet:
if isinstance(output, bytes):
output = output.decode("latin-1")
if output:
print(output, file=sys.stderr)
@ -231,8 +217,8 @@ class build_rust(Command):
continue
else:
raise DistutilsExecError(
"rust build failed; "
'unable to find executable "%s" in %s' % (name, target_dir)
"Rust build failed; "
f"unable to find executable '{name}' in '{target_dir}'"
)
else:
# search executable
@ -247,7 +233,7 @@ class build_rust(Command):
if not dylib_paths:
raise DistutilsExecError(
"rust build failed; unable to find executable in %s" % target_dir
f"Rust build failed; unable to find executable in {target_dir}"
)
else:
if sys.platform == "win32" or sys.platform == "cygwin":
@ -268,8 +254,7 @@ class build_rust(Command):
)
except StopIteration:
raise DistutilsExecError(
"rust build failed; unable to find any %s in %s"
% (wildcard_so, artifactsdir)
f"Rust build failed; unable to find any {wildcard_so} in {artifactsdir}"
)
# Ask build_ext where the shared library would go if it had built it,
@ -303,11 +288,7 @@ class build_rust(Command):
finally:
del build_ext.ext_map[modpath]
try:
os.makedirs(os.path.dirname(ext_path))
except OSError:
pass
os.makedirs(os.path.dirname(ext_path), exist_ok=True)
shutil.copyfile(dylib_path, ext_path)
if sys.platform != "win32" and not debug_build:
@ -330,40 +311,3 @@ class build_rust(Command):
mode = os.stat(ext_path).st_mode
mode |= (mode & 0o444) >> 2 # copy R bits to X
os.chmod(ext_path, mode)
def run(self):
if not self.extensions:
return
all_optional = all(ext.optional for ext in self.extensions)
try:
version = get_rust_version()
except DistutilsPlatformError as e:
if not all_optional:
raise
else:
print(str(e))
return
for ext in self.extensions:
try:
rust_version = ext.get_rust_version()
if rust_version is not None and version not in rust_version:
raise DistutilsPlatformError(
"Rust %s does not match extension requirement %s"
% (version, ext.rust_version)
)
self.build_extension(ext)
except (
DistutilsSetupError,
DistutilsFileError,
DistutilsExecError,
DistutilsPlatformError,
CompileError,
) as e:
if not ext.optional:
raise
else:
print("Build optional Rust extension %s failed." % ext.name)
print(str(e))

@ -1,61 +1,33 @@
from __future__ import print_function, absolute_import
import os
import sys
import subprocess
from distutils.cmd import Command
from distutils.errors import (
CompileError,
DistutilsFileError,
DistutilsExecError,
DistutilsPlatformError,
)
import semantic_version
from .command import RustCommand
from .extension import RustExtension
from .utils import cpython_feature, get_rust_version
from .utils import rust_features
MIN_VERSION = semantic_version.Spec(">=1.16")
class check_rust(Command):
""" Run rust check"""
class check_rust(RustCommand):
"""Run Rust check"""
description = "check rust extensions"
def initialize_options(self):
self.extensions = ()
def finalize_options(self):
self.extensions = [
ext
for ext in self.distribution.rust_extensions
if isinstance(ext, RustExtension)
]
description = "check Rust extensions"
def run(self):
if "sdist" in self.distribution.commands:
return
if not self.extensions:
return
all_optional = all(ext.optional for ext in self.extensions)
try:
version = get_rust_version()
except DistutilsPlatformError as e:
if not all_optional:
raise
else:
print(str(e))
return
if version not in MIN_VERSION:
print(
"Rust version mismatch: required rust%s got rust%s"
% (MIN_VERSION, version)
)
return
super().run()
def run_for_extension(self, ext: RustExtension):
# Make sure that if pythonXX-sys is used, it builds against the current
# executing python interpreter.
bindir = os.path.dirname(sys.executable)
@ -71,43 +43,35 @@ class check_rust(Command):
}
)
for ext in self.extensions:
try:
if not os.path.exists(ext.path):
raise DistutilsFileError(
"Can not file rust extension project file: %s" % ext.path
)
if not os.path.exists(ext.path):
raise DistutilsFileError(
f"can't find Rust extension project file: {ext.path}"
)
features = set(ext.features)
features.update(cpython_feature(binding=ext.binding))
features = set(ext.features)
features.update(rust_features(binding=ext.binding))
# check cargo command
feature_args = ["--features", " ".join(features)] if features else []
args = (
["cargo", "check", "--manifest-path", ext.path]
+ feature_args
+ list(ext.args or [])
)
# check cargo command
feature_args = ["--features", " ".join(features)] if features else []
args = (
["cargo", "check", "--manifest-path", ext.path]
+ feature_args
+ list(ext.args or [])
)
# Execute cargo command
try:
subprocess.check_output(args)
except subprocess.CalledProcessError as e:
raise CompileError(
"cargo failed with code: %d\n%s"
% (e.returncode, e.output.decode("utf-8"))
)
except OSError:
raise DistutilsExecError(
"Unable to execute 'cargo' - this package "
"requires rust to be installed and "
"cargo to be on the PATH"
)
else:
print("Extension '%s' checked" % ext.name)
except (DistutilsFileError, DistutilsExecError, CompileError) as e:
if not ext.optional:
raise
else:
print("Check optional Rust extension %s failed." % ext.name)
print(str(e))
# Execute cargo command
try:
subprocess.check_output(args)
except subprocess.CalledProcessError as e:
raise CompileError(
"cargo failed with code: %d\n%s"
% (e.returncode, e.output.decode("utf-8"))
)
except OSError:
raise DistutilsExecError(
"unable to execute 'cargo' - this package "
"requires Rust to be installed and "
"cargo to be on the PATH"
)
else:
print(f"extension '{ext.name}' checked")

@ -1,40 +1,27 @@
from __future__ import print_function, absolute_import
import sys
import subprocess
from distutils.cmd import Command
from .command import RustCommand
from .extension import RustExtension
class clean_rust(RustCommand):
"""Clean Rust extensions. """
class clean_rust(Command):
""" Clean rust extensions. """
description = "clean rust extensions (compile/link to build directory)"
description = "clean Rust extensions (compile/link to build directory)"
def initialize_options(self):
self.extensions = ()
super().initialize_options()
self.inplace = False
def finalize_options(self):
self.extensions = [
ext
for ext in self.distribution.rust_extensions
if isinstance(ext, RustExtension)
]
def run(self):
if not self.extensions:
return
for ext in self.extensions:
# build cargo command
args = ["cargo", "clean", "--manifest-path", ext.path]
def run_for_extension(self, ext: RustExtension):
# build cargo command
args = ["cargo", "clean", "--manifest-path", ext.path]
if not ext.quiet:
print(" ".join(args), file=sys.stderr)
if not ext.quiet:
print(" ".join(args), file=sys.stderr)
# Execute cargo command
try:
subprocess.check_output(args)
except:
pass
# Execute cargo command
try:
subprocess.check_output(args)
except:
pass

@ -0,0 +1,55 @@
from abc import ABC, abstractmethod
from distutils.cmd import Command
from distutils.errors import DistutilsPlatformError
from .extension import RustExtension
from .utils import get_rust_version
class RustCommand(Command, ABC):
"""Abstract base class for commands which interact with Rust Extensions."""
def initialize_options(self):
self.extensions = ()
def finalize_options(self):
self.extensions = [
ext
for ext in self.distribution.rust_extensions
if isinstance(ext, RustExtension)
]
def run(self):
if not self.extensions:
return
all_optional = all(ext.optional for ext in self.extensions)
try:
version = get_rust_version()
except DistutilsPlatformError as e:
if not all_optional:
raise
else:
print(str(e))
return
for ext in self.extensions:
try:
rust_version = ext.get_rust_version()
if rust_version is not None and version not in rust_version:
raise DistutilsPlatformError(
f"Rust {version} does not match extension requirement {rust_version}"
)
self.run_for_extension(ext)
except Exception as e:
if not ext.optional:
raise
else:
command_name = self.get_command_name()
print(f"{command_name}: optional Rust extension {ext.name} failed")
print(str(e))
@abstractmethod
def run_for_extension(self, extension: RustExtension) -> None:
...

@ -1,4 +1,3 @@
from __future__ import print_function, absolute_import
import os
import re
from distutils.errors import DistutilsSetupError
@ -100,16 +99,6 @@ class RustExtension:
# get relative path to Cargo manifest file
path = os.path.relpath(path)
# file = sys._getframe(1).f_globals.get('__file__')
# if file:
# dirname = os.path.dirname(file)
# print(dirname)
# if dirname:
# cwd = os.getcwd()
# os.chdir(dirname)
# path = os.path.abspath(path)
# os.chdir(cwd)
self.path = path
def get_lib_name(self):
@ -158,8 +147,7 @@ class RustExtension:
f.write(TMPL.format({"name": name}))
TMPL = """from __future__ import absolute_import, print_function
TMPL = """
import os
import sys
@ -171,5 +159,5 @@ def run():
if os.path.isfile(file):
os.execv(file, sys.argv)
else:
print("Can not execute '%s'" % name)
print("can't execute '{name}'")
"""

@ -1,6 +1,9 @@
from abc import ABC, abstractmethod
from distutils import log
from distutils.cmd import Command
from distutils.command.check import check
from distutils.command.clean import clean
from distutils.errors import DistutilsPlatformError
from setuptools.command.install import install
from setuptools.command.build_ext import build_ext
@ -9,6 +12,9 @@ try:
except ImportError:
bdist_wheel = None
from .extension import RustExtension
from .utils import get_rust_version
def add_rust_extension(dist):
build_ext_base_class = dist.cmdclass.get('build_ext', build_ext)

@ -1,4 +1,3 @@
from __future__ import print_function, absolute_import
import os
import sys
import subprocess
@ -8,40 +7,19 @@ from distutils.errors import CompileError, DistutilsFileError, DistutilsExecErro
import semantic_version
from .extension import RustExtension
from .utils import cpython_feature, get_rust_version
from .utils import rust_features, get_rust_version
MIN_VERSION = semantic_version.Spec(">=1.15")
class test_rust(Command):
""" Run cargo test"""
"""Run cargo test"""
description = "test rust extensions"
description = "test Rust extensions"
user_options = []
def initialize_options(self):
self.extensions = ()
def finalize_options(self):
self.extensions = [
ext
for ext in self.distribution.rust_extensions
if isinstance(ext, RustExtension)
]
def run(self):
if not self.extensions:
return
version = get_rust_version()
if version not in MIN_VERSION:
print(
"Rust version mismatch: required rust%s got rust%s"
% (MIN_VERSION, version)
)
return
def run_for_extension(self, ext: RustExtension):
# Make sure that if pythonXX-sys is used, it builds against the current
# executing python interpreter.
bindir = os.path.dirname(sys.executable)
@ -57,37 +35,36 @@ class test_rust(Command):
}
)
for ext in self.extensions:
if not os.path.exists(ext.path):
raise DistutilsFileError(
"Can not file rust extension project file: %s" % ext.path
)
if not os.path.exists(ext.path):
raise DistutilsFileError(
f"can't find Rust extension project file: {ext.path}"
)
features = set(ext.features)
features.update(cpython_feature(ext=False, binding=ext.binding))
features = set(ext.features)
features.update(rust_features(ext=False, binding=ext.binding))
# test cargo command
feature_args = ["--features", " ".join(features)] if features else []
args = (
["cargo", "test", "--manifest-path", ext.path]
+ feature_args
+ list(ext.args or [])
)
# test cargo command
feature_args = ["--features", " ".join(features)] if features else []
args = (
["cargo", "test", "--manifest-path", ext.path]
+ feature_args
+ list(ext.args or [])
)
# Execute cargo command
print(" ".join(args))
try:
subprocess.check_output(args, env=env)
except subprocess.CalledProcessError as e:
raise CompileError(
"cargo failed with code: %d\n%s"
% (e.returncode, e.output.decode("utf-8"))
)
except OSError:
raise DistutilsExecError(
"Unable to execute 'cargo' - this package "
"requires rust to be installed and "
"cargo to be on the PATH"
)
else:
print("Test has been completed for '%s' extension" % ext.name)
# Execute cargo command
print(" ".join(args))
try:
subprocess.check_output(args, env=env)
except subprocess.CalledProcessError as e:
raise CompileError(
"cargo failed with code: %d\n%s"
% (e.returncode, e.output.decode("utf-8"))
)
except OSError:
raise DistutilsExecError(
"Unable to execute 'cargo' - this package "
"requires Rust to be installed and "
"cargo to be on the PATH"
)
else:
print(f"test completed for '{ext.name}' extension")

@ -1,13 +1,8 @@
# coding: utf-8
import glob
import configparser
import os
import string
try:
import configparser
except ImportError:
import ConfigParser as configparser
import setuptools
from distutils import log
@ -21,7 +16,7 @@ __all__ = ["tomlgen"]
class tomlgen_rust(setuptools.Command):
description = "Generate `Cargo.toml` for rust extensions"
description = "Generate `Cargo.toml` for Rust extensions"
user_options = [
(str("force"), str("f"), str("overwrite existing files if any")),

@ -1,12 +1,12 @@
from __future__ import print_function, absolute_import
import sys
import subprocess
from enum import IntEnum
from distutils.errors import DistutilsPlatformError
import semantic_version
class Binding:
class Binding(IntEnum):
"""
Binding Options
"""
@ -20,7 +20,7 @@ class Binding:
Exec = 3
class Strip:
class Strip(IntEnum):
"""
Strip Options
"""
@ -32,19 +32,19 @@ class Strip:
All = 2
def cpython_feature(ext=True, binding=Binding.PyO3):
def rust_features(ext=True, binding=Binding.PyO3):
version = sys.version_info
if binding in (Binding.NoBinding, Binding.Exec):
return ()
elif binding is Binding.PyO3:
if version > (3, 5):
if version >= (3, 6):
if ext:
return {"pyo3/extension-module"}
else:
return {}
else:
raise DistutilsPlatformError("Unsupported python version: %s" % sys.version)
raise DistutilsPlatformError(f"unsupported python version: {sys.version}")
elif binding is Binding.RustCPython:
if (3, 3) < version:
if ext:
@ -52,21 +52,19 @@ def cpython_feature(ext=True, binding=Binding.PyO3):
else:
return {"cpython/python3-sys"}
else:
raise DistutilsPlatformError("Unsupported python version: %s" % sys.version)
raise DistutilsPlatformError(f"unsupported python version: {sys.version}")
else:
raise DistutilsPlatformError('Unknown Binding: "{}" '.format(binding))
raise DistutilsPlatformError(f"unknown Rust binding: '{binding}'")
def get_rust_version():
try:
output = subprocess.check_output(["rustc", "-V"])
if isinstance(output, bytes):
output = output.decode("latin-1")
output = subprocess.check_output(["rustc", "-V"]).decode("latin-1")
return semantic_version.Version(output.split(" ")[1], partial=True)
except (subprocess.CalledProcessError, OSError):
raise DistutilsPlatformError("Can not find Rust compiler")
raise DistutilsPlatformError("can't find Rust compiler")
except Exception as exc:
raise DistutilsPlatformError("Can not get rustc version: %s" % str(exc))
raise DistutilsPlatformError(f"can't get rustc version: {str(exc)}")
def get_rust_target_info():

Loading…
Cancel
Save