summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--attoconf/_version.py2
-rw-r--r--attoconf/classy.py112
-rw-r--r--attoconf/core.py26
-rw-r--r--attoconf/help.py8
-rw-r--r--attoconf/lib/__init__.py0
-rw-r--r--attoconf/lib/arches.py68
-rw-r--r--attoconf/lib/c.py268
-rw-r--r--attoconf/lib/install.py320
-rw-r--r--attoconf/lib/make.py94
-rw-r--r--attoconf/tests/test_core.py12
-rw-r--r--demo-project/Makefile.in9
-rwxr-xr-xdemo-project/configure54
-rw-r--r--demo-project/header.c16
-rw-r--r--demo-project/hello.c25
-rw-r--r--demo-project/hello.h22
-rw-r--r--demo-project/main.c24
-rwxr-xr-xtest-everything.sh9
18 files changed, 1044 insertions, 26 deletions
diff --git a/.gitignore b/.gitignore
index 0d20b64..926df93 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
*.pyc
+/demo-build/
diff --git a/attoconf/_version.py b/attoconf/_version.py
index 8d9820c..1b6e204 100644
--- a/attoconf/_version.py
+++ b/attoconf/_version.py
@@ -7,7 +7,7 @@ major = 0
# Incremented for releases with compatible API additions.
# This is the number that is usually incremented.
-minor = 1
+minor = 2
# Incremented if there is a bugfix release.
# Might not be contiguous.
diff --git a/attoconf/classy.py b/attoconf/classy.py
new file mode 100644
index 0000000..f390ebc
--- /dev/null
+++ b/attoconf/classy.py
@@ -0,0 +1,112 @@
+# Copyright 2013 Ben Longbons <b.r.longbons@gmail.com>
+#
+# This file is part of attoconf.
+#
+# attoconf is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# attoconf is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with attoconf. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function, division, absolute_import
+
+from .core import Project, Build
+
+# TODO: put this in a metaclass
+# some of the init/jiggle logic would also be simplified by a metaclass
+def add_slots(cls):
+ ''' A decorator that fixes __slots__ after multiple inheritance.
+ '''
+ return type(cls.__name__, cls.__bases__,
+ dict(cls.__dict__, __slots__=cls.slots()))
+
+class ClassyProject(Project):
+ ''' A more convenient, objectish, way of setting up a project.
+ '''
+ __slots__ = ()
+ @classmethod
+ def slots(self):
+ ''' slots don't work too well with multiple inheritance,
+ so make the most-derived class create all the slots
+ '''
+ return ()
+
+ def __init__(self, srcdir):
+ super(ClassyProject, self).__init__(srcdir)
+
+ def jiggle(self):
+ self.general()
+ self.paths()
+ self.arches()
+ self.vars()
+ self.features()
+ self.packages()
+
+ if 0:
+ self.tests()
+ self.post()
+
+ def general(self):
+ ''' Registration hook for general options (usually unneeded).
+ '''
+ self.add_help('General:', hidden=False)
+ self.add_alias('--help', ['--help=default'],
+ help='display standard help, then exit', hidden=False)
+ self.add_option('--help', init=None,
+ type=self.do_help, check=None,
+ help='display some kind of help', hidden=False,
+ help_var='KIND')
+ self.help.add_option('--help=hidden',
+ help='display help you should never ever ever care about',
+ hidden=False)
+
+ def paths(self):
+ ''' Registration hook for path-related options.
+ (--prefix, --bindir, etc)
+
+ Probably only used by attoconf.lib.install.
+ '''
+
+ def arches(self):
+ ''' Registration hook for arch-related options.
+ (--build, --host, and sometimes --target)
+
+ Typically this changes the prefix of the compiler.
+ '''
+
+ def vars(self):
+ ''' Environment variables (usually capital, don't start with a --).
+
+ Usually there is one or two of these for every program needed.
+ '''
+
+ def features(self):
+ ''' Customizations for this package (--enable-*).
+ '''
+
+ def packages(self):
+ ''' Settings related to dependencies (--with-*).
+ '''
+
+ if 0: # not sure if really needed
+ def tests(self):
+ ''' Late tests, but still before post.
+
+ '''
+
+ def post(self):
+ ''' Special hook for things that need to be done at the very end.
+
+ attoconf.post.make
+ '''
+
+ # okay, no more registration hooks
+ def build(self, bindir):
+ return Build(self, bindir)
diff --git a/attoconf/core.py b/attoconf/core.py
index 8b53209..38ba485 100644
--- a/attoconf/core.py
+++ b/attoconf/core.py
@@ -28,7 +28,7 @@ Option = namedtuple('Option', ['type', 'init'])
class ArgumentError(Exception): pass
def as_var(name):
- return name.lstrip('-').upper()
+ return name.lstrip('-').replace('-', '_').upper()
def trim_trailing_slashes(path):
p, s = os.path.split(path)
@@ -95,7 +95,9 @@ class Project(object):
if name in self.options:
raise KeyError(name)
self.options[name] = Option(init=init, type=type)
- self.checks.append(lambda bld: check(bld, bld.vars[as_var(name)][0]))
+ if check is not None:
+ self.checks.append(
+ lambda bld: check(bld, bld.vars[as_var(name)][0]) )
if help_var is None:
help_var = as_var(name)
if help_def is None:
@@ -125,15 +127,15 @@ class Build(object):
''' A Build is a directory and set of options applied to a Project.
'''
__slots__ = (
- 'bindir',
+ 'builddir',
'project',
'vars',
)
- def __init__(self, project, bindir):
+ def __init__(self, project, builddir):
''' A Build is initially constructed from a project and a build dir.
'''
self.project = project
- self.bindir = trim_trailing_slashes(bindir)
+ self.builddir = trim_trailing_slashes(builddir)
self.vars = {as_var(k): (o.init, 'default')
for k, o in project.options.iteritems()}
@@ -173,7 +175,9 @@ class Build(object):
''' First apply variables from the environment,
then call apply_arg() a bunch of times, then finish().
'''
- for k in self.vars:
+ for k in self.project.options:
+ if k != as_var(k):
+ continue
val = env.get(k)
if val is not None:
self.vars[k] = (self.project.options[k].type(val), 'environment')
@@ -186,5 +190,11 @@ class Build(object):
def relative_source(self):
''' Return a relative path from the build tree to the source tree.
'''
- return os.path.relpath(os.path.realpath(self.project.srcdir),
- os.path.realpath(self.bindir))
+ srcdir = self.project.srcdir
+ builddir = self.builddir
+ if os.path.isabs(srcdir):
+ return srcdir
+ if os.path.isabs(builddir):
+ return os.path.realpath(srcdir)
+ return os.path.relpath(os.path.realpath(srcdir),
+ os.path.realpath(builddir))
diff --git a/attoconf/help.py b/attoconf/help.py
index 610efde..0fbc0a3 100644
--- a/attoconf/help.py
+++ b/attoconf/help.py
@@ -140,23 +140,23 @@ class Help(object):
'''
self.sections = []
- def add_text(self, text, hidden):
+ def add_text(self, help, hidden):
''' Add a header line.
This creates a new section if the last line wasn't a header.
'''
if not self.sections or self.sections[-1].options:
self.sections.append(HelpSection())
- self.sections[-1].add_text(text, hidden)
+ self.sections[-1].add_text(help, hidden)
- def add_option(self, name, text, hidden):
+ def add_option(self, name, help, hidden):
''' Add an option with its description to the current section.
This creates a new section only if it is first.
'''
if not self.sections:
self.sections.append(HelpSection())
- self.sections[-1].add_option(name, text, hidden)
+ self.sections[-1].add_option(name, help, hidden)
def print(self, file, hidden, width=0):
''' Print all the help at the given level of hidden-ness.
diff --git a/attoconf/lib/__init__.py b/attoconf/lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/attoconf/lib/__init__.py
diff --git a/attoconf/lib/arches.py b/attoconf/lib/arches.py
new file mode 100644
index 0000000..bafef38
--- /dev/null
+++ b/attoconf/lib/arches.py
@@ -0,0 +1,68 @@
+# Copyright 2013 Ben Longbons <b.r.longbons@gmail.com>
+#
+# This file is part of attoconf.
+#
+# attoconf is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# attoconf is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with attoconf. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function, division, absolute_import
+
+from ..classy import ClassyProject
+
+def triple(s):
+ # Triples do not, in fact, follow a regular pattern.
+ # Some have only two segments, some appear to have four ...
+ # Also, sometimes a wrong thing is used as a triple.
+ # All we *really* care about is generating the tool names.
+ if s.startswith('-') or s.endswith('-') or '-' not in s[1:-1]:
+ raise ValueError('Probably not a triple')
+ return s
+
+def host(build, HOST):
+ if HOST is None:
+ BUILD, origin = build.vars['BUILD']
+ if origin != 'default':
+ origin = 'derived from BUILD'
+ build.vars['HOST'] = (BUILD, origin)
+
+def target(build, TARGET):
+ if TARGET is None:
+ HOST, origin = build.vars['HOST']
+ if origin != 'default':
+ origin = 'derived from HOST'
+ build.vars['TARGET'] = (HOST, origin)
+
+class Arches2(ClassyProject):
+ __slots__ = ()
+
+ def arches(self):
+ super(Arches2, self).arches()
+ self.add_help('System types:', hidden=False)
+ self.add_option('--build', init=None,
+ type=triple, check=None,
+ help='configure for building on BUILD', hidden=False,
+ help_def='native')
+ self.add_option('--host', init=None,
+ type=triple, check=host,
+ help='cross-compile to build programs to run on HOST',
+ hidden=False, help_def='BUILD')
+
+# TODO figure out the mro implications when I use this
+class Arches3(Arches2):
+ __slots__ = ()
+ def arches(self):
+ super(Arches3, self).arches()
+ self.add_option('--target', init=None,
+ type=triple, check=target,
+ help='configure for building compilers for TARGET',
+ hidden=False, help_def='HOST')
diff --git a/attoconf/lib/c.py b/attoconf/lib/c.py
new file mode 100644
index 0000000..4d3f252
--- /dev/null
+++ b/attoconf/lib/c.py
@@ -0,0 +1,268 @@
+# Copyright 2013 Ben Longbons <b.r.longbons@gmail.com>
+#
+# This file is part of attoconf.
+#
+# attoconf is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# attoconf is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with attoconf. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function, division, absolute_import
+
+import errno
+import os
+from shlex import split as shell
+import subprocess
+
+from .arches import Arches2
+
+class TestError(Exception):
+ pass
+
+def do_exec(build, args):
+ p = subprocess.Popen(args, cwd=build.builddir,
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ out, _ = p.communicate()
+ retcode = p.wait()
+ return retcode, out
+
+
+class TempFile:
+ ''' context manager that optionally creates and then removes a file
+ '''
+ __slots__ = ('filename')
+
+ def __init__(self, filename, content):
+ self.filename = filename
+ if content is not None:
+ with open(filename, 'wx') as of:
+ of.write(content)
+ else:
+ # TODO: raise OSError(errno.EEXIST) if file already exists
+ pass
+
+ def __enter__(self):
+ pass
+
+ def __exit__(self, type, value, traceback):
+ try:
+ os.remove(self.filename)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+
+
+def try_compile_c(build, body, CFLAGS=[], CPPFLAGS=[]):
+ CC = build.vars['CC'][0]
+ CFLAGS = build.vars['CFLAGS'][0] + CFLAGS
+ CPPFLAGS = build.vars['CPPFLAGS'][0] + CPPFLAGS
+ in_ = 'atto-test.c'
+ ins = [in_]
+ out = 'atto-test.o'
+
+ args = CC + CFLAGS + CPPFLAGS + ['-c', '-o', out, in_]
+ with TempFile(in_, body), TempFile(out, None):
+ status, error = do_exec(build, args)
+ if status:
+ raise TestError(error)
+
+def try_compile_link_c(build, body, CFLAGS=[], CPPFLAGS=[], LDFLAGS=[], LDLIBS=[]):
+ CC = build.vars['CC'][0]
+ CFLAGS = build.vars['CFLAGS'][0] + CFLAGS
+ CPPFLAGS = build.vars['CPPFLAGS'][0] + CPPFLAGS
+ LDFLAGS = build.vars['LDFLAGS'][0] + LDFLAGS
+ LDLIBS = build.vars['LDLIBS'][0] + LDLIBS
+ in_ = 'atto-test.c'
+ ins = [in_]
+ out = 'atto-test'
+
+ args = CC + CFLAGS + CPPFLAGS + LDFLAGS + ins + LDLIBS + ['-o', out]
+ with TempFile(in_, body), TempFile(out, None):
+ status, error = do_exec(build, args)
+ if status:
+ raise TestError(error)
+
+def try_compile_cxx(build, body, CXXFLAGS=[], CPPFLAGS=[]):
+ CXX = build.vars['CXX'][0]
+ CXXFLAGS = build.vars['CXXFLAGS'][0] + CXXFLAGS
+ CPPFLAGS = build.vars['CPPFLAGS'][0] + CPPFLAGS
+ in_ = 'atto-test.cxx'
+ out = 'atto-test.o'
+
+ args = CXX + CXXFLAGS + CPPFLAGS + ['-c', '-o', out, in_]
+ with TempFile(in_, body), TempFile(out, None):
+ status, error = do_exec(build, args)
+ if status:
+ raise TestError(error)
+
+def try_compile_link_cxx(build, body, CXXFLAGS=[], CPPFLAGS=[], LDFLAGS=[], LDLIBS=[]):
+ CXX = build.vars['CXX'][0]
+ CXXFLAGS = build.vars['CXXFLAGS'][0] + CXXFLAGS
+ CPPFLAGS = build.vars['CPPFLAGS'][0] + CPPFLAGS
+ LDFLAGS = build.vars['LDFLAGS'][0] + LDFLAGS
+ LDLIBS = build.vars['LDLIBS'][0] + LDLIBS
+ in_ = 'atto-test.cxx'
+ out = 'atto-test'
+
+ args = CXX + CXXFLAGS + CPPFLAGS + LDFLAGS + ins + LDLIBS + ['-o', out]
+ with TempFile(in_, body), TempFile(out, None):
+ status, error = do_exec(build, args)
+ if status:
+ raise TestError(error)
+
+if 0:
+ def try_linkonly_c(build, ins, LDFLAGS=[], LDLIBS=[]):
+ CC = build.vars['CC'][0]
+ LDFLAGS = build.vars['LDFLAGS'][0] + LDFLAGS
+ LDLIBS = build.vars['LDLIBS'][0] + LDLIBS
+ out = 'atto-test'
+
+ args = CC + LDFLAGS + ins + LDLIBS + ['-o', out]
+ with TempFile(out, None):
+ status, error = do_exec(build, args)
+ if status:
+ raise TestError(error)
+
+def try_compile_link2_c(build, body, CFLAGS=[], CPPFLAGS=[], LDFLAGS=[], LDLIBS=[]):
+ CC = build.vars['CC'][0]
+ CFLAGS = build.vars['CFLAGS'][0] + CFLAGS
+ CPPFLAGS = build.vars['CPPFLAGS'][0] + CPPFLAGS
+ LDFLAGS = build.vars['LDFLAGS'][0] + LDFLAGS
+ LDLIBS = build.vars['LDLIBS'][0] + LDLIBS
+ in_ = 'atto-test.c'
+ ins = [in_]
+ mid = 'atto-test.o'
+ mids = [mid]
+ out = 'atto-test'
+
+ args1 = CC + CFLAGS + CPPFLAGS + ['-c', '-o', mid, in_]
+ args2 = CC + LDFLAGS + mids + LDLIBS + ['-o', out]
+ with TempFile(mid, None):
+ with TempFile(in_, body):
+ status, error = do_exec(build, args1)
+ if status:
+ raise TestError(error)
+
+ with TempFile(out, None):
+ status, error = do_exec(build, args2)
+ if status:
+ raise TestError(error)
+
+if 0:
+ def try_linkonly_cxx(build, ins, LDFLAGS=[], LDLIBS=[]):
+ CXX = build.vars['CXX'][0]
+ LDFLAGS = build.vars['LDFLAGS'][0] + LDFLAGS
+ LDLIBS = build.vars['LDLIBS'][0] + LDLIBS
+ out = 'atto-test'
+
+ args = CXX + LDFLAGS + ins + LDLIBS + ['-o', out]
+ with TempFile(out, None):
+ status, error = do_exec(build, args)
+ if status:
+ raise TestError(error)
+
+def try_compile_link2_cxx(build, body, CXXFLAGS=[], CPPFLAGS=[], LDFLAGS=[], LDLIBS=[]):
+ CXX = build.vars['CXX'][0]
+ CXXFLAGS = build.vars['CXXFLAGS'][0] + CXXFLAGS
+ CPPFLAGS = build.vars['CPPFLAGS'][0] + CPPFLAGS
+ LDFLAGS = build.vars['LDFLAGS'][0] + LDFLAGS
+ LDLIBS = build.vars['LDLIBS'][0] + LDLIBS
+ in_ = 'atto-test.cxx'
+ ins = [in_]
+ mid = 'atto-test.o'
+ mids = [mid]
+ out = 'atto-test'
+
+ args1 = CXX + CXXFLAGS + CPPFLAGS + ['-c', '-o', mid, in_]
+ args2 = CXX + LDFLAGS + mids + LDLIBS + ['-o', out]
+ with TempFile(mid, None):
+ with TempFile(in_, body):
+ status, error = do_exec(build, args1)
+ if status:
+ raise TestError(error)
+
+ with TempFile(out, None):
+ status, error = do_exec(build, args2)
+ if status:
+ raise TestError(error)
+
+
+def ldflags(build, LDFLAGS):
+ pass
+
+def libs(build, LIBS):
+ # Make expects something different
+ build.vars['LDLIBS'] = build.vars['LIBS']
+ del build.vars['LIBS']
+
+def cppflags(build, CPPFLAGS):
+ pass
+
+def cc(build, CC):
+ pass
+
+def cflags(build, CFLAGS):
+ try_compile_c(build, 'int main() {}\n')
+ try_compile_link_c(build, 'int main() {}\n')
+ try_compile_link2_c(build, 'int main() {}\n')
+
+def cxx(build, CXX):
+ pass
+
+def cxxflags(build, CXXFLAGS):
+ try_compile_cxx(build, 'int main() {}\n')
+ try_compile_link_cxx(build, 'int main() {}\n')
+ try_compile_link2_cxx(build, 'int main() {}\n')
+
+class Link(Arches2):
+ __slots__ = ()
+ def vars(self):
+ super(Link, self).vars()
+ self.add_option('LDFLAGS', init=[],
+ type=shell, check=ldflags,
+ help='linker flags, e.g. -L<lib dir> if you have libraries in a nonstandard directory <lib dir>',
+ hidden=False)
+ self.add_option('LIBS', init=[],
+ type=shell, check=libs,
+ help='libraries to pass to the linker, e.g. -l<library>',
+ hidden=False)
+
+class Preprocess(Arches2):
+ __slots__ = ()
+ def vars(self):
+ super(Preprocess, self).vars()
+ self.add_option('CPPFLAGS', init=[],
+ type=shell, check=cppflags,
+ help='C/C++/Objective C preprocessor flags, e.g. -I<include dir> if you have headers in a nonstandard directory <include dir>',
+ hidden=False)
+
+class C(Link, Preprocess):
+ __slots__ = ()
+ def vars(self):
+ super(C, self).vars()
+ self.add_option('CC', init=['gcc'],
+ type=shell, check=cc,
+ help='C compiler command', hidden=False)
+ self.add_option('CFLAGS', init=[],
+ type=shell, check=cflags,
+ help='C compiler flags', hidden=False)
+
+class Cxx(Link, Preprocess):
+ __slots__ = ()
+ def vars(self):
+ super(Cxx, self).vars()
+ self.add_option('CXX', init=['g++'],
+ type=shell, check=cxx,
+ help='C++ compiler command', hidden=False)
+ self.add_option('CXXFLAGS', init=[],
+ type=shell, check=cxxflags,
+ help='C++ compiler flags', hidden=False)
diff --git a/attoconf/lib/install.py b/attoconf/lib/install.py
new file mode 100644
index 0000000..3e135e8
--- /dev/null
+++ b/attoconf/lib/install.py
@@ -0,0 +1,320 @@
+# Copyright 2013 Ben Longbons <b.r.longbons@gmail.com>
+#
+# This file is part of attoconf.
+#
+# attoconf is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# attoconf is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with attoconf. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function, division, absolute_import
+
+import os
+
+from ..classy import ClassyProject
+from ..core import trim_trailing_slashes
+
+
+def word(s):
+ if ' ' in s:
+ raise ValueError('not a word: %s' % s)
+ return s
+
+
+def version(s):
+ if s.startswith('v'):
+ s = s[1:]
+ [int(b) for b in s.split('.')]
+ return s
+
+
+def filepath(s):
+ s = trim_trailing_slashes(s)
+ # must be absolute *and* canonical
+ if s != os.path.abspath(s):
+ raise ValueError('Not an absolute, canonical pathname: %s' % s)
+ return s
+
+
+def exec_prefix(build, EPREFIX):
+ if EPREFIX is None:
+ PREFIX, origin = build.vars['PREFIX']
+ if origin != 'default':
+ origin = 'derived from PREFIX'
+ build.vars['EXEC_PREFIX'] = (PREFIX, origin)
+ # is this a good idea? is there a better way?
+ # how will this interfere with hashing?
+ # at least we don't have to worry about the environment ...
+ build.vars['EPREFIX'] = build.vars['EXEC_PREFIX']
+ del build.vars['EXEC_PREFIX']
+
+def bindir(build, DIR):
+ if DIR is None:
+ EPREFIX, origin = build.vars['EPREFIX']
+ if origin != 'default':
+ origin = 'derived from EPREFIX'
+ build.vars['BINDIR'] = (os.path.join(EPREFIX, 'bin'), origin)
+
+def sbindir(build, DIR):
+ if DIR is None:
+ EPREFIX, origin = build.vars['EPREFIX']
+ if origin != 'default':
+ origin = 'derived from EPREFIX'
+ build.vars['SBINDIR'] = (os.path.join(EPREFIX, 'sbin'), origin)
+
+def libexecdir(build, DIR):
+ if DIR is None:
+ EPREFIX, origin = build.vars['EPREFIX']
+ if origin != 'default':
+ origin = 'derived from EPREFIX'
+ build.vars['LIBEXECDIR'] = (os.path.join(EPREFIX, 'libexec'), origin)
+
+def sysconfdir(build, DIR):
+ if DIR is None:
+ PREFIX, origin = build.vars['PREFIX']
+ if origin != 'default':
+ origin = 'derived from PREFIX'
+ build.vars['SYSCONFDIR'] = (os.path.join(PREFIX, 'etc'), origin)
+
+def sharedstatedir(build, DIR):
+ if DIR is None:
+ PREFIX, origin = build.vars['PREFIX']
+ if origin != 'default':
+ origin = 'derived from PREFIX'
+ build.vars['SHAREDSTATEDIR'] = (os.path.join(PREFIX, 'com'), origin)
+
+def localstatedir(build, DIR):
+ if DIR is None:
+ PREFIX, origin = build.vars['PREFIX']
+ if origin != 'default':
+ origin = 'derived from PREFIX'
+ build.vars['LOCALSTATEDIR'] = (os.path.join(PREFIX, 'var'), origin)
+
+def libdir(build, DIR):
+ if DIR is None:
+ EPREFIX, origin = build.vars['EPREFIX']
+ if origin != 'default':
+ origin = 'derived from EPREFIX'
+ build.vars['LIBDIR'] = (os.path.join(EPREFIX, 'lib'), origin)
+
+def includedir(build, DIR):
+ if DIR is None:
+ PREFIX, origin = build.vars['PREFIX']
+ if origin != 'default':
+ origin = 'derived from PREFIX'
+ build.vars['INCLUDEDIR'] = (os.path.join(PREFIX, 'include'), origin)
+
+def datarootdir(build, DIR):
+ if DIR is None:
+ PREFIX, origin = build.vars['PREFIX']
+ if origin != 'default':
+ origin = 'derived from PREFIX'
+ build.vars['DATAROOTDIR'] = (os.path.join(PREFIX, 'share'), origin)
+
+def datadir(build, DIR):
+ if DIR is None:
+ DATAROOTDIR, origin = build.vars['DATAROOTDIR']
+ if origin != 'default':
+ origin = 'derived from DATAROOTDIR'
+ build.vars['DATADIR'] = (DATAROOTDIR, origin)
+
+def packagedatadir(build, DIR):
+ if DIR is None:
+ DATADIR, origin = build.vars['DATADIR']
+ PACKAGE, prigin = build.vars['PACKAGE']
+ if origin != 'default' or prigin != 'default':
+ origin = 'derived from DATADIR and PACKAGE'
+ build.vars['DATADIR'] = (os.path.join(DATADIR, PACKAGE), origin)
+
+def infodir(build, DIR):
+ if DIR is None:
+ DATAROOTDIR, origin = build.vars['DATAROOTDIR']
+ if origin != 'default':
+ origin = 'derived from DATAROOTDIR'
+ build.vars['INFODIR'] = (os.path.join(DATAROOTDIR, 'info'), origin)
+
+def localedir(build, DIR):
+ if DIR is None:
+ DATAROOTDIR, origin = build.vars['DATAROOTDIR']
+ if origin != 'default':
+ origin = 'derived from DATAROOTDIR'
+ build.vars['LOCALEDIR'] = (os.path.join(DATAROOTDIR, 'locale'), origin)
+
+def mandir(build, DIR):
+ if DIR is None:
+ DATAROOTDIR, origin = build.vars['DATAROOTDIR']
+ if origin != 'default':
+ origin = 'derived from DATAROOTDIR'
+ build.vars['MANDIR'] = (os.path.join(DATAROOTDIR, 'man'), origin)
+
+def docdir(build, DIR):
+ if DIR is None:
+ DATAROOTDIR, origin = build.vars['DATAROOTDIR']
+ PACKAGE, origin2 = build.vars['PACKAGE']
+ if origin != 'default' or origin2 != 'default':
+ origin = 'derived from DATAROOTDIR and PACKAGE'
+ build.vars['DOCDIR'] = (os.path.join(DATAROOTDIR, 'doc', PACKAGE), origin)
+
+def htmldir(build, DIR):
+ if DIR is None:
+ DOCDIR, origin = build.vars['DOCDIR']
+ if origin != 'default':
+ origin = 'derived from DOCDIR'
+ build.vars['HTMLDIR'] = (DOCDIR, origin)
+
+def dvidir(build, DIR):
+ if DIR is None:
+ DOCDIR, origin = build.vars['DOCDIR']
+ if origin != 'default':
+ origin = 'derived from DOCDIR'
+ build.vars['DVIDIR'] = (DOCDIR, origin)
+
+def pdfdir(build, DIR):
+ if DIR is None:
+ DOCDIR, origin = build.vars['DOCDIR']
+ if origin != 'default':
+ origin = 'derived from DOCDIR'
+ build.vars['PDFDIR'] = (DOCDIR, origin)
+
+def psdir(build, DIR):
+ if DIR is None:
+ DOCDIR, origin = build.vars['DOCDIR']
+ if origin != 'default':
+ origin = 'derived from DOCDIR'
+ build.vars['PSDIR'] = (DOCDIR, origin)
+
+
+class Install(ClassyProject):
+ __slots__ = ()
+
+ @classmethod
+ def slots(cls):
+ return super(Install, cls).slots() + (
+ 'package', 'package_version', 'package_name')
+
+ def set_package(self, package, version, name):
+ self.package = package
+ self.package_version = version
+ self.package_name = name
+
+ def general(self):
+ super(Install, self).general()
+ self.add_option('--package', init=self.package,
+ type=word, check=None,
+ help='Short name of this package (don\'t change!)',
+ hidden=True)
+ self.add_option('--package-version', init=self.package_version,
+ type=version, check=None,
+ help='Version of this package (change in configure)',
+ hidden=True,
+ help_var='VERSION')
+ self.add_option('--package-name', init=self.package_name,
+ type=version, check=None,
+ help='Long name of this package (don\'t change)',
+ hidden=True,
+ help_var='NAME')
+
+ def paths(self):
+ super(Install, self).paths()
+
+ self.add_help('Installation directories:', hidden=False)
+ self.add_option('--prefix', init='/usr/local',
+ type=filepath, check=None,
+ help='install architecture-independent files in PREFIX',
+ hidden=False)
+ self.add_option('--exec-prefix', init=None,
+ type=filepath, check=exec_prefix,
+ help='install architecture-dependent files in EPREFIX',
+ hidden=False,
+ help_var='EPREFIX', help_def='PREFIX')
+
+ self.add_help('Fine tuning of the installation directories:',
+ hidden=False)
+ self.add_option('--bindir', init=None,
+ type=filepath, check=bindir,
+ help='user executables', hidden=False,
+ help_var='DIR', help_def='EPREFIX/bin')
+ self.add_option('--sbindir', init=None,
+ type=filepath, check=sbindir,
+ help='system admin executables', hidden=False,
+ help_var='DIR', help_def='EPREFIX/sbin')
+ self.add_option('--libexecdir', init=None,
+ type=filepath, check=libexecdir,
+ help='program executables', hidden=False,
+ help_var='DIR', help_def='EPREFIX/libexec')
+ self.add_option('--sysconfdir', init=None,
+ type=filepath, check=sysconfdir,
+ help='read-only single-machine data', hidden=False,
+ help_var='DIR', help_def='PREFIX/etc')
+ self.add_option('--sharedstatedir', init=None,
+ type=filepath, check=sharedstatedir,
+ help='modifiable architecture-independent data', hidden=False,
+ help_var='DIR', help_def='PREFIX/com')
+ self.add_option('--localstatedir', init=None,
+ type=filepath, check=localstatedir,
+ help='modifiable single-machine data', hidden=False,
+ help_var='DIR', help_def='PREFIX/var')
+ self.add_option('--libdir', init=None,
+ type=filepath, check=libdir,
+ help='object code libraries', hidden=False,
+ help_var='DIR', help_def='EPREFIX/lib')
+ self.add_option('--includedir', init=None,
+ type=filepath, check=includedir,
+ help='C header files', hidden=False,
+ help_var='DIR', help_def='PREFIX/include')
+ self.add_option('--oldincludedir', init='/usr/include',
+ type=filepath, check=None,
+ help='C header files for non-gcc', hidden=False,
+ help_var='DIR')
+ self.add_option('--datarootdir', init=None,
+ type=filepath, check=datarootdir,
+ help='read-only arch.-independent data root', hidden=False,
+ help_var='DIR', help_def='PREFIX/share')
+ self.add_option('--datadir', init=None,
+ type=filepath, check=datadir,
+ help='read-only architecture-independent data', hidden=False,
+ help_var='DIR', help_def='DATAROOTDIR')
+ self.add_option('--packagedatadir', init=None,
+ type=filepath, check=packagedatadir,
+ help='data specific to this package (please set datadir instead)', hidden=False,
+ help_var='DIR', help_def='DATADIR/PACKAGE')
+ self.add_option('--infodir', init=None,
+ type=filepath, check=infodir,
+ help='info documentation', hidden=False,
+ help_var='DIR', help_def='DATAROOTDIR/info')
+ self.add_option('--localedir', init=None,
+ type=filepath, check=localedir,
+ help='locale-dependent data', hidden=False,
+ help_var='DIR', help_def='DATAROOTDIR/locale')
+ self.add_option('--mandir', init=None,
+ type=filepath, check=mandir,
+ help='man documentation', hidden=False,
+ help_var='DIR', help_def='DATAROOTDIR/man')
+ self.add_option('--docdir', init=None,
+ type=filepath, check=docdir,
+ help='documentation root', hidden=False,
+ help_var='DIR', help_def='DATAROOTDIR/doc/PACKAGE')
+ self.add_option('--htmldir', init=None,
+ type=filepath, check=htmldir,
+ help='html documentation', hidden=False,
+ help_var='DIR', help_def='DOCDIR')
+ self.add_option('--dvidir', init=None,
+ type=filepath, check=dvidir,
+ help='dvi documentation', hidden=False,
+ help_var='DIR', help_def='DOCDIR')
+ self.add_option('--pdfdir', init=None,
+ type=filepath, check=pdfdir,
+ help='pdf documentation', hidden=False,
+ help_var='DIR', help_def='DOCDIR')
+ self.add_option('--psdir', init=None,
+ type=filepath, check=psdir,
+ help='ps documentation', hidden=False,
+ help_var='DIR', help_def='DOCDIR')
diff --git a/attoconf/lib/make.py b/attoconf/lib/make.py
new file mode 100644
index 0000000..e54224d
--- /dev/null
+++ b/attoconf/lib/make.py
@@ -0,0 +1,94 @@
+# Copyright 2013 Ben Longbons <b.r.longbons@gmail.com>
+#
+# This file is part of attoconf.
+#
+# attoconf is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# attoconf is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with attoconf. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function, division, absolute_import
+
+import os
+from pipes import quote
+
+from ..classy import ClassyProject
+from ..version import string as version_string
+
+blacklist = frozenset(''.join(chr(i) for i in range(0x20)) + '#$')
+def validate(s):
+ return s == s.strip() and not frozenset(s) & blacklist
+
+
+class MakeHook(object):
+ __slots__ = ('infile', 'outfile')
+ def __init__(self, infile, outfile):
+ self.infile = infile
+ self.outfile = outfile
+
+ def __call__(self, build):
+ if self.outfile is None:
+ # if there are multiple backends
+ print('Skipping generation of a makefile')
+ return
+ with open(os.path.join(build.builddir, self.outfile), 'w') as out:
+ print('Generating a makefile ...')
+ out.write('# This part was generated by %s\n' % version_string)
+ out.write('SRC_DIR = %s\n' % build.relative_source())
+ out.write('\n')
+ # TODO preserve *original* order?
+ for var, (val, origin) in sorted(build.vars.iteritems()):
+ if val is None:
+ if origin == 'default':
+ continue
+ # is it a good idea for Nones to survive this long?
+ # especially conditional ones ...
+ var = '# ' + var
+ val = 'not defined'
+ elif isinstance(val, list):
+ val = ' '.join(quote(a) for a in val)
+ elif isinstance(val, str):
+ val = quote(val)
+ else:
+ print('Assuming it\'s safe to print an instance of',
+ type(val).__name__, '...')
+ out.write('%s = %s # %s\n' % (var, val, origin))
+ if self.infile is not None:
+ out.write('\n# The rest was copied from %s\n' % self.infile)
+ infile = os.path.join(build.project.srcdir, self.infile)
+ with open(infile) as in_:
+ for line in in_:
+ assert line.endswith('\n')
+ out.write(line)
+
+
+class Make(ClassyProject):
+ ''' Post hook to generate a Makefile from Makefile.in
+ '''
+ __slots__ = ()
+ @classmethod
+ def slots(cls):
+ return super(Make, cls).slots() + ('make_in', 'make_out')
+
+ def __init__(self, srcdir):
+ super(Make, self).__init__(srcdir)
+ self.set_make_infile('Makefile.in')
+ self.set_make_outfile('Makefile') # relative to build dir
+
+ def set_make_infile(self, ipath):
+ self.make_in = ipath
+
+ def set_make_outfile(self, opath):
+ self.make_out = opath
+
+ def post(self):
+ super(Make, self).post()
+ self.checks.append(MakeHook(self.make_in, self.make_out))
diff --git a/attoconf/tests/test_core.py b/attoconf/tests/test_core.py
index 88bf616..f9854e8 100644
--- a/attoconf/tests/test_core.py
+++ b/attoconf/tests/test_core.py
@@ -47,7 +47,8 @@ class TestProject(unittest.TestCase):
help='display some subset of help', hidden=False,
help_var='TYPE')
proj.help.add_option('--help=hidden',
- 'display help you should never ever ever care about', True)
+ help='display help you should never ever ever care about',
+ hidden=True)
proj.add_option('--foo', init='asdf',
type=str, check=None,
help='set frob target', hidden=False)
@@ -89,7 +90,7 @@ General:
proj = Project('foo/')
build = Build(proj, 'bar/')
self.assertEquals(build.project.srcdir, 'foo')
- self.assertEquals(build.bindir, 'bar')
+ self.assertEquals(build.builddir, 'bar')
self.assertEquals(build.relative_source(), '../foo')
def test_configure(self):
@@ -119,7 +120,12 @@ General:
help='help for string VAR', hidden=False)
build = Build(proj, '.')
- build.configure(['--alias'], {'VAR': 'value'})
+ build.configure(['--alias'],
+ {
+ 'VAR': 'value',
+ 'QUX': 'a',
+ '--qux': 'b',
+ })
self.assertEqual(build.vars,
{
'FOO': ('B', 'command-line'),
diff --git a/demo-project/Makefile.in b/demo-project/Makefile.in
new file mode 100644
index 0000000..8f3f4f0
--- /dev/null
+++ b/demo-project/Makefile.in
@@ -0,0 +1,9 @@
+vpath %.c ${SRC_DIR}
+vpath %.h ${SRC_DIR}
+
+hello: main.o hello.o
+main.o: main.c hello.h
+hello.o: hello.c hello.h
+
+clean:
+ rm -f hello main.o hello.o
diff --git a/demo-project/configure b/demo-project/configure
index dc29870..a3e49ee 100755
--- a/demo-project/configure
+++ b/demo-project/configure
@@ -23,17 +23,53 @@ import os
import sys
from attoconf.version import require_version, string as version_string
-require_version(0, 1)
+require_version(0, 2)
-from attoconf.core import Project, Build
+from attoconf.classy import add_slots
+from attoconf.lib.c import C
+from attoconf.lib.install import Install
+from attoconf.lib.make import Make
-def main():
- print('Using', version_string)
- src = Project(os.path.dirname(sys.argv[0]))
- # customizations go here
+@add_slots
+class Configuration(C, Install, Make):
+ # usually you'll only have vars, features, and packages
+ # the rest should only be inherited
+ def __init__(self, srcdir):
+ super(Configuration, self).__init__(srcdir)
+
+ def general(self):
+ super(Configuration, self).general()
+
+ def paths(self):
+ super(Configuration, self).paths()
+
+ def arches(self):
+ super(Configuration, self).arches()
+
+ def vars(self):
+ super(Configuration, self).vars()
+
+ def features(self):
+ super(Configuration, self).features()
- # the rest shouldn't change
- build = Build(src, '.')
+ def packages(self):
+ super(Configuration, self).packages()
+
+ if 0:
+ def tests(self):
+ super(Configuration, self).tests()
+
+ def post(self):
+ super(Configuration, self).post()
+
+
+def main():
+ proj = Configuration(os.path.dirname(sys.argv[0]))
+ proj.set_package('attoconf-demo', version_string.split(' ')[1], 'Demo project for attoconf')
+ proj.jiggle()
+ build = proj.build('.')
build.configure(sys.argv[1:], os.environ)
-main()
+
+if __name__ == '__main__':
+ main()
diff --git a/demo-project/header.c b/demo-project/header.c
new file mode 100644
index 0000000..f231086
--- /dev/null
+++ b/demo-project/header.c
@@ -0,0 +1,16 @@
+// Copyright 2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of attoconf.
+//
+// attoconf is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// attoconf is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with attoconf. If not, see <http://www.gnu.org/licenses/>.
diff --git a/demo-project/hello.c b/demo-project/hello.c
new file mode 100644
index 0000000..b018dc3
--- /dev/null
+++ b/demo-project/hello.c
@@ -0,0 +1,25 @@
+// Copyright 2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of attoconf.
+//
+// attoconf is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// attoconf is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with attoconf. If not, see <http://www.gnu.org/licenses/>.
+
+#include "hello.h"
+
+#include <stdio.h>
+
+void hello(void)
+{
+ puts("Hello, World!");
+}
diff --git a/demo-project/hello.h b/demo-project/hello.h
new file mode 100644
index 0000000..899b9cc
--- /dev/null
+++ b/demo-project/hello.h
@@ -0,0 +1,22 @@
+#ifndef HELLO_H
+#define HELLO_H
+// Copyright 2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of attoconf.
+//
+// attoconf is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// attoconf is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with attoconf. If not, see <http://www.gnu.org/licenses/>.
+
+void hello(void);
+
+#endif // HELLO_H
diff --git a/demo-project/main.c b/demo-project/main.c
new file mode 100644
index 0000000..2d363e2
--- /dev/null
+++ b/demo-project/main.c
@@ -0,0 +1,24 @@
+// Copyright 2013 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of attoconf.
+//
+// attoconf is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// attoconf is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with attoconf. If not, see <http://www.gnu.org/licenses/>.
+
+#include "hello.h"
+
+int main()
+{
+ hello();
+ return 0;
+}
diff --git a/test-everything.sh b/test-everything.sh
index e16866b..64fc89d 100755
--- a/test-everything.sh
+++ b/test-everything.sh
@@ -1,2 +1,9 @@
-#!/bin/sh
+#!/bin/sh -e
python -m unittest discover "$@"
+mkdir -p demo-build
+cd demo-build
+../demo-project/configure
+make
+./hello
+cd ..
+rm -r demo-build