Credit: Aurelio A. Heckert, CC BY-SA 2.0 , via Wikimedia Commons

Go Ahead ‘make’ My Day (Part II)

In our previous blog post “Go Ahead ‘make’ My Day” we presented the scriptlet, an advanced make technique for spicing up your Makefile recipes. In this follow-up, we’ll deconstruct the scriptlet and detail the ingredients that make up the secret sauce.


Introducing the Scriptlet

Makefile scriptlets are an advanced technique that uses GNU make’s powerful functions to safely embed a multi-line script (Perl, in our example) into a single, clean shell command. It turns a complex block of logic into an easily executable template.

An Example Scriptlet

#-*- mode: makefile; -*-

DARKPAN_TEMPLATE="https://cpan.openbedrock.net/orepan2/authors/D/DU/DUMMY/%s-%s.tar.gz"

define create_requires =
 # scriptlet to create cpanfile from an list of required Perl modules
 # skip comments
 my $DARKPAN_TEMPLATE=$ENV{DARKPAN_TEMPLATE};

 while (s/^#[^\n]+\n//g){};

 # skip blank lines
 while (s/\n\n/\n/) {};

 for (split/\n/) { 
  my ($mod, $v) = split /\s+/;
  next if !$mod;

  my $dist = $mod;
  $dist =~s/::/\-/g;

  my $url = sprintf $DARKPAN_TEMPLATE, $dist, $v;

  print <<"EOF";
requires \"$mod\", \"$v\",
  url => \"$url\";
EOF
 }

endef

export s_create_requires = $(value create_requires)

cpanfile.darkpan: requires.darkpan
    DARKPAN_TEMPLATE=$(DARKPAN_TEMPLATE); \
    DARKPAN_TEMPLATE=$$DARKPAN_TEMPLATE perl -0ne "$$s_create_requires" $< > $@ || rm $@

Dissecting the Scriptlet

1. The Container: Defining the Script (define / endef)

This section creates the multi-line variable that holds your entire Perl program.

define create_requires =
# Perl code here...
endef
  • define ... endef: This is GNU Make’s mechanism for defining a recursively expanded variable that spans multiple lines. The content is not processed by the shell yet; it’s simply stored by make.
  • The Advantage: This is the only clean way to write readable, indented code (like your while loop and if statements) directly inside a Makefile.

2. The Bridge: Passing Environment Data (my $ENV{...})

This is a critical step for making your script template portable and configurable.

my $DARKPAN_TEMPLATE=$ENV{DARKPAN_TEMPLATE};
  • The Problem: Your Perl script needs dynamic values (like the template URL) that are set by make.
  • The Solution: Instead of hardcoding the URL, the Perl code is designed to read from the shell environment variable $ENV{DARKPAN_TEMPLATE}. This makes the script agnostic to its calling environment, delegating the data management back to the Makefile.

3. The Transformer: Shell Preparation (export and $(value))

This is the “magic” that turns the multi-line Make variable into a single, clean shell command.

export s_create_requires = $(value create_requires)
  • $(value create_requires): This is a specific Make function that performs a direct, single-pass expansion of the variable’s raw content. Crucially, it converts the entire multi-line block into a single-line string suitable for export, preserving special characters and line breaks that the shell will execute.
  • export s_create_requires = ...: This exports the multi-line Perl script content as an environment variable (s_create_requires) that will be accessible to any shell process running in the recipe’s environment.

4. The Execution: Atomic Execution ($$ and perl -0ne)

The final recipe executes the entire, complex process as a single, atomic operation, which is the goal of robust Makefiles.

cpanfile.darkpan: requires.darkpan
    DARKPAN_TEMPLATE=$(DARKPAN_TEMPLATE); \
    DARKPAN_TEMPLATE=$$DARKPAN_TEMPLATE perl -0ne "$$s_create_requires" $< > $@ || rm $@
  • DARKPAN_TEMPLATE=$(DARKPAN_TEMPLATE): This creates the local shell variable.
  • DARKPAN_TEMPLATE=$$DARKPAN_TEMPLATE perl...: This is the clean execution. The first DARKPAN_TEMPLATE= passes the newly created shell variable’s value as an environment variable to the perl process. The $$ ensures the shell variable is properly expanded before the Perl interpreter runs it.
  • perl -0ne "...": Runs the Perl script:
    * `-n` and `-e` (Execute script on input)
    * `-0`: Tells Perl to read the input as one single block
      (slurping the file), which is necessary for your multi-line
      regex and `split/\n/` logic.
    
  • || rm $@: This is the final mark of quality. It makes the entire command transactional—if the Perl script fails, the half-written target file ($@) is deleted, forcing make to try again later.

Hey Now! You’re a Rockstar!

(..get your game on!)

Mastering build automation using make will transform you from being an average DevOps engineer into a rockstar. GNU make is a Swiss Army knife with more tools than you might think! The knives are sharp and the tools are highly targeted to handle all the real-world issues build automation has encountered over the decades. Learning to use make effectively will put you head and shoulders above the herd (see what I did there? 😉).

Calling All Pythonistas!

The scriptlet technique creates a powerful, universal pattern for clean, atomic builds:

  • It’s Language Agnostic: Pythonistas! Join the fun! The same define/export technique works perfectly with python -c.
  • The Win: This ensures that every developer - regardless of their preferred language - can achieve the same clean, atomic build and avoid external script chaos.

Learn more about GNU make and move your Makefiles from simple shell commands to precision instruments of automation.

Thanks for reading.

Learn More


Next post: Go Ahead ‘make’ My Day (Part III)

Previous post: Bump Your Semantic Version