2

Is there a GNU make alternative if don't want to use tab indents in my make program (or make-like) program?

For example, when I use make, I need to indent everything after the make opener, (% :). This is a recipe for some problems in some circumstances (for example, I work cross-platform and I use a Windows10 AutoHotkey mechanism that strips tabs from codes I paste into Linux terminals from different reasons and it doesn't pass over make hence I need a non tab including solution).

The necessity to tab-indent everything under % : makes my work with make non fluent.

This is the make I use to create new virtual host conf files. I execute it with make domain.tld.conf:

% :
    printf '%s\n' \
    '<VirtualHost *:80>' \
    'DocumentRoot "/var/www/html/$@"' \
    'ServerName $@' \
    '<Directory "/var/www/html/$@">' \
    'Options +SymLinksIfOwnerMatch' \
    'Require all granted' \
    '</Directory>' \
    'ServerAlias www.$@' \
    '</VirtualHost>' \
    > "$@"
    a2ensite "$@"
    systemctl restart apache2.service

Is there any alternative, maybe something that comes with Unix itself that provides similar functionality but without having to use tab indents in the pattern file itself?

  • 1
    Is it being annoyed with _tabs_ or with _indentation_? Because it is easy to create `pseudo-makefile` and use `sed 's/^[ ][ ]*/\t/' pseudo-makefile | make -f - thing-to-make` (character-class brackets added for clarity) – Fox May 05 '17 at 04:55
  • Sato, can you give an example for what was hard for you to understand?... –  May 05 '17 at 05:05
  • 1
    Your use of `%:` suggests that a makefile may not be appropriate; could you [edit] your question to include an actual makefile you’re using? – Stephen Kitt May 05 '17 at 05:07
  • @StephenKitt [My bad](https://unix.stackexchange.com/a/354227) — to be fair though, there is more context than the Q suggests as to why I suggested `make` to begin with – Fox May 05 '17 at 05:27
  • @StephenKitt To be clear, I'm actually only advocating for the second half of my answer over there. The first half was hasty, and acts more as an introduction than anything else – Fox May 05 '17 at 05:33
  • @Fox yes your makefile makes sense once you build up various templates (so there *are* dependencies to declare). – Stephen Kitt May 05 '17 at 05:39
  • Hmm, I suppose one could create two makefiles, one with the actual commands, and another to run the `sed` to convert spaces to tabs and run the first one after that... – ilkkachu May 05 '17 at 07:54

4 Answers4

10

GNU Make's .RECIPEPREFIX variable (note: not a special target) can be used to change the character that sets off recipe lines.

For example:

.RECIPEPREFIX=>
%:
>printf '%s\n' \
>'<VirtualHost *:80>' \
>'DocumentRoot "/var/www/html/$@"' \
>'ServerName $@' \
>'<Directory "/var/www/html/$@">' \
>'Options +SymLinksIfOwnerMatch' \
>'Require all granted' \
>'</Directory>' \
>'ServerAlias www.$@' \
>'</VirtualHost>' \
>> "$@"
>a2ensite "$@"
>systemctl restart apache2.service
zwol
  • 7,089
  • 2
  • 19
  • 31
7

If that’s your whole Makefile, and you’re not tracking any dependencies between files, just use a shell script:

#!/bin/sh

for domain; do
> "/etc/apache2/sites-available/${domain}.conf" cat <<EOF
<VirtualHost *:80>
DocumentRoot "/var/www/html/${domain}"
ServerName "${domain}"
<Directory "/var/www/html/${domain}">
Options +SymLinksIfOwnerMatch
Require all granted
</Directory>
ServerAlias www.${domain}
</VirtualHost>
EOF
a2ensite "${domain}"
done

systemctl restart apache2.service

Copy the above into a file named for example create-vhost, make it executable:

chmod 755 create-vhost

then run it as

./create-vhost domain.tld

This even supports creating multiple virtual hosts’ configuration files (with a single restart at the end):

./create-vhost domain1.tld domain2.tld
Stephen Kitt
  • 411,918
  • 54
  • 1,065
  • 1,164
  • You are right. I confused when calling it "makefile" and I called the file `cvh` (as acronym for create-virtual-host). About Bash, yes, I used that instead of doing `chmod +x` to save a row in my handbook file but of course that's subjective. Thank you for all the help Stephen. –  May 05 '17 at 23:50
  • I do want to ask this, if I create `alias cvh="/etc/apache2/sites-available/cvh"`, why can't I use it from anywhere in the system as `cd ~ && cvh domain.tld` and must navigate each time anew to `/etc/apache2/sites-available`? In other words, why does the arguments work only when I am in the sites-available directory and is there something to do to make them work outside of it, directly on the alias? –  May 14 '17 at 20:48
  • 1
    See the updated script — all that’s needed is to add the target directory to the file name, which makes perfect sense since the script is supposed to create site configuration files which go in `/etc/apache2/sites-available` (in Debian derivatives). Thanks for the bounty! – Stephen Kitt May 14 '17 at 21:05
1

If you are using GNU make then you can put to good use the user-defined functions and accomplish what you are wanting:

# variables utilized
NULL :=
SPC  := $(NULL) $(NULL)
TAB  := $(NULL)$(shell printf '\t\n')$(NULL)

# macro to repeat a string ($2) ($1) times
_rep_str = $(if $(filter $1,$(words $3)),$(strip $3),$(call _rep_str,$1,$2,$3 $2))
rep_str  = $(subst $(SPC),$(NULL),$(subst x,$2,$(call _rep_str,$1,x)))

# TABs for depth of 1, 2, 3, ...
T1 := $(call rep_str,1,$(TAB))
T2 := $(call rep_str,2,$(TAB))
T3 := $(call rep_str,3,$(TAB))

# multiline macro to be used in recipes for generating .conf files
define create_conf
printf '%s\n' \
'<VirtualHost *:80>'                   \
'$(T1)DocumentRoot "/var/www/html/$@"' \
'$(T1)ServerName $@'                   \
'$(T1)<Directory "/var/www/html/$@">'  \
'$(T2)Options +SymLinksIfOwnerMatch'   \
'$(T2)Require all granted'             \
'$(T1)</Directory>'                    \
'$(T1)ServerAlias www.$@'              \
'</VirtualHost>' > $@
a2ensite "$@"
systemctl restart apache2.service
endef

# Now there are no leading TABs/spaces in the makefile

% :; @$(call create_conf)
  • How does this avoid the OP's problem with tabs being replaced by spaces at the beginning of each line in a Makefile? – roaima May 11 '17 at 09:19
  • 1
    @roaima Notice that there are no TABs or even spaces in the `macro` `create_conf`. –  May 11 '17 at 09:53
  • @roaima And if you are wanting to ask do away with the leading TABs in `make` `recipes` then that's a hard requirement which can't be done away with. So with that in mind, I have tried to minimize that by writing a `macro` which DOES NOT have any leading TABs/spaces and which then is used inside a recipe where you need only a single TAB. The other alternative is this ungainly: `%:; @printf '%s\n' $(call create_conf);...` –  May 11 '17 at 10:05
  • I didn't make the requirement. It is part of the OP's question, though. – roaima May 11 '17 at 11:43
  • Your edit does remove the tabs, yes. – roaima May 11 '17 at 19:15
1

i've a "newest" bash function which is used like this:

newest ${target} ${dependencies} || {

         ${command} ${dependencies} > $target 
}

It doesn't parse the script it's part of; I was never a fan of make's "transitive closure" feature, so it's only necessary to sequence the instructions in the "makefile".

slightly higher level functions "bystdout", and "bycommand" are used

 bystdout ${target} ${command} ${dependencies}
 bycommand ${target} ${command} ${dependencies}

where "bycommand" wraps those whose output may be inferred from the dependencies.

i was once crazy enough to write an awk-parsed language that unwrapped nested dependencies:

 output ={ command }{ dependencies }.

it's not that big a task.