-
Notifications
You must be signed in to change notification settings - Fork 6
/
rust_setuptools.py
141 lines (112 loc) · 4.38 KB
/
rust_setuptools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# -*- coding: utf-8 -*-
""" Based on code by Armin Ronacher and James Salter
https://github.com/mitsuhiko/rust-setuptools
https://github.com/novocaine/rust-python-ext
"""
from __future__ import print_function
import os
import sys
import shutil
import subprocess
from distutils.cmd import Command
from distutils.command.install_lib import install_lib
from distutils.dist import Distribution
if sys.platform == 'win32':
DYNAMIC_LIB_SUFFIX = '.dll'
elif sys.platform == 'darwin':
DYNAMIC_LIB_SUFFIX = '.dylib'
else:
DYNAMIC_LIB_SUFFIX = '.so'
class RustDistribution(Distribution):
def __init__(self, attrs=None):
Distribution.__init__(self, attrs)
self.ext_modules = []
def has_ext_modules(self):
return True
class RustBuildCommand(Command):
description = 'build rust crates into Python extensions'
user_options = []
def initialize_options(self):
for k, v in self.__class__.rust_build_args.items():
setattr(self, k, v)
def finalize_options(self):
pass
def run(self):
# Force binary wheel
self.distribution.has_ext_modules = lambda: True
self.distribution.ext_modules = []
# Make sure that if pythonXX-sys is used, it builds against the
# current executing python interpreter.
bindir = os.path.dirname(sys.executable)
if sys.platform == 'win32':
path_sep = ';'
else:
path_sep = ':'
env = dict(os.environ)
env.update({
# disables rust's pkg-config seeking for specified packages,
# which causes pythonXX-sys to fall back to detecting the
# interpreter from the path.
'PYTHON_2.7_NO_PKG_CONFIG': '1',
'PATH': bindir + path_sep + env.get('PATH', '')
})
for crate_path, dest in self.cargo_crates:
# Execute cargo.
try:
toml = os.path.join(crate_path, 'Cargo.toml')
args = ['cargo', 'build', '--manifest-path', toml]
if not self.debug:
args.append('--release')
args.extend(list(self.extra_cargo_args or []))
if not self.quiet:
print(' '.join(args), file=sys.stderr)
output = subprocess.check_output(args, env=env)
except subprocess.CalledProcessError as e:
msg = 'cargo failed with code: %d\n%s' % (e.returncode, e.output)
raise Exception(msg)
except OSError:
raise Exception(
'Unable to execute cargo - this package requires rust to '
'be installed and cargo to be on the PATH')
if not self.quiet:
print(output, file=sys.stderr)
# Find the shared library that cargo hopefully produced and copy
# it into the build directory as if it were produced by
# build_cext.
if self.debug:
suffix = 'debug'
else:
suffix = 'release'
dylib_path = os.path.join(crate_path, 'target/', suffix)
# Ask build_ext where the shared library would go if it had built it,
# then copy it there.
build_ext = self.get_finalized_command('build_ext')
target = os.path.dirname(build_ext.get_ext_fullpath('x'))
try:
os.makedirs(target)
except OSError:
pass
target = os.path.join(target, dest)
for filename in os.listdir(dylib_path):
if filename.endswith(DYNAMIC_LIB_SUFFIX):
shutil.copy(os.path.join(dylib_path, filename),
os.path.join(target, filename))
def build_rust_cmdclass(crates, debug=False,
extra_cargo_args=None, quiet=False):
class _RustBuildCommand(RustBuildCommand):
rust_build_args = {
'cargo_crates': crates,
'debug': debug,
'extra_cargo_args': extra_cargo_args,
'quiet': quiet,
}
return _RustBuildCommand
def build_install_lib_cmdclass(base=None):
if base is None:
base = install_lib
class _RustInstallLibCommand(base):
def build(self):
base.build(self)
if not self.skip_build:
self.run_command('build_rust')
return _RustInstallLibCommand