diff --git a/.gitignore b/.gitignore index 3bc3d39..acb6d6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.cache/ +venv/ *.pyc __pycache__/ @@ -5,7 +7,7 @@ __pycache__/ test/*/CMakeLists.txt test/*/build*/ test/*/cmake/update.py -test/*/cmake/lib/ +test/*/cmake/autocmake/ test/*/cmake/downloaded/ test/*/setup diff --git a/.travis.yml b/.travis.yml index ef3b798..9591dbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ matrix: include: - os: linux + python: 2.7 compiler: gcc env: SOURCES=ubuntu-toolchain-r-test addons: @@ -28,6 +29,30 @@ matrix: # boost - libboost-all-dev + - os: linux + python: 3.5 + compiler: gcc + env: SOURCES=ubuntu-toolchain-r-test + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + # compilers + - g++ + - gfortran + # math libraries + - libblas-dev + - liblapack-dev + - libatlas-base-dev + # mpi + - openmpi-bin + - libopenmpi-dev + # python library, development version + - libpython2.7 + # boost + - libboost-all-dev + - os: osx osx_image: xcode6.4 compiler: gcc @@ -40,20 +65,21 @@ install: pip install virtualenv elif [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then pip install --user virtualenv + curl https://cmake.org/files/v3.5/cmake-3.5.2-Linux-x86_64.tar.gz | tar xvz + export PATH=~/cmake-3.5.2-Linux-x86_64/bin:$PATH + export LD_LIBARY_PATH=~/cmake-3.5.2-Linux-x86_64/lib:$LD_LIBRARY_PATH fi - virtualenv venv - source venv/bin/activate - - pip install pep8 pytest + - pip install -r requirements.txt script: # pep8 tests - - pep8 --ignore=E501 update.py - - pep8 --ignore=E501 test/test.py - - pep8 --ignore=E501 lib/config.py + - pep8 --ignore E501 update.py + - pep8 --ignore E501,E265 autocmake # unit tests - - py.test -vv update.py + - py.test -vv autocmake/* - py.test -vv test/test.py - - py.test -vv lib/config.py notifications: email: false diff --git a/AUTHORS.md b/AUTHORS.md index b6e517b..5978cc0 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -7,7 +7,8 @@ - Miroslav Ilias (Windows, portability, earliest adopter testing and feedback, math library testing) - Ivan Hrasko (Windows, Appveyor testing) -- Dan Jonsson (idea for configuration file approach) +- Dan Jonsson (idea for configuration file approach which preceded YAML solution) - Roberto Di Remigio (design ideas and testing, OS X testing, Boost) -For a list of all the contributions see https://github.com/scisoft/autocmake/contributors. +For a list of all the contributions, +see https://github.com/coderefinery/autocmake/contributors. diff --git a/LICENSE b/LICENSE index 30c24f6..65d6739 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2016, Jonas Juselius and Radovan Bast +Copyright (c) 2015-2016 by Radovan Bast, Jonas Juselius, and contributors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -25,4 +25,3 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/README.md b/README.md index ff072f9..91920b0 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,29 @@ -[![Build Status](https://travis-ci.org/scisoft/autocmake.svg?branch=master)](https://travis-ci.org/scisoft/autocmake/builds) -[![Build Status](https://ci.appveyor.com/api/projects/status/github/scisoft/autocmake?branch=master&svg=true)](https://ci.appveyor.com/project/bast/autocmake/history) +[![Build Status](https://travis-ci.org/coderefinery/autocmake.svg?branch=master)](https://travis-ci.org/coderefinery/autocmake/builds) [![Documentation Status](https://readthedocs.org/projects/autocmake/badge/?version=latest)](http://autocmake.readthedocs.org) [![License](https://img.shields.io/badge/license-%20BSD--3-blue.svg)](../master/LICENSE) - -# Autocmake +![alt text](https://github.com/coderefinery/autocmake/raw/master/img/autocmake.png "Autocmake") A CMake plugin composer. Licensed under [BSD-3](../master/LICENSE). See http://autocmake.org. -### Projects using Autocmake +## Documentation -- [Numgrid](https://github.com/bast/numgrid/) -- [XCint](https://github.com/bast/xcint/) +- [Latest stable code](http://autocmake.readthedocs.io/en/stable-0.x/) (stable-0.x branch) +- [Bleeding edge code](http://autocmake.readthedocs.io/en/latest/) (master branch) + + +## Projects using Autocmake + +- [Numgrid](https://github.com/dftlibs/numgrid/) +- [XCint](https://github.com/dftlibs/xcint/) - [DIRAC](http://diracprogram.org) - [mathlib-tester](https://github.com/miroi/mathlibs-tester) - [Fortran Input Reader](https://github.com/miroi/fortran_input_reader) - [PCMSolver](https://github.com/PCMSolver/pcmsolver) +- GRASP: General-purpose Relativistic Atomic Structure Program +- [MRChem](https://github.com/MRChemSoft/mrchem) If you use Autocmake, please link to your project via a pull request. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 6b8f9a6..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,57 +0,0 @@ -# OS: 64-bit Windows Server 2012 R2 -# Compilers: 64-bit MinGw-w64 5.1.0 (downloaded during script execution) -# Python: 2.7, 64-bit -# "ps:" commands are executed in PowerShell - -# build version format: 1.0.1, 1.0.2, ... -version: 1.0.{build} - -# prepare environment -environment: -# set custom path (will be more extended later in build_script section) - path: C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Git\cmd;C:\Program Files\7-Zip;C:\Program Files (x86)\CMake\bin -# set MinGw-w64 (64-bit) version 5.1.0 download URL - url: http://kent.dl.sourceforge.net/project/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/5.1.0/threads-posix/seh/x86_64-5.1.0-release-posix-seh-rt_v4-rev0.7z - matrix: -# - python: C:\Python27;C:\Python27\Scripts - - python: C:\Python27-x64;C:\Python27-x64\Scripts -# - python: C:\Python34;C:\Python34\Scripts -# - python: C:\Python34-x64;C:\Python34-x64\Scripts - -build_script: -# add location of used Python to path -- set path=%path%;%python% - -# create dir for custom software and move there -- mkdir C:\software && cd C:\software - -# download and unpack MinGw-w64 compilers -- ps: wget $env:url -OutFile MinGW.7z -- 7z x MinGW.7z > NUL -# add compilers binary dir to path -- set path=%path%;C:\software\mingw64\bin - -# download and unpack the OpenBLAS library, integer*4 (or i32lp64) version -- ps: wget http://skylink.dl.sourceforge.net/project/openblas/v0.2.14/OpenBLAS-v0.2.14-Win64-int32.zip -OutFile OpenBLAS-v0.2.14-Win64-int32.zip -- 7z x OpenBLAS-v0.2.14-Win64-int32.zip > NUL -# add both OpenBLAS dynamic (libopenblas.dll) and static (libopenblas.a) library files dir to path -- set path=%path%;C:\software\OpenBLAS-v0.2.14-Win64-int32\bin;C:\software\OpenBLAS-v0.2.14-Win64-int32\lib;C:\software\OpenBLAS-v0.2.14-Win64-int32\include - -# download and upgrade pip -- ps: wget https://bootstrap.pypa.io/get-pip.py -OutFile get-pip.py -- python get-pip.py - -# install py.test -- pip install pytest - -# go back to project source dir -- cd %APPVEYOR_BUILD_FOLDER% - -test_script: -# show environment -- echo %path% -- py.test --version -# run tests -- py.test -vv update.py -- py.test -vv test/test.py -- py.test -vv lib/config.py diff --git a/autocmake/__init__.py b/autocmake/__init__.py new file mode 100644 index 0000000..db05cea --- /dev/null +++ b/autocmake/__init__.py @@ -0,0 +1 @@ +__version__ = '1.0.0-alpha-x' diff --git a/lib/config.py b/autocmake/configure.py similarity index 81% rename from lib/config.py rename to autocmake/configure.py index a635d40..74cf7cf 100644 --- a/lib/config.py +++ b/autocmake/configure.py @@ -1,12 +1,5 @@ - -# Copyright (c) 2015 by Radovan Bast and Jonas Juselius -# See https://github.com/scisoft/autocmake/blob/master/LICENSE - - -import subprocess import os import sys -import shutil def module_exists(module_name): @@ -23,10 +16,12 @@ def check_cmake_exists(cmake_command): Check whether CMake is installed. If not, print informative error message and quits. """ - p = subprocess.Popen('%s --version' % cmake_command, - shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) + from subprocess import Popen, PIPE + + p = Popen('{0} --version'.format(cmake_command), + shell=True, + stdin=PIPE, + stdout=PIPE) if not ('cmake version' in p.communicate()[0].decode('UTF-8')): sys.stderr.write(' This code is built using CMake\n\n') sys.stderr.write(' CMake is not found\n') @@ -46,7 +41,7 @@ def setup_build_path(build_path): fname = os.path.join(build_path, 'CMakeCache.txt') if os.path.exists(fname): sys.stderr.write('aborting setup\n') - sys.stderr.write('build directory %s which contains CMakeCache.txt already exists\n' % build_path) + sys.stderr.write('build directory {0} which contains CMakeCache.txt already exists\n'.format(build_path)) sys.stderr.write('remove the build directory and then rerun setup\n') sys.exit(1) else: @@ -74,7 +69,7 @@ def adapt_cmake_command_to_platform(cmake_command, platform): """ if platform == 'win32': pos = cmake_command.find('cmake') - s = ['set %s &&' % e for e in cmake_command[:pos].split()] + s = ['set {0} &&'.format(e) for e in cmake_command[:pos].split()] s.append(cmake_command[pos:]) return ' '.join(s) else: @@ -85,33 +80,40 @@ def run_cmake(command, build_path, default_build_path): """ Execute CMake command. """ + from subprocess import Popen, PIPE + from shutil import rmtree + topdir = os.getcwd() os.chdir(build_path) - p = subprocess.Popen(command, - shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + p = Popen(command, + shell=True, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE) stdout_coded, stderr_coded = p.communicate() stdout = stdout_coded.decode('UTF-8') stderr = stderr_coded.decode('UTF-8') + + # print cmake output to screen + print(stdout) + if stderr: sys.stderr.write(stderr) sys.exit(1) - # print cmake output to screen - print(stdout) + # write cmake output to file - f = open('cmake_output', 'w') - f.write(stdout) - f.close() + with open('cmake_output', 'w') as f: + f.write(stdout) + # change directory and return os.chdir(topdir) + if 'Configuring incomplete' in stdout: # configuration was not successful if (build_path == default_build_path): # remove build_path iff not set by the user # otherwise removal can be dangerous - shutil.rmtree(default_build_path) + rmtree(default_build_path) else: # configuration was successful save_setup_command(sys.argv, build_path) @@ -136,9 +138,8 @@ def save_setup_command(argv, build_path): Save setup command to a file. """ file_name = os.path.join(build_path, 'setup_command') - f = open(file_name, 'w') - f.write(' '.join(argv[:]) + '\n') - f.close() + with open(file_name, 'w') as f: + f.write(' '.join(argv[:]) + '\n') def configure(root_directory, build_path, cmake_command, only_show): @@ -158,7 +159,7 @@ def configure(root_directory, build_path, cmake_command, only_show): cmake_command = adapt_cmake_command_to_platform(cmake_command, sys.platform) - print('%s\n' % cmake_command) + print('{0}\n'.format(cmake_command)) if only_show: sys.exit(0) diff --git a/lib/docopt/LICENSE-MIT b/autocmake/external/LICENSE-MIT similarity index 100% rename from lib/docopt/LICENSE-MIT rename to autocmake/external/LICENSE-MIT diff --git a/autocmake/external/__init__.py b/autocmake/external/__init__.py new file mode 100644 index 0000000..6ff2a5b --- /dev/null +++ b/autocmake/external/__init__.py @@ -0,0 +1 @@ +# empty - this line is here to avoid problem when fetching empty files from web diff --git a/lib/docopt/docopt.py b/autocmake/external/docopt.py similarity index 75% rename from lib/docopt/docopt.py rename to autocmake/external/docopt.py index 2e43f7c..7b927e2 100644 --- a/lib/docopt/docopt.py +++ b/autocmake/external/docopt.py @@ -11,7 +11,7 @@ import re __all__ = ['docopt'] -__version__ = '0.6.1' +__version__ = '0.6.2' class DocoptLanguageError(Exception): @@ -47,18 +47,18 @@ class Pattern(object): if not hasattr(self, 'children'): return self uniq = list(set(self.flat())) if uniq is None else uniq - for i, child in enumerate(self.children): - if not hasattr(child, 'children'): - assert child in uniq - self.children[i] = uniq[uniq.index(child)] + for i, c in enumerate(self.children): + if not hasattr(c, 'children'): + assert c in uniq + self.children[i] = uniq[uniq.index(c)] else: - child.fix_identities(uniq) + c.fix_identities(uniq) def fix_repeating_arguments(self): """Fix elements that should accumulate/increment values.""" - either = [list(child.children) for child in transform(self).children] + either = [list(c.children) for c in self.either.children] for case in either: - for e in [child for child in case if case.count(child) > 1]: + for e in [c for c in case if case.count(c) > 1]: if type(e) is Argument or type(e) is Option and e.argcount: if e.value is None: e.value = [] @@ -68,40 +68,47 @@ class Pattern(object): e.value = 0 return self - -def transform(pattern): - """Expand pattern into an (almost) equivalent one, but with single Either. - - Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) - Quirks: [-a] => (-a), (-a...) => (-a -a) - - """ - result = [] - groups = [[pattern]] - while groups: - children = groups.pop(0) - parents = [Required, Optional, OptionsShortcut, Either, OneOrMore] - if any(t in map(type, children) for t in parents): - child = [c for c in children if type(c) in parents][0] - children.remove(child) - if type(child) is Either: - for c in child.children: + @property + def either(self): + """Transform pattern into an equivalent, with only top-level Either.""" + # Currently the pattern will not be equivalent, but more "narrow", + # although good enough to reason about list arguments. + ret = [] + groups = [[self]] + while groups: + children = groups.pop(0) + types = [type(c) for c in children] + if Either in types: + either = [c for c in children if type(c) is Either][0] + children.pop(children.index(either)) + for c in either.children: groups.append([c] + children) - elif type(child) is OneOrMore: - groups.append(child.children * 2 + children) + elif Required in types: + required = [c for c in children if type(c) is Required][0] + children.pop(children.index(required)) + groups.append(list(required.children) + children) + elif Optional in types: + optional = [c for c in children if type(c) is Optional][0] + children.pop(children.index(optional)) + groups.append(list(optional.children) + children) + elif AnyOptions in types: + optional = [c for c in children if type(c) is AnyOptions][0] + children.pop(children.index(optional)) + groups.append(list(optional.children) + children) + elif OneOrMore in types: + oneormore = [c for c in children if type(c) is OneOrMore][0] + children.pop(children.index(oneormore)) + groups.append(list(oneormore.children) * 2 + children) else: - groups.append(child.children + children) - else: - result.append(children) - return Either(*[Required(*e) for e in result]) + ret.append(children) + return Either(*[Required(*e) for e in ret]) -class LeafPattern(Pattern): - - """Leaf/terminal node of a pattern tree.""" +class ChildPattern(Pattern): def __init__(self, name, value=None): - self.name, self.value = name, value + self.name = name + self.value = value def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value) @@ -130,9 +137,7 @@ class LeafPattern(Pattern): return True, left_, collected + [match] -class BranchPattern(Pattern): - - """Branch/inner node of a pattern tree.""" +class ParentPattern(Pattern): def __init__(self, *children): self.children = list(children) @@ -144,15 +149,15 @@ class BranchPattern(Pattern): def flat(self, *types): if type(self) in types: return [self] - return sum([child.flat(*types) for child in self.children], []) + return sum([c.flat(*types) for c in self.children], []) -class Argument(LeafPattern): +class Argument(ChildPattern): def single_match(self, left): - for n, pattern in enumerate(left): - if type(pattern) is Argument: - return n, Argument(self.name, pattern.value) + for n, p in enumerate(left): + if type(p) is Argument: + return n, Argument(self.name, p.value) return None, None @classmethod @@ -165,23 +170,25 @@ class Argument(LeafPattern): class Command(Argument): def __init__(self, name, value=False): - self.name, self.value = name, value + self.name = name + self.value = value def single_match(self, left): - for n, pattern in enumerate(left): - if type(pattern) is Argument: - if pattern.value == self.name: + for n, p in enumerate(left): + if type(p) is Argument: + if p.value == self.name: return n, Command(self.name, True) else: break return None, None -class Option(LeafPattern): +class Option(ChildPattern): def __init__(self, short=None, long=None, argcount=0, value=False): assert argcount in (0, 1) - self.short, self.long, self.argcount = short, long, argcount + self.short, self.long = short, long + self.argcount, self.value = argcount, value self.value = None if value is False and argcount else value @classmethod @@ -202,9 +209,9 @@ class Option(LeafPattern): return class_(short, long, argcount, value) def single_match(self, left): - for n, pattern in enumerate(left): - if self.name == pattern.name: - return n, pattern + for n, p in enumerate(left): + if self.name == p.name: + return n, p return None, None @property @@ -216,34 +223,34 @@ class Option(LeafPattern): self.argcount, self.value) -class Required(BranchPattern): +class Required(ParentPattern): def match(self, left, collected=None): collected = [] if collected is None else collected l = left c = collected - for pattern in self.children: - matched, l, c = pattern.match(l, c) + for p in self.children: + matched, l, c = p.match(l, c) if not matched: return False, left, collected return True, l, c -class Optional(BranchPattern): +class Optional(ParentPattern): def match(self, left, collected=None): collected = [] if collected is None else collected - for pattern in self.children: - m, left, collected = pattern.match(left, collected) + for p in self.children: + m, left, collected = p.match(left, collected) return True, left, collected -class OptionsShortcut(Optional): +class AnyOptions(Optional): """Marker/placeholder for [options] shortcut.""" -class OneOrMore(BranchPattern): +class OneOrMore(ParentPattern): def match(self, left, collected=None): assert len(self.children) == 1 @@ -265,13 +272,13 @@ class OneOrMore(BranchPattern): return False, left, collected -class Either(BranchPattern): +class Either(ParentPattern): def match(self, left, collected=None): collected = [] if collected is None else collected outcomes = [] - for pattern in self.children: - matched, _, _ = outcome = pattern.match(left, collected) + for p in self.children: + matched, _, _ = outcome = p.match(left, collected) if matched: outcomes.append(outcome) if outcomes: @@ -279,18 +286,12 @@ class Either(BranchPattern): return False, left, collected -class Tokens(list): +class TokenStream(list): - def __init__(self, source, error=DocoptExit): + def __init__(self, source, error): self += source.split() if hasattr(source, 'split') else source self.error = error - @staticmethod - def from_pattern(source): - source = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source) - source = [s for s in re.split('\s+|(\S*<.*?>)', source) if s] - return Tokens(source, error=DocoptLanguageError) - def move(self): return self.pop(0) if len(self) else None @@ -323,7 +324,7 @@ def parse_long(tokens, options): raise tokens.error('%s must not have an argument' % o.long) else: if value is None: - if tokens.current() in [None, '--']: + if tokens.current() is None: raise tokens.error('%s requires argument' % o.long) value = tokens.move() if tokens.error is DocoptExit: @@ -354,7 +355,7 @@ def parse_shorts(tokens, options): value = None if o.argcount != 0: if left == '': - if tokens.current() in [None, '--']: + if tokens.current() is None: raise tokens.error('%s requires argument' % short) value = tokens.move() else: @@ -367,7 +368,8 @@ def parse_shorts(tokens, options): def parse_pattern(source, options): - tokens = Tokens.from_pattern(source) + tokens = TokenStream(re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source), + DocoptLanguageError) result = parse_expr(tokens, options) if tokens.current() is not None: raise tokens.error('unexpected ending: %r' % ' '.join(tokens)) @@ -414,7 +416,7 @@ def parse_atom(tokens, options): return [result] elif token == 'options': tokens.move() - return [OptionsShortcut()] + return [AnyOptions()] elif token.startswith('--') and token != '--': return parse_long(tokens, options) elif token.startswith('-') and token not in ('-', '--'): @@ -450,26 +452,27 @@ def parse_argv(tokens, options, options_first=False): def parse_defaults(doc): - defaults = [] - for s in parse_section('options:', doc): - # FIXME corner case "bla: options: --foo" - _, _, s = s.partition(':') # get rid of "options:" - split = re.split('\n[ \t]*(-\S+?)', '\n' + s)[1:] - split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] - options = [Option.parse(s) for s in split if s.startswith('-')] - defaults += options - return defaults + # in python < 2.7 you can't pass flags=re.MULTILINE + split = re.split('\n *(<\S+?>|-\S+?)', doc)[1:] + split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] + options = [Option.parse(s) for s in split if s.startswith('-')] + #arguments = [Argument.parse(s) for s in split if s.startswith('<')] + #return options, arguments + return options -def parse_section(name, source): - pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)', - re.IGNORECASE | re.MULTILINE) - return [s.strip() for s in pattern.findall(source)] +def printable_usage(doc): + # in python < 2.7 you can't pass flags=re.IGNORECASE + usage_split = re.split(r'([Uu][Ss][Aa][Gg][Ee]:)', doc) + if len(usage_split) < 3: + raise DocoptLanguageError('"usage:" (case-insensitive) not found.') + if len(usage_split) > 3: + raise DocoptLanguageError('More than one "usage:" (case-insensitive).') + return re.split(r'\n\s*\n', ''.join(usage_split[1:]))[0].strip() -def formal_usage(section): - _, _, section = section.partition(':') # drop "usage:" - pu = section.split() +def formal_usage(printable_usage): + pu = printable_usage.split()[1:] # split and drop "usage:" return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )' @@ -509,7 +512,7 @@ def docopt(doc, argv=None, help=True, version=None, options_first=False): If passed, the object will be printed if --version is in `argv`. options_first : bool (default: False) - Set to True to require options precede positional arguments, + Set to True to require options preceed positional arguments, i.e. to forbid options and positional arguments intermix. Returns @@ -523,15 +526,15 @@ def docopt(doc, argv=None, help=True, version=None, options_first=False): ------- >>> from docopt import docopt >>> doc = ''' - ... Usage: - ... my_program tcp [--timeout=] - ... my_program serial [--baud=] [--timeout=] - ... my_program (-h | --help | --version) - ... - ... Options: - ... -h, --help Show this screen and exit. - ... --baud= Baudrate [default: 9600] - ... ''' + Usage: + my_program tcp [--timeout=] + my_program serial [--baud=] [--timeout=] + my_program (-h | --help | --version) + + Options: + -h, --help Show this screen and exit. + --baud= Baudrate [default: 9600] + ''' >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30'] >>> docopt(doc, argv) {'--baud': '9600', @@ -550,15 +553,9 @@ def docopt(doc, argv=None, help=True, version=None, options_first=False): at https://github.com/docopt/docopt#readme """ - argv = sys.argv[1:] if argv is None else argv - - usage_sections = parse_section('usage:', doc) - if len(usage_sections) == 0: - raise DocoptLanguageError('"usage:" (case-insensitive) not found.') - if len(usage_sections) > 1: - raise DocoptLanguageError('More than one "usage:" (case-insensitive).') - DocoptExit.usage = usage_sections[0] - + if argv is None: + argv = sys.argv[1:] + DocoptExit.usage = printable_usage(doc) options = parse_defaults(doc) pattern = parse_pattern(formal_usage(DocoptExit.usage), options) # [default] syntax for argument is disabled @@ -566,13 +563,14 @@ def docopt(doc, argv=None, help=True, version=None, options_first=False): # same_name = [d for d in arguments if d.name == a.name] # if same_name: # a.value = same_name[0].value - argv = parse_argv(Tokens(argv), list(options), options_first) + argv = parse_argv(TokenStream(argv, DocoptExit), list(options), + options_first) pattern_options = set(pattern.flat(Option)) - for options_shortcut in pattern.flat(OptionsShortcut): + for ao in pattern.flat(AnyOptions): doc_options = parse_defaults(doc) - options_shortcut.children = list(set(doc_options) - pattern_options) + ao.children = list(set(doc_options) - pattern_options) #if any_options: - # options_shortcut.children += [Option(o.short, o.long, o.argcount) + # ao.children += [Option(o.short, o.long, o.argcount) # for o in argv if type(o) is Option] extras(help, version, argv, doc) matched, left, collected = pattern.fix().match(argv) diff --git a/autocmake/extract.py b/autocmake/extract.py new file mode 100644 index 0000000..4595f90 --- /dev/null +++ b/autocmake/extract.py @@ -0,0 +1,46 @@ +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 to_d(l): + """ + Converts list of dicts to dict. + """ + _d = {} + for x in l: + for k, v in x.items(): + _d[k] = v + return _d + + +def test_to_d(): + l = [{'a': 'b'}, {'c': 'd'}] + d = {'a': 'b', 'c': 'd'} + assert to_d(l) == d + + +def to_l(x): + """ + Converts list of dicts to dict. + """ + if isinstance(x, str): + return [x] + else: + return x + + +def test_to_l(): + assert to_l('foo') == ['foo'] + assert to_l(['foo', 'bar']) == ['foo', 'bar'] diff --git a/autocmake/generate.py b/autocmake/generate.py new file mode 100644 index 0000000..79fb1e8 --- /dev/null +++ b/autocmake/generate.py @@ -0,0 +1,176 @@ +def gen_cmake_command(config): + """ + Generate CMake command. + """ + from autocmake.extract import extract_list + + s = [] + s.append("\n\ndef gen_cmake_command(options, arguments):") + s.append(' """') + s.append(" Generate CMake command based on options and arguments.") + s.append(' """') + s.append(" command = []") + + for env in config['export']: + s.append(' command.append({0})'.format(env)) + + s.append(" command.append(arguments['--cmake-executable'])") + + for definition in config['define']: + s.append(' command.append({0})'.format(definition)) + + s.append(" command.append('-DCMAKE_BUILD_TYPE={0}'.format(arguments['--type']))") + s.append(" command.append('-G \"{0}\"'.format(arguments['--generator']))") + s.append(" if arguments['--cmake-options'] != \"''\":") + s.append(" command.append(arguments['--cmake-options'])") + s.append(" if arguments['--prefix']:") + s.append(" command.append('-DCMAKE_INSTALL_PREFIX=\"{0}\"'.format(arguments['--prefix']))") + + s.append("\n return ' '.join(command)") + + return '\n'.join(s) + + +def autogenerated_notice(): + from datetime import date + from . import __version__ + + current_year = date.today().year + year_range = '2015-{0}'.format(current_year) + + s = [] + s.append('# This file is autogenerated by Autocmake v{0} http://autocmake.org'.format(__version__)) + s.append('# Copyright (c) {0} by Radovan Bast, Jonas Juselius, and contributors.'.format(year_range)) + + return '\n'.join(s) + + +def gen_setup(config, relative_path, setup_script_name): + """ + Generate setup script. + """ + from autocmake.extract import extract_list + + s = [] + s.append('#!/usr/bin/env python') + s.append('\n{0}'.format(autogenerated_notice())) + s.append('\nimport os') + s.append('import sys') + + s.append("\nsys.path.insert(0, '{0}')".format(relative_path)) + + s.append('from autocmake import configure') + s.append('from autocmake.external import docopt') + + s.append('\n\noptions = """') + s.append('Usage:') + s.append(' ./{0} [options] []'.format(setup_script_name)) + s.append(' ./{0} (-h | --help)'.format(setup_script_name)) + s.append('\nOptions:') + + options = [] + + for opt in config['docopt']: + first = opt.split()[0].strip() + rest = ' '.join(opt.split()[1:]).strip() + options.append([first, rest]) + + options.append(['--type=', 'Set the CMake build type (debug, release, or relwithdeb) [default: release].']) + options.append(['--generator=', 'Set the CMake build system generator [default: Unix Makefiles].']) + options.append(['--show', 'Show CMake command and exit.']) + options.append(['--cmake-executable=', 'Set the CMake executable [default: cmake].']) + options.append(['--cmake-options=', "Define options to CMake [default: '']."]) + options.append(['--prefix=', 'Set the install path for make install.']) + options.append(['', 'Build directory.']) + options.append(['-h --help', 'Show this screen.']) + + s.append(align_options(options)) + + s.append('"""') + + s.append(gen_cmake_command(config)) + + s.append("\n") + s.append("# parse command line args") + s.append("try:") + s.append(" arguments = docopt.docopt(options, argv=None)") + s.append("except docopt.DocoptExit:") + s.append(r" sys.stderr.write('ERROR: bad input to {0}\n'.format(sys.argv[0]))") + s.append(" sys.stderr.write(options)") + s.append(" sys.exit(-1)") + s.append("\n") + s.append("# use extensions to validate/post-process args") + s.append("if configure.module_exists('extensions'):") + s.append(" import extensions") + s.append(" arguments = extensions.postprocess_args(sys.argv, arguments)") + s.append("\n") + s.append("root_directory = os.path.dirname(os.path.realpath(__file__))") + s.append("\n") + s.append("build_path = arguments['']") + s.append("\n") + s.append("# create cmake command") + s.append("cmake_command = '{0} {1}'.format(gen_cmake_command(options, arguments), root_directory)") + s.append("\n") + s.append("# run cmake") + s.append("configure.configure(root_directory, build_path, cmake_command, arguments['--show'])") + + return s + + +def gen_cmakelists(project_name, min_cmake_version, relative_path, modules): + """ + Generate CMakeLists.txt. + """ + import os + + s = [] + + s.append(autogenerated_notice()) + + s.append('\n# set minimum cmake version') + s.append('cmake_minimum_required(VERSION {0} FATAL_ERROR)'.format(min_cmake_version)) + + s.append('\n# project name') + s.append('project({0})'.format(project_name)) + + s.append('\n# do not rebuild if rules (compiler flags) change') + s.append('set(CMAKE_SKIP_RULE_DEPENDENCY TRUE)') + + s.append('\n# if CMAKE_BUILD_TYPE undefined, we set it to Debug') + s.append('if(NOT CMAKE_BUILD_TYPE)') + s.append(' set(CMAKE_BUILD_TYPE "Debug")') + s.append('endif()') + + if len(modules) > 0: + s.append('\n# directories which hold included cmake modules') + + module_paths = [module.path for module in modules] + module_paths.append('downloaded') # this is done to be able to find fetched modules when testing + module_paths = list(set(module_paths)) + module_paths.sort() # we do this to always get the same order and to minimize diffs + for directory in module_paths: + rel_cmake_module_path = os.path.join(relative_path, directory) + # on windows cmake corrects this so we have to make it wrong again + rel_cmake_module_path = rel_cmake_module_path.replace('\\', '/') + s.append('set(CMAKE_MODULE_PATH ${{CMAKE_MODULE_PATH}} ${{PROJECT_SOURCE_DIR}}/{0})'.format(rel_cmake_module_path)) + + if len(modules) > 0: + s.append('\n# included cmake modules') + for module in modules: + s.append('include({0})'.format(os.path.splitext(module.name)[0])) + + return s + + +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) diff --git a/autocmake/interpolate.py b/autocmake/interpolate.py new file mode 100644 index 0000000..0a95793 --- /dev/null +++ b/autocmake/interpolate.py @@ -0,0 +1,66 @@ +def replace(s, d): + from re import findall + + if isinstance(s, str): + for var in findall(r"%\(([A-Za-z0-9_]*)\)", s): + s = s.replace("%({0})".format(var), str(d[var])) + return s + + +def test_replace(): + assert replace('hey %(foo) ho %(bar)', + {'foo': 'hey', 'bar': 'ho'}) == 'hey hey ho ho' + + +def interpolate(d, d_map): + from collections import Mapping, Iterable + from copy import copy + + for k, v in d.items(): + if isinstance(v, Mapping): + d[k] = interpolate(d[k], d_map) + elif isinstance(v, Iterable) and not isinstance(v, str): + l = [] + for x in v: + if isinstance(x, Mapping): + l.append(interpolate(x, d_map)) + else: + l.append(replace(x, d_map)) + d[k] = copy(l) + else: + d[k] = replace(d[k], d_map) + return d + + +def test_interpolate(): + d = {'foo': 'hey', + 'bar': 'ho', + 'one': 'hey %(foo) ho %(bar)', + 'two': {'one': 'hey %(foo) ho %(bar)', + 'two': 'raboof'}} + d_interpolated = {'foo': 'hey', + 'bar': 'ho', + 'one': 'hey hey ho ho', + 'two': {'one': 'hey hey ho ho', + 'two': 'raboof'}} + assert interpolate(d, d) == d_interpolated + + +def test_interpolate_int(): + d = {'foo': 1, + 'bar': 2, + 'one': 'hey %(foo) ho %(bar)', + 'two': {'one': 'hey %(foo) ho %(bar)', + 'two': 'raboof'}} + d_interpolated = {'foo': 1, + 'bar': 2, + 'one': 'hey 1 ho 2', + 'two': {'one': 'hey 1 ho 2', + 'two': 'raboof'}} + assert interpolate(d, d) == d_interpolated + + +def test_interpolate_nested(): + d2 = {'modules': [{'fc': [{'source': '%(url_root)fc_optional.cmake'}]}], 'url_root': 'downloaded/downloaded_'} + d2_interpolated = {'modules': [{'fc': [{'source': 'downloaded/downloaded_fc_optional.cmake'}]}], 'url_root': 'downloaded/downloaded_'} + assert interpolate(d2, d2) == d2_interpolated diff --git a/autocmake/parse_rst.py b/autocmake/parse_rst.py new file mode 100644 index 0000000..a5fede4 --- /dev/null +++ b/autocmake/parse_rst.py @@ -0,0 +1,132 @@ +def parse_cmake_module(s_in, overrides={}): + import sys + from collections import Mapping, Iterable, defaultdict + from autocmake.parse_yaml import parse_yaml + + # we do not use the nicer sys.version_info.major + # for compatibility with Python < 2.7 + if sys.version_info[0] > 2: + from io import StringIO + else: + from StringIO import StringIO + + parsed_config = defaultdict(lambda: None) + + if 'autocmake.yml configuration::' not in s_in: + return parsed_config + + s_out = [] + is_rst_line = False + for line in s_in.split('\n'): + if is_rst_line: + if len(line) > 0: + if line[0] != '#': + is_rst_line = False + else: + is_rst_line = False + if is_rst_line: + s_out.append(line[2:]) + if '#.rst:' in line: + is_rst_line = True + + autocmake_entry = '\n'.join(s_out).split('autocmake.yml configuration::')[1] + autocmake_entry = autocmake_entry.replace('\n ', '\n') + + buf = StringIO(autocmake_entry) + config = parse_yaml(buf, overrides) + + for k, v in config.items(): + if isinstance(v, Iterable) and not isinstance(v, str): + parsed_config[k] = [x for x in config[k]] + else: + parsed_config[k] = [config[k]] + + return parsed_config + + +def test_parse_cmake_module(): + + s = r'''#.rst: +# +# Foo ... +# +# autocmake.yml configuration:: +# +# docopt: +# - "--cxx= C++ compiler [default: g++]." +# - "--extra-cxx-flags= Extra C++ compiler flags [default: '']." +# export: "'CXX={0}'.format(arguments['--cxx'])" +# define: "'-DEXTRA_CXXFLAGS=\"{0}\"'.format(arguments['--extra-cxx-flags'])" + +enable_language(CXX) + +if(NOT DEFINED CMAKE_C_COMPILER_ID) + message(FATAL_ERROR "CMAKE_C_COMPILER_ID variable is not defined!") +endif()''' + + parsed_config = parse_cmake_module(s) + assert parsed_config['docopt'] == ["--cxx= C++ compiler [default: g++].", "--extra-cxx-flags= Extra C++ compiler flags [default: '']."] + + +def test_parse_cmake_module_no_key(): + + s = '''#.rst: +# +# Foo ... +# +# Bar ... + +enable_language(CXX) + +if(NOT DEFINED CMAKE_C_COMPILER_ID) + message(FATAL_ERROR "CMAKE_C_COMPILER_ID variable is not defined!") +endif()''' + + parsed_config = parse_cmake_module(s) + assert parsed_config['docopt'] is None + + +def test_parse_cmake_module_interpolate(): + + s = r'''#.rst: +# +# Foo ... +# +# autocmake.yml configuration:: +# +# major: 1 +# minor: 2 +# patch: 3 +# a: v%(major) +# b: v%(minor) +# c: v%(patch) + +enable_language(CXX)''' + + parsed_config = parse_cmake_module(s) + assert parsed_config['a'] == ['v1'] + assert parsed_config['b'] == ['v2'] + assert parsed_config['c'] == ['v3'] + + +def test_parse_cmake_module_overrides(): + + s = r'''#.rst: +# +# Foo ... +# +# autocmake.yml configuration:: +# +# major: 1 +# minor: 2 +# patch: 3 +# a: v%(major) +# b: v%(minor) +# c: v%(patch) + +enable_language(CXX)''' + + parsed_config = parse_cmake_module(s, {'minor': 4}) + assert parsed_config['a'] == ['v1'] + assert parsed_config['b'] == ['v4'] + assert parsed_config['c'] == ['v3'] diff --git a/autocmake/parse_yaml.py b/autocmake/parse_yaml.py new file mode 100644 index 0000000..ded3656 --- /dev/null +++ b/autocmake/parse_yaml.py @@ -0,0 +1,30 @@ +def parse_yaml(stream, overrides={}): + import yaml + import sys + from autocmake.interpolate import interpolate + + try: + config = yaml.load(stream, yaml.SafeLoader) + except yaml.YAMLError as exc: + print(exc) + sys.exit(-1) + + for k in config: + if k in overrides: + config[k] = overrides[k] + + config = interpolate(config, config) + return config + + +def test_parse_yaml(): + text = """foo: bar +this: that +var: '%(foo)' +list: + - a: '%(foo)' + - b: '%(foo)' + - c: '%(foo)'""" + + assert parse_yaml(text) == {'foo': 'bar', 'this': 'that', 'var': 'bar', + 'list': [{'a': 'bar'}, {'b': 'bar'}, {'c': 'bar'}]} diff --git a/doc/conf.py b/doc/conf.py index fcdcf21..8eb64ba 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -14,15 +14,15 @@ import sys import os -import shlex - -sys.path.append(os.path.relpath(os.path.abspath('.'))) -import extract_rst # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('.')) +import extract_rst + +sys.path.insert(0, os.path.abspath('..')) +from autocmake import __version__ as _version # -- General configuration ------------------------------------------------ @@ -52,17 +52,17 @@ master_doc = 'index' # General information about the project. project = u'Autocmake' -copyright = u'2015, Radovan Bast and Jonas Juselius' -author = u'Radovan Bast and Jonas Juselius' +copyright = u'2015-2016, Radovan Bast, Jonas Juselius, and contributors' +author = u'Radovan Bast, Jonas Juselius, and contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -version = '0.0' # The full version, including alpha/beta/rc tags. -release = '0.0' +release = _version +# The short X.Y version. +version = '.'.join(release.split('.')[0:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -228,7 +228,7 @@ latex_elements = { # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'Autocmake.tex', u'Autocmake Documentation', - u'Radovan Bast and Jonas Juselius', 'manual'), + author, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of diff --git a/doc/contributors/doc.rst b/doc/contributors/doc.rst index 4e789fd..36d85cc 100644 --- a/doc/contributors/doc.rst +++ b/doc/contributors/doc.rst @@ -9,12 +9,12 @@ This documentation is refreshed upon each push to the central repository. The module reference documentation is generated from the module sources using ``#.rst:`` tags (compare for instance -http://autocmake.readthedocs.org/en/latest/module-reference.html#cc-cmake with -https://github.com/scisoft/autocmake/blob/master/modules/cc.cmake). +http://autocmake.readthedocs.io/en/latest/module-reference.html#cc-cmake with +https://github.com/coderefinery/autocmake/blob/master/modules/cc.cmake). -Please note that the lines following ``# autocmake.cfg configuration::`` are -understood by the ``update.py`` script to infer autocmake.cfg code from the +Please note that the lines following ``# autocmake.yml configuration::`` are +understood by the ``update.py`` script to infer autocmake.yml code from the documentation. As an example consider -https://github.com/scisoft/autocmake/blob/master/modules/cc.cmake#L20-L25. +https://github.com/coderefinery/autocmake/blob/master/modules/cc.cmake#L20-L26. Here, ``update.py`` will infer the configurations for ``docopt``, ``export``, and ``define``. diff --git a/doc/contributors/testing.rst b/doc/contributors/testing.rst index f4843ae..d320fca 100644 --- a/doc/contributors/testing.rst +++ b/doc/contributors/testing.rst @@ -5,12 +5,16 @@ Testing Autocmake You will need to install `pytest `__. -Check also the `Travis `__ +Check also the `Travis `__ build and test recipe for other requirements. -Your contributions and changes should preserve the test set. You can run locally all tests with:: +Your contributions and changes should preserve the test set and be PEP8 conform. +You can run locally all tests with:: - $ py.test test/test.py + $ pep8 --ignore E501 update.py + $ pep8 --ignore E501,E265 autocmake + $ py.test -vv autocmake/* + $ py.test -vv test/test.py You can also select individual tests, for example those with ``fc_blas`` string in the name:: @@ -19,8 +23,5 @@ You can also select individual tests, for example those with ``fc_blas`` string For more options, see the ``py.test`` flags. This test set is run upon each push to the central repository. -See also the `Travis `__ +See also the `Travis `__ build and test history. - -In addition we also test Autocmake on a Windows CI server: `Appveyor `__. -This is controlled by `Appveyor `__. diff --git a/doc/developers/bootstrap.rst b/doc/developers/bootstrap.rst index fe9b303..051535b 100644 --- a/doc/developers/bootstrap.rst +++ b/doc/developers/bootstrap.rst @@ -12,48 +12,48 @@ infrastructure files which will be needed to build the project:: $ mkdir cmake # does not have to be called "cmake" - take the name you prefer $ cd cmake - $ wget https://github.com/scisoft/autocmake/raw/master/update.py + $ wget https://github.com/coderefinery/autocmake/raw/master/update.py + $ virtualenv venv + $ source venv/bin/activate + $ pip install pyyaml $ python update.py --self On the MS Windows system, you can use the PowerShell wget-replacement:: - $ Invoke-WebRequest https://github.com/scisoft/autocmake/raw/master/update.py -OutFile update.py + $ Invoke-WebRequest https://github.com/coderefinery/autocmake/raw/master/update.py -OutFile update.py -This creates (or updates) the following files (an existing ``autocmake.cfg`` is +This creates (or updates) the following files (an existing ``autocmake.yml`` is not overwritten by the script):: cmake/ - autocmake.cfg # edit this file + autocmake.yml # edit this file update.py # no need to edit - lib/ - config.py # no need to edit - docopt/ - docopt.py # no need to edit + autocmake/ # no need to edit + ... # no need to edit -Note that all other listed files are overwritten (use version control). +Note that ``update.py`` and files under ``autocmake/`` +are overwritten (use version control). Generating the CMake infrastructure ----------------------------------- -Now customize ``autocmake.cfg`` to your needs -(see :ref:`autocmake_cfg`) +Now customize ``autocmake.yml`` to your needs +(see :ref:`autocmake_yml`) and then run the ``update.py`` script which creates ``CMakeLists.txt`` and a setup script in the target path:: $ python update.py .. -The script also downloads external CMake modules specified in ``autocmake.cfg`` to a directory +The script also downloads external CMake modules specified in ``autocmake.yml`` to a directory called ``downloaded/``:: cmake/ - autocmake.cfg # edit this file + autocmake.yml # edit this file update.py # no need to edit - lib/ - config.py # no need to edit - docopt/ - docopt.py # no need to edit - downloaded/ # contains CMake modules fetched from the web + autocmake/ # no need to edit + ... # no need to edit + downloaded/ # contains CMake modules fetched from the web Building the project diff --git a/doc/developers/configuration.rst b/doc/developers/configuration.rst index c45caa7..084a6b8 100644 --- a/doc/developers/configuration.rst +++ b/doc/developers/configuration.rst @@ -1,115 +1,101 @@ -.. _autocmake_cfg: +.. _autocmake_yml: -Configuring autocmake.cfg +Configuring autocmake.yml ========================= -The script ``autocmake.cfg`` is the high level place where you configure +The script ``autocmake.yml`` is the high level place where you configure your project. Here is an example. We will discuss it in detail further below:: - [project] name: numgrid + min_cmake_version: 2.8 - [fc] - source: https://github.com/scisoft/autocmake/raw/master/modules/fc.cmake + url_root: https://github.com/coderefinery/autocmake/raw/master/ - [cc] - source: https://github.com/scisoft/autocmake/raw/master/modules/cc.cmake - - [cxx] - source: https://github.com/scisoft/autocmake/raw/master/modules/cxx.cmake - - [flags] - source: https://github.com/scisoft/autocmake/raw/master/compilers/GNU.CXX.cmake - https://github.com/scisoft/autocmake/raw/master/compilers/Intel.CXX.cmake - - [rpath] - source: custom/rpath.cmake - - [definitions] - source: https://github.com/scisoft/autocmake/raw/master/modules/definitions.cmake - - [coverage] - source: https://github.com/scisoft/autocmake/raw/master/modules/code_coverage.cmake - - [safeguards] - source: https://github.com/scisoft/autocmake/raw/master/modules/safeguards.cmake - - [default_build_paths] - source: https://github.com/scisoft/autocmake/raw/master/modules/default_build_paths.cmake - - [src] - source: https://github.com/scisoft/autocmake/raw/master/modules/src.cmake - - [googletest] - source: https://github.com/scisoft/autocmake/raw/master/modules/googletest.cmake - - [custom] - source: custom/api.cmake - custom/test.cmake + modules: + - compilers: + - source: + - '%(url_root)modules/fc.cmake' + - '%(url_root)modules/cc.cmake' + - '%(url_root)modules/cxx.cmake' + - flags: + - source: + - '%(url_root)compilers/GNU.CXX.cmake' + - '%(url_root)compilers/Intel.CXX.cmake' + - 'compilers/Clang.CXX.cmake' + - plugins: + - source: + - '%(url_root)modules/ccache.cmake' + - 'custom/rpath.cmake' + - '%(url_root)modules/definitions.cmake' + - '%(url_root)modules/code_coverage.cmake' + - '%(url_root)modules/safeguards.cmake' + - '%(url_root)modules/default_build_paths.cmake' + - '%(url_root)modules/src.cmake' + - '%(url_root)modules/googletest.cmake' + - 'custom/api.cmake' + - 'custom/test.cmake' Name and order of sections -------------------------- -We see that the configuration file has sections. -The only section where the name matters is ``[project]``:: +First we define the project name (here "numgrid"). This section has to be there +and it has to be called "project" (but it does not have to be on top). - [project] - name: numgrid - min_cmake_version: 2.8 +We also have to define ``min_cmake_version``. -This is where we define the project name (here "numgrid"). This section has to -be there and it has to be called "project" (but it does not have to be on top). +The definition ``url_root`` is an interpolation (see :ref:`interpolation`) and +we use it to avoid retyping the same line over and over and to be able to +change it in one place. The explicit name "url_root" has no special meaning to +Autocmake and we could have chosen a different name. -The names of the other sections do not matter to Autocmake. You could name them like this:: +The section ``modules`` is a list of CMake plugins. The names of the list +elements (here "compilers", "flags", and "plugins") does not matter to +Autocmake. We could have called them "one", "two", and "whatever", but it would +not make much sense. It is better to choose names that are meaningful to you +and readers of your code. - [project] - name: numgrid - min_cmake_version: 2.8 - - [one] - source: https://github.com/scisoft/autocmake/raw/master/modules/fc.cmake - - [two] - source: https://github.com/scisoft/autocmake/raw/master/modules/cc.cmake - - [whatever] - source: https://github.com/scisoft/autocmake/raw/master/modules/cxx.cmake - -But it would not make much sense. It is better to choose names that are -meaningful to you. - -The order of the sections does matter and the sections will be processed in the -exact order as you specify them in ``autocmake.cfg``. +The order of the elements under ``modules`` does matter and the list will be +processed in the exact order as you specify them in ``autocmake.yml``. Minimal example --------------- -As a minimal example we take an ``autocmake.cfg`` which only contains:: +As a minimal example we take an ``autocmake.yml`` which only contains:: - [project] name: minime min_cmake_version: 2.8 +If you don't have the ``update.py`` script yet, you need to fetch it from the web:: + + $ wget https://github.com/coderefinery/autocmake/raw/master/update.py + First we make sure that the ``update.py`` script is up-to-date and that it has access to all libraries it needs:: $ python update.py --self - creating .gitignore - - fetching lib/config.py - - fetching lib/docopt/docopt.py + - fetching autocmake/configure.py + - fetching autocmake/__init__.py + - fetching autocmake/external/docopt.py + - fetching autocmake/external/__init__.py + - fetching autocmake/generate.py + - fetching autocmake/extract.py + - fetching autocmake/interpolate.py + - fetching autocmake/parse_rst.py + - fetching autocmake/parse_yaml.py - fetching update.py Good. Now we can generate ``CMakeLists.txt`` and the setup script:: - $ python update .. + $ python update.py .. - - parsing autocmake.cfg + - parsing autocmake.yml - generating CMakeLists.txt - generating setup script @@ -147,38 +133,38 @@ the following default options:: --show Show CMake command and exit. --cmake-executable= Set the CMake executable [default: cmake]. --cmake-options= Define options to CMake [default: '']. + --prefix= Set the install path for make install. Build directory. -h --help Show this screen. That's not too bad although currently we cannot do much with this since there are no sources listed, no targets, hence nothing to build. We need to flesh out -``CMakeLists.txt`` by extending ``autocmake.cfg`` -and this is what we will do in the next section. +``CMakeLists.txt`` by extending ``autocmake.yml`` and this is what we will do +in the next section. Assembling CMake plugins ------------------------ -The preferred way to extend ``CMakeLists.txt`` is by editing ``autocmake.cfg`` +The preferred way to extend ``CMakeLists.txt`` is by editing ``autocmake.yml`` and using the ``source`` option:: - [fc] - source: https://github.com/scisoft/autocmake/raw/master/modules/fc.cmake + - compilers: + - source: + - '%(url_root)modules/fc.cmake' + - '%(url_root)modules/cc.cmake' + - '%(url_root)modules/cxx.cmake' -This will download ``fc.cmake`` and include it in ``CMakeLists.txt``. +This will download ``fc.cmake``, ``cc.cmake``, and ``cxx.cmake``, and include +them in ``CMakeLists.txt``, in this order. You can also include local CMake modules, e.g.:: - [rpath] - source: custom/rpath.cmake + - source: + - 'custom/rpath.cmake' -It is also OK to include several modules at once:: - - [flags] - source: https://github.com/scisoft/autocmake/raw/master/compilers/GNU.CXX.cmake - https://github.com/scisoft/autocmake/raw/master/compilers/Intel.CXX.cmake - -The modules will be included in the same order as they appear in ``autocmake.cfg``. +It is also OK to include several modules at once as we have seen above. The +modules will be included in the same order as they appear in ``autocmake.yml``. Fetching files without including them in CMakeLists.txt @@ -187,9 +173,9 @@ Fetching files without including them in CMakeLists.txt Sometimes you want to fetch a file without including it in ``CMakeLists.txt``. This can be done with the ``fetch`` option. This is for instance done by the ``git_info.cmake`` module (see -https://github.com/scisoft/autocmake/blob/master/modules/git_info/git_info.cmake#L10-L11). +https://github.com/coderefinery/autocmake/blob/master/modules/git_info/git_info.cmake#L10-L13). -If ``fetch`` is invoked in ``autocmake.cfg``, then the fetched file is placed +If ``fetch`` is invoked in ``autocmake.yml``, then the fetched file is placed under ``downloaded/``. If ``fetch`` is invoked from within a CMake module documentation (see below), then the fetched file is placed into the same directory as the CMake module file which fetches it. @@ -199,11 +185,11 @@ Generating setup options ------------------------ Options for the setup script can be generated with the ``docopt`` -option. As an example, the following ``autocmake.cfg`` snippet will add a +option. As an example, the following ``autocmake.yml`` snippet will add a ``--something`` flag:: - [my_section] - docopt: --something Enable something [default: False]. + - my_section: + - docopt: "--something Enable something [default: False]." Setting CMake options @@ -213,9 +199,9 @@ Configure-time CMake options can be generated with the ``define`` option. Consider the following example which toggles the CMake variable ``ENABLE_SOMETHING``:: - [my_section] - docopt: --something Enable something [default: False]. - define: '-DENABLE_SOMETHING={0}'.format(arguments['--something']) + - my_section: + - docopt: "--something Enable something [default: False]." + - define: "'-DENABLE_SOMETHING={0}'.format(arguments['--enable-something'])" Setting environment variables @@ -224,33 +210,23 @@ Setting environment variables You can export environment variables at configure-time using the ``export`` option. Consider the following example:: - [cc] - docopt: --cc= C compiler [default: gcc]. - --extra-cc-flags= Extra C compiler flags [default: '']. - export: 'CC=%s' % arguments['--cc'] - define: '-DEXTRA_CFLAGS="%s"' % arguments['--extra-cc-flags'] + docopt: + - "--cc= C compiler [default: gcc]." + - "--extra-cc-flags= Extra C compiler flags [default: '']." + export: "'CC={0}'.format(arguments['--cc'])" + define: "'-DEXTRA_CFLAGS=\"{0}\"'.format(arguments['--extra-cc-flags'])" Auto-generating configurations from the documentation ----------------------------------------------------- -To avoid a boring re-typing of boilerplate ``autocmake.cfg`` code it is possible +To avoid a boring re-typing of boilerplate ``autocmake.yml`` code it is possible to auto-generate configurations from the documentation. This is the case for many core modules which come with own options once you have sourced them. -The lines following ``# autocmake.cfg configuration::`` are -understood by the ``update.py`` script to infer ``autocmake.cfg`` code from the +The lines following ``# autocmake.yml configuration::`` are +understood by the ``update.py`` script to infer ``autocmake.yml`` code from the documentation. As an example consider -https://github.com/scisoft/autocmake/blob/master/modules/cc.cmake#L20-L25. +https://github.com/coderefinery/autocmake/blob/master/modules/cc.cmake#L20-L26. Here, ``update.py`` will infer the configurations for ``docopt``, ``export``, and ``define``. - - -Overriding documented configurations ------------------------------------- - -Configurable documented defaults can be achieved using interpolations. See for -instance -https://github.com/scisoft/autocmake/blob/master/modules/boost/boost.cmake#L33-L36. -These can be modified within ``autocmake.cfg`` with a dictionary, e.g.: -https://github.com/scisoft/autocmake/blob/master/test/boost_libs/cmake/autocmake.cfg#L9 diff --git a/doc/developers/customizing-modules.rst b/doc/developers/customizing-modules.rst index 0f3066b..5122396 100644 --- a/doc/developers/customizing-modules.rst +++ b/doc/developers/customizing-modules.rst @@ -3,7 +3,7 @@ Customizing CMake modules ========================= -The ``update.py`` script assembles modules listed in ``autocmake.cfg`` into +The ``update.py`` script assembles modules listed in ``autocmake.yml`` into ``CMakeLists.txt``. Those that are fetched from the web are placed inside ``downloaded/``. You have several options to customize downloaded CMake modules: @@ -22,7 +22,7 @@ Adapt local copies of CMake modules A slightly better solution is to download the CMake modules that you wish you customize to a separate directory (e.g. ``custom/``) and source the customized CMake -modules in ``autocmake.cfg``. Alternatively you can serve your custom modules +modules in ``autocmake.yml``. Alternatively you can serve your custom modules from your own http server. @@ -34,19 +34,30 @@ the branched customized versions. This will make it easier for you to stay up-to-date with upstream development. -Overriding defaults +Overriding settings ------------------- -Some modules use interpolations to set defaults, see for instance -https://github.com/scisoft/autocmake/blob/master/modules/boost/boost.cmake#L33-L36. -These can be modified within ``autocmake.cfg``, e.g.: -https://github.com/scisoft/autocmake/blob/master/test/boost_libs/cmake/autocmake.cfg#L9 +If you source a module which contains directives such as +``define``, +``docopt``, +``export``, or +``fetch``, and you wish to modify those, +then you can override these settings in ``autocmake.yml``. +Settings in ``autocmake.yml`` take precedence over +settings imported by a sourced module. + +As an example consider the Boost module which defines and uses +interpolation variables ``major``, ``minor``, ``patch``, and ``components``, see +https://github.com/coderefinery/autocmake/blob/master/modules/boost/boost.cmake#L52-L55. + +The recommended way to customize these is in ``autocmake.yml``, e.g.: +https://github.com/coderefinery/autocmake/blob/master/test/boost_libs/cmake/autocmake.yml#L12-L17. Create own CMake modules ------------------------ -Of course you can also create own modules and source them in ``autocmake.cfg``. +Of course you can also create own modules and source them in ``autocmake.yml``. Contribute customizations to the "standard library" @@ -54,5 +65,5 @@ Contribute customizations to the "standard library" If you think that your customization will be useful for other users as well, you may consider contributing the changes directly to -https://github.com/scisoft/autocmake/. We very much encourage such +https://github.com/coderefinery/autocmake/. We very much encourage such contributions. But we also strive for generality and portability. diff --git a/doc/developers/example.rst b/doc/developers/example.rst index 2ef3f6d..4d83150 100644 --- a/doc/developers/example.rst +++ b/doc/developers/example.rst @@ -1,10 +1,10 @@ -Example Hello World project +Example hello world project =========================== This is a brief example for the busy and impatient programmer. For a longer -tour please see :ref:`autocmake_cfg`. +tour please see :ref:`autocmake_yml`. We start with a mixed Fortran-C project with the following sources:: @@ -23,40 +23,49 @@ there. This is not necessary for Autocmake but it is a generally good practice:: Now we create ``cmake/`` and fetch ``update.py``:: - $ mkdir cmake - $ cd cmake/ - $ wget https://raw.githubusercontent.com/scisoft/autocmake/master/update.py + $ mkdir cmake # does not have to be called "cmake" - take the name you prefer + $ cd cmake + $ wget https://github.com/coderefinery/autocmake/raw/master/update.py $ python update.py --self Now from top-level our file tree looks like this:: . |-- cmake - | |-- autocmake.cfg - | |-- lib - | | |-- config.py - | | `-- docopt - | | `-- docopt.py + | |-- autocmake + | | |-- __init__.py + | | |-- configure.py + | | |-- external + | | | |-- __init__.py + | | | `-- docopt.py + | | |-- extract.py + | | |-- generate.py + | | |-- interpolate.py + | | |-- parse_rst.py + | | `-- parse_yaml.py + | |-- autocmake.yml | `-- update.py `-- src |-- feature1.F90 |-- feature2.c `-- main.F90 -Now we edit ``cmake/autocmake.cfg`` to look like this:: +Now we edit ``cmake/autocmake.yml`` to look like this:: - [project] name: hello + min_cmake_version: 2.8 - [fc] - source: https://github.com/scisoft/autocmake/raw/master/modules/fc.cmake + url_root: https://github.com/coderefinery/autocmake/raw/master/ - [cc] - source: https://github.com/scisoft/autocmake/raw/master/modules/cc.cmake - - [src] - source: https://github.com/scisoft/autocmake/raw/master/modules/src.cmake + modules: + - compilers: + - source: + - '%(url_root)modules/fc.cmake' + - '%(url_root)modules/cc.cmake' + - src_support: + - source: + - '%(url_root)modules/src.cmake' What we have specified here is the project name and that we wish Fortran and C support. The ``src.cmake`` module tells CMake to include a ``src/CMakeLists.txt``. @@ -76,20 +85,32 @@ Now we have everything to generate ``CMakeLists.txt`` and a setup script:: $ cd cmake $ python update .. + - parsing autocmake.yml + - assembling modules: [##############################] (3/3) + - generating CMakeLists.txt + - generating setup script + And this is what we got:: . |-- CMakeLists.txt |-- cmake - | |-- autocmake.cfg + | |-- autocmake + | | |-- __init__.py + | | |-- configure.py + | | |-- external + | | | |-- __init__.py + | | | `-- docopt.py + | | |-- extract.py + | | |-- generate.py + | | |-- interpolate.py + | | |-- parse_rst.py + | | `-- parse_yaml.py + | |-- autocmake.yml | |-- downloaded | | |-- autocmake_cc.cmake | | |-- autocmake_fc.cmake | | `-- autocmake_src.cmake - | |-- lib - | | |-- config.py - | | `-- docopt - | | `-- docopt.py | `-- update.py |-- setup `-- src @@ -100,12 +121,12 @@ And this is what we got:: Now we are ready to build:: - $ python setup --fc=gfortran --cc=gcc + $ ./setup --fc=gfortran --cc=gcc - FC=gfortran CC=gcc cmake -DEXTRA_FCFLAGS="''" -DENABLE_FC_SUPPORT="ON" -DEXTRA_CFLAGS="''" -DCMAKE_BUILD_TYPE=release -G "Unix Makefiles" None /home/user/example + FC=gfortran CC=gcc cmake -DEXTRA_FCFLAGS="''" -DEXTRA_CFLAGS="''" -DCMAKE_BUILD_TYPE=release -G "Unix Makefiles" /home/user/hello - -- The C compiler identification is GNU 4.9.2 - -- The CXX compiler identification is GNU 4.9.2 + -- The C compiler identification is GNU 6.1.1 + -- The CXX compiler identification is GNU 6.1.1 -- Check for working C compiler: /usr/bin/gcc -- Check for working C compiler: /usr/bin/gcc -- works -- Detecting C compiler ABI info @@ -118,7 +139,7 @@ Now we are ready to build:: -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done - -- The Fortran compiler identification is GNU 4.9.2 + -- The Fortran compiler identification is GNU 6.1.1 -- Check for working Fortran compiler: /usr/bin/gfortran -- Check for working Fortran compiler: /usr/bin/gfortran -- works -- Detecting Fortran compiler ABI info @@ -127,14 +148,14 @@ Now we are ready to build:: -- Checking whether /usr/bin/gfortran supports Fortran 90 -- yes -- Configuring done -- Generating done - -- Build files have been written to: /home/user/example/build + -- Build files have been written to: /home/user/hello/build configure step is done now you need to compile the sources: $ cd build $ make - $ cd build/ + $ cd build $ make Scanning dependencies of target hello.x diff --git a/doc/developers/faq.rst b/doc/developers/faq.rst index 2c7da9a..0c8e4ef 100644 --- a/doc/developers/faq.rst +++ b/doc/developers/faq.rst @@ -4,6 +4,18 @@ FAQ for developers ================== +Which files do I need to edit? +------------------------------ + +Let us start with files which you normally never edit: ``CMakeLists.txt`` and +``setup`` - these are generated based on ``autocmake.yml``. Have a look in +``autocmake.yml`` and you will see all CMake files which are assembled into +``CMakeLists.txt``. You can edit those files. If you change the order of files +listed in ``autocmake.yml`` or if you add or remove CMake modules, then you +need to rerun the ``update.py`` script to refresh ``CMakeLists.txt`` and +``setup``. + + Autocmake does not do feature X - I really need feature X and a setup flag --X ------------------------------------------------------------------------------ @@ -19,18 +31,22 @@ naming):: cmake/custom/my_feature.cmake -And include this feature to the main ``CMakeLists.txt`` in ``autocmake.cfg``:: +And include this feature to the main ``CMakeLists.txt`` in ``autocmake.yml`` +under the ``modules`` section:: - [my_feature] - source: custom/my_feature.cmake + modules: + - my_feature: + - source: + - custom/my_feature.cmake Now your code is included in the main ``CMakeLists.txt``. Perhaps you also want a setup script flag to toggle the feature:: - [my_feature] - source: custom/my_feature.cmake - docopt: --my-feature Enable my feature [default: False]. - define: '-DENABLE_MY_FEATURE={0}'.format(arguments['--my-feature']) + - my_feature: + - docopt: "--enable-my-feature Enable my feature [default: False]." + - define: "'-DENABLE_MY_FEATURE={0}'.format(arguments['--enable-my-feature'])" + - source: + - custom/my_feature.cmake Implement your ideas, test them, and share them. If your module is portable, good code quality, and of general interest, you can suggest it to be part of @@ -43,17 +59,16 @@ How can I get a setup flag --X that toggles a CMake variable? The following will add a ``--something`` flag which toggles the CMake variable ``ENABLE_SOMETHING``:: - [my_section] - docopt: --something Enable something [default: False]. - define: '-DENABLE_SOMETHING={0}'.format(arguments['--something']) + - my_section: + - docopt: "--something Enable something [default: False]." + - define: "'-DENABLE_SOMETHING={0}'.format(arguments['--enable-something'])" Can I change the name of the setup script? ------------------------------------------ -Yes you can do that in ``autocmake.cfg``. Here we for instance change the name to "configure":: +Yes you can do that in ``autocmake.yml``. Here we for instance change the name to "configure":: - [project] name: myproject min_cmake_version: 2.8 setup_script: configure @@ -70,12 +85,13 @@ realized in Autocmake. Should I include and track also files generated by Autocmake in my repository? ------------------------------------------------------------------------------ -Yes, you probably want to do that. Autocmake generates a number of files which -in principle could be generated at configure- or build-time. However, you -probably do not want the users of your code to run any Autocmake scripts like -``update.py`` to generate the files they need to build the project. The users -of your code will run ``setup`` directly and expect everything to just work -(TM). +Yes, you probably want to do that. Autocmake downloads and generates a number +of files which in principle could be generated at configure- or build-time. +However, you probably do not want the users of your code to run any Autocmake +scripts like ``update.py`` to generate the files they need to build the +project. The users of your code will run ``setup`` directly and typically expect +everything to just work (TM). Note also that the users of your code will +not need to install the pyyaml package. The update.py script is overwriting my CMakeLists.txt and setup, isn't this bad? @@ -93,7 +109,7 @@ But I need to manually edit and customize CMakeLists.txt and setup every time I ----------------------------------------------------------------------------------------------- You typically never need to manually edit and customize ``CMakeLists.txt`` and -``setup`` directly. You can introduce customizations in ``autocmake.cfg`` +``setup`` directly. You can introduce customizations in ``autocmake.yml`` which get assembled into the front-end scripts. @@ -101,19 +117,21 @@ Where is a good place to list my sources and targets? ----------------------------------------------------- As mentioned above ``CMakeLists.txt`` is not a good place because this file is -generated from ``autocmake.cfg`` and your modifications would become +generated from ``autocmake.yml`` and your modifications would become overwritten at some point. A good standard is to organize your sources under ``src/`` and to list your sources and targets in ``src/CMakeLists.txt``. You -can include the latter in ``autocmake.cfg`` using:: +can include the latter in ``autocmake.yml`` using:: - [src] - source: https://github.com/scisoft/autocmake/raw/master/modules/src.cmake + - my_sources: + - source: + - https://github.com/coderefinery/autocmake/raw/master/modules/src.cmake -If you really don't like to do it this way, you can describe your sources and +If you really do not like to do it this way, you can describe your sources and targets in a custom module in a local file and include it like this:: - [my_sources] - source: custom/my_sources.cmake + - my_sources: + - source: + - custom/my_sources.cmake How can I do some more sophisticated validation of setup flags? @@ -121,7 +139,7 @@ How can I do some more sophisticated validation of setup flags? Sometimes you need to do more sophisticated validation and post-processing of setup flags. This can be done by placing a module called ``extensions.py`` -under ``cmake/`` (or wherever you have ``autocmake.cfg``). +under ``cmake/`` (or wherever you have ``autocmake.yml``). This file should implement a function with the following signature: .. code-block:: python diff --git a/doc/developers/interpolation.rst b/doc/developers/interpolation.rst new file mode 100644 index 0000000..1a2dff8 --- /dev/null +++ b/doc/developers/interpolation.rst @@ -0,0 +1,34 @@ + +.. _interpolation: + +Interpolation +============= + +In a custom extension to the YAML specification you can define and reuse +variables like this (observe how we interpolate ``url_root``, ``major``, +``minor``, ``patch``, and ``components`` in this example):: + + url_root: https://github.com/coderefinery/autocmake/raw/master/ + major: 1 + minor: 48 + patch: 0 + components: "" + fetch: + - "%(url_root)modules/boost/boost_unpack.cmake" + - "%(url_root)modules/boost/boost_userconfig.cmake" + - "%(url_root)modules/boost/boost_configure.cmake" + - "%(url_root)modules/boost/boost_build.cmake" + - "%(url_root)modules/boost/boost_install.cmake" + - "%(url_root)modules/boost/boost_headers.cmake" + - "%(url_root)modules/boost/boost_cleanup.cmake" + - "http://sourceforge.net/projects/boost/files/boost/%(major).%(minor).%(patch)/boost_%(major)_%(minor)_%(patch).zip" + docopt: + - "--boost-headers= Include directories for Boost [default: '']." + - "--boost-libraries= Library directories for Boost [default: '']." + - "--build-boost= Deactivate Boost detection and build on-the-fly [default: OFF]." + define: + - "'-DBOOST_INCLUDEDIR=\"{0}\"'.format(arguments['--boost-headers'])" + - "'-DBOOST_LIBRARYDIR=\"{0}\"'.format(arguments['--boost-libraries'])" + - "'-DFORCE_CUSTOM_BOOST={0}'.format(arguments['--build-boost'])" + - "'-DBOOST_MINIMUM_REQUIRED=\"%(major).%(minor).%(patch)\"'" + - "'-DBOOST_COMPONENTS_REQUIRED=\"%(components)\"'" diff --git a/doc/developers/updating-modules.rst b/doc/developers/updating-modules.rst index 7eeca52..834aeb7 100644 --- a/doc/developers/updating-modules.rst +++ b/doc/developers/updating-modules.rst @@ -20,11 +20,11 @@ Sometimes you may want to avoid using the latest version of a CMake module and rather fetch an older version, for example with the hash ``abcd123``. To achieve this, instead of:: - [foo] - source: https://github.com/scisoft/autocmake/raw/master/modules/foo.cmake + - my_feature: + - source: https://github.com/coderefinery/autocmake/raw/master/modules/foo.cmake pin the version to ``abcd123`` (you do not need to specify the full Git hash, a unique beginning will do):: - [foo] - source: https://github.com/scisoft/autocmake/raw/abcd123/modules/foo.cmake + - my_feature: + - source: https://github.com/coderefinery/autocmake/raw/abcd123/modules/foo.cmake diff --git a/doc/extract_rst.py b/doc/extract_rst.py index 9a788f7..436a168 100644 --- a/doc/extract_rst.py +++ b/doc/extract_rst.py @@ -73,7 +73,7 @@ def main(): if s_out != '': output.append('\n\n%s' % file_name) output.append('-'*len(file_name)) - output.append('`[Source code] `__' % full_file_name) + output.append('`[Source code] `__' % full_file_name) output.append(s_out) with open(os.path.join(THIS_DIR, 'module-reference.rst'), 'w') as f: diff --git a/doc/general/about.rst b/doc/general/about.rst index 95f141c..7a75ac2 100644 --- a/doc/general/about.rst +++ b/doc/general/about.rst @@ -7,9 +7,10 @@ Building libraries and executables from sources can be a complex task. Several solutions exist to this problem: GNU Makefiles is the traditional approach. Today, CMake is one of the trendier alternatives which can generate Makefiles starting from a file called ``CMakeLists.txt``. + Autocmake composes CMake building blocks into a CMake project and generates ``CMakeLists.txt`` as well as a setup script, which serves as a front-end to -``CMakeLists.txt``. All this is done based on a lightweight ``autocmake.cfg`` +``CMakeLists.txt``. All this is done based on a lightweight ``autocmake.yml`` file:: python update.py --self @@ -19,14 +20,14 @@ file:: | and updates the update.py script | | | v Developer maintaining - autocmake.cfg Autocmake + autocmake.yml Autocmake | | | python update.py .. | | | v v CMakeLists.txt (and setup front-end) | | - | python setup | + | python setup or ./setup | | which invokes CMake | v User of the code Makefile (or something else) | @@ -37,7 +38,7 @@ file:: Build/install/test targets Our main motivation to create Autocmake as a CMake framework library and -CMake module composer was to simplify CMake code transfer between codes. We got +CMake module composer is to simplify CMake code transfer between programs. We got tired of manually diffing and copy-pasting boiler-plate CMake code and watching it diverge while maintaining the CMake infrastructure in a growing number of scientific projects which typically have very similar requirements: diff --git a/doc/general/requirements.rst b/doc/general/requirements.rst index 5c81f63..453c3a1 100644 --- a/doc/general/requirements.rst +++ b/doc/general/requirements.rst @@ -3,13 +3,11 @@ Requirements and dependencies ============================= -Autocmake update and test scripts require Python 2.7 or higher. We try to also -support Python 3 (tested with Python 3.4). If the script fails with Python 3, -consider this a bug and please file an issue. +Autocmake update and test scripts require Python 2.7 or higher. We also +support Python 3 (we automatically test with 2.7 and 3.5). -The generated setup script runs with Python >= 2.7 (also tested with Python -3.4; probably also lower). +The generated setup script runs with Python >= 2.6 (also tested with Python +3.5). -.. todo:: - - Figure out lower Python version bound for setup. +To generate ``CMakeLists.txt`` and the ``setup`` script, Autocmake +requires the pyyaml package. diff --git a/doc/index.rst b/doc/index.rst index e5693c1..e28c430 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -27,6 +27,7 @@ For developers who use Autocmake developers/example.rst developers/customizing-modules.rst developers/updating-modules.rst + developers/interpolation.rst For users of projects which use Autocmake diff --git a/doc/users/faq.rst b/doc/users/faq.rst index b46e833..68d0822 100644 --- a/doc/users/faq.rst +++ b/doc/users/faq.rst @@ -47,4 +47,9 @@ How can I select CMake options via the setup script? Like this:: - $ python setup --cmake-options="-DTHIS_OPTION=ON -DTHAT_OPTION=OFF" + $ python setup --cmake-options='"-DTHIS_OPTION=ON -DTHAT_OPTION=OFF"' + +We use two sets of quotes because the shell swallows one set of them before +passing the arguments to Python. Yeah that's not nice, but nothing we can do +about it on the Python side. If you do not use two sets of quotes then the +setup command may end up incorrectly saved in `build/setup_command`. diff --git a/example/autocmake.cfg b/example/autocmake.cfg deleted file mode 100644 index cb9b3db..0000000 --- a/example/autocmake.cfg +++ /dev/null @@ -1,7 +0,0 @@ -# CMakeLists.txt and setup script will be generated from this file -# see: http://autocmake.readthedocs.org/en/latest/developers/configuration.html - -# uncomment the following three lines and set the project name -# [project] -# name: myproject -# min_cmake_version: 2.8 diff --git a/example/autocmake.yml b/example/autocmake.yml new file mode 100644 index 0000000..c57e39c --- /dev/null +++ b/example/autocmake.yml @@ -0,0 +1,7 @@ +# CMakeLists.txt and setup script will be generated from this file +# see: http://autocmake.readthedocs.io/en/latest/developers/configuration.html +# adapt the following lines and expand + +name: myproject + +min_cmake_version: 2.8 diff --git a/img/autocmake.png b/img/autocmake.png new file mode 100644 index 0000000..74b0247 Binary files /dev/null and b/img/autocmake.png differ diff --git a/img/autocmake.svg b/img/autocmake.svg new file mode 100644 index 0000000..511176a --- /dev/null +++ b/img/autocmake.svg @@ -0,0 +1,90 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + Autocmake + + + diff --git a/modules/boost/boost.cmake b/modules/boost/boost.cmake index a4e0b38..a33665e 100644 --- a/modules/boost/boost.cmake +++ b/modules/boost/boost.cmake @@ -5,11 +5,14 @@ # Autocmake update time. # Note that the build-up commands are not Windows-compatible! # -# Your autocmake.cfg should look like this:: +# Your autocmake.yml should look like this:: # -# [boost] -# override: {'major': 1, 'minor': 59, 'patch': 0, 'components': 'chrono;timer;system'} -# source: https://github.com/scisoft/autocmake/raw/master/modules/boost/boost.cmake +# - boost: +# - major: 1 +# - minor: 59 +# - patch: 0 +# - components: "chrono;timer;system" +# - source: "https://github.com/coderefinery/autocmake/raw/master/modules/boost/boost.cmake" # # Cross-dependencies between required components are not checked for. # For example, Boost.Timer depends on Boost.Chrono and Boost.System thus you @@ -43,39 +46,41 @@ # MPI_FOUND # BUILD_CUSTOM_BOOST # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# major=1 -# minor=48 -# patch=0 -# components='' -# fetch: https://github.com/scisoft/autocmake/raw/master/modules/boost/boost_unpack.cmake -# https://github.com/scisoft/autocmake/raw/master/modules/boost/boost_userconfig.cmake -# https://github.com/scisoft/autocmake/raw/master/modules/boost/boost_configure.cmake -# https://github.com/scisoft/autocmake/raw/master/modules/boost/boost_build.cmake -# https://github.com/scisoft/autocmake/raw/master/modules/boost/boost_install.cmake -# https://github.com/scisoft/autocmake/raw/master/modules/boost/boost_headers.cmake -# https://github.com/scisoft/autocmake/raw/master/modules/boost/boost_cleanup.cmake -# http://sourceforge.net/projects/boost/files/boost/%(major)s.%(minor)s.%(patch)s/boost_%(major)s_%(minor)s_%(patch)s.zip -# docopt: --boost-headers= Include directories for Boost [default: '']. -# --boost-libraries= Library directories for Boost [default: '']. -# --build-boost= Deactivate Boost detection and build on-the-fly [default: OFF]. -# define: '-DBOOST_INCLUDEDIR="{0}"'.format(arguments['--boost-headers']) -# '-DBOOST_LIBRARYDIR="{0}"'.format(arguments['--boost-libraries']) -# '-DFORCE_CUSTOM_BOOST="{0}"'.format(arguments['--build-boost']) -# '-DBOOST_MINIMUM_REQUIRED="%(major)s.%(minor)s.%(patch)s"' -# '-DBOOST_COMPONENTS_REQUIRED="%(components)s"' +# url_root: https://github.com/coderefinery/autocmake/raw/master/ +# major: 1 +# minor: 48 +# patch: 0 +# components: "" +# fetch: +# - "%(url_root)modules/boost/boost_unpack.cmake" +# - "%(url_root)modules/boost/boost_userconfig.cmake" +# - "%(url_root)modules/boost/boost_configure.cmake" +# - "%(url_root)modules/boost/boost_build.cmake" +# - "%(url_root)modules/boost/boost_install.cmake" +# - "%(url_root)modules/boost/boost_headers.cmake" +# - "%(url_root)modules/boost/boost_cleanup.cmake" +# - "http://sourceforge.net/projects/boost/files/boost/%(major).%(minor).%(patch)/boost_%(major)_%(minor)_%(patch).zip" +# docopt: +# - "--boost-headers= Include directories for Boost [default: '']." +# - "--boost-libraries= Library directories for Boost [default: '']." +# - "--build-boost= Deactivate Boost detection and build on-the-fly [default: OFF]." +# define: +# - "'-DBOOST_INCLUDEDIR=\"{0}\"'.format(arguments['--boost-headers'])" +# - "'-DBOOST_LIBRARYDIR=\"{0}\"'.format(arguments['--boost-libraries'])" +# - "'-DFORCE_CUSTOM_BOOST={0}'.format(arguments['--build-boost'])" +# - "'-DBOOST_MINIMUM_REQUIRED=\"%(major).%(minor).%(patch)\"'" +# - "'-DBOOST_COMPONENTS_REQUIRED=\"%(components)\"'" # FIXME Maintainer should be able to choose between fail (end-user has to satisfy dependency # on its own) and soft-fail (self-build of Boost) # Underscore-separated version number string(REGEX REPLACE "\\." "_" BOOSTVER ${BOOST_MINIMUM_REQUIRED}) + # Where the Boost .zip archive is located -# CMAKE_CURRENT_LIST_DIR is undefined in CMake 2.8.2 -# see https://public.kitware.com/Bug/print_bug_page.php?bug_id=11675 -# workaround: create CMAKE_CURRENT_LIST_DIR -get_filename_component(CMAKE_CURRENT_LIST_DIR ${CMAKE_CURRENT_LIST_FILE} PATH) -set(BOOST_ARCHIVE_LOCATION ${CMAKE_CURRENT_LIST_DIR}) +set(BOOST_ARCHIVE_LOCATION ${PROJECT_SOURCE_DIR}/cmake/downloaded) + set(BOOST_ARCHIVE boost_${BOOSTVER}.zip) # FIXME These are possibly not always good settings diff --git a/modules/cc.cmake b/modules/cc.cmake index 06e6e42..2975024 100644 --- a/modules/cc.cmake +++ b/modules/cc.cmake @@ -17,12 +17,13 @@ # # CFLAGS # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --cc= C compiler [default: gcc]. -# --extra-cc-flags= Extra C compiler flags [default: '']. -# export: 'CC={0}'.format(arguments['--cc']) -# define: '-DEXTRA_CFLAGS="{0}"'.format(arguments['--extra-cc-flags']) +# docopt: +# - "--cc= C compiler [default: gcc]." +# - "--extra-cc-flags= Extra C compiler flags [default: '']." +# export: "'CC={0}'.format(arguments['--cc'])" +# define: "'-DEXTRA_CFLAGS=\"{0}\"'.format(arguments['--extra-cc-flags'])" enable_language(C) diff --git a/modules/ccache.cmake b/modules/ccache.cmake index ced3027..3979fa2 100644 --- a/modules/ccache.cmake +++ b/modules/ccache.cmake @@ -10,10 +10,10 @@ # # CCACHE_FOUND # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --ccache= Toggle use of ccache [default: ON]. -# define: '-DUSE_CCACHE="{0}"'.format(arguments['--ccache']) +# docopt: "--ccache= Toggle use of ccache [default: ON]." +# define: "'-DUSE_CCACHE={0}'.format(arguments['--ccache'])" if(USE_CCACHE) find_program(CCACHE_FOUND ccache) diff --git a/modules/code_coverage.cmake b/modules/code_coverage.cmake index d617945..650f983 100644 --- a/modules/code_coverage.cmake +++ b/modules/code_coverage.cmake @@ -8,10 +8,10 @@ # CMAKE_C_FLAGS # CMAKE_CXX_FLAGS # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --coverage Enable code coverage [default: False]. -# define: '-DENABLE_CODE_COVERAGE=%s' % arguments['--coverage'] +# docopt: "--coverage Enable code coverage [default: False]." +# define: "'-DENABLE_CODE_COVERAGE={0}'.format(arguments['--coverage'])" option(ENABLE_CODE_COVERAGE "Enable code coverage" OFF) diff --git a/modules/cxx.cmake b/modules/cxx.cmake index d81ff3e..5ec6bf2 100644 --- a/modules/cxx.cmake +++ b/modules/cxx.cmake @@ -17,12 +17,13 @@ # # CXXFLAGS # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --cxx= C++ compiler [default: g++]. -# --extra-cxx-flags= Extra C++ compiler flags [default: '']. -# export: 'CXX={0}'.format(arguments['--cxx']) -# define: '-DEXTRA_CXXFLAGS="{0}"'.format(arguments['--extra-cxx-flags']) +# docopt: +# - "--cxx= C++ compiler [default: g++]." +# - "--extra-cxx-flags= Extra C++ compiler flags [default: '']." +# export: "'CXX={0}'.format(arguments['--cxx'])" +# define: "'-DEXTRA_CXXFLAGS=\"{0}\"'.format(arguments['--extra-cxx-flags'])" enable_language(CXX) diff --git a/modules/definitions.cmake b/modules/definitions.cmake index 4d4f75c..23e5fc2 100644 --- a/modules/definitions.cmake +++ b/modules/definitions.cmake @@ -7,10 +7,10 @@ # # PREPROCESSOR_DEFINITIONS # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --add-definitions= Add preprocesor definitions [default: '']. -# define: '-DPREPROCESSOR_DEFINITIONS="%s"' % arguments['--add-definitions'] +# docopt: "--add-definitions= Add preprocesor definitions [default: '']." +# define: "'-DPREPROCESSOR_DEFINITIONS=\"{0}\"'.format(arguments['--add-definitions'])" if(NOT "${PREPROCESSOR_DEFINITIONS}" STREQUAL "") add_definitions(${PREPROCESSOR_DEFINITIONS}) diff --git a/modules/fc.cmake b/modules/fc.cmake index 657a06b..5832571 100644 --- a/modules/fc.cmake +++ b/modules/fc.cmake @@ -21,12 +21,13 @@ # # FCFLAGS # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --fc= Fortran compiler [default: gfortran]. -# --extra-fc-flags= Extra Fortran compiler flags [default: '']. -# export: 'FC={0}'.format(arguments['--fc']) -# define: '-DEXTRA_FCFLAGS="{0}"'.format(arguments['--extra-fc-flags']) +# docopt: +# - "--fc= Fortran compiler [default: gfortran]." +# - "--extra-fc-flags= Extra Fortran compiler flags [default: '']." +# export: "'FC={0}'.format(arguments['--fc'])" +# define: "'-DEXTRA_FCFLAGS=\"{0}\"'.format(arguments['--extra-fc-flags'])" enable_language(Fortran) diff --git a/modules/fc_optional.cmake b/modules/fc_optional.cmake index aec0d7e..c7cf6c6 100644 --- a/modules/fc_optional.cmake +++ b/modules/fc_optional.cmake @@ -26,14 +26,16 @@ # # FCFLAGS # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --fc= Fortran compiler [default: gfortran]. -# --extra-fc-flags= Extra Fortran compiler flags [default: '']. -# --fc-support= Toggle Fortran language support (ON/OFF) [default: ON]. -# export: 'FC={0}'.format(arguments['--fc']) -# define: '-DEXTRA_FCFLAGS="{0}"'.format(arguments['--extra-fc-flags']) -# '-DENABLE_FC_SUPPORT="{0}"'.format(arguments['--fc-support']) +# docopt: +# - "--fc= Fortran compiler [default: gfortran]." +# - "--extra-fc-flags= Extra Fortran compiler flags [default: '']." +# - "--fc-support= Toggle Fortran language support (ON/OFF) [default: ON]." +# export: "'FC={0}'.format(arguments['--fc'])" +# define: +# - "'-DEXTRA_FCFLAGS=\"{0}\"'.format(arguments['--extra-fc-flags'])" +# - "'-DENABLE_FC_SUPPORT={0}'.format(arguments['--fc-support'])" option(ENABLE_FC_SUPPORT "Enable Fortran language support" ON) diff --git a/modules/git_info/git_info.cmake b/modules/git_info/git_info.cmake index 8d33c20..e8f85a4 100644 --- a/modules/git_info/git_info.cmake +++ b/modules/git_info/git_info.cmake @@ -65,6 +65,6 @@ function(generate_git_info_header _header_location _header_name) add_custom_target( git_info - ALL DEPENDS ${_header_location}/${_header_name} + ALL DEPENDS ${PROJECT_BINARY_DIR}/git_info.h ) endfunction() diff --git a/modules/googletest.cmake b/modules/googletest.cmake index edbb0db..353acaf 100644 --- a/modules/googletest.cmake +++ b/modules/googletest.cmake @@ -6,11 +6,11 @@ # # GOOGLETEST_ROOT # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# define: '-DGOOGLETEST_ROOT=external/googletest' +# define: "'-DGOOGLETEST_ROOT=external/googletest/googletest'" -set(GOOGLETEST_ROOT external/googletest CACHE STRING "Google Test source root") +set(GOOGLETEST_ROOT external/googletest/googletest CACHE STRING "Google Test source root") message(STATUS "GOOGLETEST_ROOT set to ${GOOGLETEST_ROOT}") diff --git a/modules/int64.cmake b/modules/int64.cmake index 944ea3b..b8847fa 100644 --- a/modules/int64.cmake +++ b/modules/int64.cmake @@ -6,10 +6,10 @@ # # CMAKE_Fortran_FLAGS # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --int64 Enable 64bit integers [default: False]. -# define: '-DENABLE_64BIT_INTEGERS=%s' % arguments['--int64'] +# docopt: "--int64 Enable 64bit integers [default: False]." +# define: "'-DENABLE_64BIT_INTEGERS={0}'.format(arguments['--int64'])" option(ENABLE_64BIT_INTEGERS "Enable 64-bit integers" OFF) diff --git a/modules/math/accelerate.cmake b/modules/math/accelerate.cmake index 2e6a277..9483e5a 100644 --- a/modules/math/accelerate.cmake +++ b/modules/math/accelerate.cmake @@ -8,12 +8,14 @@ # ACCELERATE_LIBRARIES - describe me, uncached # ACCELERATE_INCLUDE_DIR - describe me, uncached # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --accelerate Find and link to ACCELERATE [default: False]. -# define: '-DENABLE_ACCELERATE=%s' % arguments['--accelerate'] -# fetch: https://github.com/scisoft/autocmake/raw/master/modules/find/find_libraries.cmake -# https://github.com/scisoft/autocmake/raw/master/modules/find/find_include_files.cmake +# url_root: https://github.com/coderefinery/autocmake/raw/master/ +# docopt: "--accelerate Find and link to ACCELERATE [default: False]." +# define: "'-DENABLE_ACCELERATE={0}'.format(arguments['--accelerate'])" +# fetch: +# - "%(url_root)modules/find/find_libraries.cmake" +# - "%(url_root)modules/find/find_include_files.cmake" option(ENABLE_ACCELERATE "Find and link to ACCELERATE" OFF) diff --git a/modules/math/acml.cmake b/modules/math/acml.cmake index 01a375c..e5c230d 100644 --- a/modules/math/acml.cmake +++ b/modules/math/acml.cmake @@ -8,10 +8,10 @@ # ACML_LIBRARIES # ACML_INCLUDE_DIR # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --acml Find and link to ACML [default: False]. -# define: '-DENABLE_ACML=%s' % arguments['--acml'] +# docopt: "--acml Find and link to ACML [default: False]." +# define: "'-DENABLE_ACML={0}'.format(arguments['--acml'])" option(ENABLE_ACML "Find and link to ACML" OFF) diff --git a/modules/math/atlas.cmake b/modules/math/atlas.cmake index 64c5bab..23565fa 100644 --- a/modules/math/atlas.cmake +++ b/modules/math/atlas.cmake @@ -8,10 +8,10 @@ # ATLAS_LIBRARIES # ATLAS_INCLUDE_DIR # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --atlas Find and link to ATLAS [default: False]. -# define: '-DENABLE_ATLAS=%s' % arguments['--atlas'] +# docopt: "--atlas Find and link to ATLAS [default: False]." +# define: "'-DENABLE_ATLAS={0}'.format(arguments['--atlas'])" option(ENABLE_ATLAS "Find and link to ATLAS" OFF) diff --git a/modules/math/blas.cmake b/modules/math/blas.cmake index fdcda94..9014fb7 100644 --- a/modules/math/blas.cmake +++ b/modules/math/blas.cmake @@ -8,10 +8,10 @@ # BLAS_LIBRARIES # BLAS_INCLUDE_DIR # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --blas Find and link to BLAS [default: False]. -# define: '-DENABLE_BLAS=%s' % arguments['--blas'] +# docopt: "--blas Find and link to BLAS [default: False]." +# define: "'-DENABLE_BLAS={0}'.format(arguments['--blas'])" option(ENABLE_BLAS "Find and link to BLAS" OFF) diff --git a/modules/math/cblas.cmake b/modules/math/cblas.cmake index a5b57b0..86fa49f 100644 --- a/modules/math/cblas.cmake +++ b/modules/math/cblas.cmake @@ -8,12 +8,14 @@ # CBLAS_LIBRARIES - describe me, uncached # CBLAS_INCLUDE_DIR - describe me, uncached # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --cblas Find and link to CBLAS [default: False]. -# define: '-DENABLE_CBLAS=%s' % arguments['--cblas'] -# fetch: https://github.com/scisoft/autocmake/raw/master/modules/find/find_libraries.cmake -# https://github.com/scisoft/autocmake/raw/master/modules/find/find_include_files.cmake +# url_root: https://github.com/coderefinery/autocmake/raw/master/ +# docopt: "--cblas Find and link to CBLAS [default: False]." +# define: "'-DENABLE_CBLAS={0}'.format(arguments['--cblas'])" +# fetch: +# - "%(url_root)modules/find/find_libraries.cmake" +# - "%(url_root)modules/find/find_include_files.cmake" option(ENABLE_CBLAS "Find and link to CBLAS" OFF) diff --git a/modules/math/goto.cmake b/modules/math/goto.cmake index df9fb6d..9d5586e 100644 --- a/modules/math/goto.cmake +++ b/modules/math/goto.cmake @@ -8,10 +8,10 @@ # GOTO_LIBRARIES # GOTO_INCLUDE_DIR # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --goto Find and link to GOTO [default: False]. -# define: '-DENABLE_GOTO=%s' % arguments['--goto'] +# docopt: "--goto Find and link to GOTO [default: False]." +# define: "'-DENABLE_GOTO={0}'.format(arguments['--goto'])" option(ENABLE_GOTO "Find and link to GOTO" OFF) diff --git a/modules/math/lapack.cmake b/modules/math/lapack.cmake index e86e53e..7f3ac52 100644 --- a/modules/math/lapack.cmake +++ b/modules/math/lapack.cmake @@ -8,10 +8,10 @@ # LAPACK_LIBRARIES # LAPACK_INCLUDE_DIR # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --lapack Find and link to LAPACK [default: False]. -# define: '-DENABLE_LAPACK=%s' % arguments['--lapack'] +# docopt: "--lapack Find and link to LAPACK [default: False]." +# define: "'-DENABLE_LAPACK={0}'.format(arguments['--lapack'])" option(ENABLE_LAPACK "Find and link to LAPACK" OFF) diff --git a/modules/math/lapacke.cmake b/modules/math/lapacke.cmake index a05d5e2..8b89044 100644 --- a/modules/math/lapacke.cmake +++ b/modules/math/lapacke.cmake @@ -8,12 +8,14 @@ # LAPACKE_LIBRARIES - describe me, uncached # LAPACKE_INCLUDE_DIR - describe me, uncached # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --lapacke Find and link to LAPACKE [default: False]. -# define: '-DENABLE_LAPACKE=%s' % arguments['--lapacke'] -# fetch: https://github.com/scisoft/autocmake/raw/master/modules/find/find_libraries.cmake -# https://github.com/scisoft/autocmake/raw/master/modules/find/find_include_files.cmake +# url_root: https://github.com/coderefinery/autocmake/raw/master/ +# docopt: "--lapacke Find and link to LAPACKE [default: False]." +# define: "'-DENABLE_LAPACKE={0}'.format(arguments['--lapacke'])" +# fetch: +# - "%(url_root)modules/find/find_libraries.cmake" +# - "%(url_root)modules/find/find_include_files.cmake" option(ENABLE_LAPACKE "Find and link to LAPACKE" OFF) diff --git a/modules/math_libs.cmake b/modules/math_libs.cmake index e3d3bf2..5040fa8 100644 --- a/modules/math_libs.cmake +++ b/modules/math_libs.cmake @@ -37,18 +37,20 @@ # MKL_ROOT # MKLROOT # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --blas= Detect and link BLAS library (auto or off) [default: auto]. -# --lapack= Detect and link LAPACK library (auto or off) [default: auto]. -# --mkl= Pass MKL flag to the Intel compiler and linker and skip BLAS/LAPACK detection (sequential, parallel, cluster, or off) [default: off]. -# define: '-DENABLE_BLAS=%s' % arguments['--blas'] -# '-DENABLE_LAPACK=%s' % arguments['--lapack'] -# '-DMKL_FLAG=%s' % arguments['--mkl'] -# '-DMATH_LIB_SEARCH_ORDER="MKL;ESSL;OPENBLAS;ATLAS;ACML;SYSTEM_NATIVE"' -# '-DBLAS_LANG=Fortran' -# '-DLAPACK_LANG=Fortran' -# warning: 'This module is deprecated and will be removed in future versions' +# docopt: +# - "--blas= Detect and link BLAS library (auto or off) [default: auto]." +# - "--lapack= Detect and link LAPACK library (auto or off) [default: auto]." +# - "--mkl= Pass MKL flag to the Intel compiler and linker and skip BLAS/LAPACK detection (sequential, parallel, cluster, or off) [default: off]." +# define: +# - "'-DENABLE_BLAS={0}'.format(arguments['--blas'])" +# - "'-DENABLE_LAPACK={0}'.format(arguments['--lapack'])" +# - "'-DMKL_FLAG={0}'.format(arguments['--mkl'])" +# - "'-DMATH_LIB_SEARCH_ORDER=\"MKL;ESSL;OPENBLAS;ATLAS;ACML;SYSTEM_NATIVE\"'" +# - "'-DBLAS_LANG=Fortran'" +# - "'-DLAPACK_LANG=Fortran'" +# warning: "the math_libs.cmake module is deprecated and will be removed in future versions" #------------------------------------------------------------------------------- # ENABLE_STATIC_LINKING @@ -506,7 +508,7 @@ if (ENABLE_STATIC_LINKING) BLAS_TYPE MATCHES SYSTEM_NATIVE OR BLAS_TYPE MATCHES OPENBLAS) #cc_blas_static with ATLAS on travis-ci needs -lm - set(MATH_LIBS ${MATH_LIBS} -Wl,--whole-archive -lpthread -Wl,--no-whole-archive -lm) + set(MATH_LIBS ${MATH_LIBS} -Wl,--whole-archive -lpthread -ldl -Wl,--no-whole-archive -lm) endif() if (LAPACK_TYPE MATCHES MKL OR BLAS_TYPE MATCHES MKL) diff --git a/modules/mpi.cmake b/modules/mpi.cmake index 7f73eac..418975e 100644 --- a/modules/mpi.cmake +++ b/modules/mpi.cmake @@ -13,10 +13,10 @@ # CMAKE_C_FLAGS # CMAKE_CXX_FLAGS # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --mpi Enable MPI parallelization [default: False]. -# define: '-DENABLE_MPI=%s' % arguments['--mpi'] +# docopt: "--mpi Enable MPI parallelization [default: False]." +# define: "'-DENABLE_MPI={0}'.format(arguments['--mpi'])" option(ENABLE_MPI "Enable MPI parallelization" OFF) diff --git a/modules/omp.cmake b/modules/omp.cmake index 9784868..f33dd91 100644 --- a/modules/omp.cmake +++ b/modules/omp.cmake @@ -13,33 +13,14 @@ # CMAKE_C_FLAGS # CMAKE_CXX_FLAGS # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --omp Enable OpenMP parallelization [default: False]. -# define: '-DENABLE_OPENMP=%s' % arguments['--omp'] +# docopt: "--omp Enable OpenMP parallelization [default: False]." +# define: "'-DENABLE_OPENMP={0}'.format(arguments['--omp'])" option(ENABLE_OPENMP "Enable OpenMP parallelization" OFF) if(ENABLE_OPENMP) - if(DEFINED CMAKE_Fortran_COMPILER_ID) - # we do this in a pedestrian way because the Fortran support is relatively recent - if(CMAKE_Fortran_COMPILER_ID MATCHES GNU) - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fopenmp") - endif() - if(CMAKE_Fortran_COMPILER_ID MATCHES Intel) - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -openmp") - endif() - if(CMAKE_Fortran_COMPILER_ID MATCHES PGI) - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -mp") - endif() - if(CMAKE_Fortran_COMPILER_ID MATCHES XL) - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -qsmp") - endif() - if(CMAKE_Fortran_COMPILER_ID MATCHES Cray) - # do nothing in this case - endif() - set(OPENMP_FOUND TRUE) - endif() if(NOT OPENMP_FOUND) find_package(OpenMP) @@ -53,5 +34,35 @@ if(ENABLE_OPENMP) if(DEFINED CMAKE_CXX_COMPILER_ID) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") endif() + if(DEFINED CMAKE_Fortran_COMPILER_ID) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${OpenMP_Fortran_FLAGS}") + endif() + endif() + + if(DEFINED CMAKE_Fortran_COMPILER_ID AND NOT DEFINED OpenMP_Fortran_FLAGS) + # we do this in a pedestrian way because the Fortran support is relatively recent + if(CMAKE_Fortran_COMPILER_ID MATCHES GNU) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fopenmp") + endif() + if(CMAKE_Fortran_COMPILER_ID MATCHES Intel) + if(WIN32) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -Qopenmp") + elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "Intel" AND + "${CMAKE_Fortran_COMPILER_VERSION}" VERSION_LESS "15.0.0.20140528") + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -openmp") + else() + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -qopenmp") + endif() + endif() + if(CMAKE_Fortran_COMPILER_ID MATCHES PGI) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -mp") + endif() + if(CMAKE_Fortran_COMPILER_ID MATCHES XL) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -qsmp") + endif() + if(CMAKE_Fortran_COMPILER_ID MATCHES Cray) + # do nothing in this case + endif() + set(OPENMP_FOUND TRUE) endif() endif() diff --git a/modules/python_interpreter.cmake b/modules/python_interpreter.cmake index a726da9..6d200ef 100644 --- a/modules/python_interpreter.cmake +++ b/modules/python_interpreter.cmake @@ -15,10 +15,10 @@ # PYTHON_VERSION_MINOR - Python minor version found e.g. 5 # PYTHON_VERSION_PATCH - Python patch version found e.g. 2 # -# autocmake.cfg configuration:: +# autocmake.yml configuration:: # -# docopt: --python= The Python interpreter (development version) to use. [default: '']. -# define: '-DPYTHON_INTERPRETER="%s"' % arguments['--python'] +# docopt: "--python= The Python interpreter (development version) to use. [default: '']." +# define: "'-DPYTHON_INTERPRETER=\"{0}\"'.format(arguments['--python'])" if("${PYTHON_INTERPRETER}" STREQUAL "") find_package(PythonInterp REQUIRED) diff --git a/modules/save_flags.cmake b/modules/save_flags.cmake new file mode 100644 index 0000000..d3dd677 --- /dev/null +++ b/modules/save_flags.cmake @@ -0,0 +1,43 @@ +#.rst: +# +# Take care of updating the cache for fresh configurations. +# +# Variables modified (provided the corresponding language is enabled):: +# +# DEFAULT_Fortran_FLAGS_SET +# DEFAULT_C_FLAGS_SET +# DEFAULT_CXX_FLAGS_SET + +macro(save_compiler_flags lang) + if (NOT DEFINED DEFAULT_${lang}_FLAGS_SET) + mark_as_advanced(DEFAULT_${lang}_FLAGS_SET) + + set (DEFAULT_${lang}_FLAGS_SET ON + CACHE INTERNAL + "Flag that the default ${lang} compiler flags have been set.") + + set(CMAKE_${lang}_FLAGS "${CMAKE_${lang}_FLAGS}" + CACHE STRING + "Flags used by the compiler during all builds." FORCE) + + set(CMAKE_${lang}_FLAGS_DEBUG "${CMAKE_${lang}_FLAGS_DEBUG}" + CACHE STRING + "Flags used by the compiler during debug builds." FORCE) + + set(CMAKE_${lang}_FLAGS_RELEASE "${CMAKE_${lang}_FLAGS_RELEASE}" + CACHE STRING + "Flags used by the compiler during release builds." FORCE) + endif() +endmacro() + +if(DEFINED CMAKE_Fortran_COMPILER_ID) + save_compiler_flags(Fortran) +endif() + +if(DEFINED CMAKE_C_COMPILER_ID) + save_compiler_flags(C) +endif() + +if(DEFINED CMAKE_CXX_COMPILER_ID) + save_compiler_flags(CXX) +endif() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..35fd7f8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pep8 +pytest +pyyaml diff --git a/test/boost_header_only/cmake/autocmake.cfg b/test/boost_header_only/cmake/autocmake.cfg deleted file mode 100644 index e76db41..0000000 --- a/test/boost_header_only/cmake/autocmake.cfg +++ /dev/null @@ -1,16 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[cxx] -source: ../../../modules/cxx.cmake - -[boost] -override: {'major': 1, 'minor': 48, 'patch': 0} -source: ../../../modules/boost/boost.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/boost_header_only/cmake/autocmake.yml b/test/boost_header_only/cmake/autocmake.yml new file mode 100644 index 0000000..3b319b1 --- /dev/null +++ b/test/boost_header_only/cmake/autocmake.yml @@ -0,0 +1,14 @@ +name: example +min_cmake_version: 2.8 +modules: +- cxx: + - source: ../../../modules/cxx.cmake +- boost: + - major: 1 + - minor: 48 + - patch: 0 + - source: ../../../modules/boost/boost.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/boost_libs/cmake/autocmake.cfg b/test/boost_libs/cmake/autocmake.cfg deleted file mode 100644 index 02d73b0..0000000 --- a/test/boost_libs/cmake/autocmake.cfg +++ /dev/null @@ -1,25 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[cxx] -source: ../../../modules/cxx.cmake - -[mpi] -source: ../../../modules/mpi.cmake - -[python_interpreter] -source: ../../../modules/python_interpreter.cmake - -[python_libs] -source: ../../../modules/python_libs.cmake - -[boost] -override: {'major': 1, 'minor': 59, 'patch': 0, 'components': 'mpi;serialization;python'} -source: ../../../modules/boost/boost.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/boost_libs/cmake/autocmake.yml b/test/boost_libs/cmake/autocmake.yml new file mode 100644 index 0000000..43f5d47 --- /dev/null +++ b/test/boost_libs/cmake/autocmake.yml @@ -0,0 +1,21 @@ +name: example +min_cmake_version: 2.8 +modules: +- cxx: + - source: ../../../modules/cxx.cmake +- mpi: + - source: ../../../modules/mpi.cmake +- python_interpreter: + - source: ../../../modules/python_interpreter.cmake +- python_libs: + - source: ../../../modules/python_libs.cmake +- boost: + - major: 1 + - minor: 59 + - patch: 0 + - components: 'mpi;serialization;python' + - source: ../../../modules/boost/boost.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/cxx/cmake/autocmake.cfg b/test/cxx/cmake/autocmake.cfg deleted file mode 100644 index ce3bf11..0000000 --- a/test/cxx/cmake/autocmake.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[cxx] -source: ../../../modules/cxx.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/cxx/cmake/autocmake.yml b/test/cxx/cmake/autocmake.yml new file mode 100644 index 0000000..5bfe461 --- /dev/null +++ b/test/cxx/cmake/autocmake.yml @@ -0,0 +1,9 @@ +name: example +min_cmake_version: 2.8 +modules: +- cxx: + - source: ../../../modules/cxx.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/cxx_accelerate/cmake/autocmake.cfg b/test/cxx_accelerate/cmake/autocmake.cfg deleted file mode 100644 index 5c91c67..0000000 --- a/test/cxx_accelerate/cmake/autocmake.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[cxx] -source: ../../../modules/cxx.cmake - -[math] -source: ../../../modules/math/accelerate.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/cxx_accelerate/cmake/autocmake.yml b/test/cxx_accelerate/cmake/autocmake.yml new file mode 100644 index 0000000..c2fccf4 --- /dev/null +++ b/test/cxx_accelerate/cmake/autocmake.yml @@ -0,0 +1,11 @@ +name: example +min_cmake_version: 2.8 +modules: +- cxx: + - source: ../../../modules/cxx.cmake +- math: + - source: ../../../modules/math/accelerate.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/cxx_cblas/cmake/autocmake.cfg b/test/cxx_cblas/cmake/autocmake.cfg deleted file mode 100644 index b0d3897..0000000 --- a/test/cxx_cblas/cmake/autocmake.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[cxx] -source: ../../../modules/cxx.cmake - -[math] -source: ../../../modules/math/cblas.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/cxx_cblas/cmake/autocmake.yml b/test/cxx_cblas/cmake/autocmake.yml new file mode 100644 index 0000000..6df6437 --- /dev/null +++ b/test/cxx_cblas/cmake/autocmake.yml @@ -0,0 +1,11 @@ +name: example +min_cmake_version: 2.8 +modules: +- cxx: + - source: ../../../modules/cxx.cmake +- math: + - source: ../../../modules/math/cblas.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/extra_cmake_options/cmake/autocmake.cfg b/test/extra_cmake_options/cmake/autocmake.cfg deleted file mode 100644 index ce3bf11..0000000 --- a/test/extra_cmake_options/cmake/autocmake.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[cxx] -source: ../../../modules/cxx.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/extra_cmake_options/cmake/autocmake.yml b/test/extra_cmake_options/cmake/autocmake.yml new file mode 100644 index 0000000..5bfe461 --- /dev/null +++ b/test/extra_cmake_options/cmake/autocmake.yml @@ -0,0 +1,9 @@ +name: example +min_cmake_version: 2.8 +modules: +- cxx: + - source: ../../../modules/cxx.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/fc/cmake/autocmake.cfg b/test/fc/cmake/autocmake.cfg deleted file mode 100644 index ca40fb4..0000000 --- a/test/fc/cmake/autocmake.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[fc] -source: ../../../modules/fc.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/fc/cmake/autocmake.yml b/test/fc/cmake/autocmake.yml new file mode 100644 index 0000000..7d2318c --- /dev/null +++ b/test/fc/cmake/autocmake.yml @@ -0,0 +1,9 @@ +name: example +min_cmake_version: 2.8 +modules: +- fc: + - source: ../../../modules/fc.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/fc_blas/cmake/autocmake.cfg b/test/fc_blas/cmake/autocmake.cfg deleted file mode 100644 index dd61239..0000000 --- a/test/fc_blas/cmake/autocmake.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[fc] -source: ../../../modules/fc.cmake - -[math] -source: ../../../modules/math/blas.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/fc_blas/cmake/autocmake.yml b/test/fc_blas/cmake/autocmake.yml new file mode 100644 index 0000000..0ed452b --- /dev/null +++ b/test/fc_blas/cmake/autocmake.yml @@ -0,0 +1,11 @@ +name: example +min_cmake_version: 2.8 +modules: +- fc: + - source: ../../../modules/fc.cmake +- math: + - source: ../../../modules/math/blas.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/fc_git_info/cmake/autocmake.cfg b/test/fc_git_info/cmake/autocmake.cfg deleted file mode 100644 index a1e1d12..0000000 --- a/test/fc_git_info/cmake/autocmake.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[fc] -source: ../../../modules/fc.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake - -[git_info] -source: ../../../modules/git_info/git_info.cmake diff --git a/test/fc_git_info/cmake/autocmake.yml b/test/fc_git_info/cmake/autocmake.yml new file mode 100644 index 0000000..a8d32dd --- /dev/null +++ b/test/fc_git_info/cmake/autocmake.yml @@ -0,0 +1,11 @@ +name: example +min_cmake_version: 2.8 +modules: +- fc: + - source: ../../../modules/fc.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake +- git_info: + - source: ../../../modules/git_info/git_info.cmake diff --git a/test/fc_int64/cmake/autocmake.cfg b/test/fc_int64/cmake/autocmake.cfg deleted file mode 100644 index 922c59c..0000000 --- a/test/fc_int64/cmake/autocmake.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[fc] -source: ../../../modules/fc.cmake - -[int64] -source: ../../../modules/int64.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/fc_int64/cmake/autocmake.yml b/test/fc_int64/cmake/autocmake.yml new file mode 100644 index 0000000..0cc3d23 --- /dev/null +++ b/test/fc_int64/cmake/autocmake.yml @@ -0,0 +1,11 @@ +name: example +min_cmake_version: 2.8 +modules: +- fc: + - source: ../../../modules/fc.cmake +- int64: + - source: ../../../modules/int64.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/fc_lapack/cmake/autocmake.cfg b/test/fc_lapack/cmake/autocmake.cfg deleted file mode 100644 index 6d5ea5a..0000000 --- a/test/fc_lapack/cmake/autocmake.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[fc] -source: ../../../modules/fc.cmake - -[math] -source: ../../../modules/math/lapack.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/fc_lapack/cmake/autocmake.yml b/test/fc_lapack/cmake/autocmake.yml new file mode 100644 index 0000000..fa696e5 --- /dev/null +++ b/test/fc_lapack/cmake/autocmake.yml @@ -0,0 +1,11 @@ +name: example +min_cmake_version: 2.8 +modules: +- fc: + - source: ../../../modules/fc.cmake +- math: + - source: ../../../modules/math/lapack.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/fc_omp/cmake/autocmake.cfg b/test/fc_omp/cmake/autocmake.cfg deleted file mode 100644 index 5f3475e..0000000 --- a/test/fc_omp/cmake/autocmake.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[fc] -source: ../../../modules/fc.cmake - -[omp] -source: ../../../modules/omp.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/fc_omp/cmake/autocmake.yml b/test/fc_omp/cmake/autocmake.yml new file mode 100644 index 0000000..09596a6 --- /dev/null +++ b/test/fc_omp/cmake/autocmake.yml @@ -0,0 +1,11 @@ +name: example +min_cmake_version: 2.8 +modules: +- fc: + - source: ../../../modules/fc.cmake +- omp: + - source: ../../../modules/omp.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/python_interpreter/cmake/autocmake.cfg b/test/python_interpreter/cmake/autocmake.cfg deleted file mode 100644 index d77d2cf..0000000 --- a/test/python_interpreter/cmake/autocmake.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[cxx] -source: ../../../modules/cxx.cmake - -[python_interpreter] -source: ../../../modules/python_interpreter.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/python_interpreter/cmake/autocmake.yml b/test/python_interpreter/cmake/autocmake.yml new file mode 100644 index 0000000..560fd7e --- /dev/null +++ b/test/python_interpreter/cmake/autocmake.yml @@ -0,0 +1,13 @@ +name: example +min_cmake_version: 2.8 +modules: +- cxx: + - source: ../../../modules/cxx.cmake +- python_interpreter: + - source: ../../../modules/python_interpreter.cmake +- python_libs: + - source: ../../../modules/python_libs.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/python_interpreter_custom/cmake/autocmake.cfg b/test/python_interpreter_custom/cmake/autocmake.cfg deleted file mode 100644 index d77d2cf..0000000 --- a/test/python_interpreter_custom/cmake/autocmake.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[cxx] -source: ../../../modules/cxx.cmake - -[python_interpreter] -source: ../../../modules/python_interpreter.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/python_interpreter_custom/cmake/autocmake.yml b/test/python_interpreter_custom/cmake/autocmake.yml new file mode 100644 index 0000000..1bb701d --- /dev/null +++ b/test/python_interpreter_custom/cmake/autocmake.yml @@ -0,0 +1,11 @@ +name: example +min_cmake_version: 2.8 +modules: +- cxx: + - source: ../../../modules/cxx.cmake +- python_interpreter: + - source: ../../../modules/python_interpreter.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/python_libs/cmake/autocmake.cfg b/test/python_libs/cmake/autocmake.cfg deleted file mode 100644 index df9c15d..0000000 --- a/test/python_libs/cmake/autocmake.cfg +++ /dev/null @@ -1,18 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[cxx] -source: ../../../modules/cxx.cmake - -[python_interpreter] -source: ../../../modules/python_interpreter.cmake - -[python_libs] -source: ../../../modules/python_libs.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/python_libs/cmake/autocmake.yml b/test/python_libs/cmake/autocmake.yml new file mode 100644 index 0000000..560fd7e --- /dev/null +++ b/test/python_libs/cmake/autocmake.yml @@ -0,0 +1,13 @@ +name: example +min_cmake_version: 2.8 +modules: +- cxx: + - source: ../../../modules/cxx.cmake +- python_interpreter: + - source: ../../../modules/python_interpreter.cmake +- python_libs: + - source: ../../../modules/python_libs.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/python_libs_custom/cmake/autocmake.cfg b/test/python_libs_custom/cmake/autocmake.cfg deleted file mode 100644 index df9c15d..0000000 --- a/test/python_libs_custom/cmake/autocmake.cfg +++ /dev/null @@ -1,18 +0,0 @@ -[project] -name: example -min_cmake_version: 2.8 - -[cxx] -source: ../../../modules/cxx.cmake - -[python_interpreter] -source: ../../../modules/python_interpreter.cmake - -[python_libs] -source: ../../../modules/python_libs.cmake - -[default_build_paths] -source: ../../../modules/default_build_paths.cmake - -[src] -source: ../../../modules/src.cmake diff --git a/test/python_libs_custom/cmake/autocmake.yml b/test/python_libs_custom/cmake/autocmake.yml new file mode 100644 index 0000000..560fd7e --- /dev/null +++ b/test/python_libs_custom/cmake/autocmake.yml @@ -0,0 +1,13 @@ +name: example +min_cmake_version: 2.8 +modules: +- cxx: + - source: ../../../modules/cxx.cmake +- python_interpreter: + - source: ../../../modules/python_interpreter.cmake +- python_libs: + - source: ../../../modules/python_libs.cmake +- default_build_paths: + - source: ../../../modules/default_build_paths.cmake +- src: + - source: ../../../modules/src.cmake diff --git a/test/test.py b/test/test.py index 69fa309..d2a521a 100644 --- a/test/test.py +++ b/test/test.py @@ -10,15 +10,11 @@ import pytest HERE = os.path.abspath(os.path.dirname(__file__)) -skip_on_windows = pytest.mark.skipif('sys.platform == "win32"', reason="not working on windows") skip_on_osx = pytest.mark.skipif('sys.platform == "darwin"', reason="not working on osx") skip_on_linux = pytest.mark.skipif('sys.platform == "linux2"', reason="not working on linux") skip_always = pytest.mark.skipif('1 == 1', reason="tests are broken") -# ------------------------------------------------------------------------------ - - def exe(command): """ Executes command and returns string representations of stdout and stderr captured from the console. @@ -42,8 +38,6 @@ def exe(command): return stdout, stderr -# ------------------------------------------------------------------------------ - def configure_build_and_exe(name, setup_command, launcher=None): @@ -52,15 +46,9 @@ def configure_build_and_exe(name, setup_command, launcher=None): os.chdir(os.path.join(HERE, name, 'cmake')) shutil.copy(os.path.join('..', '..', '..', 'update.py'), 'update.py') - dst_dir = 'lib' - if not os.path.exists(dst_dir): - os.makedirs(dst_dir) - shutil.copy(os.path.join('..', '..', '..', dst_dir, 'config.py'), dst_dir) - - dst_dir = os.path.join('lib', 'docopt') - if not os.path.exists(dst_dir): - os.makedirs(dst_dir) - shutil.copy(os.path.join('..', '..', '..', dst_dir, 'docopt.py'), dst_dir) + if os.path.exists('autocmake'): + shutil.rmtree('autocmake') + shutil.copytree(os.path.join('..', '..', '..', 'autocmake'), 'autocmake') stdout, stderr = exe('python update.py ..') os.chdir(os.path.join(HERE, name)) @@ -90,8 +78,6 @@ def configure_build_and_exe(name, setup_command, launcher=None): assert 'PASSED' in stdout -# ------------------------------------------------------------------------------ - def test_extra_cmake_options(): configure_build_and_exe('extra_cmake_options', 'python setup --cxx=g++ --cmake-options="-DENABLE_SOMETHING=OFF -DENABLE_FOO=ON"') @@ -119,24 +105,20 @@ def test_fc_omp(): configure_build_and_exe('fc_omp', 'python setup --omp --fc=gfortran') -@skip_on_windows def test_fc_blas(): configure_build_and_exe('fc_blas', 'python setup --fc=gfortran --blas') -@skip_on_windows def test_fc_lapack(): configure_build_and_exe('fc_lapack', 'python setup --fc=gfortran --lapack') @skip_on_osx -@skip_on_windows def test_cxx_cblas(): configure_build_and_exe('cxx_cblas', 'python setup --cxx=g++ --cblas') @skip_on_linux -@skip_on_windows def test_cxx_accelerate(): configure_build_and_exe('cxx_accelerate', 'python setup --cxx=g++ --accelerate') @@ -150,23 +132,19 @@ def test_python_interpreter_custom(): configure_build_and_exe('python_interpreter_custom', setup) -@skip_on_windows def test_python_libs(): configure_build_and_exe('python_libs', 'python setup --cxx=g++') -@skip_on_windows def test_python_libs_custom(): python_executable = sys.executable - configure_build_and_exe('python_libs_custom', 'python setup --cxx=g++ --python={}'.format(python_executable)) + configure_build_and_exe('python_libs_custom', 'python setup --cxx=g++ --python={0}'.format(python_executable)) -@skip_on_windows def test_boost_header_only(): configure_build_and_exe('boost_header_only', 'python setup --cxx=g++') @skip_on_osx -@skip_on_windows def test_boost_libs(): configure_build_and_exe('boost_libs', 'python setup --cxx=g++ --mpi') diff --git a/update.py b/update.py index f3e78f7..9508edd 100644 --- a/update.py +++ b/update.py @@ -2,41 +2,278 @@ import os import sys -import datetime -import ast -import collections - -# we do not use the nicer sys.version_info.major -# for compatibility with Python < 2.7 -if sys.version_info[0] > 2: - from io import StringIO - from configparser import ConfigParser - 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 %s\n" % url) - sys.exit(-1) -else: - from StringIO import StringIO - from ConfigParser import ConfigParser - import urllib - - class URLopener(urllib.FancyURLopener): - def http_error_default(self, url, fp, errcode, errmsg, headers): - sys.stderr.write("ERROR: could not fetch %s\n" % url) - sys.exit(-1) -AUTOCMAKE_GITHUB_URL = 'https://github.com/scisoft/autocmake' +if sys.version_info[0] == 2 and sys.version_info[1] < 7: + sys.stderr.write("ERROR: update.py requires at least Python 2.7\n") + sys.exit(-1) -# ------------------------------------------------------------------------------ + +AUTOCMAKE_GITHUB_URL = 'https://github.com/coderefinery/autocmake/raw/master/' + + +def check_for_yaml(): + try: + import yaml + except: + sys.stderr.write("ERROR: you need to install the pyyaml package\n") + sys.exit(-1) + + +def print_progress_bar(text, done, total, width): + """ + Print progress bar. + """ + if total > 0: + 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 flat_add(l, x): + if isinstance(x, int): + l.append(x) + return l + elif isinstance(x, str): + l.append(x) + return l + else: + return l + x + + +def fetch_modules(config, relative_path, download_directory): + """ + Assemble modules which will + be included in CMakeLists.txt. + """ + from collections import Iterable, namedtuple, defaultdict + from autocmake.extract import extract_list, to_d, to_l + from autocmake.parse_rst import parse_cmake_module + + cleaned_config = defaultdict(lambda: []) + + modules = [] + Module = namedtuple('Module', 'path name') + + num_sources = len(extract_list(config, 'source')) + + print_progress_bar(text='- assembling modules:', + done=0, + total=num_sources, + width=30) + + if 'modules' in config: + i = 0 + for t in config['modules']: + for k, v in t.items(): + + d = to_d(v) + for _k, _v in to_d(v).items(): + cleaned_config[_k] = flat_add(cleaned_config[_k], _v) + + # fetch sources and parse them + if 'source' in d: + for src in to_l(d['source']): + i += 1 + + # we download the file + 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) + + # we infer config from the module documentation + # dictionary d overrides the configuration in the module documentation + # this allows to override interpolation inside the module + with open(file_name, 'r') as f: + parsed_config = parse_cmake_module(f.read(), d) + for _k2, _v2 in parsed_config.items(): + if _k2 not in to_d(v): + # we add to clean_config only if the entry does not exist + # in parent autocmake.yml already + # this allows to override + cleaned_config[_k2] = flat_add(cleaned_config[_k2], _v2) + + modules.append(Module(path=path, name=name)) + print_progress_bar(text='- assembling modules:', + done=i, + total=num_sources, + width=30) + print('') + + return modules, cleaned_config + + +def process_yaml(argv): + from autocmake.parse_yaml import parse_yaml + from autocmake.generate import gen_cmakelists, gen_setup + from autocmake.extract import extract_list + + 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) + + download_directory = 'downloaded' + if not os.path.exists(download_directory): + os.makedirs(download_directory) + + # fetch modules from the web or from relative paths + modules, cleaned_config = fetch_modules(config, relative_path, download_directory) + + # fetch files which are not parsed + for src in cleaned_config['fetch']: + dst = os.path.join(download_directory, os.path.basename(src)) + fetch_url(src, dst) + + # print warnings + for warning in cleaned_config['warning']: + print('- WARNING: {0}'.format(warning)) + + # 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(cleaned_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} \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 ', + '(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/__init__.py', + 'autocmake/external/docopt.py', + 'autocmake/external/__init__.py', + 'autocmake/generate.py', + 'autocmake/extract.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): @@ -45,505 +282,7 @@ def fetch_url(src, dst): opener = URLopener() opener.retrieve(src, dst) -# ------------------------------------------------------------------------------ - - -def print_progress_bar(text, done, total, width): - """ - Print progress bar. - """ - n = int(float(width) * float(done) / float(total)) - sys.stdout.write("\r%s [%s%s] (%i/%i)" % (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(' %s%s %s' % (opt[0], ' ' * (l - len(opt[0])), opt[1])) - return '\n'.join(s) - -# ------------------------------------------------------------------------------ - - -def gen_cmake_command(config): - """ - Generate CMake command. - """ - s = [] - - s.append("\n\ndef gen_cmake_command(options, arguments):") - s.append(' """') - s.append(" Generate CMake command based on options and arguments.") - s.append(' """') - s.append(" command = []") - - # take care of environment variables - for section in config.sections(): - if config.has_option(section, 'export'): - for env in config.get(section, 'export').split('\n'): - s.append(' command.append(%s)' % env) - - s.append(" command.append('%s' % arguments['--cmake-executable'])") - - # take care of cmake definitions - for section in config.sections(): - if config.has_option(section, 'define'): - for definition in config.get(section, 'define').split('\n'): - s.append(' command.append(%s)' % definition) - - s.append(" command.append('-DCMAKE_BUILD_TYPE=%s' % arguments['--type'])") - s.append(" command.append('-G \"%s\"' % arguments['--generator'])") - s.append(" if arguments['--cmake-options'] != \"''\":") - s.append(" command.append('%s' % arguments['--cmake-options'])") - s.append(" if arguments['--prefix']:") - s.append(" command.append('-DCMAKE_INSTALL_PREFIX=\"{0}\"'.format(arguments['--prefix']))") - - s.append("\n return ' '.join(command)") - - return '\n'.join(s) - -# ------------------------------------------------------------------------------ - - -def autogenerated_notice(): - start_year = 2015 - year_range = str(start_year) - current_year = datetime.date.today().year - if current_year > start_year: - year_range += '-%s' % current_year - s = [] - s.append('# This file is autogenerated by Autocmake http://autocmake.org') - s.append('# Copyright (c) %s by Radovan Bast and Jonas Juselius' % year_range) - return '\n'.join(s) - -# ------------------------------------------------------------------------------ - - -def gen_setup(config, relative_path, setup_script_name): - """ - Generate setup script. - """ - s = [] - s.append('#!/usr/bin/env python') - s.append('\n%s' % autogenerated_notice()) - s.append('\nimport os') - s.append('import sys') - - s.append("\nsys.path.insert(0, '{0}')".format(relative_path)) - s.append("sys.path.insert(0, '{0}')".format(os.path.join(relative_path, 'lib'))) - s.append("sys.path.insert(0, '{0}')".format(os.path.join(relative_path, 'lib', 'docopt'))) - - s.append('import config') - s.append('import docopt') - - s.append('\n\noptions = """') - s.append('Usage:') - s.append(' ./{0} [options] []'.format(setup_script_name)) - s.append(' ./{0} (-h | --help)'.format(setup_script_name)) - s.append('\nOptions:') - - options = [] - for section in config.sections(): - if config.has_option(section, 'docopt'): - for opt in config.get(section, 'docopt').split('\n'): - first = opt.split()[0].strip() - rest = ' '.join(opt.split()[1:]).strip() - options.append([first, rest]) - - options.append(['--type=', 'Set the CMake build type (debug, release, or relwithdeb) [default: release].']) - options.append(['--generator=', 'Set the CMake build system generator [default: Unix Makefiles].']) - options.append(['--show', 'Show CMake command and exit.']) - options.append(['--cmake-executable=', 'Set the CMake executable [default: cmake].']) - options.append(['--cmake-options=', "Define options to CMake [default: '']."]) - options.append(['--prefix=', 'Set the install path for make install.']) - options.append(['', 'Build directory.']) - options.append(['-h --help', 'Show this screen.']) - - s.append(align_options(options)) - - s.append('"""') - - s.append(gen_cmake_command(config)) - - s.append("\n") - s.append("# parse command line args") - s.append("try:") - s.append(" arguments = docopt.docopt(options, argv=None)") - s.append("except docopt.DocoptExit:") - s.append(r" sys.stderr.write('ERROR: bad input to %s\n' % sys.argv[0])") - s.append(" sys.stderr.write(options)") - s.append(" sys.exit(-1)") - s.append("\n") - s.append("# use extensions to validate/post-process args") - s.append("if config.module_exists('extensions'):") - s.append(" import extensions") - s.append(" arguments = extensions.postprocess_args(sys.argv, arguments)") - s.append("\n") - s.append("root_directory = os.path.dirname(os.path.realpath(__file__))") - s.append("\n") - s.append("build_path = arguments['']") - s.append("\n") - s.append("# create cmake command") - s.append("cmake_command = '%s %s' % (gen_cmake_command(options, arguments), root_directory)") - s.append("\n") - s.append("# run cmake") - s.append("config.configure(root_directory, build_path, cmake_command, arguments['--show'])") - - return s - -# ------------------------------------------------------------------------------ - - -def gen_cmakelists(project_name, min_cmake_version, relative_path, modules): - """ - Generate CMakeLists.txt. - """ - s = [] - - s.append(autogenerated_notice()) - - s.append('\n# set minimum cmake version') - s.append('cmake_minimum_required(VERSION %s FATAL_ERROR)' % min_cmake_version) - - s.append('\n# project name') - s.append('project(%s)' % project_name) - - s.append('\n# do not rebuild if rules (compiler flags) change') - s.append('set(CMAKE_SKIP_RULE_DEPENDENCY TRUE)') - - s.append('\n# if CMAKE_BUILD_TYPE undefined, we set it to Debug') - s.append('if(NOT CMAKE_BUILD_TYPE)') - s.append(' set(CMAKE_BUILD_TYPE "Debug")') - s.append('endif()') - - if len(modules) > 0: - s.append('\n# directories which hold included cmake modules') - - module_paths = [module.path for module in modules] - module_paths.append('downloaded') # this is done to be able to find fetched modules when testing - module_paths = list(set(module_paths)) - module_paths.sort() # we do this to always get the same order and to minimize diffs - for directory in module_paths: - rel_cmake_module_path = os.path.join(relative_path, directory) - # on windows cmake corrects this so we have to make it wrong again - rel_cmake_module_path = rel_cmake_module_path.replace('\\', '/') - s.append('set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/%s)' % rel_cmake_module_path) - - if len(modules) > 0: - s.append('\n# included cmake modules') - for module in modules: - s.append('include(%s)' % os.path.splitext(module.name)[0]) - - return 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%s' % config.get(section, option, 0, defaults) - config.set(section, option, value) - return config - -# ------------------------------------------------------------------------------ - - -def fetch_modules(config, relative_path): - """ - Assemble modules which will - be included in CMakeLists.txt. - """ - - download_directory = 'downloaded' - if not os.path.exists(download_directory): - os.makedirs(download_directory) - - l = list(filter(lambda x: config.has_option(x, 'source'), - config.sections())) - n = len(l) - - modules = [] - Module = collections.namedtuple('Module', 'path name') - - warnings = [] - - if n > 0: # otherwise division by zero in print_progress_bar - i = 0 - print_progress_bar(text='- assembling modules:', done=0, total=n, width=30) - for section in config.sections(): - if config.has_option(section, 'source'): - for src in config.get(section, 'source').split('\n'): - module_name = os.path.basename(src) - if 'http' in src: - path = download_directory - name = 'autocmake_%s' % module_name - dst = os.path.join(download_directory, 'autocmake_%s' % 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: %s does not exist\n" % src) - sys.exit(-1) - - if config.has_option(section, 'override'): - defaults = ast.literal_eval(config.get(section, 'override')) - else: - defaults = {} - - # 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)) - i += 1 - print_progress_bar( - text='- assembling modules:', - done=i, - total=n, - width=30 - ) - if config.has_option(section, 'fetch'): - # when we fetch directly from autocmake.cfg - # 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('- %s' % '\n- '.join(warnings)) - - return modules - -# ------------------------------------------------------------------------------ - - -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(" $ %s --self\n\n" % argv[0]) - sys.stderr.write("Step 2: Create CMakeLists.txt and setup script in PROJECT_ROOT:\n") - sys.stderr.write(" $ %s \n" % argv[0]) - sys.stderr.write(" example:\n") - sys.stderr.write(" $ %s ..\n" % argv[0]) - sys.exit(-1) - - if argv[1] in ['-h', '--help']: - print('Usage:') - print(' python update.py --self Update this script and fetch or update infrastructure files under lib/.') - print(' python update.py (Re)generate CMakeLists.txt and setup script and fetch or update CMake modules.') - print(' python update.py (-h | --help) Show this help text.') - sys.exit(0) - - if argv[1] == '--self': - # update self - if not os.path.isfile('autocmake.cfg'): - print('- fetching example autocmake.cfg') - fetch_url( - src='%s/raw/master/example/autocmake.cfg' % AUTOCMAKE_GITHUB_URL, - dst='autocmake.cfg' - ) - if not os.path.isfile('.gitignore'): - print('- creating .gitignore') - with open('.gitignore', 'w') as f: - f.write('*.pyc\n') - print('- fetching lib/config.py') - fetch_url( - src='%s/raw/master/lib/config.py' % AUTOCMAKE_GITHUB_URL, - dst='lib/config.py' - ) - print('- fetching lib/docopt/docopt.py') - fetch_url( - src='%s/raw/master/lib/docopt/docopt.py' % AUTOCMAKE_GITHUB_URL, - dst='lib/docopt/docopt.py' - ) - print('- fetching update.py') - fetch_url( - src='%s/raw/master/update.py' % AUTOCMAKE_GITHUB_URL, - dst='update.py' - ) - sys.exit(0) - - project_root = argv[1] - if not os.path.isdir(project_root): - sys.stderr.write("ERROR: %s is not a directory\n" % project_root) - sys.exit(-1) - - # read config file - print('- parsing autocmake.cfg') - config = ConfigParser(dict_type=collections.OrderedDict) - config.read('autocmake.cfg') - - if not config.has_option('project', 'name'): - sys.stderr.write("ERROR: you have to specify the project name\n") - sys.stderr.write(" in autocmake.cfg under [project]\n") - sys.exit(-1) - project_name = config.get('project', 'name') - if ' ' in project_name.rstrip(): - sys.stderr.write("ERROR: project name contains a space\n") - sys.exit(-1) - - if not config.has_option('project', 'min_cmake_version'): - sys.stderr.write("ERROR: you have to specify the min_cmake_version for CMake\n") - sys.stderr.write(" in autocmake.cfg under [project]\n") - sys.exit(-1) - min_cmake_version = config.get('project', 'min_cmake_version') - - if config.has_option('project', 'setup_script'): - setup_script_name = config.get('project', '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('%s\n' % '\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('%s\n' % '\n'.join(s)) - if sys.platform != 'win32': - make_executable(file_path) - -# ------------------------------------------------------------------------------ - - -# http://stackoverflow.com/a/30463972 -def make_executable(path): - mode = os.stat(path).st_mode - mode |= (mode & 0o444) >> 2 # copy R bits to X - os.chmod(path, mode) - -# ------------------------------------------------------------------------------ - - -def parse_cmake_module(s_in, defaults={}): - - parsed_config = collections.defaultdict(lambda: None) - - if 'autocmake.cfg configuration::' not in s_in: - return parsed_config - - s_out = [] - is_rst_line = False - for line in s_in.split('\n'): - if is_rst_line: - if len(line) > 0: - if line[0] != '#': - is_rst_line = False - else: - is_rst_line = False - if is_rst_line: - s_out.append(line[2:]) - if '#.rst:' in line: - is_rst_line = True - - autocmake_entry = '\n'.join(s_out).split('autocmake.cfg configuration::')[1] - autocmake_entry = autocmake_entry.replace('\n ', '\n') - - # we prepend a fake section heading so that we can parse it with configparser - autocmake_entry = '[foo]\n' + autocmake_entry - - buf = StringIO(autocmake_entry) - config = ConfigParser(dict_type=collections.OrderedDict) - config.readfp(buf) - - for section in config.sections(): - for s in ['docopt', 'define', 'export', 'fetch', 'warning']: - if config.has_option(section, s): - parsed_config[s] = config.get(section, s, 0, defaults) - - return parsed_config - -# ------------------------------------------------------------------------------ - - -def test_parse_cmake_module(): - - s = '''#.rst: -# -# Foo ... -# -# autocmake.cfg configuration:: -# -# docopt: --cxx= C++ compiler [default: g++]. -# --extra-cxx-flags= Extra C++ compiler flags [default: '']. -# export: 'CXX=%s' % arguments['--cxx'] -# define: '-DEXTRA_CXXFLAGS="%s"' % arguments['--extra-cxx-flags'] - -enable_language(CXX) - -if(NOT DEFINED CMAKE_C_COMPILER_ID) - message(FATAL_ERROR "CMAKE_C_COMPILER_ID variable is not defined!") -endif()''' - - parsed_config = parse_cmake_module(s) - assert parsed_config['docopt'] == "--cxx= C++ compiler [default: g++].\n--extra-cxx-flags= Extra C++ compiler flags [default: '']." - - s = '''#.rst: -# -# Foo ... -# -# Bar ... - -enable_language(CXX) - -if(NOT DEFINED CMAKE_C_COMPILER_ID) - message(FATAL_ERROR "CMAKE_C_COMPILER_ID variable is not defined!") -endif()''' - - parsed_config = parse_cmake_module(s) - assert parsed_config['docopt'] is None - -# ------------------------------------------------------------------------------ - if __name__ == '__main__': + check_for_yaml() main(sys.argv)