7. Understanding module dependence¶
Lesson Overview¶
In this lesson, you will learn how to do the following:
Understand the environment variables used to manage dependencies
Understand how dependencies are determined at build-time
Install a new module version with different dependencies
Understand how incompatible dependencies can cause issues when loading an IOC
Dependent environment variables¶
Note
The versions of the modules mentioned below may be different that you will see in your environment, because the e3 tool that installs the modules packs gets the latest version of each module.
One of the key ideas with e3 (arguably, the key idea) is the management of dependencies of a given module in a common, structured way. That is, if a module A depends on a module B, then you should only need to load A; module B should be loaded and handled automatically (and so on, recursively). There are a few pieces that manage this.
Begin by switching to e3-stream/
. Run make vars
, and look at the variables
*DEP_VERSION
in the output:
[iocuser@host:e3-stream]$ make vars | grep DEP_VERSION
ASYN_DEP_VERSION = 4.42.0
CALC_DEP_VERSION = 3.7.4+1
EXPORT_VARS = E3_MODULES_VENDOR_LIBS_LOCATION E3_MODULES_INSTALL_LOCATION_LIB TEMP_CELL_PATH EPICS_HOST_ARCH EPICS_BASE MSI E3_MODULE_NAME E3_MODULE_VERSION E3_SITEMODS_PATH E3_SITEAPPS_PATH E3_SITELIBS_PATH E3_REQUIRE_MAKEFILE_INPUT_OPTIONS E3_REQUIRE_NAME E3_REQUIRE_CONFIG E3_REQUIRE_DB E3_REQUIRE_LOCATION E3_REQUIRE_DBD E3_REQUIRE_VERSION E3_REQUIRE_TOOLS E3_REQUIRE_INC E3_REQUIRE_LIB E3_REQUIRE_BIN QUIET PCRE_DEP_VERSION CALC_DEP_VERSION ASYN_DEP_VERSION
PCRE_DEP_VERSION = 8.44.0
These variables are defined and used, respectively, in configure/CONFIG_MODULE
and StreamDevice.Makefile
.
[iocuser@host:e3-stream]$ cat configure/CONFIG_MODULE | grep DEP_VERSION
ASYN_DEP_VERSION:=4.42.0
PCRE_DEP_VERSION:=8.44.0
CALC_DEP_VERSION:=3.7.4+1
[iocuser@host:e3-stream]$ cat StreamDevice.Makefile | grep DEP_VERSION
ifneq ($(strip $(ASYN_DEP_VERSION)),)
asyn_VERSION=$(ASYN_DEP_VERSION)
ifneq ($(strip $(PCRE_DEP_VERSION)),)
pcre_VERSION=$(PCRE_DEP_VERSION)
ifneq ($(strip $(CALC_DEP_VERSION)),)
calc_VERSION=$(CALC_DEP_VERSION)
Let us see where these variables are used. Run make build
and look for
(something like) the following in the output.
make[4]: Entering directory `/home/simonrose/data/git/e3/modules/core/e3-stream/StreamDevice/O.7.0.6.1_linux-x86_64'
/usr/bin/gcc -D_GNU_SOURCE -D_DEFAULT_SOURCE -DUSE_TYPED_RSET -D_X86_64_ -DUNIX -Dlinux -MD -O3 -g -Wall -Werror-implicit-function-declaration -mtune=generic -m64 -fPIC -I. -I../src/ -I.././src/ -I/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/pcre/8.44.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/calc/3.7.4+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/pcre/8.44.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/calc/3.7.4+0/include -I/epics/base-7.0.6.1/include -I/epics/base-7.0.6.1/include/compiler/gcc -I/epics/base-7.0.6.1/include/os/Linux -c .././src/StreamVersion.c
/usr/bin/g++ -D_GNU_SOURCE -D_DEFAULT_SOURCE -DUSE_TYPED_RSET -D_X86_64_ -DUNIX -Dlinux -MD -O3 -g -Wall -std=c++11 -std=c++11 -mtune=generic -m64 -fPIC -I. -I../src/ -I.././src/ -I/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/pcre/8.44.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/calc/3.7.4+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/pcre/8.44.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/calc/3.7.4+0/include -I/epics/base-7.0.6.1/include -I/epics/base-7.0.6.1/include/compiler/gcc -I/epics/base-7.0.6.1/include/os/Linux -c ../src/StreamFormatConverter.cc
/usr/bin/g++ -D_GNU_SOURCE -D_DEFAULT_SOURCE -DUSE_TYPED_RSET -D_X86_64_ -DUNIX -Dlinux -MD -O3 -g -Wall -std=c++11 -std=c++11 -mtune=generic -m64 -fPIC -I. -I../src/ -I.././src/ -I/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/pcre/8.44.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/calc/3.7.4+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/pcre/8.44.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/calc/3.7.4+0/include -I/epics/base-7.0.6.1/include -I/epics/base-7.0.6.1/include/compiler/gcc -I/epics/base-7.0.6.1/include/os/Linux -c ../src/StreamProtocol.cc
In particular, note the following segments:
-I/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include
-I/epics/base-7.0.6.1/require/4.0.0/siteMods/pcre/8.44.0+0/include
-I/epics/base-7.0.6.1/require/4.0.0/siteMods/calc/3.7.4+1/include
These variables are used by the build system to locate the necessary header
files. To see this, let us clear the definition of ASYN_DEP_VERSION
and re-run
the build.
[iocuser@host:e3-stream]$ echo "ASYN_DEP_VERSION:=" > configure/CONFIG_MODULE.local
[iocuser@host:e3-stream]$ make clean
[iocuser@host:e3-stream]$ make build
# --- snip snip ---
/usr/bin/g++ -D_GNU_SOURCE -D_DEFAULT_SOURCE -DUSE_TYPED_RSET -D_X86_64_ -DUNIX -Dlinux -MD -O3 -g -Wall -std=c++11 -std=c++11 -mtune=generic -m64 -fPIC -I. -I../src/ -I.././src/ -I/epics/base-7.0.6.1/require/4.0.0/siteMods/pcre/8.44.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/calc/3.7.4+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/pcre/8.44.0+0/include -I/epics/base-7.0.6.1/require/4.0.0/siteMods/calc/3.7.4+0/include -I/epics/base-7.0.6.1/include -I/epics/base-7.0.6.1/include/compiler/gcc -I/epics/base-7.0.6.1/include/os/Linux -c ../src/AsynDriverInterface.cc
../src/AsynDriverInterface.cc:41:24: fatal error: asynDriver.h: No such file or directory
#include "asynDriver.h"
^
compilation terminated.
If you look at this output, you will find that
-I/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include
now is missing.
At this point, the build system cannot find asynDriver.h
(which is used in
src/AsynDriverInterface.cc
). If you revert your changes and rebuild, then
everything should work correctly.
[iocuser@host:e3-stream]$ rm configure/CONFIG_MODULE.local
[iocuser@host:e3-stream]$ make clean
[iocuser@host:e3-stream]$ make build
Exercise
What is the purpose of creating the CONFIG_MODULE.local
file? Why do we modify
ASYN_DEP_VERSION
there instead of just modifying CONFIG_MODULE
?
Updating a dependency¶
Suppose that for some reason (perhaps due critical bugs in an IOC or some other
reason) we need to downgrade from StreamDevice 2.8.22
to 2.8.20
. When updating
a dependency, we also have to consider all of its dependencies (asyn, pcre,
and calc in this case), although we will focus only on asyn.
We first need to check that StreamDevice is compatible with the current version of asyn. It is often useful to check release notes in this case, but it is not a given that every possible combination has been tried, and this may require some testing on your part.
If the older version of StreamDevice and the old version of asyn are compatible, then all you need to do is what was done in Chapter 3.
Note
This assumes that the list of source files for StreamDevice have not changed.
If they have, you will have to modify StreamDevice.Makefile
to account for any
new or removed source files.
If the old version of StreamDevice is not compatible with asyn, then you
will need to install a different version of asyn in the current e3 environment.
The procedure for that is also the same as in Chapter 3.
We can start by checking the current version and seeing what is installed, and
then installing the necessary version (4.41.0
)
[iocuser@host:e3-asyn]$ make vars # Check the current version
[iocuser@host:e3-asyn]$ make existent
/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn
`-- 4.42.0+0
|-- asyn_meta.yaml
|-- db
|-- dbd
|-- include
`-- lib
[iocuser@host:e3-asyn]$ echo "EPICS_MODULE_TAG:=tags/R4-41" > configure/CONFIG_MODULE.local
[iocuser@host:e3-asyn]$ echo "E3_MODULE_VERSION:=4.41.0" >> configure/CONFIG_MODULE.local
[iocuser@host:e3-asyn]$ make vars
[iocuser@host:e3-asyn]$ make init patch build install # You can do these all at once
[iocuser@host:e3-asyn]$ make existent
/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn
|-- 4.41.0+0
| |-- asyn_meta.yaml
| |-- db
| |-- dbd
| |-- include
| `-- lib
`-- 4.42.0+0
|-- asyn_meta.yaml
|-- db
|-- dbd
|-- include
`-- lib
Note
Between asyn 4-41 and 4-42 there actually are some source and .dbd
files
that have been added; you may need to remove the following lines
SOURCES += drvPrologixGPIB.c
DBDS += drvPrologixGPIB.dbd
from asyn.Makefile
.
This will now allow us to update StreamDevice’s dependencies and install it
properly. Save the following as CONFIG_MODULE.local
in e3-stream/configure/
:
EPICS_MODULE_TAG:=2.8.22
E3_MODULE_VERSION:=2.8.23+0
ASYN_DEP_VERSION:=4.41.0
and then run
[iocuser@host:e3-stream]$ make vars # Again, a good sanity check
[iocuser@host:e3-stream]$ make init patch build install
[iocuser@host:e3-stream]$ make existent
/epics/base-7.0.6.1/require/4.0.0/siteMods/stream
`-- 2.8.22+0
|-- dbd
|-- include
|-- lib
|-- SetSerialPort.iocsh
`-- stream_meta.yaml
`-- 2.8.23+0
|-- dbd
|-- include
|-- lib
|-- SetSerialPort.iocsh
`-- stream_meta.yaml
Once you have installed a module, it is always a good idea to test that it can be loaded; this is a minimal test that a module must pass! You can do this by running any of the following:
[iocuser@host:~]$ iocsh -r stream,2.8.23
[iocuser@host:~]$ iocsh -r stream,2.8.22
[iocuser@host:~]$ iocsh -r stream
Exercises
Which version does the last one load, and why?
Which version of asyn is loaded in each case?
What happens if you run either of the following?
[iocuser@host:~]$ iocsh -r stream -r asyn [iocuser@host:~]$ iocsh -r asyn -r stream
Can you explain the result?
Where is the dependency information stored in the installed module?
Dependency resolution limitations¶
require is not perfect when it comes to dependency resolution. Consider the following file:
[iocuser@host:~]$ cat /epics/base-7.0.6.1/require/4.0.0/siteMods/stream/2.8.22+0/lib/linux-x86_64/stream.dep
# Generated file. Do not edit.
asyn 4.42.0+0
calc 3.7.4+1
pcre 8.44.0+0
This file is generated at build time, and lists the modules and versions that
StreamDevice depends on. require will attempt to load these modules (and
versions) when require stream
occurs during IOC startup. In that sense,
require does not so much resolve dependencies as it does load them.
If there are any conflicts in this process, then require will shut the IOC
down. This is what happens when you run iocsh -r asyn -r stream
above:
since no version is specified for asyn, the latest version (4.42.0
) is loaded.
Then when StreamDevice is loaded, it depends on asyn 4.42.0
. The loaded
version of asyn and the requested version of asyn differ, so require shuts
the IOC down.
This is a sneak-preview of so-called dependency hell; in this case, the
solution is simple. Since asyn is only really being loaded due to
StreamDevice’s dependency, the solution is to only load StreamDevice
directly and let it take care of loading the correct version of asyn. However,
what happens if you need to use StreamDevice, which depends on asyn 4.42.0
,
and a version of modbus which depends on a different version of asyn?
In general the best practice when writing a startup script for an IOC is only to require the top-level modules. So instead of
require asyn
require sequencer
require sscan
require calc
require stream
it is much simpler to have
require stream
The resulting startup script will be much more maintainable over time and less prone to errors when new module versions are installed.
To clean up the previous changes to your EPICs environment, you can run:
[iocuser@host:e3-stream]$ make uninstall
Exercise
Suppose that your IOC starts fine today with the startup script
require asyn
require stream
What simple and reasonable action could you take that would cause this IOC to fail on startup without changing this script? Why is this a problem from the perspective of the maintainer of a shared e3 environment?
Whence cometh the dependencies¶
We have so far only discussed the build-time dependencies. These are
dependencies that arise when compiling a module, indicated by the inclusion of a
C/C++
header file: consider the file AsynDriverInterface.cc
from
StreamDevice
// --- snip --
#include "asynDriver.h"
#include "asynOctet.h"
#include "asynInt32.h"
#include "asynUInt32Digital.h"
#include "asynGpibDriver.h"
// --- snip ---
When this file is compiled, it generates a .d
file that describes the
dependencies recognized by make
:
[iocuser@host:e3-stream]$ cat StreamDevice/O.7.0.6.1_linux-x86_64/AsynDriverInterface.d
AsynDriverInterface.o: ../src/AsynDriverInterface.cc \
# --- snip ---
/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include/asynAPI.h \
/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include/asynOctet.h \
/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include/asynInt32.h \
/epics/base-7.0.6.1/include/epicsTypes.h \
/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include/asynUInt32Digital.h \
/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include/asynGpibDriver.h \
/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include/asynDriver.h \
/epics/base-7.0.6.1/require/4.0.0/siteMods/asyn/4.42.0+0/include/asynInt32.h \
require uses this file to detect build-time dependencies.
Exercise
Similar to asyn, there are header files from calc and pcre that are included which result in them being build-time dependencies. Determine which files include header files from both calc and pcre.
Assignment¶
There is another kind of dependency, the run-time dependency. This is often
associated with dependencies such as StreamDevice where the dependency is
based on run-time functionality as connoted by the existence of .proto
(protocol) files. For example, consider the e3 module for communicating with a
FuG power supply: if you look
at the underlying module, you can
see that it includes such a protocol file,
fug.proto.
Clone the e3 wrapper above, and build and install it.
Look through temporary build files. Can you see where stream is referenced there?
How did
e3-fug
know that StreamDevice is a dependency? Hint: The configuration files are only part of the story.