initial commit.

This commit is contained in:
yafox 2020-11-25 06:29:21 +00:00
commit 81b330c30e
No known key found for this signature in database
GPG Key ID: B501C30B37F4806C
17 changed files with 904 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
lower
system
built-with
build-conf

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright 2020 "yafox"
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

206
README Normal file
View File

@ -0,0 +1,206 @@
lix
===
lix is a source-based package manager written entirely in POSIX-compliant shell
script. it was developed to serve as the core component of lix os.
it uses chroots and package-and-version-specific overlayfs layers to avoid
package conflicts during the build process. aside from anything in the "system"
bootstrap directory, the only tools and libraries available to a package are
those it explicitly lists as a dependency. all package files are kept in
separate directories and soft-linked in to the system root. this makes it easy
to see what packages have supplied each file in one's system root.
lix is composed of the following small shell script utilities, all of which can
be used independently of lix and each other (with the exception of `how`, which
uses `vercmp` to allow defining instructions for ranges of package versions and
`shsort` to quicksort them):
1) `lmr` can merge one directory into another using soft or hard links.
2) `lyr` provides directories and commands for managing overlayfs overlays.
3) `src` pulls and verifies source code and provides default version numbers.
4) `how` provides scripts for patching, configuring, building, and installing.
5) `chin` sets up common system paths and chroots in to a target directory.
6) `vercmp` allows comparison of version strings in package-specific formats.
7) `shsort` is a POSIX shell quicksort implementation which takes a user
supplied comparison command.
because of its small size and modularity, lix and its utilities can provide a
foundation for building a simple source-based distribution of one's own. the
lix os project serves as an example of how one might do this.
## manual intervention
lix favors an interactive approach to problem solving. patching, configuring,
building, and installing are all separate commands. the results of commands
executed on a package up to any given point may be examined via:
- mounting the package's chroot contents using `lix mount <path> <package>`
- obtaining a shell into the package's chroot using `lix do sh <package>`
- examining the package's layers at `lyr upperdir lix/<package>/<version>`
## dependency handling
lix does not solve dependency graphs. dependency graph resolution is a
non-trivial problem, and chrooted builds make solving it largely unnecessary
anyway. each package can have whatever dependencies it needs without causing
conflicts with other packages. if conflicts between packages arise, they can be
quickly resolved by swapping around softlinks using `lix up` and `lix down`.
`lix` records the versions of each dependency used when building a package in
the `built-with` directory in LIXROOT after each successful build. this makes
it possible to determine which packages need to be rebuilt when changes are made
to a dependency.
lix also does not recursively build or install dependencies. only code the user
has explicitly asked for should ever run on a user's machine. however, assuming
package dependency lists are exhaustive and kept sorted by their height in the
(acyclic) dependency graph in a similar way to the linux kernel's module
dependencies list, implementing this behavior should require little more than a
loop and some list deduplication.
## dynamic dependencies
any line in a package's list of dependencies that matches the regex
"^\$[A-Za-z0-9]+$" will get evaluated before being parsed. this allows some
dependencies to be provided via environment variables. this functionality is
provided in order to support the user's ability to choose a text editor during
the configuration of certain packages. by convention, lix packages use the
"EDITORDEP" variable to reference the package providing the command named in the
"EDITOR" variable. because of its general nature, this technique can also be
extended to provide build flexibility as needed.
for example, the line:
$EDITORDEP
in combination with the command:
export EDITORDEP="elvis > 0"
results in the line:
elvis > 0
which selects the most recent version of the elvis package, assuming its version
number is greater than zero.
## --bootstrap
every package chroot needs, at minimum, a userland of some kind. (e.g., a shell
and utilities like `ls` and `cat`.) this userland should be included in the
dependencies of the `how` packages one has chosen. however, when bootstrapping
a lix system, no dependencies have been compiled yet and compilation must depend
on the host system's userland. the `--bootstrap` option allows one to specify a
directory to use as the lowest layer in lix's overlayfs chroot. this directory
must be constructed; `--bootstrap=/` will NOT work because overlayfs does not
allow mounting overlays inside of lower directories. `lmr` may come in handy
here. `lix-os-utilities` also provides a script for constructing such a
directory, `mkbootstrap.sh`.
## the `build-conf` directory
when a package's build chroot is being constructed, versions for the target
package and each of its dependencies must be chosen somehow. if a version for
the target package is not passed on the command line, the `build-conf` directory
is examined for a file with the same name as the target package. this file
should contain a whitespace delimited list of package names and version numbers,
with the first line containing the name of the target package and the version to
build by default when no version is specified on the command line.
if there is no such file, the versions are taken from the `defaults.sh` file in
the `src` package roots for the target package and its dependencies.
each version specified is validated against the dependency constraints supplied
by `how`'s `deps` command.
if no version can be determined for a dependency, or if the dependency's layer
does not exist yet, a warning is emitted but the build attempts to continue.
this is because there is no guarantee that the dependency has not simply been
included via the `system` directory. during a bootstrap build, this will be the
case more often than not. in this situation, the dependency's name is still
written to a `built-with` file, but no version number is included.
## the `built-with` directory
every time a package is built successfully, a tab delimited list of the
dependencies included in its build chroot and their versions is written to the
`built-with` directory using the pattern `<package>-<version>` for the filename.
dependencies whose versions could not be determined (likely because they were
included via the `system` directory) or whose layers don't exist yet are listed
without a version number.
## layers
in the process of building and installing a package, the following `lyr` layers
are created:
1. lix/<package>/<version>/src
used as the top layer for /mnt/src in the package chroot. overlaid on top
of the package's source code. holds all changes that would have been made
to the package's source code directory during the patching, configuring, and
building process.
2. lix/<package>/<version>/build
bind mounted to /mnt/build in the package chroot. not a true layer as there
is nothing to overlay. gives packages a place to build out-of-source without
having to mess up the package's root file system.
3. lix/<package>/<version>/how
provided as the upper layer for the package's `how` instructions. this layer
gets read-only bind mounted to /mnt/how in the package chroot. mostly exists
to give `how` a place to mount its layers. (see lib/ch.sh for details.)
4. lix/<package>/<version>/fs
contains the root filesystem for the package. this is the "upper directory"
in the overlayfs for the package's chroot. gets used as a "lower directory"
in the overlayfs mounts of packages that depend on it. gets `lmr`ed in to the
system root directory by `lix up`.
## usage
lix <command> <package> [<version>]
commands:
pl, pull
acquire and patch package source code.
cf, config, configure
configure the package.
mk, make
build the package.
in, inst, install
install the package to its overlay.
un, uninst, uninstall
remove the package overlay.
ad, add
pull, configure, make, and install the package to its overlay.
rm, remove
delete the package version's overlay, source code, and signature.
up
symlink the package overlay into the system root.
dn, down
remove the package symlinks from the system root.
do <cmd>
chroot into the package filesystem overlay and run <cmd>.
mt, mount <path>
mounts the package filesystem overlay at the given path.
um, umount <path>
unmounts the package filesystem overlay at the given path.
vr, ver, version
print the version inferred for the given package.
dp, deps, dependencies
list package dependencies and their inferred versions.

9
lib/built.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/sh -e
. "$LIXROOT/lib/versionformat.sh"
built() {
ls "$(lyr upperdir lix/$1)" | while read ver; do
[ "ls $(lyr upperdir lix/$1/$ver/fs/)" = "" ] || echo "$ver"
done | shsort -r "vercmp -f $(versionformat "$1")" 2> /dev/null
}

14
lib/clearopaques.sh Normal file
View File

@ -0,0 +1,14 @@
#!/bin/sh -e
clearopaques() {
# opaque directories are created when a layer creates a directory that does
# not exist in one of its currently mounted lower layers. preserving the
# layer's illusion of being the progenitor of a directory when mounted with
# different lower layers is incompatible with lix's operation, and blocking
# another layer's files in general is unsupported behavior as far as lix is
# concerned. this strips all directories of the "opaque" attribute.
find "$1" -type d -exec setfattr -x trusted.overlay.opaque {} \; 2>/dev/null \
|| true # find returns nonzero if no 'opaque' files were found.
}

33
lib/dependencies.sh Normal file
View File

@ -0,0 +1,33 @@
#!/bin/sh -e
. "$LIXROOT/lib/built.sh"
. "$LIXROOT/lib/reverse.sh"
. "$LIXROOT/lib/versionpasses.sh"
dependencies() { # <contents of a 'deps' file>
seen=""
deps="$(echo "$1" | while read line; do
eval "printf '%s\n' "\"$line\"""
done)"
echo "$deps" | while read line; do
[ "$line" ] || continue;
# eval inline expressions and convert all whitespace to single spaces.
line="$(echo "$line" | tr -s '[:space:]' ' ')"
dep="$(echo "$line" | cut -d' ' -f1)"
depreq="$(echo "$line" | cut -d' ' -f2-)"
depver="$(built "$dep" | reverse | while read ver; do
[ "$ver" ] || continue;
if [ "$(versionpasses "$dep" "$ver" "$depreq")" = "yes" ]; then
echo "$ver"
break
fi
done)"
resolved="$(printf '%s\t%s\n' "$dep" "$depver")"
echo "$seen" | grep -q "^$resolved\$" || echo "$resolved"
seen="$resolved\n$seen"
done
}

21
lib/guessver.sh Normal file
View File

@ -0,0 +1,21 @@
#!/bin/sh
. "$LIXROOT/lib/versionformat.sh"
guessver() {
pkg="$1"
tgt="${2:-$pkg}"
if [ -f "$LIXROOT/build-conf/$pkg" ]; then
awk '$1 == "'$tgt'" { print $2 }' "$LIXROOT/build-conf/$tgt"
return 0
fi
printf '%s\n%s\n%s' \
"$(version=''; eval "$(src -d $tgt 2> /dev/null)"; echo "$version")" \
"$(ls "$HOWROOT/pkg/$tgt" 2>/dev/null)" \
"$(built "$tgt" 2> /dev/null)" \
| grep -vE '^default$|^$' \
| shsort -r "vercmp -f $(versionformat "$tgt")" 2> /dev/null \
| tail -n1
}

3
lib/log.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/sh -e

36
lib/lowerlayers.sh Normal file
View File

@ -0,0 +1,36 @@
#!/bin/sh -e
#. "$LIXROOT/libs/reverse.sh"
# errors out if a dependency was not resolved or if the LAYERMAX is exceeded.
lowerlayers() { # <resolved dependencies> [<bootstrap directory>]
layers="$2"
layercount="0"
maxlayers=${LAYERMAX:-100}
layers="$(echo "$1" | reverse | while read line; do
[ "$line" ] || continue;
dep="$(echo "$line" | awk '{ print $1 }')"
ver="$(echo "$line" | awk '{ print $2 }')"
dir="$(lyr upperdir lix/$dep/$ver/fs)"
# only warn on missing dependencies if a bootstrap directory was
# provided. it's possible the dependency is in the bootstrap.
[ "$2" ] && logfn="wrn possible" || logfn="err"
[ "$ver" ] && [ -d "$dir" ] \
&& printf "$dir:" \
|| $logfn "missing dependency: $dep $ver"
layercount="$(expr $layercount + 1)"
[ "$layercount" -le "$maxlayers" ] \
|| err "overlayfs can't handle more than $maxlayers lower directories!"
done)$layers"
[ "$?" -eq 0 ] || exit $? # error codes weren't bubbling up... :/
# strip possible trailing colon and echo.
echo "${layers%:}"
}

13
lib/mounts.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/sh -e
. "$LIXROOT/lib/reverse.sh"
registermount() {
MOUNTS="$1\n$MOUNTS"
}
unmountall() {
echo "$MOUNTS" | while read path; do
umount "$path"
done
}

100
lib/pkg.sh Normal file
View File

@ -0,0 +1,100 @@
#!/bin/sh -e
. "$LIXROOT/lib/built.sh"
. "$LIXROOT/lib/dependencies.sh"
. "$LIXROOT/lib/clearopaques.sh"
. "$LIXROOT/lib/lowerlayers.sh"
. "$LIXROOT/lib/versionformat.sh"
export PKGHOW=""
export PKGDEPS=""
# sets up the package chroot, runs the given command in it, then tears it down.
pkgdo() {
if [ -z "$FAKEROOT" ]; then
FAKEROOT="$(mktemp -d)"
CLEANUPFAKEROOT="yes"
log "loading $name version $version overlay"
mountpkg "$FAKEROOT" "$2" \
|| err "failed to mount $name $version overlay."
fi
chin "$FAKEROOT" "cd /mnt/src; $1"
}
mountpkghow() {
# mount the 'how' overlay for the given package and version if not mounted.
PKGHOW="$(lyr mountdir "lix/$name/$version/how")"
if ! (mountpoint -q "$PKGHOW"); then
lyr mk "lix/$name/$version/how"
lyr up "$(how "$name" "$version")" "lix/$name/$version/how" > /dev/null
fi
}
loadpkgdeps() {
[ "$PKGHOW" ] || mountpkghow
if [ -z "$PKGDEPS" ]; then
[ -f "$PKGHOW/deps" ] \
|| err "could not find a 'deps' file in the package's 'how' overlay!"
PKGDEPS="$(dependencies "$(cat "$PKGHOW/deps")")"
fi
}
# sets up the package chroot overlay. optionally takes a path at which the
# overlay should be created.
mountpkg() {
loadpkgdeps
tgt="$1"
pkglower="$(lowerlayers "$PKGDEPS" "$BSLAYER")"
# make and mount package root filesystem overlay
lyr mk "lix/$name/$version/fs"
fs="$(lyr up "$pkglower" "lix/$name/$version/fs")"
[ -n "$fs" ] || err "could not bring up chroot layer for $name $version."
# bind mount overlay and override $fs value for remainder of function if a
# target path was specified.
mount -o bind "$fs" "$tgt"
# bind the how overlay to /mnt/how in the chroot.
mkdir -p "$tgt/mnt/how"
mount -o bind "$PKGHOW" "$tgt/mnt/how"
# mount source code overlay and bind to /mnt/src in the chroot.
lyr mk "lix/$name/$version/src"
srcmnt="$(lyr up "$SRCREPO/$name/$version" "lix/$name/$version/src")"
mkdir -p "$tgt/mnt/src"
mount -o bind "$srcmnt" "$tgt/mnt/src"
# create build layer and bind to /mnt/build in the chroot.
# this layer exists to support packages that need an out-of-source build.
# doesn't really *need* to be a layer, but it's a handy place to stash any
# writes we make to it.
lyr mk "lix/$name/$version/build"
mkdir -p "$tgt/mnt/build"
mount -o bind "$(lyr upperdir "lix/$name/$version/build")" "$tgt/mnt/build"
}
unmountpkg() {
_dn() { mountpoint -q "$1" && umount "$1"; };
_dnandrm() { _dn "$1" && rmdir "$1" 2> /dev/null; }
_dnandrm "$1/mnt/build" || true
_dnandrm "$1/mnt/src" || true
_dnandrm "$1/mnt/how" || true
_dn "$1" || true
lyr dn "lix/$name/$version/src" || true
lyr dn "lix/$name/$version/fs" || true
_dn "$PKGHOW"
rmdir "$(lyr upperdir lix/$name/$version)/fs/mnt" || true
clearopaques "$(lyr upperdir lix/$name/$version)/fs" || true
}

3
lib/reverse.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/sh
alias reverse="sed '1!x;H;1h;\$!d;g'"

7
lib/versionformat.sh Normal file
View File

@ -0,0 +1,7 @@
#!/bin/sh -e
versionformat() {
(vercmp formats | grep -q "^$1\$") \
&& echo "$1" \
|| echo "default"
}

34
lib/versionpasses.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/sh
. "$LIXROOT/lib/versionformat.sh"
versionpasses() { # <package> <version> <constraints expression>
index() { echo "$1" | tr -s '[:space:]' ' ' | cut -d' ' -f$2; }
format="$(versionformat "$1")"
version="$2"
constraints="$3"
value="yes"
while [ "$constraints" != "" ]; do
# handle 'or' operator
if [ "$(index "$constraints" 1)" = "||" ]; then
constraints="$(index "$constraints" 2-)"
[ "$value" = "yes" ] \
&& break \
|| { value="yes" && continue; }
fi
# handle implicit 'and' operator
if [ "$value" = "yes" ]; then
value="$(vercmp -f "$format" "$version $(index "$constraints" -2)")"
fi
constraints="$(index "$constraints" 3-)"
done
echo "$value"
}

363
lix.sh Executable file
View File

@ -0,0 +1,363 @@
#!/bin/sh -e
[ -n "$2" ] || { \
echo "
usage: $(basename $0) [--bootstrap=<path>] <command> <package> [<version>]
options:
--bootstrap=<path>
use <path> as the lowest layer in the overlay stack.
commands:
pl, pull
acquire and patch package source code.
cf, config, configure
configure the package.
mk, make
build the package.
in, inst, install
install the package to its overlay.
un, uninst, uninstall
remove the package overlay.
ad, add
pull, configure, make, and install the package to its overlay.
rm, remove
delete the package version's overlay, source code, and signature.
up [additional lmr flags]
symlink the package into the system root with lmr, passing along flags.
dn, down
remove the package symlinks from the system root with lmr.
do <cmd>
chroot into the package filesystem overlay and run <cmd>.
mt, mount <path>
mounts the package filesystem overlay at the given path.
um, umount <path>
unmounts the package filesystem overlay at the given path.
vr, ver, version
print the default version for the given package.
bl, built
list all built versions for the given package.
dp, deps, dependencies
list package dependencies and their inferred versions.
" && exit 0; }
# if this is running on an interactive terminal (as opposed to in a cron job,
# for example), if tput is installed, and if tput knows some color codes, then
# set the color values.
if [ -t 1 ] && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]; then
_clr="$(tput sgr0)"
_blu="$(tput setaf 6)"
_ylw="$(tput setaf 3)"
_red="$(tput setaf 1)"
fi
log() { echo "$_blu[LOG]$_clr $@"; }
wrn() { echo "$_ylw[WRN]$_clr $@" >&2; }
err() { echo "$_red[ERR]$_clr $@" >&2; exit 1; }
export LIXPREFIX="${LIXPREFIX:-${0%/bin/lix}}"
export LIXROOT="$(dirname "$(readlink -f "$0")")"
export HOWROOT="$(dirname "$(readlink -f "$(which how)")")"
export SRCREPO="${SRCREPO:-$LIXPREFIX/var/src}"
export SRCROOT="${SRCROOT:-${LIXROOT%/lix}/src}"
export cmd="$1"
export OP="$cmd"
export FAKEROOT=""
export CLEANUPFAKEROOT=""
export SUCCESS=""
# these are lowercase because they will be used in 'how' scripts.
export name
export version
[ -d "$SRCROOT" ] || err "$SRCROOT is not a directory!"
[ -d "$SRCREPO" ] || err "$SRCREPO is not a directory!"
[ -d "$LIXROOT" ] || err "$LIXROOT is not a directory!"
. "$LIXROOT/lib/pkg.sh"
. "$LIXROOT/lib/built.sh"
. "$LIXROOT/lib/dependencies.sh"
. "$LIXROOT/lib/lowerlayers.sh"
. "$LIXROOT/lib/guessver.sh"
# process command options
if [ "${cmd#--bootstrap}" != "$cmd" ]; then
if [ "$cmd" = "--bootstrap" ]; then
shift
export BSLAYER="$1"
else
export BSLAYER="$(echo "$cmd" | cut -d'=' -f2)"
fi
[ -d "$BSLAYER" ] || err "$BSLAYER is not a directory!"
shift
cmd="$1"
fi
case "$cmd" in
do)
docmd="$2"
shift
;;
mt|mnt|mount|um|umt|umnt|umount)
FAKEROOT="$2"
shift
;;
up)
while [ "${2#-}" != "$2" ]; do
upflags="$upflags $2"
shift
done
;;
sh|shell)
# maybe this should be its own utility.
[ "$1" ] || err "missing 'layer name' argument!"
[ -e "$2" ] || err "no such file as '$2'."
[ "$1" = "${1#lix/}" ] \
|| wrn "you are using the 'lix' namespace! this can be dangerous..."
layers="$(lowerlayers "$(dependencies "$(cat "$2")")" "$BSLAYER")"
lyr mk "$1"
chin "$(lyr up "$layers" "$1")" "${3:-sh}"
exit 0
;;
esac
# get required positional arguments
name="$2"
[ -n "$3" ] \
&& version="$3" \
|| version="$(guessver "$name")"
[ -n "$version" ] || err "could not determine version for '$name'."
cleanup() {
[ -z "$PKGHOW" ] || umount "$PKGHOW" || true
[ -z "$FAKEROOT" ] || unmountpkg "$FAKEROOT" || true
[ -z "$CLEANUPFAKEROOT" ] || rmdir "$FAKEROOT" || true
[ -n "$SUCCESS" ] || err "$name $version '$OP' failed"
}
trap cleanup EXIT INT HUP
beginmsg() {
OP="$1"
log "starting '$OP' for $name $version"
}
successmsg() { log "completed '$OP' for $name $version"; }
expectsrc() {
[ -d "$(lyr upperdir lix/$name/$version/src)" ] \
|| err "'src' layer missing! did you run 'lix pull $name $version' first?"
}
pullcmd() {
beginmsg "pull"
# if `how` has specific instructions for this package but there is no
# `src` package, then this must be a "bundle" package or something.
# just create an empty source directory and call it a day. (but please
# consider finding a more elegant way to handle bundles. there must
# be one, but it will probably require a rewrite. design problems
# usually arise out of fundamentally flawed models. with the right
# model, a _clean_ solution becomes trivial.)
if { how $name $version | grep -q ':' ; } \
&& [ ! -d "$SRCROOT/pkg/$name" ]
then
wrn "no 'src' entry. assuming this is a 'sourceless' package..."
mkdir -p "$SRCREPO/$name/$version" # ew...
elif [ -e "$SRCREPO/$name/$version" ]; then
wrn "$SRCREPO/$name/$version already exists. skipping remote pull."
else
src "$name" "$version";
fi
if [ -d "$(lyr upperdir lix/$name/$version/src)" ]; then
wrn "source code upper layer already exists! skipping patch.sh."
else
loadpkgdeps
log "looking for a patch.sh from 'how' to run in $name's overlay."
pkgdo ". ../how/env.sh; ../how/patch.sh"
fi
successmsg
}
confcmd() {
beginmsg "configure"
pkgdo ". ../how/env.sh; ../how/conf.sh"
successmsg
}
makecmd() {
beginmsg "make"
pkgdo ". ../how/env.sh; ../how/make.sh"
# compile "built-with" list
mkdir -p -- "$LIXROOT/built-with/${name}"
echo "$deps" > "$LIXROOT/built-with/${name}/$version"
successmsg
}
instcmd() {
beginmsg "install"
upperdir="$(lyr upperdir lix/$name/$version)"
# clean out previous installation and make backup
if [ -d "$upperdir/fs" ]; then
log "making backup of package's existing 'fs' layer..."
fsbak="$upperdir/bak/$(basename "$(mktemp -u)")"
mkdir -p "$fsbak"
cp -a "$upperdir/fs/." "$fsbak/"
fi
# install. restore backup on failure. delete backup on success.
if { pkgdo ". ../how/env.sh; ../how/inst.sh"; }; then
log "removing backup of package's previous 'fs' layer..."
rm -fr "$fsbak"
rmdir "$(dirname "$fsbak")" 2> /dev/null || true
else
log "restoring backup of package's previous 'fs' layer..."
rm -fr "$upperdir/fs"
mv "$fsbak" "$upperdir/fs"
rmdir "$(dirname "$fsbak")" 2> /dev/null || true
exit 1
fi
successmsg
}
# commands can be invoked with their full name, with a two-letter short form
# composed of the first letter and the last consonant (e.g., 'mk' for 'make'),
# and with an assortment of one-off abbreviations that just make sense for each
# command. e.g., 'rm' for 'remove'.
case "$cmd" in
pl|pull) pullcmd ;;
cf|cr|cg|conf|config|configure)
expectsrc
confcmd
;;
mk|make)
expectsrc
makecmd
;;
in|il|inst|install)
expectsrc
instcmd
;;
ad|add)
pullcmd
expectsrc
confcmd
makecmd
instcmd
;;
cl|cn|clean)
rm -fr "$(lyr upperdir lix/$name/$version)/src" 2> /dev/null || true
rm -fr "$(lyr upperdir lix/$name/$version)/build" 2> /dev/null || true
;;
rm|rv|remove) lyr rm "lix/$name/$version" ;;
pg|purge)
rm -fr $SRCREPO/$name/$version
lyr rm "lix/$name/$version"
;;
up) lmr -s $upflags "$(lyr upperdir lix/$name/$version/fs)" / ;;
dn|down) lmr -s -u "$(lyr upperdir lix/$name/$version/fs)" / ;;
do)
expectsrc
beginmsg "do"
pkgdo ". ../how/env.sh; $docmd"
successmsg
;;
mt|mnt|mount)
beginmsg "mount"
[ -d "$mntpth" ] || err "no such directory: $mntpth"
expectsrc
mountpkg "$mntpth" >/dev/null \
|| err "failed to mount $name $version overlay."
successmsg
;;
um|ut|umt|umnt|umount|unmount)
unmountpkg "$mntpth" \
&& log "unmounted $name $version overlay at $mntpth" \
|| err "error unmounting $name $version overlay at $mntpth"
;;
vr|vn|ver|version) echo "$version" ;;
bl|built) built "$name" ;;
dp|deps|dependencies)
loadpkgdeps
cols=0
cols="$(echo "$PKGDEPS" | while read line; do
newcols="$(echo "$line" | awk '{ print $1 }' | wc -c)"
[ "$newcols" -ge "$cols" ] || continue
cols="$newcols"
echo "$cols"
done | tail -n1)"
# get a list of dependencies with resolved version numbers. if no
# version number could be resolved, the "version" column will be empty
# and a warning will be printed.
echo "$PKGDEPS" | while read line; do
[ "$line" ] || continue;
dep="$(echo "$line" | awk '{ print $1 }')"
depver="$(echo "$line" | awk '{ print $2 }')"
[ "$depver" ] || wrn "could not resolve dependency '$dep'!"
# pad-space each dependency name so the versions align in a column.
printf "%-${cols}s %s\n" "$dep" "$depver"
done
;;
*) err "unrecognized command '$cmd'";;
esac
SUCCESS="yes"

26
makefile Normal file
View File

@ -0,0 +1,26 @@
SRCDIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
PREFIX ?= /usr
DESTDIR ?= $(PREFIX)/bin
LIXROOT ?= $(PREFIX)/share/lix
.PHONY: default install uninstall
default: $(SRCDIR)built-with $(SRCDIR)build-conf
$(SRCDIR)built-with:
mkdir -p $(SRCDIR)built-with
$(SRCDIR)build-conf:
mkdir -p $(SRCDIR)build-conf
$(LIXROOT):
ln -sf $(SRCDIR) $(LIXROOT)
$(DESTDIR)/lix:
ln -sf $(LIXROOT)/lix.sh $(DESTDIR)/lix
install: $(LIXROOT) $(DESTDIR)/lix
uninstall:
rm $(DESTDIR)/lix
rm $(LIXROOT)

12
sloc.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/sh
# find all *.sh files not under `pkg` that are not symbolic links, strip all
# trailing whitespace, then all leading whitespace, then all lines starting
# with '#', then all empty lines. then count the remaining lines.
find . -name "*.sh" ! -type l \
| xargs sed 's/[[:space:]]*$//g; s/^[[:space:]]*//g; s/^#.*$//g; /^$/d' \
| wc -l - \
| cut -d' ' -f1
# note that this script's ELOC is also included in the count.