Module creation¶
This tutorial demonstrates the complete end-to-end workflow for creating and
packaging an EPICS module for e3. We’ll create a simple example module called
exampleModule, showing how all the pieces from previous chapters fit together.
Overview¶
The tutorial covers:
Creating an EPICS module using
makeBaseAppandmakeSupportSetting up the recipe repository to package the module
Creating the build script for conda-build integration
Integrating with e3 using makefiles and recipes from previous chapters
Testing the complete workflow
Note
This tutorial integrates concepts from:
Building modules - build environment and conda-build usage
Module build recipes - recipe structure and meta.yaml details
Module build configurations - e3 makefile creation
For detailed EPICS module development guidance, refer to:
We recommend storing modules in the ESS GitLab epics-modules namespace and submitting them to the EPICS modules database for community awareness.
Tip
If you already have an existing EPICS module that you want to package, you can skip to Step 2: Package the module.
Step 1: Create the EPICS module¶
We’ll create a simple example module called exampleModule using EPICS base tools.
1.1 Set up the module directory¶
$ mkdir exampleModule
$ cd exampleModule
$ git init
1.2 Create the application structure¶
Create the basic EPICS application structure using makeBaseApp:
$ makeBaseApp.pl -t example exampleModule
$ makeBaseApp.pl -i -t example exampleModule
This creates the standard EPICS application structure with configure/, exampleModuleApp/, iocBoot/, and more.
Note
The EPICS base utility makeBaseApp scaffolds an IOC application; installable module artifacts are provided
via the e3 makefile in 2.3 Create the e3 makefile.
1.3 Create device support (optional)¶
If your module needs device support, use makeSupport:
$ makeSupport.pl -t devGpib exampleModule
This creates device support files in exampleModuleSup/ directory, including:
devExampleModule.c- Device support implementationexampleModule.dbd- Database definition file
1.4 Module structure overview¶
Your module now has a typical EPICS structure:
$ tree -L 2
.
├── configure/
├── exampleModuleApp/
├── exampleModuleSup/ # If device support was created
├── db/
├── iocBoot/
└── Makefile
Note
The actual implementation of device support, database files, and application code is beyond the scope of this tutorial. Refer to the EPICS documentation links provided earlier for detailed development guidance.
After your code has been reviewed and merged into the default branch, tag the release with a version number.
Your first published release should typically be 1.0.0.
Tip
We encourage use of semantic versioning.
Step 2: Package the module¶
Now we’ll create a separate repository for the conda recipe. This separation allows for independent versioning, cleaner CI/CD, community contributions, and site-specific customizations separate from upstream.
2.1 Create recipe repository structure¶
$ mkdir exampleModule-recipe
$ cd exampleModule-recipe
$ mkdir -p recipe src
$ git init
For detailed information about recipe repository structure and organization principles, see Module build recipes.
2.2 Create the build script¶
Create recipe/build.sh - this is the standard build script for all e3 modules:
conda-build will automatically execute this script during the build process.
Tip
You can use the cookiecutter-e3-recipe template to scaffold a recipe repository with the standard structure and files.
Make it executable:
$ chmod +x recipe/build.sh
2.3 Create the e3 makefile¶
Create src/Makefile following the guidance in
Module build configurations. Start with the minimal
example and extend based on your module’s needs.
2.4 Create the conda recipe¶
Create recipe/meta.yaml with a minimal structure.
For background and all available fields, see conda-build’s documentation: Defining metadata (meta.yaml).
{% set name = "exampleModule" %}
{% set version = "1.0.0" %}
package:
name: {{ name|lower }}
version: {{ version }}
source:
- url: https://gitlab.esss.lu.se/epics-modules/{{ name }}/-/archive/v{{ version }}/{{ name }}-v{{ version }}.tar.gz
sha256: <checksum>
- path: ../src
build:
number: 0
run_exports:
- {{ pin_subpackage(name, max_pin='x.x.x') }}
requirements:
build:
- {{ compiler('cxx') }}
- {{ compiler('c') }} # If you are using .c sources
- {{ stdlib('c') }}
- make
- perl
host:
- epics-base
- require
test:
requires:
- run-iocsh
commands:
- run-iocsh -r {{ name|lower }}
about:
home: https://gitlab.esss.lu.se/epics-modules/exampleModule
license: BSD-3-Clause
license_file: LICENSE
summary: "EPICS example module"
Tip
Use jinja2 filters when upstream version format differs from the URL or tag format. This can be useful when upstream
uses a different version format like R1-2 or v1-2-3-4. For example:
{% set version = "1.2" %}
source:
url: .../-/archive/R{{ version | replace('.', '-') }}/...tar.gz
Note
Advanced testing:
The test: section can also include integration tests (pytest with run-iocsh/p4p) or C/C++ unit tests.
Simulated devices (e.g., using lewis) can be used for testing hardware behavior.
For an example with pytest integration tests, see displayform-recipe.
Add any needed site-specific files (IOC shell snippets, templates, patches) to
the src/ directory as described in
Module build recipes.
Tip
Compute the checksum from the exact tarball URL you use:
$ curl -L "https://gitlab.esss.lu.se/epics-modules/{{ name }}/-/archive/v{{ version }}/{{ name }}-v{{ version }}.tar.gz" | shasum -a 256
Important
Always specify the correct license. This is crucial for legal compliance and package distribution.
ESS recipe best practices¶
Prefer
source: urltarballs with asha256; use tags for traceability.Bind
versionto upstream version, using jinja2 filters for separator conversionVersion considerations:
Avoid pre-release versions (rc, dev, alpha, beta) in production environments
Be aware that conda treats
1.1rc1>1.0.0(may be prioritized unexpectedly)Prefer
>=1.0.0versions for production deployments when possible
Keep requirements minimal and in the correct layer:
build: compilers,
make,perlhost:
epics-base,require, and module-specific dependencies
Avoid version pins inside the recipe; rely on global pinning files where e3-pinning overrides conda-forge (see Pinning and variants).
Use
run_exportsonly when producing libraries consumed by others to ensure ABI stability.Always include
licenseandlicense_fileunderabout.Do not hardcode system paths in
build.shor Makefiles; use$(PREFIX).Don’t bundle vendor libraries with your package - create separate conda packages for these.
Increment build number when changing the recipe without changing upstream version.
Tests: Prefer
run-iocsh -r <module>overtest -f *.so; it dynamically loads the library. Addtest -fchecks for other key installed files as needed.
Contributing to conda-forge¶
If you’re packaging libraries, SDKs, or tools that could benefit users beyond the EPICS community, consider contributing them to conda-forge rather than keeping them in organization-specific channels.
Tip
Good candidates for conda-forge contributions include:
Vendor SDKs and hardware drivers with broad applicability
General-purpose scientific libraries
Development tools and utilities
EPICS modules themselves stay in e3-specific channels due to their specialized nature.
See conda-forge’s Contributing packages guide for details on the submission process.
2.5 Build and test¶
Local build¶
Create a clean build environment and run a local build:
$ conda create -n conda-build conda-build conda-verify
$ conda activate conda-build
$ conda build recipe
Tip
To catch overlinking and long-prefix path issues during local builds, add:
$ conda build --error-overlinking --no-long-test-prefix recipe
Tip
If a freshly published dependency is not being resolved during conda build, clear the local index cache and retry:
$ conda clean --index-cache
Artifacts are written to your conda-bld folder. You can install the fresh
build for local testing using --use-local (see below).
Build with pinning files (recommended)¶
For consistent builds across ESS infrastructure, apply variant pinning as described in Pinning and variants.
Test build using Docker¶
For production-like testing, use the ESS conda-build Docker image:
$ docker run --rm -v $(pwd):/workspace \
registry.esss.lu.se/ics-docker/conda-build:latest \
conda-build /workspace/recipe
Test the package¶
$ conda install --use-local examplemodule
$ iocsh -r examplemodule
Tip
You can also inspect installed files with ls or tree under your prefix.
Prefer declaring required files via your recipe rather than checking them ad-hoc.
Debugging builds¶
If builds fail, open an interactive debug environment to investigate:
$ conda debug recipe
This reproduces the build environment, allowing you to run build steps manually.
Tip
Recipe development is iterative: Creating conda recipes often involves trial and error, especially when adapting existing modules. Don’t expect the first attempt to work perfectly - iterate based on build logs and error messages.
2.6 Repository structure overview¶
Your final repository structure should look like:
$ tree
exampleModule-recipe/
├── LICENSE
├── README.md
├── recipe/
│ ├── meta.yaml
│ └── build.sh
└── src/
├── Makefile
├── iocsh/ # IOC shell snippets (if needed)
├── template/ # Database templates (if needed)
└── patches/ # Source patches (if needed)
Important
Only include directories and files that you actually use.
Note
For ESS-hosted recipes, include the standard CI configuration to build and release packages:
include:
- project: 'ics-infrastructure/gitlab-ci-yml'
file: 'E3CondaBuild.gitlab-ci.yml'
Summary¶
This tutorial showed the complete end-to-end workflow integrating concepts from previous chapters:
EPICS module creation using standard EPICS tools
Recipe repository setup with separation of concerns
Build script creation for conda-build integration
e3 integration using makefiles and recipes
Package building and testing with proper pinning
For production use, consider adding comprehensive tests and documentation.