blob: cf149ef6d0c6617b82949f4f612ac58e4e23fe8a [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
import re
import os
import shutil
# Generate the nanoarrow_c.pxd file used by the Cython extension
class NanoarrowPxdGenerator:
def __init__(self):
self._define_regexes()
def generate_nanoarrow_pxd(self, file_in, file_out):
file_in_name = os.path.basename(file_in)
# Read the nanoarrow.h header
content = None
with open(file_in, 'r') as input:
content = input.read()
# Strip comments
content = self.re_comment.sub('', content)
# Find types and function definitions
types = self._find_types(content)
func_defs = self._find_func_defs(content)
# Make corresponding cython definitions
types_cython = [self._type_to_cython(t, ' ') for t in types]
func_defs_cython = [self._func_def_to_cython(d, ' ') for d in func_defs]
# Unindent the header
header = self.re_newline_plus_indent.sub('\n', self._pxd_header())
# Write nanoarrow_c.pxd
with open(file_out, 'wb') as output:
output.write(header.encode('UTF-8'))
output.write(f'\ncdef extern from "{file_in_name}" nogil:\n'.encode("UTF-8"))
# A few things we add in manually
output.write(b'\n')
output.write(b' ctypedef int ArrowErrorCode\n')
output.write(b' cdef int NANOARROW_OK\n')
output.write(b'\n')
for type in types_cython:
output.write(type.encode('UTF-8'))
output.write(b'\n\n')
for func_def in func_defs_cython:
output.write(func_def.encode('UTF-8'))
output.write(b'\n')
def _define_regexes(self):
self.re_comment = re.compile(r'\s*//[^\n]*')
self.re_type = re.compile(r'(?P<type>struct|union|enum) (?P<name>Arrow[^ ]+) {(?P<body>[^}]*)}')
self.re_func_def = re.compile(r'\n(static inline )?(?P<const>const )?(struct|enum )?(?P<return_type>[A-Za-z0-9_*]+) (?P<name>Arrow[A-Za-z]+)\((?P<args>[^\)]*)\);')
self.re_tagged_type = re.compile(r'(?P<type>struct|union|enum) (?P<name>Arrow[A-Za-z]+)')
self.re_struct_delim = re.compile(r';\s*')
self.re_enum_delim = re.compile(r',\s*')
self.re_whitespace = re.compile(r'\s+')
self.re_newline_plus_indent = re.compile(r'\n +')
def _strip_comments(self, content):
return self.re_comment.sub('', content)
def _find_types(self, content):
return [m.groupdict() for m in self.re_type.finditer(content)]
def _find_func_defs(self, content):
return [m.groupdict() for m in self.re_func_def.finditer(content)]
def _type_to_cython(self, t, indent=''):
type = t['type']
name = t['name']
body = self.re_tagged_type.sub(r'\2', t['body'].strip())
if type == 'enum':
items = [item for item in self.re_enum_delim.split(body) if item]
else:
items = [item for item in self.re_struct_delim.split(body) if item]
cython_body = f'\n{indent} '.join([''] + items)
return f'{indent}{type} {name}:{cython_body}'
def _func_def_to_cython(self, d, indent=''):
return_type = d['return_type'].strip()
if d['const']:
return_type = 'const ' + return_type
name = d['name']
args = re.sub(r'\s+', ' ', d['args'].strip())
args = self.re_tagged_type.sub(r'\2', args)
# Cython doesn't do (void)
if args == 'void':
args = ''
return f'{indent}{return_type} {name}({args})'
def _pxd_header(self):
return """
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
# cython: language_level = 3
from libc.stdint cimport int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t
"""
# Runs cmake -DNANOARROW_BUNDLE=ON if cmake exists or copies nanoarrow.c/h
# from ../dist if it does not. Running cmake is safer because it will sync
# any changes from nanoarrow C library sources in the checkout but is not
# strictly necessary for things like installing from GitHub.
def copy_or_generate_nanoarrow_c():
this_wd = os.getcwd()
this_dir = os.path.abspath(os.path.dirname(__file__))
source_dir = os.path.dirname(this_dir)
maybe_nanoarrow_h = os.path.join(this_dir, 'src/nanoarrow/nanoarrow.h')
maybe_nanoarrow_c = os.path.join(this_dir, 'src/nanoarrow/nanoarrow.c')
for f in (maybe_nanoarrow_c, maybe_nanoarrow_h):
if os.path.exists(f):
os.unlink(f)
is_cmake_dir = 'CMakeLists.txt' in os.listdir(source_dir)
is_in_nanoarrow_repo = 'nanoarrow.h' in os.listdir(os.path.join(source_dir, 'src', 'nanoarrow'))
has_cmake = os.system('cmake --version') == 0
build_dir = os.path.join(this_dir, '_cmake')
if has_cmake and is_cmake_dir and is_in_nanoarrow_repo:
try:
os.mkdir(build_dir)
os.chdir(build_dir)
os.system(f'cmake ../.. -DNANOARROW_BUNDLE=ON -DNANOARROW_NAMESPACE=PythonPkg')
os.system(f'cmake --install . --prefix=../src/nanoarrow')
finally:
if os.path.exists(build_dir):
# Can fail on Windows with permission issues
try:
shutil.rmtree(build_dir)
except Exception as e:
print(f'Failed to remove _cmake temp directory: {str(e)}')
os.chdir(this_wd)
elif is_in_nanoarrow_repo:
shutil.copyfile()
else:
raise ValueError('Attempt to build source distribution outside the nanoarrow repo')
if not os.path.exists(os.path.join(this_dir, 'src/nanoarrow/nanoarrow.h')):
raise ValueError('Attempt to vendor nanoarrow.c/h failed')
maybe_nanoarrow_hpp = os.path.join(this_dir, 'src/nanoarrow/nanoarrow.hpp')
if os.path.exists(maybe_nanoarrow_hpp):
os.unlink(maybe_nanoarrow_hpp)
# Runs the pxd generator with some information about the file name
def generate_nanoarrow_pxd():
this_dir = os.path.abspath(os.path.dirname(__file__))
maybe_nanoarrow_h = os.path.join(this_dir, 'src/nanoarrow/nanoarrow.h')
maybe_nanoarrow_pxd = os.path.join(this_dir, 'src/nanoarrow/nanoarrow_c.pxd')
NanoarrowPxdGenerator().generate_nanoarrow_pxd(
maybe_nanoarrow_h,
maybe_nanoarrow_pxd
)
if __name__ == '__main__':
copy_or_generate_nanoarrow_c()
generate_nanoarrow_pxd()