diff options
-rw-r--r-- | .dir-locals.el | 4 | ||||
-rw-r--r-- | .github/workflows/main.yml | 2 | ||||
-rw-r--r-- | .gitignore | 21 | ||||
-rw-r--r-- | INSTALL | 33 | ||||
-rw-r--r-- | Makefile | 69 | ||||
-rw-r--r-- | README | 13 | ||||
-rw-r--r-- | bin/cpt-readlink.c | 47 | ||||
-rw-r--r-- | bin/cpt-stat.c | 41 | ||||
-rw-r--r-- | clean.do | 5 | ||||
-rw-r--r-- | config.mk | 25 | ||||
-rw-r--r-- | config.rc | 22 | ||||
-rwxr-xr-x | contrib/cpt-chbuild | 15 | ||||
-rwxr-xr-x | contrib/cpt-owns | 2 | ||||
-rw-r--r-- | default.do | 40 | ||||
-rw-r--r-- | docs/cpt.org | 150 | ||||
-rw-r--r-- | docs/cpt.texi | 143 | ||||
-rw-r--r-- | docs/default.do | 33 | ||||
-rw-r--r-- | install.do | 17 | ||||
-rw-r--r-- | lib.rc | 60 | ||||
-rw-r--r-- | man/cpt-contrib.1 | 10 | ||||
-rw-r--r-- | src/Makefile | 4 | ||||
-rwxr-xr-x | src/cpt | 6 | ||||
-rwxr-xr-x | src/cpt-build | 2 | ||||
-rwxr-xr-x | src/cpt-checksum | 3 | ||||
-rwxr-xr-x | src/cpt-install | 2 | ||||
-rw-r--r-- | src/cpt-lib.in (renamed from src/cpt-lib) | 189 | ||||
-rwxr-xr-x | src/cpt-list | 12 | ||||
-rwxr-xr-x | src/cpt-remove | 2 | ||||
-rw-r--r-- | src/test.do | 7 | ||||
-rwxr-xr-x | tools/do | 446 | ||||
-rwxr-xr-x | tools/install.sh | 90 | ||||
-rw-r--r-- | uninstall.do | 14 |
32 files changed, 1263 insertions, 266 deletions
diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..81ec972 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,4 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((sh-mode . ((flycheck-shellcheck-excluded-warnings . ("2119"))))) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 836d891..48b29d9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,4 +10,4 @@ jobs: steps: - uses: actions/checkout@v1 - name: Run tests. - run: make test + run: ./tools/do test @@ -1,8 +1,5 @@ -### Binaries/Objects ### -cpt-stat -cpt-readlink -getopt -*.o +# Built library +cpt-lib ### Emacs ### \#*\# @@ -14,4 +11,16 @@ getopt [._]*.sw[a-p] [._]s[a-rt-v][a-z] [._]ss[a-gi-z] -[._]sw[a-p]
\ No newline at end of file +[._]sw[a-p] + +### Redo files +/.redo +/.do_built +/.do_built.dir +*.tmp +*.did +.dep* +.target* + +### Texinfo ### +*.info @@ -0,0 +1,33 @@ +Installation Instructions +========================= + +In order to install cpt, you either need a redo implementation on your system, +or use the `do` script inside the `tools/` directory (which is slower but the +source code isn't that big anyways). + +Paths and version information can be configured from the `config.rc` file or +through environment variables. + + +Installing from a git checkout +------------------------------ + +On the git checkout of the repository, the info page for the package manager +doesn't exist. The build system looks for `makeinfo` in order to build the info +page, but exits with success if it isn't on your system. That's why you +shouldn't edit the MAKEINFO variable even if you don't have it on your system, +it is optional. + + +With redo +--------- + + redo + DESTDIR= PREFIX=/usr/local redo install + + +With minimal do +--------------- + + ./tools/do + DESTDIR= PREFIX=/usr/local ./tools/do install diff --git a/Makefile b/Makefile deleted file mode 100644 index 1f03f57..0000000 --- a/Makefile +++ /dev/null @@ -1,69 +0,0 @@ -# See LICENSE for copyright information -include config.mk - -SRC = bin/cpt-readlink.c bin/cpt-stat.c -OBJ = ${SRC:.c=.o} -BIN = ${SRC:.c=} - -.SUFFIXES: -.SUFFIXES: .o .c .org .texi .info - -.org.texi: - ${EMACS} $< --batch -f org-texinfo-export-to-texinfo - -.texi.info: - ${MAKEINFO} $< -o $@ - -all: ${BIN} - -.c: - ${CC} ${CFLAGS} ${LDFLAGS} -o $@ $< ${LIBS} - -clean: - rm -f ${BIN} ${OBJ} - -test: ${BIN} - bin/cpt-stat bin - bin/cpt-stat Makefile - bin/cpt-readlink /bin/sh - ${MAKE} -C src test - -install-bin: ${BIN} - for bin in ${BIN}; do \ - install -Dm755 $${bin} ${DESTDIR}${BINDIR}/$${bin##*/}; done - -install-src: - for bin in src/cpt-*; do \ - install -Dm755 $${bin} ${DESTDIR}${BINDIR}/$${bin##*/}; done - -install-contrib: - for bin in contrib/cpt-*; do \ - install -Dm755 $${bin} ${DESTDIR}${BINDIR}/$${bin##*/}; done - -install-contrib-static: - mkdir -p ${DESTDIR}${BINDIR} - for bin in contrib/cpt-*; do \ - sed '/\. cpt-lib/r src/cpt-lib' $${bin} | \ - sed '/\. cpt-lib/d' > ${DESTDIR}${BINDIR}/$${bin##*/}; \ - chmod 755 ${DESTDIR}${BINDIR}/$${bin##*/}; done - -install-src-static: - mkdir -p ${DESTDIR}${BINDIR} - for bin in src/cpt-*; do \ - sed '/\. cpt-lib/r src/cpt-lib' $${bin} | \ - sed '/\. cpt-lib/d' > ${DESTDIR}${BINDIR}/$${bin##*/}; \ - chmod 755 ${DESTDIR}${BINDIR}/$${bin##*/}; done - -install-doc: - for man in man/*.1; do install -Dm644 $${man} ${DESTDIR}${MAN1}/$${man##*/}; done - -install: install-bin install-src install-contrib install-doc -install-static: install-bin install-src-static install-contrib-static install-doc - -uninstall: - for bin in ${BIN} src/cpt-* contrib/cpt-*; do \ - rm -f ${DESTDIR}${BINDIR}/$${bin##*/}; done - for man in man/*; do rm -f ${DESTDIR}${MAN1}/$${man##*/}; done - - -.PHONY: all install-bin install-src install-contrib install-doc install-src-static install-contrib-static install uninstall test clean @@ -19,25 +19,22 @@ Dependencies To build and use cpt, you need the following software. -- C compiler [make] +RUNTIME DEPENDS - rsync - curl -- getopt [provided by cpt if not available] - POSIX base utilities [coreutils, busybox, sbase, etc.] - tar [GNU tar, busybox, toybox, libarchive, etc.] -Build configuration can be done from the 'config.mk' file. If you have getopt -on your system, add SYSTEM_GETOPT=1 option to your 'config.mk'. +MAKE DEPENDS +- redo (optional, the repository contains tools/do) Directory Structure -------------------------------------------------------------------------------- - / -- cpt, README, Makefile, LICENSE, CHANGELOG - bin/ -- for C programs. + / -- README, LICENSE, CHANGELOG contrib/ -- for Shell scripts that wrap around cpt. - doc/ -- for documentation. - getopt-ul -- for cpt provided util-linux standalone getopt(1). + docs/ -- for documentation. man/ -- for manual pages. src/ -- for the tools that make up the package manager. diff --git a/bin/cpt-readlink.c b/bin/cpt-readlink.c deleted file mode 100644 index e7cfe50..0000000 --- a/bin/cpt-readlink.c +++ /dev/null @@ -1,47 +0,0 @@ -/* cpt-readlink --- a utility replacement for readlink - * See LICENSE for copyright information. - * - * This is basically a 'readlink -f' command. - */ -#include <stdio.h> -#include <stdlib.h> -#include <libgen.h> -#include <string.h> -#include <limits.h> - -#define DIR_MAX PATH_MAX - NAME_MAX - 1 - - -char *realpath(const char *path, char *resolved_path); - -int -main(int argc, char *argv[]) -{ - - char buf[PATH_MAX]; - - /* We are going to use these if the file doesn't exist, but we can still - * use directories above the file. We are using dname and bname so that - * they don't clash with the functions with the same name. - */ - char dname[DIR_MAX]; /* directory name */ - char bname[NAME_MAX]; /* base name */ - sprintf(bname, "%s", (basename(argv[1]))); - - if (argc != 2 || strcmp(argv[1], "--help") == 0) { - fprintf(stderr, "usage: %s [file]\n", argv[0]); - return 1; - } - - if (!realpath(argv[1], buf)) { - - if (!realpath(dirname(argv[1]), dname)) { - perror(argv[0]); - return 1; - } - sprintf(buf, "%s/%s", dname, bname); - } - - printf("%s\n", buf); - return 0; -} diff --git a/bin/cpt-stat.c b/bin/cpt-stat.c deleted file mode 100644 index 584c2df..0000000 --- a/bin/cpt-stat.c +++ /dev/null @@ -1,41 +0,0 @@ -/* cpt-stat --- a utility for getting the user name of file owner - * See LICENSE for copyright information - * - * The reason this simple tool exists is because 'stat' is not - * portable and ls is not exactly stable enough for scripting. - * This program is for outputting the owner name, and that's it. - */ - -#include <pwd.h> -#include <sys/stat.h> -#include <stdio.h> -#include <string.h> - -struct passwd *pw; -struct stat sb; - -int -main (int argc, char *argv[]) -{ - /* Exit if no or multiple arguments are given. */ - if (argc != 2 || strcmp(argv[1], "--help") == 0) { - fprintf(stderr, "Usage: %s [pathname]\n", argv[0]); - return 1; - } - - /* Exit if file stat cannot be obtained. */ - if (lstat(argv[1], &sb) == -1) { - perror(argv[0]); - return 1; - } - - /* Exit if name of the owner cannot be retrieved. */ - if (!getpwuid(sb.st_uid)) { - return 1; - } - - /* Print the user name of file owner. */ - pw = getpwuid(sb.st_uid); - printf("%s\n", pw->pw_name); - return 0; -} diff --git a/clean.do b/clean.do new file mode 100644 index 0000000..57ab99f --- /dev/null +++ b/clean.do @@ -0,0 +1,5 @@ +. ./config.rc +redo src/clean +redo_clean +rm -f "cpt-$VERSION.tar.xz" +find docs -name '*.info' -exec rm -f -- {} + diff --git a/config.mk b/config.mk deleted file mode 100644 index ba76118..0000000 --- a/config.mk +++ /dev/null @@ -1,25 +0,0 @@ -# See LICENSE for copyright information -VERSION = 5.1.1 - -# Paths -PREFIX = /usr/local -BINDIR = ${PREFIX}/bin -SHAREDIR = ${PREFIX}/share -DOCDIR = ${SHAREDIR}/doc -CPTDOC = ${DOCDIR}/cpt -MANPREFIX = ${SHAREDIR}/man -MAN1 = ${MANPREFIX}/man1 - -# Flags -CFLAGS = -std=c99 -Wpedantic -Wall -Os -CFLAGS += -D_XOPEN_SOURCE=700 -LDFLAGS = -s -static -LIBS = -lc - -# C compiler and linker -CC = cc -LD = ${CC} - -# Documentation tools -EMACS = emacs -MAKEINFO = makeinfo diff --git a/config.rc b/config.rc new file mode 100644 index 0000000..963e71e --- /dev/null +++ b/config.rc @@ -0,0 +1,22 @@ +# -*- mode: redo -*- +# See LICENSE for copyright information + +# This loads some functions that are shared between redo files. +# shellcheck source=./lib.rc +. "${SRC_ROOT:=.}/lib.rc" + +setv VERSION = 5.1.1 + +# Paths +setv PREFIX = /usr/local +setv BINDIR = "${PREFIX}/bin" +setv SHAREDIR = "${PREFIX}/share" +setv DOCDIR = "${SHAREDIR}/doc" +setv CPTDOC = "${DOCDIR}/cpt" +setv INFODIR = "${SHAREDIR}/info" +setv MANPREFIX = "${SHAREDIR}/man" +setv MAN1 = "${MANPREFIX}/man1" + +# Documentation tools +setv EMACS = emacs +setv MAKEINFO = makeinfo diff --git a/contrib/cpt-chbuild b/contrib/cpt-chbuild index 923a7fa..6177e67 100755 --- a/contrib/cpt-chbuild +++ b/contrib/cpt-chbuild @@ -18,7 +18,6 @@ die() { case "$(uname -m)" in i*86) arch=i686; esac url="https://dl.carbslinux.org/releases/${arch:-$(uname -m)}/carbs-rootfs.tar.xz" -pid=$$ cd "$cac_dir" @@ -44,20 +43,22 @@ sh256 carbs-rootfs.tar.xz | diff - carbs-rootfs.tar.xz.sum || ( cd carbs-chroot; tar xf ../carbs-rootfs.tar.xz ) } +create_cache empty + log "Creating temporary chroot" -cp -a carbs-chroot "chroot-$pid" +cp -a carbs-chroot "${chr_dir:=$tmp_dir/chroot}" [ "$1" ] && { log "Installing extra packages" - CPT_ROOT=$PWD/chroot-$pid cpt-install "$@" + CPT_ROOT=$chr_dir cpt-install "$@" } -run_hook pre-chroot "" "$PWD/chroot-$pid" +run_hook pre-chroot "" "$chr_dir" log "Entering chroot" if [ "$(id -u)" -eq 0 ]; then - cpt-chroot "chroot-$pid" - rm -rf "chroot-$pid" + cpt-chroot "$chr_dir" + rm -rf "$chr_dir" else - as_root sh -c "cpt-chroot chroot-$pid; rm -rf chroot-$pid" + as_root sh -c "cpt-chroot $chr_dir; rm -rf $chr_dir" fi diff --git a/contrib/cpt-owns b/contrib/cpt-owns index 5f73674..494e0fd 100755 --- a/contrib/cpt-owns +++ b/contrib/cpt-owns @@ -17,7 +17,7 @@ esac # Strip 'CPT_ROOT' from the file path if passed and # follow symlinks. file="${1#$CPT_ROOT}" -dirname=$(cpt-readlink "$CPT_ROOT/${file%/*}") +dirname=$(_readlinkf "$CPT_ROOT/${file%/*}") file="$dirname/${file##*/}" # Check if the file exists and exit if it is not. diff --git a/default.do b/default.do new file mode 100644 index 0000000..23cedf8 --- /dev/null +++ b/default.do @@ -0,0 +1,40 @@ +. ./config.rc + +case "$1" in + all) redo-ifchange src/cpt-lib docs/all ;; + dist) + redo clean + redo "cpt-$VERSION.tar.xz" + ;; + src/cpt-lib) + redo-ifchange "$1.in" + sed -e "s|@VERSION@|$VERSION|g" \ + -e "s|@DOCSTRING@|Call functions from the library|g" < "$1.in" > "$3" + chmod +x "$3" + ;; + "cpt-$VERSION.tar.xz") + redo docs/cpt.info + rm -rf -- "cpt-$VERSION" + mkdir -p "cpt-$VERSION" + { git ls-tree -r HEAD --name-only && echo docs/cpt.info ;} | + while read -r file; do + [ "${file##*/*}" ] || + mkdir -p "cpt-$VERSION/${file%/*}" + cp "$file" "cpt-$VERSION/$file" + done + tar cf "cpt-$VERSION.tar" "cpt-$VERSION" + xz -z "cpt-$VERSION.tar" + rm -rf -- "cpt-$VERSION" + mv "$1" "$3" + ;; + test) + redo src/test + ;; + src/clean) + rm -f src/cpt-lib + PHONY + ;; + *) + echo "Unknown target $1" + exit 99 +esac diff --git a/docs/cpt.org b/docs/cpt.org index bff8ac9..dfddc38 100644 --- a/docs/cpt.org +++ b/docs/cpt.org @@ -48,7 +48,9 @@ manual for *Carbs Packaging Tools*. For development logs see [[https://git.carbs - [[#option-parsing][Option parsing]] - [[#message-functions][Message functions]] - [[#text-functions][Text functions]] + - [[#portability-functions][Portability functions]] - [[#system-functions][System Functions]] + - [[#package-functions][Package Functions]] * Copying :PROPERTIES: @@ -890,11 +892,11 @@ Following functions are used to manipulate, check, or interact with text. given string. If the string is inside the list, it will return 0, otherwise 1. #+begin_src sh - # Usage - contains "$LIST" foo +# Usage +contains "$LIST" foo - contains "foo bar" foo # Returns 0 - contains "foo bar" baz # Returns 1 +contains "foo bar" foo # Returns 0 +contains "foo bar" baz # Returns 1 #+end_src *** =regesc()= @@ -906,7 +908,7 @@ given string. If the string is inside the list, it will return 0, otherwise 1. in POSIX BRE. Those characters are, =$=, =.=, =*=, =[=, =\\=, and =^=. #+begin_src sh - regesc '^[$\' # Returns \^\[\$\\ +regesc '^[$\' # Returns \^\[\$\\ #+end_src *** =pop()= @@ -918,13 +920,75 @@ in POSIX BRE. Those characters are, =$=, =.=, =*=, =[=, =\\=, and =^=. call. Word splitting is intentional when using this function. #+begin_src sh - # Usage - pop foo from $LIST +# Usage +pop foo from $LIST - pop foo from foo baz bar # Returns baz bar +pop foo from foo baz bar # Returns baz bar #+end_src -** System Functions +*** =sepchar()= +:PROPERTIES: +:DESCRIPTION: Separate characters from a string +:END: + +This function can be used to separate characters from the given string without +resorting to external resources. + +#+begin_src sh +sepchar mystring +# Prints: +# m +# y +# s +# t +# r +# i +# n +# g +#+end_src + +** Portability functions +:PROPERTIES: +:DESCRIPTION: Functions to replace non-POSIX commands +:END: + +These helper functions are used so that we don't depend on non-POSIX programs for +certain functionality. They are prefixed with the =_= character. + +*** =_seq()= +:PROPERTIES: +:DESCRIPTION: 'seq(1)' but no newline +:END: + +This function is similar to =seq(1)= except that it only takes a single argument +and doesn't print any newlines. It is suitable to be used in =for= loops. + +#+begin_src sh +_seq 5 +# Prints: +# 1 2 3 4 5 +#+end_src + +*** =_stat()= +:PROPERTIES: +:DESCRIPTION: 'stat %U' replacement +:END: + +This function imitates =stat %U=. =stat= isn't defined by POSIX, and this is +also a GNU extension. This function returns the owner of a file. If the owner +cannot be found, it will return =root=. + +*** =_readlinkf()= +:PROPERTIES: +:DESCRIPTION: 'readlink -f' replacement +:END: + +This function was taken from [[https://github.com/ko1nksm/readlinkf][POSIX sh readlinkf library by Koichi Nakashima]]. +=readlink= is also not defined by POSIX, so this function uses =ls= to follow +symbolic links until it reaches the actual file. + +** TODO System Functions +- [ ] Add description *** =as_root()= :PROPERTIES: :DESCRIPTION: Run a command as the root user @@ -935,6 +999,68 @@ environment variable is set, it will call the following arguments as the root user. It supports the following programs for privilege escalation with the following order: -1. =sudo= -2. =doas= -3. =su= +1. =sls= +2. =sudo= +3. =doas= +4. =su= + +** TODO Package Functions +:PROPERTIES: +:DESCRIPTION: Manipulate, or query anything related to packages +:END: + +Obviously, package functions are the most important ones for =cpt-lib=, those +are the ones you will use to build, to query, to manipulate, or to otherwise +interact with packages. + +*** =pkg_owner()= +:PROPERTIES: +:DESCRIPTION: Check which package owns the given file +:END: + +This function can be used to determine the owner of a package. The first +argument is used for flags that will be passed to =grep=, and the second one is +for the file query. Rest of the arguments can be used in order to specify the +manifests to be used, but it is optional. =pkg_owner()= will search for all the +installed packages if no other arguments are given. + +#+begin_src sh +# Example +pkg_owner -lFx /usr/bin/grep # Returns 'busybox' + +# An example call made by `pkg_fix_deps()` to figure out whether the built +# package contains the file it depends. +pkg_owner -l "/${dep#/}\$" "$PWD/manifest" >/dev/null && continue +pkg_owner -l "/${dep#/}\$" "$@" ||: +#+end_src + +*** =pkg_isbuilt()= +:PROPERTIES: +:DESCRIPTION: Check whether the given package is built +:END: + +This function returns with success when the given package has a built tarball +with the matching version and release strings from the repository. + +*** =pkg_lint()= +:PROPERTIES: +:DESCRIPTION: Check whether a package directory fits the standards +:END: + +This function checks whether a given package fits the proper package +specification. This function *does not return with failure, it exits outright* +if it fails. + +*** TODO =pkg_find()= +:PROPERTIES: +:DESCRIPTION: Query package locations +:END: + +=pkg_find()= + +*** TODO =pkg_gentree= +:PROPERTIES: +:DESCRIPTION: Generate a dependency tree for the given package +:END: + +Keep in mind /etc/cpt-base diff --git a/docs/cpt.texi b/docs/cpt.texi index 9a9974f..7c1a95f 100644 --- a/docs/cpt.texi +++ b/docs/cpt.texi @@ -106,7 +106,9 @@ CPT Library * Option parsing:: Easy way of parsing options with cpt-lib * Message functions:: Communicate to users * Text functions:: Manipulate or check text +* Portability functions:: Functions to replace non-POSIX commands * System Functions:: +* Package Functions:: Manipulate, or query anything related to packages Option parsing @@ -126,11 +128,26 @@ Text functions * @samp{contains()}:: Check if a "string list" contains a word * @samp{regesc()}:: Escape regular expression characters * @samp{pop()}:: Remove an item from a string list +* @samp{sepchar()}:: Separate characters from a string + +Portability functions + +* @samp{_seq()}:: 'seq(1)' but no newline +* @samp{_stat()}:: 'stat %U' replacement +* @samp{_readlinkf()}:: 'readlink -f' replacement System Functions * @samp{as_root()}:: Run a command as the root user +Package Functions + +* @samp{pkg_owner()}:: Check which package owns the given file +* @samp{pkg_isbuilt()}:: Check whether the given package is built +* @samp{pkg_lint()}:: Check whether a package directory fits the standards +* @samp{pkg_find()}:: Query package locations +* @samp{pkg_gentree}:: Generate a dependency tree for the given package + @end detailmenu @end menu @@ -888,7 +905,9 @@ package manager library. * Option parsing:: Easy way of parsing options with cpt-lib * Message functions:: Communicate to users * Text functions:: Manipulate or check text +* Portability functions:: Functions to replace non-POSIX commands * System Functions:: +* Package Functions:: Manipulate, or query anything related to packages @end menu @node Calling the library @@ -1048,6 +1067,7 @@ Following functions are used to manipulate, check, or interact with text. * @samp{contains()}:: Check if a "string list" contains a word * @samp{regesc()}:: Escape regular expression characters * @samp{pop()}:: Remove an item from a string list +* @samp{sepchar()}:: Separate characters from a string @end menu @node @samp{contains()} @@ -1087,8 +1107,70 @@ pop foo from $LIST pop foo from foo baz bar # Returns baz bar @end example +@node @samp{sepchar()} +@subsection @samp{sepchar()} + +This function can be used to separate characters from the given string without +resorting to external resources. + +@example +sepchar mystring +# Prints: +# m +# y +# s +# t +# r +# i +# n +# g +@end example + +@node Portability functions +@section Portability functions + +These helper functions are used so that we don't depend on non-POSIX programs for +certain functionality. They are prefixed with the @samp{_} character. + +@menu +* @samp{_seq()}:: 'seq(1)' but no newline +* @samp{_stat()}:: 'stat %U' replacement +* @samp{_readlinkf()}:: 'readlink -f' replacement +@end menu + +@node @samp{_seq()} +@subsection @samp{_seq()} + +This function is similar to @samp{seq(1)} except that it only takes a single argument +and doesn't print any newlines. It is suitable to be used in @samp{for} loops. + +@example +_seq 5 +# Prints: +# 1 2 3 4 5 +@end example + +@node @samp{_stat()} +@subsection @samp{_stat()} + +This function imitates @samp{stat %U}. @samp{stat} isn't defined by POSIX, and this is +also a GNU extension. This function returns the owner of a file. If the owner +cannot be found, it will return @samp{root}. + +@node @samp{_readlinkf()} +@subsection @samp{_readlinkf()} + +This function was taken from @uref{https://github.com/ko1nksm/readlinkf, POSIX sh readlinkf library by Koichi Nakashima}. +@samp{readlink} is also not defined by POSIX, so this function uses @samp{ls} to follow +symbolic links until it reaches the actual file. + @node System Functions -@section System Functions +@section @strong{TODO} System Functions + +@itemize +@item +Add description +@end itemize @menu * @samp{as_root()}:: Run a command as the root user @@ -1104,6 +1186,8 @@ following order: @enumerate @item +@samp{sls} +@item @samp{sudo} @item @samp{doas} @@ -1111,4 +1195,61 @@ following order: @samp{su} @end enumerate +@node Package Functions +@section @strong{TODO} Package Functions + +Obviously, package functions are the most important ones for @samp{cpt-lib}, those +are the ones you will use to build, to query, to manipulate, or to otherwise +interact with packages. + +@menu +* @samp{pkg_owner()}:: Check which package owns the given file +* @samp{pkg_isbuilt()}:: Check whether the given package is built +* @samp{pkg_lint()}:: Check whether a package directory fits the standards +* @samp{pkg_find()}:: Query package locations +* @samp{pkg_gentree}:: Generate a dependency tree for the given package +@end menu + +@node @samp{pkg_owner()} +@subsection @samp{pkg_owner()} + +This function can be used to determine the owner of a package. The first +argument is used for flags that will be passed to @samp{grep}, and the second one is +for the file query. Rest of the arguments can be used in order to specify the +manifests to be used, but it is optional. @samp{pkg_owner()} will search for all the +installed packages if no other arguments are given. + +@example +# Example +pkg_owner -lFx /usr/bin/grep # Returns 'busybox' + +# An example call made by `pkg_fix_deps()` to figure out whether the built +# package contains the file it depends. +pkg_owner -l "/$@{dep#/@}\$" "$PWD/manifest" >/dev/null && continue +pkg_owner -l "/$@{dep#/@}\$" "$@@" ||: +@end example + +@node @samp{pkg_isbuilt()} +@subsection @samp{pkg_isbuilt()} + +This function returns with success when the given package has a built tarball +with the matching version and release strings from the repository. + +@node @samp{pkg_lint()} +@subsection @samp{pkg_lint()} + +This function checks whether a given package fits the proper package +specification. This function @strong{does not return with failure, it exits outright} +if it fails. + +@node @samp{pkg_find()} +@subsection @strong{TODO} @samp{pkg_find()} + +@samp{pkg_find()} + +@node @samp{pkg_gentree} +@subsection @strong{TODO} @samp{pkg_gentree} + +Keep in mind /etc/cpt-base + @bye
\ No newline at end of file diff --git a/docs/default.do b/docs/default.do new file mode 100644 index 0000000..fcf8802 --- /dev/null +++ b/docs/default.do @@ -0,0 +1,33 @@ +SRC_ROOT=.. +. ../config.rc + +# Extensionless name of file +fn="${1%.*}" + +case "$1" in + all) redo info ;; + allclean) redo ../clean; rm -f cpt.texi ;; + info) redo-ifchange cpt.info cpt.texi cpt.org ;; + *.info) + # Don't bother if makeinfo doesn't exist on the system, exit with success. + if ! command -v $MAKEINFO >/dev/null; then + PHONY + exit 0 + fi + redo-ifchange "$fn.texi" + $MAKEINFO "$fn.texi" -o "$3" + ;; + *.texi) + [ -f "$fn.org" ] || exit 0 + redo-ifchange "$fn.org" + cp "$fn.org" "$3.org" + $EMACS "$3.org" --batch -f org-texinfo-export-to-texinfo + rm -f "$3.org" + mv "$3.texi" "$3" + ;; + *) + echo "Unknown target $1" + exit 99 +esac + +PHONY all info html diff --git a/install.do b/install.do new file mode 100644 index 0000000..dcab154 --- /dev/null +++ b/install.do @@ -0,0 +1,17 @@ +. ./config.rc +redo all +PHONY + +INSTALLSH=./tools/install.sh + +# Install executables. +"$INSTALLSH" -Dm755 -t "$DESTDIR$BINDIR" $(getbin) + +# Install manual pages. +"$INSTALLSH" -Dm644 -t "$DESTDIR$MAN1" man/*.1 + +# Install the documentation info page. +# We don't want to bother if the info page wasn't created, just exit without an +# error. +[ -f docs/cpt.info ] || exit 0 +"$INSTALLSH" -Dm644 docs/cpt.info "$DESTDIR$INFODIR/cpt.info" @@ -0,0 +1,60 @@ +# -*- mode: redo -*- +# Helper functions +target=$1 basename=$2 dest=$3 + +# Make all targets dependent on the library and the config file +redo-ifchange "$SRC_ROOT/lib.rc" "$SRC_ROOT/config.rc" + +setv() { + # Set variables if unset. Works similar to the Makefile syntax. + [ "$3" ] || { + printf '%s\n' "Faulty variable syntax" >&2 + exit 1 + } + var=$1; sym=$2; shift 2 + case "$sym" in + \?=|=) eval "[ \"\$$var\" ]" || export "$var=$*" ;; + +=) eval "export \"$var=\$$var $*\"" + esac +} + +redo_clean() { + # Clean function for various redo implementations + [ -r .do_built ] && { + while read -r file; do + [ -d "$file" ] || rm -f "$file" + done < .do_built + } + find . -type f \( -name '*.tmp' -o -name '*.did' -o -name '.dep*' -o -name '.target*' \) \ + -exec rm -f -- {} + + [ "$DO_BUILT" ] || find . -name '.do_built*' -exec rm -rf -- {} + + [ "$REDO_BASE" ] || find . -name .redo -type d -exec rm -rf -- {} + +} + +targcheck() { + # Usage: targcheck [target...] + # + # Check if current target is one of the given arguments of this function. + # Returns 0 if target is one of the arguments, returns 1 if not. + case " $* " in *" $target "*) return 0; esac; return 1 +} + +PHONY() { + # Usage: PHONY [[target...]] + # + # Function that resembles the .PHONY: target on the classic 'make' system. + # You can either use it without an argument on a single target, or specify + # multiple targets. + if [ -z "$1" ] || targcheck "$@"; then + # shellcheck disable=2064 + trap "rm -f $dest" EXIT INT + fi +} + +getbin() { + # Function to get all executables + find src contrib \( -name cpt -o -name 'cpt-*' \) ! -name '*.in' ! -name '*.did' +} + +# Phony targets +PHONY all dist clean install uninstall test diff --git a/man/cpt-contrib.1 b/man/cpt-contrib.1 index 8a34364..ffc5e54 100644 --- a/man/cpt-contrib.1 +++ b/man/cpt-contrib.1 @@ -88,11 +88,6 @@ can be used to check if personal packages are outdated. <file> Checks which package has installed the given file. -.SH CPT-READLINK -.B cpt-readlink -<file> - -A 'readlink -f' replacement to be used inside the package manager. .SH CPT-REPODEPENDS .B cpt-repodepends <pkg> @@ -112,11 +107,6 @@ Prints the packages that depend on the given package. (Reverse dependencies) <pkg> Prints the given package's size, and its individual files. -.SH CPT-STAT -.B cpt-stat -<file> - -Outputs the owner name of a file/directory .SH CPT-WHICH .B cpt-which <pkg> diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index aae4e59..0000000 --- a/src/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -test: - shellcheck -x -f gcc ./cpt* ../contrib/* - -.PHONY: test @@ -16,11 +16,9 @@ case "$arg" in done for path; do - # These are the files to be ignored. - contains "lib readlink stat" "$path" && continue - printf "%b->%b %-${max}s " "$colorb" "$colre" "${path#*/cpt-}" - sed -n 's/^# *//;2p' "$(command -v "cpt-$path")" + awk 'NR==2{if(/^# /){sub(/^# */,"");print}else print "";exit}' \ + "$(command -v "cpt-$path")" done | sort -uk1 >&2 exit ;; diff --git a/src/cpt-build b/src/cpt-build index d67f1a6..cb93949 100755 --- a/src/cpt-build +++ b/src/cpt-build @@ -10,7 +10,7 @@ parser_definition() { if [ -f ./cpt-lib ]; then . ./cpt-lib; else . cpt-lib; fi -[ "$1" ] || set -- "${PWD##*/}"; export CPT_PATH=${PWD%/*}:$CPT_PATH +[ "$1" ] || { set -- "${PWD##*/}"; export CPT_PATH=${PWD%/*}:$CPT_PATH ;} create_cache diff --git a/src/cpt-checksum b/src/cpt-checksum index 0a83ae0..cd1fa4e 100755 --- a/src/cpt-checksum +++ b/src/cpt-checksum @@ -22,8 +22,7 @@ for pkg; do tee "$repo_dir/checksums" else log "$pkg" "Need permissions to generate checksums" - - user=$(cpt-stat "$repo_dir") as_root tee "$repo_dir/checksums" + user=$(_stat "$repo_dir") as_root tee "$repo_dir/checksums" fi } diff --git a/src/cpt-install b/src/cpt-install index b046e02..aab70ea 100755 --- a/src/cpt-install +++ b/src/cpt-install @@ -10,7 +10,7 @@ parser_definition() { if [ -f ./cpt-lib ]; then . ./cpt-lib; else . cpt-lib; fi -[ "$1" ] || set -- "${PWD##*/}"; export CPT_PATH=${PWD%/*}:$CPT_PATH +[ "$1" ] || { set -- "${PWD##*/}"; export CPT_PATH=${PWD%/*}:$CPT_PATH ;} [ -w "$CPT_ROOT/" ] || [ "$uid" = 0 ] || { as_root "$0" "$@" diff --git a/src/cpt-lib b/src/cpt-lib.in index 6afd030..7728ae8 100644 --- a/src/cpt-lib +++ b/src/cpt-lib.in @@ -1,4 +1,5 @@ #!/bin/sh -ef +# @DOCSTRING@ # shellcheck source=/dev/null # # This is the Carbs Packaging Toolchain written for Carbs Linux. @@ -8,7 +9,7 @@ # Currently maintained by Cem Keylan. version() { - log "Carbs Packaging Tools" 5.1.1 + log "Carbs Packaging Tools" @VERSION@ exit 0 } @@ -44,6 +45,72 @@ trap_set() { esac } +sepchar() ( + # Seperate every character on the given string without resorting to external + # processes. + [ "$1" ] || return 0; str=$1; set -- + while [ "$str" ]; do + str_tmp=$str + for i in $(_seq $(( ${#str} - 1 ))); do + str_tmp=${str_tmp%?} + done + set -- "$@" "$str_tmp" + str=${str#$str_tmp} + done + printf '%s\n' "$@" +) + +_seq() ( + # Pure shell counter meant to be used in 'for' loops. + i=0 buf='' + while [ "$(( i += 1 ))" -le "$1" ]; do + buf="$buf $i " + done + printf '%s' "$buf" +) + +_stat() ( + _user=; eval set -- "$(ls -ld "$1")" + id -u "${_user:=$3}" >/dev/null 2>&1 || _user=root + printf '%s' "$_user" +) + +_readlinkf() ( + # Public domain POSIX sh readlink function by Koichi Nakashima + [ "${1:-}" ] || return 1 + max_symlinks=40 + CDPATH='' # to avoid changing to an unexpected directory + + target=$1 + [ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes + [ -d "${target:-/}" ] && target="$target/" + + cd -P . 2>/dev/null || return 1 + while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do + if [ ! "$target" = "${target%/*}" ]; then + case $target in + /*) cd -P "${target%/*}/" 2>/dev/null || break ;; + *) cd -P "./${target%/*}" 2>/dev/null || break ;; + esac + target=${target##*/} + fi + + if [ ! -L "$target" ]; then + target="${PWD%/}${target:+/}${target}" + printf '%s\n' "${target:-/}" + return 0 + fi + + # `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n", + # <file mode>, <number of links>, <owner name>, <group name>, + # <size>, <date and time>, <pathname of link>, <contents of link> + # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html + link=$(ls -dl -- "$target" 2>/dev/null) || break + target=${link#*" $target -> "} + done + return 1 +) + # This is the public domain getoptions shell library. It also forms a usage # function. # URL: https://github.com/ko1nksm/getoptions (v2.0.1) @@ -346,7 +413,7 @@ as_root() { "$@" case ${su##*/} in - sudo|doas) "$su" -u "$user" -- env "$@" ;; + sls|sudo|doas) "$su" -u "$user" -- env "$@" ;; su) su -c "env $* <&3" "$user" 3<&0 </dev/tty ;; *) die "Invalid CPT_SU value: $su" ;; esac @@ -706,16 +773,29 @@ pkg_depends() { (pkg_list "$1" >/dev/null) && return while read -r dep type || [ "$dep" ]; do + # Skip comments and empty lines. + [ "${dep##\#*}" ] || continue # Skip test dependencies unless $CPT_TEST is set to 1. - case $type in test) [ "$CPT_TEST" = 1 ] || continue; esac + # + # Skip make dependencies on the 'tree' operation for child packages + # or when the 'first-nomake' argument is given. + case $type in + test) [ "$CPT_TEST" = 1 ] || continue ;; + make) [ "$2" = tree ] && [ -z "${3#first-nomake}" ] && continue + esac - # Recurse through the dependencies of the child packages. - [ "${dep##\#*}" ] && pkg_depends "$dep" + # Recurse through the dependencies of the child packages. Forward + # the 'tree' operation. + if [ "$2" = tree ]; then + pkg_depends "$dep" tree + else + pkg_depends "$dep" + fi done 2>/dev/null < "$(pkg_find "$1")/depends" ||: # After child dependencies are added to the list, # add the package which depends on them. - [ "$2" = explicit ] || deps="$deps $1 " + [ "$2" = explicit ] || [ "$3" ] || deps="$deps $1 " } } @@ -1019,6 +1099,11 @@ pkg_build() { diff -U 3 "$pkg_build" .build.cpt > "$pkg_build.diff" && rm -f "$pkg_build.diff" + # We don't want the package manager to track 'dir' pages of the info + # directory. We don't want every single package to create their own dir + # files either. + rm -f "$pkg_dir/$pkg/usr/share/info/dir" + # We never ever want this. Let's end the endless conflicts # and remove it. find "$pkg_dir/$pkg" -name charset.alias -exec rm -f {} + @@ -1145,14 +1230,11 @@ pkg_conflicts() { # Use $CPT_ROOT in filename so that we follow its symlinks. file=$CPT_ROOT/${file#/} - # We will only follow the symlinks of the directories, so we - # reserve the directory name in this 'dirname' value. cpt-readlink - # functions in a similar fashion to 'readlink -f', it makes sure - # every component except for the first one to be available on - # the directory structure. If we cannot find it in the system, - # we don't need to make this much more complex by trying so - # hard to find it. Simply use the original directory name. - dirname="$(cpt-readlink "${file%/*}" 2>/dev/null)" || + # We will only follow the symlinks of the directories, so we reserve the + # directory name in this 'dirname' value. If we cannot find it in the + # system, we don't need to make this much more complex by trying so hard + # to find it. Simply use the original directory name. + dirname="$(_readlinkf "${file%/*}" 2>/dev/null)" || dirname="${file%/*}" @@ -1577,8 +1659,7 @@ pkg_fetch() { # ownership of files and directories in the rare # case that the repository is owned by a 3rd user. ( - user=$(cpt-stat "$PWD") || user=root - id -u "$user" >/dev/null 2>&1 || user=root + user=$(_stat "$PWD") [ "$user" = root ] || log "Dropping permissions to $user for pull" @@ -1614,8 +1695,7 @@ pkg_fetch() { # We are going to do the same operation as above, to # find the owner of the repository. ( - user=$(cpt-stat "$PWD") || user=root - id -u "$user" >/dev/null 2>&1 || user=root + user=$(_stat "$PWD") [ "$user" = root ] || log "Dropping permissions to $user for pull" @@ -1647,8 +1727,7 @@ pkg_fetch() { # Similar to the git update, we find the owner of # the repository and spawn rsync as that user. ( - user=$(cpt-stat "$PWD") || user=root - id -u "$user" >/dev/null 2>&1 || user=root + user=$(_stat "$PWD") [ "$user" = root ] || log "Dropping permissions to $user for pull" @@ -1743,6 +1822,48 @@ pkg_updates(){ log "Updated all packages" } +pkg_get_base() ( + # Print the packages defined in the /etc/cpt-base file. + # If an argument is given, it prints a space seperated list instead + # of a list seperated by newlines. + + # cpt-base is an optional file, return with success if it doesn't exist. + [ -f "$CPT_ROOT/etc/cpt-base" ] || return 0 + nonl=$1; set -- + + # Older versions of shellcheck warns us that the variable is changing on the + # subshell. That is our purpose here, thank you very much. + # shellcheck disable=SC2030 + while read -r pkgname _; do + [ "${pkgname##\#*}" ] || continue + set -- "$@" "$pkgname" + done < "$CPT_ROOT/etc/cpt-base" + if [ "$nonl" ]; then printf '%s ' "$@"; else printf '%s\n' "$@"; fi +) + +pkg_gentree() ( + # Generate an ordered dependency tree of a package. Useful for testing + # whether the generated dependency tree is enough to actually building a + # given package. A second argument can be given as a combination of + # characters (similar to 'tar(1)' keys) which will be used as an option + # parser. See the documentation for more information on the keys. + deps='' reverse='' nonl='' make_deps=first + for op in $(sepchar "$2"); do + case "$op" in + b) deps="$(pkg_get_base nonl)" ;; + x) make_deps=first-nomake ;; + r) reverse=1 ;; + n) nonl=1 ;; + *) return 1 + esac + done + pkg_depends "$1" tree "${make_deps:+first}" + eval set -- "$deps" + pkg_order "$@" + if [ "$reverse" ]; then eval set -- "$redro"; else eval set -- "$order"; fi + if [ "$nonl" ]; then printf '%s ' "$@"; else printf '%s\n' "$@"; fi +) + pkg_clean() { # Clean up on exit or error. This removes everything related # to the build. @@ -1763,10 +1884,14 @@ create_cache() { # # Create the required temporary directories and set the variables # which point to them. - mkdir -p "${CPT_TMPDIR:=$cac_dir/proc}" \ - "${mak_dir:=$CPT_TMPDIR/$pid/build}" \ - "${pkg_dir:=$CPT_TMPDIR/$pid/pkg}" \ - "${tar_dir:=$CPT_TMPDIR/$pid/export}" + mkdir -p "${tmp_dir:=${CPT_TMPDIR:=$cac_dir/proc}/$pid}" + + # If an argument is given, skip the creation of other cache directories. + # This here makes shellcheck extremely angry, so I am globally disabling + # SC2119. + [ "$1" ] || mkdir -p "${mak_dir:=$tmp_dir/build}" \ + "${pkg_dir:=$tmp_dir/pkg}" \ + "${tar_dir:=$tmp_dir/export}" } @@ -1821,7 +1946,9 @@ create_cache() { # Figure out which 'sudo' command to use based on the user's choice or # what is available on the system. - su=${CPT_SU:-$(command -v sudo || command -v doas)} || su=su + su=${CPT_SU:-$(command -v sls || + command -v sudo || + command -v doas)} || su=su # Store the date and time of script invocation to be used as the name # of the log files the package manager creates uring builds. @@ -1872,3 +1999,15 @@ create_cache() { fi } + +# If the library is being called with its own name, run arguments. +if [ "${0##*/}" = cpt-lib ]; then + pd() { + setup REST help:usage -- "usage: ${0##*/} [funcall...]" + global_options + } + eval "$(getoptions pd parse "$0")" + parse "$@" + eval set -- "$REST" + "$@" +fi diff --git a/src/cpt-list b/src/cpt-list index a161abf..363e22f 100755 --- a/src/cpt-list +++ b/src/cpt-list @@ -5,11 +5,23 @@ parser_definition() { setup REST help:usage -- "usage: ${0##*/} [-c] [pkg...]" msg -- '' 'Options:' flag CURRENT -c --current -- "Use the current directory as a package" + param PKG --check label:" --check PKG TRUE FALSE" -- \ + "Check if PKG exists and return the string of TRUE if"\ + "it exists, and the string of FALSE if it doesn't." \ + "Useful for optional packaging." global_options } if [ -f ./cpt-lib ]; then . ./cpt-lib; else . cpt-lib; fi +if [ "$PKG" ]; then + if pkg_list "$PKG" >/dev/null 2>&1; then + printf %s "$1" + else + printf %s "$2" + fi +else [ "$CURRENT" ] && set -- "${PWD##*/}" pkg_list "$@" +fi diff --git a/src/cpt-remove b/src/cpt-remove index bcf5047..cce3739 100755 --- a/src/cpt-remove +++ b/src/cpt-remove @@ -10,7 +10,7 @@ parser_definition() { if [ -f ./cpt-lib ]; then . ./cpt-lib; else . cpt-lib; fi -[ "$1" ] || set -- "${PWD##*/}"; export CPT_PATH=${PWD%/*}:$CPT_PATH +[ "$1" ] || { set -- "${PWD##*/}"; export CPT_PATH=${PWD%/*}:$CPT_PATH ;} [ -w "$CPT_ROOT/" ] || [ "$uid" = 0 ] || { as_root "$0" "$@" diff --git a/src/test.do b/src/test.do new file mode 100644 index 0000000..159cece --- /dev/null +++ b/src/test.do @@ -0,0 +1,7 @@ +SRC_ROOT=.. +. ${SRC_ROOT}/config.rc + +redo-ifchange cpt-lib +exec >&2 +find . ../contrib -name 'cpt-*' ! -name '*.*' -exec shellcheck -e 2119 -x -f gcc {} + +PHONY diff --git a/tools/do b/tools/do new file mode 100755 index 0000000..f38a2a7 --- /dev/null +++ b/tools/do @@ -0,0 +1,446 @@ +#!/bin/sh +# +# A minimal alternative to djb redo that doesn't support incremental builds. +# For the full version, visit http://github.com/apenwarr/redo +# +# The author disclaims copyright to this source file and hereby places it in +# the public domain. (2010 12 14; updated 2019 02 24) +# +USAGE=" +usage: do [-d] [-x] [-v] [-c] <targets...> + -d print extra debug messages (mostly about dependency checks) + -v run .do files with 'set -v' + -x run .do files with 'set -x' + -c clean up all old targets before starting + + Note: do is an implementation of redo that does *not* check dependencies. + It will never rebuild a target it has already built, unless you use -c. +" + +# CDPATH apparently causes unexpected 'cd' output on some platforms. +unset CDPATH + +# By default, no output coloring. +green="" +bold="" +plain="" + +if [ -n "$TERM" -a "$TERM" != "dumb" ] && tty <&2 >/dev/null 2>&1; then + green="$(printf '\033[32m')" + bold="$(printf '\033[1m')" + plain="$(printf '\033[m')" +fi + +# The 'seq' command is not available on all platforms. +_seq() { + local x=0 max="$1" + while [ "$x" -lt "$max" ]; do + x=$((x + 1)) + echo "$x" + done +} + +# Split $1 into a dir part ($_dirsplit_dir) and base filename ($_dirsplit_base) +_dirsplit() { + _dirsplit_base=${1##*/} + _dirsplit_dir=${1%$_dirsplit_base} +} + +# Like /usr/bin/dirname, but avoids a fork and uses _dirsplit semantics. +qdirname() ( + _dirsplit "$1" + dir=${_dirsplit_dir%/} + echo "${dir:-.}" +) + +_dirsplit "$0" +REDO=$(cd "$(pwd -P)" && + cd "${_dirsplit_dir:-.}" && + echo "$PWD/$_dirsplit_base") +export REDO +_cmd=$_dirsplit_base + +DO_TOP= +if [ -z "$DO_BUILT" ]; then + export _do_opt_debug= + export _do_opt_exec= + export _do_opt_verbose= + export _do_opt_clean= +fi +while getopts 'dxvcj:h?' _opt; do + case $_opt in + d) _do_opt_debug=1 ;; + x) _do_opt_exec=x ;; + v) _do_opt_verbose=v ;; + c) _do_opt_clean=1 ;; + j) ;; # silently ignore, for compat with real redo + \?|h|*) printf "%s" "$USAGE" >&2 + exit 99 + ;; + esac +done +shift "$((OPTIND - 1))" +_debug() { + [ -z "$_do_opt_debug" ] || echo "$@" >&2 +} + +if [ -z "$DO_BUILT" -a "$_cmd" != "redo-whichdo" ]; then + DO_TOP=1 + if [ "$#" -eq 0 ] && [ "$_cmd" = "do" -o "$_cmd" = "redo" ]; then + set all # only toplevel redo has a default target + fi + export DO_STARTDIR="$(pwd -P)" + # If starting /bin/pwd != $PWD, this will fix it. + # That can happen when $PWD contains symlinks that the shell is + # trying helpfully (but unsuccessfully) to hide from the user. + cd "$DO_STARTDIR" || exit 99 + export DO_BUILT="$PWD/.do_built" + if [ -z "$_do_opt_clean" -a -e "$DO_BUILT" ]; then + echo "do: Incremental mode. Use -c for clean rebuild." >&2 + fi + : >>"$DO_BUILT" + sort -u "$DO_BUILT" >"$DO_BUILT.new" + while read f; do + [ -n "$_do_opt_clean" ] && printf "%s\0%s.did\0" "$f" "$f" + printf "%s.did.tmp\0" "$f" + done <"$DO_BUILT.new" | + xargs -0 rm -f 2>/dev/null + mv "$DO_BUILT.new" "$DO_BUILT" + export DO_PATH="$DO_BUILT.dir" + export PATH="$DO_PATH:$PATH" + rm -rf "$DO_PATH" + mkdir "$DO_PATH" + for d in redo redo-ifchange redo-whichdo; do + ln -s "$REDO" "$DO_PATH/$d" + done + for d in redo-ifcreate redo-stamp redo-always redo-ood \ + redo-targets redo-sources; do + echo "#!/bin/sh" >"$DO_PATH/$d" + chmod a+rx "$DO_PATH/$d" + done +fi + + +# Chop the "file" part off a /path/to/file pathname. +# Note that if the filename already ends in a /, we just remove the slash. +_updir() +{ + local v="${1%/*}" + [ "$v" != "$1" ] && echo "$v" + # else "empty" which means we went past the root +} + + +# Returns true if $1 starts with $2. +_startswith() +{ + [ "${1#"$2"}" != "$1" ] +} + + +# Returns true if $1 ends with $2. +_endswith() +{ + [ "${1%"$2"}" != "$1" ] +} + + +# Prints $1 if it's absolute, or $2/$1 if $1 is not absolute. +_abspath() +{ + local here="$2" there="$1" + if _startswith "$1" "/"; then + echo "$1" + else + echo "$2/$1" + fi +} + + +# Prints $1 as a path relative to $PWD (not starting with /). +# If it already doesn't start with a /, doesn't change the string. +_relpath() +{ + local here="$2" there="$1" out= hadslash= + #echo "RP start '$there' hs='$hadslash'" >&2 + _startswith "$there" "/" || { echo "$there" && return; } + [ "$there" != "/" ] && _endswith "$there" "/" && hadslash=/ + here=${here%/}/ + while [ -n "$here" ]; do + #echo "RP out='$out' here='$here' there='$there'" >&2 + [ "${here%/}" = "${there%/}" ] && there= && break; + [ "${there#$here}" != "$there" ] && break + out=../$out + _dirsplit "${here%/}" + here=$_dirsplit_dir + done + there=${there#$here} + if [ -n "$there" ]; then + echo "$out${there%/}$hadslash" + else + echo "${out%/}$hadslash" + fi +} + + +# Prints a "normalized relative" path, with ".." resolved where possible. +# For example, a/b/../c will be reduced to just a/c. +_normpath() +( + local path="$1" relto="$2" out= isabs= + #echo "NP start '$path'" >&2 + if _startswith "$path" "/"; then + isabs=1 + else + path="${relto%/}/$path" + fi + set -f + IFS=/ + for d in ${path%/}; do + #echo "NP out='$out' d='$d'" >&2 + if [ "$d" = ".." ]; then + out=$(_updir "${out%/}")/ + else + out=$out$d/ + fi + done + #echo "NP out='$out' (done)" >&2 + out=${out%/} + if [ -n "$isabs" ]; then + echo "${out:-/}" + else + _relpath "${out:-/}" "$relto" + fi +) + + +# Prints a "real" path, with all symlinks resolved where possible. +_realpath() +{ + local path="$1" relto="$2" isabs= rest= + if _startswith "$path" "/"; then + isabs=1 + else + path="${relto%/}/$path" + fi + ( + for d in $(_seq 100); do + #echo "Trying: $PWD--$path" >&2 + if cd -P "$path" 2>/dev/null; then + # success + pwd=$(pwd -P) + #echo " chdir ok: $pwd--$rest" >&2 + np=$(_normpath "${pwd%/}/$rest" "$relto") + if [ -n "$isabs" ]; then + echo "$np" + else + _relpath "$np" "$relto" + fi + break + fi + _dirsplit "${path%/}" + path=$_dirsplit_dir + rest="$_dirsplit_base/$rest" + done + ) +} + + +# List the possible names for default*.do files in dir $1 matching the target +# pattern in $2. We stop searching when we find the first one that exists. +_find_dofiles_pwd() +{ + local dodir="$1" dofile="$2" + _startswith "$dofile" "default." || dofile=${dofile#*.} + while :; do + dofile=default.${dofile#default.*.} + echo "$dodir$dofile" + [ -e "$dodir$dofile" ] && return 0 + [ "$dofile" = default.do ] && break + done + return 1 +} + + +# List the possible names for default*.do files in $PWD matching the target +# pattern in $1. We stop searching when we find the first name that works. +# If there are no matches in $PWD, we'll search in .., and so on, to the root. +_find_dofiles() +{ + local target="$1" dodir= dofile= newdir= + _debug "find_dofile: '$PWD' '$target'" + dofile="$target.do" + echo "$dofile" + [ -e "$dofile" ] && return 0 + + # Try default.*.do files, walking up the tree + _dirsplit "$dofile" + dodir=$_dirsplit_dir + dofile=$_dirsplit_base + [ -n "$dodir" ] && dodir=${dodir%/}/ + [ -e "$dodir$dofile" ] && return 0 + for i in $(_seq 100); do + [ -n "$dodir" ] && dodir=${dodir%/}/ + #echo "_find_dofiles: '$dodir' '$dofile'" >&2 + _find_dofiles_pwd "$dodir" "$dofile" && return 0 + newdir=$(_realpath "${dodir}.." "$PWD") + [ "$newdir" = "$dodir" ] && break + dodir=$newdir + done + return 1 +} + + +# Print the last .do file returned by _find_dofiles. +# If that file exists, returns 0, else 1. +_find_dofile() +{ + local files="$(_find_dofiles "$1")" + rv=$? + #echo "files='$files'" >&2 + [ "$rv" -ne 0 ] && return $rv + echo "$files" | { + while read -r linex; do line=$linex; done + printf "%s\n" "$line" + } +} + + +# Actually run the given $dofile with the arguments in $@. +# Note: you should always run this in a subshell. +_run_dofile() +{ + export DO_DEPTH="$DO_DEPTH " + export REDO_TARGET="$PWD/$target" + local line1 + set -e + read line1 <"$PWD/$dofile" || true + cmd=${line1#"#!/"} + if [ "$cmd" != "$line1" ]; then + set -$_do_opt_verbose$_do_opt_exec + exec /$cmd "$PWD/$dofile" "$@" + else + set -$_do_opt_verbose$_do_opt_exec + # If $dofile is empty, "." might not change $? at + # all, so we clear it first with ":". + :; . "$PWD/$dofile" + fi +} + + +# Find and run the right .do file, starting in dir $1, for target $2, +# providing a temporary output file as $3. Renames the temp file to $2 when +# done. +_do() +{ + local dir="$1" target="$1$2" tmp="$1$2.redo.tmp" tdir= + local dopath= dodir= dofile= ext= + if [ "$_cmd" = "redo" ] || + ( [ ! -e "$target" -o -d "$target" ] && + [ ! -e "$target.did" ] ); then + printf '%sdo %s%s%s%s\n' \ + "$green" "$DO_DEPTH" "$bold" "$target" "$plain" >&2 + dopath=$(_find_dofile "$target") + if [ ! -e "$dopath" ]; then + echo "do: $target: no .do file ($PWD)" >&2 + return 1 + fi + _dirsplit "$dopath" + dodir=$_dirsplit_dir dofile=$_dirsplit_base + if _startswith "$dofile" "default."; then + ext=${dofile#default} + ext=${ext%.do} + else + ext= + fi + target=$PWD/$target + tmp=$PWD/$tmp + cd "$dodir" || return 99 + target=$(_relpath "$target" "$PWD") || return 98 + tmp=$(_relpath "$tmp" "$PWD") || return 97 + base=${target%$ext} + tdir=$(qdirname "$target") + [ ! -e "$DO_BUILT" ] || [ ! -w "$tdir/." ] || + : >>"$target.did.tmp" + # $qtmp is a temporary file used to capture stdout. + # Since it might be accidentally deleted as a .do file + # does its work, we create it, then open two fds to it, + # then immediately delete the name. We use one fd to + # redirect to stdout, and the other to read from after, + # because there's no way to fseek(fd, 0) in sh. + qtmp=$DO_PATH/do.$$.tmp + ( + rm -f "$qtmp" + ( _run_dofile "$target" "$base" "$tmp" >&3 3>&- 4<&- ) + rv=$? + if [ $rv != 0 ]; then + printf "do: %s%s\n" "$DO_DEPTH" \ + "$target: got exit code $rv" >&2 + rm -f "$tmp.tmp" "$tmp.tmp2" "$target.did" + return $rv + fi + echo "$PWD/$target" >>"$DO_BUILT" + if [ ! -e "$tmp" ]; then + # if $3 wasn't created, copy from stdout file + cat <&4 >$tmp + # if that's zero length too, forget it + [ -s "$tmp" ] || rm -f "$tmp" + fi + ) 3>$qtmp 4<$qtmp # can't use "|| return" here... + # ...because "|| return" would mess up "set -e" inside the () + # on some shells. Running commands in "||" context, even + # deep inside, will stop "set -e" from functioning. + rv=$? + [ "$rv" = 0 ] || return "$rv" + mv "$tmp" "$target" 2>/dev/null + [ -e "$target.did.tmp" ] && + mv "$target.did.tmp" "$target.did" || + : >>"$target.did" + else + _debug "do $DO_DEPTH$target exists." >&2 + fi +} + + +# Implementation of the "redo" command. +_redo() +{ + local i startdir="$PWD" dir base + set +e + for i in "$@"; do + i=$(_abspath "$i" "$startdir") + ( + cd "$DO_STARTDIR" || return 99 + i=$(_realpath "$(_relpath "$i" "$PWD")" "$PWD") + _dirsplit "$i" + dir=$_dirsplit_dir base=$_dirsplit_base + _do "$dir" "$base" + ) + [ "$?" = 0 ] || return 1 + done +} + + +# Implementation of the "redo-whichdo" command. +_whichdo() +{ + _find_dofiles "$1" +} + + +case $_cmd in + do|redo|redo-ifchange) _redo "$@" ;; + redo-whichdo) _whichdo "$1" ;; + do.test) ;; + *) printf "do: '%s': unexpected redo command" "$_cmd" >&2; exit 99 ;; +esac +[ "$?" = 0 ] || exit 1 + +if [ -n "$DO_TOP" ]; then + if [ -n "$_do_opt_clean" ]; then + echo "do: Removing stamp files..." >&2 + [ ! -e "$DO_BUILT" ] || + while read f; do printf "%s.did\0" "$f"; done <"$DO_BUILT" | + xargs -0 rm -f 2>/dev/null + fi +fi diff --git a/tools/install.sh b/tools/install.sh new file mode 100755 index 0000000..f79cb3b --- /dev/null +++ b/tools/install.sh @@ -0,0 +1,90 @@ +#!/bin/sh -e +# Portable install version that supports -D -m and -t +usage() { + printf '%s\n' "usage: $0 [-D] [-m mode] source dest" \ + " or: $0 [-D] [-m mode] [-t dir] [source...]" >&2 + exit 1 +} + +die() { printf '%s\n' "$@" >&2; exit 1;} + +mkdirp='' +target='' +mode='' +REST='' +parse() { + OPTIND=$(($#+1)) + while OPTARG= && [ $# -gt 0 ]; do + case $1 in + -[tm]?*) OPTARG=$1; shift + eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'} + ;; + -[!-]?*) OPTARG=$1; shift + eval 'set -- "${OPTARG%"${OPTARG#??}"}" -"${OPTARG#??}"' ${1+'"$@"'} + OPTARG= ;; + esac + case $1 in + '-D') + [ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break + eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG='' + mkdirp="$OPTARG" + ;; + '-t') + [ $# -le 1 ] && set "required" "$1" && break + OPTARG=$2 + target="$OPTARG" + shift ;; + '-m') + [ $# -le 1 ] && set "required" "$1" && break + OPTARG=$2 + mode="$OPTARG" + shift ;; + '-h'|'--help') + usage + exit 0 ;; + --) + shift + while [ $# -gt 0 ]; do + REST="${REST} \"\${$((OPTIND-$#))}\"" + shift + done + break ;; + [-]?*) set "unknown" "$1"; break ;; + *) + REST="${REST} \"\${$((OPTIND-$#))}\"" + esac + shift + done + [ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; } + case $1 in + unknown) set "Unrecognized option: $2" "$@" ;; + noarg) set "Does not allow an argument: $2" "$@" ;; + required) set "Requires an argument: $2" "$@" ;; + pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;; + notcmd) set "Not a command: $2" "$@" ;; + *) set "Validation error ($1): $2" "$@" + esac + echo "$1" >&2 + exit 1 +} + +parse "$@" && eval set -- "$REST" + +if [ "$target" ]; then + [ "$mkdirp" ] || [ -d "$target" ] || die "$target doesn't exist" + mkdir -p "$target" + for arg; do + [ -d "$target/${arg##*/}" ] && die "$target/${arg##*/} is a directory" + cp "$arg" "$target/${arg##*/}" + + # Most implementations set the mode to 0755 by default when -t is set. + chmod "${mode:=0755}" "$target/${arg##*/}" + done +else + case "$2" in */*) [ "$mkdirp" ] || [ -d "${2%/*}" ] || die "${2%/*} doesn't exist" + mkdir -p "${2%/*}" + esac + [ -d "$2" ] && die "$2 is a directory" + cp "$1" "$2" + chmod "${mode:=0755}" "$2" +fi diff --git a/uninstall.do b/uninstall.do new file mode 100644 index 0000000..3347874 --- /dev/null +++ b/uninstall.do @@ -0,0 +1,14 @@ +. ./config.rc + +# Remove executables. +getbin | while read -r file; do + rm -f "${DESTDIR}${BINDIR}/${file##*/}" +done + +# Remove manual pages. +for man in man/*.1; do + rm -f "${DESTDIR}${MAN1}/${man##*/}" +done + +# Remove the info page. +rm -f "${DESTDIR}${INFODIR}/cpt.info" |