#!/usr/bin/env bash ## ## A minimalistic MacPorts Packet Manager for OSXCross, ## based on: https://github.com/maci0/pmmacports. ## Please see README.MACPORTS for more. ## License: GPLv2. ## set -e export LC_ALL=C pushd "${0%/*}" &>/dev/null PUBKEYURL="https://svn.macports.org/repository/" PUBKEYURL+="macports/trunk/base/macports-pubkey.pem" # Darwin's antique OpenSSL does not support the SHA-2 family PUBKEYRMD160="d3a22f5be7184d6575afcc1be6fdb82fd25562e8" PUBKEYSHA1="214baa965af76ff71187e6c1ac91c559547f48ab" PLATFORM=$(uname -s) ARCH="x86_64" if [ -z "$BASHPID" ]; then BASHPID=$(sh -c 'echo $PPID') fi errorMsg() { echo "$@" 1>&2 } verboseMsg() { if [ -n "$VERBOSE" ]; then errorMsg "$@" fi } verbosePlaceHolder() { if [ -n "$VERBOSE" ]; then errorMsg "" fi } require() { set +e command -v $1 &>/dev/null if [ $? -ne 0 ]; then errorMsg "$1 is required" exit 1 fi set -e } if [ -z "$MACOSX_DEPLOYMENT_TARGET" ]; then errorMsg "you must set MACOSX_DEPLOYMENT_TARGET first." errorMsg "please see README.MACPORTS." exit 1 fi unsupportedDepTarget() { errorMsg "unsupported deployment target" exit 1 } require "openssl" case $MACOSX_DEPLOYMENT_TARGET in 10.6* ) OSXVERSION="darwin_10" ;; 10.7* ) OSXVERSION="darwin_11" ;; 10.8* ) OSXVERSION="darwin_12" ;; 10.9* ) OSXVERSION="darwin_13" ;; 10.10* ) OSXVERSION="darwin_14" ;; 10.11* ) OSXVERSION="darwin_15" ;; 10.12* ) OSXVERSION="darwin_16" ;; 10.13* ) OSXVERSION="darwin_17" ;; 10.14* ) OSXVERSION="darwin_18" ;; 10.15* ) OSXVERSION="darwin_19" ;; 10.16* ) OSXVERSION="darwin_20" ;; 11.* ) OSXVERSION="darwin_20" ;; * ) unsupportedDepTarget ;; esac require "osxcross-conf" eval $(osxcross-conf) if [ -z "$OSXCROSS_TARGET_DIR" ]; then errorMsg "OSXCROSS_TARGET_DIR should be set" exit 1 fi ROOT="$OSXCROSS_TARGET_DIR/macports" if [ ! -d "$ROOT" ]; then echo "creating macports directory: $ROOT" mkdir -p $ROOT fi PUBKEY="$ROOT/mp-pubkey.pem" INSTALLDB="$ROOT/INSTALLED" SELECTEDMIRROR="$ROOT/MIRROR" CACHE="$ROOT/cache" INSTALL="$ROOT/pkgs" TMP="$ROOT/tmp" LASTPKGNAME="" LOCKDIR="/tmp/osxcross-macports-lock" LOCKPID="$LOCKDIR/pid" checkLock() { if mkdir $LOCKDIR 2>/dev/null; then echo $BASHPID > $LOCKPID else pid=$(cat $LOCKPID 2>/dev/null) || { errorMsg "remove $LOCKDIR"; exit 1; } if [ $? -eq 0 -a "$(ps -p $pid -o comm=)" == "bash" ]; then errorMsg "locked by pid $pid" else errorMsg "dead lockdir detected! please remove '$LOCKDIR' by hand." fi exit 1 fi } createDir() { if [ ! -d $2 ]; then echo "creating $1 directory: $2" mkdir -p $2 fi } checkLock createDir "cache" $CACHE createDir "install" $INSTALL createDir "tmp" $TMP selectMirror() { echo "available mirrors:" echo "" echo "1: (https) packages.macports.org (United States)" echo "2: (http) mirrorservice.org (United Kingdom)" echo "3: (https) nue.de.packages.macports.org (Germany)" echo "4: (https) lil.fr.packages.macports.org (France)" echo "" local OK=0 local mirrorNumber while [ $OK -ne 1 ]; do echo -n "please enter the number of the mirror you want to use: " if [ -n "$UNATTENDED" ]; then echo "1" mirrorNumber=1 else read mirrorNumber fi case $mirrorNumber in 1) echo -n "https://packages.macports.org" > \ $SELECTEDMIRROR && OK=1 ;; 2) echo -n "http://www.mirrorservice.org/sites/packages.macports.org" > \ $SELECTEDMIRROR && OK=1 ;; 3) echo -n "https://nue.de.packages.macports.org/macports/packages" > \ $SELECTEDMIRROR && OK=1 ;; 4) echo -n "https://lil.fr.packages.macports.org" > \ $SELECTEDMIRROR && OK=1 ;; *) echo "please enter a number between 1 and 4..." 1>&2 ;; esac done MIRROR=$(cat $SELECTEDMIRROR) } function download() { local uri=$1 local filename if [ $# -eq 2 ]; then filename=$2 else filename=$(basename $1) fi if command -v curl &>/dev/null; then ## cURL ## local curl_opts="-L -C - " if [ -z "$VERBOSE" ]; then curl_opts+="-s " fi if [ $filename != "-" ]; then curl_opts+="-o $filename " fi curl $curl_opts $uri elif command -v wget &>/dev/null; then ## wget ## local wget_opts="-c -O $filename " local output=$(wget --no-config 2>&1) if [[ $output != *--no-config* ]]; then wget_opts+="--no-config " fi if [ $PLATFORM == "FreeBSD" ]; then wget_opts="--ca-certificate=" wget_opts+="/usr/local/share/certs/ca-root-nss.crt " fi if [ -z "$VERBOSE" ]; then wget_opts+="--quiet " fi wget $wget_opts $uri else echo "Required dependency 'curl or wget' not installed" 1>&2 exit 1 fi } getFileStdout() { verbosePlaceHolder download $1 - #verbosePlaceHolder } getFile() { verbosePlaceHolder if [ $# -ge 2 ]; then download $1 $2 else download $1 "$CACHE/$(basename $1)" fi #verbosePlaceHolder } verifyFileIntegrity() { local file="$1" if [ ! -e "$PUBKEY" ]; then echo "getting macports public key ..." getFile $PUBKEYURL "$PUBKEY" fi local rmd160=$(openssl rmd160 "$PUBKEY" | awk '{print $2}') local sha1=$(openssl sha1 "$PUBKEY" | awk '{print $2}') if [ "$rmd160" != "$PUBKEYRMD160" -o "$sha1" != "$PUBKEYSHA1" ]; then errorMsg "invalid macports public key (hash check failed)" exit 1 fi echo "verifying file integrity of $file ..." set +e openssl dgst -ripemd160 -verify "$PUBKEY" -signature \ "$CACHE/$file.rmd160" "$CACHE/$file" 1>/dev/null if [ $? -ne 0 ]; then errorMsg "file integrity check failed ($CACHE/$file)" exit 2 fi set -e } getPkgUrl() { local pkgname="$1" local pkgs local pkg set +e pkgs=$(getFileStdout "$MIRROR/$pkgname/?C=M;O=A" | \ grep -o -E 'href="([^"#]+)"' | \ cut -d'"' -f2 | grep '.tbz2$') local ec=$? set -e if [ $ec -ne 0 ]; then errorMsg "no package found" return fi verboseMsg " candidates for $pkgname:" for p in $pkgs; do verboseMsg " $p" done local pkg=$(echo "$pkgs" | grep $OSXVERSION | grep $ARCH | uniq | tail -n1) if [ -z "$pkg" ]; then pkg=$(echo "$pkgs" | grep $OSXVERSION | grep "noarch" | uniq | tail -n1) fi verboseMsg " selected: $pkg" if [ -z "$pkg" ]; then verboseMsg -n " " errorMsg "no suitable version found for $OSXVERSION" return fi echo "$MIRROR/$pkgname/$pkg" } pkgInstalled() { local pkgname="$1" if [ ! -e "$INSTALLDB" ]; then echo 0 return fi set +e grep -x "$pkgname" "$INSTALLDB" &>/dev/null local status=$? set -e if [ $status -eq 0 ]; then echo 1 else echo 0 fi } installPkg() { local pkgname="$1" LASTPKGNAME=$pkgname if [ $(pkgInstalled $pkgname) -eq 1 ]; then return fi echo "searching package $pkgname ..." local pkgurl=$(getPkgUrl "$pkgname") local pkgfile=$(echo "$pkgurl" | awk -F'/' '{print $NF}') if [ -z "$pkgurl" ]; then local oldpkgname=$pkgname local tmp=$(echo "$pkgname" | awk -F'-' '{print $NF}') [ -n "$tmp" ] && pkgname=${pkgname/-$tmp/} if [ "$pkgname" != "$oldpkgname" ]; then echo "trying $pkgname instead ..." else [ -n "$UPGRADE" ] && return exit 3 fi installPkg $pkgname return fi echo "getting $pkgfile ..." getFile "$pkgurl" verboseMsg "getting $pkgname.rmd160 ..." getFile "$pkgurl.rmd160" verifyFileIntegrity "$pkgfile" pushd $TMP &>/dev/null echo "installing $pkgname ..." verboseMsg " extracting $pkgfile ..." bzip2 -dc "$CACHE/$pkgfile" | tar xf - if [ -d opt/local ]; then verboseMsg " fixing permissions ..." find opt/local -type d -exec chmod 755 {} \; find opt/local -type f -exec chmod 644 {} \; if [ -d opt/local/lib ]; then if [ -n "$STATIC" ]; then verboseMsg " " echo "removing dylibs ..." find opt/local/lib -name "*.dylib" -exec rm {} \; else find opt/local/lib -type f -name "*.dylib" -exec chmod +x {} \; fi fi if [ -d opt/local/bin ]; then find opt/local/bin -type f -exec chmod +x {} \; fi set +e cp -r opt $INSTALL local status=$? set -e if [ $status -eq 1 ]; then errorMsg "removing broken symlinks ..." find -L . -type l -exec rm {} \; cp -r opt $INSTALL fi fi local pkgdeps=$(grep '@pkgdep' \+CONTENTS | cut -d\ -f2 | \ rev | cut -f2-100 -d\- | rev) popd &>/dev/null # TMP rm -rf $TMP/* for pkgdep in $pkgdeps; do installPkg $pkgdep done echo "$pkgname" >> "$INSTALLDB" } installPkgs() { local packages="$1" for pkgname in $packages; do if [ $(pkgInstalled $pkgname) == "1" ]; then errorMsg "$pkgname is already installed" continue fi installPkg $pkgname echo "installed $pkgname" rm -f $INSTALL/+* done } updateSearchCache() { pushd $CACHE &>/dev/null echo "generating index cache (this may take several minutes ...)" getFile $MIRROR $CACHE/packages cat packages | grep -o -E 'href="([^"#]+)"' | cut -d'"' -f2 | \ sed 's/.\{1\}$//' | uniq | sort > INDEXCACHE rm -f packages echo "generated index cache for $(cat INDEXCACHE | wc -l) packages" popd &>/dev/null } searchPkg() { local pkg="$1" pushd $CACHE &>/dev/null if [ ! -e INDEXCACHE ]; then updateSearchCache fi local packages=$(grep -i "$pkg" INDEXCACHE) if [ -z "$packages" ]; then echo "no matching packages found for $1" return fi for pkg in $packages; do echo $pkg done popd &>/dev/null } _exit() { local ec=$? if [ $ec -ne 0 ]; then if [ -n "$LASTPKGNAME" ]; then errorMsg "failed to install $LASTPKGNAME, try with '-v'" fi if [ $ec -eq 3 ]; then errorMsg -n "use '$(basename $0) fake-install ' to " errorMsg "fake install non-existing packages" fi fi rm -rf $LOCKDIR } showFlags() { if [ -n "$OSXCROSS_TARGET" ]; then PKG_CONFIG="x86_64-apple-${OSXCROSS_TARGET}-pkg-config" else PKG_CONFIG="pkg-config" fi $PKG_CONFIG $1 $2 } showCFLAGS() { showFlags "--cflags" $1 } showLDFLAGS() { showFlags "--libs" $1 } upgrade() { if [ ! -e $INSTALLDB ]; then if [ -e $INSTALLDB.old ]; then echo "restoring old installation database" mv $INSTALLDB.old $INSTALLDB else errorMsg "no install database" exit 1 fi fi local PKGS=$(cat $INSTALLDB) mv $INSTALLDB $INSTALLDB.old UPGRADE=1 rm -rf $INSTALL createDir "" $INSTALL 1>/dev/null for pkg in $PKGS; do installPkg $pkg done } clearCache() { rm -rf $CACHE } removeDylibs() { find $INSTALL -name "*.dylib" -exec rm {} \; } showHelpText() { errorMsg "please see README.MACPORTS" } main() { local args local cmd MIRROR="$OSXCROSS_MACPORTS_MIRROR" if [ -z "$MIRROR" ] && [ -f "$SELECTEDMIRROR" ]; then MIRROR=$(cat $SELECTEDMIRROR | head -n1) fi if [ -z "$MIRROR" ]; then selectMirror fi for opt in $@; do if [[ $opt == -* ]]; then if [ $opt == "-v" -o $opt == "--verbose" ]; then VERBOSE=1 elif [ $opt == "-v=2" -o $opt == "--verbose=2" ]; then set -x VERBOSE=1 elif [ $opt == "-s" -o $opt == "--static" ]; then STATIC=1 elif [ $opt == "-c" -o $opt == "--cflags" ]; then showCFLAGS $2 exit elif [ $opt == "-l" -o $opt == "--ldflags" ]; then showLDFLAGS $2 exit elif [ $opt == "-32" -o $opt == "--i386" ]; then ARCH="i386" elif [ $opt == "-universal" -o $opt == "--universal" ]; then ARCH="i386-x86_64" elif [ $opt == "-arm64" -o $opt == "--arm64" ]; then ARCH="arm64" elif [ $opt == "-sm" -o $opt == "--select-mirror" ]; then selectMirror exit elif [ $opt == "-h" -o $opt == "--help" ]; then showHelpText exit else errorMsg "unknown option: $opt" exit 1 fi else if [ -z "$cmd" ]; then cmd="$opt" else args+="$opt " fi fi done if [ -z "$cmd" ]; then errorMsg "no command given" showHelpText exit 1 fi case "$cmd" in update*cache ) updateSearchCache exit ;; upgrade ) upgrade exit ;; clear*cache ) clearCache echo "done" exit ;; remove*dylibs ) removeDylibs echo "done" exit ;; esac local packages="$args" if [ -z "$packages" ]; then errorMsg "no package name given" exit 1 fi case "$cmd" in install ) installPkgs "$packages" ;; fake*install ) for pkgname in $args; do if [ $(pkgInstalled $pkgname) -eq 0 ]; then echo $pkgname >> $INSTALLDB fi done echo "done" ;; search ) for pkgname in $args; do searchPkg $pkgname done ;; * ) showHelpText ;; esac } trap '' TERM trap '' INT trap _exit EXIT main "$@"