Modify how the stages and steps work #1

Merged
raoul merged 1 commits from rework-stages into master 2021-06-05 06:48:19 +00:00
3 changed files with 103 additions and 15 deletions

View File

@ -20,28 +20,26 @@ Install bou with pip:
Running bou Running bou
----------- -----------
To run bou, simply call it: To run bou, simply run the command. The build file will be automatically detected.
.. code-block:: .. code-block::
$ bou $ bou
To specify a build configuration file, use the ``-f`` option: To specify a build configuration file, use the ``-f`` option.
.. code-block:: .. code-block::
$ bou -f /path/to/build.yaml $ bou -f /path/to/build.yaml
To specify a stage or a step to run, just add it to the command To specify a stage or a step to run, just add it to the command. Stages take priority over steps, so if you have a
stage and a step with the same name, the stage will be run.
.. code-block:: .. code-block::
$ bou build $ bou build
$ bou test $ bou test
.. note::
By default, all stages are run, and stages are run in the order in the build configuration file.
Task Configuration Task Configuration
------------------ ------------------
@ -90,6 +88,16 @@ variables are set.
- $PYTHON $SOURCE/setup.py build - $PYTHON $SOURCE/setup.py build
Stages and Steps
----------------
If no steps or stages are specified, by default bou will attempt to run the following, in order:
1. All of the stages in the ``stages`` section of the task configuration
2. If no stages are specified in the task config, all of the stages discovered in the steps
3. If no stages are found, all of the steps
Source Code Source Code
----------- -----------

94
bou.py
View File

@ -13,6 +13,11 @@ ENV_VAR = re.compile(r'(\$([A-Za-z0-9_]+))')
def parse_args(): def parse_args():
"""
Parse command line arguments, and return an object with all the arguments
:return: A namespace object with all the supplied arguments
"""
parser = ArgumentParser() parser = ArgumentParser()
parser.add_argument('-f', '--file', dest='build_file', help='Path to the build file') parser.add_argument('-f', '--file', dest='build_file', help='Path to the build file')
parser.add_argument('stage_or_step', nargs='?', default=None, help='Run a particular stage or step') parser.add_argument('stage_or_step', nargs='?', default=None, help='Run a particular stage or step')
@ -20,7 +25,14 @@ def parse_args():
def setup_env(env, base_path): def setup_env(env, base_path):
"""Set up the environment dictionary, resolving shell variables""" """
Set up the environment dictionary, resolving shell variables
:param env: The environment list or dictionary
:param base_path: The base path that the build runs in
:return: A merged dictionary of environment variables
"""
if isinstance(env, list): if isinstance(env, list):
env = {pair.split('=')[0]: pair.split('=')[1] for pair in env} env = {pair.split('=')[0]: pair.split('=')[1] for pair in env}
env = dict(BASE_DIR=str(base_path), **env) env = dict(BASE_DIR=str(base_path), **env)
@ -33,13 +45,28 @@ def setup_env(env, base_path):
def setup_step(config, step_name): def setup_step(config, step_name):
"""Prepare a step for usage""" """
Prepare a step for usage
:param config: The build configuration
:param step_name: The name of the step to prepare
:return: A step object
"""
step = config['steps'][step_name] step = config['steps'][step_name]
step['environment'] = config.get('environment', []) + step.get('environment', []) step['environment'] = config.get('environment', []) + step.get('environment', [])
return step return step
def get_steps_for_stage(config, stage_name): def get_steps_for_stage(config, stage_name):
"""
Get all the steps for a particular stage
:param config: The build configuration
:param stage_name: The name of the stage
:return: A list of step objects
"""
steps = [] steps = []
for step_name in config['steps'].keys(): for step_name in config['steps'].keys():
if config['steps'][step_name]['stage'] == stage_name: if config['steps'][step_name]['stage'] == stage_name:
@ -48,6 +75,14 @@ def get_steps_for_stage(config, stage_name):
def run_step(step, base_path): def run_step(step, base_path):
"""
Run a particular step
:param step: The step (as a dictionary)
:param base_path: The base path to run this in
:return: Return True if the step passed, or False if the step failed
"""
script = step['script'] script = step['script']
if isinstance(script, list): if isinstance(script, list):
script = os.linesep.join(script) script = os.linesep.join(script)
@ -60,14 +95,49 @@ def run_step(step, base_path):
def run_stage(config, stage_name, base_path): def run_stage(config, stage_name, base_path):
"""
Run all the steps in a particular stage
:param config: The build configuration
:param stage_name: The stage to run
:param base_path: The base path of the build
"""
for step in get_steps_for_stage(config, stage_name): for step in get_steps_for_stage(config, stage_name):
result = run_step(step, base_path) result = run_step(step, base_path)
if not result: if not result:
break break
def get_all_stages(config):
"""
Return all the stages available in the build configuration
:param config: The build configuration
:return: A list of stages
"""
stages = config.get('stages', [])
for step_name, step in config['steps'].items():
if step['stage'] not in stages:
stages.append(step['stage'])
return stages
def get_all_steps(config):
"""
Return all the steps available in the build configuration
:param config: The build configuration
:return: A list of steps
"""
return list(config.get('steps', {}).keys())
def get_build_file(): def get_build_file():
"""Determine the local build file""" """
Determine the local build file
"""
base_path = Path.cwd() base_path = Path.cwd()
for child in base_path.iterdir(): for child in base_path.iterdir():
if child.name in BUILD_FILES: if child.name in BUILD_FILES:
@ -76,7 +146,9 @@ def get_build_file():
def main(): def main():
"""Run the build system""" """
Run the build system
"""
args = parse_args() args = parse_args()
if args.build_file: if args.build_file:
build_file = Path(args.build_file).resolve() build_file = Path(args.build_file).resolve()
@ -87,18 +159,26 @@ def main():
return 1 return 1
base_path = build_file.parent base_path = build_file.parent
config = yaml.full_load(build_file.open()) config = yaml.full_load(build_file.open())
all_stages = get_all_stages(config)
all_steps = get_all_steps(config)
if args.stage_or_step: if args.stage_or_step:
if args.stage_or_step in config['stages']: if args.stage_or_step in all_stages:
run_stage(config, args.stage_or_step, base_path) run_stage(config, args.stage_or_step, base_path)
elif args.stage_or_step in config['steps'].keys(): elif args.stage_or_step in all_steps:
step = setup_step(config, args.stage_or_step) step = setup_step(config, args.stage_or_step)
run_step(config, step, base_path) run_step(config, step, base_path)
else: else:
print('"{stage}" is not a valid stage or step name'.format(stage=args.stage_or_step)) print('"{stage}" is not a valid stage or step name'.format(stage=args.stage_or_step))
return 2 return 2
else: else:
for stage_name in config['stages']: stages = config.get('stages', all_stages)
if stages:
for stage_name in stages:
run_stage(config, stage_name, base_path) run_stage(config, stage_name, base_path)
else:
for step_name in all_steps:
step = setup_step(config, step_name)
run_step(config, step, base_path)
return 0 return 0

View File

@ -1,6 +1,6 @@
[metadata] [metadata]
name = bou name = bou
version = 0.0.1 version = 0.0.2
author = Raoul Snyman author = Raoul Snyman
author_email = raoul@snyman.info author_email = raoul@snyman.info
description = Simple YAML-driven build or task runner description = Simple YAML-driven build or task runner