Meta: Update makem.sh, test.yml

This commit is contained in:
Adam Porter 2020-01-10 07:26:22 -06:00
parent 16cb6cef52
commit 1f79632372
2 changed files with 178 additions and 151 deletions

View file

@ -27,6 +27,7 @@ name: "CI"
on: on:
pull_request: pull_request:
push: push:
# Comment out this section to enable testing of all branches.
# branches: # branches:
# - master # - master
@ -46,16 +47,11 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install sandbox script
run: |
curl -o $GITHUB_WORKSPACE/emacs-sandbox.sh \
https://raw.githubusercontent.com/alphapapa/emacs-sandbox.sh/master/emacs-sandbox.sh
chmod +x $GITHUB_WORKSPACE/emacs-sandbox.sh
echo ::add-path::$GITHUB_WORKSPACE
echo ::set-env name=SANDBOX_DIR::$(mktemp -d)
- name: Initialize sandbox - name: Initialize sandbox
run: ./makem.sh -vv --sandbox-dir=$SANDBOX_DIR --auto-install --install package-lint run: |
SANDBOX_DIR=$(mktemp -d) || exit 1
echo ::set-env name=SANDBOX_DIR::$SANDBOX_DIR
./makem.sh -vv --sandbox-dir=$SANDBOX_DIR --auto-install --install package-lint
# The "all" rule is not used, because it treats compilation warnings # The "all" rule is not used, because it treats compilation warnings
# as failures, so linting and testing are run as separate steps. # as failures, so linting and testing are run as separate steps.

315
makem.sh
View file

@ -21,13 +21,11 @@
# occur. With increasing verbosity levels, more detail gives positive # occur. With increasing verbosity levels, more detail gives positive
# feedback. Output is colored by default to make reading easy. # feedback. Output is colored by default to make reading easy.
# When desired, emacs-sandbox.sh can be used as a backend, which # The script can run Emacs with the developer's local Emacs
# allows package dependencies to be installed automatically into a # configuration, or with a clean, "sandbox" configuration that can be
# clean Emacs "sandbox" configuration without affecting the # optionally removed afterward. This is especially helpful when
# developer's personal configuration. This is especially helpful when
# upstream dependencies may have released new versions that differ # upstream dependencies may have released new versions that differ
# from those installed in the developer's personal configuration. See # from those installed in the developer's personal configuration.
# <https://github.com/alphapapa/emacs-sandbox.sh>.
# * License: # * License:
@ -63,18 +61,17 @@ Rules:
test-buttercup Run Buttercup tests. test-buttercup Run Buttercup tests.
test-ert Run ERT tests. test-ert Run ERT tests.
These are especially useful with --sandbox: batch Run Emacs in batch mode, loading project source and test files
automatically, with remaining args (after "--") passed to Emacs.
batch Run Emacs in batch mode, loading project source and test files interactive Run Emacs interactively, loading project source and test files
automatically, with remaining args (after "--") passed to Emacs. automatically.
interactive Run Emacs interactively, loading project source and test files
automatically.
Options: Options:
-d, --debug Print debug info. -d, --debug Print debug info.
-h, --help I need somebody! -h, --help I need somebody!
-v, --verbose Increase verbosity, up to -vv. -v, --verbose Increase verbosity, up to -vv.
--debug-load-path Print load-path.
--debug-load-path Print load-path from inside Emacs.
-f FILE, --file FILE Check FILE in addition to discovered files. -f FILE, --file FILE Check FILE in addition to discovered files.
@ -82,10 +79,7 @@ Options:
-C, --no-compile Don't compile files automatically. -C, --no-compile Don't compile files automatically.
Sandbox options: Sandbox options:
These require emacs-sandbox.sh to be on your PATH. Find it at -s, --sandbox Run Emacs with an empty config in a temporary
<https://github.com/alphapapa/emacs-sandbox.sh>.
-s, --sandbox Run Emacs with emacs-sandbox.sh in a temporary
directory (removing directory on exit). directory (removing directory on exit).
-S, --sandbox-dir DIR Use DIR for the sandbox directory (leaving it -S, --sandbox-dir DIR Use DIR for the sandbox directory (leaving it
on exit). Implies -s. on exit). Implies -s.
@ -93,10 +87,8 @@ Sandbox options:
-i, --install PACKAGE Install PACKAGE before running rules. -i, --install PACKAGE Install PACKAGE before running rules.
Source files are automatically discovered from git, or may be Source files are automatically discovered from git, or may be
specified with options. specified with options. Package dependencies are discovered from
"Package-Requires" headers in source files and from a Cask file.
Package dependencies are discovered from "Package-Requires" headers in
source files and from a Cask file.
EOF EOF
} }
@ -156,8 +148,8 @@ function elisp-package-initialize-file {
(require 'package) (require 'package)
(setq package-archives (list (cons "gnu" "https://elpa.gnu.org/packages/") (setq package-archives (list (cons "gnu" "https://elpa.gnu.org/packages/")
(cons "melpa" "https://melpa.org/packages/") (cons "melpa" "https://melpa.org/packages/")
(cons "melpa-stable" "https://stable.melpa.org/packages/") (cons "melpa-stable" "https://stable.melpa.org/packages/")))
(cons "org" "https://orgmode.org/elpa/"))) $elisp_org_package_archive
(package-initialize) (package-initialize)
(setq load-prefer-newer t) (setq load-prefer-newer t)
EOF EOF
@ -167,30 +159,39 @@ EOF
# ** Emacs # ** Emacs
function run_emacs { function run_emacs {
debug "run_emacs: $emacs_command -Q $batch_arg --load=$package_initialize_file -L \"$load_path\" $@" # NOTE: The sandbox args need to come before the package
if [[ $debug_load_path ]] # initialization so Emacs will use the sandbox's packages.
then local emacs_command=(
debug $($emacs_command -Q $batch_arg \ "${emacs_command[@]}"
--load=$package_initialize_file \ -Q
-L "$load_path" \ "${args_sandbox[@]}"
--eval "(message \"LOAD-PATH: %s\" load-path)" \ -l $package_initialize_file
2>&1) $arg_batch
fi -L "$load_path"
)
output_file=$(mktemp) # Show debug message with load-path from inside Emacs.
$emacs_command -Q $batch_arg \ [[ $debug_load_path ]] \
--load=$package_initialize_file \ && debug $("${emacs_command[@]}" \
-L "$load_path" \ --batch \
"$@" \ --eval "(message \"LOAD-PATH: %s\" load-path)" \
&>$output_file 2>&1)
# Set output file.
output_file=$(mktemp) || die "Unable to make output file."
paths_temp+=("$output_file")
# Run Emacs.
debug "run_emacs: ${emacs_command[@]} $@ &>\"$output_file\""
"${emacs_command[@]}" "$@" &>"$output_file"
# Check exit code and output.
exit=$? exit=$?
[[ $exit != 0 ]] && debug "Emacs exited non-zero: $exit" [[ $exit != 0 ]] \
if [[ $verbose -gt 1 || $exit != 0 ]] && debug "Emacs exited non-zero: $exit"
then
cat $output_file [[ $verbose -gt 1 || $exit != 0 ]] \
fi && cat $output_file
rm -f $output_file
return $exit return $exit
} }
@ -210,27 +211,27 @@ function batch-byte-compile {
# ** Files # ** Files
function project-elisp-files { function files-project-elisp {
# Echo list of Elisp files in project. # Echo list of Elisp files in project.
git ls-files 2>/dev/null | egrep "\.el$" | exclude-files git ls-files 2>/dev/null | egrep "\.el$" | filter-files-exclude
} }
function project-source-files { function files-project-source {
# Echo list of Elisp files that are not tests. # Echo list of Elisp files that are not tests.
project-elisp-files | egrep -v "$test_files_regexp" | feature-files files-project-elisp | egrep -v "$test_files_regexp" | filter-files-feature
} }
function project-test-files { function files-project-test {
# Echo list of Elisp test files. # Echo list of Elisp test files.
project-elisp-files | egrep "$test_files_regexp" files-project-elisp | egrep "$test_files_regexp"
} }
function exclude-files { function filter-files-exclude {
# Filter out paths (STDIN) which should be excluded by default. # Filter out paths (STDIN) which should be excluded by default.
egrep -v "(/\.cask/|-autoloads.el|.dir-locals)" egrep -v "(/\.cask/|-autoloads.el|.dir-locals)"
} }
function feature-files { function filter-files-feature {
# Read paths on STDIN and echo ones that (provide 'a-feature). # Read paths on STDIN and echo ones that (provide 'a-feature).
while read path while read path
do do
@ -240,7 +241,7 @@ function feature-files {
done done
} }
function load-files-args { function args-load-files {
# For file in $@, echo "--load $file". # For file in $@, echo "--load $file".
for file in "$@" for file in "$@"
do do
@ -248,17 +249,9 @@ function load-files-args {
done done
} }
function files_args {
# For file in STDIN, echo "$file".
while read file
do
printf -- '%q ' "$file"
done
}
function test-files-p { function test-files-p {
# Return 0 if $project_test_files is non-empty. # Return 0 if $files_project_test is non-empty.
[[ "${project_test_files[@]}" ]] [[ "${files_project_test[@]}" ]]
} }
function buttercup-tests-p { function buttercup-tests-p {
@ -266,7 +259,7 @@ function buttercup-tests-p {
test-files-p || die "No tests found." test-files-p || die "No tests found."
debug "Checking for Buttercup tests..." debug "Checking for Buttercup tests..."
grep "(require 'buttercup)" "${project_test_files[@]}" &>/dev/null grep "(require 'buttercup)" "${files_project_test[@]}" &>/dev/null
} }
function ert-tests-p { function ert-tests-p {
@ -277,14 +270,14 @@ function ert-tests-p {
# We check for this rather than "(require 'ert)", because ERT may # We check for this rather than "(require 'ert)", because ERT may
# already be loaded in Emacs and might not be loaded with # already be loaded in Emacs and might not be loaded with
# "require" in a test file. # "require" in a test file.
grep "(ert-deftest" "${project_test_files[@]}" &>/dev/null grep "(ert-deftest" "${files_project_test[@]}" &>/dev/null
} }
function dependencies { function dependencies {
# Echo list of package dependencies. # Echo list of package dependencies.
# Search package headers. # Search package headers.
egrep '^;; Package-Requires: ' $(project-source-files) $(project-test-files) \ egrep '^;; Package-Requires: ' $(files-project-source) $(files-project-test) \
| egrep -o '\([^([:space:]][^)]*\)' \ | egrep -o '\([^([:space:]][^)]*\)' \
| egrep -o '^[^[:space:])]+' \ | egrep -o '^[^[:space:])]+' \
| sed -r 's/\(//g' \ | sed -r 's/\(//g' \
@ -298,12 +291,70 @@ function dependencies {
fi fi
} }
# ** Sandbox
function sandbox {
# Initialize sandbox.
# *** Sandbox arguments
# Check or make user-emacs-directory.
if [[ $sandbox_dir ]]
then
# Directory given as argument: ensure it exists.
[[ -d $sandbox_dir ]] || die "Directory doesn't exist: $sandbox_dir"
else
# Not given: make temp directory, and delete it on exit.
local sandbox_dir=$(mktemp -d) || die "Unable to make sandbox dir."
paths_temp+=("$sandbox_dir")
fi
# Make argument to load init file if it exists.
init_file="$sandbox_dir/init.el"
[[ -r $init_file ]] \
&& local args_load_init_file=(--load "$init_file")
# Set sandbox args. This is a global variable used by the run_emacs function.
args_sandbox=(
--eval "(setq user-emacs-directory (file-truename \"$sandbox_dir\"))"
--eval "(setq user-init-file (file-truename \"$init_file\"))"
"${args_load_init_file[@]}"
)
# Add package-install arguments for dependencies.
if [[ $auto_install ]]
then
local deps=($(dependencies))
debug "Installing dependencies: ${deps[@]}"
for package in "${deps[@]}"
do
args_sandbox_package_install+=(--eval "(package-install '$package)")
done
fi
# *** Install packages into sandbox
if [[ ${args_sandbox_package_install[@]} ]]
then
# Initialize the sandbox (installs packages once rather than for every rule).
debug "Initializing sandbox..."
run_emacs \
--eval "(package-refresh-contents)" \
"${args_sandbox_package_install[@]}" \
|| die "Unable to initialize sandbox."
fi
debug "Sandbox initialized."
}
# ** Utility # ** Utility
function cleanup { function cleanup {
# Remove temporary paths (${temp_paths[@]}). # Remove temporary paths (${paths_temp[@]}).
for path in "${temp_paths[@]}" for path in "${paths_temp[@]}"
do do
if [[ $debug ]] if [[ $debug ]]
then then
@ -398,28 +449,28 @@ function compile {
unset compile # Only compile once. unset compile # Only compile once.
verbose 1 "Compiling..." verbose 1 "Compiling..."
debug "Byte-compile files: ${project_byte_compile_files[@]}" debug "Byte-compile files: ${files_project_byte_compile[@]}"
batch-byte-compile "${project_byte_compile_files[@]}" \ batch-byte-compile "${files_project_byte_compile[@]}" \
&& success "Compiling finished without errors." \ && success "Compiling finished without errors." \
|| error "Compilation failed." || error "Compilation failed."
} }
function batch { function batch {
# Run Emacs with $batch_args and with project source and test files loaded. # Run Emacs with $args_batch and with project source and test files loaded.
verbose 1 "Executing Emacs with arguments: ${batch_args[@]}" verbose 1 "Executing Emacs with arguments: ${args_batch[@]}"
run_emacs \ run_emacs \
$(load-files-args "${project_source_files[@]}" "${project_test_files[@]}") \ $(args-load-files "${files_project_source[@]}" "${files_project_test[@]}") \
"${batch_args[@]}" "${args_batch[@]}"
} }
function interactive { function interactive {
# Run Emacs interactively. Most useful with --sandbox and --auto-install. # Run Emacs interactively. Most useful with --sandbox and --auto-install.
unset batch_arg unset arg_batch
run_emacs \ run_emacs \
$(load-files-args "${project_source_files[@]}" "${project_test_files[@]}") $(args-load-files "${files_project_source[@]}" "${files_project_test[@]}")
batch_arg="--batch" arg_batch="--batch"
} }
function lint { function lint {
@ -434,11 +485,11 @@ function lint-checkdoc {
verbose 1 "Linting checkdoc..." verbose 1 "Linting checkdoc..."
local checkdoc_file="$(elisp-checkdoc-file)" local checkdoc_file="$(elisp-checkdoc-file)"
temp_paths+=("$checkdoc_file") paths_temp+=("$checkdoc_file")
run_emacs \ run_emacs \
--load="$checkdoc_file" \ --load="$checkdoc_file" \
"${project_source_files[@]}" \ "${files_project_source[@]}" \
&& success "Linting checkdoc finished without errors." \ && success "Linting checkdoc finished without errors." \
|| error "Linting checkdoc failed." || error "Linting checkdoc failed."
} }
@ -447,7 +498,7 @@ function lint-compile {
verbose 1 "Linting compilation..." verbose 1 "Linting compilation..."
compile_error_on_warn=true compile_error_on_warn=true
batch-byte-compile "${project_byte_compile_files[@]}" \ batch-byte-compile "${files_project_byte_compile[@]}" \
&& success "Linting compilation finished without errors." \ && success "Linting compilation finished without errors." \
|| error "Linting compilation failed." || error "Linting compilation failed."
unset compile_error_on_warn unset compile_error_on_warn
@ -459,7 +510,7 @@ function lint-package {
run_emacs \ run_emacs \
--load package-lint \ --load package-lint \
--funcall package-lint-batch-and-exit \ --funcall package-lint-batch-and-exit \
"${project_source_files[@]}" \ "${files_project_source[@]}" \
&& success "Linting package finished without errors." \ && success "Linting package finished without errors." \
|| error "Linting package failed." || error "Linting package failed."
} }
@ -478,7 +529,7 @@ function test-buttercup {
verbose 1 "Running Buttercup tests..." verbose 1 "Running Buttercup tests..."
local buttercup_file="$(elisp-buttercup-file)" local buttercup_file="$(elisp-buttercup-file)"
temp_paths+=("$buttercup_file") paths_temp+=("$buttercup_file")
run_emacs \ run_emacs \
--load buttercup \ --load buttercup \
@ -493,10 +544,10 @@ function test-ert {
compile || die compile || die
verbose 1 "Running ERT tests..." verbose 1 "Running ERT tests..."
debug "Test files: ${project_test_files[@]}" debug "Test files: ${files_project_test[@]}"
run_emacs \ run_emacs \
$(load-files-args "${project_test_files[@]}") \ $(args-load-files "${files_project_test[@]}") \
-f ert-run-tests-batch-and-exit \ -f ert-run-tests-batch-and-exit \
&& success "ERT tests finished without errors." \ && success "ERT tests finished without errors." \
|| error "ERT tests failed." || error "ERT tests failed."
@ -505,11 +556,12 @@ function test-ert {
# * Defaults # * Defaults
test_files_regexp='^(tests?|t)/' test_files_regexp='^(tests?|t)/'
emacs_command="emacs"
emacs_command=("emacs")
errors=0 errors=0
verbose=0 verbose=0
compile=true compile=true
batch_arg="--batch" arg_batch="--batch"
# MAYBE: Disable color if not outputting to a terminal. (OTOH, the # MAYBE: Disable color if not outputting to a terminal. (OTOH, the
# colorized output is helpful in CI logs, and I don't know if, # colorized output is helpful in CI logs, and I don't know if,
@ -549,22 +601,36 @@ COLOR_purple='\e[0;35m'
COLOR_cyan='\e[0;36m' COLOR_cyan='\e[0;36m'
COLOR_white='\e[0;37m' COLOR_white='\e[0;37m'
# ** Package system args
args_package_archives=(
--eval "(add-to-list 'package-archives '(\"gnu\" . \"https://elpa.gnu.org/packages/\") t)"
--eval "(add-to-list 'package-archives '(\"melpa\" . \"https://melpa.org/packages/\") t)"
)
args_org_package_archives=(
--eval "(add-to-list 'package-archives '(\"org\" . \"https://orgmode.org/elpa/\") t)"
)
args_package_init=(
--eval "(package-initialize)"
)
elisp_org_package_archive="(add-to-list 'package-archives '(\"org\" . \"https://orgmode.org/elpa/\") t)"
# * Project files # * Project files
# MAYBE: Option to not byte-compile test files. (OTOH, byte-compiling reveals many # MAYBE: Option to not byte-compile test files. (OTOH, byte-compiling reveals many
# errors that would otherwise go unnoticed, so it's worth it to fix the warnings.) # errors that would otherwise go unnoticed, so it's worth it to fix the warnings.)
project_source_files=($(project-source-files)) files_project_source=($(files-project-source))
project_test_files=($(project-test-files)) files_project_test=($(files-project-test))
project_byte_compile_files=("${project_source_files[@]}" "${project_test_files[@]}") files_project_byte_compile=("${files_project_source[@]}" "${files_project_test[@]}")
package_initialize_file="$(elisp-package-initialize-file)"
temp_paths+=("$package_initialize_file")
# * Args # * Args
args=$(getopt -n "$0" \ args=$(getopt -n "$0" \
-o dhi:sS:vf:C \ -o dhi:sS:vf:CO \
-l auto-install,debug,debug-load-path,help,install:,verbose,file:,no-color,no-compile,sandbox,sandbox-dir: \ -l auto-install,debug,debug-load-path,help,install:,verbose,file:,no-color,no-compile,no-org-repo,sandbox,sandbox-dir: \
-- "$@") \ -- "$@") \
|| { usage; exit 1; } || { usage; exit 1; }
eval set -- "$args" eval set -- "$args"
@ -588,7 +654,7 @@ do
;; ;;
-i|--install) -i|--install)
shift shift
sandbox_install_packages_args+=(--install "$1") args_sandbox_package_install+=(--eval "(package-install '$1)")
;; ;;
-s|--sandbox) -s|--sandbox)
sandbox=true sandbox=true
@ -606,6 +672,9 @@ do
project_source_files+=("$1") project_source_files+=("$1")
project_byte_compile_files+=("$1") project_byte_compile_files+=("$1")
;; ;;
-O|--no-org-repo)
unset elisp_org_package_archive
;;
--no-color) --no-color)
unset color unset color
;; ;;
@ -626,60 +695,22 @@ done
debug "ARGS: $args" debug "ARGS: $args"
debug "Remaining args: ${rest[@]}" debug "Remaining args: ${rest[@]}"
# Set package elisp (which depends on --no-org-repo arg).
package_initialize_file="$(elisp-package-initialize-file)"
paths_temp+=("$package_initialize_file")
# * Main # * Main
trap cleanup EXIT INT TERM trap cleanup EXIT INT TERM
if ! [[ ${project_source_files[@]} ]] if ! [[ ${files_project_source[@]} ]]
then then
error "No files specified and not in a git repo." error "No files specified and not in a git repo."
exit 1 exit 1
fi fi
if [[ $sandbox ]] # Initialize sandbox.
then [[ $sandbox ]] && sandbox
# Setup sandbox.
type emacs-sandbox.sh &>/dev/null || die "emacs-sandbox.sh not found."
if ! [[ $sandbox_dir ]]
then
# No sandbox dir specified: make temp dir and remove it on exit.
sandbox_dir=$(mktemp -d) || die "Unable to make temp dir."
temp_paths+=("$sandbox_dir")
fi
sandbox_basic_args=(
-d "$sandbox_dir"
)
[[ $debug ]] && sandbox_basic_args+=(--debug)
if [[ $auto_install ]]
then
# Add dependencies to package install list.
deps=($(dependencies))
debug "Installing dependencies: ${deps[@]}"
for package in "${deps[@]}"
do
sandbox_install_packages_args+=(--install $package)
done
fi
if [[ ${sandbox_install_packages_args[@]} ]]
then
# Initialize the sandbox (installs packages once rather than for every rule).
emacs_command="emacs-sandbox.sh ${sandbox_basic_args[@]} ${sandbox_install_packages_args[@]} -- "
debug "Initializing sandbox..."
run_emacs || die "Unable to initialize sandbox."
fi
# After the sandbox is initialized and packages are installed, set the command
# to prevent the package lists from being refreshed on each invocation.
emacs_command="emacs-sandbox.sh ${sandbox_basic_args[@]} --no-refresh-packages -- "
debug "Sandbox initialized."
fi
# Run rules. # Run rules.
for rule in "${rest[@]}" for rule in "${rest[@]}"
@ -687,13 +718,13 @@ do
if [[ $batch ]] if [[ $batch ]]
then then
debug "Adding batch argument: $rule" debug "Adding batch argument: $rule"
batch_args+=("$rule") args_batch+=("$rule")
elif [[ $rule = batch ]] elif [[ $rule = batch ]]
then then
# Remaining arguments are passed to Emacs. # Remaining arguments are passed to Emacs.
batch=true batch=true
elif type "$rule" 2>/dev/null | grep "$rule is a function" &>/dev/null elif type -t "$rule" 2>/dev/null | grep function &>/dev/null
then then
$rule $rule
elif [[ $rule = test ]] elif [[ $rule = test ]]