This guide explains the clails task system. The task system provides functionality to define and execute application-specific operations such as database operations, data seeding, cleanup, and more.
- Tasks are defined using the
deftaskmacro - Related tasks can be grouped using the
defnamespacemacro - Tasks are executed with the
clails taskcommand - Tasks can define dependencies, which are automatically executed before the task
- Task files are placed in the
lib/tasks/directory - Custom tasks are automatically loaded when the application starts
In a clails project, task files are organized as follows:
your-app/
├── app/
│ ├── controllers/
│ ├── models/
│ └── views/
├── lib/
│ └── tasks/ # Custom task directory
│ ├── cleanup.lisp # Sample task
│ └── seed.lisp # Data seeding tasks, etc.
├── your-app.asd
└── your-app-test.asd
- Task files use the
.lispextension - File names should be descriptive of their content
- Can be placed in any directory under
lib/tasks/(including subdirectories)
Use the deftask macro to define tasks:
(defpackage #:your-app/tasks/cleanup
(:use #:cl)
(:import-from #:clails/task/core
#:deftask)
(:import-from #:clails/logger/core
#:log.task))
(in-package #:your-app/tasks/cleanup)
(deftask :cleanup
:description "Clean up temporary files"
:function (lambda ()
(log.task "Task started" :task-name "cleanup")
(format t "Cleaning up temporary files~%")
;; Implement cleanup logic here
(log.task "Task completed" :task-name "cleanup")))Use the defnamespace macro to group related tasks:
(defpackage #:your-app/tasks/db-tasks
(:use #:cl)
(:import-from #:clails/task/core
#:deftask
#:defnamespace)
(:import-from #:clails/logger/core
#:log.task))
(in-package #:your-app/tasks/db-tasks)
(defnamespace :db
(deftask :seed
:description "Load seed data into database"
:function (lambda ()
(log.task "Task started" :task-name "db:seed")
(format t "Loading seed data~%")
;; Implement seed logic here
(log.task "Task completed" :task-name "db:seed")))
(deftask :reset
:description "Reset database"
:function (lambda ()
(log.task "Task started" :task-name "db:reset")
(format t "Resetting database~%")
;; Implement reset logic here
(log.task "Task completed" :task-name "db:reset"))))Tasks can define dependencies. Dependent tasks are automatically executed first:
(deftask :setup
:description "Setup application"
:function (lambda ()
(format t "Setting up application~%")))
(deftask :deploy
:description "Deploy application"
:depends-on (:setup)
:function (lambda ()
(format t "Deploying application~%")))
;; Dependencies on namespaced tasks
(deftask :full-deploy
:description "Full deployment with database setup"
:depends-on ((:db :migrate) (:db :seed) :deploy)
:function (lambda ()
(format t "Full deployment completed~%")))Tasks can accept arguments:
(deftask :import-data
:description "Import data from file"
:args (&key (file "data.csv") (verbose nil))
:function (lambda (&key (file "data.csv") (verbose nil))
(format t "Importing data from: ~A~%" file)
(when verbose
(format t "Verbose mode enabled~%"))
;; Implement data import logic here
))Generate a new task file.
clails generate:task TASK_NAME [OPTIONS]| Option | Short | Description |
|---|---|---|
--namespace NS |
-ns NS |
Specify task namespace |
# Generate a simple task
clails generate:task cleanup
# Generate a namespaced task
clails generate:task seed -ns db
clails generate:task import --namespace data# Simple task
lib/tasks/cleanup.lisp
# Namespaced tasks
lib/tasks/db/seed.lisp
lib/tasks/data/import.lisp
Execute tasks with the clails task command:
# Run a simple task
clails task cleanup
# Run a namespaced task
clails task db:seed
clails task db:migrateView available tasks:
# List all tasks
clails task --list
# List tasks in a specific namespace
clails task --list dbSample output:
Available tasks:
Global tasks:
cleanup Clean up temporary files
db:
db:seed Load seed data into database
db:reset Reset database
db:migrate Run database migrations
View detailed task information:
# Show task details
clails task --info cleanup
clails task --info db:seedSample output:
Task: db:seed
Description: Load seed data into database
Dependencies: (:create :migrate)
Tasks are managed by the clails/task/registry package:
register-task: Register a taskfind-task: Search for a tasklist-tasks: Get list of taskslist-namespaces: Get list of namespacesload-custom-tasks: Load custom task files
Task execution is handled by the clails/task/runner package:
- Automatic dependency resolution
- Task idempotency (same task executes only once per run)
- Error handling and logging
Each task is represented by the <task-info> class:
name: Task namenamespace: Namespace (optional)description: Description textdepends-on: List of dependent tasksargs: Argument listfunction: Function to execute
(defpackage #:your-app/tasks/db-seed
(:use #:cl)
(:import-from #:clails/task/core
#:deftask
#:defnamespace)
(:import-from #:clails/logger/core
#:log.task)
(:import-from #:your-app/models/user
#:<user>)
(:import-from #:clails/model/core
#:make-record
#:save))
(in-package #:your-app/tasks/db-seed)
(defnamespace :db
(deftask :seed
:description "Load initial user data"
:depends-on ((:db :migrate))
:function (lambda ()
(log.task "Task started" :task-name "db:seed")
;; Create user data
(let ((users '(("Alice" "alice@example.com")
("Bob" "bob@example.com")
("Charlie" "charlie@example.com"))))
(dolist (user-data users)
(let ((user (make-record '<user>
:name (first user-data)
:email (second user-data))))
(save user)
(format t "Created user: ~A~%" (first user-data)))))
(log.task "Task completed" :task-name "db:seed"))))Execution:
clails task db:seed(defpackage #:your-app/tasks/log-cleanup
(:use #:cl)
(:import-from #:clails/task/core
#:deftask)
(:import-from #:clails/logger/core
#:log.task)
(:import-from #:uiop
#:delete-file-if-exists))
(in-package #:your-app/tasks/log-cleanup)
(deftask :log-cleanup
:description "Remove old log files"
:args (&key (days 30))
:function (lambda (&key (days 30))
(log.task "Task started"
:task-name "log-cleanup"
:days days)
(format t "Removing log files older than ~A days~%" days)
;; Log file deletion logic
(let ((log-dir (merge-pathnames "logs/" (uiop:getcwd)))
(cutoff-time (- (get-universal-time)
(* days 24 60 60))))
(dolist (file (uiop:directory-files log-dir "*.log"))
(when (< (file-write-date file) cutoff-time)
(delete-file-if-exists file)
(format t "Deleted: ~A~%" file))))
(log.task "Task completed" :task-name "log-cleanup")))Execution:
# Default (30 days)
clails task log-cleanup
# Custom days (keyword arguments currently unsupported, planned for future)(defpackage #:your-app/tasks/report
(:use #:cl)
(:import-from #:clails/task/core
#:deftask)
(:import-from #:clails/logger/core
#:log.task))
(in-package #:your-app/tasks/report)
(deftask :generate-report
:description "Generate monthly report"
:depends-on (:backup-data)
:function (lambda ()
(log.task "Task started" :task-name "generate-report")
(format t "Generating monthly report~%")
;; Report generation logic
(let ((report-file (format nil "report-~A.txt"
(local-time:format-timestring
nil
(local-time:now)
:format '(:year :month :day)))))
(with-open-file (out report-file
:direction :output
:if-exists :supersede)
(format out "Monthly Report~%")
(format out "Generated: ~A~%"
(local-time:format-timestring nil (local-time:now))))
(format t "Report saved to: ~A~%" report-file))
(log.task "Task completed" :task-name "generate-report")))
(deftask :backup-data
:description "Backup application data"
:function (lambda ()
(format t "Backing up data~%")
;; Backup logic
))Execution:
clails task generate-reportIn this case, since generate-report depends on backup-data,
backup-data will be executed first.
- Single Responsibility Principle: Each task should do one thing
- Idempotency: Design tasks to be safe when executed multiple times
- Logging: Log task start and completion
- Error Handling: Provide appropriate error messages
- Use Namespaces: Group related tasks with namespaces
- Split Files: Divide large task sets into multiple files
- Explicit Dependencies: Clearly define dependencies between tasks
- Long-Running Tasks: Display progress
- Database Access: Use appropriate transaction management
- Batch Processing: Process large data in chunks
Error: Task not found: mytask
Solutions:
- Verify task file is placed in
lib/tasks/ - Check that task name and namespace are correct
- Ensure task is properly defined with
deftask
Dependency task not found: :setup
Solutions:
- Verify dependent task is defined
- Check that dependent task's file is loaded
- Dependencies on namespaced tasks should use
(:namespace :task-name)format
Loading task file: /path/to/task.lisp ... failed: <error>
Solutions:
- Check task file for syntax errors
- Verify required packages are declared with
:import-from - Ensure Common Lisp syntax is correct
The clails task system is a powerful feature for easily defining and executing application-specific operations:
- Easy task definition with
deftaskmacro - Group related tasks with
defnamespace - Automatic dependency resolution
- Simple execution with
clails taskcommand - Built-in support for logging and error handling
Leverage the task system to efficiently execute various batch processes and maintenance tasks such as database setup, data seeding, cleanup, report generation, and more.