build.zsh/build.zsh.in

544 lines
11 KiB
Bash
Raw Normal View History

2014-11-05 13:32:11 +01:00
#!/usr/bin/env zsh
# TODO:
# - Make more checks about whether the “things” built and installed are C.
# (stuff *will* break if you add non-C things as targets)
# - Clean some more (or a lot). I mean, this script could even be reused if
# it were cleaner, more readable and somewhat more documented.
#
# WARNINGS and LIMITATIONS:
# - Using a relative path in DESTDIR= *will* fail.
#
# Script output
function info {
echo "${fg_bold[green]}-- ${fg_bold[white]}$@${reset_color}"
}
function warning {
echo "${fg_bold[yellow]}-- $@${reset_color}" >&2
}
function error {
echo "${fg_bold[red]}-- ERROR: $@${reset_color}" >&2
}
# Makefiles output
function write {
echo "$@" >> $Makefile
}
2019-11-14 15:26:23 +01:00
function build_string {
local type_indicator="$1"
local operation="$2"
local file_name="$3"
local color="blue"
case $operation in
build)
color=blue;;
build-alt)
color=cyan;;
assemble)
color=green;;
generate)
color=yellow;;
install)
color=red;;
remove)
color=white;;
*)
color=magenta;;
esac
printf "${fg_bold[$color]} %-6s ${fg_bold[white]}$file_name${reset_color}\n" \
"$type_indicator >"
}
2014-11-05 13:32:11 +01:00
function CC {
2019-11-14 15:26:23 +01:00
build_string CC build "$@"
2014-11-05 13:32:11 +01:00
}
function LD {
2019-11-14 15:26:23 +01:00
build_string LD assemble "$@"
2014-11-05 13:32:11 +01:00
}
function IN {
2019-11-14 15:26:23 +01:00
build_string IN install "$@"
2014-11-05 13:32:11 +01:00
}
function LN {
2019-11-14 15:26:23 +01:00
build_string LN other "$@"
2014-11-05 13:32:11 +01:00
}
function RM {
2019-11-14 15:26:23 +01:00
build_string RM remove "$@"
2014-11-05 13:32:11 +01:00
}
function SED {
2019-11-14 15:26:23 +01:00
build_string SED generate "$@"
}
2014-11-05 13:32:11 +01:00
function DIR {
2019-11-14 15:26:23 +01:00
build_string DIR other "$@"
2014-11-05 13:32:11 +01:00
}
function TAR {
2019-11-14 15:26:23 +01:00
build_string TAR generate "$@"
2014-11-05 13:32:11 +01:00
}
# Generic helpers
function has {
local elem="$1"
shift 1
for i in "$@"; do
[[ "$i" == "$elem" ]] && return 0
done
return 1
}
2019-11-14 14:49:35 +01:00
function has_array_key {
local element="$1"
shift 1
for key value ($@) {
[[ "$key" == "$element" ]] && return 0
}
return 1
}
2014-11-05 13:32:11 +01:00
# Specialized helpers
function get_distfiles {
for file in "${dist[@]}" $(echo ${sources[@]}) $(echo ${depends[@]}); do
if [[ -z "${nodist[$file]}" ]]; then
echo "$file"
fi
2014-11-05 13:32:11 +01:00
done
typeset -a src
src=($(echo ${sources[@]}))
for file in ${src[@]}; do
typeset file="${file%.c}.h"
if [[ -e "$file" ]]; then
echo "$file"
fi
done
}
function exists {
[[ "$(whence -w ${1})" != "${1}: none" ]]
}
function duplicated {
local elem="$1"
local count=0
shift 1
local i
for i in $@; do
if [[ "$elem" == "$i" ]]; then
((count++))
if (( $count > 1 )); then
return 0
fi
fi
done
return 1
}
function dirdep {
dirname="$(dirname "$1")"
local sources=(${(@s/ /)2})
local depends=(${(@s/ /)3})
for dep in ${sources[@]} ${depends[@]}; do
if [[ "$(dirname $dep)" == "$dirname" ]]; then
return 0
fi
done
if [[ "$dirname" == "." ]]; then
return 0
fi
echo "$dirname"
}
for dir in "@SHAREDIR@/build.zsh" ./build; do
[[ -d "$dir" ]] && {
for i in "$dir"/*.zsh; do
. "$i"
done
}
2014-11-05 13:32:11 +01:00
done
##
# And so it begins
##
function main {
2019-11-26 19:17:30 +01:00
# “Global”, per-project configuration.
2014-11-05 13:32:11 +01:00
typeset -a prefixes directories
2019-11-04 19:28:13 +01:00
2019-11-26 19:17:30 +01:00
# “Local”, per-target configuration.
2019-11-04 19:28:13 +01:00
typeset -A sources type depends install auto filename nodist
typeset -A chmod
2014-11-05 13:32:11 +01:00
2019-11-26 19:17:30 +01:00
# Per-target configuration that is specific to certain target types.
# FIXME: Those should be removed from this file and exported in
# the .prelude of their backend, or similar.
typeset -A ldflags cflags crflags
2014-11-05 13:32:11 +01:00
prefixes=(
PREFIX '/usr/local'
BINDIR '$(PREFIX)/bin'
LIBDIR '$(PREFIX)/lib'
SHAREDIR '$(PREFIX)/share'
INCLUDEDIR '$(PREFIX)/include'
2017-11-04 13:01:13 +01:00
MANDIR '$(SHAREDIR)/man'
2014-11-05 13:32:11 +01:00
)
if [[ -f project.zsh && -r project.zsh ]]; then
. ./project.zsh
else
error "No “project.zsh” found in $(pwd)."
exit 1
fi
: > $Makefile
2019-11-05 01:16:59 +01:00
for target in ${targets[@]}; do
local _type=${type[$target]}
if exists ${_type}.prelude; then
${_type}.prelude
fi
done
2014-11-05 13:32:11 +01:00
if [[ -n "$package" && -n "$version" ]]; then
export package version
write "PACKAGE = '$package'"
write "VERSION = '$version'"
write
else
if $root; then
error "No \$package or no \$version defined in your project.zsh!"
error "Making tarballs and other stuff will be disabled."
error "(please note that those variables should be defined only..."
error " ... in the root directory of your projects repository)"
root=false
fi
fi
# Got some issues of PATH being replaced by a value of $path…
for prefix _path in ${prefixes[@]}; do
write "$prefix := $_path"
done
for var val in ${variables[@]}; do
write "$var := ${val}"
done
write
write "Q := @"
write
if [[ -z "${all}" ]] || (( ${#all[@]} == 0 )); then
2019-11-14 14:31:22 +01:00
all=(${targets[@]})
fi
write -n "all: ${all[@]}"
2014-11-05 13:32:11 +01:00
if $gnu; then
write "\n"
else
write "\n\t@:\n"
fi
typeset -l -a exported_rules
local target_index=1
while (($target_index <= ${#targets[@]})); do
local target="${targets[$target_index]}"
2014-11-05 13:32:11 +01:00
if has "${target}" "${exported_rules[@]}"; then
((target_index++))
continue
else
exported_rules+=("$target")
fi
typeset -a src
src=($(echo ${sources[$target]}))
local installdir="${install[$target]}"
2014-11-05 13:32:11 +01:00
if exists "${type[$target]}.build"; then
${type[$target]}.build
else
2019-11-14 19:59:34 +01:00
error "No predefined rule for the following type: ${type[$target]}"
error " (expect trouble, nothings gonna work!)"
fi
2014-11-05 13:32:11 +01:00
if [[ "${installdir}" == "-" ]]; then
write "${target}.install:"
write ""
else
if exists "${type[$target]}.install"; then
${type[$target]}.install
2014-11-05 13:32:11 +01:00
else
if [[ -z "${installdir}" ]]; then
error "No install[${type[${target}]}] and no default installation directory."
error "Your “install” rule will be broken!"
else
local F="${filename[$target]}"
if [[ -z "$F" ]]; then
F="${target}"
fi
write "${target}.install: \$(DESTDIR)${installdir}"
write "\t@echo '$(IN ${installdir}/${filename})'"
write "\t@mkdir -p '\$(DESTDIR)/${installdir}'"
write "\t${Q}install -m755 $target \$(DESTDIR)${installdir}/$filename"
write
fi
2014-11-05 13:32:11 +01:00
fi
fi
2014-11-05 13:32:11 +01:00
if exists "${type[$target]}.clean"; then
${type[$target]}.clean
else
write "${target}.clean:"
write "\t@echo '$(RM ${target})'"
write "\t${Q}rm -f ${target}"
write
fi
2014-11-05 13:32:11 +01:00
if [[ "${installdir}" == "-" ]]; then
write "${target}.uninstall:"
write ""
else
if exists "${type[$target]}.uninstall"; then
${type[$target]}.uninstall
else
write "${target}.uninstall:"
write "\t@echo '$(RM "${installdir}/${target}")'"
write "\t${Q}rm -f '\$(DESTDIR)${installdir}/${target}'"
write
fi
fi
((target_index++))
2014-11-05 13:32:11 +01:00
done
typeset -l -a target_directories
for target in "${targets[@]}"; do
directory="${target%/*}"
if [[ "$target" =~ .*/ ]] && ! has "${directory}" "${target_directories[@]}"; then
target_directories+=(${directory})
fi
done
for dir in ${target_directories[@]}; do
write "${dir}:"
write "\t${Q}mkdir -p ${dir}"
done
2014-11-05 13:32:11 +01:00
for dir in ${directories[@]}; do
write "\$(DESTDIR)${dir}:"
write "\t@echo '$(DIR ${dir})'"
write "\t${Q}mkdir -p \$(DESTDIR)${dir}"
done
for dir __ in ${prefixes[@]}; do
write "\$(DESTDIR)\$(${dir}):"
write "\t@echo '$(DIR "\$(${dir})")'"
write "\t${Q}mkdir -p \$(DESTDIR)\$(${dir})"
done
2019-11-14 14:31:22 +01:00
write -n "install:"
2014-11-05 13:32:11 +01:00
for target in ${targets[@]}; do
write -n " ${target}.install"
done
write "\n\t@:\n"
2019-11-14 14:31:22 +01:00
write -n "uninstall:"
2014-11-05 13:32:11 +01:00
for target in ${targets[@]}; do
write -n " ${target}.uninstall"
done
write "\n\t@:\n"
write -n "clean:"
(( ${#targets[@]} > 0 )) && {
for target in ${targets[@]}; do
write -n " ${target}.clean"
#(
# typeset -a src
# src=($(echo ${sources[$target]}))
# for file in ${src[@]}; do
# write -n " ${file%.c}.o.clean"
# done
#)
2014-11-05 13:32:11 +01:00
done
}
write
write "distclean: clean"
if $root; then
write "dist: dist-gz dist-xz dist-bz2"
write "\t"$Q'rm -- $(PACKAGE)-$(VERSION)'
write
write "distdir:"
write "\t"$Q'rm -rf -- $(PACKAGE)-$(VERSION)'
write "\t"$Q'ln -s -- . $(PACKAGE)-$(VERSION)'
write
typeset -a distfiles
distfiles=($(get_distfiles))
local i
for i in {1..${#distfiles[@]}}; do
if duplicated "${distfiles[${i}]}" "${distfiles[@]}"; then
distfiles[${i}]=
fi
done
for i flag in gz z xz J bz2 j; do
write "dist-${i}: \$(PACKAGE)-\$(VERSION).tar.$i"
write "\$(PACKAGE)-\$(VERSION).tar.$i: distdir"
write "\t@echo '$(TAR "\$(PACKAGE)-\$(VERSION).tar.$i")'"
write "\t${Q}tar c${flag}f \$(PACKAGE)-\$(VERSION).tar.$i \\"
for i in {1..${#distfiles}}; do
if [[ -n "${distfiles[$i]}" ]]; then
write -n "\t\t\$(PACKAGE)-\$(VERSION)/${distfiles[$i]}"
if (( i != ${#distfiles} )); then
write " \\"
fi
fi
done
write "\n"
done
fi
write "help:"
if [[ -n "$package" ]]; then
write " @echo '${fg_bold[white]} :: $package-$version${reset_color}'"
write " @echo ''"
fi
write " @echo '${fg_bold[white]}Generic targets:${reset_color}'"
typeset -la help
help=(
help "Prints this help message."
all "Builds all targets."
dist "Creates tarballs of the files of the project."
install "Installs the project."
clean "Removes compiled files."
uninstall "Deinstalls the project."
)
for rule message in ${help[@]}; do
2016-06-10 19:55:30 +02:00
printf " @echo '${reset_color} - ${fg_bold[green]}%-14s${fg[white]} $message${reset_color}'\n" \
"$rule" >> $Makefile
done
write " @echo ''"
write " @echo '${fg_bold[white]}CLI-modifiable variables:${reset_color}'"
2019-11-14 14:49:35 +01:00
for VAR __ in ${variables}; do
2016-06-10 19:55:30 +02:00
printf " @echo ' - ${fg_bold[blue]}%-14s${fg[white]} \${$VAR}${reset_color}'\n" "$VAR" >> $Makefile
done
for VAR __ in ${prefixes}; do
2016-06-10 19:55:30 +02:00
printf " @echo ' - ${fg_bold[blue]}%-14s${fg[white]} \${$VAR}${reset_color}'\n" "$VAR" >> $Makefile
done
write " @echo ''"
write " @echo '${fg_bold[white]}Project targets: ${reset_color}'"
# Computing how many characters should be reserved for the left part
# of those lines. The name of the target will be displayed in it.
local name_slot_size=14
for T in ${targets[@]}; do
if [[ "${auto[$T]}" == true ]]; then
continue
fi
if (( $#T > 22 )); then
name_slot_size=30
break
elif (( $#T > 14 )); then
name_slot_size=22
fi
done
for T in ${targets[@]}; do
if [[ "${auto[$T]}" != true ]]; then
printf " @echo ' - ${fg_bold[yellow]}%-${name_slot_size}s${fg[white]} ${type[$T]}${reset_color}'\n" "$T" >> $Makefile
fi
done
write " @echo ''"
write " @echo '${fg_bold[white]}Makefile options:${reset_color}'"
2016-06-10 19:55:30 +02:00
printf " @echo ' - %-14s $gnu'\n" "gnu:" >> $Makefile
printf " @echo ' - %-14s $colors'\n" "colors:" >> $Makefile
write " @echo ''"
write " @echo '${fg_bold[white]}Rebuild the Makefile with:${reset_color}'"
write " @echo ' zsh ./build.zsh$($colors && echo -n " -c")$($gnu && echo -n " -g")'"
2019-11-14 14:31:22 +01:00
write ".PHONY: all clean distclean dist install uninstall help"
2014-11-05 13:32:11 +01:00
write
}
export Makefile=Makefile
export MAKE='$(MAKE)'
export Q='$(Q)'
export gnu=false
export colors=false
2014-11-05 13:32:11 +01:00
while (($# > 0)); do
case "$1" in
-c|--colors)
export colors=true
2014-11-05 13:32:11 +01:00
autoload -U colors
colors
;;
-g|--gnu)
export gnu=true
;;
-h|--help)
echo "usage: $0 [OPTIONS]"
echo
echo "Options:"
echo " -h, --help Print this help message."
echo " -c, --colors Use colors in your Makefiles"
echo " (relies on zsh/colors and your current \$TERM)"
echo " -g, --gnu Sell your soul for gmake-dependent features."
return 0
;;
*)
error "unrecognised parameter: $1"
return 1
;;
esac
shift 1
done
echo "Generating Makefiles..."
root=true main