Remark: Put this Question on tex.stackexchange and was adviced to also ask here
Under Windows (using make from GnuWin32) i want to set my TEXINPUTS variable in a makefile
My Structure is as follows:
./
|-texmf_project/
|-Package.sty
|-main.tex
|-makefile
I want to be able to use Package.sty in my compilation process. The files look like this:
Package.sty contains:
\ProvidesClass{Package}[]
\NeedsTeXFormat{LaTeX2e}
\RequirePackage{xcolor}
\newcommand{\red}[1]{\textcolor{red}{#1}}
main.tex contains:
\documentclass[11pt,a4paper]{report}
\usepackage{xcolor}
\usepackage{Package}
\begin{document}
Hello World\\
\red{Hello World}
\end{document}
Now i want to set the TEXINPUTS to include what is in ./texmf-project/
Hence my Makefile:
edit:
set TEXINPUTS=./texmf-project//;
pdflatex -synctex=1 -interaction=nonstopmode main.tex
del *.log
del *.aux
However this does not seem to make the location available for compilation.
However if I put the line
set TEXINPUTS=./texmf-project//; directly into the cmd and run make afterwards it works.
I believe I am making a mistake with the set, but I am far from beeing an expert.
Any helps or hints would be greatly appreciated.
Edit: Fixed a spelling mistake
Please remember that every recipe line is executed in a separate shell, so your set is executed in a different shell than pdflatex. Either put those two commands on a single line, or concatenate lines with \ so that lines are executed in a single shell, i.e.:
edit:
set TEXINPUTS=./texmf-project//; \
pdflatex -synctex=1 -interaction=nonstopmode main.tex
Edit:
Since this is Windows, it gets more complicated. Just adding \ and even & is not enough, since make will run it in a single line (through a batch file):
> type Makefile
all:
set TEXINPUTS=./texmf-project// & \
echo %TEXINPUTS%
> make -dr
...
Must remake target 'all'.
Creating temporary batch file C:\Users\user\AppData\Local\Temp\make11444-1.bat
Batch file contents:
#echo off
set TEXINPUTS=./texmf-project// & echo %TEXINPUTS%
...
ECHO is off.
...
Now, cmd.exe uses a line parser that expands variables when the line is parsed, so it first expands %TEXINPUTS% to an empty string (since it was not yet defined) and after that it evaluates the code which sets the variable. Therefore it is crucial that those commands are on separate lines to have variable set before it is used. The easiest way (if you use quite modern make) is including a .ONESHELL directive which would place all recipe into the batch and execute all at once:
> type Makefile
.ONESHELL:
all:
set TEXINPUTS=./texmf-project//
echo %TEXINPUTS%
> make -dr
...
Must remake target 'all'.
Creating temporary batch file C:\Users\user\AppData\Local\Temp\make29908-1.bat
Batch file contents:
#echo off
set TEXINPUTS=./texmf-project//
echo %TEXINPUTS%
...
./texmf-project//
...
Alternatively, if you don't somehow calculate the value, you can just set the variable at the make level (global or target level) and export it to the process, i.e.:
> type Makefile
all: export TEXINPUTS := ./texmf-project//
all:
echo %TEXINPUTS%
> make -dr
...
Must remake target 'all'.
Creating temporary batch file C:\Users\user\AppData\Local\Temp\make25392-1.bat
Batch file contents:
#echo off
echo %TEXINPUTS%
...
./texmf-project//
...
Related
Using nmake I have the following makefile which currently does what I need it to do. mycmd (the program being run) will take a .inp file and produce a .out file. I can make as many .inp files as I need to and the makefile doesn't have to change. It will find them all and make all the relevant .out files.
#####################################################################################
# A SUFFIXES declaration is required in order to later use the rule with target .inp.out
#####################################################################################
.SUFFIXES: .inp
#####################################################################################
# Here, NMAKE will expand *.inp in the prereq list for all, into the list of *.inp
# files in the directory, and then it will start a new NMAKE instance, specifying the
# goals to build all those files.
#####################################################################################
all: *.inp
$(MAKE) $(**:.inp=.out)
#####################################################################################
# $(*B) represents the current target's base name minus the path and the file extension
#####################################################################################
.inp.out:
mycmd -i $(*B).inp -o $(*B).out
My question is, how do I enhance this makefile further so that I could, for example, run it for a set of .inp files, so not *.inp but say, ABC*.inp?
A simple modification to your makefile should work. Add a new $(pattern) macro:
.SUFFIXES: .inp
pattern = * # new macro; defaults to *
all: $(pattern).inp # use it!
#$(MAKE) -nologo $(**:.inp=.out)
.inp.out: # dummy stub for testing
#echo mycmd -i $(*B).inp -o $(*B).out
#type NUL > $(*B).out
Then in your command line, overwrite pattern. For example, nmake -nologo pattern=ABC*.
Update: The command line in your makefile:
$(MAKE) $(**:.inp=.out)
will fail with fatal error U1095: expanded command line ... too long if the string $** is too long. On my system, this happens at roughly 32800 characters.
Adding an exclamation mark ! to the start (see here) doesn't seem to work, probably because there is no simple $**. Two workarounds are to use:
!call set a=$** & call nmake %%a:.inp=.out%%
or:
!for %a in ($**) do nmake -nologo %~na.out
These are both about twice as slow as your original, with a do-nothing mycmd stub. (The for loop here is not really a loop, because $** is just a single item.)
A different solution is to keep your original makefile, and use a DOS command such as:
for %a in (ABC*.inp) do nmake -nologo %~na.out
Here the syntax %~na removes the extension from the variable %a.
This is slightly slower than just using the makefile, but not by much. For example, with 600 inp files, and a mycmd stub, on my system this command takes 20 seconds compared to 15 seconds for the makefile.
I am trying to remove the path prefix. Here is a small example showing just the issue.
Makefile
dist_directory = ./dist
default: build
build: $(patsubst %.md, $(dist_directory)/%.html, $(wildcard *.md))
$(dist_directory)/%.html: %.md
#echo start
#echo $#
#echo ${$#//$(dist_directory)/}
#echo end
Create a file: touch stuff.md
Then build: make
The output is:
start
dist/stuff.html
end
The expected output is:
start
dist/stuff.html
/stuff.html
end
There are similar posts on Stack Exchange. However, they have not worked for me in a Makefile for some reason. I'm probably doing something wrong.
https://unix.stackexchange.com/questions/311758/remove-specific-word-in-variable
Remove a fixed prefix/suffix from a string in Bash
Remove substring matching pattern both in the beginning and the end of the variable
You have many issues here. The most fundamental one is that if you want to use shell variables you have to escape the dollar sign so that make doesn't interpret it. And, you can only use shell variable substitutions on shell variables, while $# is a make variable, so you need:
#foo='$#' ; echo $${foo//$(dist_directory)/}
The more subtle one is that make always uses /bin/sh (POSIX standard shell) when it invokes recipes, and the above syntax is specific to bash. One way around that would be to explicitly set SHELL := /bin/bash in your makefile to force make to use bash. Luckily that is not necessary because POSIX sh can also do this, as mentioned by Reda in another answer:
#foo='$#' ; echo $${###*/}
But even more, you don't need any of this because make sets the automatic variable $* to the part of the target that matches the stem (the %):
#echo $*.html
It also sets $(#F) to the filename part of the $# variable:
#echo $(#F)
ETA
If you want to do something very similar to your shell variable expansion using GNU make you can use:
#echo $(patsubst $(dist_directory)/%,%,$#)
I have a Makefile like so:
T:=$(shell mktemp)
include ${T}
I:=$(shell rm ${T})
all:
echo done
In theory, mktemp should create an empty file and return its name. The next line should include that file. The following line should delete it.
When I run it I get:
make: *** No rule to make target `/tmp/tmp.Cwe7kiNBA3'. Stop.
If I comment out the third line like so:
T:=$(shell mktemp)
include ${T}
#I:=$(shell rm ${T})
all:
echo done
The Makefile works as expected, but leaves the temporary file behind.
Why doesn't the original example work as expected?
Your Makefile seems good without the include ${T} command. As described by GNU, the include directive is useful to:
suspend reading the current makefile and read one or more other
makefiles before continuing.
So, the following Makefile:
T:=$(shell mktemp)
I:=$(shell rm ${T})
all:
echo done
will produce this output and it will not report errors:
echo done
done
Make is trying to remake your included Makefile - for example, it works if you replace include with -include (which doesn't complain when the remake fails). You can fix it by adding an empty recipe for it: ${T}: ;.
I have a makefile in a directory of mine which builds scripts with certain environment variables set. What if I want to create another makefile in the same directory with different environment variables set? How should I name the two make files? Does makefile.1 and makefile.2 work? How do I call them?
You can give sensible names to the files like makefile.win and makefile.nix and use them:
make -f makefile.win
make -f makefile.nix
or have a Makefile that contains:
win:
make -f makefile.win
nix:
make -f makefile.nix
and use make win or make nix
You can name makefile whatever you want. I usually name it like somename.mk. To use it later you need to tell make what makefile you want. Use -f option for this:
make -f somename.mk
Actually you can have two set of environment variables in the same make file. for example
COMPILER = gcc
CCFLAGS1 = -g
CCFLAGS2 = -Wall
a: main.c
${COMPILER} ${CCFLAGS1} main.c
b: test.c
${COMPILER} ${CCFLAGS2} test.c
then you can just say make a or make b. Depending on what you want.
Also it is possible with -f flag to call which makefile you want to call.
You can do something like this rather than using multiple makefiles for the same purpose. You can pass the environment or set a flag to the same makefile. For eg:
ifeq ($(ENV),ENV1)
ENV_VAR = THIS
else
ENV_VAR = THAT
endif
default : test
.PHONY : test
test:
#echo $(ENV_VAR)
Then you can simply run the make command with arguments
make ENV=ENV1
I have two makefiles in the same directory. Many of the recipes have identical names and here are two solutions:
1. Prefix in make
proja_hello:
#echo "hello A"
projb_hello:
#echo "hello N"
2. Keep two separate files
Project A has makefile. Type make hello.
Project B has a separate make file called projb.mk. Type bmake hello.
This works since I've added alias bmake ='make -f projb.mk to my .bashrc. Note! This command can be called anywhere but only works where projb.mk exists.
Note! You lose autocompletion of make with the alias and typing make -f projb.mk hello is not better than typing make projb_hello.
How can I write the contents of a makefile variable to file, without invoking a shell command?
The problem is that the contents of the variable is possible longer than the shell allows for a command (i.e. longer than MAX_ARG_STRLEN (131072) characters).
In particular, in a makefile I have a variable containing a long list of filenames to process (including their absolute pathes for out-of-source builds). Now I need to write those filenames to a (temporary) file, which I can then pass to another command.
So far, we had a rule like ($COLLATED_FILES is the variable containing the paths):
$(outdir)/collated-files.tely: $(COLLATED_FILES)
$(LYS_TO_TELY) --name=$(outdir)/collated-files.tely --title="$(TITLE)" \
--author="$(AUTHOR)" $^
This breaks if COLLATED_FILES is longer than about 130000 characters, we get the error message:
make[2]: execvp: /bin/sh: Argument list too long
As a solution, we are now trying to write the contents of the variable to a file and use that file in the $(LYS_TO_TELY) command. Unfortunately, I have not yet found a way to do this without invoking the shell.
My attempts include:
$(outdir)/collated-files.list: $(COLLATED_FILES)
echo "" > $#
$(foreach f,$^,echo $f >> $#;)
But this also invokes all echo commands at once in a shell, so the shell command is just as long.
Is there any way to write the contents of $(COLLATED_FILES) to a file on disk without passing them on the command line to a shell command?
I also searched whether I could pipe the contents of the variable to the shell, but I couldn't find anything in that direction, either...
Assuming you are using GNU Make, there is the file function!
https://www.gnu.org/software/make/manual/html_node/File-Function.html
$(file op filename,text)
where op is either > or >>.
This requires GNU Make 4.0+
You could move whatever makefile code you use to build up the value of COLLATED_FILES to a trivial helper makefile, then invoke make recursively from your original makefile and use trivial shell redirection to capture the stdout of the recursive make invocation -- basically using make as a rudimentary text-processing tool in that context. For example, create a makefile called get_collated_files.mk with these contents:
COLLATED_FILES=abc
COLLATED_FILES+=def
COLLATED_FILES+=ghi
# ... etc ...
# Use $(info) to print the list to stdout. If you want each filename on a
# separate line, use this instead:
#
# $(foreach name,$(COLLATED_FILES),$(info $(name)))
$(info $(COLLATED_FILES))
all: ;##shell no-op to quell make complaints
Then, in your original makefile:
collated-files.list:
$(MAKE) -f get_collated_files.mk > $#
$(outdir)/collated-files.tely: collated-files.list
$(LYS_TO_TELY) --name=$(outdir)/collated-files.tely --title="$(TITLE)" \
--author="$(AUTHOR)" --filelist=collated-files.list
This will be quite a lot more efficient than using hundreds or thousands of individual echo invocations to append to the file one path at a time.
EDIT: One final option, if you really want to have each filename on a separate line, and you have a lot of control over how COLLATED_FILES is defined:
define NL
endef
COLLATED_FILES=abc
COLLATED_FILES+=$(NL)def
COLLATED_FILES+=$(NL)ghi
$(info $(COLLATED_FILES))
all: ;##no-op
This approach allows you to again use just one call to $(info), if that's important to you for some reason.
Here's a patch to gnu make that lets you directly write a variable into a file. It creates a new 'writefile' function, similar to the existing 'info' function, except it takes a filename argument and writes to the file:
https://savannah.gnu.org/bugs/?35384
It looks to me as if you should rethink your build design-- surely there's a better way than letting a variable get this big. But here's a way to do it:
# Make sure this doesn't collide with any of your other targets.
NAMES_TO_WRITE = $(addprefix write_,$(COLLATED_FILES))
collated-files.list: $(NAMES_TO_WRITE)
write_blank:
echo "" > collated-files.list
.PHONY: $(NAMES_TO_WRITE)
$(NAMES_TO_WRITE) : write_% : write_blank
echo $* >> collated-files.list