107 lines
3.2 KiB
Python
107 lines
3.2 KiB
Python
|
#!/usr/bin/env python3
|
||
|
import os
|
||
|
import re
|
||
|
import sys
|
||
|
from argparse import ArgumentParser
|
||
|
from pathlib import Path
|
||
|
from subprocess import Popen, PIPE, STDOUT
|
||
|
|
||
|
import yaml
|
||
|
|
||
|
BUILD_FILES = ['build.yaml', 'build.yml', 'bou.yaml', 'bou.yml']
|
||
|
ENV_VAR = re.compile(r'(\$([A-Za-z0-9_]+))')
|
||
|
|
||
|
|
||
|
def parse_args():
|
||
|
parser = ArgumentParser()
|
||
|
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')
|
||
|
return parser.parse_args()
|
||
|
|
||
|
|
||
|
def setup_env(env, base_path):
|
||
|
"""Set up the environment dictionary, resolving shell variables"""
|
||
|
if isinstance(env, list):
|
||
|
env = {pair.split('=')[0]: pair.split('=')[1] for pair in env}
|
||
|
env = dict(BASE_DIR=str(base_path), **env)
|
||
|
for key, value in env.items():
|
||
|
match = ENV_VAR.search(value)
|
||
|
if match:
|
||
|
value = value.replace(match.group(1), env[match.group(2)])
|
||
|
env[key] = value
|
||
|
return dict(**os.environ, **env)
|
||
|
|
||
|
|
||
|
def setup_step(config, step_name):
|
||
|
"""Prepare a step for usage"""
|
||
|
step = config['steps'][step_name]
|
||
|
step['environment'] = config.get('environment', []) + step.get('environment', [])
|
||
|
return step
|
||
|
|
||
|
|
||
|
def get_steps_for_stage(config, stage_name):
|
||
|
steps = []
|
||
|
for step_name in config['steps'].keys():
|
||
|
if config['steps'][step_name]['stage'] == stage_name:
|
||
|
steps.append(setup_step(config, step_name))
|
||
|
return steps
|
||
|
|
||
|
|
||
|
def run_step(step, base_path):
|
||
|
script = step['script']
|
||
|
if isinstance(script, list):
|
||
|
script = os.linesep.join(script)
|
||
|
env = setup_env(step['environment'], base_path)
|
||
|
proc = Popen([script], shell=True, stdout=PIPE, stderr=STDOUT, env=env)
|
||
|
for output in iter(lambda: proc.stdout.read(1), b''):
|
||
|
sys.stdout.buffer.write(output)
|
||
|
sys.stdout.buffer.flush()
|
||
|
return proc.returncode == 0
|
||
|
|
||
|
|
||
|
def run_stage(config, stage_name, base_path):
|
||
|
for step in get_steps_for_stage(config, stage_name):
|
||
|
result = run_step(step, base_path)
|
||
|
if not result:
|
||
|
break
|
||
|
|
||
|
|
||
|
def get_build_file():
|
||
|
"""Determine the local build file"""
|
||
|
base_path = Path.cwd()
|
||
|
for child in base_path.iterdir():
|
||
|
if child.name in BUILD_FILES:
|
||
|
return child.resolve()
|
||
|
return None
|
||
|
|
||
|
|
||
|
def main():
|
||
|
"""Run the build system"""
|
||
|
args = parse_args()
|
||
|
if args.build_file:
|
||
|
build_file = Path(args.build_file).resolve()
|
||
|
else:
|
||
|
build_file = get_build_file()
|
||
|
if not build_file:
|
||
|
print('Could not find a valid build file')
|
||
|
return 1
|
||
|
base_path = build_file.parent
|
||
|
config = yaml.full_load(build_file.open())
|
||
|
if args.stage_or_step:
|
||
|
if args.stage_or_step in config['stages']:
|
||
|
run_stage(config, args.stage_or_step, base_path)
|
||
|
elif args.stage_or_step in config['steps'].keys():
|
||
|
step = setup_step(config, args.stage_or_step)
|
||
|
run_step(config, step, base_path)
|
||
|
else:
|
||
|
print('"{stage}" is not a valid stage or step name'.format(stage=args.stage_or_step))
|
||
|
return 2
|
||
|
else:
|
||
|
for stage_name in config['stages']:
|
||
|
run_stage(config, stage_name, base_path)
|
||
|
return 0
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
sys.exit(main())
|