Skip to main content

What the SHELL!?!?

Have you ever had that lingering question of:
What else can I do with the SHELL environment variable in GNU/Make?
Well, I did and tried a few different little tests and figured I'd share them with you.

If you do much work with GNU/Make, you've probably seen some projects specifically use /bin/bash instead of /bin/sh. But outside of that, maybe you've never seen SHELL set to anything else.

I have two new favorite features in GNU/Make 3.82:
  • .SHELLFLAGS
  • .ONESHELL:
 .SHELLFLAGS
The argument(s) passed to the shell are taken from the variable .SHELLFLAGS. The default value of .SHELLFLAGS is -c normally, or -ec in POSIX-conforming mode.
.ONESHELL:
Sometimes you would prefer that all the lines in the recipe be passed to a single invocation of the shell.
Lets have some fun! Lets say instead of using a posix shell for our Makefile recipe, lets use Python instead!
SHELL = /usr/bin/python                                                         
.PHONY: all clean

all:
        f = open("hello.txt", "w"); f.write("Hello, World!\n"); f.close()

clean:
        import os; os.remove("hello.txt")
This example doesn't show my two new favorite GNU/Make features, but it's a great example of why I like them!

One thing to note with this example is that the default option passed to the $(SHELL) is '-c', which... luckily is the right option to pass python to execute a command on the python command-line! If we used GNU/Make 3.81 and wanted to use perl instead of python, there would be no way of setting '-e' or '-E' on the perl command-line to say that what is in the recipe of the Makefile are not files themselves, but commands to execute (like one-liner's). So in this episode, I'm not going to go much into .SHELLFLAGS.

Another thing to note about this example is that the whole recipe is on one line! If we wanted to break it up on multiple lines, we could do it this way:
SHELL = /usr/bin/python                                                         
.PHONY: all clean

all:
        f = open("hello.txt", "w"); \
        f.write("Hello, World!\n"); \
        f.close()

clean:
        import os; \
        os.remove("hello.txt")
Great! But, this is still hellishly ugly! All those semi-colons and back slashes are ugly and error prone to seasoned python developers.

Enter .ONESHELL:
.ONESHELL:
SHELL = /usr/bin/python                                                         
.PHONY: all clean

all:
        f = open("hello.txt", "w")
        f.write("Hello, World!\n")
        f.close()

clean:
        import os
        os.remove("hello.txt")
Awesome! Instead of the interpreter executing and exiting for each line of the recipe, it runs the entire recipe with one instance.

Now, it would be great to not see the commands when we run make! I noticed when I add the '@' character to just the first line in the recipe, it silences the whole recipe, so now we have:
.ONESHELL:
SHELL = /usr/bin/python                                                         
.PHONY: all clean

all:
        @f = open("hello.txt", "w")
        f.write("Hello, World!\n")
        f.close()

clean:
        @import os
        os.remove("hello.txt")
And this make sense, because with .ONESHELL set, the whole recipe is being ran by the same interpreter instance.

I know the examples here aren't exactly mind blowing, but I didn't see a whole lot of documentation on using other interpreters with $(SHELL) on GNU/Make. Hopefully this is helpful to someone!

EDIT: One last thing to note, if you are new to using GNU/Make, the lines before the recipe start with tabs, not 8 spaces! This can also be changed with the new feature .RECIPEPREFIX:
.ONESHELL:
.RECIPEPREFIX=>
SHELL = /usr/bin/python                                                         
.PHONY: all clean

all:
>@f = open("hello.txt", "w")
>f.write("Hello, World!\n")
>f.close()

clean:
>@import os
>os.remove("hello.txt")
Enjoy!

Comments

Popular posts from this blog

Master of android repo mirrors

Sometimes you want to mirror an entire android platform repo, locally, so that you can init & sync to a working directory without being connected to the internet. Handy if your internet connection sucks, or you travel a lot. I'm sure you can use your imagination. Updating means you just have to keep track of a list of mirrors and make sure they are synced in order (for reference chaining). For AOSP, this task is pretty simple, and the first repository you'll probably mirror: popd mkdir aosp-mirror pushd $_ repo init -u https://android.googlesource.com/mirror/manifest --mirror repo sync -j[dl-jobs] NOTES: $_ in bash is the first argument to the previous command, also dl-jobs is the number of download jobs. popd is the directory we started in. If say, I were on my Mac10.8 and made a case-sensitive sparse disk, and: mkdir /Volumes/Android/mirrors && cd /Volumes/Android/mirrors/ then popd will just return the current directory, as current w...

Using repo-mm's gerrit-mirror.sh

Overview From my previous post on the topic of using a local mirror to do android platform development while not connected to the internet, I've started working on a small shell project called repo-mm . On the road to completing repo-mm's goal, i've created a small component called gerrit-mirror.sh. Eventually, this will be a part of repo-mm's script, but this is a WIP... In this post, we'll sync AOSP and CyanogenMod, and setup a cm-10.1 working directory. I'll also show some manual steps that will be later managed by repo-mm. The scope does not include building CyanogenMod. Their wiki does a great job with that . Install and Setup First step is to get the repo-mm repository and fetch the submodules. I use Mac OS X 10.8.4 and homebrew . But this should also work on Linux and Windows[Cygwin/MinGW]. On Mac OS X, don't forget to make a case-sensitive disk image ! I called mine Android. Go figure. I recommend making a large sparse image. This way ...