Skip to content

Commit 3c8f310

Browse files
committed
Support internal imports
1 parent fd7c6c3 commit 3c8f310

File tree

15 files changed

+161
-51
lines changed

15 files changed

+161
-51
lines changed

Diff for: README.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@ It is incompatible with pypy3 (for now) due to a lack of c-api.
1818
## Usage
1919

2020
Add `setuptools-golang` to the `setup_requires` in your setup.py and
21-
`build_golang=True`.
21+
`build_golang={'root': ...}`. `root` refers to the root go import path of
22+
your project.
23+
24+
An extension must only contain a single file in the `main` go package.
25+
You may have multiple extensions in your `setup.py`.
2226

2327
```python
2428
setup(
2529
...
26-
build_golang=True,
30+
build_golang={'root': 'github.com/user/project'},
31+
ext_modules=[Extension('example', ['example.go'])],
2732
setup_requires=['setuptools-golang'],
2833
...
2934
)

Diff for: setuptools_golang.py

+34-30
Original file line numberDiff line numberDiff line change
@@ -76,40 +76,35 @@ def _tmpdir():
7676
shutil.rmtree(tempdir)
7777

7878

79-
class build_ext(_build_ext):
79+
def _get_build_extension_method(base, root):
8080
def build_extension(self, ext):
81+
def _raise_error(msg):
82+
raise IOError(
83+
'Error building extension `{}`: '.format(ext.name) + msg,
84+
)
85+
8186
# If there are no .go files then the parent should handle this
8287
if not any(source.endswith('.go') for source in ext.sources):
83-
return _build_ext.build_extension(self, ext)
84-
85-
for source in ext.sources:
86-
if not os.path.exists(source):
87-
raise IOError(
88-
'Error building extension `{}`: {} does not exist'.format(
89-
ext.name, source,
90-
),
91-
)
92-
93-
# Passing non-.go files to `go build` results in a failure
94-
# Passing only .go files to `go build` causes it to ignore C files
95-
# So we'll set our cwd to the root of the files and go from there!
96-
source_dirs = {os.path.dirname(src) for src in ext.sources}
97-
if len(source_dirs) != 1:
98-
raise IOError(
99-
'Error building extension `{}`: '
100-
'Cannot compile across directories: {}'.format(
101-
ext.name, ' '.join(sorted(source_dirs)),
102-
)
88+
return base.build_extension(self, ext)
89+
90+
if len(ext.sources) != 1:
91+
_raise_error(
92+
'sources must be a single file in the `main` package.\n'
93+
'Recieved: {!r}'.format(ext.sources)
10394
)
104-
source_dir, = source_dirs
105-
source_dir = os.path.abspath(source_dir)
95+
96+
main_file, = ext.sources
97+
if not os.path.exists(main_file):
98+
_raise_error('{} does not exist'.format(main_file))
99+
main_dir = os.path.dirname(main_file)
106100

107101
# Copy the package into a temporary GOPATH environment
108102
with _tmpdir() as tempdir:
109-
srcdir = os.path.join(tempdir, 'src')
110-
os.mkdir(srcdir)
111-
pkg_path = os.path.join(srcdir, '_mypkg')
112-
shutil.copytree(source_dir, pkg_path)
103+
root_path = os.path.join(tempdir, 'src', root)
104+
# Make everything but the last directory (copytree interface)
105+
os.makedirs(os.path.dirname(root_path))
106+
shutil.copytree('.', root_path)
107+
pkg_path = os.path.join(root_path, main_dir)
113108

114109
env = {
115110
'GOPATH': tempdir,
@@ -131,8 +126,17 @@ def build_extension(self, ext):
131126
cmd_build, cwd=pkg_path, env=dict(os.environ, **env),
132127
)
133128

129+
return build_extension
130+
131+
132+
def _get_build_ext_cls(base, root):
133+
class build_ext(base):
134+
build_extension = _get_build_extension_method(base, root)
135+
136+
return build_ext
137+
134138

135139
def set_build_ext(dist, attr, value):
136-
if not value:
137-
return
138-
dist.cmdclass['build_ext'] = build_ext
140+
root = value['root']
141+
base = dist.cmdclass.get('build_ext', _build_ext)
142+
dist.cmdclass['build_ext'] = _get_build_ext_cls(base, root)

Diff for: setuptools_golang_test.py

+16-10
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def run(*cmd, **kwargs):
3636
if returncode is not None:
3737
if proc.returncode != returncode:
3838
raise AssertionError(
39-
'{!r} returned {} (expected {})\nout:\n{}err:\n{}'.format(
39+
'{!r} returned {} (expected {})\nout:\n{}\nerr:\n{}\n'.format(
4040
cmd, proc.returncode, returncode, out, err,
4141
)
4242
)
@@ -49,14 +49,11 @@ def run_output(*cmd, **kwargs):
4949

5050
def test_sets_cmdclass():
5151
dist = Distribution()
52-
setuptools_golang.set_build_ext(dist, 'build_golang', True)
53-
assert dist.cmdclass['build_ext'] == setuptools_golang.build_ext
54-
55-
56-
def test_sets_cmdclass_value_falsey():
57-
dist = Distribution()
58-
setuptools_golang.set_build_ext(dist, 'build_golang', False)
59-
assert dist.cmdclass.get('build_ext') != setuptools_golang.build_ext
52+
assert not dist.cmdclass.get('build_ext')
53+
setuptools_golang.set_build_ext(
54+
dist, 'build_golang', {'root': 'github.com/asottile/fake'},
55+
)
56+
assert dist.cmdclass['build_ext']
6057

6158

6259
GET_LDFLAGS = (
@@ -157,5 +154,14 @@ def test_integration_multidir(venv):
157154
assert ret.returncode != 0
158155
assert (
159156
'Error building extension `multidir`: '
160-
'Cannot compile across directories: dir1 dir2' in ret.out
157+
'sources must be a single file in the `main` package.' in ret.out
161158
)
159+
160+
161+
OHAI = 'import hello_lib; print(hello_lib.ohai(u"Anthony"))'
162+
163+
164+
def test_integration_internal_imports(venv):
165+
run(venv.pip, 'install', 'testing/internal_imports')
166+
out = run_output(venv.python, '-c', OHAI)
167+
assert out == 'ohai, Anthony\n'

Diff for: testing/imports_gh/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
setup(
66
name='imports-gh',
77
ext_modules=[Extension('red', ['red.go'])],
8-
build_golang=True,
8+
build_golang={'root': 'github.com/asottile/fake'},
99
# Would do this, but we're testing *our* implementation and this would
1010
# install from pypi. We can rely on setuptools-golang being already
1111
# installed under test.

Diff for: testing/internal_imports/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Assume this directory is actually rooted at github.com/asottile/fake

Diff for: testing/internal_imports/hello/ohai.go

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package hello
2+
3+
func OHai(s string) string {
4+
return "ohai, " + s
5+
}

Diff for: testing/internal_imports/hello_lib/hello_lib.c

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include <Python.h>
2+
3+
#if PY_MAJOR_VERSION >= 3
4+
#define PyHelloLib_Bytes_AS_STRING PyBytes_AS_STRING
5+
#else
6+
#define PyHelloLib_Bytes_AS_STRING PyString_AS_STRING
7+
#endif
8+
9+
/* Will come from go */
10+
PyObject* ohai(PyObject*);
11+
12+
/* To shim go's missing variadic function support */
13+
int PyArg_ParseTuple_U(PyObject* args, PyObject** obj) {
14+
return PyArg_ParseTuple(args, "U", obj);
15+
}
16+
17+
/* To shim go's lack of C macro support */
18+
void PyHelloLib_DECREF(PyObject* obj) {
19+
Py_DECREF(obj);
20+
}
21+
22+
const char* PyHelloLib_Bytes_AsString(PyObject* s) {
23+
return PyHelloLib_Bytes_AS_STRING(s);
24+
}
25+
26+
static struct PyMethodDef methods[] = {
27+
{"ohai", (PyCFunction)ohai, METH_VARARGS},
28+
{NULL, NULL}
29+
};
30+
31+
#if PY_MAJOR_VERSION >= 3
32+
static struct PyModuleDef module = {
33+
PyModuleDef_HEAD_INIT,
34+
"hello_lib",
35+
NULL,
36+
-1,
37+
methods
38+
};
39+
40+
PyMODINIT_FUNC PyInit_hello_lib(void) {
41+
return PyModule_Create(&module);
42+
}
43+
#else
44+
PyMODINIT_FUNC inithello_lib(void) {
45+
Py_InitModule3("hello_lib", methods, NULL);
46+
}
47+
#endif

Diff for: testing/internal_imports/hello_lib/main.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
// #include <stdlib.h>
4+
// #include <Python.h>
5+
// int PyArg_ParseTuple_U(PyObject*, PyObject**);
6+
// const char* PyHelloLib_Bytes_AsString(PyObject*);
7+
// void PyHelloLib_DECREF(PyObject*);
8+
import "C"
9+
import "unsafe"
10+
import "github.com/asottile/fake/hello"
11+
12+
//export ohai
13+
func ohai(self *C.PyObject, args *C.PyObject) *C.PyObject {
14+
var obj *C.PyObject
15+
if C.PyArg_ParseTuple_U(args, &obj) == 0 {
16+
return nil
17+
}
18+
bytes := C.PyUnicode_AsUTF8String(obj)
19+
cstr := C.PyHelloLib_Bytes_AsString(bytes)
20+
ohai := hello.OHai(C.GoString(cstr))
21+
cstr = C.CString(ohai)
22+
ret := C.PyUnicode_FromString(cstr)
23+
24+
C.free(unsafe.Pointer(cstr))
25+
C.PyHelloLib_DECREF(bytes)
26+
27+
return ret
28+
}
29+
30+
func main() {}

Diff for: testing/internal_imports/setup.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from setuptools import Extension
2+
from setuptools import setup
3+
4+
5+
setup(
6+
name='internal_imports',
7+
ext_modules=[Extension('hello_lib', ['hello_lib/main.go'])],
8+
build_golang={'root': 'github.com/asottile/fake'},
9+
# Would do this, but we're testing *our* implementation and this would
10+
# install from pypi. We can rely on setuptools-golang being already
11+
# installed under test.
12+
# setup_requires=['setuptools-golang'],
13+
)

Diff for: testing/multidir/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
ext_modules=[Extension(
88
'multidir', ['dir1/sum.go', 'dir2/sum_support.go'],
99
)],
10-
build_golang=True,
10+
build_golang={'root': 'github.com/asottile/fake'},
1111
# Would do this, but we're testing *our* implementation and this would
1212
# install from pypi. We can rely on setuptools-golang being already
1313
# installed under test.

Diff for: testing/notfound/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
setup(
66
name='notfound',
77
ext_modules=[Extension('notfound', ['notfound.go'])],
8-
build_golang=True,
8+
build_golang={'root': 'github.com/asottile/fake'},
99
# Would do this, but we're testing *our* implementation and this would
1010
# install from pypi. We can rely on setuptools-golang being already
1111
# installed under test.

Diff for: testing/project_with_c/setup.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55

66
setup(
77
name='project_with_c',
8-
version='0.1.0',
98
packages=find_packages(),
109
ext_modules=[
1110
Extension('project_with_c', ['project_with_c.c']),
1211
Extension('project_with_c_sum.sum', ['project_with_c_sum/sum.go']),
1312
],
14-
build_golang=True,
13+
build_golang={'root': 'github.com/asottile/fake'},
1514
# Would do this, but we're testing *our* implementation and this would
1615
# install from pypi. We can rely on setuptools-golang being already
1716
# installed under test.

Diff for: testing/sum/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
setup(
66
name='sum',
77
ext_modules=[Extension('sum', ['sum.go'])],
8-
build_golang=True,
8+
build_golang={'root': 'github.com/asottile/fake'},
99
# Would do this, but we're testing *our* implementation and this would
1010
# install from pypi. We can rely on setuptools-golang being already
1111
# installed under test.

Diff for: testing/sum_pure_go/setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
setup(
66
name='sum_pure_go',
7-
ext_modules=[Extension('sum_pure_go', ['sum.go', 'sum_support.go'])],
8-
build_golang=True,
7+
ext_modules=[Extension('sum_pure_go', ['sum.go'])],
8+
build_golang={'root': 'github.com/asottile/fake'},
99
# Would do this, but we're testing *our* implementation and this would
1010
# install from pypi. We can rely on setuptools-golang being already
1111
# installed under test.

Diff for: testing/sum_sub_package/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
name='sum_sub_package',
88
ext_modules=[Extension('sum_sub_package.sum', ['sum_sub_package/sum.go'])],
99
packages=find_packages(),
10-
build_golang=True,
10+
build_golang={'root': 'github.com/asottile/fake'},
1111
# Would do this, but we're testing *our* implementation and this would
1212
# install from pypi. We can rely on setuptools-golang being already
1313
# installed under test.

0 commit comments

Comments
 (0)