├── .gitignore ├── examples ├── hello │ ├── __init__.py │ └── _build_ffi.py ├── hellorust │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── rust_setuptools.py └── setup.py └── rust_setuptools.py /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | *.egg-info 4 | *.dist-info 5 | .DS_Store 6 | venv 7 | .eggs 8 | *.pyc 9 | *.pyo 10 | -------------------------------------------------------------------------------- /examples/hello/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ._ffi import ffi 3 | 4 | 5 | capi = ffi.dlopen(os.path.join(os.path.dirname(__file__), 6 | 'libhellorust.dylib')) 7 | -------------------------------------------------------------------------------- /examples/hello/_build_ffi.py: -------------------------------------------------------------------------------- 1 | from cffi import FFI 2 | 3 | ffi = FFI() 4 | ffi.set_source('hello._ffi', None) 5 | ffi.cdef(''' 6 | int debug(void); 7 | ''') 8 | 9 | if __name__ == '__main__': 10 | ffi.compile() 11 | -------------------------------------------------------------------------------- /examples/hellorust/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /examples/hellorust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hellorust" 3 | version = "0.1.0" 4 | authors = ["Armin Ronacher "] 5 | 6 | [lib] 7 | name = "hellorust" 8 | crate-type = ["dylib"] 9 | -------------------------------------------------------------------------------- /examples/hellorust/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "dylib"] 2 | 3 | 4 | #[no_mangle] 5 | pub fn debug() -> i32 { 6 | 42 7 | } 8 | -------------------------------------------------------------------------------- /examples/rust_setuptools.py: -------------------------------------------------------------------------------- 1 | ../rust_setuptools.py -------------------------------------------------------------------------------- /examples/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup(name='hello-rust', 5 | version='1.0', 6 | setup_requires=['cffi>=1.0.0'], 7 | install_requires=['cffi>=1.0.0'], 8 | cffi_modules=['hello/_build_ffi.py:ffi'], 9 | entry_points={'distutils.setup_keywords': [ 10 | 'rust_crates = rust_setuptools:rust_crates' 11 | ]}, 12 | rust_crates=[('hellorust', 'hello')], 13 | packages=['hello'], 14 | zip_safe=False 15 | ) 16 | -------------------------------------------------------------------------------- /rust_setuptools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import sys 4 | import shutil 5 | import subprocess 6 | 7 | from distutils.cmd import Command 8 | from distutils.command.install_lib import install_lib 9 | 10 | 11 | if sys.platform == 'win32': 12 | DYNAMIC_LIB_SUFFIX = '.dll' 13 | elif sys.platform == 'darwin': 14 | DYNAMIC_LIB_SUFFIX = '.dylib' 15 | else: 16 | DYNAMIC_LIB_SUFFIX = '.so' 17 | 18 | 19 | class RustBuildCommand(Command): 20 | description = 'build rust crates into Python extensions' 21 | 22 | user_options = [] 23 | 24 | def initialize_options(self): 25 | for k, v in self.__class__.rust_build_args.iteritems(): 26 | setattr(self, k, v) 27 | 28 | def finalize_options(self): 29 | pass 30 | 31 | def run(self): 32 | if self.debug: 33 | debug_or_release = '--debug' 34 | else: 35 | debug_or_release = '--release' 36 | 37 | # Make sure that if pythonXX-sys is used, it builds against the 38 | # current executing python interpreter. 39 | bindir = os.path.dirname(sys.executable) 40 | if sys.platform == 'win32': 41 | path_sep = ';' 42 | else: 43 | path_sep = ':' 44 | 45 | env = dict(os.environ) 46 | env.update({ 47 | # disables rust's pkg-config seeking for specified packages, 48 | # which causes pythonXX-sys to fall back to detecting the 49 | # interpreter from the path. 50 | 'PYTHON_2.7_NO_PKG_CONFIG': '1', 51 | 'PATH': bindir + path_sep + env.get('PATH', '') 52 | }) 53 | 54 | for crate_path, dest in self.cargo_crates: 55 | # Execute cargo. 56 | try: 57 | toml = os.path.join(crate_path, 'Cargo.toml') 58 | args = (['cargo', 'build', '--manifest-path', toml, 59 | debug_or_release] + list(self.extra_cargo_args or [])) 60 | if not self.quiet: 61 | print >> sys.stderr, ' '.join(args) 62 | output = subprocess.check_output(args, env=env) 63 | except subprocess.CalledProcessError as e: 64 | msg = 'cargo failed with code: %d\n%s' % (e.returncode, e.output) 65 | raise Exception(msg) 66 | except OSError: 67 | raise Exception('Unable to execute cargo - this package ' 68 | 'requires rust to be installed and cargo to be on the PATH') 69 | 70 | if not self.quiet: 71 | print >> sys.stderr, output 72 | 73 | # Find the shared library that cargo hopefully produced and copy 74 | # it into the build directory as if it were produced by 75 | # build_cext. 76 | if self.debug: 77 | suffix = 'debug' 78 | else: 79 | suffix = 'release' 80 | 81 | dylib_path = os.path.join(crate_path, 'target/', suffix) 82 | 83 | # Ask build_ext where the shared library would go if it had built it, 84 | # then copy it there. 85 | build_ext = self.get_finalized_command('build_ext') 86 | 87 | target = os.path.dirname(build_ext.get_ext_fullpath('x')) 88 | try: 89 | os.makedirs(target) 90 | except OSError: 91 | pass 92 | 93 | target = os.path.join(target, dest) 94 | 95 | for filename in os.listdir(dylib_path): 96 | if filename.endswith(DYNAMIC_LIB_SUFFIX): 97 | shutil.copy(os.path.join(dylib_path, filename), 98 | os.path.join(target, filename)) 99 | 100 | 101 | def build_rust_cmdclass(crates, debug=False, 102 | extra_cargo_args=None, quiet=False): 103 | class _RustBuildCommand(RustBuildCommand): 104 | rust_build_args = { 105 | 'cargo_crates': crates, 106 | 'debug': debug, 107 | 'extra_cargo_args': extra_cargo_args, 108 | 'quiet': quiet, 109 | } 110 | 111 | return _RustBuildCommand 112 | 113 | 114 | def build_install_lib_cmdclass(base=None): 115 | if base is None: 116 | base = install_lib 117 | class _RustInstallLibCommand(base): 118 | def build(self): 119 | base.build(self) 120 | if not self.skip_build: 121 | self.run_command('build_rust') 122 | return _RustInstallLibCommand 123 | 124 | 125 | def rust_crates(dist, attr, value): 126 | dist.is_pure = lambda: False 127 | dist.cmdclass['build_rust'] = build_rust_cmdclass(value) 128 | dist.cmdclass['install_lib'] = build_install_lib_cmdclass( 129 | dist.cmdclass.get('install_lib')) 130 | --------------------------------------------------------------------------------