Giter Site home page Giter Site logo

lgersman / make-recipes-in-different-scripting-languages-demo Goto Github PK

View Code? Open in Web Editor NEW
9.0 2.0 2.0 47 KB

Did you kow that Make recipes can be written in almost any scripting language ? You don't need to know about shell scripting. Just use NodeJS, Typescript , Python or whatever you want !

License: MIT License

Makefile 75.55% Shell 24.45%
make makefile bash demo shell sqlite3 posix

make-recipes-in-different-scripting-languages-demo's Introduction

Makefile recipes in various languages

Using make does not mean that you have to learn shell scripting (although it's always a good idea to have at least basic knowledge about it).

The important point is : You can use almost any scripting language for make recipes, not just shell scripts !

  • Thats how a Makefile looks using NodeJS as make SHELL :
# suppress verbose make output
MAKEFLAGS += --silent

# make nodejs the "shell" for make recipes
# (this requires a modern nodejs version to be installed - in my case )
SHELL != sh -c "command -v node"
# tell make which commandline options to apply for shell invocation
.SHELLFLAGS := --experimental-modules --input-type=module -e
# => from now on every shell invocation by make will be nodejs

# .ONESHELL tells make to execute a target recipe as a single SHELL call
# (by default make would execute a recipet line by line in separate SHELL calls
.ONESHELL:

# et voilà : the recipe can now be written in plain modern javascript :-)
all: foo	
	// use some make variables
	console.log("# target name is '$@', depends on '$^'");

	import {readdir } from 'node:fs/promises';
	console.log(await readdir('..'));

foo:
	// use some make variables
	console.log("# target name is '$@', depends on '$^'");

	console.log("node version is ", process.version);

# tell make that these targets are NOT meant to be files/directories
.PHONY: all foo
  • Do you prefer Python ? Yes, it's possible !
# suppress verbose make output
MAKEFLAGS += --silent

# make python the "shell" for make recipes
SHELL != sh -c "command -v python"
# tell make which commandline options to apply for shell invocation
.SHELLFLAGS := -c
# => from now on every shell invocation by make will be python

# .ONESHELL tells make to execute a target recipe as a single SHELL call
# (by default make would execute a recipe line by line in separate SHELL calls
.ONESHELL:

# et voilà : the recipe can now be written in pure python :-)
all: foo	
	# use some make variables
	print "# target name is '$@', depends on '$^'"

	import os
	print os.listdir ("..")

foo:
	# use some make variables
	print "# target name is '$@', depends on '$^'"

	import sys
	print 'python version is %s.%s.%s' % (sys.version_info[0], sys.version_info[1], sys.version_info[2])

# tell make that these targets are NOT meant to be files/directories
.PHONY: all foo
  • What about PHP ? Yes, you can :
# suppress verbose make output
MAKEFLAGS += --silent

# make php the "shell" for make recipes
SHELL != sh -c "command -v php"
# tell php within make to take the following as php code
.SHELLFLAGS := -r
# => from now on every shell invocation by make will be php

# .ONESHELL tells make to execute a target recipe as a single SHELL call
# (by default make would execute a recipe line by line in separate SHELL calls
.ONESHELL:

# et voilà : the recipe can now be written in pure php :-)
all: foo	
	# use some make variables
	echo PHP_EOL . "# target name is '$@', depends on '$^'";

	print_r(glob('../*'));

foo:
	# to use variables, escape the $ with a second $ like this
	$$greetings= 'world';
	printf("Hello, %s!\n", $$greetings);

	echo 'PHP version is '. phpversion() . PHP_EOL;

# tell make that these targets are NOT meant to be files/directories
.PHONY: all foo
  • How about something special ... let's say we want use SQL (SQLite in this case) as make SHELL:
# suppress verbose make output
MAKEFLAGS += --silent

# sqlite database file name
DB := db.sqlite3

# make sqlite the "shell" for make recipes
SHELL = ./sqlite-shell.sh
# empty default shell commandline options to apply for shell invocation
.SHELLFLAGS = sqlite3 $(DB) 
# => from now on every shell invocation by make will be sqlite

# .ONESHELL tells make to execute a target recipe as a single SHELL call
# (by default make would execute a recipe line by line in separate SHELL calls
.ONESHELL:

# et voilà : recipes can now be written in pure sqlite syntax :-)
all: create insert import
	.print "# target name is '$@', depends on '$^'\n"
	.headers ON
	.mode columns
	SELECT * FROM foo;

import: create data.csv
	.print "# target name is '$@', depends on '$^'\n"
	.separator ;
	.import data.csv data
	INSERT INTO foo(name, familyname) 
		SELECT * FROM data;
	DROP TABLE IF EXISTS data;

insert: create
	INSERT INTO foo 
		(name, familyname) 
	VALUES 
		('Erika', 'Mustermann'),
		('Max', 'Mustermann');

	INSERT INTO foo (name, familyname) VALUES ('John', 'Doe');

create: clean
	CREATE TABLE 
		IF NOT EXISTS 
	foo 
		(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING, familyname STRING);

clean:
	DROP TABLE IF EXISTS foo;
	DROP TABLE IF EXISTS data;

# tell make that these targets are NOT meant to be files/directories
.PHONY: all create insert import clean import

Make is a tool following the classic Unix philosophy :

Make each program do one thing well.

Make does

  • resolve tasks (aka targets) and their dependencies

  • execute target recipe if a target is not build/out-of-date

The recipe target can be written in whatever scripting language you want.

It is configurable using make variables SHELL and .SHELLFLAGS

configuration

  • make takes the SHELL defined in your makefile for executing target recipes.

    make will replace all make variables used in a target recipe before delegating them to the declared SHELL

  • the .SHELLFLAGS directive can be used to customize the commandline parameters used for SHELL execution.

Examples

This repo provides some examples using make with pure

  • NodeJS
  • Python
  • SQLite3
  • PHP
  • and even make in make 🤯

If you have some more ideas to showcase - create a pull request !

Usage

The execute an example

  • cd into the desired example directory

  • execute make

Some more helpful make settings

  • .ONESHELL tells make to execute target recipe lines in a single SHELL invocation. That is what most people want !

    By default, every recipe line would be executed in a separate SHELL call.

  • MAKEFLAGS can be used to customize make behaviour. I personally prefer to set MAKEFLAGS+=--no-builtin-rules --no-builtin-variables disable stone age old buil-tin rules/variables of make dedicated to c/c++ development of the eighties.

    • --no-builtin-variables tells make to not create out of the box variables like $(CC), $(YACC) and stuff

    • --no-builtin-rules tells make to not create c/c++ standard rules for targeting yacc/c/cc++ files

  • .SECONDEXPANSION is pure magic. It makes make target dependencies dynamic. Read more here : https://www.cmcrossroads.com/article/making-directories-gnu-make

Opinionated tip: use .RECIPEPREFIX

make is a impressive but stone age old tool with a long history.

At the time of development (=> some decades ago 😅) their authors thought it's a good idea to use '\t' as recipe prefix.

The result is one of the most asked questions from make novices :

When executing my Makefile I get the following error

Makefile:xx: *** missing separator.  Stop.

What does that error mean ?

What is the recipe prefix ?

A Makefile consists mostly of targets and their recipes.

A target is usually a file/directory to be generated by the target recipe.

An example:

foo.exe: foo.c
  cc -o foo.exe foo.c

The recipe prefix is the prefix starting each recipe line.

By default it is '\t'.

And that's often a problem - because depending of the used editor / settings tabs may be converted to spaces.

If you use spaces instead of tabs you get this mysterious error message :

Makefile:xx: *** missing separator.  Stop.

Solution

A solution to workaround that potential issue is to change the recipe prefix to something different :

# tell make to use '>' as recipe prefix
.RECIPEPREFIX = >

foo.exe: foo.c
> cc -o foo.exe foo.c

Now we have a visible character as recipe prefix which is much less error prone.

The .RECIPEPREFIX directive is available since make version 4.0.

make-recipes-in-different-scripting-languages-demo's People

Contributors

lgersman avatar larsgersmann avatar wader avatar codeispoetry avatar

Stargazers

Javier Maestro avatar Emma Tebibyte avatar  avatar  avatar David McDonald avatar Ole Morud avatar Sandalots avatar Jay Caines-Gooby avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.