I got the idea for this because I was trying to write bzl files for work one day and realized how insane the bash/makefile nonsense syntax was. Of course, this also triggered my NIH syndrome and I decided to make my own declarative build system, where the rule definitions are written in plain python.
- python >= 3.8.0
- linux >= 5.0
- bash >= 4.4 (or knowledge of completion tools in other shells)
You first need to setup your project root - this is kind of like a mono-repo,
and impulse
will use this directory if it needs to fetch things from git
or look up build targets. This is generally something like ~/git
or
~/src
, but really any user-writable directory works fine. Lets call this
directory $impulse-root
.
cd {$impulse-root}
git clone https://github.com/tmathmeyer/impulse.git impulse
cd impulse && make && sudo make install
(installed to/usr/local/bin
)cd .. && impulse init
Source the file complete.sh
from your shell. You can copy this somewhere
else and source it from your ~/.bashrc
as well.
Default help menu:
~~$ impulse
usage: impulse [-h] {build,print_tree,run,docker,test,init,testsuite} ...
optional arguments:
-h, --help show this help message and exit
tasks:
{build,print_tree,run,docker,test,init,testsuite}
build Builds the given target.
print_tree Builds the given target.
run Builds a testcase and executes it.
docker Builds a docker container from the target.
test Builds a testcase and executes it.
init Initializes impulse in the current directory.
testsuite testsuite
You can get more help for each individual command by running
impulse {command} --help
. for example:
$ impulse init --help
usage: impulse init [-h]
optional arguments:
-h, --help show this help message and exit
or
$ impulse testsuite --help
usage: impulse testsuite [-h] [--project PROJECT] [--debug] [--notermcolor] [--fakeroot FAKEROOT]
optional arguments:
-h, --help show this help message and exit
--project PROJECT
--debug
--notermcolor
--fakeroot FAKEROOT
An impulse target is something that impulse can build. A target is either:
- relative -- the target name starts with a colon, ex:
":local_rule"
. relative rules are rules located in the SAMEBUILD
file as the current rule. - fixed -- the target name starts with TWO forward slashes, ex:
"//project/lib_example:lib_example"
. This rule references a build rule defined in$ROOT/project/lib_example/BUILD
and namedlib_example
.
Projects have one or more BUILD
files defined in them. These files use a subset of python syntax, however most builtin tools aren't present and can't be expected to work normally anyway. There is one core function availible -- load()
, which accepts comma seperated strings referencing build rule files. These references always exist as a fixed path starting with two slashes. The rule "//rules/core/C/build_defs.py"
references the file $ROOT/rules/core/C/build_defs.py
. Once loaded, all rules defined in loaded rule files may be used.
$ROOT/impulse/BUILD:
langs("Python")
py_library(
name = "debug",
srcs = [ "debug.py" ],
)
py_library(
name = "exceptions",
srcs = [ "exceptions.py" ],
)
py_library(
name = "job_printer",
srcs = [ "job_printer.py" ],
deps = [ ":debug" ],
)
$ROOT/example_unittests/BUILD:
langs("C")
c_header (
name = 'argparse_h',
srcs = [
"argparse.h"
],
)
cpp_test (
name = 'argparse_test2',
srcs = ['test.cpp'],
deps = [':argparse_h']
)
entries in deps can also be some internal builtin functions. Currently supported is git_repo
which takes url, repo, commit, target as arguments. A git repo dependency will attempt to checkout
the repository into $IMPULSE_ROOT, make a sub-working tree checked out at |commit| and then
build |target| as a dependency.
In this directory live python-defined build rules (see core_rules for some examples). For rules used in many different projects, these rules should live under $ROOT/rules/core/{lang}/build_defs.py
however these rules may be placed anywhere located under $ROOT
.
A canonical example of a rule file is the included py_library
rule:
def _add_files(target, srcs):
for src in srcs:
target.AddFile(os.path.join(target.GetPackageDirectory(), src))
for deplib in target.Dependencies(tags=Any('py_library')):
for f in deplib.IncludedFiles():
target.AddFile(f)
for deplib in target.Dependencies(tags=Any('data')):
for f in deplib.IncludedFiles():
target.AddFile(f)
d = os.path.dirname(f)
while d:
_write_file(target, os.path.join(d, '__init__.py'), '#generated')
d = os.path.dirname(d)
def _write_file(target, name, contents):
if not os.path.exists(name):
with open(name, 'w+') as f:
f.write(contents)
target.AddFile(name)
@using(_add_files, _write_file)
@buildrule
def py_library(target, name, srcs, **kwargs):
target.SetTags('py_library')
_add_files(target, srcs + kwargs.get('data', []))
# Create the init files
directory = target.GetPackageDirectory()
while directory:
_write_file(target, os.path.join(directory, '__init__.py'), '#generated')
directory = os.path.dirname(directory)
More to come.