Skip to content

Commit

Permalink
Implement vendoring
Browse files Browse the repository at this point in the history
  • Loading branch information
ZeroIntensity committed Jun 24, 2024
1 parent e942937 commit fd63c00
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 57 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
__pycache__/
.venv/

# Builds
*.egg-info
test/
dist/
pyawaitable-vendor/

# LSP
compile_flags.txt
Expand Down
3 changes: 2 additions & 1 deletion include/pyawaitable/backport.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ PyObject *_PyObject_VectorcallBackport(
PyObject *obj,
PyObject **args,
size_t nargsf,
PyObject *kwargs);
PyObject *kwargs
);

#define PyObject_CallNoArgs(o) PyObject_CallObject(o, NULL)
#define PyObject_Vectorcall _PyObject_VectorcallBackport
Expand Down
94 changes: 94 additions & 0 deletions include/vendor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#ifndef PYAWAITABLE_VENDOR_H
#define PYAWAITABLE_VENDOR_H

#include <Python.h>
#include <pyawaitable/awaitableobject.h>
#include <pyawaitable/genwrapper.h>

/*
* vendor.h is only for use by the vendor build tool, don't use it manually!
* (If you're seeing this message from a vendored copy, you're fine)
*/

#define PYAWAITABLE_ADD_TYPE(m, tp) \
do \
{ \
Py_INCREF(&tp); \
if (PyType_Ready(&tp) < 0) { \
Py_DECREF(&tp); \
return -1; \
} \
if (PyModule_AddObject(m, #tp, (PyObject *)&tp) < 0) { \
Py_DECREF(&tp); \
return -1; \
} \
} while (0)

#define PYAWAITABLE_MAJOR_VERSION 1
#define PYAWAITABLE_MINOR_VERSION 0
#define PYAWAITABLE_MICRO_VERSION 0
#define PYAWAITABLE_RELEASE_LEVEL 0xF

#ifdef PYAWAITABLE_PYAPI
#define PyAwaitable_New pyawaitable_new
#define PyAwaitable_AddAwait pyawaitable_await
#define PyAwaitable_Cancel pyawaitable_cancel
#define PyAwaitable_SetResult pyawaitable_set_result
#define PyAwaitable_SaveValues pyawaitable_save
#define PyAwaitable_SaveArbValues pyawaitable_save_arb
#define PyAwaitable_UnpackValues pyawaitable_unpack
#define PyAwaitable_UnpackArbValues pyawaitable_unpack_arb
#define PyAwaitable_Init pyawaitable_init
#define PyAwaitable_ABI pyawaitable_abi
#define PyAwaitable_Type PyAwaitableType
#define PyAwaitable_AwaitFunction pyawaitable_await_function
#endif

static int
pyawaitable_init()
{
PyErr_SetString(
PyExc_SystemError,
"cannot use pyawaitable_init from a vendored copy, use pyawaitable_vendor_init instead!"
);
return -1;
}

static void
close_pool(PyObject *Py_UNUSED(capsule))
{
dealloc_awaitable_pool();
}

static int
pyawaitable_vendor_init(PyObject *mod)
{
PYAWAITABLE_ADD_TYPE(mod, _PyAwaitableType);
PYAWAITABLE_ADD_TYPE(mod, _PyAwaitableGenWrapperType);

PyObject *capsule = PyCapsule_New(
NULL,
"_pyawaitable.__do_not_touch",
close_pool
);

if (!capsule)
{
return -1;
}

if (PyModule_AddObject(mod, "__do_not_touch", capsule) < 0)
{
Py_DECREF(capsule);
return -1;
}

if (alloc_awaitable_pool() < 0)
{
return -1;
}

return 0;
}

#endif
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
setup(
name="pyawaitable",
license="MIT",
version = "1.0.0-rc2",
version = "1.0.0",
ext_modules=[
Extension(
"_pyawaitable",
Expand Down
100 changes: 52 additions & 48 deletions src/_pyawaitable/values.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,63 @@
#include <pyawaitable/backport.h>
#include <pyawaitable/values.h>
#include <pyawaitable/awaitableobject.h>
#define UNPACK(arr, tp, err) \
assert(awaitable != NULL); \
PyAwaitableObject *aw = (PyAwaitableObject *) awaitable; \
Py_INCREF(awaitable); \
if (arr[0] == NULL) { \
PyErr_SetString( \
PyExc_ValueError, \
"pyawaitable: awaitable object has no stored " err \
); \
Py_DECREF(awaitable); \
return -1; \
} \
va_list args; \
va_start(args, awaitable); \
for (int i = 0; i < VALUE_ARRAY_SIZE; ++i) { \
if (!arr[i]) \
break; \
tp ptr = va_arg(args, tp); \
if (ptr == NULL) \
continue; \
*ptr = arr[i]; \
} \
va_end(args); \
Py_DECREF(awaitable); \
return 0
#define UNPACK(arr, tp, err) \
do { \
assert(awaitable != NULL); \
PyAwaitableObject *aw = (PyAwaitableObject *) awaitable; \
Py_INCREF(awaitable); \
if (arr[0] == NULL) { \
PyErr_SetString( \
PyExc_ValueError, \
"pyawaitable: awaitable object has no stored " err \
); \
Py_DECREF(awaitable); \
return -1; \
} \
va_list args; \
va_start(args, awaitable); \
for (int i = 0; i < VALUE_ARRAY_SIZE; ++i) { \
if (!arr[i]) \
break; \
tp ptr = va_arg(args, tp); \
if (ptr == NULL) \
continue; \
*ptr = arr[i]; \
} \
va_end(args); \
Py_DECREF(awaitable); \
return 0; \
} while (0)

#define SAVE_ERR(err) \
"pyawaitable: " err " array has a capacity of 32" \
", so storing %ld more would overflow it" \

#define SAVE(arr, index, tp, err, wrap) \
assert(awaitable != NULL); \
assert(nargs != 0); \
Py_INCREF(awaitable); \
PyAwaitableObject *aw = (PyAwaitableObject *) awaitable; \
Py_ssize_t final_size = index + nargs; \
if (final_size >= VALUE_ARRAY_SIZE) { \
PyErr_Format( \
PyExc_SystemError, \
SAVE_ERR(err), \
final_size \
); \
return -1; \
} \
va_list vargs; \
va_start(vargs, nargs); \
for (Py_ssize_t i = index; i < final_size; ++i) { \
arr[i] = wrap(va_arg(vargs, tp)); \
} \
index += nargs; \
va_end(vargs); \
Py_DECREF(awaitable); \
return 0
#define SAVE(arr, index, tp, err, wrap) \
do { \
assert(awaitable != NULL); \
assert(nargs != 0); \
Py_INCREF(awaitable); \
PyAwaitableObject *aw = (PyAwaitableObject *) awaitable; \
Py_ssize_t final_size = index + nargs; \
if (final_size >= VALUE_ARRAY_SIZE) { \
PyErr_Format( \
PyExc_SystemError, \
SAVE_ERR(err), \
final_size \
); \
return -1; \
} \
va_list vargs; \
va_start(vargs, nargs); \
for (Py_ssize_t i = index; i < final_size; ++i) { \
arr[i] = wrap(va_arg(vargs, tp)); \
} \
index += nargs; \
va_end(vargs); \
Py_DECREF(awaitable); \
return 0; \
} while (0)

#define NOTHING

Expand Down
1 change: 1 addition & 0 deletions src/pyawaitable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Type

__all__ = "PyAwaitable", "include", "abi"
__version__ = "1.0.0"

PyAwaitable: Type = _PyAwaitableType

Expand Down
17 changes: 10 additions & 7 deletions src/pyawaitable/pyawaitable.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#define PYAWAITABLE_MINOR_VERSION 0
#define PYAWAITABLE_MICRO_VERSION 0
/* Per CPython Conventions: 0xA for alpha, 0xB for beta, 0xC for release candidate or 0xF for final. */
#define PYAWAITABLE_RELEASE_LEVEL 0xC
#define PYAWAITABLE_RELEASE_LEVEL 0xF

typedef int (*awaitcallback)(PyObject *, PyObject *);
typedef int (*awaitcallback_err)(PyObject *, PyObject *);
Expand All @@ -21,8 +21,7 @@ typedef struct _pyawaitable_abi
PyObject *,
PyObject *,
awaitcallback,
awaitcallback_err
);
awaitcallback_err);
void (*cancel)(PyObject *);
int (*set_result)(PyObject *, PyObject *);
int (*save)(PyObject *, Py_ssize_t, ...);
Expand All @@ -36,8 +35,7 @@ typedef struct _pyawaitable_abi
const char *fmt,
awaitcallback,
awaitcallback_err,
...
);
...);
} PyAwaitableABI;

#ifdef PYAWAITABLE_THIS_FILE_INIT
Expand Down Expand Up @@ -87,8 +85,7 @@ pyawaitable_init()
{
PyErr_SetString(
PyExc_RuntimeError,
"pyawaitable_init() can only be called in a file with a PYAWAITABLE_THIS_FILE_INIT #define"
);
"pyawaitable_init() can only be called in a file with a PYAWAITABLE_THIS_FILE_INIT #define");
return -1;
}

Expand All @@ -109,4 +106,10 @@ pyawaitable_init()
#define PyAwaitable_AwaitFunction pyawaitable_await_function
#endif

static int pyawaitable_vendor_init(PyObject *mod)
{
PyErr_SetString(PyExc_SystemError, "cannot use pyawaitable_vendor_init from an installed version, use pyawaitable_init instead!");
return -1;
}

#endif
69 changes: 69 additions & 0 deletions vendor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import os
from pathlib import Path
from typing import TextIO
from pyawaitable import __version__
import shutil

def _write(fp: TextIO, value: str) -> None:
fp.write(value)
print(f"Wrote {len(value.encode('utf-8'))} bytes to {fp.name}")


def _header_file(text: str) -> str:
lines = text.replace("_impl", "").split("\n")

# Remove header guards and trailing newlines
for i in (0, 0, -1, -1):
lines.pop(i)

return "\n".join([i for i in lines if not i.startswith("#include")])


def _source_file(fp: TextIO, file: Path) -> None:
text: str = file.read_text(encoding="utf-8").replace("_impl", "")
lines = text.split("\n")
lines.insert(0, f"/* Vendor of {file} */")
_write(fp, "\n".join([i for i in lines if not i.startswith("#include")]))


def main():
dist = Path("./pyawaitable-vendor")
if dist.exists():
print("./pyawaitable-vendor already exists, removing it...")
shutil.rmtree(dist)
print("Creating vendored copy of pyawaitable in ./pyawaitable-vendor...")
os.mkdir(dist)

with open(dist / "pyawaitable.h", "w", encoding="utf-8") as f:
_write(f, """#ifndef PYAWAITABLE_VENDOR_H
#define PYAWAITABLE_VENDOR_H
#include <Python.h>
#include <stdbool.h>
#include <stdlib.h>
""")
for path in [i for i in Path("./include/pyawaitable").iterdir()] + [Path("./include/vendor.h")]:
_write(f, _header_file(path.read_text(encoding="utf-8")))

_write(f, "\n#endif")


with open(dist / "pyawaitable.c", "w", encoding="utf-8") as f:
f.write(f"""/*
* PyAwaitable - Vendored copy of version {__version__}
*
* Docs: https://awaitable.zintensity.dev
* Source: https://github.com/ZeroIntensity/pyawaitable
*/
#include "pyawaitable.h"
""")
for source_file in Path("./src/_pyawaitable/").iterdir():
if source_file.name == "mod.c":
continue

_source_file(f, source_file)


if __name__ == "__main__":
main()

0 comments on commit fd63c00

Please sign in to comment.