autocmake/update.py
2016-05-18 22:30:47 +02:00

299 lines
10 KiB
Python

#!/usr/bin/env python
import os
import sys
import datetime
import ast
import collections
__version__ = 'X.Y.Z'
AUTOCMAKE_GITHUB_URL = 'https://github.com/coderefinery/autocmake/raw/yaml/'
def print_progress_bar(text, done, total, width):
"""
Print progress bar.
"""
n = int(float(width) * float(done) / float(total))
sys.stdout.write("\r{0} [{1}{2}] ({3}/{4})".format(text, '#' * n, ' ' * (width - n), done, total))
sys.stdout.flush()
def align_options(options):
"""
Indents flags and aligns help texts.
"""
l = 0
for opt in options:
if len(opt[0]) > l:
l = len(opt[0])
s = []
for opt in options:
s.append(' {0}{1} {2}'.format(opt[0], ' ' * (l - len(opt[0])), opt[1]))
return '\n'.join(s)
def prepend_or_set(config, section, option, value, defaults):
"""
If option is already set, then value is prepended.
If option is not set, then it is created and set to value.
This is used to prepend options with values which come from the module documentation.
"""
if value:
if config.has_option(section, option):
value += '\n{0}'.format(config.get(section, option, 0, defaults))
config.set(section, option, value)
return config
def extract_list(config, section):
from collections import Iterable
l = []
if 'modules' in config:
for module in config['modules']:
for k, v in module.items():
for x in v:
if section in x:
if isinstance(x[section], Iterable) and not isinstance(x[section], str):
for y in x[section]:
l.append(y)
else:
l.append(x[section])
return l
def fetch_modules(config, relative_path):
"""
Assemble modules which will
be included in CMakeLists.txt.
"""
from collections import Iterable
download_directory = 'downloaded'
if not os.path.exists(download_directory):
os.makedirs(download_directory)
# here we get the list of sources to fetch
sources = extract_list(config, 'source')
modules = []
Module = collections.namedtuple('Module', 'path name')
warnings = []
if len(sources) > 0: # otherwise division by zero in print_progress_bar
print_progress_bar(text='- assembling modules:', done=0, total=len(sources), width=30)
for i, src in enumerate(sources):
module_name = os.path.basename(src)
if 'http' in src:
path = download_directory
name = 'autocmake_{0}'.format(module_name)
dst = os.path.join(download_directory, 'autocmake_{0}'.format(module_name))
fetch_url(src, dst)
file_name = dst
fetch_dst_directory = download_directory
else:
if os.path.exists(src):
path = os.path.dirname(src)
name = module_name
file_name = src
fetch_dst_directory = path
else:
sys.stderr.write("ERROR: {0} does not exist\n".format(src))
sys.exit(-1)
# FIXME
# if config.has_option(section, 'override'):
# defaults = ast.literal_eval(config.get(section, 'override'))
# else:
# defaults = {}
# FIXME
# # we infer config from the module documentation
# with open(file_name, 'r') as f:
# parsed_config = parse_cmake_module(f.read(), defaults)
# if parsed_config['warning']:
# warnings.append('WARNING from {0}: {1}'.format(module_name, parsed_config['warning']))
# config = prepend_or_set(config, section, 'docopt', parsed_config['docopt'], defaults)
# config = prepend_or_set(config, section, 'define', parsed_config['define'], defaults)
# config = prepend_or_set(config, section, 'export', parsed_config['export'], defaults)
# if parsed_config['fetch']:
# for src in parsed_config['fetch'].split('\n'):
# dst = os.path.join(fetch_dst_directory, os.path.basename(src))
# fetch_url(src, dst)
modules.append(Module(path=path, name=name))
print_progress_bar(
text='- assembling modules:',
done=(i + 1),
total=len(sources),
width=30
)
# FIXME
# if config.has_option(section, 'fetch'):
# # when we fetch directly from autocmake.yml
# # we download into downloaded/
# for src in config.get(section, 'fetch').split('\n'):
# dst = os.path.join(download_directory, os.path.basename(src))
# fetch_url(src, dst)
print('')
if warnings != []:
print('- {0}'.format('\n- '.join(warnings)))
return modules
def process_yaml(argv):
from autocmake.parse_yaml import parse_yaml
project_root = argv[1]
if not os.path.isdir(project_root):
sys.stderr.write("ERROR: {0} is not a directory\n".format(project_root))
sys.exit(-1)
# read config file
print('- parsing autocmake.yml')
with open('autocmake.yml', 'r') as stream:
config = parse_yaml(stream)
if 'name' in config:
project_name = config['name']
else:
sys.stderr.write("ERROR: you have to specify the project name in autocmake.yml\n")
sys.exit(-1)
if ' ' in project_name.rstrip():
sys.stderr.write("ERROR: project name contains a space\n")
sys.exit(-1)
if 'min_cmake_version' in config:
min_cmake_version = config['min_cmake_version']
else:
sys.stderr.write("ERROR: you have to specify min_cmake_version in autocmake.yml\n")
sys.exit(-1)
if 'setup_script' in config:
setup_script_name = config['setup_script']
else:
setup_script_name = 'setup'
# get relative path from setup script to this directory
relative_path = os.path.relpath(os.path.abspath('.'), project_root)
# fetch modules from the web or from relative paths
modules = fetch_modules(config, relative_path)
# create CMakeLists.txt
print('- generating CMakeLists.txt')
s = gen_cmakelists(project_name, min_cmake_version, relative_path, modules)
with open(os.path.join(project_root, 'CMakeLists.txt'), 'w') as f:
f.write('{0}\n'.format('\n'.join(s)))
# create setup script
print('- generating setup script')
s = gen_setup(config, relative_path, setup_script_name)
file_path = os.path.join(project_root, setup_script_name)
with open(file_path, 'w') as f:
f.write('{0}\n'.format('\n'.join(s)))
if sys.platform != 'win32':
make_executable(file_path)
def main(argv):
"""
Main function.
"""
if len(argv) != 2:
sys.stderr.write("\nYou can update a project in two steps.\n\n")
sys.stderr.write("Step 1: Update or create infrastructure files\n")
sys.stderr.write(" which will be needed to configure and build the project:\n")
sys.stderr.write(" $ {0} --self\n\n".format(argv[0]))
sys.stderr.write("Step 2: Create CMakeLists.txt and setup script in PROJECT_ROOT:\n")
sys.stderr.write(" $ {0} <PROJECT_ROOT>\n".format(argv[0]))
sys.stderr.write(" example:\n")
sys.stderr.write(" $ {0} ..\n".format(argv[0]))
sys.exit(-1)
if argv[1] in ['-h', '--help']:
print('Usage:')
for t, h in [('python update.py --self',
'Update this script and fetch or update infrastructure files under autocmake/.'),
('python update.py <builddir>',
'(Re)generate CMakeLists.txt and setup script and fetch or update CMake modules.'),
('python update.py (-h | --help)',
'Show this help text.')]:
print(' {0:30} {1}'.format(t, h))
sys.exit(0)
if argv[1] == '--self':
# update self
if not os.path.isfile('autocmake.yml'):
print('- fetching example autocmake.yml')
fetch_url(
src='{0}example/autocmake.yml'.format(AUTOCMAKE_GITHUB_URL),
dst='autocmake.yml'
)
if not os.path.isfile('.gitignore'):
print('- creating .gitignore')
with open('.gitignore', 'w') as f:
f.write('*.pyc\n')
for f in ['autocmake/configure.py',
'autocmake/external/docopt.py',
'autocmake/__init__.py',
'autocmake/interpolate.py',
'autocmake/parse_rst.py',
'autocmake/parse_yaml.py',
'update.py']:
print('- fetching {0}'.format(f))
fetch_url(
src='{0}{1}'.format(AUTOCMAKE_GITHUB_URL, f),
dst='{0}'.format(f)
)
sys.exit(0)
process_yaml(argv)
def make_executable(path):
# http://stackoverflow.com/a/30463972
mode = os.stat(path).st_mode
mode |= (mode & 0o444) >> 2 # copy R bits to X
os.chmod(path, mode)
def fetch_url(src, dst):
"""
Fetch file from URL src and save it to dst.
"""
# we do not use the nicer sys.version_info.major
# for compatibility with Python < 2.7
if sys.version_info[0] > 2:
import urllib.request
class URLopener(urllib.request.FancyURLopener):
def http_error_default(self, url, fp, errcode, errmsg, headers):
sys.stderr.write("ERROR: could not fetch {0}\n".format(url))
sys.exit(-1)
else:
import urllib
class URLopener(urllib.FancyURLopener):
def http_error_default(self, url, fp, errcode, errmsg, headers):
sys.stderr.write("ERROR: could not fetch {0}\n".format(url))
sys.exit(-1)
dirname = os.path.dirname(dst)
if dirname != '':
if not os.path.isdir(dirname):
os.makedirs(dirname)
opener = URLopener()
opener.retrieve(src, dst)
if __name__ == '__main__':
main(sys.argv)