Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 0 additions & 63 deletions GNUmakefile

This file was deleted.

8 changes: 0 additions & 8 deletions Makefile.am

This file was deleted.

116 changes: 15 additions & 101 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
#<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) ; => #<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
Expand Down
4 changes: 2 additions & 2 deletions src/async-process.asd → async-process.asd
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(defsystem "async-process"
:description "asynchronous process execution for common lisp"
:author "cxxxr <[email protected]>"
:version "0.0.1"
:author "cxxxr <[email protected]>, Ethan Smith <[email protected]"
:version "0.0.2"
:license "MIT"
:depends-on ("cffi")
:serial t
Expand Down
140 changes: 140 additions & 0 deletions async-process.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
(defpackage :async-process
(:use :cl)
(:export
:delete-process
:process-send-input
:process-receive-output
:process-alive-p
:create-process))

(in-package async-process)

(defvar *active-processes* nil
"list of processes started by async-process. If a process won't exit and needs
killed, it can be found in this list.")

(defclass process ()
((info :type uiop:process-info :initarg :info)
(nonblockp :type t :initform t :initarg :nonblockp)
(command :type string :initarg :command))
(:documentation "Represents an asynchronous process. `async-process` used to
implement bespoke logic for starting processes. Now, this functionality is
implemented using `uiop:launch-program` which returns a `process-info` class"))

(defun create-process (command &rest keys &key nonblock
(encode cffi:*default-foreign-encoding*)
&allow-other-keys)
"calls creates a process that runs in the background. `DELETE-PROCESS` must
be called when process is completed. Passes arguments to uiop:launch-program.
`NONBLOCK` will affect behavior of reading output. Encode is not used."
(declare (ignore encode))

(let ((proc (make-instance 'process
:command command
:nonblockp nonblock
:info (apply 'uiop:launch-program
command
:input :stream
:output :stream
:error-output :stream
keys))))

(push proc *active-processes*)
proc))

(defun delete-process (proc)
"terminate `PROCESS` and remove it from *active-processes*"
(declare (type process proc))

(with-slots (info) proc
(when (uiop:process-alive-p info)
(uiop:terminate-process info)
(uiop:wait-process info)))

(setf *active-processes* (delete proc *active-processes*))
t)

(defun process-pid (proc)
(declare (type process proc))

(uiop:process-info-pid (slot-value proc 'info)))

(defun process-send-input (proc input)
(declare (type process proc)
(type string input))

(let ((s (uiop:process-info-input (slot-value proc 'info))))
(write-string input s)
(finish-output s)))

(defun process-receive-output (proc &key (errorp nil))
(declare (type process proc)
(optimize (debug 3)))

(with-slots (info nonblockp) proc
(let ((s (if errorp
(uiop:process-info-error-output info)
(uiop:process-info-output info)))

(blockp (not nonblockp))
(str-out (make-array 20
:element-type 'character
:adjustable t
:fill-pointer 0)))
(when (or blockp (listen s))
;; attempt reading output whenever there is data, or we can block

(loop :for c = (read-char-no-hang s nil nil)
:while (or (and blockp (= 0 (length str-out)))
c)
:do (when c (vector-push-extend c str-out))))
str-out)))


(defun process-alive-p (proc)
(declare (type process proc))

(uiop:process-alive-p (slot-value proc 'info)))

(defun test-process-output ()
(let ((proc (create-process '("echo" "hello" "world") :nonblock nil)))
(sleep 0.5)
(format t "~&ouptut: ~S" (process-receive-output proc))
(delete-process proc)))

(defun test-process-input ()
(let ((proc (create-process '("tee") :nonblock nil)))
(sleep 0.5)
(process-send-input proc "hello world
")
(format t "~&output: ~S" (process-receive-output proc))
(delete-process proc)))

;(test-process-input)

(defvar *test-proc* nil)

(defun test1 ()
(setf *test-proc* (create-process '("tee") :nonblock t)))

(defun test2 ()
(process-send-input *test-proc* "hello world
"))
(defun test-process-input ()
(let ((proc (create-process '("tee") :nonblock nil)))
(sleep 0.5)
(process-send-input proc "hello world
")
(format t "~&output: ~S" (process-receive-output proc))
(delete-process proc)))
(defun test3 ()
(format t "~&~A" (process-receive-output *test-proc*)))

(defun test1-2-cleanup ()
(delete-process *test-proc*)
(setf *test-proc* nil))

;(test1)
;(test2)
;(test3)
;(test1-2-cleanup)
File renamed without changes.
Loading