vix 0.1.1

- improve vipakfile
- reuse code for build, test and release
- add release subcommand
- add clean subcommand
- improve portability
- better skeleton
This commit is contained in:
Antranig Vartanian 2025-06-18 13:13:54 +04:00
parent 6f90eab38f
commit 88b36af849
No known key found for this signature in database
GPG key ID: DE3998662D59F21C
2 changed files with 246 additions and 65 deletions

75
README.md Normal file
View file

@ -0,0 +1,75 @@
# vix
Vix is a simple, POSIX-shell-based project and build manager for [Vishap Oberon](https://vishap.oberon.am) projects.
It provides commands to create a new project skeleton, compile library modules, link and run your program, run tests, and produce release binaries.
## vix in action
[![asciicast](https://asciinema.org/a/723640.svg)](https://asciinema.org/a/723640)
## Installation
1. clone this repository
2. `sudo cp vix /usr/local/bin/vix`
## Usage
```
Usage: vix ...
help
version
new PATH [--module MODULE] [--app APP]
run
build
test
release
```
```
vix new hello_world
```
## Directory Layout
```
hello_world/
src/
HelloWorld.Mod ← library API
HelloWorldMain.Mod ← "main" program entry
test/
HelloWorldTest.Mod ← test suite
vipakfile ← project manifest
README.md
.gitignore
.gitattributes
```
## vipakfile format
```
NAME = hello_world
VERSION = 0.1.0
AUTHOR =
LICENSE =
DEPS =
RUN = ./HelloWorldMain
MAIN = %projdir%/src/HelloWorldMain.Mod
BUILD = %projdir%/src/HelloWorld.Mod
TEST_RUN = ./HelloWorldTest
TEST_MAIN = %projdir%/test/HelloWorldTest.Mod
TEST =
```
- `%projdir%` is replaced at runtime with the project root directory.
- Semicolons (`;`) separate multiple entries, preserving order.
## TODO
- Integrate `vix` with `vipak` for dependency management
- Incremental builds
- Release tarballs, Dockerfiles and Jailerfiles
- Rewrite vix in Oberon

236
vix
View file

@ -3,18 +3,19 @@
# set global variables # set global variables
PROGNAME=${0##*/} PROGNAME=${0##*/}
VERSION="0.1.0" VERSION="0.1.1"
PROGPATH=$(readlink -f "$0")
COLOUR_SET_R="\033[0;31m" COLOUR_SET_R="\033[0;31m"
COLOUR_SET_G="\033[0;32m" COLOUR_SET_G="\033[0;32m"
COLOUR_SET_Y="\033[0;33m"
COLOUR_SET_B="\033[0;34m" COLOUR_SET_B="\033[0;34m"
COLOUR_SET_M="\033[0;35m"
COLOUR_SET_C="\033[0;36m"
COLOUR_SET_W="\033[0;37m"
COLOUR_END="\033[0m" COLOUR_END="\033[0m"
PROJECT_ROOT=$(pwd)
host_os=$(uname -s)
host_arch=$(uname -m)
: "${VIX_TARGET_DIR:=build/${host_os}-${host_arch}}"
help_usage(){ help_usage(){
cat <<EOT cat <<EOT
@ -22,10 +23,11 @@ Usage: vix ...
help help
version version
new PATH [--module MODULE] [--app APP] new PATH [--module MODULE] [--app APP]
run
build build
test test
run
clean clean
release [--tarball]
EOT EOT
exit 0 exit 0
} }
@ -48,20 +50,15 @@ pinfo(){
} }
paction(){ paction(){
printf "${COLOUR_SET_G}[\*] ${COLOUR_END} %s\n" "${@}" >&2 printf "${COLOUR_SET_G}[*] ${COLOUR_END} %s\n" "${@}" >&2
} }
confirm_create_path(){ confirm_create_path(){
printf "The directory \"${PATH_ARG}\" already exists." perror "The directory \"${PATH_ARG}\" already exists."
printf " Are you sure you want to continue?"
read -p " [yN] " yesno
echo "${yesno}"
exit 0
} }
check_path(){ check_path(){
#paction "check_path"
stat "$PATH_ARG" >/dev/null 2>&1 && confirm_create_path stat "$PATH_ARG" >/dev/null 2>&1 && confirm_create_path
} }
@ -72,7 +69,7 @@ create_path(){
create_readme(){ create_readme(){
paction "Creating ${PATHBASE}/README.md" paction "Creating ${PATHBASE}/README.md"
cat <<EOF > ${PATH_ARG}/README.md cat <<EOF > "${PATH_ARG}"/README.md
# ${APP} # ${APP}
**TODO: Add description** **TODO: Add description**
@ -94,7 +91,7 @@ EOF
create_gitattributes(){ create_gitattributes(){
paction "Creating ${PATHBASE}/.gitattributes" paction "Creating ${PATHBASE}/.gitattributes"
cat <<EOF > ${PATH_ARG}/.gitattributes cat <<EOF > "${PATH_ARG}"/.gitattributes
# Set the language to Oberon # Set the language to Oberon
*.Mod linguist-language=Oberon *.Mod linguist-language=Oberon
*.mod linguist-language=Oberon *.mod linguist-language=Oberon
@ -103,7 +100,7 @@ EOF
create_gitignore(){ create_gitignore(){
paction "Creating ${PATHBASE}/.gitignore" paction "Creating ${PATHBASE}/.gitignore"
cat <<EOF > ${PATH_ARG}/.gitignore cat <<EOF > "${PATH_ARG}"/.gitignore
build build
release release
EOF EOF
@ -111,18 +108,22 @@ EOF
create_vipakfile(){ create_vipakfile(){
paction "Creating ${PATHBASE}/vipakfile" paction "Creating ${PATHBASE}/vipakfile"
cat <<EOF > ${PATH_ARG}/vipakfile cat <<EOF > "${PATH_ARG}"/vipakfile
NAME = ${APP} NAME = ${APP}
VERSION = 0.1.0 VERSION = 0.1.0
AUTHOR = AUTHOR =
LICENSE = LICENSE =
DEPS = DEPS =
BUILD = voc %projdir%/src/${MODULE}.Mod -s RUN = ./${MODULE}Main
MAIN = %projdir%/src/${MODULE}Main.Mod
BUILD = %projdir%/src/${MODULE}.Mod
TEST = voc %projdir%/test/${MODULE}Test.Mod -m ; ./${MODULE}Test TEST_RUN = ./${MODULE}Test
TEST_MAIN = %projdir%/test/${MODULE}Test.Mod
TEST =
EOF EOF
} }
@ -133,22 +134,34 @@ create_src(){
create_src_prog(){ create_src_prog(){
paction "Creating ${PATHBASE}/src/${MODULE}.Mod" paction "Creating ${PATHBASE}/src/${MODULE}.Mod"
cat <<EOF > ${PATH_ARG}/src/${MODULE}.Mod cat <<EOF > "${PATH_ARG}"/src/${MODULE}.Mod
MODULE ${MODULE}; MODULE ${MODULE};
IMPORT Out; IMPORT Out;
PROCEDURE Hello*(s: ARRAY OF CHAR); PROCEDURE Run*(): INTEGER;
BEGIN BEGIN
Out.String("Hello "); RETURN 42
Out.String(s); END Run;
Out.Ln;
END Hello;
END ${MODULE}. END ${MODULE}.
EOF EOF
} }
create_src_progmain(){
paction "Creating ${PATHBASE}/src/${MODULE}Main.Mod"
cat <<EOF > "${PATH_ARG}"/src/${MODULE}Main.Mod
MODULE ${MODULE}Main;
IMPORT ${MODULE}, Out;
BEGIN
Out.Int(${MODULE}.Run(), 0); Out.Ln;
END ${MODULE}Main.
EOF
}
create_test(){ create_test(){
paction "Creating ${PATHBASE}/test" paction "Creating ${PATHBASE}/test"
mkdir -p "${PATH_ARG}/test" mkdir -p "${PATH_ARG}/test"
@ -156,31 +169,48 @@ create_test(){
create_test_progtest(){ create_test_progtest(){
paction "Creating ${PATHBASE}/test/${MODULE}Test.Mod" paction "Creating ${PATHBASE}/test/${MODULE}Test.Mod"
cat <<EOF > ${PATH_ARG}/test/${MODULE}Test.Mod cat <<EOF > "${PATH_ARG}"/test/${MODULE}Test.Mod
MODULE ${MODULE}Test; MODULE ${MODULE}Test;
IMPORT ${MODULE}; IMPORT ${MODULE}, Out;
BEGIN BEGIN
${MODULE}.Hello("world") IF ${MODULE}.Run() = 42 THEN
Out.String("All works!")
ELSE
Out.String("Test Failed...")
END;
Out.Ln;
END ${MODULE}Test. END ${MODULE}Test.
EOF EOF
} }
success_msg(){ success_msg(){
pinfo 'All good!' pinfo 'All good!'
cat <<EOF
Your Oberon project was created successfully.
You can use "vix" to build it, test it, and more:
cd ${PATHBASE}
vix build && vix test
vix run
Run "vix help" for more commands.
EOF
} }
new_proj() { new_proj() {
shift # remove 'new' shift
if [ $# -lt 1 ]; then if [ $# -lt 1 ]; then
perror "missing PATH argument for '${PROGNAME} new'" perror "missing PATH argument for '${PROGNAME} new'"
fi fi
PATH_ARG="$1" PATH_ARG="$1"
PATHBASE="$(basename ${PATH_ARG})" PATHBASE=$(basename "${PATH_ARG}")
shift shift
MODULE="" MODULE=""
@ -232,7 +262,6 @@ new_proj() {
APP="${PATHBASE}" APP="${PATHBASE}"
fi fi
# Output debug values
pinfo "PATH : $PATH_ARG" pinfo "PATH : $PATH_ARG"
pinfo "MODULE : $MODULE" pinfo "MODULE : $MODULE"
pinfo "APP : $APP" pinfo "APP : $APP"
@ -245,54 +274,128 @@ new_proj() {
create_vipakfile create_vipakfile
create_src create_src
create_src_prog create_src_prog
create_src_progmain
create_test create_test
create_test_progtest create_test_progtest
success_msg success_msg
} }
_process_mods() {
key=$1; mode=$2
[ -f vipakfile ] || perror "vipakfile not found"
_run_steps() { # grab & massage the RHS
key="$1"; dir="$2" line=$(
grep -E "^${key}[[:space:]]*=" vipakfile \
| cut -d= -f2- \
| sed -e 's/^[[:space:]]*//' \
-e "s|%projdir%|${PROJECT_ROOT}|g"
)
[ -f vipakfile ] || perror "vipakfile not found in current directory" paction "Processing ${key} (${mode}) in '${VIX_TARGET_DIR}/'"
mkdir -p "${VIX_TARGET_DIR}"
cd "${VIX_TARGET_DIR}" || perror "cd to ${VIX_TARGET_DIR} failed"
# pull everything after the first '='
line=$(grep -E "^${key}[[:space:]]*=" vipakfile \
| cut -d= -f2-)
# trim leading whitespace and replace %projdir% → ../
line=$(printf "%s" "$line" \
| sed -e 's/^[[:space:]]*//' -e 's|%projdir%|..|g')
paction "Running ${key} commands in '${dir}/'"
mkdir -p "$dir"
cd "$dir" || perror "cannot enter directory '$dir'"
# split on ';' and run each chunk
oldIFS=$IFS; IFS=';' oldIFS=$IFS; IFS=';'
for cmd in $line; do for entry in $line; do
# trim whitespace # trim
cmd=$(printf "%s" "$cmd" \ entry=$(printf "%s" "$entry" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') [ -z "$entry" ] && continue
[ -z "$cmd" ] && continue
paction "Executing: $cmd" paction "voc ${mode} ${entry}"
eval "$cmd" || perror "${key} command failed: $cmd" voc "${mode}" "${entry}" 2>/dev/null \
|| perror "${key} step failed: ${entry}"
done done
IFS=$oldIFS IFS=$oldIFS
cd -
pinfo "${key} complete"
} }
_run_cmds() {
key=$1
[ -f vipakfile ] || perror "vipakfile not found"
run_line=$(
grep -E "^${key}[[:space:]]*=" vipakfile \
| cut -d= -f2- \
| sed -e 's/^[[:space:]]*//'
)
[ -n "$run_line" ] || perror "${key} not set in vipakfile"
paction "Running ${key} in '${VIX_TARGET_DIR}/'"
cd "${VIX_TARGET_DIR}" || perror "cd to ${VIX_TARGET_DIR} failed"
oldIFS=$IFS; IFS=';'
for cmd in $run_line; do
cmd=$(printf "%s" "$cmd" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
[ -z "$cmd" ] && continue
paction "Executing: ${cmd}"
eval "${cmd}" \
|| perror "execution failed: ${cmd}"
done
IFS=$oldIFS
cd -
}
# now the four subcommands:
build_proj() { build_proj() {
_run_steps BUILD build _process_mods BUILD -s
_process_mods MAIN -m
pinfo "Build complete"
}
run_proj() {
_run_cmds RUN
} }
test_proj() { test_proj() {
_run_steps TEST build _process_mods TEST -s
_process_mods TEST_MAIN -m
_run_cmds TEST_RUN
pinfo "All tests passed"
} }
release_proj() {
[ -f vipakfile ] || perror "vipakfile not found in current directory"
_process_mods BUILD -s
_process_mods MAIN -M
release_subdir="${VIX_TARGET_DIR}/release"
paction "Creating release directory '${release_subdir}'"
mkdir -p "${release_subdir}"
# 4) copy each MAIN binary into release/
main_line=$(
grep -E '^MAIN[[:space:]]*=' vipakfile \
| cut -d= -f2- \
| sed -e 's/^[[:space:]]*//' \
-e "s|%projdir%|${PROJECT_ROOT}|g"
)
oldIFS=$IFS; IFS=';'
for entry in $main_line; do
entry=$(printf "%s" "$entry" \
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
[ -z "$entry" ] && continue
bin=$(basename "$entry" .Mod)
paction "Copying '${bin}' → '${release_subdir}/'"
cp "${VIX_TARGET_DIR}/${bin}" "${release_subdir}/" \
|| perror "failed to install release binary: ${bin}"
done
IFS=$oldIFS
pinfo "Release ready in '${release_subdir}'"
}
clean_proj() {
paction "Removing build artifacts in '${VIX_TARGET_DIR}'"
rm -rf "${VIX_TARGET_DIR}" \
|| perror "failed to remove '${VIX_TARGET_DIR}'"
pinfo "Clean complete"
}
vix_main(){ vix_main(){
[ $# -eq 0 ] && help_usage [ $# -eq 0 ] && help_usage
@ -302,6 +405,9 @@ vix_main(){
new) new_proj "$@" ;; new) new_proj "$@" ;;
build) build_proj ;; build) build_proj ;;
test) test_proj ;; test) test_proj ;;
run) run_proj ;;
release) release_proj ;;
clean) clean_proj ;;
*) help_usage ;; *) help_usage ;;
esac esac
} }