ALPM — AutoLISP Package Management — User Manual

Version: 0.6.2. The implementation in ‘sources/alpm.lsp’ is complete (encoding conversion covers ascii / utf-8 / utf-8-bom / cp1252 / mac-roman; cp1250 / cp1251 / cp932 are recognized in declarations but not converted yet).

ALPM is to AutoLISP what ASDF is to Common Lisp: you describe your libraries as systems — named sets of source files with declared dependencies — and ALPM loads them in the right order, every time, on AutoCAD, BricsCAD, or clautolisp.

One calling convention to know: AutoLISP functions take a fixed number of arguments, so the arguments written ‘[in brackets]’ in this manual must still be passed — give ‘nil’ to get the default, as in ‘(alpm-load-system "geometry" nil)’.

Table of Contents


1 Installation

Install ALPM from a checkout of its repository:

make install                 # default PREFIX=/opt/local
make install PREFIX=/usr/local

This generates a single self-contained ‘alpm.lsp’ (ALPM concatenated by itself) and installs it as ‘$PREFIX/share/autolisp/alpm.lsp’, the documentation (org + PDF) under ‘$PREFIX/share/doc/alpm/’, and this manual as an Info file ‘$PREFIX/share/info/alpm.info’ (read it with ‘info alpm’). ‘sudo make install’ works; packagers can use ‘DESTDIR’. Then one form, once per session (or from your ‘acaddoc.lsp’ / ‘on_start.lsp’ / clautolisp init), does everything:

(load "/opt/local/share/autolisp/alpm.lsp")

Loading it defines ALPM and initializes the site registry — the file is self-locating (it registers the directory it is loaded from, so it works under any prefix or relocated), performing the equivalent of:

(alpm-register-directory "/opt/local/share/autolisp/" T)
(alpm-register-support-path)
(alpm-trust-registry)

$PREFIX/share/autolisp/’ is the site directory: install each system there as a subdirectory ‘<name>/’ containing ‘<name>.alpm’ and its sources — then ‘(alpm-load-system "foo" nil)’ finds ‘/opt/local/share/autolisp/foo/foo.alpm’ and loads foo’s sources from ‘/opt/local/share/autolisp/foo/’.

For systems living elsewhere (development trees), extend the registry yourself:

(alpm-register-directory "/home/pjb/lisp/systems" nil)
(alpm-register-directory "d:/work/lisp" T)   ; T = also scan subdirectories

(When working on ALPM itself, ‘(load ".../alpm/sources/alpm.lsp")’ loads the uninstalled implementation without any site initialization.)

Use ‘/’ as the path separator on every platform. The recursive form registers a whole tree of projects at once (symlink loops are detected and skipped). ‘alpm-register-support-path’ adds the directories of AutoCAD’s / BricsCAD’s own search path — they are configured in the GUI, and ALPM does not consult them unless you ask. ‘alpm-trust-registry’ keeps ‘SECURELOAD’ from blocking loads out of registered directories that are not already trusted.

To find out where systems live in a tree before registering anything, use ‘alpm-find-systems-in-tree’ — same recursive, symlink-safe traversal as ‘alpm-register-directory’’s recursive form, but read-only:

(alpm-find-systems-in-tree "/home/pjb/lisp")
;; => ("/home/pjb/lisp/geometry" "/home/pjb/lisp/vectors" ...)

It returns the directories that directly contain a ‘.alpm’ file, without touching ‘*alpm-registry*’.


2 Your First System

Suppose a small library in ‘/home/pjb/lisp/geometry/’:

geometry/
  geometry.alpm
  points.lsp
  lines.lsp
  circles.lsp

points.lsp’ defines point constructors; ‘lines.lsp’ calls functions from ‘points.lsp’; ‘circles.lsp’ uses both. Write this in ‘geometry.alpm’:

;;;; System definition for GEOMETRY.
(alpm-define-system 1
 '(name        "geometry"
   version     "1.0.0"
   description "2D geometry entities and predicates."
   files       ("points"
                (file "lines"   depends-on ("points"))
                (file "circles" depends-on ("points" "lines")))))

Things to note:

(alpm-register-directory "/home/pjb/lisp" nil)

3 Loading a System

(alpm-load-system "geometry" nil)

This finds ‘geometry.alpm’, registers the definition, computes the load order from the dependencies (‘points’, then ‘lines’, then ‘circles’), checks that every file exists before loading anything, and loads them. It returns the system name on success.

Loading it a second time in the same session does nothing (it is already loaded). After editing sources, force a reload:

(alpm-load-system "geometry" T)      ; reload this system's files
(alpm-load-system "geometry" 'all)   ; ... and all its dependencies too

3.1 Alternative: serial systems

If your files simply load in the order listed — the common case for existing code bases — skip the per-file dependencies:

(alpm-define-system 1
 '(name   "geometry"
   serial T
   files  ("points" "lines" "circles")))

With ‘serial T’, every file depends on all the files listed before it.


4 Depending on Other Systems

Say ‘geometry’ needs your ‘vectors’ library. Add:

depends-on ("vectors")

to the definition. Now ‘(alpm-load-system "geometry" nil)’ first locates ‘vectors.alpm’ (in the registered directories), loads the ‘vectors’ system completely, then loads ‘geometry’’s own files. Dependencies are transitive and loaded exactly once; circular system dependencies are detected and reported as an error with the cycle spelled out.

File-level ‘depends-on’ can only name files of the same system; if a file needs another system, put that system in the system-level ‘depends-on’.


5 Declaring the Public Interface: exports

AutoLISP has no ‘defpackage’, so nothing in the language records which functions of a library are meant to be called from outside. ALPM provides that missing declaration:

(alpm-define-system 1
 '(name    "geometry"
   exports ("geo-make-point" "geo-intersect" "c:geodraw")
   files   ("points" "lines" "circles")))

exports’ lists the functions (including ‘c:’ commands) that the system offers; everything else is internal by convention. Even before any enforcement exists, this is checked documentation:

(alpm-check-exports "geometry")

reads the sources (recognizing both ‘defun’ and ‘defun-q’), prints warnings — declared names never defined (the interface lies), names published with ‘vl-doc-export’ but missing from the declaration — and returns three lists: ‘(common-export-list only-in-vl-doc-export-list only-in-alpm-export-list)’.

To adopt what the code already exports into the declaration:

(alpm-scan-exports "geometry" nil)     ; look first: what would be merged
(alpm-scan-exports "geometry" T)   ; merge scanned exports into the declaration
(alpm-save-system (alpm-find-system "geometry")
                  "/home/pjb/lisp/geometry/geometry.alpm")  ; persist it

5.1 Where enforcement does exist

When you compile a library into a module with the Separate Namespace option — an AutoCAD ‘.vlx’ built by the Make Application wizard (Expert mode; the settings live in the ‘.prv’ file), or a BricsCAD DES-encrypted file with the VLX-NameSpace feature (DEScoder v2.7+ / BricsCAD V18.2 and later) — the module’s functions are hidden from the loading document, except those published at load time by top-level ‘(vl-doc-export 'name)’ calls inside the module.

ALPM generates those calls from your declaration. Declare where the generated file lives by listing it as a file of the system with ‘generated-exports T’:

files ("points"
       (file "lines" depends-on ("points"))
       (file "geometry-exports" generated-exports T
             depends-on ("points" "lines")))

then

(alpm-generate-exports-file "geometry" nil)

(re)writes that file with one guarded ‘(if vl-doc-export (vl-doc-export 'geo-make-point))’ per exported name, and it is loaded at its declared position like any other file — and listed like any other file when the sources are compiled into a module (‘.prj’ / DEScoder project), which is what makes the exports take effect there. Loaded as plain source, the file is harmless on every engine. Without a ‘generated-exports’ file spec, the file is written as ‘geometry-exports.lsp’ next to the sources but never added to a plan behind your back.

If your existing sources already contain inline ‘vl-doc-export’ calls, keep them: declare the same names in ‘exports’ and ‘alpm-check-exports’ will keep declaration and code in sync.


6 Source File Encodings

Unless a source file starts with a UTF-8 BOM, engines read it in a system-dependent encoding: MacRoman for BricsCAD on macOS, the locale’s ANSI code page on Windows (cp1252 in Western Europe), and clautolisp reads UTF-8. Accented characters in string constants therefore change meaning from one machine to another — unless the encoding is declared and handled.

Declare the encoding of your sources once for the whole system, and per file for exceptions:

(alpm-define-system 1
 '(name     "geometry"
   encoding "utf-8"
   files    ("points"
             "lines"
             (file "legacy" encoding "cp1252"))))

Canonical names: ‘"ascii"’ (the default — reads identically everywhere), ‘"utf-8"’, ‘"utf-8-bom"’, ‘"cp1252"’, ‘"cp1250"’, ‘"cp1251"’, ‘"mac-roman"’ (also accepted as ‘"macroman"’, ‘"mac_roman"’, ‘"macintosh"’, ‘"x-mac-roman"’, ‘"cp10000"’).

When loading, ALPM compares each file’s declared encoding with what the running engine expects, and does what the situation requires: nothing when they match (or the file is ASCII), setting the load encoding where the engine supports that (clautolisp’s ‘*AUTOLISP-FILE-ENCODING*’), or converting the file to the engine’s expected encoding in a scratch directory and loading the converted copy. A declared encoding ALPM cannot honor on the current engine is a plan-time error — never silently corrupted characters.

To audit a system, run:

(alpm-check-encodings "geometry")

It flags files inconsistent with their declaration — in particular files declared (or defaulted to) ‘"ascii"’ that actually contain non-ASCII bytes, which is exactly the situation the declaration exists to prevent.

Note: generated loader files (Generating a Loader File) do no conversion; if your system needs conversion for the target engine, use ‘alpm-generate-distribution’ (a tree of pre-converted sources with the loader inside) or keep the sources in the engine’s native encoding.


7 Generating a Loader File

To use a system on a machine where ALPM is not installed (e.g. an end user’s AutoCAD), generate a self-contained loader:

(alpm-generate-loader "geometry" "/home/pjb/lisp/geometry/geometry-loader.lsp" nil)

geometry-loader.lsp’ loads every source file, dependencies included, in the correct order, and requires nothing but the sources themselves. Regenerate it whenever the definition changes (it says so in its header).

The loader is self-locating: AutoCAD and BricsCAD offer no way to set search paths from a program and no way for a file to know where it is being loaded from, so the loader finds its base directory at run time by trying, in order: a ‘*geometry-loader-base*’ variable you may set before loading it; clautolisp’s ‘*AUTOLISP-LOAD-PATHNAME*’; ‘(findfile "geometry-loader.lsp")’ — which works when its directory is on the engine search path (set in the GUI) or is the current directory; the ALPM registry, when ALPM happens to be loaded; and finally the absolute path recorded at generation time. All source files are then loaded relative to that base, so the whole tree can be moved or deployed anywhere as one piece.

Loading the loader loads the system; it also defines ‘(load-geometry base)’ so you can reload later from an explicit directory:

(load "d:/deploy/geometry/geometry-loader.lsp")   ; loads the system
(load-geometry "d:/elsewhere/geometry")           ; reload from another tree

If the system needs encoding conversion for the target engine (see Source File Encodings), generate a distribution instead: it copies the sources pre-converted and puts the loader inside the tree:

(alpm-generate-distribution "geometry" "d:/dist/geometry" "cp1252" nil)

8 Generating a Single Self-Sufficient File

Sometimes one file beats a tree: mailing a library to a colleague, APPLOADing on a locked-down machine, pasting into a console. Concatenate the system into a single source file:

(alpm-concatenate-system "geometry" "d:/out/geometry-all.lsp" nil nil)

The file contains every source of ‘geometry’ in dependency order, each section labeled with a banner comment naming its original file. Dependencies are handled by the fourth argument:

;; default: dependencies as (alpm-load-system ...) forms at the top
;; -- small file, but loading it requires ALPM + registry.
(alpm-concatenate-system "geometry" "d:/out/geometry-all.lsp" nil nil)

;; monolithic: dependency sources concatenated in too -- the file
;; needs nothing at all, one load brings in everything.
(alpm-concatenate-system "geometry" "d:/out/geometry-full.lsp" "cp1252" T)

The third argument is the encoding of the generated file (default: the system’s declared ‘encoding’); every source is transcoded from its own declared encoding, and a character that does not fit the target encoding is a generation error, never mojibake.

Caveats (also written into the generated header): the value of ‘load’ is the last form of the last file, not of each file; code that locates siblings of its own source file behaves differently once everything is one file; and a monolithic file redefines every included dependency at load time — in a session that manages libraries with ALPM, prefer the default form, which lets ‘alpm-load-system’ keep each dependency loaded exactly once.


9 Inspecting Systems

(alpm-registered-systems)                    ; -> ("geometry" "vectors")
(alpm-loaded-systems)                        ; -> (("geometry" . "1.0.0") ...)
(alpm-system-files "geometry")               ; -> ("points" "lines" "circles")
(alpm-system-depends-on "geometry")          ; -> ("vectors")
(alpm-system-all-depends-on "geometry")      ; transitive, in load order
(alpm-system-get "geometry" 'version)        ; -> "1.0.0"
(alpm-system-pathname "geometry")            ; -> "/home/pjb/lisp/geometry/"
(alpm-file-pathname "geometry" "lines" nil)      ; -> ".../geometry/lines.lsp"
(alpm-compute-plan "geometry" nil)               ; the exact ordered action list

Two introspection functions deal with AutoLISP’s ‘load’ returning the value of the file’s last top-level form (some programs rely on that value, e.g. a file whose last form builds an entry function):

(alpm-file-last-value "geometry" "main")   ; value returned by loading main.lsp
(alpm-file-loaded-p   "geometry" "main")   ; T if it was loaded this session

10 Building Definitions Programmatically

The ‘.alpm’ file format is just the serialization of calls you can make yourself:

(setq sys (alpm-make-system "newlib"))
(alpm-system-set sys 'version "0.1.0")
(alpm-system-set sys 'depends-on '("geometry"))
(alpm-add-file sys "base")
(alpm-add-file sys "extra" '(depends-on ("base")))
(alpm-register-system sys)
(alpm-save-system sys "/home/pjb/lisp/newlib/newlib.alpm")

alpm-save-system’ writes a ‘.alpm’ file that reproduces the definition exactly when loaded.


11 Importing an Existing .prj Project

If you already maintain an AutoCAD/BricsCAD project file, convert it:

(alpm-import-project "/home/pjb/work/schmsplus/schmsplus.prj"
                     "/home/pjb/work/schmsplus/schmsplus.alpm")

This reads the project’s source list and produces a serial ALPM system with the same files in the same order (a ‘.prj’ expresses a linear order, so the import preserves it faithfully). You can then refine the generated ‘.alpm’ by hand — typically replacing ‘serial T’ with real per-file dependencies. Compile-related project settings are preserved under the system’s ‘properties’ for future use.


12 Errors, Verbosity, Troubleshooting


13 Conventions and Tips


14 Quick Reference

callpurpose
(alpm-register-directory dir [recursive])add a directory (or a whole tree) to the search path
(alpm-find-systems-in-tree dir)list directories under ‘dir’ holding a ‘.alpm’ (read-only)
(alpm-register-support-path)’ / ‘(alpm-trust-registry)import the engine search path / trust the registry
(alpm-load-definition name [force])find and load a ‘.alpm’ file
(alpm-load-system name [force])load a system and its dependencies; ‘force’ = ‘T’ or ‘all
(alpm-generate-loader name output [relative-to])write a standalone self-locating loader ‘.lsp
(alpm-generate-distribution name target-dir target-encoding [relative-to])pre-converted source tree + loader for one target engine
(alpm-concatenate-system name output [target-encoding] [include-dependencies])single self-sufficient source file (monolithic with ‘T’)
(alpm-system-exports name [effective])’ / ‘(alpm-scan-exports name [update])’ / ‘(alpm-check-exports name)’ / ‘(alpm-generate-exports-file name [output])declared interface: read / scan-and-merge / cross-check / generate
(alpm-check-encodings name)verify sources against their declared encodings
(alpm-compute-plan name [operation])ordered list of load actions
(alpm-registered-systems)’ / ‘(alpm-loaded-systems)what is known / what is loaded
(alpm-system-files name)’ / ‘(alpm-system-depends-on name)’ / ‘(alpm-system-get name prop)system introspection
(alpm-file-pathname name file)’ / ‘(alpm-file-last-value name file)file introspection
(alpm-make-system name)’ + ‘alpm-system-set’ / ‘alpm-add-file’ / ‘alpm-register-system’ / ‘alpm-save-systembuild and save definitions from code
(alpm-import-project prj [output])convert a ‘.prj’ to a system
(alpm-version)ALPM version