Skip to content

Commit

Permalink
Add basic configuration API.
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanseefeld committed Nov 9, 2017
1 parent 06396fa commit 7377844
Show file tree
Hide file tree
Showing 11 changed files with 447 additions and 0 deletions.
60 changes: 60 additions & 0 deletions examples/config/fabscript
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# -*- python -*-
#
# Copyright (c) 2016 Stefan Seefeld
# All rights reserved.
#
# This file is part of Faber. It is made available under the
# Boost Software License, Version 1.0.
# (Consult LICENSE or http://www.boost.org/LICENSE_1_0.txt)

#
# This example demonstrates how to configure a project.
#
# Run with:
#
# * `faber cxxflags=-I/usr/include/python2.7 libs=python2.7`
# * `faber cxxflags=-std=c++14`
# * `faber --with-python-inc=/usr/include/python2.7 --with-python-lib=python2.7`
#
# To remove config artefacts run with:
#
# * `faber .config-clean`

from faber.artefacts.binary import binary
from faber.tools.compiler import define, include, linkpath, libs
from faber.types import cxx, c
from faber.config.try_link import *
from faber.config import report, c_checks, cxx_checks, try_run
from faber import scheduler

python_inc = options.get_with('python-inc')
python_linkpath = options.get_with('python-linkpath')
python_lib = options.get_with('python-lib')
if python_inc:
features |= include(python_inc)
if python_linkpath:
features |= linkpath(python_linkpath)
if python_lib:
features |= libs(python_lib)

pysrc="""
#include <Python.h>
int main()
{
Py_Initialize();
}
"""
checks = [c_checks.sizeof('char', cxx, features=features),
c_checks.sizeof('long', cxx, features=features),
try_link('pytest', pysrc, cxx, features,
define('HAS_PYTHON=1'),
define('HAS_PYTHON=0')),
cxx_checks.has_cxx11(features, define('HAS_CXX11')),
cxx_checks.has_cxx14(features, define('HAS_CXX14')),
cxx_checks.has_cxx17(features, define('HAS_CXX17'))]

config = report('config', checks)
bin = binary('check', 'main.cpp', dependencies=config, features=config.use)
report = rule(action('run', '$(>)'), 'report', bin, attrs=notfile|always)

default = report
35 changes: 35 additions & 0 deletions examples/config/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Copyright (c) 2016 Stefan Seefeld
// All rights reserved.
//
// This file is part of Faber. It is made available under the
// Boost Software License, Version 1.0.
// (Consult LICENSE or http://www.boost.org/LICENSE_1_0.txt)

#include <iostream>

#ifdef HAS_CXX11
# define cxx11 "defined"
#else
# define cxx11 "undefined"
#endif
#ifdef HAS_CXX14
# define cxx14 "defined"
#else
# define cxx14 "undefined"
#endif
#ifdef HAS_CXX17
# define cxx17 "defined"
#else
# define cxx17 "undefined"
#endif


int main()
{
std::cout << "HAS_PYTHON=" << HAS_PYTHON << '\n'
<< "HAS_CXX11 " << cxx11 << '\n'
<< "HAS_CXX14 " << cxx14 << '\n'
<< "HAS_CXX17 " << cxx17 << '\n'
<< std::endl;
}
32 changes: 32 additions & 0 deletions src/faber/config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# Copyright (c) 2016 Stefan Seefeld
# All rights reserved.
#
# This file is part of Faber. It is made available under the
# Boost Software License, Version 1.0.
# (Consult LICENSE or http://www.boost.org/LICENSE_1_0.txt)

from ..artefact import artefact, notfile
from ..rule import depend
from .. import output


class report(artefact):

def __init__(self, name, checks):
use = [c.use for c in checks]
artefact.__init__(self, name, attrs=notfile, use=use)
depend(self, checks)
self.checks = checks

def _report(self):

max_name_length = max(len(c.qname) for c in self.checks)
print(output.coloured('configuration check results:', attrs=['bold']))
for c in self.checks:
print(' {:{}} : {} {}'
.format(c.qname, max_name_length, c.result, '(cached)' if c.cached else ''))

def __status__(self, status):
artefact.__status__(self, status)
self._report()
23 changes: 23 additions & 0 deletions src/faber/config/c_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# Copyright (c) 2016 Stefan Seefeld
# All rights reserved.
#
# This file is part of Faber. It is made available under the
# Boost Software License, Version 1.0.
# (Consult LICENSE or http://www.boost.org/LICENSE_1_0.txt)

from .try_run import check_output
from .. import types


class sizeof(check_output):

src = """#include <stdio.h>
int main(){{ printf("%i", sizeof({}));}}"""

def __init__(self, c_type, lang_type=types.c, features=()):
check_output.__init__(self, 'sizeof_' + c_type, sizeof.src.format(c_type), lang_type,
features)

def post_process(self, output):
self.result = int(output)
92 changes: 92 additions & 0 deletions src/faber/config/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#
# Copyright (c) 2016 Stefan Seefeld
# All rights reserved.
#
# This file is part of Faber. It is made available under the
# Boost Software License, Version 1.0.
# (Consult LICENSE or http://www.boost.org/LICENSE_1_0.txt)

from ..feature import conditional
from ..artefact import artefact, notfile, nocare
from ..module import module
import sqlite3
import hashlib
import os
import os.path
import logging

logger = logging.getLogger('config')


class cache(object):

def __init__(self):
if not os.path.exists(module.current.builddir):
os.makedirs(module.current.builddir)
self.filename = os.path.join(module.current.builddir, '.configcache')
self.conn = sqlite3.connect(self.filename)
# Create table if it doesn't exist yet.
if not next(self.conn.execute('SELECT name FROM sqlite_master '
'WHERE type="table" AND name="checks"'), None):
self.conn.execute('CREATE TABLE checks '
'(key TEXT, status INTEGER, type TEXT, value TEXT)')

def __del__(self):
self.conn.commit()

def clean(self):
self.conn.close()
os.remove(self.filename)
del self

def __contains__(self, key):
with self.conn:
a = self.conn.execute('SELECT key FROM checks WHERE key=?', (key,))
value = next(a, None)
return bool(value)

def __setitem__(self, key, value):
status, result = value[0], value[1]
with self.conn:
self.conn.execute('INSERT INTO checks VALUES(?,?,?,?)',
(key, status, type(result).__name__, str(result)))

def __getitem__(self, key):
with self.conn:
a = self.conn.execute('SELECT status, type, value FROM checks WHERE key=?',
(key,))
status, type, value = next(a, (None, None, None))
value = {'str': lambda x: x,
'unicode': lambda x: x,
'bool': lambda x: eval(x),
'int': lambda x: int(x)}[type](value)
return status, value


class check(artefact):
"""A check is an artefact that performs some tests (typically involving compilation),
then stores the result in a cache, so it doesn't need to be performed again,
until the cache is explicitly cleared."""

cache = cache() if module.current else None # to support sphinx' autoclass

def __init__(self, name, features=(), if_=(), ifnot=()):

self.result = None
artefact.__init__(self, name, attrs=notfile|nocare, features=features)
# The 'condition' here is simply the value of the check's status member.
self.use = conditional(lambda ctx: self.status, self, if_, ifnot)
key = str((self.name, str(self.features))).encode('utf-8')
self._cache_key = hashlib.md5(key).hexdigest()
self.cached = self._cache_key in check.cache
if self.cached:
self.status, self.result = check.cache[self._cache_key]

def __status__(self, status):
logger.debug('check.__status__({})'.format(status))
if self.cached:
return # the cached value takes precedence
artefact.__status__(self, status)
if not self.status or self.result is None:
self.result = self.status
check.cache[self._cache_key] = (self.status, self.result)
43 changes: 43 additions & 0 deletions src/faber/config/cxx_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#
# Copyright (c) 2016 Stefan Seefeld
# All rights reserved.
#
# This file is part of Faber. It is made available under the
# Boost Software License, Version 1.0.
# (Consult LICENSE or http://www.boost.org/LICENSE_1_0.txt)

from .try_compile import try_compile
from .. import types


class has_cxx11(try_compile):

src = r"""#if __cplusplus < 201103L
#error no C++11
#endif"""

def __init__(self, features=(), if_=(), ifnot=()):
try_compile.__init__(self, 'has_cxx11', has_cxx11.src, types.cxx, features,
if_, ifnot)


class has_cxx14(try_compile):

src = r"""#if __cplusplus < 201402L
#error no C++14
#endif"""

def __init__(self, features=(), if_=(), ifnot=()):
try_compile.__init__(self, 'has_cxx14', has_cxx14.src, types.cxx, features,
if_, ifnot)


class has_cxx17(try_compile):

src = r"""#if __cplusplus < 201500L
#error no C++17
#endif"""

def __init__(self, features=(), if_=(), ifnot=()):
try_compile.__init__(self, 'has_cxx17', has_cxx17.src, types.cxx, features,
if_, ifnot)
32 changes: 32 additions & 0 deletions src/faber/config/try_compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# Copyright (c) 2016 Stefan Seefeld
# All rights reserved.
#
# This file is part of Faber. It is made available under the
# Boost Software License, Version 1.0.
# (Consult LICENSE or http://www.boost.org/LICENSE_1_0.txt)

from .check import check
from ..artefact import intermediate, always
from ..tools.compiler import compiler
from ..rule import rule, alias
from ..artefacts.object import object


class try_compile(check):
"""Try to compile a chunk of source code."""

def __init__(self, name, source, type, features=(), if_=(), ifnot=()):

check.__init__(self, name, features, if_, ifnot)
compiler.check_instance_for_type(type, features)
if not self.cached:
# create source file
src = type.synthesize_name(self.name)

def generate(targets, _):
with open(targets[0]._filename, 'w') as os:
os.write(source)
src = rule(generate, src, attrs=intermediate|always)
obj = object(self.name, src, attrs=intermediate, features=self.features)
alias(self, obj)
32 changes: 32 additions & 0 deletions src/faber/config/try_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# Copyright (c) 2016 Stefan Seefeld
# All rights reserved.
#
# This file is part of Faber. It is made available under the
# Boost Software License, Version 1.0.
# (Consult LICENSE or http://www.boost.org/LICENSE_1_0.txt)

from .check import check
from ..artefact import intermediate, always
from ..tools.compiler import compiler
from ..rule import rule, alias
from ..artefacts.binary import binary


class try_link(check):
"""Try to compile and link a chunk of source code."""

def __init__(self, name, source, type, features=(), if_=(), ifnot=()):

check.__init__(self, name, features, if_, ifnot)
compiler.check_instance_for_type(type, features)
if not self.cached:
# create source file
src = type.synthesize_name(self.name)

def generate(targets, _):
with open(targets[0]._filename, 'w') as os:
os.write(source)
src = rule(generate, src, attrs=intermediate|always)
bin = binary(self.name, src, attrs=intermediate, features=self.features)
alias(self, bin)
Loading

0 comments on commit 7377844

Please sign in to comment.