milksnake_0.1.5.orig/.gitignore 0000644 0000000 0000000 00000000073 13255044222 014631 0 ustar 0000000 0000000 *.egg-info
venv
dist
build
__pycache__
.eggs
.pytest_cache
milksnake_0.1.5.orig/.travis.yml 0000644 0000000 0000000 00000000514 13255044720 014755 0 ustar 0000000 0000000 os: linux
sudo: false
language: python
matrix:
include:
- python: 3.6
- python: 2.7
- python: pypy
fast_finish: true
install:
- pip install pytest
- curl https://sh.rustup.rs -sSf | sh -s -- -y
- export PATH="${HOME}/.cargo/bin:${PATH}"
- which cargo
- which rustc
script:
- make test
cache:
- pip
milksnake_0.1.5.orig/LICENSE 0000644 0000000 0000000 00000025132 13164717204 013657 0 ustar 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 by Armin Ronacher.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
milksnake_0.1.5.orig/Makefile 0000644 0000000 0000000 00000000062 13255044346 014306 0 ustar 0000000 0000000 all: test
test:
@pytest tests
.PHONY: all test
milksnake_0.1.5.orig/README.md 0000644 0000000 0000000 00000006756 13255045375 014150 0 ustar 0000000 0000000 # Milksnake
Milksnake is an extension for setuptools that allows you to distribute
dynamic linked libraries in Python wheels in the most portable way imaginable.
It gives you a hook to invoke your own build process and to then take the
resulting dynamic linked library.
## Why?
There are already other projects that make Python and native libraries play
along but this one is different. Unlike other projects that build Python
extension modules the goal of this project is to build regular native libraries
that are then loaded with CFFI at runtime. Why not just use CFFI? Because
CFFI's setuptools support alone does not properly work with such wheels (it
does not provide a way to build and properly tag wheels for shared libraries) and
it does not provide a good way to invoke an external build process (like a
makefile, cargo to build rust binaries etc.)
In particular you will most likely only need two wheels for Linux, one for macs
and soon one for Windows independently of how many Python interpreters you want
to target.
## What is supported?
* Platforms: Linux, Mac, Windows
* setuptools commands: `bdist_wheel`, `build`, `build_ext`, `develop`
* `pip install --editable .`
* Universal wheels (`PACKAGE-py2.py3-none-PLATFORM.whl`); this can be disabled
with `milksnake_universal=False` in `setup()` in case the package also contains
stuff that does link against libpython.
## How?
This example shows how to build a rust project with it:
This is what a `setup.py` file looks like:
```python
from setuptools import setup
def build_native(spec):
# build an example rust library
build = spec.add_external_build(
cmd=['cargo', 'build', '--release'],
path='./rust'
)
spec.add_cffi_module(
module_path='example._native',
dylib=lambda: build.find_dylib('example', in_path='target/release'),
header_filename=lambda: build.find_header('example.h', in_path='target'),
rtld_flags=['NOW', 'NODELETE']
)
setup(
name='example',
version='0.0.1',
packages=['example'],
zip_safe=False,
platforms='any',
setup_requires=['milksnake'],
install_requires=['milksnake'],
milksnake_tasks=[
build_native
]
)
```
You then need a `rust/` folder that has a Rust library (with a crate type
of `cdylib`) and a `example/` python package.
Example `example/__init__.py` file:
```python
from example._native import ffi, lib
def test():
return lib.a_function_from_rust()
```
And a `rust/src/lib.rs`:
```rust
#[no_mangle]
pub unsafe extern "C" fn a_function_from_rust() -> i32 {
42
}
```
And the `rust/Cargo.toml`:
```toml
[package]
name = "example"
version = "0.1.0"
build = "build.rs"
[lib]
name = "example"
crate-type = ["cdylib"]
[build-dependencies]
cbindgen = "0.4"
```
And finally the build.rs file:
```rust
extern crate cbindgen;
use std::env;
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let mut config: cbindgen::Config = Default::default();
config.language = cbindgen::Language::C;
cbindgen::generate_with_config(&crate_dir, config)
.unwrap()
.write_to_file("target/example.h");
}
```
milksnake_0.1.5.orig/example/ 0000755 0000000 0000000 00000000000 13164716275 014311 5 ustar 0000000 0000000 milksnake_0.1.5.orig/milksnake/ 0000755 0000000 0000000 00000000000 13164716275 014634 5 ustar 0000000 0000000 milksnake_0.1.5.orig/setup.cfg 0000644 0000000 0000000 00000000032 13164722715 014466 0 ustar 0000000 0000000 [bdist_wheel]
universal=1
milksnake_0.1.5.orig/setup.py 0000644 0000000 0000000 00000001765 13255046472 014375 0 ustar 0000000 0000000 from setuptools import setup
from setuptools.dist import Distribution
with open('README.md', 'rb') as f:
readme = f.read().decode('utf-8')
setup(
name='milksnake',
version='0.1.5',
author='Armin Ronacher',
author_email='armin.ronacher@active-4.com',
license='Apache License 2.0',
packages=['milksnake'],
package_data={
'milksnake': ['empty.c'],
},
description='A python library that extends setuptools for binary extensions.',
long_description=readme,
zip_safe=False,
platforms='any',
install_requires=[
'cffi>=1.6.0',
],
setup_requires=[
'cffi>=1.6.0',
],
entry_points={
'distutils.setup_keywords': [
'milksnake_tasks = milksnake.setuptools_ext:milksnake_tasks',
'milksnake_universal = milksnake.setuptools_ext:milksnake_universal',
],
},
classifiers=[
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
],
)
milksnake_0.1.5.orig/tests/ 0000755 0000000 0000000 00000000000 13255044222 014003 5 ustar 0000000 0000000 milksnake_0.1.5.orig/example/.gitignore 0000644 0000000 0000000 00000000021 13164716275 016272 0 ustar 0000000 0000000 example/_native*
milksnake_0.1.5.orig/example/example/ 0000755 0000000 0000000 00000000000 13164716275 015744 5 ustar 0000000 0000000 milksnake_0.1.5.orig/example/rust/ 0000755 0000000 0000000 00000000000 13164716275 015306 5 ustar 0000000 0000000 milksnake_0.1.5.orig/example/setup.py 0000644 0000000 0000000 00000001646 13165000455 016015 0 ustar 0000000 0000000 from setuptools import setup, find_packages
def build_native(spec):
# Step 1: build the rust library
build = spec.add_external_build(
cmd=['cargo', 'build', '--release'],
path='./rust'
)
# Step 2: add a cffi module based on the dylib we built
#
# We use lambdas here for dylib and header_filename so that those are
# only called after the external build finished.
spec.add_cffi_module(
module_path='example._native',
dylib=lambda: build.find_dylib('example', in_path='target/release'),
header_filename=lambda: build.find_header('example.h', in_path='target'),
rtld_flags=['NOW', 'NODELETE']
)
setup(
name='example',
version='0.0.1',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
platforms='any',
install_requires=[
'milksnake',
],
milksnake_tasks=[
build_native,
]
)
milksnake_0.1.5.orig/example/example/__init__.py 0000644 0000000 0000000 00000000157 13164716275 020060 0 ustar 0000000 0000000 from . import _native
def test():
point = _native.lib.example_get_origin()
return (point.x, point.y)
milksnake_0.1.5.orig/example/rust/.gitignore 0000644 0000000 0000000 00000000022 13164716275 017270 0 ustar 0000000 0000000 target
Cargo.lock
milksnake_0.1.5.orig/example/rust/Cargo.toml 0000644 0000000 0000000 00000000322 13255007152 017217 0 ustar 0000000 0000000 [package]
name = "example"
version = "0.1.0"
authors = ["Armin Ronacher "]
build = "build.rs"
[lib]
name = "example"
crate-type = ["cdylib"]
[build-dependencies]
cbindgen = "0.4"
milksnake_0.1.5.orig/example/rust/build.rs 0000644 0000000 0000000 00000000513 13164716275 016752 0 ustar 0000000 0000000 extern crate cbindgen;
use std::env;
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let mut config: cbindgen::Config = Default::default();
config.language = cbindgen::Language::C;
cbindgen::generate_with_config(&crate_dir, config)
.unwrap()
.write_to_file("target/example.h");
}
milksnake_0.1.5.orig/example/rust/src/ 0000755 0000000 0000000 00000000000 13164716275 016075 5 ustar 0000000 0000000 milksnake_0.1.5.orig/example/rust/src/lib.rs 0000644 0000000 0000000 00000000630 13164716275 017210 0 ustar 0000000 0000000 #[repr(C)]
pub struct Point {
pub x: f32,
pub y: f32,
}
#[repr(u32)]
pub enum Foo {
A = 1,
B,
C
}
#[no_mangle]
pub unsafe extern "C" fn example_get_origin() -> Point {
Point { x: 0.0, y: 0.0 }
}
#[no_mangle]
pub unsafe extern "C" fn example_print_foo(foo: *const Foo) {
println!("{}", match *foo {
Foo::A => "a",
Foo::B => "b",
Foo::C => "c",
});
}
milksnake_0.1.5.orig/milksnake/__init__.py 0000644 0000000 0000000 00000000000 13164716275 016733 0 ustar 0000000 0000000 milksnake_0.1.5.orig/milksnake/_compat.py 0000644 0000000 0000000 00000000146 13164716275 016631 0 ustar 0000000 0000000 import sys
PY2 = sys.version_info[0] == 2
if PY2:
text_type = unicode
else:
text_type = str
milksnake_0.1.5.orig/milksnake/ffi.py 0000644 0000000 0000000 00000000742 13164716275 015755 0 ustar 0000000 0000000 import os
import re
import cffi
from ._compat import PY2
_directive_re = re.compile(r'^\s*#.*?$(?m)')
def make_ffi(module_path, header, strip_directives=False):
"""Creates a FFI instance for the given configuration."""
if not PY2 and isinstance(header, bytes):
header = header.decode('utf-8')
if strip_directives:
header = _directive_re.sub('', header)
ffi = cffi.FFI()
ffi.cdef(header)
ffi.set_source(module_path, None)
return ffi
milksnake_0.1.5.orig/milksnake/setuptools_ext.py 0000644 0000000 0000000 00000025211 13227610107 020273 0 ustar 0000000 0000000 import os
import sys
import uuid
import shutil
import tempfile
import subprocess
from distutils import log
from distutils.core import Extension
from distutils.ccompiler import new_compiler
from distutils.command.build_py import build_py
from distutils.command.build_ext import build_ext
from cffi import FFI
from cffi import recompiler as cffi_recompiler
from cffi import setuptools_ext as cffi_ste
try:
from wheel.bdist_wheel import bdist_wheel
except ImportError:
bdist_wheel = None
here = os.path.abspath(os.path.dirname(__file__))
EMPTY_C = u'''
void init%(mod)s() {}
void PyInit_%(mod)s() {}
'''
BUILD_PY = u'''
import cffi
from milksnake.ffi import make_ffi
ffi = make_ffi(**%(kwargs)r)
'''
MODULE_PY = u'''# auto-generated file
__all__ = ['lib', 'ffi']
import os
from %(cffi_module_path)s import ffi
lib = ffi.dlopen(os.path.join(os.path.dirname(__file__), %(lib_filename)r), %(rtld_flags)r)
del os
'''
class Spec(object):
def __init__(self, dist):
self.dist = dist
self.builds = []
self._dist_build_funcs = []
def add_build_step(self, build_step):
self.builds.append(build_step)
return build_step
def prepare_build(self):
for build_step in self.builds:
build_step.prepare_build()
def add_external_build(self, *args, **kwargs):
return self.add_build_step(ExternalBuildStep(self, *args, **kwargs))
def add_cffi_module(self, *args, **kwargs):
return self.add_build_step(CffiModuleBuildStep(self, *args, **kwargs))
def add_build_func(self, func, module_base=None):
"""Registers a function to be called with the build."""
patched = bool(self._dist_build_funcs)
self._dist_build_funcs.append((func, module_base))
if not patched:
self._patch_build_commands()
def _patch_build_commands(self):
base_build_ext = self.dist.cmdclass.get('build_ext', build_ext)
base_build_py = self.dist.cmdclass.get('build_py', build_py)
spec = self
class MilksnakeBuildPy(base_build_py):
def run(self):
base_build_py.run(self)
for func, module_base in spec._dist_build_funcs:
base_path = None
if module_base is not None:
base_path = os.path.join(
self.build_lib, *module_base.split('.'))
func(base_path=base_path, inplace=False)
class MilksnakeBuildExt(base_build_ext):
def get_ext_fullpath(self, ext_name):
milksnake_dummy_ext = None
for ext in spec.dist.ext_modules:
if ext.name == ext_name:
milksnake_dummy_ext = getattr(
ext, 'milksnake_dummy_ext', None)
break
if milksnake_dummy_ext is None:
return base_build_ext.get_ext_fullpath(self, ext_name)
fullname = self.get_ext_fullname(ext_name)
modpath = fullname.split('.')
package = '.'.join(modpath[0:-1])
build_py = self.get_finalized_command('build_py')
package_dir = os.path.abspath(build_py.get_package_dir(package))
return os.path.join(package_dir, milksnake_dummy_ext)
def run(self):
base_build_ext.run(self)
if self.inplace:
build_py = self.get_finalized_command('build_py')
for func, module_base in spec._dist_build_funcs:
base_path = None
if module_base is not None:
base_path = build_py.get_package_dir(
module_base)
func(base_path=base_path, inplace=True)
self.dist.cmdclass['build_py'] = MilksnakeBuildPy
self.dist.cmdclass['build_ext'] = MilksnakeBuildExt
class BuildStep(object):
def __init__(self, spec, path=None):
self.spec = spec
self.path = path
def prepare_build(self):
raise NotImplementedError('no build step implemented')
class ExternalBuildStep(BuildStep):
def __init__(self, spec, cmd=None, cwd=None, path=None, env=None):
BuildStep.__init__(self, spec, path=path)
self.cmd = cmd
self.cwd = cwd
self.path = path
self.env = env
def find_dylib(self, name, in_path=None):
path = self.path or '.'
if in_path is not None:
path = os.path.join(path, *in_path.split('/'))
to_find = None
if sys.platform == 'darwin':
to_find = 'lib%s.dylib' % name
elif sys.platform == 'win32':
to_find = '%s.dll' % name
else:
to_find = 'lib%s.so' % name
for filename in os.listdir(path):
if filename == to_find:
return os.path.join(path, filename)
raise LookupError('dylib %r not found' % name)
def find_header(self, name, in_path=None):
path = self.path or '.'
if in_path is not None:
path = os.path.join(path, *in_path.split('/'))
for filename in os.listdir(path):
if filename == name:
return os.path.join(path, filename)
raise LookupError('header %r not found' % name)
def prepare_build(self):
def build(**extra):
cwd = self.cwd or self.path
env = dict(os.environ)
if self.env is not None:
env.update(self.env)
rv = subprocess.Popen(self.cmd, cwd=cwd, env=env).wait()
if rv != 0:
sys.exit(rv)
self.spec.add_build_func(build)
def get_rtld_flags(flags):
if sys.platform == "win32":
return 0
ffi = FFI()
if not flags:
return ffi.RTLD_NOW
rv = 0
for flag in flags:
if flag.startswith('RTLD_'):
flag = flag[5:]
rv |= getattr(ffi, 'RTLD_' + flag.upper())
return rv
class CffiModuleBuildStep(BuildStep):
def __init__(self, spec, module_path, dylib=None, header_filename=None,
header_source=None, header_strip_directives=True,
path=None, rtld_flags=None):
BuildStep.__init__(self, spec, path=path)
self.module_path = module_path
self.dylib = dylib
self.header_filename = header_filename
self.header_source = header_source
self.header_strip_directives = header_strip_directives
self.rtld_flags = get_rtld_flags(rtld_flags)
parts = self.module_path.rsplit('.', 2)
self.module_base = parts[0]
self.name = parts[-1]
genbase = '%s._%s' % (parts[0], parts[1].lstrip('_'))
self.cffi_module_path = '%s__ffi' % genbase
self.fake_module_path = '%s__lib' % genbase
from distutils.sysconfig import get_config_var
self.lib_filename = '%s__lib%s' % (
genbase.split('.')[-1],
get_config_var('SHLIB_SUFFIX') or get_config_var('SO')
)
def get_header_source(self):
if self.header_source is not None:
return self.header_source
if callable(self.header_filename):
fn = self.header_filename()
else:
fn = self.header_filename
with open(fn, 'rb') as f:
return f.read()
def prepare_build(self):
dist = self.spec.dist
# Because distutils was never intended to support other languages and
# this was never cleaned up, we need to generate a fake C module which
# we later override with our rust module. This means we just compile
# an empty .c file into a Python module. This will trick wheel and
# other systems into assuming our library has binary extensions.
if dist.ext_modules is None:
dist.ext_modules = []
build = dist.get_command_obj('build')
build.ensure_finalized()
empty_c_path = os.path.join(build.build_temp, 'empty.c')
if not os.path.isdir(build.build_temp):
os.makedirs(build.build_temp)
with open(empty_c_path, 'w') as f:
f.write(EMPTY_C % {'mod': self.fake_module_path.split('.')[-1]})
ext = Extension(self.fake_module_path, sources=[empty_c_path])
ext.milksnake_dummy_ext = self.lib_filename
dist.ext_modules.append(ext)
def make_ffi():
from milksnake.ffi import make_ffi
return make_ffi(self.module_path,
self.get_header_source(),
strip_directives=self.header_strip_directives)
def build_cffi(base_path, **extra):
# dylib
dylib = self.dylib()
if callable(dylib):
dylib = dylib()
log.info('copying dylib %s', os.path.basename(dylib))
shutil.copy2(dylib, os.path.join(base_path, self.lib_filename))
# generate cffi module
ffi = make_ffi()
log.info('generating cffi module for %r' % self.module_path)
py_file = os.path.join(
base_path, *self.cffi_module_path.split('.')[1:]) + '.py'
updated = cffi_recompiler.make_py_source(
ffi, self.cffi_module_path, py_file)
if not updated:
log.info('already up-to-date')
# wrapper
log.info('generating wrapper for %r' % self.module_path)
with open(os.path.join(base_path, self.name + '.py'), 'wb') as f:
f.write((MODULE_PY % {
'cffi_module_path': self.cffi_module_path,
'lib_filename': self.lib_filename,
'rtld_flags': self.rtld_flags,
}).encode('utf-8'))
self.spec.add_build_func(build_cffi, module_base=self.module_base)
def milksnake_universal(dist, attr, value):
"""Enables or disables universal wheel support."""
patch_universal_wheel(dist)
dist.milksnake_universal = value
def patch_universal_wheel(dist):
value = getattr(dist, 'milksnake_universal', None)
if value is None:
dist.milksnake_universal = True
base_bdist_wheel = dist.cmdclass.get('bdist_wheel', bdist_wheel)
if base_bdist_wheel is None:
return
class MilksnakeBdistWheel(base_bdist_wheel):
def get_tag(self):
rv = base_bdist_wheel.get_tag(self)
if not dist.milksnake_universal:
return rv
return ('py2.py3', 'none',) + rv[2:]
dist.cmdclass['bdist_wheel'] = MilksnakeBdistWheel
def milksnake_tasks(dist, attr, value):
"""Registers task callbacks."""
patch_universal_wheel(dist)
spec = Spec(dist)
for callback in value or ():
callback(spec)
spec.prepare_build()
milksnake_0.1.5.orig/tests/conftest.py 0000644 0000000 0000000 00000003121 13255045146 016205 0 ustar 0000000 0000000 import os
import uuid
import json
import atexit
import pytest
import shutil
import tempfile
import subprocess
root = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
EVAL_HOOK = '''
import sys
import json
ns = {}
code = compile(sys.stdin.read(), '', 'exec')
eval(code, ns)
print(json.dumps(ns['execute']()))
'''
class VirtualEnv(object):
def __init__(self, path):
self.path = path
def spawn(self, executable, args=None, **kwargs):
return subprocess.Popen([os.path.join(self.path, 'bin', executable)] +
list(args or ()), **kwargs)
def run(self, executable, args=None):
rv = self.spawn(executable, args).wait()
if rv != 0:
raise RuntimeError('Program exited with %d' % rv)
def eval(self, code):
proc = self.spawn('python', ['-c', EVAL_HOOK],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
stdout = proc.communicate(code.encode('utf-8'))[0]
rv = proc.wait()
if rv != 0:
raise RuntimeError('Interpreter exited with %d' % rv)
return json.loads(stdout)
@pytest.fixture
def virtualenv():
path = os.path.join(tempfile.gettempdir(), '.' + str(uuid.uuid4()))
def _remove():
try:
shutil.rmtree(path)
except Exception:
pass
atexit.register(_remove)
subprocess.Popen(['virtualenv', path]).wait()
try:
venv = VirtualEnv(path)
venv.run('pip', ['install', '--editable', root])
yield venv
finally:
_remove()
milksnake_0.1.5.orig/tests/res/ 0000755 0000000 0000000 00000000000 13255044222 014574 5 ustar 0000000 0000000 milksnake_0.1.5.orig/tests/test_basic.py 0000644 0000000 0000000 00000000564 13255044222 016502 0 ustar 0000000 0000000 import os
def test_example_dev_run(virtualenv):
pkg = os.path.abspath(os.path.join(os.path.dirname(__file__),
'res', 'minimal'))
virtualenv.run('pip', ['install', '-v', '--editable', pkg])
virtualenv.eval('''if 1:
from example import test
def execute():
assert test() == (0.0, 0.0)
''')
milksnake_0.1.5.orig/tests/res/minimal/ 0000755 0000000 0000000 00000000000 13255044222 016222 5 ustar 0000000 0000000 milksnake_0.1.5.orig/tests/res/minimal/.gitignore 0000644 0000000 0000000 00000000021 13255044222 020203 0 ustar 0000000 0000000 example/_native*
milksnake_0.1.5.orig/tests/res/minimal/example/ 0000755 0000000 0000000 00000000000 13255044222 017655 5 ustar 0000000 0000000 milksnake_0.1.5.orig/tests/res/minimal/rust/ 0000755 0000000 0000000 00000000000 13255044222 017217 5 ustar 0000000 0000000 milksnake_0.1.5.orig/tests/res/minimal/setup.py 0000644 0000000 0000000 00000001641 13255044222 017736 0 ustar 0000000 0000000 from setuptools import setup, find_packages
def build_native(spec):
# Step 1: build the rust library
build = spec.add_external_build(
cmd=['cargo', 'build', '--release'],
path='./rust'
)
# Step 2: add a cffi module based on the dylib we built
#
# We use lambdas here for dylib and header_filename so that those are
# only called after the external build finished.
spec.add_cffi_module(
module_path='example._native',
dylib=lambda: build.find_dylib('example', in_path='target/release'),
header_filename=lambda: build.find_header('example.h', in_path='.'),
rtld_flags=['NOW', 'NODELETE']
)
setup(
name='example',
version='0.0.1',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
platforms='any',
install_requires=[
'milksnake',
],
milksnake_tasks=[
build_native,
]
)
milksnake_0.1.5.orig/tests/res/minimal/example/__init__.py 0000644 0000000 0000000 00000000157 13255044222 021771 0 ustar 0000000 0000000 from . import _native
def test():
point = _native.lib.example_get_origin()
return (point.x, point.y)
milksnake_0.1.5.orig/tests/res/minimal/rust/.gitignore 0000644 0000000 0000000 00000000022 13255044222 021201 0 ustar 0000000 0000000 target
Cargo.lock
milksnake_0.1.5.orig/tests/res/minimal/rust/Cargo.toml 0000644 0000000 0000000 00000000230 13255044222 021142 0 ustar 0000000 0000000 [package]
name = "example"
version = "0.1.0"
authors = ["Armin Ronacher "]
[lib]
name = "example"
crate-type = ["cdylib"]
milksnake_0.1.5.orig/tests/res/minimal/rust/example.h 0000644 0000000 0000000 00000000267 13255044222 021030 0 ustar 0000000 0000000 typedef struct {
float x;
float y;
} Point;
typedef enum {
FOO_A = 1,
FOO_B = 2,
FOO_C = 3
} Foo;
Point example_get_origin(void);
void example_print_foo(Foo *);
milksnake_0.1.5.orig/tests/res/minimal/rust/src/ 0000755 0000000 0000000 00000000000 13255044222 020006 5 ustar 0000000 0000000 milksnake_0.1.5.orig/tests/res/minimal/rust/src/lib.rs 0000644 0000000 0000000 00000000630 13255044222 021121 0 ustar 0000000 0000000 #[repr(C)]
pub struct Point {
pub x: f32,
pub y: f32,
}
#[repr(u32)]
pub enum Foo {
A = 1,
B,
C
}
#[no_mangle]
pub unsafe extern "C" fn example_get_origin() -> Point {
Point { x: 0.0, y: 0.0 }
}
#[no_mangle]
pub unsafe extern "C" fn example_print_foo(foo: *const Foo) {
println!("{}", match *foo {
Foo::A => "a",
Foo::B => "b",
Foo::C => "c",
});
}