6770e0de0ae18cb976e3b3bdb7e115432949952e — Drew DeVault 3 months ago a2f07ab master
Add configure script and Makefile

The configure script is POSIX sh and the Makefile is POSIX make.
8 files changed, 405 insertions(+), 15 deletions(-)

M .gitignore
A Makefile
A configure
A mkpc
M test/conformance/if.sh
M test/harness.sh -rw-r--r-- => -rwxr-xr-x
M test/meson.build
M test/pipeline.sh
M .gitignore => .gitignore +4 -0
@@ 53,3 53,7 @@   /build
  /build-*
+ 
+ .build
+ highlight
+ mrsh

A Makefile => Makefile +88 -0
@@ 0,0 1,88 @@
+ .POSIX:
+ .SUFFIXES:
+ OUTDIR=.build
+ include $(OUTDIR)/config.mk
+ 
+ INCLUDE=-Iinclude
+ 
+ public_includes=\
+ 		include/mrsh/arithm.h \
+ 		include/mrsh/array.h \
+ 		include/mrsh/ast.h \
+ 		include/mrsh/buffer.h \
+ 		include/mrsh/builtin.h \
+ 		include/mrsh/entry.h \
+ 		include/mrsh/getopt.h \
+ 		include/mrsh/hashtable.h \
+ 		include/mrsh/parser.h \
+ 		include/mrsh/shell.h
+ 
+ tests=\
+ 		test/conformance/if.sh \
+ 		test/case.sh \
+ 		test/command.sh \
+ 		test/for.sh \
+ 		test/function.sh \
+ 		test/loop.sh \
+ 		test/pipeline.sh \
+ 		test/subshell.sh \
+ 		test/syntax.sh \
+ 		test/ulimit.sh \
+ 		test/word.sh
+ 
+ include $(OUTDIR)/cppcache
+ 
+ .SUFFIXES: .c .o
+ 
+ .c.o:
+ 	@mkdir -p $$(dirname "$@")
+ 	@printf 'CC\t$@\n'
+ 	@touch $(OUTDIR)/cppcache
+ 	@grep $< $(OUTDIR)/cppcache >/dev/null || \
+ 		$(CPP) $(INCLUDE) -MM -MT $@ $< >> $(OUTDIR)/cppcache
+ 	@$(CC) -c $(CFLAGS) $(INCLUDE) -o $@ $<
+ 
+ $(OUTDIR)/libmrsh.a: $(libmrsh_objects)
+ 	@printf 'AR\t$@\n'
+ 	@$(AR) -csr $@ $(libmrsh_objects)
+ 
+ libmrsh.so.$(SOVERSION): $(OUTDIR)/libmrsh.a
+ 	@printf 'LD\t$@\n'
+ 	@$(CC) -shared $(LDFLAGS) -o $@ $(OUTDIR)/libmrsh.a
+ 
+ $(OUTDIR)/mrsh.pc:
+ 	@printf 'MKPC\t$@\n'
+ 	@PREFIX=$(PREFIX) ./mkpc $@
+ 
+ mrsh: $(OUTDIR)/libmrsh.a $(mrsh_objects)
+ 	@printf 'CCLD\t$@\n'
+ 	@$(CC) -o $@ $(LIBS) $(mrsh_objects) -L$(OUTDIR) -lmrsh
+ 
+ highlight: $(OUTDIR)/libmrsh.a $(highlight_objects)
+ 	@printf 'CCLD\t$@\n'
+ 	@$(CC) -o $@ $(LIBS) $(highlight_objects) -L$(OUTDIR) -lmrsh
+ 
+ check: mrsh $(tests)
+ 	@for t in $(tests); do \
+ 		printf '%-30s... ' "$$t" && \
+ 		MRSH=./mrsh REF_SH=$${REF_SH:-sh} ./test/harness.sh $$t >/dev/null && \
+ 		echo OK || echo FAIL; \
+ 	done
+ 
+ install: mrsh libmrsh.so.$(SOVERSION) $(OUTDIR)/mrsh.pc
+ 	mkdir -p $(BINDIR) $(LIBDIR) $(INCDIR)/mrsh $(PCDIR)
+ 	install -m755 mrsh $(BINDIR)/mrsh
+ 	install -m755 libmrsh.so.$(SOVERSION) $(LIBDIR)/libmrsh.so.$(SOVERSION)
+ 	for inc in $(public_includes); do \
+ 		install -m644 $$inc $(INCDIR)/mrsh/$$(basename $$inc); \
+ 	done
+ 	install -m644 $(OUTDIR)/mrsh.pc $(PCDIR)/mrsh.pc
+ 
+ clean:
+ 	rm -rf \
+ 		$(libmrsh_objects) \
+ 		$(mrsh_objects) \
+ 		$(highlight_objects) \
+ 		mrsh highlight libmrsh.so.$(SOVERSION) $(OUTDIR)/mrsh.pc
+ 
+ .PHONY: all install clean check

A configure => configure +286 -0
@@ 0,0 1,286 @@
+ #!/bin/sh -e
+ SOVERSION=0.0.0
+ 
+ pkg_config=${PKG_CONFIG:-pkg-config}
+ outdir=${OUTDIR:-.build}
+ srcdir=${SRCDIR:-$(dirname "$0")}
+ CC=${CC:-cc}
+ LIBS=
+ 
+ use_readline=-1
+ readline=readline
+ 
+ for arg
+ do
+ 	case "$arg" in
+ 		--prefix=*)
+ 			PREFIX=${arg#*=}
+ 			;;
+ 		--without-readline)
+ 			use_readline=0
+ 			;;
+ 		--with-readline=*)
+ 			use_readline=1
+ 			readline=${arg#*=}
+ 			;;
+ 	esac
+ done
+ 
+ libmrsh() {
+ 	genrules libmrsh \
+ 		'arithm.c' \
+ 		'array.c' \
+ 		'ast_print.c' \
+ 		'ast.c' \
+ 		'buffer.c' \
+ 		'builtin/alias.c' \
+ 		'builtin/bg.c' \
+ 		'builtin/break.c' \
+ 		'builtin/builtin.c' \
+ 		'builtin/cd.c' \
+ 		'builtin/colon.c' \
+ 		'builtin/command.c' \
+ 		'builtin/dot.c' \
+ 		'builtin/eval.c' \
+ 		'builtin/exit.c' \
+ 		'builtin/export.c' \
+ 		'builtin/false.c' \
+ 		'builtin/fg.c' \
+ 		'builtin/getopts.c' \
+ 		'builtin/pwd.c' \
+ 		'builtin/read.c' \
+ 		'builtin/set.c' \
+ 		'builtin/shift.c' \
+ 		'builtin/times.c' \
+ 		'builtin/true.c' \
+ 		'builtin/type.c' \
+ 		'builtin/ulimit.c' \
+ 		'builtin/umask.c' \
+ 		'builtin/unalias.c' \
+ 		'builtin/unset.c' \
+ 		'builtin/unspecified.c' \
+ 		'builtin/wait.c' \
+ 		'getopt.c' \
+ 		'hashtable.c' \
+ 		'parser/arithm.c' \
+ 		'parser/parser.c' \
+ 		'parser/program.c' \
+ 		'parser/word.c' \
+ 		'shell/arithm.c' \
+ 		'shell/entry.c' \
+ 		'shell/job.c' \
+ 		'shell/path.c' \
+ 		'shell/process.c' \
+ 		'shell/redir.c' \
+ 		'shell/shell.c' \
+ 		'shell/task/pipeline.c' \
+ 		'shell/task/simple_command.c' \
+ 		'shell/task/task.c' \
+ 		'shell/task/word.c' \
+ 		'shell/word.c'
+ }
+ 
+ mrsh() {
+ 	if [ $use_readline -eq 1 ]
+ 	then
+ 		genrules mrsh \
+ 			'main.c' \
+ 			'frontend/readline.c'
+ 	else
+ 		genrules mrsh \
+ 			'main.c' \
+ 			'frontend/basic.c'
+ 	fi
+ }
+ 
+ highlight() {
+ 	genrules highlight example/highlight.c
+ }
+ 
+ genrules() {
+ 	target="$1"
+ 	shift
+ 	printf '# Begin generated rules for %s\n' "$target"
+ 	for file in "$@"
+ 	do
+ 		file="${file%.*}"
+ 		printf '%s.o: %s.c\n' "$file" "$file"
+ 	done
+ 	printf '%s_objects=\\\n' "$target"
+ 	n=0
+ 	for file in "$@"
+ 	do
+ 		file="${file%.*}"
+ 		n=$((n+1))
+ 		if [ $n -eq $# ]
+ 		then
+ 			printf '\t%s.o\n' "$file"
+ 		else
+ 			printf '\t%s.o \\\n' "$file"
+ 		fi
+ 	done
+ 	printf '# End generated rules for %s\n' "$target"
+ }
+ 
+ append_cflags() {
+ 	for flag
+ 	do
+ 		CFLAGS="$(printf '%s \\\n\t%s' "$CFLAGS" "$flag")"
+ 	done
+ }
+ 
+ append_ldflags() {
+ 	for flag
+ 	do
+ 		LDFLAGS="$(printf '%s \\\n\t%s' "$LDFLAGS" "$flag")"
+ 	done
+ }
+ 
+ append_libs() {
+ 	for flag
+ 	do
+ 		LIBS="$(printf '%s \\\n\t%s' "$LIBS" "$flag")"
+ 	done
+ }
+ 
+ test_cflags() {
+ 	[ ! -e "$outdir"/check.c ] && cat <<-EOF > "$outdir"/check.c
+ 	int main(void) { return 0; }
+ 	EOF
+ 	werror=""
+ 	case "$CFLAGS" in
+ 		*-Werror*)
+ 			werror="-Werror"
+ 			;;
+ 	esac
+ 	if $CC $werror "$@" -o /dev/null "$outdir"/check.c >/dev/null 2>&1
+ 	then
+ 		append_cflags "$@"
+ 	else
+ 		return 1
+ 	fi
+ }
+ 
+ test_ldflags() {
+ 	[ ! -e "$outdir"/check.c ] && cat <<-EOF > "$outdir"/check.c
+ 	int main(void) { return 0; }
+ 	EOF
+ 	if $CC "$@" -o /dev/null "$outdir"/check.c >/dev/null 2>&1
+ 	then
+ 		append_ldflags "$@"
+ 	else
+ 		return 1
+ 	fi
+ }
+ 
+ mkdir -p $outdir
+ 
+ for flag in \
+ 	-g -std=c99 -pedantic -Werror -Wundef -Wlogical-op \
+ 	-Wmissing-include-dirs -Wold-style-definition -Wpointer-arith -Winit-self \
+ 	-Wfloat-equal -Wstrict-prototypes -Wredundant-decls \
+ 	-Wimplicit-fallthrough=2 -Wendif-labels -Wstrict-aliasing=2 -Woverflow \
+ 	-Wformat=2 -Wno-missing-braces -Wno-missing-field-initializers \
+ 	-Wno-unused-parameter
+ do
+ 	printf "Checking for $flag... "
+ 	if test_cflags "$flag"
+ 	then
+ 		echo yes
+ 	else
+ 		echo no
+ 	fi
+ done
+ 
+ for flag in -fPIC -Wl,--no-undefined -Wl,--as-needed
+ do
+ 	test_ldflags "$flag"
+ done
+ 
+ soname=libmrsh.so.$(echo "$SOVERSION" | cut -d. -f1)
+ printf "Checking for specifying soname for shared lib... "
+ if ! \
+ 	test_ldflags -Wl,-soname,$soname || \
+ 	test_ldflags -Wl,-install_name,$soname
+ then
+ 	echo no
+ 	echo "Unable to specify soname (is $(uname) supported?)" >&2
+ 	exit 1
+ else
+ 	echo yes
+ fi
+ 
+ printf "Checking for exported symbol restrictions... "
+ if ! \
+ 	test_ldflags -Wl,--version-script="libmrsh.gnu.sym" || \
+ 	test_ldflags -Wl,-exported_symbols_list,"libmrsh.darwin.sym"
+ then
+ 	echo no
+ 	echo "Unable to specify exported symbols (is $(uname) supported?)" >&2
+ 	exit 1
+ else
+ 	echo yes
+ fi
+ 
+ if [ $use_readline -eq -1 ]
+ then
+ 	printf "Checking for readline... "
+ 	if $pkg_config readline
+ 	then
+ 		readline=readline
+ 		use_readline=1
+ 		append_cflags -DHAVE_READLINE
+ 		echo yes
+ 	else
+ 		echo no
+ 	fi
+ fi
+ if [ $use_readline -eq -1 ]
+ then
+ 	printf "Checking for libedit... "
+ 	if $pkg_config libedit
+ 	then
+ 		echo yes
+ 		readline=libedit
+ 		use_readline=1
+ 		append_cflags -DHAVE_EDITLINE
+ 	else
+ 		echo no
+ 	fi
+ fi
+ 
+ if [ $use_readline -eq 1 ]
+ then
+ 	append_cflags $($pkg_config --cflags $readline)
+ 	append_libs $($pkg_config --libs $readline)
+ 	if [ "$readline" = "readline" ]
+ 	then
+ 		# Undo GNU_SOURCE
+ 		append_cflags -U_GNU_SOURCE
+ 	fi
+ fi
+ 
+ printf "Creating $outdir/config.mk... "
+ cat <<EOF > "$outdir"/config.mk
+ SOVERSION=$SOVERSION
+ CC=$CC
+ PREFIX=${PREFIX:-/usr/local}
+ _INSTDIR=\$(DESTDIR)\$(PREFIX)
+ BINDIR?=${BINDIR:-\$(_INSTDIR)/bin}
+ LIBDIR?=${LIBDIR:-\$(_INSTDIR)/lib}
+ INCDIR?=${INCDIR:-\$(_INSTDIR)/include}
+ MANDIR?=${MANDIR:-\$(_INSTDIR)/share/man}
+ PCDIR?=${PCDIR:-\$(_INSTDIR)/lib/pkgconfig}
+ CFLAGS=${CFLAGS}
+ LDFLAGS=${LDFLAGS}
+ LIBS=${LIBS}
+ SRCDIR=${srcdir}
+ 
+ all: mrsh highlight libmrsh.so.\$(SOVERSION) \$(OUTDIR)/mrsh.pc
+ EOF
+ libmrsh >>"$outdir"/config.mk
+ mrsh >>"$outdir"/config.mk
+ highlight >>"$outdir"/config.mk
+ echo done
+ 
+ touch $outdir/cppcache

A mkpc => mkpc +12 -0
@@ 0,0 1,12 @@
+ #!/bin/sh
+ cat <<EOF > $1
+ prefix=$PREFIX
+ libdir=\${prefix}/lib
+ includedir=\${prefix}/include
+ 
+ Name: mrsh
+ Description: POSIX shell library
+ Version: 0.0.0
+ Libs: -L\${libdir} -lmrsh
+ Cflags: -I\${includedir}
+ EOF

M test/conformance/if.sh => test/conformance/if.sh +8 -8
@@ 7,20 7,20 @@ # pass
  # pass
  
- echo >&2 "-> if..fi with true condition"
+ echo "-> if..fi with true condition"
  if true
  then
  	echo pass
  fi
  
- echo >&2 "-> if..fi with false condition"
+ echo "-> if..fi with false condition"
  if false
  then
  	echo >&2 "fail: This branch should not have run" && exit 1
  fi
  echo pass
  
- echo >&2 "-> if..else..fi with true condition"
+ echo "-> if..else..fi with true condition"
  if true
  then
  	echo pass


@@ 28,7 28,7 @@ echo >&2 "fail: This branch should not have run" && exit 1
  fi
  
- echo >&2 "-> if..else..fi with false condition"
+ echo "-> if..else..fi with false condition"
  if false
  then
  	echo >&2 "fail: This branch should not have run" && exit 1


@@ 36,7 36,7 @@ echo pass
  fi
  
- echo >&2 "-> if..else..fi with true condition"
+ echo "-> if..else..fi with true condition"
  if true
  then
  	echo pass


@@ 44,7 44,7 @@ echo >&2 "fail: This branch should not have run" && exit 1
  fi
  
- echo >&2 "-> if..else..elif..fi with false condition"
+ echo "-> if..else..elif..fi with false condition"
  if false
  then
  	echo >&2 "fail: This branch should not have run" && exit 1


@@ 55,7 55,7 @@ echo >&2 "fail: This branch should not have run" && exit 1
  fi
  
- echo >&2 "-> test exit status"
+ echo "-> test exit status"
  if true
  then
  	( exit 10 )


@@ 71,7 71,7 @@ fi
  [ $# -eq 20 ] || { echo >&2 "fail: Expected status code = 20" && exit 1; }
  
- echo >&2 "-> test alternate syntax"
+ echo "-> test alternate syntax"
  # These tests are only expected to parse, they do not make assertions
  if true; then true; fi
  if true; then true; else true; fi

M test/harness.sh => test/harness.sh +3 -3
@@ 1,11 1,11 @@ #!/bin/sh
  dir=$(dirname "$0")
- testcase="$dir/$1"
+ testcase="$1"
  
- echo >&2 "Running with mrsh"
+ echo "Running with mrsh"
  mrsh_out=$("$MRSH" "$testcase")
  mrsh_ret=$?
- echo >&2 "Running with reference shell"
+ echo "Running with reference shell"
  ref_out=$("$REF_SH" "$testcase")
  ref_ret=$?
  if [ $mrsh_ret -ne $ref_ret ] || [ "$mrsh_out" != "$ref_out" ]

M test/meson.build => test/meson.build +1 -1
@@ 19,7 19,7 @@   foreach test_file : test_files
  	test(
- 		test_file,
+ 		join_paths(meson.current_source_dir(), test_file),
  		harness,
  		env: [
  			'MRSH=@0@'.format(mrsh_exe.full_path()),

M test/pipeline.sh => test/pipeline.sh +3 -3
@@ 1,12 1,12 @@ #!/bin/sh
  
- echo >&2 "Pipeline with 1 command"
+ echo "Pipeline with 1 command"
  echo "a b c d"
  
- echo >&2 "Pipeline with 2 commands"
+ echo "Pipeline with 2 commands"
  echo "a b c d" | sed s/b/B/
  
- echo >&2 "Pipeline with 3 commands"
+ echo "Pipeline with 3 commands"
  echo "a b c d" | sed s/b/B/ | sed s/c/C/
  
  echo >&2 "Pipeline with bang"