Setting env variable from Perl and using in Makefile - bash

I'm trying to set env variable from Perl script and using it inside Makefile doesn't work. While dumping ENV hash tree shows variable successfully set.
Makefile code
list_gen: $(TESTBENCH_PATH)/blocks/soc_tb/global/default
perl list_gen.pl; \
$(MAKE) $(RECITAL_PATH)/catalog/catalog.xml
# touch $#
# Generates catalog.xml from .xmls of VIPs
$(RECITAL_PATH)/catalog/catalog.xml: $(VIP_XMLs)
#echo "# generating catalog.xml " ; \
echo "vip list is $(VIP_LIST)" ; \
rpfCatalog --add $(VIP_XMLs); \
for vip in $(VIP_LIST); \
do \
rpfCatalog --add $$TESTBENCH_PATH/common_blocks/$$vip/global/default.xml; \
done
Perl Script code
$ENV{"VIP_LIST"} = $vip_exists;

I don't think there is any elegant way to update environment of make by running any external command (like perl script) from it. Because even a direct way wouldn't work.
/home/user> cat makefile
NUM=100
first:
NUM=200
echo $(NUM)
second:
NUM=200; echo $(NUM)
third:
NUM=200; \
echo $(NUM);
/home/user> make first
NUM=200
echo 100
100
/home/user> make second
NUM=200; echo 100
100
/home/user> make third
NUM=200; \
echo 100;
100
If you are only looking for a perl script to modify it's parent's environment, something like this works (in a very limited way):
/home/user> cat change_env.pl
#!/usr/bin/perl -w
print "NUM=100\n";
/home/user> NUM=1
/home/user> echo $NUM
1
/home/user> eval $(./change_env.pl)
/home/user> echo $NUM
100
Coming to original your question, .. can't you call make after making the changes to your shell environment?

VIP_LIST=$(perl my.pl)
Would save the output of the script in the variable of the parent shell.
HTH
Georg

When you execute your Perl script, do it like $(./myperlscript.pl) on the prompt. What you need to do is
$ENV{VIP_LIST} = $vip_exists;
Drop the quotes

Related

how to read from stdin into a makefile variable

I want to write a Makefile to install go program and other assets, so I want a installdir. I want to check if GOBIN, GOPATH is set, if not, want user to enter a installdir.
I wrote the Makefile as following, but the makefile variable installdir is empty. echo output nothing.
installdir:=$(shell echo $(GOPATH) | cut -d':' -f1)
all: *.go
#GO111MODULE=on GOPATH=$(GOPATH) go build -o trpc main.go
install:
ifeq ($(installdir),)
installdir=$(shell echo $(GOBIN) | cut -d':' -f1)
endif
ifeq ($(installdir),)
installdir=$(shell bash -c 'read -s -p "Please input installdir: " tmpdir; echo $$tmpdir')
endif
echo $(installdir)
Please help!
This code:
install:
ifeq ($(installdir),)
installdir=$(shell echo $(GOBIN) | cut -d':' -f1)
endif
ifeq ($(installdir),)
installdir=$(shell bash -c 'read -s -p "Please input installdir: " tmpdir; echo $$tmpdir')
endif
echo $(installdir)
simply cannot work and represents a fundamental misunderstanding of how make operates.
Make works in two distinct stages: first, it reads and parses all the makefiles, included makefiles, etc. Second, it determines which targets are out of date and runs the recipes to update those targets. Recipes will start a shell and pass the recipe text to that shell. When the shell exits make determines whether it worked or not by looking at the exit code. All make variables and functions in the entire recipe are expanded first, the the shell is invoked on the results. Further, every logical line in the recipe is started in a different shell.
So in your makefile, the ifeq options (which are makefile constructs) are parsed during the first stage, as the makefile is read in. The recipe lines are not run until the second stage, so changes to the installdir variable in a recipe cannot impact the ifeq lines. Further, changes to installdir in a recipe cannot even be seen by make because they happen in a shell, then the shell exits and those changes are lost.
You'll have to write this entire thing in shell syntax and put all of it into a recipe, something like this:
install:
installdir='$(installdir)'; \
[ -n "$$installdir" ] || installdir=$$(echo $(GOBIN) | cut -d':' -f1); \
[ -n "$$installdir" ] || read -s -p "Please input installdir: " installdir; \
echo $$installdir
(untested). You have to use shell constructs, not make constructs. You should virtually never use the $(shell ...) make function inside a recipe: a recipe is already running in a shell. And you have to use backslash/newline pairs to ensure make considers the entire recipe one logical line, else variables set on one line will not be set on the next line.
Finally, I should point out that this (reading input during make) is just generally a bad idea. For example, if you run make install with the -j option, only one recipe can have control of stdin and make will choose more-or-less randomly which it is.
Generally instead you want to have the user pass the value on the command line, with something like:
$ make installdir=my/dir
so your check in the makefile should instead be something like this:
install:
installdir='$(installdir)'; \
[ -n "$$installdir" ] || installdir=$$(echo $(GOBIN) | cut -d':' -f1); \
[ -n "$$installdir" ] || { echo "Please add installdir=... on the command line"; exit 1; }; \
echo $$installdir

how to use bash script variable which read from file each line in makefile

I'm not familiar with Makefile, and I try to run the piece of bash script in Makefile, and use the variable as result in target.
my bash script like below, read from file, merge each line to variable
#!/bin/bash
input="./logs"
context=""
while IFS= read -r var; \
do \
context+="\n$var"; \
done < "$input"
echo -e $context
my logs file like below
- foo
- bar
- barzz
and the bash script work fine
but when I move it to Makefile, it can't works
Makefile
.PHONY: test pack clean
INPUT = "./logs"
CONTEXT = ""
while IFS= read -r var; \
do \
CONTEXT+="\n$(var)"; \
done < "$(INPUT)"
test:
echo -e $(CONTEXT)
how should I fix it?
thanks for your time.
I'm assuming the main goal here is to fill the CONTEXT variable with all the values you have in your logs file, rather than solving make running that shell line. I would keep the little shell script in it's own separate file, lets call it myScript for now. You can keep what you have for that.
Now for the makefile you can just initialize the variable by:
CONTEXT = $(shell ./myScript)
I personally think this is the cleanest way to achieve your goal. In case you really want everything in the makefile, take a look at this answer.

Calling one file in which there are multiple file names and has to be run in parallel

Imagine, a sample.txt file contains multiple file names (a.sh,b.sh,c.sh...). I have another file test.sh, through which I want to run all the files in parallel present in sample.txt and get exit status of each file.
Can you please help me with this?
Thanks in advance.
Ok, I think you want this:
#!/usr/bin/bash
export PATH=.:$PATH
parallel -a sample.txt '{} ; if [ $? -eq 0 ]; then echo "PASS:" {}; else echo "FAIL:" {};fi'
First test that you can run the commands in serial:
bash sample.txt
If this fails, fix that first; GNU Parallel will not magically make something work that did not work before.
Then define the testing function:
tester() {
if (eval "$#") >&/dev/null; then
perl -e 'printf "\033[30;102m[ OK ]\033[0m #ARGV\n"' "$#"
else
perl -e 'printf "\033[30;101m[FAIL]\033[0m #ARGV\n"' "$#"
fi
}
export -f tester
Then call the testing function:
parallel tester :::: sample.txt
If you are not using bash (and export -f fails above) then try (use version 20161022 or later):
env_parallel tester :::: sample.txt
If your fix above was to prepend each line with bash you can make GNU Parallel do that (in which case you do not need to fix sample.txt):
parallel tester bash :::: sample.txt
To run more jobs in parallel use -j0. To get the actual exit code use --joblog mylog.txt and look in mylog.txt.

Use $RANDOM in a makefile

I am making a makefile to rename files with a random number in it (I am a newbie in shell script). I don't understand why, but when I run the file $rand is given the value 'ANDOM'. When I run this outside of the makefile it works.
I run this in the Mac os terminal, in case it's helpful.
all: renamefiles
renamefiles:
rand=$RANDOM && mv myfile.css $rand-myfile.css && mv myotherfile.css $rand-myotherfile.css
Wouldn't it be easier/better to use a date/time stamp so that the renamed files are listed in date order?
You need to use two $ signs in the makefile for each $ that you want the shell to see.
Thus:
all: renamefiles
renamefiles:
rand=$$RANDOM && \
mv myfile.css $$rand-myfile.css && \
mv myotherfile.css $$rand-myotherfile.css
Or, with date/time stamps:
all: renamefiles
renamefiles:
time=$$(date +'%Y%m%d-%H%M%S') && \
mv myfile.css $$time-myfile.css && \
mv myotherfile.css $$time-myotherfile.css
To use a random number within one or multiple make variables, the following works fine for me:
FOO="some string with \"$$rand\" in it"
BAR=" you may use it $$rand times."
foobar:
rand=$$$$ && \
echo $(FOO) $(BAR)
You might need to surround a multi-letter macro name with braces (or parentheses), for example
${RANDOM}
$(RANDOM)
ref

Makefile (counting)

I'm completely stumped on how to do this in a Makefile
Let's say I have a target. Inside the target I have a loop. How do i change a variable to keep track of the iterations?
For example:
COUNTER = 0
target:
(loop){
COUNTER++
echo COUNTER
}
I know that variables in Makefiles are only expanded, and I'm not sure if they can be permanently changed, but there has to be a way to do this, right? :(
Here are some sources that are asking similar questions. It seems like those examples only change the variable temporarily:
How do I perform arithmetic in a makefile?
How to do arithmetic operation in makefile?
Doing simple math in Makefile
Maybe I have to use the eval function somehow?
Maybe I have to append onto a Makefile string a character each time and then use something in the shell to count the characters?
If the variable doesn't have to survive the rule, this should do (I'm assuming bash):
clean:
#n=0 ; \
for x in $(THINGS_TO_BE_DELETED); do \
if [ -f $$x ] ; then \
rm $$x; \
let "n+=1" ; \
fi ; \
done ; \
echo deleted $$n files;
Here is one solution: Write a simple script like this:
#!/bin/bash
count=`cat count.txt`
count=$((count + 1))
echo $count
cat $count > count.txt
Initialize the file by doing
$ echo "0" > count.txt
Then include it as a .PHONY requirement to build whatever you'd like.
This is similar to the accepted answer, but the syntax below should work with a POSIX compliant shell. Quotes should also be used inside of the test.
clean:
#n=0; \
for x in *.a *.b *.c ; do \
if [ -f "$$x" ]; then \
rm "$$x"; \
n=$$((n+1)); \
fi; \
done; \
echo deleted $$n files;
Note: tabs must be used for indentation

Resources