build.zsh/build.zsh.in

544 lines
11 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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
}
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 >"
}
function CC {
build_string CC build "$@"
}
function LD {
build_string LD assemble "$@"
}
function IN {
build_string IN install "$@"
}
function LN {
build_string LN other "$@"
}
function RM {
build_string RM remove "$@"
}
function SED {
build_string SED generate "$@"
}
function DIR {
build_string DIR other "$@"
}
function TAR {
build_string TAR generate "$@"
}
# Generic helpers
function has {
local elem="$1"
shift 1
for i in "$@"; do
[[ "$i" == "$elem" ]] && return 0
done
return 1
}
function has_array_key {
local element="$1"
shift 1
for key value ($@) {
[[ "$key" == "$element" ]] && return 0
}
return 1
}
# Specialized helpers
function get_distfiles {
for file in "${dist[@]}" $(echo ${sources[@]}) $(echo ${depends[@]}); do
if [[ -z "${nodist[$file]}" ]]; then
echo "$file"
fi
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
}
done
##
# And so it begins
##
function main {
# “Global”, per-project configuration.
typeset -a prefixes directories
# “Local”, per-target configuration.
typeset -A sources type depends install auto filename nodist
typeset -A chmod
# 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
prefixes=(
PREFIX '/usr/local'
BINDIR '$(PREFIX)/bin'
LIBDIR '$(PREFIX)/lib'
SHAREDIR '$(PREFIX)/share'
INCLUDEDIR '$(PREFIX)/include'
MANDIR '$(SHAREDIR)/man'
)
if [[ -f project.zsh && -r project.zsh ]]; then
. ./project.zsh
else
error "No “project.zsh” found in $(pwd)."
exit 1
fi
: > $Makefile
for target in ${targets[@]}; do
local _type=${type[$target]}
if exists ${_type}.prelude; then
${_type}.prelude
fi
done
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
all=(${targets[@]})
fi
write -n "all: ${all[@]}"
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]}"
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]}"
if exists "${type[$target]}.build"; then
${type[$target]}.build
else
error "No predefined rule for the following type: ${type[$target]}"
error " (expect trouble, nothings gonna work!)"
fi
if [[ "${installdir}" == "-" ]]; then
write "${target}.install:"
write ""
else
if exists "${type[$target]}.install"; then
${type[$target]}.install
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
fi
fi
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
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++))
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
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
write -n "install:"
for target in ${targets[@]}; do
write -n " ${target}.install"
done
write "\n\t@:\n"
write -n "uninstall:"
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
#)
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
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}'"
for VAR __ in ${variables}; do
printf " @echo ' - ${fg_bold[blue]}%-14s${fg[white]} \${$VAR}${reset_color}'\n" "$VAR" >> $Makefile
done
for VAR __ in ${prefixes}; do
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}'"
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")'"
write ".PHONY: all clean distclean dist install uninstall help"
write
}
export Makefile=Makefile
export MAKE='$(MAKE)'
export Q='$(Q)'
export gnu=false
export colors=false
while (($# > 0)); do
case "$1" in
-c|--colors)
export colors=true
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