blob: 9bc4be633e27db421d6125261ab7e83c1d91ba0c [file] [log] [blame]
#!/bin/sh
#
# build_module.sh (c) NGINX, Inc., Liam Crilly <liam.crilly@nginx.com>
#
# This script supports apt(8) and yum(8) package managers. Installs the minimum
# necessary prerequisite packages to build 3rd party modules for NGINX Plus.
# Obtains source for module and NGINX OSS, prepares for pkg-oss tool. Inspects
# module configuration and attempts to rewrite for dynamic build if necessary.
# Obtains pkg-oss tool, creates packaging files and copies in module source.
#
# CHANGELOG
# v0.18 [29-Apr-2021] Added -V option to specify module version
# v0.17 [11-Nov-2020] Fixed bashisms and made /bin/sh default interpreter
# v0.16 [09-Nov-2020] Added Alpine Linux packaging
# v0.15 [03-Nov-2020] use latest version tag if -v is specified
# use HTTPS while fetching sources
# v0.14 [02-Nov-2020] sudo is not mandatory anymore
# update repo caches prior to dependencies installs
# do not install suggested/recommended packages on debian-based distros
# xmllint/xsltproc are added to build dependencies
# exit with code 1 when module build failed
# v0.13 [12-Oct-2020] adjusted for refactored package tooling
# -o option made de-facto mandatory with preconfigured default
# v0.12 [30-Aug-2017] -o option to specify destination for package files
# -y (--non-interactive) option for automated builds
# v0.11 [20-Jun-2017] Enforces NGINX versions that support dynamic modules
# v0.10 [27-Apr-2017] Fixed postinstall banner, improved .so filename detection,
# -v option for specifying OSS build/version
# v0.9 [10-Apr-2017] @defan patch, improved postinstall banner, added disclaimer
# v0.8 [30-Mar-2017] Package version is now tied to base OSS version instead of 0.01
# v0.7 [29-Mar-2017] Added RPM packaging, flexible command line options with defaults
# v0.6 [16-Feb-2017] Using pkg-oss tool instead of only compiling .so files
OUTPUT_DIR="`pwd`/build-module-artifacts"
cat << __EOF__
DISCLAIMER DISCLAIMER DISCLAIMER DISCLAIMER DISCLAIMER DISCLAIMER DISCLAIMER
This script is provided as a demonstration of how to use the NGINX pkg-oss
tooling to compile and package a dynamic module for NGINX and NGINX Plus.
It will produce an installable package with the correct dependency on the
NGINX version used for the build so that upgrades will not lead to mismatch
between NGINX and the module. When using this script please bear in mind:
- It will not work for every module, check for module prerequisites.
- The installable packages are not intended for redistribution.
- Seamless upgrades with dependency matching require a yum/apt repository.
__EOF__
#
# Check command line parameters
#
ME=`basename $0`
if [ $# -eq 0 ]; then
echo "USAGE: $ME [options] <URL | path to module source>"
echo ""
echo " URL may be Github clone or download link, otherwise 'tarball' is assumed."
echo " Options:"
echo " -n | --nickname <word> # Used for packaging, lower case alphanumeric only"
echo " -V | --module-version # Module version (default is 1.0-1)"
echo " -s | --skip-depends # Skip dependecies check/install"
echo " -y | --non-interactive # Automatically install dependencies and overwrite files"
echo " -f | --force-dynamic # Attempt to convert static configuration to dynamic module"
echo " -r <NGINX Plus release number> # Build against the corresponding OSS version for this release"
echo " -v [NGINX OSS version number] # Build against this OSS version [current mainline] (default)"
echo " -o <package output directory> # Create package(s) in this directory (default: $OUTPUT_DIR)"
echo ""
exit 1
fi
#
# Process command line options
#
CHECK_DEPENDS=1
SAY_YES=""
COPY_CMD="cp -i"
DO_DYNAMIC_CONVERT=0
MODULE_NAME=""
MODULE_VERSION="1.0"
MODULE_RELEASE="1"
BUILD_PLATFORM=OSS
while [ $# -gt 1 ]; do
case "$1" in
"-s" | "--skip-depends")
CHECK_DEPENDS=0
shift
;;
"-y" | "--non-interactive")
SAY_YES="-y"
COPY_CMD="cp -f"
shift
;;
"-f" | "--force-dynamic")
DO_DYNAMIC_CONVERT=1
shift
;;
"-n" | "--nickname" )
MODULE_NAME=$2
shift; shift
;;
"-V" | "--module-version" )
MODULE_VERSION="${2%-*}"
if [ "${2#*-}" != "$2" ]; then
MODULE_RELEASE="${2#*-}"
fi
shift; shift
;;
"-r")
BUILD_PLATFORM=Plus
if [ `echo -n $2 | tr -d '[0-9p]' | wc -c` -gt 0 ]; then
echo "$ME: ERROR: NGINX Plus release must be in the format NN[pN] - quitting"
exit 1
elif [ "`echo "10^$2" | tr '^' '\n' | sort -nr | head -1`" = "10" ]; then
echo "$ME: ERROR: NGINX Plus release must be at least 11 to support dynamic modules - quitting"
exit 1
fi
PLUS_REL=$2
shift; shift
;;
"-v")
BUILD_PLATFORM=OSS
if [ `echo -n .$2 | tr -d '[0-9\.]' | wc -c` -eq 0 ]; then
OSS_VER=$2
shift
fi
if [ `echo "1.11.4^$OSS_VER" | tr '^' '\n' | tr '.' ',' | sort -nr | head -1` = "1,11,4" ]; then
echo "$ME: ERROR: NGINX version must be at least 1.11.5 to support dynamic modules - quitting"
exit 1
fi
shift
;;
"-o")
OUTPUT_DIR=`realpath $2`
if [ $? -ne 0 ]; then
echo "$ME: ERROR: Could not access output directory $2 - quitting"
exit 1
fi
shift; shift
;;
*)
echo "$ME: ERROR: Invalid command line option ($1) - quitting"
exit 1
;;
esac
done
#
# Create package output directory
#
if [ ! -d $OUTPUT_DIR ]; then
mkdir -p $OUTPUT_DIR
if [ $? -ne 0 ]; then
echo "$ME: ERROR: Could not create output directory $OUTPUT_DIR - quitting"
exit 1
fi
fi
#
# Locate/select package manager and configure
#
if [ `whereis yum 2>/dev/null | grep -c "^yum: /"` -eq 1 ]; then
PKG_MGR_INSTALL="yum install $SAY_YES"
PKG_MGR_UPDATE="yum makecache"
PKG_FMT=rpm
NGINX_PACKAGES="pcre2-devel pcre-devel zlib-devel openssl-devel"
DEVEL_PACKAGES="rpm-build libxml2 libxslt"
PACKAGING_ROOT=pkg-oss/rpm/
PACKAGING_DIR=rpm/SPECS
PACKAGE_SOURCES_DIR=../SOURCES
PACKAGE_OUTPUT_DIR=RPMS
elif [ `whereis apt-get 2>/dev/null | grep -c "^apt-get: /"` -eq 1 ]; then
PKG_MGR_INSTALL="apt-get --no-install-suggests --no-install-recommends install $SAY_YES"
PKG_MGR_UPDATE="apt-get update"
PKG_FMT=deb
NGINX_PACKAGES="libpcre2-dev libpcre3-dev zlib1g-dev libssl-dev"
DEVEL_PACKAGES="devscripts fakeroot debhelper dpkg-dev quilt lsb-release build-essential libxml2-utils xsltproc"
PACKAGING_ROOT=pkg-oss/debian/
PACKAGING_DIR=debian
PACKAGE_SOURCES_DIR=extra
PACKAGE_OUTPUT_DIR="debuild-module-*/"
elif [ `apk --version | grep -c "^apk-tools"` -eq 1 ]; then
PKG_MGR_INSTALL="apk add"
PKG_MGR_UPDATE="apk update"
PKG_FMT=apk
NGINX_PACKAGES="linux-headers openssl-dev pcre2-dev pcre-dev zlib-dev"
DEVEL_PACKAGES="openssl abuild musl-dev"
PACKAGING_ROOT=pkg-oss/alpine/
PACKAGING_DIR=alpine
PACKAGE_SOURCES_DIR=src
else
echo "$ME: ERROR: Could not locate a supported package manager - quitting"
exit 1
fi
if [ $CHECK_DEPENDS = 1 ]; then
if [ `id -u` -ne 0 ]; then
echo "$ME: INFO: testing sudo"
sudo pwd > /dev/null
if [ $? -ne 0 ]; then
echo "ERROR: sudo failed. If you do not have sudo credentials then try using the '--skip-depends' option. Quitting."
exit 1
else
SUDO=sudo
fi
fi
echo "$ME: INFO: checking for dependent packages"
CORE_PACKAGES="gcc make unzip wget mercurial"
if [ "${1##*.}" = "git" ]; then
CORE_PACKAGES="$CORE_PACKAGES git"
fi
$SUDO $PKG_MGR_UPDATE
$SUDO $PKG_MGR_INSTALL $CORE_PACKAGES $NGINX_PACKAGES $DEVEL_PACKAGES
fi
#
# Ask for a nickname if we did't get one on the command line
#
if [ "$MODULE_NAME" = "" ]; then
#
# Construct a reasonable nickname from the module source location
#
MODULE_NAME=`basename $1 | tr '[:blank:][:punct:]' '\n' | tr '[A-Z]' '[a-z]' | grep -ve nginx -e ngx -e http -e stream -e module -e plus -e tar -e zip -e gz -e git | tr -d '\n'`
if [ -z "$SAY_YES" ]; then
echo -n "$ME: INPUT: Enter module nickname [$MODULE_NAME]: "
read -r REPLY
if [ "$REPLY" != "" ]; then
MODULE_NAME=$REPLY
fi
else
echo "$ME: INFO: using \"$MODULE_NAME\" as module nickname"
fi
fi
#
# Sanitize module nickname (this is a debbuild requirement, probably needs to check for more characters)
#
while true; do
MODULE_NAME_CLEAN=`echo $MODULE_NAME | tr '[A-Z]' '[a-z]' | tr -d '[/_\-\.\t ]'`
if [ "$MODULE_NAME_CLEAN" != "$MODULE_NAME" ] || [ -z $MODULE_NAME ]; then
echo "$ME: WARNING: Removed illegal characters from module nickname - using \"$MODULE_NAME_CLEAN\""
if [ -z $SAY_YES ]; then
echo -n "$ME: INPUT: Confirm module nickname [$MODULE_NAME_CLEAN]: "
read -r MODULE_NAME
if [ "$MODULE_NAME" = "" ]; then
MODULE_NAME=$MODULE_NAME_CLEAN
fi
else
MODULE_NAME=$MODULE_NAME_CLEAN
break
fi
else
break
fi
done
#
# Create temporary build area, with working copy of module source
#
BUILD_DIR=/tmp/$ME.$$
MODULE_DIR=$BUILD_DIR/$MODULE_NAME
echo "$ME: INFO: Creating $BUILD_DIR build area"
mkdir $BUILD_DIR
if [ -d $1 ]; then
mkdir -v $MODULE_DIR
echo "$ME: INFO: Building $MODULE_NAME from $MODULE_DIR"
cp -a $1/* $MODULE_DIR
else
#
# Module sources string is not a local directory so assume it is a URL.
# Obtain the sources in the best way for the suffix provided.
#
case "${1##*.}" in
"git")
echo "$ME: INFO: Cloning module source"
git clone --recursive $1 $MODULE_DIR
;;
"zip")
echo "$ME: INFO Downloading module source"
wget -O $BUILD_DIR/module.zip $1
ARCHIVE_DIR=`zipinfo -1 $BUILD_DIR/module.zip | head -n 1 | cut -f1 -d/`
unzip $BUILD_DIR/module.zip -d $BUILD_DIR
mv $BUILD_DIR/$ARCHIVE_DIR $MODULE_DIR
;;
*)
echo "$ME: INFO Downloading module source"
# Assume tarball of some kind
wget -O $BUILD_DIR/module.tgz $1
ARCHIVE_DIR=`tar tfz $BUILD_DIR/module.tgz | head -n 1 | cut -f1 -d/`
cd $BUILD_DIR
tar xfz module.tgz
mv $ARCHIVE_DIR $MODULE_DIR
cd -
;;
esac
fi
#
# Check the module sources look OK
#
if [ ! -f $MODULE_DIR/config ]; then
echo "$ME: ERROR: Cannot locate module config file - quitting"
exit 1
fi
#
# Check/convert module config
#
if [ `grep -c "\.[[:space:]]auto/module" $MODULE_DIR/config` -eq 0 ]; then
if [ $DO_DYNAMIC_CONVERT = 1 ]; then
echo "$ME: WARNING: This is a static module, attempting to convert to dynamic (experimental)"
grep -ve HTTP_MODULES -e STREAM_MODULES -e NGX_ADDON_SRCS $MODULE_DIR/config > $MODULE_DIR/config.dynamic
echo "ngx_module_name=`grep ngx_addon_name= $MODULE_DIR/config | cut -f2 -d=`" >> $MODULE_DIR/config.dynamic
if [ `grep -c "HTTP_AUX_FILTER_MODULES=" $MODULE_DIR/config` -gt 0 ]; then
echo "ngx_module_type=HTTP_AUX_FILTER" >> $MODULE_DIR/config.dynamic
elif [ `grep -c "STREAM_MODULES=" $MODULE_DIR/config` -gt 0 ]; then
echo "ngx_module_type=Stream" >> $MODULE_DIR/config.dynamic
else
echo "ngx_module_type=HTTP" >> $MODULE_DIR/config.dynamic
fi
echo "ngx_module_srcs=\"`grep NGX_ADDON_SRCS= $MODULE_DIR/config | cut -f2 -d\\" | sed -e 's/^\$NGX_ADDON_SRCS \(\$ngx_addon_dir\/.*$\)/\1/'`\"" >> $MODULE_DIR/config.dynamic
echo ". auto/module" >> $MODULE_DIR/config.dynamic
mv $MODULE_DIR/config $MODULE_DIR/config.static
cp $MODULE_DIR/config.dynamic $MODULE_DIR/config
else
echo "$ME: ERROR: This is a static module and should be updated to dynamic configuration. To attempt automatic conversion to dynamic module configuration use the '--force-dynamic' option. This will not modify the original configuration. Quitting."
exit 1
fi
fi
#
# Get the internal module name(s) from the module config so we can write
# the .so files into the postinstall banner.
#
touch $BUILD_DIR/postinstall.txt
for MODULE_SO_NAME in $(grep ngx_module_name= $MODULE_DIR/config | cut -f2 -d= | cut -f2 -d\"); do
if [ "`echo $MODULE_SO_NAME | cut -c1`" = "$" ]; then
# Dereference variable
SOURCE_VAR=`echo $MODULE_SO_NAME | cut -f2 -d\$`
MODULE_SO_NAME=`grep $SOURCE_VAR= $MODULE_DIR/config | cut -f2 -d= | cut -f2 -d\"`
fi
# Only write load_module line when no backslash present (can't cope with multi-line values)
echo $MODULE_SO_NAME | grep -c '\\' > /dev/null
if [ $? -eq 1 ]; then
echo " load_module modules/$MODULE_SO_NAME.so;" >> $BUILD_DIR/postinstall.txt
fi
done
if [ ! -s $BUILD_DIR/postinstall.txt ]; then
# Didn't find any .so names so this is a final attempt to extract from config file
MODULE_SO_NAME=`grep ngx_addon_name= $MODULE_DIR/config | cut -f2 -d= | cut -f2 -d\"`
echo " load_module modules/$MODULE_SO_NAME.so;" >> $BUILD_DIR/postinstall.txt
fi
#
# Get NGINX OSS packaging tool
#
echo "$ME: INFO: Downloading NGINX packaging tool"
cd $BUILD_DIR
PKG_OSS_URL="https://hg.nginx.org/pkg-oss"
if [ "$PKG_FMT" = "rpm" ]; then
if [ `rpm --eval "0%{?rhel}"` -lt 8 ] || [ `rpm --eval "0%{?amzn}"` -le 2 ]; then
PKG_OSS_URL="http://hg.nginx.org/pkg-oss"
fi
fi
hg clone $PKG_OSS_URL
if [ "$BUILD_PLATFORM" = "OSS" ]; then
if [ "$OSS_VER" != "" ]; then
( cd pkg-oss && hg update `hg tags | grep "^$OSS_VER" | head -1 | awk '{print $1}'` )
fi
else
( cd pkg-oss && hg update target-plus-r$PLUS_REL )
fi
cd pkg-oss/$PACKAGING_DIR
if [ $? -ne 0 ]; then
echo "$ME: ERROR: Unable to obtain NGINX packaging tool - quitting"
exit 1
fi
#
# Archive the module source for use with packaging tool using the base OSS version
#
cd $BUILD_DIR
if [ -d pkg-oss/contrib ]; then
VERSION=`grep "^NGINX_VERSION :=" pkg-oss/contrib/src/nginx/version | cut -f2 -d= | tr -d "[:blank:]"`
else
VERSION=`grep "^BASE_VERSION=" pkg-oss/$PACKAGING_DIR/Makefile | cut -f2 -d= | tr -d "[:blank:]"`
fi
echo "$ME: INFO: Archiving module source for $VERSION"
mv $MODULE_NAME $MODULE_NAME-$VERSION
tar cf - $MODULE_NAME-$VERSION | gzip -1 > $MODULE_NAME-$VERSION.tar.gz
if [ -d pkg-oss/contrib ]; then
cp $MODULE_NAME-$VERSION.tar.gz pkg-oss/contrib/tarballs/
else
cp $MODULE_NAME-$VERSION.tar.gz $OLDPWD/$PACKAGE_SOURCES_DIR/
fi
cd -
echo "$ME: INFO: Creating changelog"
cd $BUILD_DIR
cat << __EOF__ >pkg-oss/docs/nginx-module-$MODULE_NAME.xml
<?xml version="1.0" ?>
<!DOCTYPE change_log SYSTEM "changes.dtd" >
<change_log title="nginx_module_$MODULE_NAME">
<changes apply="nginx-module-$MODULE_NAME" ver="$MODULE_VERSION" rev="$MODULE_RELEASE" basever="$VERSION"
date="`date '+%Y-%m-%d'`" time="`date '+%H:%M:%S %z'`"
packager="Build Script &lt;build.script@example.com&gt;">
<change>
<para>
initial release of $MODULE_NAME module for nginx
</para>
</change>
</changes>
</change_log>
__EOF__
cat << __EOF__ >pkg-oss/docs/nginx-module-$MODULE_NAME.copyright
placeholder for nginx-module-$MODULE_NAME license / copyrights
__EOF__
echo "$ME: INFO: Creating module Makefile"
cat << __EOF__ > Makefile.module-$MODULE_NAME
MODULES=$MODULE_NAME
MODULE_PACKAGE_VENDOR= Build Script <build.script@example.com>
MODULE_PACKAGE_URL= https://www.nginx.com/blog/compiling-dynamic-modules-nginx-plus/
MODULE_SUMMARY_$MODULE_NAME= $MODULE_NAME dynamic module
MODULE_VERSION_$MODULE_NAME= $MODULE_VERSION
MODULE_RELEASE_$MODULE_NAME= $MODULE_RELEASE
MODULE_VERSION_PREFIX_$MODULE_NAME= \$(MODULE_TARGET_PREFIX)
MODULE_CONFARGS_$MODULE_NAME= --add-dynamic-module=\$(MODSRC_PREFIX)$MODULE_NAME-$VERSION
MODULE_SOURCES_$MODULE_NAME= $MODULE_NAME-$VERSION.tar.gz
define MODULE_POST_$MODULE_NAME
cat <<BANNER
----------------------------------------------------------------------
The \$(MODULE_SUMMARY_$MODULE_NAME) for nginx has been installed.
To enable this module, add the following to /etc/nginx/nginx.conf
and reload nginx:
`uniq $BUILD_DIR/postinstall.txt`
----------------------------------------------------------------------
BANNER
endef
export MODULE_POST_$MODULE_NAME
__EOF__
cp Makefile.module-$MODULE_NAME $BUILD_DIR/pkg-oss/rpm/SPECS/
cp Makefile.module-$MODULE_NAME $BUILD_DIR/pkg-oss/debian/
cp Makefile.module-$MODULE_NAME $BUILD_DIR/pkg-oss/alpine/
#
# Build!
#
echo "$ME: INFO: Building"
if [ "$PKG_FMT" = "rpm" ]; then
cd $BUILD_DIR/pkg-oss/rpm/SPECS
elif [ "$PKG_FMT" = "deb" ]; then
cd $BUILD_DIR/pkg-oss/debian
else
cd $BUILD_DIR/pkg-oss/alpine
fi
if [ "$BUILD_PLATFORM" = "Plus" ]; then
MODULE_TARGET=plus make module-$MODULE_NAME
else
make module-$MODULE_NAME
fi
if [ $? -ne 0 ]; then
echo "$ME: ERROR: Build failed"
exit 1
else
echo ""
echo "$ME: INFO: Module binaries created"
find $BUILD_DIR/$PACKAGING_ROOT -type f -name "*.so" -print
echo "$ME: INFO: Module packages created"
if [ "$PKG_FMT" = "apk" ]; then
find ~/packages -type f -name "*.$PKG_FMT" -exec $COPY_CMD -v {} $OUTPUT_DIR/ \;
else
find $BUILD_DIR/$PACKAGING_ROOT$PACKAGE_OUTPUT_DIR -type f -name "*.$PKG_FMT" -exec $COPY_CMD -v {} $OUTPUT_DIR/ \;
fi
echo "$ME: INFO: Removing $BUILD_DIR"
rm -fr $BUILD_DIR
fi