From b1d9fcf27454d6ddeabab3f660008f4e069404cc Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Fri, 3 Jan 2020 03:51:50 -0600 Subject: [PATCH] Meta: Update Makefile, makem.sh --- Makefile | 19 +++++- makem.sh | 185 ++++++++++++++++++++++++++++++------------------------- 2 files changed, 116 insertions(+), 88 deletions(-) diff --git a/Makefile b/Makefile index 51969fe..5000738 100644 --- a/Makefile +++ b/Makefile @@ -2,14 +2,27 @@ # For consistency, we use only var=val options, not hyphen-prefixed options. -ifdef autoinstall - AUTOINSTALL = "--auto-install" +# NOTE: I don't like duplicating the arguments here and in makem.sh, +# but I haven't been able to find a way to pass arguments which +# conflict with Make's own arguments through Make to the script. +# Using -- doesn't seem to do it. + +ifdef auto-install + AUTO_INSTALL = "--auto-install" endif ifdef sandbox SANDBOX = "--sandbox" endif +ifdef sandbox-dir + SANDBOX_DIR = "--sandbox-dir" "$(sandbox-dir)" +endif + +ifdef debug + DEBUG = "--debug" +endif + # ** Verbosity # Since the "-v" in "make -v" gets intercepted by Make itself, we have @@ -29,4 +42,4 @@ endif # directory by that name exists, which can confuse Make. %: - @./makem.sh $(VERBOSE) $(SANDBOX) $(AUTOINSTALL) $(@) + @./makem.sh $(DEBUG) $(VERBOSE) $(SANDBOX) $(SANDBOX_DIR) $(AUTO_INSTALL) $(@) diff --git a/makem.sh b/makem.sh index ab8baa9..d63285c 100755 --- a/makem.sh +++ b/makem.sh @@ -44,63 +44,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# * Safety +# * Functions -# NOTE: These are disabled by default in this template but should be -# enabled when feasible. Documentation is from the Bash man page. - -# ** errexit - -# Exit immediately if a pipeline (which may consist of a single simple -# command), a list, or a compound command (see SHELL GRAMMAR above), -# exits with a non-zero status. The shell does not exit if the -# command that fails is part of the command list immediately following -# a while or until keyword, part of the test following the if or elif -# reserved words, part of any command executed in a && or || list -# except the command follow‐ ing the final && or ||, any command in a -# pipeline but the last, or if the command's return value is being -# inverted with !. If a compound command other than a subshell -# returns a non-zero status because a command failed while -e was -# being ignored, the shell does not exit. A trap on ERR, if set, is -# executed before the shell exits. This option applies to the shell -# environment and each subshell environment separately (see COMMAND -# EXECUTION ENVIRONMENT above), and may cause subshells to exit before -# executing all the commands in the subshell. - -# If a compound command or shell function executes in a context where -# -e is being ignored, none of the commands executed within the -# compound command or function body will be affected by the -e -# setting, even if -e is set and a command returns a failure status. -# If a compound command or shell function sets -e while executing in a -# context where -e is ignored, that setting will not have any effect -# until the compound command or the command containing the function -# call completes. - -# set -o errexit - -# ** nounset - -# Treat unset variables and parameters other than the special -# parameters "@" and "*" as an error when performing parameter -# expansion. If expansion is attempted on an unset variable or -# parameter, the shell prints an error message, and, if not -# interactive, exits with a non-zero status. - -# NOTE: When this is not enabled, individual variables can be required -# to be set by using "${var:?}" parameter expansion syntax. - -# set -o nounset - -# ** pipefail - -# If set, the return value of a pipeline is the value of the last -# (rightmost) command to exit with a non-zero status, or zero if all -# commands in the pipeline exit successfully. This option is disabled -# by default. - -# set -o pipefail - -# * Elisp +# ** Elisp # These functions return a path to an elisp file which can be loaded # by Emacs on the command line with -l or --load. @@ -164,8 +110,6 @@ EOF echo $file } -# * Functions - # ** Emacs function run_emacs { @@ -219,17 +163,27 @@ function project-elisp-files { function project-source-files { # Echo list of Elisp files that are not tests. - project-elisp-files | egrep -v '^tests?/test-?' + project-elisp-files | egrep -v "$test_files_regexp" | feature-files } function project-test-files { # Echo list of Elisp test files. - project-elisp-files | egrep '^tests?/test-?' + project-elisp-files | egrep "$test_files_regexp" } function exclude-files { # Filter out paths (STDIN) which should be excluded by default. - egrep -v "(/\.cask/|-autoloads.el)" + egrep -v "(/\.cask/|-autoloads.el|.dir-locals)" +} + +function feature-files { + # Read paths on STDIN and echo ones that (provide 'a-feature). + while read path + do + debug "PATH: $path" + egrep "^\\(provide '" "$path" &>/dev/null \ + && echo "$path" + done } function load-files-args { @@ -248,30 +202,46 @@ function files_args { done } +function test-files-p { + # Return 0 if $project_test_files is non-empty. + [[ "${project_test_files[@]}" ]] +} + function buttercup-tests-p { # Return 0 if Buttercup tests are found. + test-files-p || die "No tests found." debug "Checking for Buttercup tests..." - grep "(require 'buttercup)" $(project-test-files) &>/dev/null + grep "(require 'buttercup)" "${project_test_files[@]}" &>/dev/null } function ert-tests-p { # Return 0 if ERT tests are found. + test-files-p || die "No tests found." debug "Checking for ERT tests..." # We check for this rather than "(require 'ert)", because ERT may # already be loaded in Emacs and might not be loaded with # "require" in a test file. - grep "(ert-deftest" $(project-test-files) &>/dev/null + grep "(ert-deftest" "${project_test_files[@]}" &>/dev/null } function dependencies { # Echo list of package dependencies. + + # Search package headers. egrep '^;; Package-Requires: ' $(project-source-files) $(project-test-files) \ | egrep -o '\([^([:space:]][^)]*\)' \ | egrep -o '^[^[:space:])]+' \ | sed -r 's/\(//g' \ | egrep -v '^emacs$' # Ignore Emacs version requirement. + + # Search Cask file. + if [[ -r Cask ]] + then + egrep '\(depends-on "[^"]+"' Cask \ + | sed -r -e 's/\(depends-on "([^"]+)".*/\1/g' + fi } # ** Utility @@ -390,7 +360,10 @@ Sandbox options: These require emacs-sandbox.sh to be on your PATH. Find it at . - --sandbox Run Emacs with emacs-sandbox.sh. + -s, --sandbox Run Emacs with emacs-sandbox.sh in a temporary + directory (removing directory on exit). + -S, --sandbox-dir DIR Use DIR for the sandbox directory (leaving it + on exit). Implies -s. --auto-install Automatically install package dependencies. -i, --install PACKAGE Install PACKAGE before running rules. @@ -398,7 +371,7 @@ Source files are automatically discovered from git, or may be specified with options. Package dependencies are discovered from "Package-Requires" headers in -source files. +source files and from a Cask file. EOF } @@ -507,24 +480,38 @@ function test-ert { # * Defaults +test_files_regexp='^(tests?|t)/' emacs_command="emacs" - -# TODO: Disable color if not outputting to a terminal. -color=true errors=0 verbose=0 - compile=true + +# MAYBE: Disable color if not outputting to a terminal. (OTOH, the +# colorized output is helpful in CI logs, and I don't know if, +# e.g. GitHub Actions logging pretends to be a terminal.) +color=true + +# TODO: Using the current directory (i.e. a package's repo root directory) in +# load-path can cause weird errors in case of--you guessed it--stale .ELC files, +# the zombie problem that just won't die. It's incredible how many different ways +# this problem presents itself. In this latest example, an old .ELC file, for a +# .EL file that had since been renamed, was present on my local system, which meant +# that an example .EL file that hadn't been updated was able to "require" that .ELC +# file's feature without error. But on another system (in this case, trying to +# setup CI using GitHub Actions), the old .ELC was not present, so the example .EL +# file was not able to load the feature, which caused a byte-compilation error. + +# In this case, I will prevent such example files from being compiled. But in +# general, this can cause weird problems that are tedious to debug. I guess +# the best way to fix it would be to actually install the repo's code as a +# package into the sandbox, but doing that would require additional tooling, +# pulling in something like Quelpa or package-build--and if the default recipe +# weren't being used, the actual recipe would have to be fetched off MELPA or +# something, which seems like getting too smart for our own good. + +# TODO: Emit a warning if .ELC files that don't match any .EL files are detected. load_path="." -# TODO: Option to not byte-compile test files. -project_byte_compile_files=($(project-elisp-files)) -project_source_files=($(project-source-files)) -project_test_files=($(project-test-files)) - -package_initialize_file="$(elisp-package-initialize-file)" -temp_paths+=("$package_initialize_file") - # ** Colors COLOR_off='\e[0m' @@ -537,9 +524,24 @@ COLOR_purple='\e[0;35m' COLOR_cyan='\e[0;36m' COLOR_white='\e[0;37m' +# * Project files + +# 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.) +project_source_files=($(project-source-files)) +project_test_files=($(project-test-files)) +project_byte_compile_files=("${project_source_files[@]}" "${project_test_files[@]}") + +package_initialize_file="$(elisp-package-initialize-file)" +temp_paths+=("$package_initialize_file") + # * Args -args=$(getopt -n "$0" -o dhi:svf:C -l auto-install,debug,debug-load-path,help,install:,verbose,file:,no-color,no-compile,sandbox -- "$@") || { usage; exit 1; } +args=$(getopt -n "$0" \ + -o dhi:sS:vf:C \ + -l auto-install,debug,debug-load-path,help,install:,verbose,file:,no-color,no-compile,sandbox,sandbox-dir: \ + -- "$@") \ + || { usage; exit 1; } eval set -- "$args" while true @@ -566,6 +568,11 @@ do -s|--sandbox) sandbox=true ;; + -S|--sandbox-dir) + shift + sandbox=true + sandbox_dir="$1" + ;; -v|--verbose) ((verbose++)) ;; @@ -609,16 +616,21 @@ then # Setup sandbox. type emacs-sandbox.sh &>/dev/null || die "emacs-sandbox.sh not found." - config_dir=$(mktemp -d) || die "Unable to make temp dir." - temp_paths+=("$config_dir") + 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 "$config_dir" + -d "$sandbox_dir" ) [[ $debug ]] && sandbox_basic_args+=(--debug) if [[ $auto_install ]] then + # Add dependencies to package install list. deps=($(dependencies)) debug "Installing dependencies: ${deps[@]}" @@ -628,11 +640,14 @@ then done fi - # 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..." + 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." + 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.