Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time
MakePl - Very portable build system in Perl

  Don't make your users type more than one command to build.
  Don't make your users download and install some obscure build system.
  Program your build system in a real programming language.
  ./ && run

  Just clone this repo and stuff it in your project directory.
  This has no dependencies besides Perl >= 5.10 and its core modules.
  Alternatively, use this repo as a git submodule.

  To run a
    - ./ <options> <targets>
  To generate a bare-bones
    - perl <directory (defaults to .)>

[Quick Reference]
    - Put this at the end of the script, whether standalone or included.
  rule <targets>, <dependencies>, <routine>, <options>?
    - Defines a compilation rule like in a Makefile.
       - <targets> can be a single filename or an array ref of filenames.
       - <dependencies> can be a single filename, an array ref of filenames,
          or a subroutine which returns filenames.
       - The compile routine (AKA the recipe) is given two array refs as
          arguments containing the targets and the dependencies.
       - Here are the available options:
           fork => 1
             - This rule can be run in parallel (in a forked child process).
           gendir => 1
             - Automatically generate the directory structures of all
                targets of this rule.
           suggested => 0|1
             - This rule's targets will show up (or not) in the Suggested
                Targets list in the --help message.
  phony <targets>, <dependencies>?, <routine>?, <options>?
    - like rule, but the target(s) do not correspond to actual files
  subdep <targets>, <dependencies>
    - Establishes that anything that depends on the target(s) also depends
       on the given dependencies, e.g. because of an #include statement.
  subdep <routine>
    - Provides a way to automatically deduce subdeps.  The routine will be
       called with a filename and is expected to return some more filenames.
       See "" in this repo for a function that'll scan C/C++
       files for #include statements.
  defaults <targets...>
    - With no arguments, will build these targets.  The default
       default is to run the first rule given in the workflow.
    - Returns all files or phonies that are the target of any rule that has
       been declared so far.
  exists_or_target <filename>
    - Checks if the file exists or there's a target for it.
  include <filenames...>
    - Include the targets and rules in another  Relative filenames,
       working directories, etc. all do The Right Thing.  Cyclical includes
       are fine and even encouraged.
  config <filename>, <var>, <routine>?
    - Associate a config file with a data structure.  The file will immediately
       be read into the reference, and a rule will be established for later to
       write to the config file if the data has changed.  The optional routine
       will be called before the file is written if it is determined to need
  option <name>, <routine>, <description>?
    - Allows an option to be specified on the command line, like "--<name>" or
       "--<name>=<value>".  <routine> can be a code ref, which will be called
       with <value> when the option is provided, or a scalar ref, which will
       be set to <value> when the option is provided.  The optional one-line
       description will be printed in the "--help" message.  The description
       should start with "--<name>".
  chdir <directory>
    - Please use this instead of CORE::chdir or Cwd::chdir.
  run <command>
    - Like the builtin system(), but aborts the build process if the command
       gives a non-zero exit status.
  slurp <filename>, <length>?, <fail>?
    - Just returns the contents of the file as a string.  If <length> is given,
       it only reads the first <length> bytes.  Dies on failure unless <fail>
       is provided and false.
  splat <filename>, <string>, <fail>?
    - Writes the string to the filename, clobbering any previous contents.
       Dies on failure unless <fail> is false.
  slurp_utf8 <filename>, <length>?, <fail>?
  splat_utf8 <filename>, <string>, <fail>?
    - Like slurp and splat, but with UTF-8-encoded files.
  which <command>
    - Like 'which' on UNIX and 'where' on Windows.  Searches the PATH for the
       executable file providing the given command and returns it, or undef if
       it wasn't found.
  canonpath <filename>
    - Gets rid of extraneous ..s and things like that.  Also changes all \s
       into /s.
  rel2abs <filename>, <base>?
  abs2rel <filename>, <base>?
    - Convert between relative and absolute filenames, relative to cwd if
       <base> is not provided.

[Working Directories]
  MakePl tries to make working directories a lexical concept, so that things
  just work how you expect them to.
    - All relative filenames given to the API are relative to the current
       working directory.
    - When you import MakePl, the working directory is always set to the same
       directory that the is in, no matter where you invoked it from.
    - All recipes will be run in the same working directory that the rule was
       defined in.
    - Even if one includes another, each uses filenames
       relative to its own directory.  That is, if ./ says
       "modules/cake/lime" and modules/cake/ says "lime", they are
       talking about the same file.
    - Filenames given on the command line are relative to the current working
       directory of the command line, not of the
    - If you want to manually change directories, use the chdir provided by
       this module.  Using CORE::chdir will desync $ENV{PWD}.

  - It's best to die if something fails.  The autodie pragma is useful.
  - A basic knowledge of Perl is recommended.  Knowledge of make and Makefiles
     is not required, but knowledge of why people use them is.
  - If your program is large and has good modularity, do take advantage of the
     include functionality.  If two files mutually include one another,
     you can invoke either one to do stuff; the following commands would be
       $ ./ modules/cake/lime
       $ modules/cake/ modules/cake/lime
       $ cd modules/cake; ./ lime
     This does cause one counterintuitive effect.  If you want to use a phony
     target belonging to a in a different directory, you must prefix
     the phony target with that directory (as if it's actually a file).
       $ modules/cake/ clean  # oops, this cleans the whole project
       $ modules/cake/ modules/cake/clean  # just clean the cake
       $ ./ modules/cake/clean  # this works too
  - After the previous point it goes without saying, but you can invoke a from any directory, not just the one it's in, provided the
     is correctly formed.  To make sure your can be run anywhere, put
       use lib do {__FILE__ =~ /^(.*)[\/\\]/; ($1||'.')path};
       use MakePl;
     where path is nothing if is in the same directory, or
     something like .'/tools' if is in the directory 'tools'. If you
     used 'perl' to generate a, it'll have done this for you.
  - Because your build script is in a real programming language and not a DSL,
     you can actually do real abstraction.  Take a look at ""
     to see how.
  - This module is entirely symlink-ignorant.  If you use functions that reduce
     symlinks like Cwd::realpath, you may cause confusion.
  - This won't work if you have filenames with backslashes in them.
     I haven't decided whether this is a bug or a feature.

  - If you use MakePl::C, the build system will follow #include "file"
     directives in your C and C++ files, so if you update a header, all files
     that include it will be rebuilt.  It will not follow #include <file>


Portable programmable build system







No releases published


No packages published