Skip to content
/ anvl Public

A general-purpose immediate mode task execution tool

License

Notifications You must be signed in to change notification settings

k32/anvl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ANVL

ANVL is an alternative to tools like make and ansible. It’s a general-purpose parallel task execution tool scriptable in Erlang, that aims to be simple and flexible.

Overview

A build system is a program that automatically solves the following problems:

  • Build targets that have not been built
  • Re-build targets that are out-of-date
  • Do not rebuild targets that are up to date
  • Build targets that depend on each other in sequence
  • Build independent targets in parallel

Typical build systems solve this problem by introducing concepts of source, target and rule. Source and target are usually file names, and rule is a subroutine that takes source file and produces the target file. Most build systems have a standard way of checking whether the target has to be rebuilt, based on comparison of file mtime’s, hashes etc. Dependencies between the targets are encoded in the build recipe as a graph. This way of doing things works very well as long as build system only works with files, but falls short when it’s no longer the case.

ANVL solves the same problem using a different abstraction called condition. This abstraction is not tied to the file system or anything concrete, but it can be used to implement the equivalent logic, and more.

Additionally, tools like make mix several concepts together:

  • Dependency resolution
  • Checking for updates
  • Resource management (-j flag)

In contrast, ANVL presents these as independent, composable library primitives.

What is condition?

Understanding the ANVL approach to scheduling tasks may be a bit tricky. We’ll give a formal description first, and then illustrate it with concrete examples.

ANVL condition is an Erlang function that, in a very abstract sense, ensures that state of the system satisfies certain expectations. Conditions have the following properties:

  • Condition returns a boolean indicating whether it produced side effects that changed the system or not.
    • Condition returns false if the system state already satisfies the expectations. false means the system was not changed.
    • Otherwise, it can run a subroutine that transfers the system into the expected state. If it succeeds, condition returns boolean value true.
  • Condition can depend on preconditions
  • Conditions are memoized: ANVL guarantees that body of the condition is executed only once for each unique set of arguments.

Example 1: building C code

Let’s demonstrate that ANVL’s rather abstract approach is sufficient to replace a traditional build system. We’ll define a build rule for turning a .c file into .o file by running gcc.

%% anvl macros and imports are contained in this header:
-include_lib("anvl/include/anvl.hrl").

%% Use ?MEMO macro (short for "memoize") to define a condition that
%% takes names of source file (`Src') and object file (`Obj') as
%% arguments:
?MEMO(obj, Src, Obj,
      begin
        newer(Src, Obj) andalso
          case exec("gcc", ["-o", Obj, Src]) of
            0 -> true;
            _ -> ?UNSAT("Compilation of ~s failed", [Src])
          end
      end).

This definition can be translated from Erlang to English like this: condition obj(Src, Obj) is satisfied when the object file is newer than the source file or after compiling the source file with GCC. Return true if object files was changed or false otherwise. Throw an exception if GCC exits with non-zero code.

This simple example demonstrates separation of concepts: anvl don’t assume anything about the nature of conditions, it only concerns with whether they have produced side effects.

Example 2: preconditions

In this example we’ll demonstrate how conditions can be composed. We’ll link object files created in the previous example to create an executable “build/foo”.

?MEMO(executable_built,
      begin
        Executable = "build/foo",
        %% Find all c files in "src" directory:
        Sources = filelib:wildcard("src/*.c"),
        %% Derive names of object files using pattern substitution:
        SourcesAndObjs = patsubst("build/${basename}.o", Sources),
        {_, Objs} = lists:unzip(SourcesAndObjs),
        %% Precondition: all source files are compiled:
        precondition([obj(Src, Obj) || {Src, Obj} <- SourcesAndObjs]) or
          %% Also check if any of the object files is newer than the target:
          newer(Objs, Executable) andalso
          %% Run linker if any of the preconditions produced side
          %% effects or they are newer than the executable:
          case exec("gcc", ["-o", Executable | Objs]) of
            0 -> true;
            _ -> ?UNSAT("Linking failed")
          end
      end).

While this looks verbose at first, it opens up some possibilities. First of all, using a full-fledged programming language to define conditions gives access to proper data structures and algorithms, and allows to encapsulate build rules into reusable modules with well-defined interfaces. But more importantly, it allows to manage systems in ways that go beyond simply reading and writing files.

Invocation

Configuration

Writing conditions

Plugins

Builtin Plugins

ANVL contains builtin rules for cloning Git repositories and building Erlang applications and releases. These two features are necessary for building other plugins.

About

A general-purpose immediate mode task execution tool

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published