Skip to content

Commit 689a7b6

Browse files
committed
initial import
0 parents  commit 689a7b6

File tree

4 files changed

+263
-0
lines changed

4 files changed

+263
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.pyc
2+
*.pyo
3+
/dist
4+
/build
5+
/*.egg-info

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
Setuptools helpers for rust Python extensions.
2+
3+
Compile and distribute Python extensions written in rust as easily as if they were written in C.
4+
5+
## Example
6+
7+
### setup.py
8+
9+
```python
10+
from setuptools import setup
11+
from setuptools_rust import RustExtension
12+
13+
setup(name='hello-rust',
14+
version='1.0',
15+
rust_extensions=[RustExtension('hello_rust._hello_rust', 'extensions/Cargo.toml')],
16+
packages=['hello_rust'],
17+
# rust extensions are not zip safe, just like C-extensions.
18+
zip_safe=False
19+
)
20+
```
21+
22+
You can use same commands as for c-extensions. For example::
23+
24+
```
25+
>>> python ./setup.py develop
26+
running develop
27+
running egg_info
28+
writing hello-rust.egg-info/PKG-INFO
29+
writing top-level names to hello-rust.egg-info/top_level.txt
30+
writing dependency_links to hello-rust.egg-info/dependency_links.txt
31+
reading manifest file 'hello-rust.egg-info/SOURCES.txt'
32+
writing manifest file 'hello-rust.egg-info/SOURCES.txt'
33+
running build_ext
34+
running build_rust
35+
cargo build --manifest-path extensions/Cargo.toml --features python27-sys
36+
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
37+
38+
Creating /.../lib/python2.7/site-packages/hello-rust.egg-link (link to .)
39+
40+
Installed hello-rust
41+
Processing dependencies for hello-rust==1.0
42+
Finished processing dependencies for hello-rust==1.0
43+
```
44+
45+
Or you can use commands like `bdist_wheel` or `bdist_egg`

setup.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from setuptools import setup, find_packages
2+
3+
version = '0.1'
4+
5+
6+
setup(
7+
name="setuptools-rust",
8+
version=version,
9+
author='Nikolay Kim',
10+
author_email='[email protected]',
11+
url="https://github.com/fafhrd91/setuptools-rust",
12+
keywords='distutils setuptools rust',
13+
description="Setuptools rust extension plugin",
14+
long_description=open('README.md').read(),
15+
license='MIT',
16+
packages=['setuptools_rust'],
17+
zip_safe=True,
18+
classifiers=[
19+
"Topic :: Software Development :: Version Control",
20+
"License :: OSI Approved :: MIT",
21+
"Intended Audience :: Developers",
22+
"Programming Language :: Python :: 2",
23+
"Programming Language :: Python :: 2.7",
24+
"Programming Language :: Python :: 3",
25+
"Programming Language :: Python :: 3.5",
26+
"Programming Language :: Python :: 3.6",
27+
'Development Status :: 5 - Production/Stable',
28+
'Operating System :: POSIX',
29+
'Operating System :: MacOS :: MacOS X',
30+
'Operating System :: Microsoft :: Windows',
31+
],
32+
entry_points="""
33+
[distutils.commands]
34+
build_rust=setuptools_rust:build_rust
35+
"""
36+
)

setuptools_rust/__init__.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
from __future__ import print_function
2+
import glob
3+
import os
4+
import shutil
5+
import sys
6+
import subprocess
7+
from distutils.cmd import Command
8+
from distutils.dist import Distribution
9+
from distutils.command.build import build as Build
10+
from distutils.command.build_ext import build_ext
11+
from distutils.command.install_lib import install_lib
12+
from setuptools import setup, Extension
13+
from setuptools.command import develop
14+
15+
__all__ = ('RustExtension', 'build_rust')
16+
17+
18+
# allow to use 'rust_extensions' parameter for setup() call
19+
Distribution.rust_extensions = ()
20+
21+
22+
# add support for build_rust sub0command
23+
def has_rust_extensions(self):
24+
exts = [ext for ext in self.distribution.rust_extensions
25+
if isinstance(ext, RustExtension)]
26+
return bool(exts)
27+
28+
29+
Build.has_rust_extensions = has_rust_extensions
30+
Build.sub_commands.append(('build_rust', Build.has_rust_extensions))
31+
32+
# monkey patch "develop" command
33+
orig_run_command = develop.develop.run_command
34+
35+
def monkey_run_command(self, cmd):
36+
orig_run_command(self, cmd)
37+
38+
if cmd == 'build_ext':
39+
self.reinitialize_command('build_rust', inplace=1)
40+
orig_run_command(self, 'build_rust')
41+
42+
develop.develop.run_command = monkey_run_command
43+
44+
45+
class RustExtension:
46+
"""Just a collection of attributes that describes an rust extension
47+
module and everything needed to build it
48+
49+
Instance attributes:
50+
name : string
51+
the full name of the extension, including any packages -- ie.
52+
*not* a filename or pathname, but Python dotted name
53+
path : string
54+
path to the cargo.toml manifest
55+
args : [stirng]
56+
a list of extra argumenents to be passed to cargo.
57+
quiet : bool
58+
If True, doesn't echo cargo's output.
59+
debug : bool
60+
Controls whether --debug or --release is passed to cargo.
61+
"""
62+
63+
def __init__(self, name, path, args=None, quiet=False, debug=False):
64+
self.name=name
65+
self.path=path
66+
self.args=args
67+
self.quiet=quiet
68+
self.debug=debug
69+
70+
71+
class build_rust(Command):
72+
"""
73+
Command for building rust crates via cargo.
74+
75+
Don't use this directly; use the build_rust_cmdclass
76+
factory method.
77+
"""
78+
description = "build rust crates into Python extensions"
79+
80+
user_options = [
81+
('inplace', 'i',
82+
"ignore build-lib and put compiled extensions into the source " +
83+
"directory alongside your pure Python modules"),
84+
]
85+
86+
def initialize_options(self):
87+
self.extensions = ()
88+
self.inplace = False
89+
90+
def finalize_options(self):
91+
self.extensions = [ext for ext in self.distribution.rust_extensions
92+
if isinstance(ext, RustExtension)]
93+
94+
def features(self):
95+
version = sys.version_info
96+
if (2,7) < version < (2,8):
97+
return "python27-sys"
98+
elif (3,3) < version:
99+
return "python3-sys"
100+
else:
101+
raise ValueError("Unsupported python version: %s" % sys.version)
102+
103+
def build_extension(self, ext):
104+
# Make sure that if pythonXX-sys is used, it builds against the current
105+
# executing python interpreter.
106+
bindir = os.path.dirname(sys.executable)
107+
108+
env = os.environ.copy()
109+
env.update({
110+
# disables rust's pkg-config seeking for specified packages,
111+
# which causes pythonXX-sys to fall back to detecting the
112+
# interpreter from the path.
113+
"PYTHON_2.7_NO_PKG_CONFIG": "1",
114+
"PATH": bindir + os.pathsep + os.environ.get("PATH", "")
115+
})
116+
117+
# Execute cargo.
118+
try:
119+
args = (["cargo", "build", "--manifest-path", ext.path,
120+
"--features", self.features()] + list(ext.args or []))
121+
if not ext.debug:
122+
args.append("--release")
123+
if not ext.quiet:
124+
print(" ".join(args), file=sys.stderr)
125+
output = subprocess.check_output(args, env=env)
126+
except subprocess.CalledProcessError as e:
127+
msg = "cargo failed with code: %d\n%s" % (e.returncode, e.output)
128+
raise Exception(msg)
129+
except OSError:
130+
raise Exception("Unable to execute 'cargo' - this package "
131+
"requires rust to be installed and cargo to be on the PATH")
132+
133+
if not ext.quiet:
134+
print(output, file=sys.stderr)
135+
136+
# Find the shared library that cargo hopefully produced and copy
137+
# it into the build directory as if it were produced by build_cext.
138+
if ext.debug:
139+
suffix = "debug"
140+
else:
141+
suffix = "release"
142+
143+
target_dir = os.path.join(os.path.dirname(ext.path), "target/", suffix)
144+
145+
if sys.platform == "win32":
146+
wildcard_so = "*.dll"
147+
elif sys.platform == "darwin":
148+
wildcard_so = "*.dylib"
149+
else:
150+
wildcard_so = "*.so"
151+
152+
try:
153+
dylib_path = glob.glob(os.path.join(target_dir, wildcard_so))[0]
154+
except IndexError:
155+
raise Exception(
156+
"rust build failed; unable to find any .dylib in %s" %
157+
target_dir)
158+
159+
# Ask build_ext where the shared library would go if it had built it,
160+
# then copy it there.
161+
build_ext = self.get_finalized_command('build_ext')
162+
build_ext.inplace = self.inplace
163+
target_fname = ext.name
164+
if target_fname is None:
165+
target_fname = os.path.splitext(
166+
os.path.basename(dylib_path)[3:])[0]
167+
168+
ext_path = build_ext.get_ext_fullpath(os.path.basename(target_fname))
169+
try:
170+
os.makedirs(os.path.dirname(ext_path))
171+
except OSError:
172+
pass
173+
shutil.copyfile(dylib_path, ext_path)
174+
175+
def run(self):
176+
for ext in self.extensions:
177+
self.build_extension(ext)

0 commit comments

Comments
 (0)