code: def main(output_path=None):
      
      config_output_file = output_path or os.path.join(PROJECT_ROOT, '.circleci', 'config.yml')
      yaml_structure = generate_circle_output()
      with open(config_output_file, 'w') as fh:
          yaml_content = yaml.safe_dump(data=yaml_structure, default_flow_style=False)
          fh.write(
              f'#\n'
              f'# This file is autogenerated, do not modify manually. '
              f'See {author_file} for instructions.\n'
              f'#\n'
              f'{yaml_content}'
          )
  
      for path, content in generate_docker_targets().items():
          LOG.info('writing %s', path)
          with open(path, 'w') as fh:
              fh.write(content)
docs: Writes out new python build system
  
      This is needed because CircleCI does not support build matrices
      nor parameterisation of cache paths or other aspects of their
      config
  
      There's also the added bonus of validating the yaml as we go.
  
      Additionally, we template and write Docker and docker-compose files
      for multiple python versions, as Docker `FROM` statements are also
      un-templatable using environment variables or similar.
  
      The bulk of the config structure is parsed templated yaml, which
      seems the most succinct way of building deeply nested dictionaries and lists,
      and also cleanly maps to the appearance of config.yml before & after templating.
  
      The main job blocks (build, test, deploy) are expanded as the product of python versions
      and mbed cloud environments, before being recombined into the job listing.
  
      Jobs are chained into a CircleCI workflow using a graph
      (in which nodes are job identifiers, and edges describe the dependencies
      and any additional parameters)