#/ # @license Apache-2.0 # # Copyright (c) 2021 The Stdlib Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #/ # USER VARIABLES # ifndef VERBOSE QUIET := @ else QUIET := endif # Indicate whether to "fast" fail when linting, running tests, etc: ifndef FAST_FAIL FAIL_FAST := true else ifeq ($(FAST_FAIL), 0) FAIL_FAST := false else FAIL_FAST := true endif endif # Define the `NODE_PATH` environment variable: NODE_PATH ?= # Define the `NODE_ENV` environment variable: NODE_ENV ?= # INTERNAL VARIABLES # # Instruct make to warn us when we use an undefined variable (e.g., misspellings). MAKEFLAGS += --warn-undefined-variables # Define the default target: .DEFAULT_GOAL := all # Define the `SHELL` variable to avoid issues on systems where the variable may be inherited from the environment. # # ## Notes # # - We use `bash` so that we can use `pipefail`. # # # [1]: https://www.gnu.org/prep/standards/html_node/Makefile-Basics.html#Makefile-Basics # [2]: http://clarkgrubb.com/makefile-style-guide SHELL := bash # Define shell flags. # # ## Notes # # - `.SHELLFLAGS` was introduced in GNU Make 3.82 and has no effect on the version of GNU Make installed on Mac OS X, which is 3.81. # - The `-e` flag causes `bash` to exit immediately if a `bash` executed command fails. # - The `-u` flag causes `bash` to exit with an error message if a variable is accessed without being defined. # - The `pipefail` option specifies that, if any of the commands in a pipeline fail, the entire pipeline fails. Otherwise the return value of a pipeline is the return value of the last command. # - The `-c` flag is in the default value of `.SHELLFLAGS`, which must be preserved, as this is how `make` passes the script to be executed to `bash`. # .SHELLFLAGS := -eu -o pipefail -c # Remove targets if its recipe fails. # # ## Notes # # - Mentioning this target anywhere in a Makefile prevents a user from re-running make and using an incomplete or invalid target. # - When debugging, it may be necessary to comment this line out so the incomplete or invalid target can be inspected. # # [1]: https://www.gnu.org/software/make/manual/html_node/Special-Targets.html .DELETE_ON_ERROR: # Remove all the default suffixes, preferring to define all rules explicitly. # # [1]: https://www.gnu.org/software/make/manual/html_node/Suffix-Rules.html#Suffix-Rules # [2]: https://www.gnu.org/software/make/manual/html_node/Suffix-Rules.html#Suffix-Rules .SUFFIXES: # Determine the OS ([1][1], [2][2]). # # [1]: https://en.wikipedia.org/wiki/Uname#Examples # [2]: http://stackoverflow.com/a/27776822/2225624 OS ?= $(shell uname) ifneq (, $(findstring MINGW,$(OS))) OS := WINNT else ifneq (, $(findstring MSYS,$(OS))) OS := WINNT else ifneq (, $(findstring CYGWIN,$(OS))) OS := WINNT else ifneq (, $(findstring Windows_NT,$(OS))) OS := WINNT endif endif endif endif # Determine the filename: this_file := $(lastword $(MAKEFILE_LIST)) # Determine the absolute path of the Makefile (see http://blog.jgc.org/2007/01/what-makefile-am-i-in.html): this_dir := $(dir $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))) # Remove the trailing slash: this_dir := $(patsubst %/,%,$(this_dir)) # Determine root directory: ROOT_DIR = $(this_dir) # Define the root build directory: BUILD_DIR ?= $(ROOT_DIR)/build # Define the root directory for storing distributable files: DIST_DIR ?= $(ROOT_DIR)/dist # Define the root directory for storing temporary files: TMP_DIR ?= $(ROOT_DIR)/tmp # Define the directories for writing reports, including code coverage: REPORTS_DIR ?= $(ROOT_DIR)/reports COVERAGE_DIR ?= $(REPORTS_DIR)/coverage # Define the top-level directory containing node module dependencies: NODE_MODULES ?= $(ROOT_DIR)/node_modules # Define the top-level directory containing node module executables: BIN_DIR ?= $(NODE_MODULES)/.bin # Define the path to the root `package.json`: ROOT_PACKAGE_JSON ?= $(ROOT_DIR)/package.json # Define the folder name convention for source files requiring compilation: SRC_FOLDER ?= src # Define the folder name convention for documentation files: DOCUMENTATION_FOLDER ?= docs # Define the folder name convention for configuration files: CONFIG_FOLDER ?= etc # Define the folder name convention for benchmark files: BENCHMARKS_FOLDER ?= benchmark # Define the folder name convention for benchmark fixtures: BENCHMARKS_FIXTURES_FOLDER ?= $(BENCHMARKS_FOLDER)/fixtures # Define the folder name convention for examples files: EXAMPLES_FOLDER ?= examples # Define the folder name convention for examples fixtures: EXAMPLES_FIXTURES_FOLDER ?= $(EXAMPLES_FOLDER)/fixtures # Define the folder name convention for test files: TESTS_FOLDER ?= test # Define the folder name convention for test fixtures: TESTS_FIXTURES_FOLDER ?= $(TESTS_FOLDER)/fixtures # Define a filepath pattern for benchmark files: BENCHMARKS_FILTER ?= .*/.* # Define a filepath pattern for example files: EXAMPLES_FILTER ?= .*/.* # Define a filepath pattern for test files: TESTS_FILTER ?= .*/.* # Define a filename pattern for benchmark files: BENCHMARKS_PATTERN ?= benchmark*.js # Define a filename pattern for example files: EXAMPLES_PATTERN ?= *.js # Define a filename pattern for test files: TESTS_PATTERN ?= test*.js # Define Node environments: ifdef NODE_ENV NODE_ENV_BENCHMARK := $(NODE_ENV) NODE_ENV_EXAMPLES := $(NODE_ENV) NODE_ENV_TEST := $(NODE_ENV) else NODE_ENV ?= NODE_ENV_BENCHMARK ?= benchmark NODE_ENV_EXAMPLES ?= examples NODE_ENV_TEST ?= test endif # Define whether delete operations should be safe (i.e., deleted items are sent to trash, rather than permanently deleted): SAFE_DELETE ?= false # Define the delete command: ifeq ($(SAFE_DELETE), true) # FIXME: -rm -rf DELETE := -rm DELETE_FLAGS := -rf else DELETE ?= -rm DELETE_FLAGS ?= -rf endif # Determine the `open` command: ifeq ($(OS), Darwin) OPEN ?= open else OPEN ?= xdg-open endif # TODO: add Windows command # Define the command for `node`: NODE ?= node # Define the command for `npm`: NPM ?= npm # Define the path to a JavaScript test runner. # # ## Notes # # - We reference the `bin` file directly in order to support using `istanbul` for code coverage on Windows (https://github.com/gotwarlost/istanbul#usage-on-windows) JAVASCRIPT_TEST ?= $(NODE_MODULES)/tape/bin/tape # Define any command-line options to use when invoking the test runner: JAVASCRIPT_TEST_FLAGS ?= # Define the path to the executable for parsing TAP output: TAP_REPORTER ?= $(BIN_DIR)/tap-min # Define the path to the Istanbul executable: ISTANBUL ?= $(BIN_DIR)/istanbul # Define which files and directories to exclude from coverage instrumentation: ISTANBUL_EXCLUDES_FLAGS ?= \ --no-default-excludes \ -x 'node_modules/**' \ -x 'reports/**' \ -x 'tmp/**' \ -x 'deps/**' \ -x 'dist/**' \ -x "**/$(SRC_FOLDER)/**" \ -x "**/$(TESTS_FOLDER)/**" \ -x "**/$(EXAMPLES_FOLDER)/**" \ -x "**/$(BENCHMARKS_FOLDER)/**" \ -x "**/$(CONFIG_FOLDER)/**" \ -x "**/$(DOCUMENTATION_FOLDER)/**" # Define the command to generate test coverage: ISTANBUL_COVER ?= $(ISTANBUL) cover # Define the type of report Istanbul should produce: ISTANBUL_COVER_REPORT_FORMAT ?= lcov # Define the command-line options to be used when generating code coverage: ISTANBUL_COVER_FLAGS ?= \ $(ISTANBUL_EXCLUDES_FLAGS) \ --dir $(COVERAGE_DIR) \ --report $(ISTANBUL_COVER_REPORT_FORMAT) # On Mac OSX, in order to use `|` and other regular expression operators, we need to use enhanced regular expression syntax (-E); see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man7/re_format.7.html#//apple_ref/doc/man/7/re_format. ifeq ($(OS), Darwin) find_kernel_prefix := -E else find_kernel_prefix := endif # Common exclude flags that most recipes for finding package files should use (Note: order does matter to some degree): FIND_COMMON_EXCLUDE_FLAGS ?= \ '!' -path "$(ROOT_DIR)/.*" \ '!' -path "$(NODE_MODULES)/*" \ '!' -path "$(BUILD_DIR)/*" \ '!' -path "$(REPORTS_DIR)/*" \ # Define exclusion flags to use when searching for benchmark files: FIND_BENCHMARKS_EXCLUDE_FLAGS ?= \ $(FIND_COMMON_EXCLUDE_FLAGS) \ '!' -path "$(ROOT_DIR)/**/$(BENCHMARKS_FIXTURES_FOLDER)/*" # Define flags for finding benchmark files: FIND_BENCHMARKS_FLAGS ?= \ -type f \ -name "$(BENCHMARKS_PATTERN)" \ \( -path "$(ROOT_DIR)/$(BENCHMARKS_FOLDER)/**" -o -path "$(ROOT_DIR)/**/$(BENCHMARKS_FOLDER)/**" \) \ -regex "$(BENCHMARKS_FILTER)" \ $(FIND_BENCHMARKS_EXCLUDE_FLAGS) ifneq ($(OS), Darwin) FIND_BENCHMARKS_FLAGS := -regextype posix-extended $(FIND_BENCHMARKS_FLAGS) endif # Define a command to list benchmark files: FIND_BENCHMARKS_CMD ?= find $(find_kernel_prefix) $(ROOT_DIR) $(FIND_BENCHMARKS_FLAGS) # Define exclusion flags to use when searching for examples files: FIND_EXAMPLES_EXCLUDE_FLAGS ?= \ $(FIND_COMMON_EXCLUDE_FLAGS) \ '!' -path "$(ROOT_DIR)/**/$(EXAMPLES_FIXTURES_FOLDER)/*" # Define flags for finding examples files: FIND_EXAMPLES_FLAGS ?= \ -type f \ -name "$(EXAMPLES_PATTERN)" \ \( -path "$(ROOT_DIR)/$(EXAMPLES_FOLDER)/**" -o -path "$(ROOT_DIR)/**/$(EXAMPLES_FOLDER)/**" \) \ -regex "$(EXAMPLES_FILTER)" \ $(FIND_EXAMPLES_EXCLUDE_FLAGS) ifneq ($(OS), Darwin) FIND_EXAMPLES_FLAGS := -regextype posix-extended $(FIND_EXAMPLES_FLAGS) endif # Define a command to list example files: FIND_EXAMPLES_CMD ?= find $(find_kernel_prefix) $(ROOT_DIR) $(FIND_EXAMPLES_FLAGS) # Define exclusion flags to use when searching for test files: FIND_TESTS_EXCLUDE_FLAGS ?= \ $(FIND_COMMON_EXCLUDE_FLAGS) \ '!' -path "$(ROOT_DIR)/**/$(TESTS_FIXTURES_FOLDER)/*" # Define flags for finding test files: FIND_TESTS_FLAGS ?= \ -type f \ -name "$(TESTS_PATTERN)" \ -regex "$(TESTS_FILTER)" \ $(FIND_TESTS_EXCLUDE_FLAGS) ifneq ($(OS), Darwin) FIND_TESTS_FLAGS := -regextype posix-extended $(FIND_TESTS_FLAGS) endif # Define a command to list test files: FIND_TESTS_CMD ?= find $(find_kernel_prefix) $(ROOT_DIR) $(FIND_TESTS_FLAGS) # RULES # #/ # Default target. # # @example # make # # @example # make all #/ all: help .PHONY: all #/ # Prints a `Makefile` help message. # # @example # make help #/ help: $(QUIET) echo 'Read the Makefile to see the list of available commands.' $(QUIET) echo '' .PHONY: help #/ # Prints the runtime value of a `Makefile` variable. # # ## Notes # # - The rule uses the following format: # # ```bash # $ make inspect. # ``` # # @example # make inspect.ROOT_DIR # # @example # make inspect.CC #/ inspect.%: $(QUIET) echo '$*=$($*)' #/ # Runs the project's install sequence. # # @example # make install #/ install: $(NPM) install .PHONY: install #/ # Removes node module dependencies. # # @example # make clean-node #/ clean-node: $(QUIET) $(DELETE) $(DELETE_FLAGS) $(NODE_MODULES) #/ # Runs the project's cleanup sequence. # # @example # make clean #/ clean: clean-node clean-cov $(QUIET) $(DELETE) $(DELETE_FLAGS) $(BUILD_DIR) $(QUIET) $(DELETE) $(DELETE_FLAGS) $(REPORTS_DIR) .PHONY: clean #/ # Runs JavaScript benchmarks consecutively. # # ## Notes # # - The recipe assumes that benchmark files can be run via Node.js. # - This rule is useful when wanting to glob for JavaScript benchmark files (e.g., run all JavaScript benchmarks for a particular package). # # # @param {string} [BENCHMARKS_FILTER] - file path pattern (e.g., `.*/utils/group-by/.*`) # # @example # make benchmark # # @example # make benchmark BENCHMARKS_FILTER=".*/utils/group-by/.*" #/ benchmark: $(NODE_MODULES) $(QUIET) $(FIND_BENCHMARKS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \ echo ""; \ echo "Running benchmark: $$file"; \ NODE_ENV="$(NODE_ENV_BENCHMARK)" \ NODE_PATH="$(NODE_PATH)" \ $(NODE) $$file || exit 1; \ done .PHONY: benchmark #/ # Runs JavaScript examples consecutively. # # ## Notes # # - This rule is useful when wanting to glob for JavaScript examples files (e.g., run all JavaScript examples for a particular package). # - This rule **assumes** that examples files can be run using Node.js. # # # @param {string} [EXAMPLES_FILTER] - file path pattern (e.g., `.*/math/base/special/abs/.*`) # # @example # make examples # # @example # make examples EXAMPLES_FILTER=".*/strided/common/.*" #/ examples: $(NODE_MODULES) $(QUIET) $(FIND_EXAMPLES_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \ echo ""; \ echo "Running example: $$file"; \ NODE_ENV="$(NODE_ENV_EXAMPLES)" \ NODE_PATH="$(NODE_PATH)" \ $(NODE) $$file || exit 1; \ done .PHONY: examples #/ # Runs JavaScript tests consecutively. # # ## Notes # # - This rule is useful when wanting to glob for JavaScript test files (e.g., run all JavaScript tests for a particular package). # - This rule **assumes** that test files can be run using Node.js. # # # @param {string} [TEST_FILTER] - file path pattern (e.g., `.*/math/base/special/abs/.*`) # # @example # make test # # @example # make test TESTS_FILTER=".*/strided/common/.*" #/ test: $(NODE_MODULES) $(QUIET) $(FIND_TESTS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r test; do \ echo ''; \ echo "Running test: $$test"; \ NODE_ENV="$(NODE_ENV_TEST)" \ NODE_PATH="$(NODE_PATH)" \ $(JAVASCRIPT_TEST) \ $(JAVASCRIPT_TEST_FLAGS) \ $$test \ | $(TAP_REPORTER) || exit 1; \ done .PHONY: test #/ # Runs unit tests and generate a test coverage report. # # @example # make test-cov #/ test-cov: clean-cov $(QUIET) NODE_ENV="$(NODE_ENV_TEST)" \ NODE_PATH="$(NODE_PATH)" \ $(ISTANBUL_COVER) $(ISTANBUL_COVER_FLAGS) $(JAVASCRIPT_TEST) -- $$( $(FIND_TESTS_CMD) ) .PHONY: test-cov #/ # Removes a test coverage directory. # # @example # make clean-cov #/ clean-cov: $(QUIET) $(DELETE) $(DELETE_FLAGS) $(COVERAGE_DIR)