diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index 4323451..0000000 --- a/GNUmakefile +++ /dev/null @@ -1,63 +0,0 @@ -PREFIX ?= /usr/local - -GENERATED_MAKEFILE := $(wildcard Makefile) - -.PHONY: distclean -distclean: - @if [ -f Makefile ]; then \ - echo "Running make distclean..."; \ - $(MAKE) -f Makefile distclean 2>/dev/null || true; \ - fi - @echo "Removing autotools generated files..." - @rm -rf Makefile Makefile.in configure config.* libtool aclocal.m4 stamp-h1 autom4te.cache - @rm -rf compile config.guess config.sub depcomp install-sh ltmain.sh missing - @rm -rf .libs .deps src/.libs src/.deps - @rm -f *.lo *.la src/*.lo src/*.la - @echo "All generated files removed. Run 'make' to rebuild." - -# If Makefile exists, delegate to it -ifneq ($(GENERATED_MAKEFILE),) - -.DEFAULT_GOAL := all - -%: - $(MAKE) -f Makefile $@ - -else - -# No Makefile - need to generate build system -.DEFAULT_GOAL := all - -configure: configure.ac Makefile.am - @echo "Generating build system..." - @which glibtoolize > /dev/null 2>&1 && glibtoolize --copy --force --quiet || libtoolize --copy --force --quiet - @aclocal - @autoheader - @automake --add-missing --copy --foreign - @autoconf - @echo "" - -Makefile: configure - @echo "Running configure..." - @./configure --prefix=$(PREFIX) - @echo "" - -.PHONY: all -all: Makefile - @$(MAKE) -f Makefile all - @echo "" - @echo "Build complete. Install with: make install" - @echo "" - -.PHONY: build -build: all - -.PHONY: install -install: Makefile - @$(MAKE) -f Makefile install - -.PHONY: clean -clean: Makefile - @$(MAKE) -f Makefile clean - -endif diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index a4f6213..0000000 --- a/Makefile.am +++ /dev/null @@ -1,8 +0,0 @@ -lib_LTLIBRARIES = libasyncprocess.la -libasyncprocess_la_LDFLAGS = -version-info @LT_VERSION_INFO@ -no-undefined -libasyncprocess_la_SOURCES = src/async-process.c - -include_HEADERS = src/async-process.h - -install-exec-hook: - rm -f $(DESTDIR)$(libdir)/libasyncprocess.la diff --git a/README.md b/README.md index 7231398..b44817f 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,28 @@ # async-process -A Common Lisp library for creating and managing asynchronous processes with PTY support. +A Common Lisp library for creating and managing asynchronous processes ## Platform Support -- **Linux**: Full support via C library using PTY -- **BSD**: Full support via C library using PTY -- **macOS**: Full support via C library using PTY -- **Windows**: Full support via pure Lisp CFFI implementation (no C compilation required) - -## Installation - -The build system is GNU Autotools, with a somewhat non-standard -setup. - -The default goal `all` in `GNUmakefile` can perform the following -sequence in one go: -1. Run `autoconf` using `configure.ac` and `Makefile.am` as inputs. -2. Run `configure` (generated in step 1) to produce a `Makefile`. -3. Build and compile using the generated `Makefile`. - -### Unix-like Systems (Linux, FreeBSD, macOS) -using GNU make, `gmake` on Freebsd and macOS. - -```bash -git clone https://github.com/lem-project/async-process.git -cd async-process -make -sudo make install -``` - -The library installs to `/usr/local` by default. To install elsewhere: - -To install to a different destination, the `make` command should -be substituted with another similar to the examples below, passing -an explicitly-set `PREFIX` environment variable. - -```bash -PREFIX=/your/custom/path make -PREFIX=/usr make -PREFIX=$HOME/.local make -``` - -Alternatively, you can run the Autotools toolchain sequence -as follows, with a slightly different method of setting the -destination prefix: - -```bash -autoreconf -i -./configure --prefix=/your/custom/path -make -make install -``` - -#### Configuration options -Build as a static library as follows - -```bash -make -./configure --enable-static -make all -sudo make install -``` - -### Windows - -On Windows, no C compilation is required. The library uses a pure Lisp implementation via CFFI: - -```bash -git clone https://github.com/lem-project/async-process.git -cd async-process -``` - -Then simply load the library in your Lisp environment: - -```lisp -(ql:quickload :async-process) -``` +- **Linux**: Full support via UIOP +- **BSD**: Full support via UIOP +- **macOS**: Full support via UIOP +- **Windows**: Full support via CFFI implementation The ASDF system will automatically load the Windows-specific implementation (`src/async-process_windows.lisp`) when on Windows platforms. +The the UIOP implementation may also work on windows. Once it is tested, it may be +switched over. + ## Usage -``` -CL-USER> (ql:quickload :async-process) -To load "async-process": - Load 1 ASDF system: - async-process -; Loading "async-process" -.................................................. -[package async-process]. -(:ASYNC-PROCESS) -CL-USER> (in-package async-process) -# -ASYNC-PROCESS> (create-process "python") -#.(SB-SYS:INT-SAP #X7FFFEC002830) -ASYNC-PROCESS> (defparameter p *) -#.(SB-SYS:INT-SAP #X7FFFEC002830) -ASYNC-PROCESS> (process-receive-output p) -"Python 2.7.13 (default, Nov 24 2017, 17:33:09) -[GCC 6.3.0 20170516] on linux2 -Type \"help\", \"copyright\", \"credits\" or \"license\" for more information. ->>> " -ASYNC-PROCESS> (process-send-input p "1+1 -") -; No value -ASYNC-PROCESS> (process-receive-output p) -"1+1 -2 ->>> " +```lisp +(ql:quickload "async-process") +(in-package async-process) ; => # +(defparameter p (create-process '("tee"))) ; => P +(process-send-input p "hello world") ; => NIL +(process-receive-output p) ; => "hello world" +(delete-process p) ; => T ``` ## LICENSE diff --git a/src/async-process.asd b/async-process.asd similarity index 78% rename from src/async-process.asd rename to async-process.asd index 9f506ec..b3e2a08 100644 --- a/src/async-process.asd +++ b/async-process.asd @@ -1,7 +1,7 @@ (defsystem "async-process" :description "asynchronous process execution for common lisp" - :author "cxxxr " - :version "0.0.1" + :author "cxxxr , Ethan Smith fd = fd; - process->pty_name = malloc(strlen(pts_name) + 1); - process->pid = pid; - strcpy(process->pty_name, pts_name); - return process; -} - -void my_exit(int status) { - // exitを使うとatexitで動作に影響を与えられる、これが原因でプロセスを終了できなくなる事があるので使うのを避ける - // 例えばSDL2はat_exitを使っているせいか、lemのSDL2 frontendでasync_processが動作しなくなっていた - _exit(status); -} - -struct process* create_process(char *const command[], bool nonblock, const char *path) -{ - int pty_master; - const char *pts_name = open_pty(&pty_master); - if (pts_name == NULL) - return NULL; - - if (nonblock) - fcntl(pty_master, F_SETFL, O_NONBLOCK); - - int pipefd[2]; - - if (pipe(pipefd) == -1) return NULL; - - pid_t pid = fork(); - - if (pid == 0) { - close(pipefd[0]); - pid = fork(); - if (pid == 0) { - close(pipefd[1]); - setsid(); - int pty_slave = open(pts_name, O_RDWR | O_NOCTTY); - close(pty_master); - - // Set raw mode - struct termios tty; - tcgetattr(pty_slave, &tty); - cfmakeraw(&tty); - tcsetattr(pty_slave, TCSANOW, &tty); - - dup2(pty_slave, STDIN_FILENO); - dup2(pty_slave, STDOUT_FILENO); - dup2(pty_slave, STDERR_FILENO); - close(pty_slave); - if (path != NULL) chdir(path); - execvp(command[0], command); - int error_status = errno; - if (error_status == ENOENT) { - char str[128]; - sprintf(str, "%s: command not found", command[0]); - write(STDIN_FILENO, str, strlen(str)); - } else { - char *str = strerror(error_status); - write(STDIN_FILENO, str, strlen(str)); - } - my_exit(error_status); - } else { - char buf[12]; - sprintf(buf, "%d", pid); - write(pipefd[1], buf, strlen(buf)+1); - close(pipefd[1]); - my_exit(0); - } - } else { - close(pipefd[1]); - if (waitpid(pid, NULL, 0) == -1) - return NULL; - char buf[12]; - read(pipefd[0], buf, sizeof(buf)); - close(pipefd[0]); - return allocate_process(pty_master, pts_name, atoi(buf)); - } - - return NULL; -} - -void delete_process(struct process *process) -{ - kill(process->pid, 9); - close(process->fd); - free(process->pty_name); - free(process); -} - -int process_pid(struct process *process) -{ - return process->pid; -} - -void process_send_input(struct process *process, const char *string) -{ - write(process->fd, string, strlen(string)); -} - -const char* process_receive_output(struct process *process) -{ - int n = read(process->fd, process->buffer, sizeof(process->buffer)-1); - if (n == -1) - return NULL; - process->buffer[n] = '\0'; - return process->buffer; -} - -int process_alive_p(struct process *process) -{ - return kill(process->pid, 0) == 0; -} diff --git a/src/async-process.h b/src/async-process.h deleted file mode 100644 index dd2b26f..0000000 --- a/src/async-process.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef _ASYNC_PROCESS_H_ -#define _ASYNC_PROCESS_H_ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#define _GNU_SOURCE -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -struct process { - char buffer[1024*4]; - int fd; - char *pty_name; - pid_t pid; -}; - -struct process* create_process(char *const command[], bool nonblock, const char *path); -void delete_process(struct process *process); -int process_pid(struct process *process); -void process_send_input(struct process *process, const char *string); -const char* process_receive_output(struct process *process); -int process_alive_p(struct process *process); - -#endif diff --git a/src/async-process.lisp b/src/async-process.lisp deleted file mode 100644 index a88b3c6..0000000 --- a/src/async-process.lisp +++ /dev/null @@ -1,124 +0,0 @@ -(defpackage :async-process - (:use :cl) - (:export - :delete-process - :process-send-input - :process-receive-output - :process-alive-p - :create-process)) -(in-package :async-process) - -(eval-when (:compile-toplevel :load-toplevel :execute) - (defun system (cmd) - (ignore-errors (string-right-trim '(#\Newline) (uiop:run-program cmd :output :string)))) - (defun muslp () - (ignore-errors - (not (zerop (length (uiop:run-program - "ldd /bin/ls |grep musl" - :ignore-error-status t - :output :string))))))) - -(pushnew (asdf:system-relative-pathname - :async-process - (format nil "../static/~A/" - (cond - ;; Windows - ((uiop/os:featurep '(:and :windows :x86-64)) - "x86_64/windows") - ((uiop/os:featurep :windows) - "x86/windows") - ;; macOS (Darwin) - ((uiop/os:featurep :os-macosx) - (format nil "~A/darwin" - (uiop:run-program '("uname" "-m") :output '(:string :stripped t)))) - ;; Linux / Generic Unix - ((uiop/os:featurep :unix) - (format nil "~A/~A" - (uiop:run-program '("uname" "-m") :output '(:string :stripped t)) - (let ((os (uiop:run-program '("uname") :output '(:string :stripped t)))) - (cond ((and (equal os "Linux") - (ignore-errors (funcall (read-from-string "muslp")))) - "Linux-musl") - (t os)))))))) - cffi:*foreign-library-directories* - :test #'uiop:pathname-equal) - -(cffi:define-foreign-library async-process - (:darwin "libasyncprocess.dylib") - (:unix "libasyncprocess.so") - (:windows "libasyncprocess.dll")) - -(cffi:use-foreign-library async-process) - -(defclass process () - ((process :reader process-process :initarg :process) - (encode :accessor process-encode :initarg :encode))) - -(cffi:defcfun ("create_process" %create-process) :pointer - (command :pointer) - (nonblock :boolean) - (path :string)) - -(cffi:defcfun ("delete_process" %delete-process) :void - (process :pointer)) - -(cffi:defcfun ("process_pid" %process-pid) :int - (process :pointer)) - -(cffi:defcfun ("process_send_input" %process-send-input) :void - (process :pointer) - (string :string)) - -(cffi:defcfun ("process_receive_output" %process-receive-output) :pointer - (process :pointer)) - -(cffi:defcfun ("process_alive_p" %process-alive-p) :boolean - (process :pointer)) - -(defun create-process (command &key nonblock (encode cffi:*default-foreign-encoding*) directory) - (when (and directory (not (uiop:directory-exists-p directory))) - (error "Directory ~S does not exist" directory)) - (let* ((command (uiop:ensure-list command)) - (length (length command))) - (cffi:with-foreign-object (argv :string (1+ length)) - (loop :for i :from 0 - :for c :in command - :do (setf (cffi:mem-aref argv :string i) c)) - (setf (cffi:mem-aref argv :string length) (cffi:null-pointer)) - (let ((p (%create-process argv nonblock (if directory - (namestring directory) - (cffi:null-pointer))))) - (if (cffi:null-pointer-p p) - (error "create-process failed: ~S" command) - (make-instance 'process :process p :encode encode)))))) - -(defun delete-process (process) - (%delete-process (process-process process))) - -(defun process-pid (process) - (%process-pid (process-process process))) - -(defun process-send-input (process string) - (let ((cffi:*default-foreign-encoding* (process-encode process))) - (%process-send-input (process-process process) string))) - -(defun pointer-to-string (pointer) - (unless (cffi:null-pointer-p pointer) - (let* ((bytes (loop :for i :from 0 - :for code := (cffi:mem-aref pointer :unsigned-char i) - :until (zerop code) - :collect code)) - (octets (make-array (length bytes) - :element-type '(unsigned-byte 8) - :initial-contents bytes))) - (handler-case (babel:octets-to-string octets) - (error () - ;; Fallback when an error occurs with UTF-8 encoding - (map 'string #'code-char octets)))))) - -(defun process-receive-output (process) - (let ((cffi:*default-foreign-encoding* (process-encode process))) - (pointer-to-string (%process-receive-output (process-process process))))) - -(defun process-alive-p (process) - (%process-alive-p (process-process process)))