Skip to content

Instantly share code, notes, and snippets.

@spacelatte
Last active September 26, 2021 18:04
Show Gist options
  • Save spacelatte/3b9220892d7618e2adf635991891be5f to your computer and use it in GitHub Desktop.
Save spacelatte/3b9220892d7618e2adf635991891be5f to your computer and use it in GitHub Desktop.
kanka: single-script package manager for #macos (#osx) with dependency resolution, based on #homebrew #brew.sh #api
#!/usr/bin/env bash
# -d 9 -type d
# -d 6 -type d
# -d 3 -type d
export APPS="${HOME}/.local/apps"
export PATH="${APPS}:${PATH}"
export JQ_URL="https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64"
export JQ_PATH="${APPS}/jq-osx-amd64"
export BREW_URL="https://formulae.brew.sh/api/formula"
export BREW_CACHE="${APPS}/.cache"
export HOMEBREW_PREFIX="${APPS}"
export HOMEBREW_CELLAR="${APPS}"
export BREW_DIST="$(sw_vers -productVersion)"
export BREW_SWVER=( ${BREW_DIST//\./ } )
export BREW_SWVER_MAJ=${BREW_SWVER[0]}
export BREW_SWVER_MIN=${BREW_SWVER[1]}
export BREW_SWVER_FIX=${BREW_SWVER[2]}
export BREW_NAMES=(
"cheetah" # 10.0
"puma" # 10.1
"jaguar" # 10.2
"panther" # 10.3
"tiger" # 10.4
"leopard" # 10.5
"snow_leopard" # 10.6
"lion" # 10.7
"mountain_lion" # 10.8
"mavericks" # 10.9
"yosemite" # 10.10
"el_capitan" # 10.11
"sierra" # 10.12
"high_sierra" # 10.13
"mojave" # 10.14
"catalina" # 10.15
"big_sur" # 10.16
)
function paths_cache() {
echo "updating environment!"
find "${APPS}" -iname bash_completion -type f > "${APPS}/.completions" &
tee "${APPS}/.paths" >/dev/null <<-EOF
#!/usr/bin/env ${SHELL:-sh}
# --- paths source file ---
# echo "sourcing: \$0"
for arg in "\$@"; do echo "arg: \$arg"; done
export BASH_COMPLETION="$(find "${APPS}" -iname bash_completion -type f -print0 | sort -zbfuV | tr -s \\0 \\n)"
export BASH_COMPLETION_DIR="\${APPS}/etc/bash_completion.d"
export BASH_COMPLETION_COMPAT_DIR="\${BASH_COMPLETION_DIR}"
# export BASH_COMPLETION="\${X_BASH_COMPLETION}"
# export BASH_COMPLETION_DIR="\${X_BASH_COMPLETION_DIR}"
# export BASH_COMPLETION_COMPAT_DIR="\${X_BASH_COMPLETION_COMPAT_DIR}"
# \${MANPATH:-/usr/share/man:/usr/local/share/man}
export PREFIX="\${APPS}/sysroot"
export NPM_CONFIG_PREFIX="\${PREFIX}"
export KN_AP_PATH="$( find "${APPS}" -maxdepth 3 -type ld -iregex ".*/[s]\{0,1\}bin" -not -regex ".*\.app.*" -print0 | sort -zbfuV | tr -s \\0 :)"
export KN_FW_PATH="$( find "${APPS}" -maxdepth 9 -type ld -iregex ".*/frameworks/.*/[s]\{0,1\}bin" -not -regex ".*\.app.*" -print0 | sort -zbfuV | tr -s \\0 :)"
export KN_TERMINFO="$(find "${APPS}" -maxdepth 6 -type ld -iname "terminfo" -not -regex ".*\.app.*" -print0 | sort -zbfuV | tr -s \\0 :)"
export KN_CPATH="$( find "${APPS}" -maxdepth 4 -type ld -iname "include" -not -regex ".*\.app.*" -print0 | sort -zbfuV | tr -s \\0 :)"
export KN_LIBPATH="$(find "${APPS}" -maxdepth 4 -type ld -iname "lib" -not -regex ".*\.app.*" -print0 | sort -zbfuV | tr -s \\0 :)"
export KN_MANPATH="$(find "${APPS}" -maxdepth 4 -type ld -iname "man" -not -regex ".*\.app.*" -print0 | sort -zbfuV | tr -s \\0 :)"
export TERMINFO+="\${KN_TERMINFO}"
export MANPATH+=":\${KN_MANPATH}"
export PATH+=":\${KN_AP_PATH}:\${KN_FW_PATH}"
export CPATH+=":\${KN_CPATH}"
export LIBRARY_PATH+=":\${KN_LIBPATH}"
export C_INCLUDE_PATH+=":\${KN_CPATH}"
export OBJC_INCLUDE_PATH+=":\${KN_CPATH}"
export CPLUS_INCLUDE_PATH+=":\${KN_CPATH}"
export OBJCPLUS_INCLUDE_PATH+=":\${KN_CPATH}"
EOF
wait
source "${APPS}/.paths"
return
}
function brew_github_auth() {
local REPO="${1:? REPO is missing}"
curl -#Lfk \
-u $(git config --get github.user):$(git config --get github.pat) \
"ghcr.io/token?service=ghcr.io&scope=repository:${REPO}:pull" \
| jq -r .token
}
function brew_github_helper() {
local TAP="${1:? TAP is missing}"
local URL="${2:? URL is missing}"
local TOKEN="$( brew_github_auth "${TAP}" )"
curl -#Lfk \
-H "Authorization: Bearer ${TOKEN}" \
"${URL}"
}
function brew_initialize() {
mkdir -p "${APPS}"
test -e "${JQ_PATH}" || {
echo "Downloading JQ: ${JQ_URL}"
/usr/bin/curl -#Lfo "${JQ_PATH}" "${JQ_URL}"
chmod +x "${JQ_PATH}"
}
test -e "${APPS}/.paths" && {
source "${APPS}/.paths"
} || {
paths_cache
# return
}
test -e "${BREW_CACHE}" || {
#touch "${BREW_CACHE}"
brew_update
}
test -e "${APPS}/opt" || ln -vsfF "${APPS}" "${APPS}/opt"
test -e "${APPS}/Cellar" || ln -vsfF "${APPS}" "${APPS}/Cellar"
test -e "${APPS}/sysroot" || mkdir -p "${APPS}/sysroot"
return
while read -r comp; do
echo "[ comp ] ${comp}"
source "${comp}"
continue
done < "${APPS}/.completions"
return
}
export SKIP_FIX="
fortune
"
function brew_fix_otool() {
return
}
function brew_fix() {
#set -x
local term
#export LC_ALL=C
orgdir="/usr/local/Cellar"
fixdir="${APPS}"
mkdir -p "${fixdir}"
for term in "$@"; do
test -e "${APPS}/${term}" || continue
local deps=$(brew_depends "${term}")
find "${APPS}/${term}" -type f -exec file {} + \
| grep -iE -e 'mach.+executable' -e 'shared.+library' -e 'mach-o.+bundle' \
| cut -d: -f1 \
| while read -r obj; do
#otool -L "${obj}" | grep HOMEBREW | cut -d\ -f1 | cut -b2- #| xargs -n1 -I% -- echo "%:$(basename %)"
otool -L "${obj}" \
| grep -oE '\S+' \
| grep HOMEBREW \
| while read -r lib; do
echo "${obj##${APPS}/}: $lib"
chmod ug+w "${obj}"
#ls -lha "${obj}"
for dep in ${deps}; do
test -e "${APPS}/${dep}/${lib}" || continue
install_name_tool -add_rpath "${APPS}/${dep}/lib/." "${obj}"
continue
done
install_name_tool -add_rpath "/." "${obj}"
install_name_tool -add_rpath "/usr/." "${obj}"
install_name_tool -add_rpath "/usr/local/." "${obj}"
install_name_tool -add_rpath "${fixdir}/." "${obj}"
install_name_tool -add_rpath "@loader_path/." "${obj}"
install_name_tool -add_rpath "@executable_path/." "${obj}"
#install_name_tool -add_rpath "@loader_path/lib/." "${obj}"
#install_name_tool -add_rpath "@executable_path/lib/." "${obj}"
install_name_tool -change "${lib}" "${lib//@@HOMEBREW_*@@/@rpath}" "${obj}"
install_name_tool -id "$(basename "${lib}" )" # "${lib//@@HOMEBREW_*@@/${APPS}}" "${obj}"
#otool -l "${obj}" | grep -A3 RPATH
#otool -L "${obj}"
#echo "----------"
continue
local dylib="$(basename "${lib}" .dylib)"
install_name_tool -change "$(basename "${lib}")" \
"${lib//@@HOMEBREW_*@@/@rpath}" "${obj}"
#"$(echo "${lib}" | sed "s:@@HOMEBREW.*@@:$APPS:g")" \
#"${obj}"
otool -L "${obj}"
continue
done &>/dev/null & # each lib
wait
continue
done # each obj
wait
#find "${APPS}/${term}" -type f -exec sed -i'' "s:@@HOMEBREW_PREFIX@@:${APPS}:g" {} +
#find "${APPS}/${term}" -type f -exec sed -i'' "s:@@HOMEBREW_CELLAR@@:${APPS}:g" {} +
#continue
SUFFIX="old.${RANDOM}"
find "${APPS}/${term}" -type f -exec xattr -c {} +
#cp -R "${APPS}/${term}" "${fixdir}"
#continue
if echo "${SKIP_FIX}" | grep -qi "${term}"; then
echo "Package is blacklisted, skipping fixing..."
continue
fi
LC_ALL=C find "${APPS}/${term}" -type f -perm +111 \
| while read file; do
file -b --mime-type "${file}" \
| grep -qi text \
|| continue
LC_ALL=C sed -i".${SUFFIX}" "s#${orgdir}#${fixdir}#g" "${file}"
LC_ALL=C sed -i".${SUFFIX}" "s#@@HOMEBREW_PREFIX@@#${fixdir}#g" "${file}"
LC_ALL=C sed -i".${SUFFIX}" "s#@@HOMEBREW_CELLAR@@#${fixdir}#g" "${file}"
rm -f "${file}.${SUFFIX}"
continue
done
continue
echo "replacing: $orgdir $fixdir"
LC_ALL=C find "${APPS}/${term}" -type f -perm +111 -exec \
sed -i".${SUFFIX}" "s#${orgdir}#${fixdir}#g" {} +
#continue
echo "replacing: HOMEBREW_PREFIX $fixdir"
LC_ALL=C find "${APPS}/${term}" -type f -perm +111 -exec \
sed -i".${SUFFIX}" "s#@@HOMEBREW_PREFIX@@#${fixdir}#g" {} +
#continue
echo "replacing: HOMEBREW_CELLAR $fixdir"
LC_ALL=C find "${APPS}/${term}" -type f -perm +111 -exec \
sed -i".${SUFFIX}" "s#@@HOMEBREW_CELLAR@@#${fixdir}#g" {} +
#continue
find "${APPS}/${term}" -type f -iname "*.${SUFFIX}" -delete
done
#set +x
return
}
function brew_info() {
branch=stable
for term in "$@"; do
#jq -r ".[] | if (.name == (\"${term}\")) then del(.bottle.${branch}) else empty end" "${BREW_CACHE}"
"${JQ_PATH}" -r ".[] | if (.name == (\"${term}\")) then . else empty end" "${BREW_CACHE}"
continue
done
return
}
function brew_update() {
function extract_header() (cat | tr -d '\r' | grep -iE "${1}" | tr -d '[:blank:]' | cut -d: -f2-)
etag_local="$(cat "${BREW_CACHE}.etag" 2>/dev/null | extract_header 'etag' )"
etag_remote="$(/usr/bin/curl -sfI "${BREW_URL}.json" | extract_header 'etag' )"
length_local="$(stat -f '%z' "${BREW_CACHE}" 2>/dev/null || echo 0)"
length_remote="$(/usr/bin/curl -sfI "${BREW_URL}.json" | extract_header 'length' )"
echo "# diff (local/remote): sizes=${length_local}/${length_remote} etags=${etag_local}/${etag_remote}"
# test "$((length_local))" -eq "$((length_remote))" || /usr/bin/curl -#Lfo "${BREW_CACHE}" "${BREW_URL}.json"
test "${etag_local}" = "${etag_remote}" || \
/usr/bin/curl --compressed -#Lf \
-D "${BREW_CACHE}.etag" \
-o "${BREW_CACHE}" \
"${BREW_URL}.json"
return
}
function brew_search() {
local term
for term in "$@"; do
"${JQ_PATH}" -r ".[] | {n: .name, d: .desc} | if (.n | test(\".*${term}.*\")) then [ .n, .d ] | join(\":\") else empty end" "${BREW_CACHE}"
continue
done | column -ts ':'
return
}
function brew_depends() {
local term
for term in "$@"; do
"${JQ_PATH}" -r ".[] | select(.dependencies) | { n: .name, d: .dependencies } | if (.n == (\"${term}\")) then .d else empty end | .[]" "${BREW_CACHE}"
continue
done
}
function brew_recurse() {
local term
local deps
for term in "$@"; do
deps=( $(brew_depends "${term}") )
test -n "${deps[*]}" && echo "${deps[@]}"
test -n "${deps[*]}" && brew_recurse "${deps[@]}"
continue
done | tr ' ' \\n | cat -s | sort -u | tr \\n ' '
return
}
function brew_install() {
local term
local deps
local branch=stable
local release=${BREW_NAMES[$BREW_SWVER_MIN]}
#export -f brew_github_helper
#export -f brew_github_auth
for term in "$@"; do
deps=$(brew_recurse "${term}")
test -z "${skip}" && \
test -n "${deps}" && {
local ideps=( )
for dep in ${deps}; do
test -e "${APPS}/${dep}" || ideps+=( "${dep}" )
done
echo "${term} dependencies: ${ideps[*]:-(none)} / ${deps}"
skip="$((1+${skip:-0}))" brew_install "${ideps[@]}"
#for dep in "${deps}"; do brew_install "${dep}"; done
}
# shellcheck disable=SC2016,SC1004
echo "Installing: ${skip} ${term} "
fields=(
$("${JQ_PATH}" -r "[ .[] | select(.name == \"${term}\") | (
.name, \
.tap, \
.bottle?.${branch}?.files?.all?.url // \
.bottle?.${branch}?.files?.${release}?.url \
) ] | @tsv" "${BREW_CACHE}")
)
name="${fields[0]}"
tap="${fields[1]}"
url="${fields[2]}"
dir="${APPS}/${name}"
mkdir -p "${dir}"
brew_github_helper "${tap}" "${url}" | /usr/bin/tar -C "${dir}" --strip=1 -ox
#| xargs -n3 -- bash -ec '
# name="$1"; \
# tap="$2"; \
# url="$3"; \
# dir="${APPS}/${name}"; \
# mkdir -p "${dir}" && \
# brew_github_helper "${tap}" "${url}" \
# | /usr/bin/tar -C "${dir}" --strip=1 -ox; \
#' -- && \
test -e "${APPS}/${term}" && \
find "${APPS}/${term}" -depth 1 -type l -print -delete && \
(cd "${APPS}/${term}" && echo bin etc include lib libexec sbin share man var | xargs -n1 | xargs -I% -- ln -vsfF "./$(ls -1t | head -1)/%") && \
(cd "${APPS}" && mkdir -p ./etc/bash_completion.d && cd ./etc/bash_completion.d; test -e "../../${term}" && \
find "../../${term}/$(ls -1t "../../${term}" | head -1)/etc/bash_completion.d" -type f -exec ln -vsfF {} \; 2>/dev/null) && \
true
brew_fix "${term}"
echo "cleaning up broken symlinks..."
for i in {0..3}; do
echo "@ depth: $i"
find -L "${APPS}/${term}" -depth $i -type l -exec rm -vf {} +
continue
done
continue
done
#brew_fix $@
return
}
function brew_list() {
find "${APPS}" -d 1 -type d -exec basename {} \;
return
}
function brew_remove() {
for term in "$@"; do
find "${APPS}" -d 1 -iname "${term}" -type d -exec rm -rf {} +
continue
done
return
}
function brew_paths() {
echo "#PATH"
echo "$PATH" | tr : \\n
echo "#MANPATH"
echo "$MANPATH" | tr : \\n
echo "#LIBRARY_PATH"
echo "$LIBRARY_PATH" | tr : \\n
echo "#C_INCLUDE_PATH"
echo "$C_INCLUDE_PATH" | tr : \\n
echo "#DYLD_LIBRARY_PATH"
echo "$DYLD_LIBRARY_PATH" | tr : \\n
echo "#DYLD_FRAMEWORK_PATH"
echo "$DYLD_FRAMEWORK_PATH" | tr : \\n
echo "#DYLD_FALLBACK_LIBRARY_PATH"
echo "$DYLD_FALLBACK_LIBRARY_PATH" | tr : \\n
echo "#DYLD_FALLBACK_FRAMEWORK_PATH"
echo "$DYLD_FALLBACK_FRAMEWORK_PATH" | tr : \\n
return
}
function brew_update_auto() {
test -n "${KANKA_DISABLE_AUTOUPDATE}" && return
export -f brew_update
find "${BREW_CACHE}" -mmin +1440 -exec "$SHELL" -c brew_update \;
}
function kanka() {
local method="${1}"
local params=( "${@:2}" )
brew_update_auto # automatic updates on-demand
case "$method" in
yukle|ekle|inst*)
brew_install "${params[@]}"
paths_cache
;;
bul|ara|hani|search|find)
brew_search "${params[@]}"
;;
sil|kaldir|rm|del|rem*)
brew_remove "${params[@]}"
paths_cache
;;
lazim|gerek*|deps|depend*)
brew_depends "${params[@]}"
;;
detay|bilgi|nasil|ne*|info|detail)
brew_info "${params[@]}"
;;
bak|yol*|path[s]|loc*)
brew_paths
;;
list*)
brew_list
;;
fix*)
brew_fix "${params[@]}"
;;
guncelle|upd*|"")
brew_update "${params[@]}"
;;
*)
local n=knk
echo "
$n Usage:
$n update # update the local cache
$n inst|install package... # install packages
$n del|rm package... # remove packages
$n deps|depend package... # show dependencies of the packages
" | sed -E 's:^[[:blank:]]*::g'
;;
esac
return
}
alias knk=kanka
brew_initialize
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment