#!/bin/sh usage="\ pex is a PostgreSQL package manager. Usage: pex audit [PACKAGE]... pex cat PACKAGE... pex create -n NAME URL pex debuild PACKAGE pex edit [PACKAGE]... pex fetch PACKAGE... pex home [PACKAGE]... pex info PACKAGE... pex init [REPO] pex install PACKAGE pex ls|list pex outdated pex repotest pex rpmbuild PACKAGE pex search [SUBSTRING] pex update [--rebase] pex upgrade [PACKAGE]... " PEX_HOMEPAGE='https://github.com/petere/pex' PEX_DEFAULT_REMOTE='https://github.com/petere/pex-packages.git' set -eu : ${HOME=~} : ${XDG_DATA_HOME=$HOME/.local/share} : ${XDG_CONFIG_HOME=$HOME/.config} : ${XDG_CACHE_HOME=$HOME/.cache} LC_COLLATE=C export LC_COLLATE exec 6>&1 error() { echo "pex: $@" 1>&2 exit 1 } output() { echo "==> $@" 1>&6 } assert() { if test "$@"; then : else set +u if test -n "$BASH"; then error "assertion failed: $@ (${FUNCNAME[1]})" else error "assertion failed: $@" fi fi } data_dir=$XDG_DATA_HOME/pex packages_dir=$data_dir/packages work_dir=$XDG_CACHE_HOME/pex if which gmake >/dev/null 2>&1; then gnumake=gmake else gnumake=make fi if which shasum >/dev/null 2>&1; then sha1sum=shasum else sha1sum=sha1sum fi cmd_audit() { local pkg all_packages if [ $# -gt 0 ]; then for pkg; do _audit_one "$pkg" done else all_packages=$(_all_packages) for pkg in $all_packages; do _audit_one "$pkg" done fi output "Looks good" } cmd_cat() { local pkg package_file if [ $# -eq 0 ]; then error '"cat" takes at least one argument' fi for pkg; do package_file=$(_package_file "$pkg") cat "$package_file" done } cmd_edit() { local file files files='' if [ $# -eq 0 ]; then set $(cmd_search) fi for pkg; do file=$(_package_file "$pkg") files="$files $file" done _editor $files } cmd_create() { local downloaded_sha1 package_file pkg target url pkg='' while getopts 'n:' opt; do case $opt in n) pkg=$OPTARG;; *) exit 1;; esac done shift $(($OPTIND - 1)) if [ $# -ne 1 ]; then error '"create" takes exactly one nonoption argument' fi # TODO: optionally autodetect name if [ -z "$pkg" ]; then error "name must be specified (-n)" fi if [ -e "$packages_dir/$pkg.yaml" ]; then error "package \"$pkg\" already exists (use \"pex edit\" to change it)" fi url=$1 target=$(_download_target "$pkg" "$url") download "$url" "$target" downloaded_sha1=$(_sha1 "$target") package_file=$(_package_file "$pkg" true) cat <"$package_file" homepage: FIXME url: $url sha1: $downloaded_sha1 EOF cmd_edit "$pkg" echo "Use \"pex audit $pkg\" to check the package description." } cmd_debuild() { local homepage pgmajorversion pgversion pkg pkg=$1 shift pgversion=$("$pg_config" --version) pgmajorversion=$(expr "$pgversion" : '^.* \([0-9][0-9]*\.[0-9][0-9]*\)') package_file=$(_package_file "$pkg") homepage=$(parse_yaml_field "$package_file" homepage) package_version=$(_package_version "$pkg") target=$(_fetch_one "$pkg") rm -rf "$work_dir/$pkg/debuild" mkdir -p "$work_dir/$pkg/debuild" ( cd "$work_dir/$pkg/debuild" output "Unpacking $pkg" unpack "$target" ln -s $target ${pkg}_${package_version}.orig.tar.gz # FIXME cd "$pkg"* # FIXME rm -rf debian/ mkdir debian echo 9 >debian/compat mkdir debian/source case $target in *.zip) echo '3.0 (native)' >debian/source/format;; *) echo '3.0 (quilt)' >debian/source/format;; esac : ${DEBFULLNAME=${NAME=$(getent passwd $(whoami) | cut -d : -f 5)}} : ${DEBEMAIL=${EMAIL=$(whoami)@$(hostname --fqdn)}} cat <debian/control Source: $pkg Section: database Priority: extra Maintainer: $DEBFULLNAME <$DEBEMAIL> Build-Depends: debhelper (>= 9), postgresql-server-dev-$pgmajorversion Standards-Version: 3.9.3 Homepage: $homepage Package: postgresql-$pgmajorversion-$pkg Architecture: any Depends: postgresql-$pgmajorversion, \${misc:Depends}, \${shlibs:Depends} Description: $pkg This package of $pkg was automatically generated by pex. EOF dch --create --package="$pkg" --newversion="${package_version}-1pex" 'Automatic changelog entry' dch -r -D pex --force-distribution '' cat <debian/rules #!/usr/bin/make -f export PG_CONFIG = $pg_config export USE_PGXS = 1 %: dh \$@ override_dh_auto_test: ; EOF chmod a+x debian/rules output "Building Debian package $pkg" debuild --no-lintian "$@" ) || exit output "Build results are in $work_dir/$pkg/debuild/" } cmd_fetch() { local pkg for pkg; do _fetch_one "$pkg" >/dev/null done } cmd_home() { local homepage homepages package_file pkg homepages='' if [ $# -gt 0 ]; then for pkg; do package_file=$(_package_file "$pkg") homepage=$(parse_yaml_field "$package_file" homepage) homepages="$homepages $homepage" done else homepages=$PEX_HOMEPAGE fi _browser $homepages } cmd_info() { local pkg if [ $# -eq 0 ]; then error '"info" takes at least one argument' fi for pkg; do _info_one "$pkg" done } cmd_init() { local remote if [ $# -eq 0 ]; then remote=$PEX_DEFAULT_REMOTE elif [ $# -eq 1 ]; then remote=$1 else error '"init" takes zero or one arguments' fi mkdir -p "$data_dir" ( cd "$data_dir" git clone -q $remote packages ) } cmd_install() { local pkg if_not_exists if [ $# -gt 0 ] && [ x"$1" = x'--if-not-exists' ]; then if_not_exists=true shift else if_not_exists=false fi if [ $# -eq 0 ]; then error '"install" takes at least one argument' fi for pkg in "$@"; do _install_one "$pkg" false $if_not_exists done } cmd_list() { local installed_dir if [ $# -gt 0 ]; then error '"list" takes no arguments' fi installed_dir=$(_installed_dir) if [ ! -d "$installed_dir" ]; then return fi ( cd "$installed_dir" ls -1 *.yaml 2>/dev/null | sed 's/\.yaml$//' | sort ) } cmd_outdated() { local installed_dir installed_file pkg if [ $# -gt 0 ]; then error '"outdated" takes no arguments' fi installed_dir=$(_installed_dir) if [ ! -d "$installed_dir" ]; then return fi for installed_file in $(find "$installed_dir" -name "*.yaml" -print); do pkg=$(basename "$installed_file" .yaml) if _is_outdated "$pkg"; then echo "$pkg" fi done } cmd_repotest() { local i out pkg ret set $(cmd_search) echo "1..$#" ret=0 i=0 for pkg; do i=$(($i + 1)) status=0 out=$(_fetch_one "$pkg" 2>&1 6>&1) || status=$? if [ $status -eq 0 ]; then echo "ok $i $pkg" else echo "not ok $i $pkg" echo "$out" | grep -E -v '#+ 100\.0%' | sed 's/^/ # /' ret=1 fi done exit $ret } cmd_rpmbuild() { local homepage package_file package_version pkg pkg=$1 shift pgrpmname=$(rpm -q -f "$pg_config" --queryformat '%{NAME}') || error "$pgrpmname" test -n "$pgrpmname" package_file=$(_package_file "$pkg") homepage=$(parse_yaml_field "$package_file" homepage) package_version=$(_package_version "$pkg") target=$(_fetch_one "$pkg") target_basename=$(basename $target) rm -rf "$work_dir/$pkg/rpmbuild" mkdir -p "$work_dir/$pkg/rpmbuild" cat <$work_dir/$pkg/rpmbuild/$pkg.spec Name: $pgrpmname-$pkg Version: $package_version Release: 1%{?dist} Summary: $pkg Group: Databases License: unknown URL: $homepage Source: $target_basename BuildRequires: $pgrpmname BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %description This package of $pkg was automatically generated by pex. %prep %setup -q -n $pkg-%{version} %build make %{?_smp_mflags} USE_PGXS=1 PG_CONFIG=$pg_config %install make %{?_smp_mflags} USE_PGXS=1 PG_CONFIG=$pg_config install DESTDIR=\$RPM_BUILD_ROOT %clean rm -rf \$RPM_BUILD_ROOT %files %defattr(-,root,root,-) / EOF rpm_sourcedir=$(rpmbuild -E '%_sourcedir' "$work_dir/$pkg/rpmbuild/$pkg.spec" 2>/dev/null || :) mkdir -p "$rpm_sourcedir" ln -sf "$target" "$rpm_sourcedir" output "Building RPM package $pkg" rpmbuild "$work_dir/$pkg/rpmbuild/$pkg.spec" "$@" } cmd_search() { if [ $# -gt 1 ]; then error '"search" takes zero or one arguments' fi ( cd "$packages_dir" if [ $# -gt 0 ]; then ( local a search search=$1 ls -1 *"$search"*.yaml 2>/dev/null | sed 's/\.yaml$//' for a in $(sed -n 's/^aliases: *//p' *.yaml | tr -s ' ' '\n'); do case $a in *"$search"*) echo $a;; esac done ) | sort -u else ls -1 *.yaml 2>/dev/null | sed 's/\.yaml$//' | sort -u fi ) } cmd_update() { local changes ( cd "$packages_dir" git pull -n "$@" changes=$(git diff ORIG_HEAD..HEAD --name-status *.yaml | sed 's/\.yaml$//') if echo "$changes" | grep -q '^A'; then output "New packages" echo "$changes" | sed -n 's/^A[[:space:]]*//p' | sort fi if echo "$changes" | grep -q '^M'; then output "Updated packages" echo "$changes" | sed -n 's/^M[[:space:]]*//p' | sort fi ) } cmd_upgrade() { local pkg if [ $# -gt 0 ]; then for pkg; do _upgrade_one "$pkg" done else for pkg in $(cmd_list); do _upgrade_one "$pkg" done fi } _all_packages() { assert $# -eq 0 ( cd "$packages_dir" ls -1 *.yaml 2>/dev/null | sed 's/\.yaml$//' | sort ) } _browser() { assert $# -ge 1 set +u if [ -n "$BROWSER" ]; then $BROWSER "$@" elif [ $(uname -s) = 'Darwin' ]; then open "$@" elif which xdg-open >/dev/null 2>&1; then xdg-open "$@" elif which sensible-browser >/dev/null 2>&1; then sensible-browser "$@" else error "cannot find a suitable browser" fi set -u } _editor() { assert $# -gt 0 set +u if [ -n "$EDITOR" ]; then $EDITOR "$@" elif which sensible-editor >/dev/null 2>&1; then sensible-editor "$@" else vi "$@" fi set -u } _installed_dir() { local pgsharedir pgsharedir=$("$pg_config" --sharedir) echo "$pgsharedir/pex/installed" } _package_file() { local filename pkg ignore_missing assert $# -eq 1 -o $# -eq 2 pkg=$1 ignore_missing=false if [ $# -ge 2 ]; then ignore_missing=$2 fi if [ ! -d "$packages_dir" ]; then error "packages directory \"$packages_dir\" does not exist (Did you run \"pex init\"?)" fi filename="$packages_dir/$pkg.yaml" # resolve aliases if [ ! -f "$filename" ]; then res=$(grep -l '^aliases:.*\b'"$pkg"'\b' "$packages_dir"/*.yaml || [ $? -eq 1 ]) if [ $(echo "$res" | wc -l) -gt 1 ]; then error "too many aliases: $res" fi if [ -n "$res" ]; then filename=$res fi fi if [ ! -f "$filename" ] && ! $ignore_missing; then error "package \"$pkg\" does not exist" fi echo "$filename" } _package_version() { local pkg package_file pkg=$1 package_file=$(_package_file "$pkg") url=$(parse_yaml_field "$package_file" url) base=$(basename "$url") without_ext=$(echo "$base" | sed 's/\.[^.]*$//;s/\.tar//') case $without_ext in *-*) version=$(echo "$without_ext" | sed 's/^.*-//');; *_*) version=$(echo "$without_ext" | sed 's/^.*_//');; [0-9]*) version=$without_ext;; *) error "cannot detect version from \"$without_ext\"";; esac echo "$version" | sed 's/^v//' } _audit_one() { local homepage package_file package_name pkg assert $# -eq 1 pkg=$1 package_file=$(_package_file "$pkg") package_name=$(basename "$package_file" .yaml) output "Auditing $package_name" perl -MYAML -e 'use YAML;YAML::LoadFile("'"$package_file"'")' homepage=$(parse_yaml_field "$package_file" homepage) parse_yaml_field "$package_file" url >/dev/null parse_yaml_field "$package_file" sha1 >/dev/null if [ "$homepage" = FIXME ] || [ -z "$homepage" ]; then error "homepage field not filled in correctly" fi } _fetch_one() { local downloaded_sha1 package_file pkg sha1 target url assert $# -eq 1 pkg=$1 package_file=$(_package_file "$pkg") url=$(parse_yaml_field "$package_file" url) sha1=$(parse_yaml_field "$package_file" sha1) target=$(_download_target "$pkg" "$url") if [ -f "$target" ] && [ $(_sha1 "$target") = "$sha1" ]; then output "Already downloaded $pkg" else download "$url" "$target" downloaded_sha1=$(_sha1 "$target") if [ "$downloaded_sha1" != "$sha1" ]; then error "SHA1 mismatch\nExpected: $sha1\nActual: $downloaded_sha1" fi fi echo "$target" } _download_target() { local ext pkg url assert $# -eq 2 pkg=$1 url=$2 mkdir -p "$work_dir/$pkg/download" write_cachedir_tag "$work_dir" ext=$(canonical_archive_extension "$url") echo "$work_dir/$pkg/download/$pkg.$ext" } _info_one() { local homepage package_file package_name pkg status url package_installed_file assert $# -eq 1 pkg=$1 package_file=$(_package_file "$pkg") package_name=$(basename "$package_file" .yaml) homepage=$(parse_yaml_field "$package_file" homepage) url=$(parse_yaml_field "$package_file" url) package_installed_file="$(_installed_dir)/$package_name.yaml" if [ ! -e "$package_installed_file" ]; then status='not installed' elif _is_outdated "$package_name"; then status='outdated' else status='installed' fi echo "$package_name" echo "homepage: $homepage" echo "url: $url" echo "status: $status" } _install_one() { local allow_upgrade package_file pkg target if_not_exists installed_dir assert $# -eq 3 pkg=$1 allow_upgrade=$2 if_not_exists=$3 package_file=$(_package_file "$pkg") installed_dir=$(_installed_dir) if [ -e "$installed_dir/$(basename "$package_file")" ] && ! $allow_upgrade; then if $if_not_exists; then exit 0 fi error "package \"$pkg\" is already installed" fi target=$(_fetch_one "$pkg") rm -rf "$work_dir/$pkg/build" mkdir -p "$work_dir/$pkg/build" write_cachedir_tag "$work_dir" ( cd "$work_dir/$pkg/build" output "Unpacking $pkg" unpack "$target" dir=$(find . -maxdepth 1 -mindepth 1 -type d) if [ $(echo "$dir" | wc -l) -gt 1 ]; then error "too many directories: $dir" fi cd "$dir" output "Building $pkg" if [ -x configure ]; then ./configure \ --bindir=$("$pg_config" --bindir) \ --includedir=$("$pg_config" --includedir) \ --libdir=$("$pg_config" --libdir) \ --with-pgconfig=$("$pg_config" --bindir)/pg_config \ PG_CONFIG="$pg_config" || exit fi "$gnumake" all USE_PGXS=1 PG_CONFIG="$pg_config" || exit output "Installing $pkg" $sudo "$gnumake" install USE_PGXS=1 PG_CONFIG="$pg_config" || exit ) || exit $sudo mkdir -p "$installed_dir" $sudo cp "$package_file" "$installed_dir/" output "Package $pkg installed successfully" } _is_outdated() { local available_file available_sha1 installed_file installed_sha1 pkg assert $# -eq 1 pkg=$1 installed_file=$(_installed_dir)/$pkg.yaml installed_sha1=$(parse_yaml_field "$installed_file" sha1) available_file=$(_package_file "$pkg") if [ -e "$available_file" ]; then available_sha1=$(parse_yaml_field "$available_file" sha1) if [ "$installed_sha1" != "$available_sha1" ]; then return 0 fi else return 0 fi return 1 } _sha1() { assert $# -eq 1 "$sha1sum" "$1" | cut -d ' ' -f 1 } _upgrade_one() { local installed_sha1 new_sha1 package_file package_name pkg installed_dir assert $# -eq 1 pkg="$1" package_file=$(_package_file "$pkg") package_name=$(basename "$package_file" .yaml) installed_dir=$(_installed_dir) if [ ! -e "$installed_dir/$package_name.yaml" ]; then error "package \"$pkg\" is not installed" fi installed_sha1=$(parse_yaml_field "$installed_dir/$package_name.yaml" sha1) new_sha1=$(parse_yaml_field "$package_file" sha1) if [ "$installed_sha1" = "$new_sha1" ]; then output "Package $package_name already up to date" return fi _install_one "$pkg" true false } parse_yaml_field() { local field file val assert $# -eq 2 file=$1 field=$2 val=$(cat "$file" | sed -n "s/^${field}: *//p") if [ -z "$val" ]; then error "field \"$field\" missing in file \"$file\"" fi echo "$val" } canonical_archive_extension() { local file assert $# -eq 1 file=$1 case $file in *.zip) echo "zip";; *.tgz) echo "tar.gz";; *.tbz2) echo "tar.bz2";; *.tar.*) echo "$file" | sed 's,^.*\.\(tar\..*\)$,\1,';; esac } download() { local ext target url assert $# -eq 2 url=$1 target=$2 ext=$(canonical_archive_extension "$target") output "Downloading $pkg from $url" curl --progress-bar --location -o "$target" "$url" } unpack() { local ext file assert $# -eq 1 file=$1 ext=$(canonical_archive_extension "$file") # FIXME: check directory structure case $ext in zip) unzip "$file";; tar.*) tar xvf "$file";; *) error "unsupported archive format: $file";; esac } write_cachedir_tag() { local dir assert $# -eq 1 dir=$1 if [ -e "$dir/CACHEDIR.TAG" ]; then return fi cat >$dir/CACHEDIR.TAG <