Skip to main content

A tiny build automation tool.

Project description

Coxbuild is a tiny python-script-based build automation tool, an alternative to make, psake and so on.

Supported features:

  • Task
  • Dependency
  • Pre / Post condition
  • Lifecycle hooks
    • Setup / Teardown
    • Before / After
  • Event-based build as a long-run service

Extensions:

  • Python
  • Shell
  • Git
  • Node.js
  • .NET

Install

pip install coxbuild

Usage

Coxbuild build itself by itself, see here for details.

Write Schema (buildcox.py)

from coxbuild.schema import task, depend # this line can be omitted

@task()
def pre():
    print("pre task")

@depend(pre)
@task()
def default():
    pass

Run

coxbuild
    [-D <working directory = '.'>]
    [-f <file name = 'buildcox.py'>]
    [task names = 'default']

# Run default schema and default task
coxbuild
# equivalent to
coxbuild -D . -f buildcox.py default

# Run in other directory
coxbuild -D path/to/other

# Run using other file
coxbuild -f other.py

# Run specified task
coxbuild task1 task2

Schema Specification

Task

Use task decorator to define a (named) task.

@task()
def use_function_name_as_task_name(): pass

@task("custom-task-name")
async def use_custom_name():
    """task document"""
    pass

@task()
async def cost_time():
    print("cost time")
    await asyncio.sleep(0.5)

# default task
@task()
def default(): pass

Task can have parameters, see Before/After Hook section for details.

Do NOT use setup and teardown as task parameter name. These parameter names are used by coxbuild.

Builtin tasks:

Name Description
:list List all defined tasks
:serve Start event-based service

Dependency

Use depend decorator to define task dependency (you can use full name or instance of the task).

@task()
def t1(): pass

@task("t2")
def t2(): pass

@depend("t1", t2)
@task()
def default(): pass

Group

Use group decorator to add namespace to task names (prevent from name conflicting).

ns1task = group("ns1")
ns2task = group("ns2")

# task name: 'ns1:name'
@ns1task()
def name(): pass

# task name: 'ns2:name'
@ns2task()
def name(): pass

group can be nested.

nstask = group("ns1", group("sub"))

# task name: 'ns1:sub:name'
@nstask()
def name(): pass

Pre/Post Condition

Use precondition to decide whether to run the task, and use postcondition to check the task works well.

@precond(lambda: True)
@postcond(lambda: True)
@task()
def t(): pass

Before/After Hook

Use before hook to do something before task initializing, configure task arguments and decide whether to run the task. Use after hook to do something after task finishing, and check task result.

@before(task1)
def before_task1(context: TaskContext):
    print(context.task.name)
    context.args.extend([1, 2, 3])
    context.kwds.update(a=1, b=2)
    # return False to ignore this task

@after(task1)
def after_task1(context: TaskContext, result: TaskResult):
    pass

Use before and after decorator with NO arguments to hook pipeline events.

Use pipeline before/after hooks to configure and check all tasks globally.

@before()
def pipeline_before(context: TaskContext):
    if context.task.name != "default":
        context.args.extend(["a", "b", "c"])
        context.kwds.update(p="p")
    # return False to ignore this task

@after()
def pipeline_after(context: TaskContext, result: TaskResult):
    pass

Setup/Teardown

Use setup hook to do something before task body (in task execution scope). Use teardown hook to do something after task body (in task execution scope, even some exception raises in task body). These hooks have the same parameters as the task body.

@setup(task2)
def setup_task2(*args, **kwds):
    pass

@teardown(task1)
def teardown_task1(*args, **kwds):
    pass

Use setup and teardown decorator with NO arguments to hook pipeline events.

Use setup hook to do something before pipeline, and decide whether to run the pipeline. Use teardown hook to do something after pipeline, and check pipeline result.

@setup()
def setup_pipeline(context: PipelineContext):
    # return False to cancel pipeline
    pass

@teardown()
def teardown_pipeline(context: PipelineContext, result: PipelineResult):
    pass

Lifecycle Events

The execution scope in written in parentheses.

  • Pipeline Setup (Pipeline)
    • Task 1
      • Pipeline Before (Pipeline)
        • Task Before (Pipeline)
          • Task Precondition (Task)
            • Task Setup (Task)
              • Task Body (Task)
            • Task Teardown (Task)
          • Task Postcondition (Task)
        • Task After (Pipeline)
      • Pipeline After (Pipeline)
    • Task 2
      • ... (as same as Task 1)
  • Pipeline Teardown (Pipeline)

Go here to see how to hook these events and how they work.

# See how lifecycle events occur
coxbuild -D test/demo -f lifecycle.py

Event

You can schedule some build when event occurs.

Coxbuild provides some builtin events in coxbuild.events module, each event return an awaitable iterator, i.e. event generator.

The event handlers are running asynchronously, so use async function and asyncio.sleep() instead of sync time.sleep().

from coxbuild.events import repeat, onceevent, once
from coxbuild.events.datetime import attime

@onceevent
async def e():
    print(datetime.now())
    await asyncio.sleep(0.5)

@on(repeat(e, 2))
def do():
    print(datetime.now())
    print("done")

@on(once(attime(datetime.now() + timedelta(seconds=1))))
def do_pipeline_at_next_second():
    pipeline("task1", task2)


@on(delay(timedelta(seconds=1)))
async def async_handler():
    print("before")
    await asyncio.sleep(1)
    print("after")

Example for watching filesystem changes, see here.

To start the long-run service, use builtin task :serve.

coxbuild :serve

Library

Task

Use Task to do something and get result with running metadata (exception, duration, and so on).

task: Task

# execute task individually (no pipeline, without dependencies and registered hooks)

result = await task(
    *args, **kwds, 
    setup=setup_hook,
    teardown=teardown_hook)

Pipeline

Use Pipeline to run managed tasks with dependencies and hooks.

You can access the default pipeline by variable pipeline in schema.

pipeline: Pipeline

tasklist: list[Task|str] = [task1, "task2"]

# execute tasks and their dependencies

result = await pipeline(*tasklist)

Extensions

Python

import coxbuild.extensions.python.all

Shell

import coxbuild.extensions.shell

Node.js

import coxbuild.extensions.nodejs

.NET

import coxbuild.extensions.dotnet

Git

import coxbuild.extensions.git

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

coxbuild-0.0.4.tar.gz (19.7 kB view hashes)

Uploaded Source

Built Distribution

coxbuild-0.0.4-py3-none-any.whl (24.0 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page