Call prerequisites multiple times during execution - makefile

I'm using Make for handling basic tasks in a project, and I have the following signature in my Makefile:
.PHONY: exec lint test
exec:
docker-compose exec service ${CMD}
lint: CMD := npm run lint
lint: exec
test: CMD := npm run test
test: exec
When I run the make lint test command I want it to run both npm run lint and npm run test inside the Docker container.
But as I observed it doesn't happen because make consider the prerequisite to be done after the lint task run and I get
make: Nothing to be done for 'test'.
message upon calling it. This is totally understandable from make's point of view, but it's a side-effect for my usage.
Is there a way to solve this inside the Makefile, without creating a shell script to act as an intermediate agent?

There is no way to get make to build the same target multiple times within a single invocation of make. That's just not how it works: it builds a thing, then expects it to be up to date for the rest of that session.
You can use recursive invocations of make to do what you want:
.PHONY: lint test exec
lint: CMD := npm run lint
test: CMD := npm run test
lint test:
$(MAKE) exec CMD=$(CMD)
exec:
docker-compose exec service ${CMD}

After getting inspiration from #madscientist and digging into the documentation I found what I was looking for: Canned Recipes allow to declare a list of commands to be reused in multiple tasks (recipes).
The following will satisfy the example from the question:
.PHONY: exec
define exec =
docker-compose exec service ${CMD}
endef
lint: CMD := npm run lint
lint:
$(exec)
test: CMD := npm run test
test:
$(exec)
While the solution from #madscientist can also work, I feel this is superior because it doesn't cause recursive make call and a task is not splitted into two pieces (assigning the CMD variable and calling the exec task)

Related

What that this line means in a make file `DOCKER := $(shell command -v docker)`

$(shell command -v docker) What command means? it's being used in a Makefile.
I saw this in a github repository that I'm trying to understand.
It looks like it's setting a variable with a command to test if docker is installed, and stop the build if its not, the problem is this that I don't have a command installed and I tryed to install command in ubuntu but it can't find it, looking on internet how to install this commad command seems realy difficult because of its name, how to install command in linux/ubuntu didn't bring anything useful, also search for this being using on Makefiles trying to get some clue but nothing so far.
Running the build command seems to work becuse it build the image and yes I have docker installed, but still getting that message in the terminal make: command: Command not found
Any idea?
make build output (trucated):
$ make build
make: command: Command not found
make: command: Command not found
docker build -t codelytv/typescript-ddd-skeleton:dev .
Sending build context to Docker daemon 1.023MB
.....
This is the Makefile:
.PHONY = default deps build test start clean start-database
IMAGE_NAME := codelytv/typescript-ddd-skeleton
SERVICE_NAME := app
# Test if the dependencies we need to run this Makefile are installed
DOCKER := $(shell command -v docker)
DOCKER_COMPOSE := $(shell command -v docker-compose)
deps:
ifndef DOCKER
#echo "Docker is not available. Please install docker"
#exit 1
endif
ifndef DOCKER_COMPOSE
#echo "docker-compose is not available. Please install docker-compose"
#exit 1
endif
default: build
# Build image
build:
docker build -t $(IMAGE_NAME):dev .
# Run tests
test: build
docker-compose run --rm $(SERVICE_NAME) bash -c 'npm run build && npm run test'
# Start the application
start: build
docker-compose up $(SERVICE_NAME) && docker-compose down
# Clean containers
clean:
docker-compose down --rmi local --volumes --remove-orphans
# Start mongodb container in background
start_database:
docker-compose up -d mongo
What it means is that the person who wrote this makefile wasn't careful enough to write it in a portable way.
The command command is part of the shell (which is why you won't see it if you look for it in the GNU make manual). Not only that, it's part of the bash shell specifically: it is not a POSIX sh standard command. The bash man page says:
command [-pVv] command [arg ...]
Run command with args suppressing the normal shell function
lookup. Only builtin commands or commands found in the PATH are
executed.
Basically, running command docker ... means that any shell alias or function named docker is ignored, and only the actual docker command is run.
However, GNU make always runs /bin/sh as its shell, including for both recipes and for the $(shell ...) function.
So, if you're on a system (such as Red Hat or CentOS or Fedora GNU/Linux) where the /bin/sh is a link to the bash shell, then the above command will work.
However, if you're on a system (such as Debian or Ubuntu GNU/Linux) where the /bin/sh is a link to a simpler POSIX shell such as dash, then the above command will not work.
In reality, this is not needed because there won't be any shell aliases or functions defined in the shell that make invokes, regardless. However, if the author wants to use bash shell features in their makefiles and allow them to work, they also need to tell make to use bash as its shell, by adding this to their makefile:
SHELL := /bin/bash
(of course this assumes that the user has a /bin/bash on their system, but...)

How to define an environment variable that can be automatically used in multiple npm scripts?

Consider the following npm scripts.
$ npm run
available via `npm run-script`:
make
OUTPUT=dist/main.js bash -c 'elm make src/Main.js --output=$0 $1'
make:dev
npm run make -- '$OUTPUT' --debug
make:prod
npm run make -- '>(npm run uglify -- $OUTPUT)' --optimize
uglify
uglifyjs --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' | uglifyjs --mangle --output=
I'd like to use it as follows:
$ npm run make -- '$OUTPUT' '--debug'
> experiment#0.1.0 experiment /Users/Adit/experiment
> OUTPUT=dist/main.js bash -c 'elm make src/Main.js --output=$0 $1' '$OUTPUT' '--debug'
This would correctly create the debug build of the Elm application. However, this is not what happens. Instead of using single quotes, npm run uses double quotes:
$ npm run make -- '$OUTPUT' '--debug'
> experiment#0.1.0 experiment /Users/Adit/experiment
> OUTPUT=dist/main.js bash -c 'elm make src/Main.js --output=$0 $1' "$OUTPUT" "--debug"
Due to this the output is not what I expect it to be. What's the best way to resolve this issue without writing a custom shell script? I want to use the OUTPUT variable in two different commands. However, I only want to define it in one place.
I solved the problem as follows.
{
"config": {
"input": "src/Main.elm",
"output": "dist/main.js"
},
"scripts": {
"make": "elm make $npm_package_config_input --output $npm_package_config_output",
"make:dev": "npm run make -- --debug",
"make:prod": "npm run make -- --optimize",
"postmake:prod": "uglifyjs $npm_package_config_output --compress 'pure_funcs=\"F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9\",pure_getters,keep_fargs=false,unsafe_comps,unsafe' | uglifyjs --mangle --output=$npm_package_config_output"
}
}
Hence, if you have a configuration variables that you'd like to use in multiple npm scripts, you can add them to the config dictionary of package.json. After that, you can access them as environment variables in the npm scripts via the name $npm_package_config_<name> where <name> is the name of your config variable.
I also used a post script instead of process substitution to uglify the output of the Elm compiler. Doing so was overall less of a hassle than using process substitution via bash -c.
Finally, you can run make, make:dev, or make:prod for different builds. The first one is a regular build. The second one is a development build with the Elm debugging tools. The third one is a regular build which is optimized and minified for production use.

How to get 2 flags from a make command cobra

how do i create a makefile that takes in 2 arguments?
myapp written in go, uses cobra cli. has a command that takes in 2 arguments(flags).
this works
$ go build; myapp mycmd --flag1=myvalue1 --flag2=myvalue2
in my make file i have
//makefile
run:
#echo Building and Running
$(GO) build -i -o myapp .
./myapp start $(ARGS)
so in CLI, when I try
$ make run ARGS=--flag1=arg1--flag2=arg2
or
$ make run ARGS=--flag1=arg1,--flag2=arg2
doesn't read in the flag values
how do i read in the 2 flag values, it only seems to read in 1 flag value.
make run ARGS=--flag1=arg1--flag2=arg2
Has no separator between the flags
make run ARGS=--flag1=arg1,--flag2=arg2
Cobra doesn't use ',' as a default flag separator.
Try:
make run ARGS='--flag1=arg1 --flag2=arg2'
Tried against a cobra CLI of my own, works perfectly.

How to check if npm script exists?

I am creating a bash script which runs through each of my projects and runs npm run test if the test script exists.
I know that if I get into a project and run npm run it will give me the list of available scripts as follows:
Lifecycle scripts included in www:
start
node server.js
test
mocha --require #babel/register --require dotenv/config --watch-extensions js **/*.test.js
available via `npm run-script`:
dev
node -r dotenv/config server.js
dev:watch
nodemon -r dotenv/config server.js
build
next build
However, I have no idea how to grab that information, see if test is available and then run it.
Here is my current code:
#!/bin/bash
ROOT_PATH="$(cd "$(dirname "$0")" && pwd)"
BASE_PATH="${ROOT_PATH}/../.."
while read MYAPP; do # reads from a list of projects
PROJECT="${MYAPP}"
FOLDER="${BASE_PATH}/${PROJECT}"
cd "$FOLDER"
if [ check here if the command exists ]; then
npm run test
echo ""
fi
done < "${ROOT_PATH}/../assets/apps-manifest"
EDIT:
As mentioned by Marie and James if you only want to run the command if it exists, npm has an option for that:
npm run test --if-present
This way you can have a generic script that work with multiple projects (that may or may not have an specific task) without having the risk of receiving an error.
Source: https://docs.npmjs.com/cli/run-script
EDIT
You could do a grep to check for the word test:
npm run | grep -q test
this return true if the result in npm run contains the word test
In your script it would look like this:
#!/bin/bash
ROOT_PATH="$(cd "$(dirname "$0")" && pwd)"
BASE_PATH="${ROOT_PATH}/../.."
while read MYAPP; do # reads from a list of projects
PROJECT="${MYAPP}"
FOLDER="${BASE_PATH}/${PROJECT}"
cd "$FOLDER"
if npm run | grep -q test; then
npm run test
echo ""
fi
done < "${ROOT_PATH}/../assets/apps-manifest"
It just would be a problem if the word test is in there with another meaning
Hope it helps
The right solution is using the if-present flag:
npm run test --if-present
--if-present doesn't allow you to "check if a npm script exists", but runs the script if it exists. If you have fallback logic this won't suffice. In my case, I want to run npm run test:ci if it exists and if not check for and run, npm run test. Using --if-present would run the test:ci AND test scripts if both exists. By checking if one exists first, we can decide which to run.
Because I have both "test" and "test:ci" scripts, the npm run | grep approach wasn't sufficient. As much as I'd like to do this with strictly npm, I have jq in my environments so I decided to go that route to have precision.
To check for a script named "test:ci":
if [[ $(jq '.scripts["test:ci"]' < package.json;) != null ]]; then
// script exists
fi

Go test does not find package tests in Makefile

I have following Makefile:
SHELL := /bin/bash
boot:
#go run main.go
test:
#go test ./...
test-conf:
#go test --verbose conf
test-httpd:
#go test --verbose ./httpd
.PHONY: test test-conf test-httpd
Strangely enough make test works without problems however make test-conf or make test-httpd will both result in "github.com/bodokaiser/foobar [no test files]".
When I run go test ./conf from the working dir it works - shouldn't the makefile work too then?
What do I need to do to get go test working with packages in a Makefile?
PS: I would like to avoid using $(pwd) or something in front of all paths if possible...
test-httpd and test-conf don't work, as you can't use --verbose with go test, only -v.

Resources