Skip to content

Commit fc07b6d

Browse files
authored
Merge pull request #4 from GraphBLAS/michelp/build-script
Michelp/build script
2 parents 43b1c77 + 3db4070 commit fc07b6d

File tree

12 files changed

+6892
-1
lines changed

12 files changed

+6892
-1
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,9 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
131+
# emacs
132+
*~
133+
134+
# Vim
135+
*.sw?

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
# python-suitesparse-graphblas
2-
Python CFFI Binding around SuiteSparse:GraphBLAS
2+
Python CFFI Binding around
3+
[SuiteSparse:GraphBLAS](https://github.com/DrTimothyAldenDavis/GraphBLAS)
4+
5+
This is a base package that exposes only the low level CFFI API
6+
bindings and symbols. This package is shared by the syntax bindings
7+
[pygraphblas](https://github.com/Graphegon/pygraphblas) and
8+
[grblas](https://github.com/metagraph-dev/grblas).

setup.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from setuptools import setup, find_packages
2+
3+
setup(
4+
name='suitesparse-graphblas',
5+
version='4.0.3',
6+
description='SuiteSparse:GraphBLAS Python bindings.',
7+
packages=find_packages(),
8+
author='Michel Pelletier, James Kitchen, Erik Welch',
9+
cffi_modules=["suitesparse/graphblas/build.py:ffibuilder"],
10+
install_requires=["cffi>=1.0.0"],
11+
setup_requires=["cffi>=1.0.0", "pytest-runner"],
12+
tests_require=["pytest"],
13+
)
14+

suitesparse/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

suitesparse/graphblas/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from ._graphblas import ffi, lib

suitesparse/graphblas/build.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import os
2+
import sys
3+
from cffi import FFI
4+
5+
is_win = sys.platform.startswith("win")
6+
thisdir = os.path.dirname(__file__)
7+
8+
ffibuilder = FFI()
9+
10+
with open(os.path.join(thisdir, "source.c")) as f:
11+
source = f.read()
12+
13+
ffibuilder.set_source(
14+
"suitesparse.graphblas._graphblas",
15+
source,
16+
libraries=["graphblas"],
17+
include_dirs=[os.path.join(sys.prefix, "include")],
18+
)
19+
20+
header = "suitesparse_graphblas.h"
21+
if is_win:
22+
header = "suitesparse_graphblas_no_complex.h"
23+
gb_cdef = open(os.path.join(thisdir, header))
24+
25+
ffibuilder.cdef(gb_cdef.read())
26+
27+
if __name__ == "__main__":
28+
ffibuilder.compile(verbose=True)

suitesparse/graphblas/clean_header.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import sys
2+
import os
3+
import re
4+
5+
6+
def rename_defined_constants(text):
7+
# #define GB_PUBLIC extern
8+
text = text.replace("GB_PUBLIC", "extern")
9+
10+
return text
11+
12+
13+
def remove_directives(text):
14+
# There are a few cases of safe `#define` directives that we need to keep
15+
# - #define FOO 12
16+
# - #define BAR ...
17+
safe_define = re.compile(r"^#define\s+\w+\s+(\d+|\.{3})(\s|$)")
18+
19+
out = []
20+
multiline = False
21+
for line in text.splitlines():
22+
if not line:
23+
out.append(line)
24+
elif multiline:
25+
if line[-1] != "\\":
26+
multiline = False
27+
out.append(f"/* {line} */")
28+
elif line.lstrip()[0] == "#":
29+
if line[-1] == "\\":
30+
multiline = True
31+
if not multiline and safe_define.match(line):
32+
out.append(line)
33+
else:
34+
out.append(f"/* {line} */")
35+
else:
36+
out.append(line)
37+
return "\n".join(out)
38+
39+
40+
def remove_complex(text):
41+
out = []
42+
extern_block = None
43+
complex_extern = False
44+
for line in text.splitlines():
45+
if extern_block is not None:
46+
if "FC32" in line or "FC64" in line:
47+
complex_extern = True
48+
if line.replace(" ", "") == ");": # End of extern block
49+
extern_block.append(line)
50+
if complex_extern:
51+
for i in range(len(extern_block)):
52+
extern_block[i] = f"// {extern_block[i]}"
53+
out.extend(extern_block)
54+
extern_block = None
55+
complex_extern = False
56+
else:
57+
extern_block.append(line)
58+
elif not line:
59+
out.append(line)
60+
elif line.strip() == "extern":
61+
extern_block = [line]
62+
elif "FC32" in line or "FC64" in line:
63+
# Check if the line is a terminating line
64+
if re.search(r"FC(32|64)\s*;", line):
65+
# By commenting the terminating line out, we lose the closing semicolon
66+
# Walk up the lines, looking for the last non-commented out line
67+
# then replace the trailing comma with a semicolon
68+
for i in range(5): # it's never more than 5 away
69+
if out[-i].startswith("//"):
70+
continue
71+
last_comma_pos = out[-i].rfind(",")
72+
if last_comma_pos < 0:
73+
continue
74+
out[-i] = f"{out[-i][:last_comma_pos]};{out[-i][last_comma_pos+1:]}"
75+
break
76+
out.append(f"// {line}")
77+
else:
78+
out.append(line)
79+
return "\n".join(out)
80+
81+
82+
def main(filename):
83+
with open(filename, "r") as f:
84+
text = f.read()
85+
text = rename_defined_constants(text)
86+
text = remove_directives(text)
87+
if "_no_complex_" in filename:
88+
text = remove_complex(text)
89+
with open(filename, "w") as f:
90+
f.write(text)
91+
92+
93+
if __name__ == "__main__":
94+
filename = sys.argv[1]
95+
if not os.path.exists(filename):
96+
raise Exception(f'"{filename}" does not exist')
97+
main(filename)

0 commit comments

Comments
 (0)