From 809b78b85385b8e6eb69ff09afd169a2627d5664 Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Fri, 13 Dec 2024 10:08:08 -0300 Subject: [PATCH 01/12] base: copy ioc-specific utilities last. Scripts that are executed only during IOC build steps don't need to be copied early in the base image build. In addition, doing so invalidates cache whenever such scripts are touched. Install them in the base image in the last steps, so that cache can be more efficiently used, speeding up local and CI builds. --- base/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/Dockerfile b/base/Dockerfile index a012812..31e9aa2 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -35,7 +35,6 @@ RUN apt update -y && \ ca-certificates COPY lnls-get-n-unpack.sh /usr/local/bin/lnls-get-n-unpack -COPY lnls-run.sh /usr/local/bin/lnls-run ENV EPICS_IN_DOCKER=/opt/epics-in-docker RUN mkdir $EPICS_IN_DOCKER @@ -68,3 +67,5 @@ RUN $EPICS_IN_DOCKER/install_motor.sh ARG DEBIAN_VERSION COPY opcua_versions.sh install_opcua.sh $EPICS_IN_DOCKER RUN $EPICS_IN_DOCKER/install_opcua.sh + +COPY lnls-run.sh /usr/local/bin/lnls-run From aa28b9cbcd107a82513737670d857f969af1215f Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Thu, 4 Apr 2024 14:29:33 -0300 Subject: [PATCH 02/12] ioc: prune modules from non-static builds. Remove EPICS modules that do not have a dynamic library linked to any executables in the target IOC. This shrinks the final image size by removing useless artifacts. We introduce APP_DIRS (application directories) variable to specify paths where relevant binaries are, when no-build target is used, so the proper cleanup can happen in such cases as well; APP_DIRS replaces REPONAME for these cases, since the defined REPONAME was unused. For dynamic-build, it encodes any additional directories besides the base COPY'ed directory to be considered. Cleanup phase must be executed in the build stage (before the final copy), otherwise the COPY layer would still make the image size big. This implies `no-build` target needs to copy from a pruned version of the base image, which is a new stage. The list of modules to be removed is taken from the RELEASE file, so that it contains a valid mapping of all modules that have been installed. EPICS base is assumed to always be needed, even though some binaries in it can certainly be thrown away given the IOC linkage (for instance, PVAccess binaries when the IOC uses only Channel Access). Linkage information is taken from ldd(1), which assumes that inspected binaries are safe to be potentially executed. It is assumed that we are using glibc's ldd, which provides a straightforward way to query all binaries at once. Moreover, we ignore not-found library entries to properly cover binaries already shipped in repositories, such as libopcua.so, which might not contain (properly) defined rpaths. To avoid implementing a function that returns "all EPICS modules whose path is a prefix from the used library paths", we instead reuse `filter_out_paths()` by exploting the fact that it filters *out* everything we care about when given `linked_libs`. Taking its difference to the original set gives us what we need without implementing the variant of `filter_out_paths()`. The pruning script requires `set -E` in order to propagate errors from functions called inside subshells. --- Dockerfile | 13 +++- base/Dockerfile | 1 + base/lnls-prune-artifacts.sh | 101 ++++++++++++++++++++++++++ images/docker-compose-mca.yml | 2 +- images/docker-compose-motorpigcs2.yml | 2 +- images/docker-compose-opcua.yml | 2 +- images/docker-compose-pvagw.yml | 1 + 7 files changed, 118 insertions(+), 4 deletions(-) create mode 100755 base/lnls-prune-artifacts.sh diff --git a/Dockerfile b/Dockerfile index 29dd12c..63b3db4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,9 +42,17 @@ RUN ln -s ${ENTRYPOINT} ./entrypoint ENTRYPOINT ["./entrypoint"] +FROM build-image AS pruned-build + +ARG APP_DIRS +ARG RUNDIR + +RUN lnls-prune-artifacts ${APP_DIRS} ${RUNDIR} + + FROM base AS no-build -COPY --from=build-image /opt /opt +COPY --from=pruned-build /opt /opt FROM build-image AS build-stage @@ -70,11 +78,14 @@ RUN rm -rf .git/ FROM build-stage AS dynamic-build ARG JOBS=1 +ARG APP_DIRS ARG RUNDIR ARG SKIP_TESTS RUN make distclean && make -j ${JOBS} && make $([ "$SKIP_TESTS" != 1 ] && echo runtests) && make clean && make -C ${RUNDIR} +RUN lnls-prune-artifacts ${APP_DIRS} ${PWD} ${RUNDIR} + FROM base AS dynamic-link diff --git a/base/Dockerfile b/base/Dockerfile index 31e9aa2..4cdf643 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -68,4 +68,5 @@ ARG DEBIAN_VERSION COPY opcua_versions.sh install_opcua.sh $EPICS_IN_DOCKER RUN $EPICS_IN_DOCKER/install_opcua.sh +COPY lnls-prune-artifacts.sh /usr/local/bin/lnls-prune-artifacts COPY lnls-run.sh /usr/local/bin/lnls-run diff --git a/base/lnls-prune-artifacts.sh b/base/lnls-prune-artifacts.sh new file mode 100755 index 0000000..67ba66e --- /dev/null +++ b/base/lnls-prune-artifacts.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +set -Eeu + +# Filter out from the $1 list of paths any exact match of a parent directory +# from any path in the $2 exclude list. +# +# Both list are treated as newline-separated strings, and must consist +# exclusively of absolute paths. +filter_out_paths() { + list="$1" + exclude_list="$2" + + while read -r path; do + if [ "${path:0:1}" != "/" ]; then + >&2 echo "error: filter_out_paths() expects absolute paths, but got '$path'" + exit 1 + fi + + while [ "$path" != "/" ]; do + list=$(echo "$list" | grep -xv "$path") + + path=$(dirname "$path") + done + done <<< "$exclude_list" + + echo "$list" +} + +find_elf_executables() { + targets=$@ + + # Loop on entire lines to properly handle filenames with spaces + while read -r executable; do + read -r -N 4 magic < "$executable" + + # Output only ELF binaries + if [ "$magic" = $'\x7fELF' ]; then + echo $executable + fi + done < <(find $targets -type f -executable) +} + +find_linked_libraries() { + executables=$(find_elf_executables $@) + + # Depend on the glibc-specific behavior of supporting multiple executables + # to be queried at once + linked=$(ldd $executables 2>/dev/null | grep '=>') + + # We grep out not found libraries, since they cannot be kept if we don't + # know where they are. + # + # Final binary may be actually runnable, since rpath of another binary may + # pull those not-found libraries + found="$(echo "$linked" | grep -v "not found")" + + # Get their full path + libs=$(echo "$found" | cut -d' ' -f 3) + + echo "$libs" | sort -u +} + +get_all_epics_modules() { + release_defs=$(grep = ${EPICS_RELEASE_FILE} | cut -d'=' -f 2) + + echo "$release_defs" | grep $EPICS_MODULES_PATH +} + +get_used_epics_modules() { + linked_libs=$(find_linked_libraries $@) + all_modules=$(get_all_epics_modules) + + unused_modules=$(filter_out_paths "$all_modules" "$linked_libs") + + filter_out_paths "$all_modules" "$unused_modules" +} + +remove_unused_epics_modules() { + targets=$(echo $@ | sed -E "s|\s+|\n|g") + + all_modules=$(get_all_epics_modules) + used_modules=$(get_used_epics_modules $targets) + + keep_dirs="$targets $used_modules" + unused_modules=$(filter_out_paths "$all_modules" "$keep_dirs") + + # Assume module paths do not contain spaces, as we mostly control them + for module in $unused_modules; do + # if we already removed it because of its top-level repository or + # because it is an IOC, move on to the next. + [ ! -d $module ] && continue + + size=$(du -hs $module | cut -f 1) + + echo "Removing module '$module' ($size)..." + rm -rf $module + done +} + +remove_unused_epics_modules $@ diff --git a/images/docker-compose-mca.yml b/images/docker-compose-mca.yml index 66f3c48..5d571cb 100644 --- a/images/docker-compose-mca.yml +++ b/images/docker-compose-mca.yml @@ -8,6 +8,6 @@ services: labels: org.opencontainers.image.source: https://github.com/cnpem/epics-in-docker args: - REPONAME: mca + APP_DIRS: /opt/epics/modules/mca RUNDIR: /opt/epics/modules/mca/iocBoot/iocAmptek RUNTIME_PACKAGES: libpcap0.8 libnet1 libusb-1.0-0 diff --git a/images/docker-compose-motorpigcs2.yml b/images/docker-compose-motorpigcs2.yml index 26011df..c89eeb3 100644 --- a/images/docker-compose-motorpigcs2.yml +++ b/images/docker-compose-motorpigcs2.yml @@ -8,5 +8,5 @@ services: labels: org.opencontainers.image.source: https://github.com/cnpem/epics-in-docker args: - REPONAME: motorpigcs2 + APP_DIRS: /opt/epics/modules/motor/modules/motorPIGCS2 RUNDIR: /opt/epics/modules/motor/modules/motorPIGCS2/iocs/pigcs2IOC/iocBoot/iocPIGCS2 diff --git a/images/docker-compose-opcua.yml b/images/docker-compose-opcua.yml index 95dfc34..89c478e 100644 --- a/images/docker-compose-opcua.yml +++ b/images/docker-compose-opcua.yml @@ -8,6 +8,6 @@ services: labels: org.opencontainers.image.source: https://github.com/cnpem/epics-in-docker args: - REPONAME: opcua + APP_DIRS: /opt/epics/modules/opcua RUNDIR: /opt/epics/modules/opcua/iocBoot/iocUaDemoServer RUNTIME_PACKAGES: libxml2 libssl3 diff --git a/images/docker-compose-pvagw.yml b/images/docker-compose-pvagw.yml index 5d819d7..7761bda 100644 --- a/images/docker-compose-pvagw.yml +++ b/images/docker-compose-pvagw.yml @@ -8,5 +8,6 @@ services: labels: org.opencontainers.image.source: https://github.com/cnpem/epics-in-docker args: + APP_DIRS: /opt/epics/modules/p4p RUNDIR: /opt/epics/modules/p4p/bin/linux-x86_64 RUNTIME_PACKAGES: python3-numpy python3-ply From 54ce33aa03545ae03bc31005325038b852fafd21 Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Thu, 11 Apr 2024 15:37:19 -0300 Subject: [PATCH 03/12] ioc: remove static libraries from non-static builds. Static libraries are not used during runtime, and not even compile-time for non-static builds. On the other hand, they take up a large amount of storage: about 312MB for EPICS base and 222MB for modules. Remove them all right before finishing non-static build stages, avoiding their copy to IOC images. This is not needed for static-link targets, since only the cleaned IOC directory is copied, already leaving behind all static libraries. All files with .a are assumed to be static libraries, which should be a good assumption in GNU/Linux. Other static artifacts, such as object files, are correctly removed by the build system clean target and are not handled explictly here for simplification. A lint script for the build system can be implemented in the future if this eventually becomes false. Files installed into /usr/local aren't copied into the final IOC images yet. So adding it into the argument list doesn't alter the behavior for now, it is simply future proofing. --- base/lnls-prune-artifacts.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/base/lnls-prune-artifacts.sh b/base/lnls-prune-artifacts.sh index 67ba66e..b6a346b 100755 --- a/base/lnls-prune-artifacts.sh +++ b/base/lnls-prune-artifacts.sh @@ -98,4 +98,18 @@ remove_unused_epics_modules() { done } +remove_static_libraries() { + for target; do + libs=$(find $target -type f -name *.a) + + if [ -n "$libs" ]; then + size=$(du -hsc $libs | tail -n 1 | cut -f 1) + + echo "Removing static libraries from $target ($size)" + rm -f $libs + fi + done +} + remove_unused_epics_modules $@ +remove_static_libraries /opt /usr/local From d19a55d78db1a6d5b64a1c30eecfff8f35f7932e Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Thu, 11 Apr 2024 17:00:00 -0300 Subject: [PATCH 04/12] ioc: prune shared libraries from non-static builds. Shared libraries may be installed either in /opt or /usr/local/lib but may not be used by the target binaries from the IOC image. Remove them during the prune phase before copying both directories to resulting image to shrink the final image size. Both actual versioned binary and its symbolic links are removed to keep the filesystem consistent. When deciding the set of "used libraries", we assume all libraries inside any APP_DIRS are used. This allows one to specify a dlopen(3)'ed library path in APP_DIRS, and ensure it is kept in the resulting image. Like static libraries, dynamic libraries installed into /usr/local aren't copied into the final IOC images yet. So adding it into the argument list doesn't alter the behavior for now, it is simply future proofing. --- base/lnls-prune-artifacts.sh | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/base/lnls-prune-artifacts.sh b/base/lnls-prune-artifacts.sh index b6a346b..085634c 100755 --- a/base/lnls-prune-artifacts.sh +++ b/base/lnls-prune-artifacts.sh @@ -41,6 +41,12 @@ find_elf_executables() { done < <(find $targets -type f -executable) } +find_shared_libraries() { + elf_files=$(find_elf_executables $@) + + echo "$elf_files" | grep -E "\.so(.[0-9]+)*$" | sort -u +} + find_linked_libraries() { executables=$(find_elf_executables $@) @@ -111,5 +117,23 @@ remove_static_libraries() { done } +remove_unused_shared_libraries() { + target_libs=$(find_shared_libraries $@) + linked_libs=$(find_linked_libraries $@) + remove_libs=$(find_shared_libraries /opt /usr/local) + + for lib in $target_libs $linked_libs; do + remove_libs=$(echo "$remove_libs" | grep -vx $lib) + done + + for lib in $remove_libs; do + size=$(du -hs $lib | cut -f 1) + + echo "Removing shared library '$lib' ($size)" + rm -f ${lib%.so*}.so* + done +} + remove_unused_epics_modules $@ remove_static_libraries /opt /usr/local +remove_unused_shared_libraries $@ From 898c73cbbc08d7d74a7d0689f62aec8edd49f88a Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Tue, 16 Apr 2024 08:41:20 -0300 Subject: [PATCH 05/12] ioc: prune module directories from non-static builds. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modules may contain several artifacts, including configuration files, graphical interface files and other repository artifacts that do not need to be in the IOC image. Remove them all except the ones containing EPICS database (`.db`, `.template` and `.substitutions`), autosave requirement (`.req`), or command (`.cmd`) files, besides shared libraries. Binaries directory (`bin`) is also removed, as only $REPONAME and $RUNDIR should contain target executables, which are filtered out from the list. Command files are kept, because they might be snippets to be sourced by the IOC's own main command file. ACFs were considered, but ultimately considered unlikely to be used this way. Because we call prune_directories() directly (without a subshell), we must ensure `target` and `keep_paths` are created in the local scope, avoiding corrupting the callee variables. Co-authored-by: Érico Nogueira --- base/lnls-prune-artifacts.sh | 54 +++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/base/lnls-prune-artifacts.sh b/base/lnls-prune-artifacts.sh index 085634c..712953c 100755 --- a/base/lnls-prune-artifacts.sh +++ b/base/lnls-prune-artifacts.sh @@ -82,25 +82,53 @@ get_used_epics_modules() { filter_out_paths "$all_modules" "$unused_modules" } -remove_unused_epics_modules() { +prune_directories() { + local targets="$1" + local keep_paths="$2" + + remove_dirs=$(filter_out_paths "$targets" "$keep_paths") + + while read -r remove_dir; do + # if we already removed it because of its parent directory, move on to + # the next. + [ ! -d "$remove_dir" ] && continue + + size=$(du -hs "$remove_dir" | cut -f 1) + + echo "Removing directory '$remove_dir' ($size)..." + rm -rf "$remove_dir" + done <<< "$remove_dirs" +} + +prune_module_directories() { + module=$1 + + module_dirs=$(find $module -type d) + keep_paths=$(cat << EOF +$(find_shared_libraries $module) +$(find $module -type f -regex ".*\.\(cmd\|db\|template\|req\|substitutions\)" -printf "%h\n" | sort -u) +EOF + ) + + prune_directories "$module_dirs" "$keep_paths" +} + +clean_up_epics_modules() { targets=$(echo $@ | sed -E "s|\s+|\n|g") all_modules=$(get_all_epics_modules) used_modules=$(get_used_epics_modules $targets) - keep_dirs="$targets $used_modules" - unused_modules=$(filter_out_paths "$all_modules" "$keep_dirs") - - # Assume module paths do not contain spaces, as we mostly control them - for module in $unused_modules; do - # if we already removed it because of its top-level repository or - # because it is an IOC, move on to the next. - [ ! -d $module ] && continue + keep_paths=$(printf "$targets\n$used_modules") + prune_directories "$all_modules" "$keep_paths" - size=$(du -hs $module | cut -f 1) + # Filter out targets to provide a way to disable module pruning in special + # cases + prune_dirs=$(filter_out_paths "$used_modules" "$targets") - echo "Removing module '$module' ($size)..." - rm -rf $module + for dir in $prune_dirs; do + echo "Pruning module '$dir'..." + prune_module_directories $dir done } @@ -134,6 +162,6 @@ remove_unused_shared_libraries() { done } -remove_unused_epics_modules $@ +clean_up_epics_modules $@ remove_static_libraries /opt /usr/local remove_unused_shared_libraries $@ From dd7394dcfad8489756d9d8a43dda08d003df415d Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Tue, 16 Apr 2024 13:53:16 -0300 Subject: [PATCH 06/12] ioc: prune EPICS base directories from non-static builds. EPICS base has rather large configuration files for build, and other repository files, which are not needed in the IOC images. Prune them after building the IOCs, shrinking by 40MB the final image size. Prune is performed with the same script as modules, which discards all executables in `bin` (~15MB), as well as Perl scripts. This should be fine considering that `static-link` target also does not preserve EPICS binaries in the resulting image. It is also susceptible to be skipped during module directory pruning if a target specifies it. This may be useful if we ever consider making a derivative image only with its tools. --- base/lnls-prune-artifacts.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/lnls-prune-artifacts.sh b/base/lnls-prune-artifacts.sh index 712953c..6058234 100755 --- a/base/lnls-prune-artifacts.sh +++ b/base/lnls-prune-artifacts.sh @@ -71,6 +71,8 @@ get_all_epics_modules() { release_defs=$(grep = ${EPICS_RELEASE_FILE} | cut -d'=' -f 2) echo "$release_defs" | grep $EPICS_MODULES_PATH + + echo $EPICS_BASE_PATH } get_used_epics_modules() { From 006c9ec71f5af99ebdeef1429a42101e96d2e57e Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Thu, 14 Nov 2024 17:03:31 -0300 Subject: [PATCH 07/12] ioc: don't prune pre-selected directories. When pruning modules, some of them might contain special cases where it is harder to automatically detect in the pruning algorithm. While users could workaround this by specifying such special paths under APP_DIRS, this can be handled here by adding extra tooling to be used by modules. Introduce a way for modules to specify extra paths that must be kept, besides those detected by lnls-prune-artifacts. Do this through a .lnls-keep-paths file that specifies all relative paths (directories or files) whose contents must be kept. Paths may be globs, so that they are resolved based on their bash expansion, avoiding hard-coded options when ambiguity can be automatically resolved. These exceptions are considered for all ancestors of a candidate to removal, so that they are not restricted to EPICS modules themselves. This eases implementation because we won't need to detect which parent is a module and should contain .lnls-keep-paths, and instead simply traverse the whole ancestor list looking if any of them defines one. --- base/lnls-prune-artifacts.sh | 40 +++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/base/lnls-prune-artifacts.sh b/base/lnls-prune-artifacts.sh index 6058234..81f7f59 100755 --- a/base/lnls-prune-artifacts.sh +++ b/base/lnls-prune-artifacts.sh @@ -84,6 +84,34 @@ get_used_epics_modules() { filter_out_paths "$all_modules" "$unused_modules" } +# Traverse ancestor directories of each provided path, and concatenate all +# their .lnls-keep-paths defined entries as absolute paths. +get_defined_paths_to_keep() { + for path; do + if [ "${path:0:1}" != "/" ]; then + >&2 echo "error: get_defined_paths_to_keep() expects absolute paths, but got '$path'" + exit 1 + fi + + while true; do + keep_path_file="$path/.lnls-keep-paths" + + if [ -f "$keep_path_file" ]; then + keep_paths=$(cat "$keep_path_file") + + for keep_path in $keep_paths; do + # output it as an absolute path + realpath "$path"/$keep_path + done + fi + + [ "$path" == "/" ] && break + + path=$(dirname $path) + done + done | sort -u +} + prune_directories() { local targets="$1" local keep_paths="$2" @@ -109,6 +137,7 @@ prune_module_directories() { keep_paths=$(cat << EOF $(find_shared_libraries $module) $(find $module -type f -regex ".*\.\(cmd\|db\|template\|req\|substitutions\)" -printf "%h\n" | sort -u) +$(get_defined_paths_to_keep $module) EOF ) @@ -156,11 +185,16 @@ remove_unused_shared_libraries() { remove_libs=$(echo "$remove_libs" | grep -vx $lib) done + keep_paths=$(get_defined_paths_to_keep $remove_libs) + for lib in $remove_libs; do - size=$(du -hs $lib | cut -f 1) + # if library is not found inside any $keep_dirs, remove it + if find $keep_paths -path "$lib" -exec false {} +; then + size=$(du -hs $lib | cut -f 1) - echo "Removing shared library '$lib' ($size)" - rm -f ${lib%.so*}.so* + echo "Removing shared library '$lib' ($size)" + rm -f ${lib%.so*}.so* + fi done } From db0430150622ea4e81709d3c2137fdb3bb785ec4 Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Thu, 14 Nov 2024 17:33:16 -0300 Subject: [PATCH 08/12] base: don't prune pyDevSup custom python libraries. Some of the dynamic libraries built by pyDevSup are not directly linked into the IOC binary, but rather dlopen(3)'ed by Python interpreter when a "import" statement is reached. To avoid having all users of pyDevSup manually specify which ones to keep, define everything inside python3./linux- to be kept by lnls-prune-artifacts. We use globs here (whose expansion is deferred) to avoid changing the minor version every distro upgrade. --- base/install_modules.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/base/install_modules.sh b/base/install_modules.sh index de211f9..91aec84 100755 --- a/base/install_modules.sh +++ b/base/install_modules.sh @@ -96,6 +96,7 @@ echo PYTHON=python3 >> pyDevSup/configure/CONFIG_SITE install_module pyDevSup PYDEVSUP " EPICS_BASE " +echo 'python3*/linux*/' > pyDevSup/.lnls-keep-paths mkdir snmp cd snmp From c1fa63739ca79fea6567c9c4aef4c69ae63b3e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Nogueira?= Date: Mon, 9 Dec 2024 12:43:20 -0300 Subject: [PATCH 09/12] images: fix pvagw image. pvagw isn't interactive, so we should use it as the entrypoint directly. Also fix missing runtime package. --- images/docker-compose-pvagw.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/images/docker-compose-pvagw.yml b/images/docker-compose-pvagw.yml index 7761bda..775b781 100644 --- a/images/docker-compose-pvagw.yml +++ b/images/docker-compose-pvagw.yml @@ -10,4 +10,5 @@ services: args: APP_DIRS: /opt/epics/modules/p4p RUNDIR: /opt/epics/modules/p4p/bin/linux-x86_64 - RUNTIME_PACKAGES: python3-numpy python3-ply + ENTRYPOINT: ./pvagw + RUNTIME_PACKAGES: python3-numpy python3-ply libevent-pthreads-2.1-7 From de46cffd8b1ce3bfad82a0c34332adeaefbb1774 Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Fri, 13 Dec 2024 15:57:26 -0300 Subject: [PATCH 10/12] ioc: allow skipping all pruning steps. Pruning artifacts embeds several heuristics about what is worth keeping, and what should be removed after build. Such predefined pruning steps might miss corner cases where defining extra APP_DIRS is not enough. Introduce a knob to skip the pruning steps entirely to give users a immediate last resort option to use before a corrected version is eventually released. --- Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 63b3db4..71dcc4e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,8 +46,9 @@ FROM build-image AS pruned-build ARG APP_DIRS ARG RUNDIR +ARG SKIP_PRUNE -RUN lnls-prune-artifacts ${APP_DIRS} ${RUNDIR} +RUN if [ "$SKIP_PRUNE" != 1 ]; then lnls-prune-artifacts ${APP_DIRS} ${RUNDIR}; fi FROM base AS no-build @@ -81,10 +82,11 @@ ARG JOBS=1 ARG APP_DIRS ARG RUNDIR ARG SKIP_TESTS +ARG SKIP_PRUNE RUN make distclean && make -j ${JOBS} && make $([ "$SKIP_TESTS" != 1 ] && echo runtests) && make clean && make -C ${RUNDIR} -RUN lnls-prune-artifacts ${APP_DIRS} ${PWD} ${RUNDIR} +RUN if [ "$SKIP_PRUNE" != 1 ]; then lnls-prune-artifacts ${APP_DIRS} ${PWD} ${RUNDIR}; fi FROM base AS dynamic-link From 0639e2adb5987ee282cefec3fac7706af97751c0 Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Tue, 17 Dec 2024 10:33:16 -0300 Subject: [PATCH 11/12] README: document pruning mechanism. Users should know that an automatic step is executed to make the image small, because this might break their application in (hopefully) rare situations. Also teach them it is possible to extend the paths where important binaries are, and also skip pruning entirely if needed. --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6a21b7d..427e5d7 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,24 @@ services: By default, the IOC will be built with statically linked EPICS libraries. If you **need** to link them dynamically, you must define the build target as -`dynamic-link`. This will increase the resulting image size, since unused -dependencies will also be copied. +`dynamic-link`. + +For non-static builds, an automatic pruning step is executed to make the IOC +image size smaller by removing unused artifacts. This step preserves all ELF +executables inside `RUNDIR` and the IOC repository, as well as their dependent +EPICS modules based on the linkage information. Source code, GUI files, and +other directories in used modules are assumed not to be needed at runtime and +also removed. Additional paths can be provided in `args` under the `APP_DIRS` +key to extend the list of where used applications and shared libraries are, +including any `dlopen(3)`ed libraries. If the resulting image fails at runtime, +a careful look at error messages might provide clues for directories to add to +`APP_DIRS`; if that isn't possible, or if it's believed that the directories +added to `APP_DIRS` should have been detected automatically, please open an +issue. Please do so as well if the pruning step errors out during the build. In +case it is necessary to build a working image before these issues can be fixed, +the pruning step can be skipped entirely by setting the `SKIP_PRUNE=1` +argument; note that this is highly discouraged, because it increases the image +size significantly. The resulting image contains a standard IOC run script, `lnls-run`, which will be run inside `RUNDIR` and will launch the container's command under procServ, From 234033df48510d6e2ce2074cf8cc42c9a4f27420 Mon Sep 17 00:00:00 2001 From: "Henrique F. Simoes" Date: Wed, 18 Dec 2024 11:06:37 -0300 Subject: [PATCH 12/12] CHANGES: mention pruning mechanism. --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index ccf8a79..af6c599 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,10 @@ * base: update to Debian 12. by @ericonr in https://github.com/cnpem/epics-in-docker/pull/84 * Refer to up to date README for new/updated `RUNTIME_PACKAGES`. +* Prune unused artifacts from non-static builds by @henriquesimoes in + https://github.com/cnpem/epics-in-docker/pull/59 + * Refer to README for instructions on how to configure this procedure when + needed. ## v0.12.0