Skip to content

Commit

Permalink
Add Conda package recipe
Browse files Browse the repository at this point in the history
  • Loading branch information
kartik-s committed Sep 13, 2024
1 parent b0ac141 commit a6968c2
Show file tree
Hide file tree
Showing 26 changed files with 625 additions and 56 deletions.
25 changes: 25 additions & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.12)

project(libsbcl_librarian C)

include(GNUInstallDirs)

set(CMAKE_INCLUDE_CURRENT_DIR ON)

add_library(sbcl_librarian SHARED libsbcl_librarian.c libsbcl_librarian.h libsbcl_librarian_err.h entry_point.c)
target_link_directories(sbcl_librarian PRIVATE $ENV{BUILD_PREFIX}/lib)
target_link_libraries(sbcl_librarian sbcl)

add_custom_command(
OUTPUT libsbcl_librarian.c libsbcl_librarian.h
COMMAND ${CMAKE_COMMAND} -E env CL_SOURCE_REGISTRY=${CMAKE_CURRENT_SOURCE_DIR}/..// sbcl --script ${CMAKE_CURRENT_SOURCE_DIR}/generate-bindings.lisp
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)

install(TARGETS sbcl_librarian
LIBRARY
RUNTIME
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libsbcl_librarian.core TYPE LIB)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libsbcl_librarian.h TYPE INCLUDE)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/libsbcl_librarian_err.h TYPE INCLUDE)
1 change: 1 addition & 0 deletions lib/VERSION.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
70 changes: 70 additions & 0 deletions lib/entry_point.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#define LIBSBCL_API_BUILD
#ifdef __linux__
#define _GNU_SOURCE
#endif

#ifdef _WIN32
#include <Windows.h>
#include <Process.h>
#include <psapi.h>
#else
#include <dlfcn.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "libsbcl_librarian.h"

#define BUF_SIZE 1024

extern char *sbcl_runtime_home;
extern char *sbcl_runtime;
extern char *dir_name(char *path);
extern int initialize_lisp(int argc, const char *argv[], char *envp[]);

static void do_initialize_lisp(const char *libsbcl_path)
{
char *libsbcl_dir = dir_name(libsbcl_path);
int libsbcl_dir_len = strlen(libsbcl_dir);
int core_path_size = libsbcl_dir_len + sizeof("libsbcl_librarian.core") + 1;
char *core_path = malloc(core_path_size);

snprintf(core_path, core_path_size, "%slibsbcl_librarian.core", libsbcl_dir);

const char *init_args[] = {"", "--dynamic-space-size", "8192", "--core", core_path, "--noinform", "--no-userinit"};

initialize_lisp(sizeof(init_args) / sizeof(init_args[0]), init_args, NULL);

int sbcl_home_path_size = libsbcl_dir_len + sizeof("sbcl") + 1;
int libsbcl_path_size = strlen(libsbcl_path) + 1;
sbcl_runtime = malloc(libsbcl_path_size);
strncpy(sbcl_runtime, libsbcl_path, libsbcl_path_size);
sbcl_runtime_home = malloc(sbcl_home_path_size);
snprintf(sbcl_runtime_home, sbcl_home_path_size, "%ssbcl", libsbcl_dir);
lisp_funcall0_by_name("os-cold-init-or-reinit", "sb-sys");
}

#ifdef _WIN32
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH) {
char libsbcl_path[BUF_SIZE];

GetModuleFileNameA(hinstDLL, libsbcl_path, BUF_SIZE);
do_initialize_lisp(libsbcl_path);
}

return TRUE;
}
#else
__attribute__((constructor))
static void init(void)
{
Dl_info info;

dladdr(do_initialize_lisp, &info);
do_initialize_lisp(info.dli_fname);
}
#endif
17 changes: 17 additions & 0 deletions lib/generate-bindings.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
(require "asdf")

(asdf:load-system :sbcl-librarian)
(handler-bind ((deprecation-condition #'continue))
(asdf:load-system :swank))
(in-package #:sbcl-librarian)

(define-aggregate-library libsbcl-librarian (:function-linkage "LIBSBCL_LIBRARIAN_API")
diagnostics
environment
errors
handles
loader)

(build-bindings libsbcl-librarian "." :omit-init-function t)
(build-python-bindings libsbcl-librarian "." :omit-init-call t)
(build-core-and-die libsbcl-librarian ".")
6 changes: 6 additions & 0 deletions lib/libsbcl_librarian_err.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
typedef enum {
LISP_ERR_SUCCESS = 0,
LISP_ERR_FAILURE = 1,
LISP_ERR_BUG = 2,
LISP_ERR_FATAL = 3
} lisp_err_t;
13 changes: 13 additions & 0 deletions lib/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[project]
name = "sbcl_librarian"
dynamic = ["version"]

[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"

[tool.setuptools.package-data]
"*" = ["py.typed"]

[tool.setuptools.dynamic]
version = {attr = "sbcl_librarian.version.__version__"}
4 changes: 4 additions & 0 deletions lib/python/src/sbcl_librarian/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import errors, raw, wrapper
from .version import __version__

__all__ = ["errors", "raw", "wrapper", "__version__"]
140 changes: 140 additions & 0 deletions lib/python/src/sbcl_librarian/debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"""Functions for debugging Lisp"""

import ctypes
import platform
from enum import Enum

import sbcl_librarian.raw


def enable_backtrace() -> None:
"""
Enable printing backtraces when Lisp errors are
signaled.
"""
sbcl_librarian.raw.enable_backtrace(1)


def disable_backtrace() -> None:
"""
Disable printing backtraces when Lisp errors are
signaled.
"""
sbcl_librarian.raw.enable_backtrace(0)


def disable_debugger() -> None:
"""Disable the Lisp debugger"""
sbcl_librarian.raw.lisp_disable_debugger()


def enable_debugger() -> None:
"""Enable the lisp debugger"""
sbcl_librarian.raw.lisp_enable_debugger()


def gc() -> None:
"""Explicitly run the Lisp garbage collector."""
sbcl_librarian.raw.lisp_gc()


def memory_report() -> None:
"""Print a report about memory use to standard out."""
sbcl_librarian.raw.lisp_memory_report()


def lisp_memory() -> int:
"""Return the memory currently used by the Lisp process"""
result = ctypes.c_ulonglong()
sbcl_librarian.raw.lisp_dynamic_usage(result)
return result.value


def start_swank_server(port: int) -> None:
"""
Start a swank server so that Lisp devs can interact with the Lisp
process directly.
"""
if port <= 1024:
raise ValueError("Port should be greater than 1024")

sbcl_librarian.raw.lisp_start_swank_server(port)


def crash() -> None:
"""Signal crash in the Lisp process."""
sbcl_librarian.raw.crash()


ProfilingMode = Enum("ProfilingMode", [":CPU", ":ALLOC", ":TIME"])


def start_profiling(
max_samples: int = 500,
mode: ProfilingMode = ProfilingMode[":CPU"],
sample_interval: float = 0.01,
) -> None:
"""Start profiling the Lisp image.
`max_samples`: The meximum number of stack traces to collect.
`mode`: :CPU profiles cpu time, :TIME profiles wall clock time,
:ALLOC traces thread-local allocation region overflow handlers.
`sample_interval`: the number of seconds between samples.
"""

if platform.system() == "Windows":
raise Exception("Profiling is not supported on windows")

if not max_samples > 0:
raise ValueError(f"max_samples must be greater than zero, not {max_samples}")

if not sample_interval > 0.0:
raise ValueError(f"sample_interval must be greater than zero, not {sample_interval}")

args = (
f"(:max-samples {max_samples} :mode {mode.name} :sample-interval {sample_interval})".encode(
"utf-8"
)
)
sbcl_librarian.raw.lisp_start_profiling(args)


def stop_profiling() -> None:
"""Stop the profiler."""
if platform.system() == "Windows":
raise Exception("Profiling is not supported on windows")

sbcl_librarian.raw.lisp_stop_profiling()


def profiler_report() -> None:
"""Print a report of the results of profiling."""
if platform.system() == "Windows":
raise Exception("Profiling is not supported on windows")

sbcl_librarian.raw.lisp_profiler_report("(:type :FLAT)".encode("utf-8"))


def profiler_reset() -> None:
"""Reset the proflier."""
if platform.system() == "Windows":
raise Exception("Profiling is not supported on windows")

sbcl_librarian.raw.lisp_reset_profiler()


def handle_count() -> int:
"""Get the number of (apparently living) handles to Lisp objects."""
result = ctypes.c_int()
sbcl_librarian.raw.lisp_handle_count(result)
return result.value


def print_error() -> None:
"""Print the most recent Lisp error messages."""
result = ctypes.c_char_p()
sbcl_librarian.raw.get_error_message(result)
if result.value is not None:
print(result.value.decode("utf-8")) # noqa: T201
7 changes: 7 additions & 0 deletions lib/python/src/sbcl_librarian/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class lisp_err_t(int):
_map = {
0: "LISP_ERR_SUCCESS",
1: "LISP_ERR_FAILURE",
2: "LISP_ERR_BUG",
3: "LISP_ERR_FATAL",
}
32 changes: 32 additions & 0 deletions lib/python/src/sbcl_librarian/fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import gc
from typing import Generator

import pytest

import sbcl_librarian.debug as debug


@pytest.fixture
def check_handle_leaks() -> Generator[None, None, None]:
"""
Fixture to check that all lisp handles are freed by the end of a test.
Useful for ensuring that memory leaks within lisp do not occur.
"""

# Perform a GC to ensure any previously used handles are cleaned up
gc.collect()

# Check that there are no active handles before the test
pre_count = debug.handle_count()

yield

# Perform a GC to ensure any handles are cleaned up
gc.collect()

# Check that no new handles were created
post_count = debug.handle_count()
assert post_count == pre_count, f"{post_count - pre_count} lisp handles were leaked."

return None
9 changes: 9 additions & 0 deletions lib/python/src/sbcl_librarian/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import importlib.metadata
from pathlib import Path

try:
path = Path(__file__).parents[3] / "VERSION.txt"
with Path.open(path, "r") as f:
__version__ = f.readline().strip()
except FileNotFoundError:
__version__ = importlib.metadata.version(__name__.split(".")[0])
Loading

0 comments on commit a6968c2

Please sign in to comment.