install (11648B)
1 #!/bin/zsh 2 setopt no_unset warn_create_global extended_glob 3 zmodload -F zsh/stat b:zstat || exit $? 4 zmodload -m -F zsh/files b:zf_\* || exit $? 5 6 cd $0:h || exit $? 7 umask 022 8 9 ### Utility functions {{{1 10 die_ret() { 11 local ret 12 ret=$1 13 shift 14 printf >&2 '%s\n' "$@" 15 exit $ret 16 } 17 die() { 18 die_ret 1 "$@" 19 } 20 die100() { # 100: wrong usage 21 die_ret 100 "$@" 22 } 23 die111() { # 111: system call failed 24 die_ret 111 "$@" 25 } 26 27 require() { 28 if ! check_$1; then 29 make_$1 30 if ! check_$1; then 31 die "ERROR: check_$1 failed after make_$1" 32 fi 33 fi 34 } 35 36 ### Configuration {{{1 37 typeset -ga submodule_list=( package init scripts conf ) 38 typeset -gA submodule_ignore_changes=( 39 package 1 40 ) 41 typeset -gA submodule_has_submodules=( 42 . 1 43 package 1 44 ) 45 46 ### Checks {{{1 47 check_submodule_init() { 48 local -a submodule_dirs 49 local sub 50 submodule_dirs=( ${(f)"$(sed -n '/^\tpath = /{s/^[^=]* = //;p}' <$submodule/.gitmodules)"} ) 51 for sub in $submodule_dirs; do 52 [[ -f $submodule/$sub/.git ]] || return $? 53 done 54 } 55 make_submodule_init() { 56 (cd $submodule && git submodule init && git submodule update) 57 } 58 59 check_committed() { 60 local git_status 61 git_status=$(cd $submodule && git status -s --ignore-submodules=dirty) || return $? 62 [[ -z $git_status ]] && return 0 63 printf "Following changes found in %s:\n%s\n" $submodule "$git_status" 64 return 1 65 } 66 make_committed() { 67 local REPLY 68 local -a empty_dirs 69 echo "Uncommitted changes. Add and commit them?" 70 empty_dirs=( $submodule/(**~.git)/*(ND/^F) ) 71 (( $#empty_dirs )) && echo "WARNING: $#empty_dirs empty directories found." 72 read -q || exit 0 73 ( cd $submodule && git add . && git commit ) || exit $? 74 if [[ $submodule != . ]]; then 75 git commit -m "update ${(qqq)submodule} submodule" -- $submodule || exit $? 76 fi 77 } 78 79 check_current() { 80 local -A stat_out 81 [[ -L /current ]] || return 1 82 zstat -L -H stat_out /current || return $? 83 [[ $stat_out[link] == ${${sm_dst[.]}#/} ]] 84 } 85 make_current() { 86 preinstall 87 s6-ln -s -f -n "${${sm_dst[.]}#/}" /current 88 postinstall_or_rollback 89 } 90 91 recursive_immutable() { 92 ## Noisy but hopefully appropriate exitcode 93 #chattr -R -f +i "$@" 94 ## Skips warnings on symlinks but exitcode might not be useful 95 find "$@" -depth \! -type l -exec chattr +i '{}' + 96 ## Skip lockfiles so they can be opened read-write 97 # find "$@" -depth -name lock \! -o -type l -exec chattr +i '{}' + 98 } 99 100 check_bzr_export() { 101 [[ -d ${bzr_dst} ]] 102 } 103 make_bzr_export() { 104 local tmp=$bzr_dst:h/.new.$bzr_dst:t 105 brz export --format=dir --revision=revid:$bzr_revid -d "$bzr_src" $tmp/ || exit $? 106 mv $tmp $bzr_dst || exit $? 107 recursive_immutable $bzr_dst 108 } 109 110 check_installed() { 111 [[ -f ${sm_dst[$submodule]}/.done ]] 112 } 113 make_installed() { 114 local dst=${sm_dst[$submodule]} 115 if [[ -e $dst ]]; then 116 rm -r $dst || exit $? 117 fi 118 ( cd $submodule && install_submodule ) || exit $? 119 touch $dst/.done 120 recursive_immutable $dst 121 } 122 123 ### Install functions {{{1 124 install_submodule() { 125 case $submodule in 126 (package) install_package;; 127 (s6-rc) install_unpack;; 128 (init) install_unpack;; 129 (scripts) install_unpack;; 130 (conf) install_unpack;; 131 (.) install_dot;; 132 (*) 133 echo >&2 "unhandled submodule: ${(qqq)submodule}" 134 exit 1 135 ;; 136 esac 137 } 138 139 install_package() { 140 git checkout --force || exit $? 141 git clean -fx || exit $? 142 git submodule update --init --force || exit $? 143 exec env slashpackage=${sm_dst[$submodule]} ./install-all 144 } 145 146 install_unpack() { 147 local dst=${sm_dst[$submodule]} 148 local cmd=${sm_dst[package]}/command 149 zf_mkdir -p $dst || exit $? 150 git archive --format=tar ${sm_commit[$submodule]} | tar --no-same-owner --no-same-permissions -xC $dst 151 exit $(( ${(j.|.)pipestatus} )) # error if git archive or tar exited non-zero 152 } 153 154 install_bzr_repo(){ 155 typeset -g bzr_dst bzr_revid bzr_src 156 bzr_dst=/versions/$1.$2 157 bzr_revid=$2 158 bzr_src=$3 159 160 require bzr_export 161 s6-ln -ns ../../$1.$2 $sm_dst[.]/bzr/$1 || exit $? 162 } 163 install_bzr_repos(){ 164 local name url revid 165 local -A repo_sources 166 167 mkdir -p $sm_dst[.]/bzr || exit $? 168 169 <./bzr/sources while IFS=$'\t' read name url; do 170 [[ -n "$name" && -n "$url" ]] || die "Malformed bzr/sources file" 171 (( $+repo_sources[$name] )) && die "Duplicate repo name: ${(qqq)name}" 172 repo_sources[$name]=$url 173 done 174 <./bzr/revisions while IFS=$'\t' read name revid; do 175 [[ -n "$name" && -n "$revid" ]] || die "Malformed revisions file" 176 install_bzr_repo "$name" "$revid" "$repo_sources[$name]" 177 done 178 } 179 180 # Utility for symlinking into /versions/all.*/ 181 ln_sub() { 182 s6-ln -ns ../${${sm_dst[$1]}:t} ${sm_dst[.]}/${2:-$1} || exit $? 183 } 184 ln_from_sub() { 185 s6-ln -ns ../${${sm_dst[$1]}:t}/$2 ${sm_dst[.]}/$3 || exit $? 186 } 187 ln_sub_command() { 188 ( 189 cd ${sm_dst[.]} || exit $? 190 local cmd 191 for cmd in ../${${sm_dst[$1]}:t}/{command,bin}/*(N); do 192 s6-ln -s ../$cmd ${sm_dst[.]}/command/ || exit $? 193 done 194 ) || exit $? 195 } 196 ln_sub_zsh_functions() { 197 local fun 198 for fun in ../${${sm_dst[$1]}:t}/zsh-functions/*(N); do 199 s6-ln -s ../$fun ${sm_dst[.]}/zsh-functions/ || exit $? 200 done 201 } 202 install_dot() { 203 local submodule cmd fun 204 205 # export sm_dst_* variables for each submodule 206 for submodule in "${(k@)sm_dst}"; do 207 if [[ $submodule == . ]]; then 208 export sm_dst_all=$sm_dst[.] 209 else 210 eval "export sm_dst_${submodule//-/_}"='$sm_dst[$submodule]' 211 fi 212 done 213 214 # create symlinks for submodules and populate ./command 215 zf_mkdir -p ${sm_dst[.]}/command ${sm_dst[.]}/zsh-functions || exit $? 216 217 ln_from_sub package package package 218 ln_sub_command package 219 220 install_bzr_repos 221 ( 222 cd ${sm_dst[.]} || exit $? 223 for cmd in bzr/*/{command,bin,sbin}/*(N); do 224 s6-ln -s ../$cmd ./command/ || exit $? 225 done 226 for fun in bzr/*/zsh-functions/*(N); do 227 s6-ln -s ../$fun ./zsh-functions/ || exit $? 228 done 229 ) || exit $? 230 231 # ln_sub s6-rc s6-rc-source 232 #./command/s6-rc-compile ./s6-rc-db ./s6-rc-source || exit $? 233 234 ln_sub init init-maker 235 ln_sub_command init 236 #./init-maker/install $PWD || exit $? 237 238 ln_sub scripts 239 ln_sub_command scripts 240 ln_sub_zsh_functions scripts 241 242 ln_sub conf 243 ln_sub_command conf 244 ln_sub_zsh_functions conf 245 246 # After all is symlinked the rest of installation is done by conf submodule 247 ${sm_dst[.]}/conf/install || exit $? 248 } 249 250 ### Main code {{{1 251 preinstall() { 252 typeset -g preinstall_current 253 [[ -e /current ]] || return 0 254 255 local -A statinfo 256 zstat -H statinfo +link /current 257 preinstall_current=$statinfo[link] 258 preinstall_current_p=${${:-/current}:P} 259 # for postinstall script 260 export preinstall_current preinstall_current_p 261 s6-rename /previous /previous.1 262 s6-ln -nsf $preinstall_current /previous || exit $? 263 } 264 265 postinstall_or_rollback() { 266 local l ret sub 267 268 if ! ${sm_dst[.]}/conf/postinstall; then 269 if [[ -n $preinstall_current ]]; then 270 echo >&2 "Fatal: postinstall script failed, rolling back to $preinstall_current" 271 s6-ln -nsf $preinstall_current /current || exit $? 272 s6-rename /previous.1 /previous 273 else 274 echo >&2 "Fatal: postinstall script failed for initial installation, removing /current" 275 zf_rm /current 276 fi 277 exit 1 278 fi 279 git tag --force deployed 280 # Mark current HEAD as master in submodules 281 for sub in $submodule_list; do 282 (cd $sub && git checkout -B master HEAD) 283 done 284 } 285 286 ensure_head() { 287 local line sub_state sub_commit sub_name ret git_sub_changes 288 local -a git_status git_log_to_head git_log_from_head 289 290 git_status=( ${(f)"$(git status -s --ignore-submodules=dirty)"}) \ 291 || die_ret $? "Fatal: 'git status' failed" 292 if (( $git_status[(I)???.gitmodules] )); then 293 die ".gitmodules uncommited, please make commit manually" 294 fi 295 296 git_status=( ${(f)"$(git submodule status --cached)"}) \ 297 || die_ret $? "Fatal: 'git submodule status' failed" 298 for line in $git_status; do 299 sub_state=${line[1]} 300 sub_commit=${line[2,41]} 301 sub_name=${line[43,-1]% \(*} 302 submodule=$sub_name 303 case $sub_state in 304 ('U') die "Submodule ${(qqq)sub_name} has merge conflicts" 305 ;; 306 ('-') # Submodule not initialized 307 git submodule update --init -- $sub_name \ 308 || die_ret $? "Failed to initilize submodule ${(qqq)sub_name}" 309 if (($+submodule_has_submodules[$sub_name])); then 310 (cd $sub_name && git submodule update --init) || exit $? 311 fi 312 ;; 313 ('+') # Commit in submodule differs from expected one 314 # Check if desired commit is ancestor of current 315 # Note: might return 128 if revision wasn't fetched yet 316 git_log_to_head=( ${(f)"$( cd ./$sub_name && git log --format=oneline $sub_commit..HEAD )"} ) 317 ret=$? 318 if (( $ret == 128 )); then # likely: fatal: Invalid revision range 319 (cd $sub_name && git fetch --all) 320 git_log_to_head=( ${(f)"$( git log --format=oneline $sub_commit..HEAD )"} ) 321 ret=$? 322 fi 323 (( $ret )) && die_ret $? "git log returned error" 324 git_log_from_head=( ${(f)"$( cd ./$sub_name && git log --format=oneline HEAD..$sub_commit )"} ) 325 if (($#git_log_to_head && $#git_log_from_head == 0)); then 326 # Can advance directly from saved commit to HEAD 327 echo "$#git_log_to_head new commits in submodule. Add and commit them?" 328 printf 'last submodule commit: %s\n' $git_log_to_head[1] 329 local REPLY 330 read -q || exit 0 331 git add -- $sub_name || exit $? 332 git commit -m "update ${(qqq)sub_name} submodule" -- $sub_name || exit $? 333 if ! (($+submodule_ignore_changes[$sub_name])); then 334 require committed 335 fi 336 elif (($#git_log_to_head == 0 && $#git_log_from_head)); then 337 # Can advance directly from HEAD to saved commit 338 if ! (($+submodule_ignore_changes[$sub_name])); then 339 git_sub_changes=$(cd $sub_name && git status -s --ignore-submodules=dirty) \ 340 || die "Error from git status in ${(qqq)sub_name}" 341 [[ -z $git_sub_changes ]] \ 342 || die "Uncommitted changes in ${(qqq)sub_name}" 343 fi 344 (cd $sub_name && git reset --hard && git clean -fx) || exit $? 345 git submodule update --force -- $sub_name 346 elif (($#git_log_to_head && $#git_log_from_head)); then 347 # HEAD diverged from saved commit 348 die "Submodule ${(qqq)sub_name} HEAD diverged." 349 else 350 # No shared history!? 351 die "Submodule ${(qqq)sub_name} reports no shared history." 352 fi 353 ;; 354 (' ') # Commit in submodule same as in tree 355 if ! (($+submodule_ignore_changes[$sub_name])); then 356 require committed 357 fi 358 ;; 359 (*) die "Submodule ${(qqq)sub_name} has unknown state ${(qqq)sub_state}" 360 ;; 361 esac 362 done 363 submodule=. 364 require committed 365 } 366 367 load_commits() { 368 typeset -gA sm_commit sm_dst 369 local line sub_state sub_commit sub_name 370 local -a git_status 371 372 sm_commit[.]=$(git show -s --pretty=format:%H%n) || exit $? 373 sm_dst[.]=/versions/all.${sm_commit[.]} 374 375 git_status=( ${(f)"$(git submodule status --cached)"}) \ 376 || die_ret $? "Fatal: 'git submodule status' failed" 377 for line in $git_status; do 378 sub_state=${line[1]} 379 sub_commit=${line[2,41]} 380 sub_name=${line[43,-1]% \(*} 381 case $sub_state in 382 (' ') 383 sm_commit[$sub_name]=$sub_commit 384 sm_dst[$sub_name]=/versions/$sub_name.${sm_commit[$sub_name]} 385 ;; 386 (*) die "Submodule ${(qqq)sub_name} not clean (state ${(qqq)sub_state})" 387 ;; 388 esac 389 done 390 } 391 392 main() { 393 local sub 394 ensure_head 395 load_commits 396 397 for sub in $submodule_list .; do 398 submodule=$sub 399 require installed 400 if [[ -d ${sm_dst[$sub]}/command ]]; then 401 PATH=${sm_dst[$sub]}/command:$PATH 402 fi 403 done 404 405 require current 406 } 407 408 if (($+trace_func)); then 409 # Use trace_func environment variable as a pattern and enable tracing 410 # on all functions that match it. 411 typeset -g function_to_trace 412 for function_to_trace in ${(k)functions}; do 413 if [[ $function_to_trace == $~trace_func ]]; then 414 typeset -f -t $function_to_trace || die "something wrong with typeset -f -t $function_to_trace" 415 printf >&2 '+++ tracing enabled for function %s\n' ${(qqq)function_to_trace} 416 fi 417 done 418 unset function_to_trace 419 fi 420 421 typeset -g submodule 422 423 main