System: Win10 with MinGW make
I am trying to store PowerShell output in a variable inside of the recipe, and unfortunately I've exhausted all options.
Variable that stores powershell command:
# Get newest package in the directory
LST_PKG := powershell "gci -Path . -File *.nipkg -Recurse -Name | sort creationtime | Select -First 1"
The output is builds\packages\srl-lut_1.2.0.32_windows_all.nipkg
The actual recipe that's not working (PKG_REL_PATH end up empty):
.PHONY: publish
publish: clean build package
ifeq ($(DEBUG), 1)
#echo "Publishing a DEBUG package..."
#1. Copy package to feed repo
$(eval PKG_REL_PATH = $(LST_PKG))
cp $(ROOT_DIR)$(PKG_REL_PATH) $(TEST_FEED_REPO)
#2. Publish the package
else
#echo "Publishing a RELEASE package..."
endif
Partial output indicative of the issue:
"Publishing a DEBUG package..."
cp C:\Users\Public\Projects\SRL\lut\src\powershell "gci -Path . -File *.nipkg -Recurse -Name | sort creationtime | Select -First 1" \\raven\#Raven\SRLFeeds\SRL\Repo
'cp' is not recognized as an internal or external command,
operable program or batch file.
make: *** [makefile:78: publish] Error 1
It looks like LST_PKG does not expans and evaluate and just assigned as string to PKG_REL_PATH:
cp C:\Users\Public\Projects\SRL\lut\src*powershell "gci -Path . -File .nipkg -Recurse -Name | sort creationtime | Select -First 1" \raven\#Raven\SRLFeeds\SRL\Repo
Using make functions in recipes, like eval, is usually wrong.
Your eval simply creates a copy of make variable LST_PKG named PKG_REL_PATH, it does not execute the powershell command.
The error message you get seems to say that there is no cp command in the shell used by make to execute the recipes. This is quite strange. Do you know what shell make uses?
Assuming you find a way to solve the 3rd issue, you could simply try:
# Get newest package in the directory
LST_PKG := powershell "gci -Path . -File *.nipkg -Recurse -Name | sort creationtime | Select -First 1"
PKG_REL_PATH := $(shell $(LST_PKG))
The shell make function will pass the powershell command to the shell. The shell will execute it and the output will be assigned to the PKG_REL_PATH make variable. This will happen when make parses the Makefile. If you absolutely want this command to be executed at the same time as the publish recipe, what you need is more likely a shell variable:
publish: clean build package
ifeq ($(DEBUG), 1)
#echo "Publishing a DEBUG package..."
PKG_REL_PATH=$$($(LST_PKG)); \
cp $(ROOT_DIR)"$$PKG_REL_PATH" $(TEST_FEED_REPO)
else
#echo "Publishing a RELEASE package..."
endif
Note the $$ to escape the first expansion by make and pass $(powershell ...) or $PKG_REL_PATH to the shell. Without this the first expansion by make would eat the $ and what would be passed to the shell would not be what you want.
Note also the chaining of the shell commands with a semi-colon and the \ line continuation. They are absolutely needed to guarantee that the two commands are executed in the same shell. By default recipe lines are executed in different shell invocations (unless you use the GNU make .ONESHELL feature) and shell variables defined on one line are undefined in the next. So without the chaining and the line continuation the PKG_REL_PATH shell variable would not be defined in the cp... line.
Found a solution. First, I define a CMD-type copy command to be used instead on cp (at the beginning of the makefile, outside of all recipes:
# Copy a file from targed to destination
_CP = cmd /C copy
The correct expansion was archived by doing the following:
IS
publish:
ifeq ($(DEBUG), 1)
#echo "Publishing a DEBUG package..."
#1. Copy package to feed repo
$(_CP) "$(ROOT_DIR)$(shell $(_LST_PKG))" "$(TEST_FEED_REPO)"
#2. Publish the package
else
#echo "Publishing a RELEASE package..."
endif
WAS
publish: clean build package
ifeq ($(DEBUG), 1)
#echo "Publishing a DEBUG package..."
#1. Copy package to feed repo
$(eval PKG_REL_PATH = $(LST_PKG))
cp $(ROOT_DIR)$(PKG_REL_PATH) $(TEST_FEED_REPO)
#2. Publish the package
else
#echo "Publishing a RELEASE package..."
endif
In summary, I used $(shell ...) inline evaluation to bypass all variable assignment which was giving the issue in the first place.
Related
This question already has answers here:
Escaping in makefile
(2 answers)
Closed 1 year ago.
I have the following code:
all: ./source/Listener.java ./source/ServerThread.java ./source/TestClient.java
javac -d target $(find ./source/* | grep .java)
When I run Make, I get this output
javac -d target
error: no source files
Makefile:2: recipe for target 'all' failed
make: *** [all] Error 2
When I run the javac command in bash, it compiles fine. Furthermore, when I run the 'find' section, i get a list of files like I wanted. Additionally, the file paths in line 1 are all accurate.
Any tips?
(I have to use the find function because there are a lot of files in use, and it increases over time. I trimmed it down to 3, but the bug is still there anyway)
As RenaudPacalet said, I had to put an extra $ infront of the second line.
If you want to execute shell commands in a Makefile, use this syntax : $(shell ...)
Warnings :
The default shell is sh (use SHELL macro definion to change it)
Example : SHELL=/bin/bash
The dollar ($) symbol is special into Makefile and into bash script (if you want use it in shell script espace it with double dollar : $$).
Example: $(shell X=a_value; echo $$a_value)
If you want the current process of sub-shell : $(shell echo $$$$) ... ugly, no ?
Do you want really call shell? It's not portable. Assume it.
If you search your sources files or what ever, use wildcard internal make function.
Examples:
all: x.class y.class z.class
x.class: a.java dir_b/b.java dir_c/c.java
#echo "$$^=$^="
#echo "not portable command ..." $(shell find . -name "*.java")
# Better with deps in target definition
SRCS=$(shell find . -name "*.java")
y.class: $(SRCS)
#echo x_my_command $^
# Really better (portable)
SRCS=$(wildcard */*.java *.java)
z.class: $(SRCS)
#echo y_my_command $^
Output:
$^=a.java dir_b/b.java dir_c/c.java=
not portable command ... ./dir_b/b.java ./dir_c/c.java ./a.java
x_my_command dir_b/b.java dir_c/c.java a.java
y_my_command dir_b/b.java dir_c/c.java a.java
I am trying to build a generic task that will execute other task. What I need it to do is to loop against directories and use each dir name executing other task for it.
This is what I have:
# GENERIC TASKS
all-%:
for BIN in `ls cmd`; do
#$(MAKE) --no-print-directory BIN=$(BIN) $*
done
But I get this error, could anyone explain to me how can I make it work
bash
➜ make all-build
for BIN in `ls cmd`; do
/bin/sh: -c: line 1: syntax error: unexpected end of file
make: *** [all-build] Error 2
UPDATE
this is how the complete flow of my makefile looks like:
all-%:
for BIN in `ls cmd`; do \
#$(MAKE) --no-print-directory BIN=$BIN $*; \
done
build-%:
#$(MAKE) --no-print-directory BIN=$* build
build:
docker build --no-cache --build-arg BIN=$(BIN) -t $(BIN) .
Each line of a make-recipe is executed in a distinct invocation of the shell.
Your recipe fails with a shell-syntax error because this line:
for BIN in `ls cmd`; do
is not a valid shell command. Nor is the third line:
done
To have all three lines executed in a single shell you must join them
into a single shell command with make's line-continuation character \:
# GENERIC TASKS
all-%:
for BIN in `ls cmd`; do \
#$(MAKE) --no-print-directory BIN=$$BIN $*; \
done
Note also BIN=$$BIN, not $(BIN). BIN is a shell variable here, not a make variable: $$ escapes $-expansion by make, to preserve the shell-expansion $BIN.
Using ls to drive the loop in Make is an antipattern even in shell script (you want for f in cmd/* if I'm guessing correctly) but doubly so in a Makefile. A proper design would be to let make know what the dependencies are, and take it from there.
all-%: %: $(patsubst cmd/%,%,$(wildcard cmd/*))
$(MAKE) --no-print-directory -$(MAKEFLAGS) BIN=$< $*
I am writing a makefile that should work on both Windows and Linux. So wherever possible, I avoid OS-specific shell commands.
Here is a snippet from my makefile with the clean function at the end:
# OS specific part
# -----------------
ifeq ($(OS),Windows_NT)
RM = del /F /Q
RMDIR = -RMDIR /S /Q
MKDIR = -mkdir
ERRIGNORE = 2>NUL || (exit 0)
SEP=\\
else
RM = rm -rf
RMDIR = rm -rf
MKDIR = mkdir -p
ERRIGNORE = 2>/dev/null
SEP=/
endif
PSEP = $(strip $(SEP))
# Definitions for nullstring and space
# -------------------------------------
nullstring :=
space := $(nullstring) #End
# Lists of all files and folders to keep or remove
# -------------------------------------------------
buildFiles_toKeep := ... # I wrote some scripts to
buildDirs_toKeep := ... # automatically generate
buildFiles_toRemove := ... # these lists. But that would lead
buildDirs_toRemove := ... # us too far here.
.PHONY: clean
clean:
#echo.
#echo ----------------------------------------------------------
#echo.$(space) __ ************** $(space)
#echo.$(space) __\ \___ * make clean * $(space)
#echo.$(space) \ _ _ _ \ ************** $(space)
#echo.$(space) \_`_`_`_\ $(space)
#echo.$(space) $(space)
#echo.$(space)Keep these files:
#echo.$(space) $(buildFiles_toKeep)
#echo.$(space)
#echo $(space)Keep these directories:
#echo.$(space) $(buildDirs_toKeep)
#echo.$(space)
#echo.$(space)Remove these files:
#echo.$(space) $(buildFiles_toRemove)
$(RM) $(buildFiles_toRemove)
#echo.
#echo.$(space)Remove these directories:
#echo.$(space) $(buildDirs_toRemove)
$(RMDIR) $(buildDirs_toRemove)
#echo.
#echo ----------------------------------------------------------
This makefile works great. Both on Windows and Linux, it replaces $(RM) and $(RMDIR) with the proper shell commands to delete files and folders. But I would like to prompt the user such that he/she can press Y or N. I would not want to delete files he/she wants to keep. I've tried to insert some batch-commands into the recipees of the clean target that prompt the user for input. But the prompt is not shown. Perhaps because GNU make defers the input stream.
I wonder if it is possible to generate a [Y/N] prompt with 'pure' make syntaxis (no OS-specific shell commands). I know that the make language has its limitations. Perhaps a clever solution can work on one operating system (eg. Linux) and be ported with minimal overhead to another (eg. Windows).
Anyone an idea?
EDIT :
I got referred to this link: How do I get `make` to prompt the user for a password and store it in a Makefile variable?
Gnu make will create the variable PASSWORD by prompting the user:
$ cat Makefile
PASSWORD ?= $(shell bash -c 'read -s -p "Password: " pwd; echo $$pwd')
This way of prompting the user for input works fine as long as you want the prompt to appear at the moment your makefile is parsed. But my case is different. I want to prompt the user when the makefile is already running, in other words, when the recipees in the makefile are executing.
EDIT :
Prompting the user won't be possible when you run make multi-threaded. So I'm perfectly fine with calling the clean function single-threaded:
>> make clean -j1
After all, the clean function doesn't take long to finish. I do not intend to prompt the user for anything in the build function, so that can be executed multi-threaded :-)
>> make all -j8 --output-sync=target
Conceptually make is a very simple application with one purpose - build a tree of dependencies, and remake stuff that has a newer ancestor. Interaction shouldn't be a factor so make doesn't provide it natively, ideally make should already have all the information it needs. If you really need to you can work around this with shell, !=, or even provide your own extension using guile or load.
This doesn't really apply to the clean rule though, because clean doesn't remake anything in the first place, it's just a quick hack to allow you to conveniently express a non-make operation using make syntax.
Personally I don't see the value of prompting the user on file deletion, unless you're about to delete things you aren't responsible for, which is already an antipattern in itself.
If you're absolutely positive you need this then wrap the clean recipe in a script and provide two versions for bash and windows. You could also just assume that anyone running GNU make on windows is already using MSYS2, Cygwin, or MS's sanctioned release of bash for Windows 10, and forgo the cmd / powershell script altogether.
If you want to use a Yes / No into a one line for example to execute somethings (here another makefile target) :
request-test:
#echo -n "Are you sure? [y/N] " && read ans && if [ $${ans:-'N'} = 'y' ]; then make ENV=test spec-tests; fi
Question will be prompt ;
Response will be read and store into a variable called ans;
if read the value of the ans variable with a default value to avoid errors.
Makefile:
sure:
#echo -n "Are you sure? [Y/n] " && read ans && if [ $${ans:-'Y'} = 'n' ]; then \
printf $(_ERROR) "KO" "Stopping" ; \
exit 1 ; \
else \
printf $(_SUCCESS) "OK" "Continuing" ; \
exit 0; \
fi
_SUCCESS := "\033[32m[%s]\033[0m %s\n" # Green text for "printf"
_ERROR := "\033[31m[%s]\033[0m %s\n" # Red text for "printf"
I am trying to get all the files inside each folder with Makefile:
Test = $(wildcard */)
install: all
#for dir in $(TEST); do \
echo $$dir; \
echo $(wildcard $$dir); \
done
First echo outputs correctly: folder1/ folder2/ folder3/ but when used with wildcard in second echo I am getting empty output (each folder contains many files).
Any suggestions why this doesn't work and how to archive this?
$(wildcard) is make function, which is evaluated once while makefile is parsed.
dir is a shell variable, which exists only when receipt is evaluated (using shell).
Because make is unaware about shell variables, pattern in $(wildcard $$dir) is interpreted by make literally: $$dir.
If you want to output list of files in a directory pointed by dir variable, you should use shell utilities. E.g. ls $$dir.
I have defined a list of files in a Makefile.
I need to copy those files, with their directory structure, to a new location.
I can't use cp --parent because the source files live in ../ (so .. will be a part of the dest path).
I've decided, then, to do one for loop that creates the dirs, then a second for loop that copies the files. The problem, however, is that I can't seem to call $(dir $$file) in the body of the for loop. I always get "./" as the result, rather than the actual dir name. If I do something like echo $(dir foo/bar), that works fine, but echo $(dir $$file) (when $$file is 'foo/bar') always returns './'.
I've tried other options like $(shell basename $$file), but that also doesn't work.
I've tried defining a function and calling it in the body of the for, but that, too, doesn't work.
Here's a quick example Makefile:
FILES := foo/faz \
bar/baz \
gah/gaz
all:
#for f in $(FILES); do \
echo $(dir $$f); \
done
I expect the output of this to be:
foo
bar
gah
but instead, I'm getting:
./
./
./
I'm open to other solutions if my method is not the best. At the end of the day, I need to be able to copy all the $(FILES) that exist in ../ (so ../foo/bar, for example) to a new dir called $(NEWDIR) (so newdir/foo/bar, for example).
This only needs to work under Linux.
f is a shell variable here. The make function dir is called on the string $f (which the shell then interprets as a variable expansion).
If you use a shell loop, you need to use the shell's constructs to extract the directory part:
#set -e; for f in $(FILES); do \
echo $$(dirname $$f); \
done
Don't forget set -e, so that if there's an error during the copy, the make rule will fail.
You can also use a foreach function in make to generate a bunch of cp commands. It makes the makefile harder to debug though.