Skip to content

Commit

Permalink
add cli for generating a .deb file from a directory, original python …
Browse files Browse the repository at this point in the history
…packaging functionality should be unchanged
  • Loading branch information
rholder committed Feb 23, 2016
1 parent 697ea50 commit b2c4ab3
Showing 1 changed file with 198 additions and 13 deletions.
211 changes: 198 additions & 13 deletions debinate
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
set -o errexit
set -o pipefail

readonly DEBINATE=${0##*/}
readonly DEBINATE_VERSION=0.3.0
readonly DEBINATE_VERSION=0.4.0-dev
readonly PROVISIONING=.debinate
readonly DEBINATE_BUILD=${PROVISIONING}/build
readonly DEBINATE_TARGET=${PROVISIONING}/target
Expand All @@ -40,11 +39,32 @@ readonly DEBINATE_PYTHON="${DEBINATE_PYTHON:-$(which python)}"
# print usage information and exit
function usage () {
cat << EOF
Debinate ${DEBINATE_VERSION} - roll up your Python as a Debian package
Debinate ${DEBINATE_VERSION} - roll up your project into a Debian package
${DEBINATE} init - create and initialize a Debinate project structure in ${PROVISIONING}
${DEBINATE} package - creates a .deb file from your project in ${DEBINATE_BUILD}
${DEBINATE} clean - delete everything in the target, build, and cache folders
Python:
debinate init - create and initialize a Debinate project structure in ${PROVISIONING}
debinate package - creates a .deb file from your project in ${DEBINATE_BUILD}
debinate clean - delete everything in the target, build, and cache folders
Advanced:
debinate generate - build a .deb from a directory
-r, --root root directory, e.g. ./root with ./root/usr/local/bin/thing inside
-n, --name project name (optional, default: current directory)
-v, --version project version (optional, default: 1.0.0)
-d, --debian-dir directory that contains debian package files, like control (optional)
-o, --output output .deb file location (optional, default: "./name-version.deb")
Examples:
debinate init
debinate package
debinate clean
# minimal default to build a .deb from the given root
debinate generate -r ./build/root
# kitchen sink, specify all the things!
debinate generate --root ./build/root --name potato --version 1.3.2 --debian-dir ./debian --output ./potato-123.deb
You can find the latest version and file bugs at https://github.com/rholder/debinate.
Expand All @@ -69,7 +89,7 @@ function init () {
}

# create the target Python package with a virtualenv
function package () {
function package_python () {
TARGET_PROJECT_DIR="${DEBINATE_INSTALL_PREFIX}/${PROJECT_NAME}"
TARGET_VIRTUAL_ENV="${TARGET_PROJECT_DIR}/.virtualenv"
echo Building ${PROJECT_NAME} - ${VERSION}
Expand Down Expand Up @@ -149,7 +169,7 @@ function clean () {

# check that all the required programs are available
function check_environment () {
local programs=(ar find sort xargs tar md5sum virtualenv)
local programs=(ar find sort xargs tar sed md5sum virtualenv rsync)
for program in "${programs[@]}"; do
check_program_exists "$program"
done
Expand Down Expand Up @@ -208,8 +228,168 @@ function copy_script () {
fi
}

# fail if the given value is empty with the given message
function fail_if_empty () {
local the_value=$1
local message_when_empty=$2

if [ -z "${the_value}" ]; then
echo "${message_when_empty}"
exit 1
fi
}

# parse commandline arguments and generate a Debian package archive
function generate_cli () {
# required, will fail if empty
local deb_root=

# optional, defaults provided
local project_name=${PROJECT_NAME}
local project_version="1.0.0"
local deb_output="${project_name}-${project_version}.deb"

# this can be empty
local debian_dir=

while :; do
case $1 in
-h|-\?|--help) # Call a "usage" function to display a synopsis, then exit.
usage
exit 0
;;
-r|--root)
if [ -n "$2" ]; then
deb_root=$2
shift 2
continue
else
printf 'ERROR: "--root" requires a non-empty option argument.\n' >&2
exit 1
fi
;;
-n|--name) # Takes an option argument, ensuring it has been specified.
if [ -n "$2" ]; then
project_name=$2
shift 2
continue
else
printf 'ERROR: "--name" requires a non-empty option argument.\n' >&2
exit 1
fi
;;
-v|--version)
if [ -n "$2" ]; then
project_version=$2
shift 2
continue
else
printf 'ERROR: "--version" requires a non-empty option argument.\n' >&2
exit 1
fi
;;
-d|--debian-dir)
if [ -n "$2" ]; then
debian_dir=$2
shift 2
continue
else
printf 'ERROR: "--debian-dir" requires a non-empty option argument.\n' >&2
exit 1
fi
;;
-o|--output)
if [ -n "$2" ]; then
deb_output=$2
shift 2
continue
else
printf 'ERROR: "--output" requires a non-empty option argument.\n' >&2
exit 1
fi
;;
--) # End of all options.
shift
break
;;
-?*)
printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2
;;
*) # Default case: If no more options then break out of the loop.
break
esac

shift
done

fail_if_empty "${deb_root}" "--root cannot be empty"
fail_if_empty "${project_name}" "--name cannot be empty"
fail_if_empty "${project_version}" "--version cannot be empty"
fail_if_empty "${deb_output}" "--output cannot be empty"

generate_deb "${deb_root}" "${project_name}" "${project_version}" "${debian_dir}" "${deb_output}"
}

# generate a Debian package archive
function generate_deb () {
local deb_root=$1
local project_name=$2
local project_version=$3
local debian_custom_dir=$4
local deb_output=$5

local tmp_dir=$(mktemp -d)
local build_dir="${tmp_dir}/build"
local target_dir="${tmp_dir}/target"

# control archive files are staged here
local debian_dir="${build_dir}/debian"
mkdir -p "${build_dir}"
mkdir -p "${target_dir}"
mkdir -p "${debian_dir}"

# rsync needs a / at the end to make sure things copy over without the source dir
case "${deb_root}" in
*/) # do nothing if already ends in /
;;
*) # add a / if it doesn't end with one
deb_root="${deb_root}/"
;;
esac

# copy over root folder mirror, anything you needed
rsync -a "${deb_root}" "${target_dir}"

# check for debian_custom_dir
if [ -d "${debian_custom_dir}" ]; then
rsync -a "${debian_custom_dir}" "${debian_dir}"
fi

# create Debian control archive
generate_md5sums "${target_dir}" > "${debian_dir}/md5sums"

# use an existing Debian control file if it exists
if [ ! -f "${debian_dir}/control" ]; then
# otherwise generate one
generate_control "${project_name}" "${project_version}" "unknown" "" > "${debian_dir}/control"
fi

tar czf "${build_dir}/control.tar.gz" --numeric-owner --owner=0 --group=0 -C "${debian_dir}" --ignore-failed-read control md5sums preinst postinst prerm postrm 2>/dev/null

# create Debian data archive from target dir
tar czf "${build_dir}/data.tar.gz" --numeric-owner --owner=0 --group=0 -C "${target_dir}" .

# create Debian version file
echo "2.0" > "${build_dir}/debian-binary"

# create final Debian package
ar cr "${deb_output}" "${build_dir}/debian-binary" "${build_dir}/control.tar.gz" "${build_dir}/data.tar.gz"
echo "Debian archive ${deb_output} was generated successfully."
}

# TODO this will eventually roll into generate_deb, keeping for now to minimize backward compatible changes
# generate a Debian package archive
function generate_deb_for_python () {
local project_name=$1
local version=$2
local vendor=$3
Expand Down Expand Up @@ -255,7 +435,7 @@ function generate_deb () {
}

# run sanity checks to validate project structure
function sanity_check_package () {
function sanity_check_python () {
# sanity check existence of .debinate
if [ ! -d "${PROVISIONING}" ]; then
echo "Could not find ${PROVISIONING} directory."
Expand Down Expand Up @@ -298,17 +478,22 @@ function main () {
clean
;;
package)
sanity_check_package
package
generate_deb "${PROJECT_NAME}" "${VERSION}" "${VENDOR}" "${PROVISIONING}" "${DEBINATE_BUILD}/${PROJECT_NAME}-${VERSION}.deb"
sanity_check_python
package_python
generate_deb_for_python "${PROJECT_NAME}" "${VERSION}" "${VENDOR}" "${PROVISIONING}" "${DEBINATE_BUILD}/${PROJECT_NAME}-${VERSION}.deb"
;;
generate)
shift 1
generate_cli "$@"
;;
*)
usage
exit 1
;;
esac
}

# skip running the main function, suitable for sourcing, testing, etc.
if [ "$1" != "--skip-main" ]; then
main $1
main "$@"
fi

0 comments on commit b2c4ab3

Please sign in to comment.