2
0
mirror of https://gitlab.com/ita1024/waf.git synced 2024-11-22 01:46:15 +01:00

conf.check generalization using arbitrary build functions

This commit is contained in:
Thomas Nagy 2014-09-14 10:58:29 +02:00
parent 08c4b175bf
commit d491de7486
No known key found for this signature in database
GPG Key ID: 67A565EDFDF90E64
6 changed files with 176 additions and 136 deletions

View File

@ -1,6 +1,6 @@
#! /usr/bin/env python
# encoding: utf-8
# Thomas Nagy, 2005, 2010 (ita)
# Thomas Nagy, 2005, 2014 (ita)
VERSION='1.0.0'
APPNAME='tex_test'
@ -15,6 +15,15 @@ def configure(conf):
if not conf.env.MAKEGLOSSARIES:
conf.fatal('could not find the program makeglossaries which is absolutely required for this example')
# and example of a complex configuration test
def build_latex_test(bld):
def write_tex(tsk):
tsk.outputs[0].write(r'''\documentclass[a4paper,12pt]{article} \usepackage{ucs} \begin{document} test \end{document} ''')
bld(rule=write_tex, target='main.tex')
bld(features='tex', type='pdflatex', source='main.tex', prompt=0)
conf.test(build_fun=build_latex_test, msg='Checking for UCS', okmsg='ok', errmsg='ucs.sty is missing install latex-extras')
def build(bld):
bld.recurse('src')

View File

@ -68,7 +68,7 @@ image::classes_build{PIC}["Build classes"{backend@docbook:,width=500:},align="ce
==== Context commands and recursion
The context commands are designed to be as independent as possible, and may be executed concurrently. The main application is the execution of small builds as part of configuration tests. For example, the method 'waflib.Tools.c_config.run_c_code' creates a private build context internally to perform the tests.
The context commands are designed to be as independent as possible, and may be executed concurrently. The main application is the execution of small builds as part of configuration tests. For example, the method 'waflib.Configure.run_build' creates a private build context internally to perform the tests.
Here is an example of a build that creates and executes simple configuration contexts concurrently:
// architecture_link

View File

@ -493,10 +493,6 @@ Configuration methods
* ret_msg_
.. _run_c_code: tools/c_config.html#waflib.Tools.c_config.run_c_code
* run_c_code_
.. _scc_common_flags: tools/suncc.html#waflib.Tools.suncc.scc_common_flags
* scc_common_flags_

View File

@ -12,7 +12,7 @@ A :py:class:`waflib.Configure.ConfigurationContext` instance is created when ``w
* hold configuration routines such as ``find_program``, etc
"""
import os, shlex, sys, time, re
import os, shlex, sys, time, re, shutil
from waflib import ConfigSet, Utils, Options, Logs, Context, Build, Errors
try:
@ -571,3 +571,143 @@ def find_binary(self, filenames, exts, paths):
return x
return None
@conf
def run_build(self, *k, **kw):
"""
Create a temporary build context to execute a build. A reference to that build
context is kept on self.test_bld for debugging purposes, and you should not rely
on it too much (read the note on the cache below).
The parameters given in the arguments to this function are passed as arguments for
a single task generator created in the build. Only three parameters are obligatory:
:param features: features to pass to a task generator created in the build
:type features: list of string
:param compile_filename: file to create for the compilation (default: *test.c*)
:type compile_filename: string
:param code: code to write in the filename to compile
:type code: string
Though this function returns *0* by default, the build may set an attribute named *retval* on the
build context object to return a particular value. See :py:func:`waflib.Tools.c_config.test_exec_fun` for example.
This function also provides a limited cache. To use it, provide the following option::
def options(opt):
opt.add_option('--confcache', dest='confcache', default=0,
action='count', help='Use a configuration cache')
And execute the configuration with the following command-line::
$ waf configure --confcache
"""
lst = [str(v) for (p, v) in kw.items() if p != 'env']
h = Utils.h_list(lst)
dir = self.bldnode.abspath() + os.sep + (not Utils.is_win32 and '.' or '') + 'conf_check_' + Utils.to_hex(h)
try:
os.makedirs(dir)
except OSError:
pass
try:
os.stat(dir)
except OSError:
self.fatal('cannot use the configuration test folder %r' % dir)
cachemode = getattr(Options.options, 'confcache', None)
if cachemode == 1:
try:
proj = ConfigSet.ConfigSet(os.path.join(dir, 'cache_run_build'))
except OSError:
pass
except IOError:
pass
else:
ret = proj['cache_run_build']
if isinstance(ret, str) and ret.startswith('Test does not build'):
self.fatal(ret)
return ret
bdir = os.path.join(dir, 'testbuild')
if not os.path.exists(bdir):
os.makedirs(bdir)
self.test_bld = bld = Build.BuildContext(top_dir=dir, out_dir=bdir)
bld.init_dirs()
bld.progress_bar = 0
bld.targets = '*'
bld.logger = self.logger
bld.all_envs.update(self.all_envs) # not really necessary
bld.env = kw['env']
# OMG huge hack
bld.kw = kw
bld.conf = self
kw['build_fun'](bld)
ret = -1
try:
try:
bld.compile()
except Errors.WafError:
ret = 'Test does not build: %s' % Utils.ex_stack()
self.fatal(ret)
else:
ret = getattr(bld, 'retval', 0)
finally:
if cachemode == 1:
# cache the results each time
proj = ConfigSet.ConfigSet()
proj['cache_run_build'] = ret
proj.store(os.path.join(dir, 'cache_run_build'))
else:
shutil.rmtree(dir)
return ret
@conf
def ret_msg(self, msg, args):
if isinstance(msg, str):
return msg
return msg(args)
@conf
def test(self, *k, **kw):
if not 'env' in kw:
kw['env'] = self.env.derive()
# validate_c for example
if kw.get('validate', None):
kw['validate'](kw)
self.start_msg(kw['msg'], **kw)
ret = None
try:
ret = self.run_build(*k, **kw)
except self.errors.ConfigurationError:
self.end_msg(kw['errmsg'], 'YELLOW', **kw)
if Logs.verbose > 1:
raise
else:
self.fatal('The configuration failed')
else:
kw['success'] = ret
if kw.get('post_check', None):
ret = kw['post_check'](kw)
if ret:
self.end_msg(kw['errmsg'], 'YELLOW', **kw)
self.fatal('The configuration failed %r' % ret)
else:
self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw)
return ret

View File

@ -6,7 +6,7 @@
C/C++/D configuration helpers
"""
import os, re, shlex, shutil, sys
import os, re, shlex, sys
from waflib import Build, Utils, Task, Options, Logs, Errors, ConfigSet, Runner
from waflib.TaskGen import after_method, feature
from waflib.Configure import conf
@ -196,12 +196,6 @@ def parse_flags(self, line, uselib_store, env=None, force_static=False, posix=No
elif x.endswith('.a') or x.endswith('.so') or x.endswith('.dylib') or x.endswith('.lib'):
appu('LINKFLAGS_' + uselib, [x]) # not cool, #762
@conf
def ret_msg(self, f, kw):
if isinstance(f, str):
return f
return f(kw)
@conf
def validate_cfg(self, kw):
"""
@ -404,10 +398,23 @@ def check_cfg(self, *k, **kw):
return ret
def build_fun(bld):
if bld.kw['compile_filename']:
node = bld.srcnode.make_node(bld.kw['compile_filename'])
node.write(bld.kw['code'])
o = bld(features=bld.kw['features'], source=bld.kw['compile_filename'], target='testprog')
for k, v in bld.kw.items():
setattr(o, k, v)
if not bld.kw.get('quiet', None):
bld.conf.to_log("==>\n%s\n<==" % bld.kw['code'])
@conf
def validate_c(self, kw):
"""
pre-check the parameters that will be given to run_c_code
pre-check the parameters that will be given to :py:func:`waflib.Configure.run_build`
:param compiler: c or cxx (tries to guess what is best)
:type compiler: string
@ -433,6 +440,9 @@ def validate_c(self, kw):
:type auto_add_header_name: bool
"""
if not 'build_fun' in kw:
kw['build_fun'] = build_fun
if not 'env' in kw:
kw['env'] = self.env.derive()
env = kw['env']
@ -634,15 +644,15 @@ def post_check(self, *k, **kw):
@conf
def check(self, *k, **kw):
"""
Perform a configuration test by calling :py:func:`waflib.Tools.c_config.run_c_code`.
Perform a configuration test by calling :py:func:`waflib.Configure.run_build`.
For the complete list of parameters, see :py:func:`waflib.Tools.c_config.validate_c`.
To force a specific compiler, prefer the methods :py:func:`waflib.Tools.c_config.check_cxx` or :py:func:`waflib.Tools.c_config.check_cc`
To force a specific compiler, pass "compiler='c'" or "compiler='cxx'" in the arguments
"""
self.validate_c(kw)
self.start_msg(kw['msg'], **kw)
ret = None
try:
ret = self.run_c_code(*k, **kw)
ret = self.run_build(*k, **kw)
except self.errors.ConfigurationError:
self.end_msg(kw['errmsg'], 'YELLOW', **kw)
if Logs.verbose > 1:
@ -695,130 +705,15 @@ def test_exec_fun(self):
"""
self.create_task('test_exec', self.link_task.outputs[0])
CACHE_RESULTS = 1
COMPILE_ERRORS = 2
@conf
def run_c_code(self, *k, **kw):
"""
Create a temporary build context to execute a build. A reference to that build
context is kept on self.test_bld for debugging purposes, and you should not rely
on it too much (read the note on the cache below).
The parameters given in the arguments to this function are passed as arguments for
a single task generator created in the build. Only three parameters are obligatory:
:param features: features to pass to a task generator created in the build
:type features: list of string
:param compile_filename: file to create for the compilation (default: *test.c*)
:type compile_filename: string
:param code: code to write in the filename to compile
:type code: string
Though this function returns *0* by default, the build may set an attribute named *retval* on the
build context object to return a particular value. See :py:func:`waflib.Tools.c_config.test_exec_fun` for example.
This function also provides a limited cache. To use it, provide the following option::
def options(opt):
opt.add_option('--confcache', dest='confcache', default=0,
action='count', help='Use a configuration cache')
And execute the configuration with the following command-line::
$ waf configure --confcache
"""
lst = [str(v) for (p, v) in kw.items() if p != 'env']
h = Utils.h_list(lst)
dir = self.bldnode.abspath() + os.sep + (not Utils.is_win32 and '.' or '') + 'conf_check_' + Utils.to_hex(h)
try:
os.makedirs(dir)
except OSError:
pass
try:
os.stat(dir)
except OSError:
self.fatal('cannot use the configuration test folder %r' % dir)
cachemode = getattr(Options.options, 'confcache', None)
if cachemode == CACHE_RESULTS:
try:
proj = ConfigSet.ConfigSet(os.path.join(dir, 'cache_run_c_code'))
except OSError:
pass
except IOError:
pass
else:
ret = proj['cache_run_c_code']
if isinstance(ret, str) and ret.startswith('Test does not build'):
self.fatal(ret)
return ret
bdir = os.path.join(dir, 'testbuild')
if not os.path.exists(bdir):
os.makedirs(bdir)
self.test_bld = bld = Build.BuildContext(top_dir=dir, out_dir=bdir)
bld.init_dirs()
bld.progress_bar = 0
bld.targets = '*'
if kw['compile_filename']:
node = bld.srcnode.make_node(kw['compile_filename'])
node.write(kw['code'])
bld.logger = self.logger
bld.all_envs.update(self.all_envs) # not really necessary
bld.env = kw['env']
o = bld(features=kw['features'], source=kw['compile_filename'], target='testprog')
for k, v in kw.items():
setattr(o, k, v)
if not kw.get('quiet', None):
self.to_log("==>\n%s\n<==" % kw['code'])
# compile the program
bld.targets = '*'
ret = -1
try:
try:
bld.compile()
except Errors.WafError:
ret = 'Test does not build: %s' % Utils.ex_stack()
self.fatal(ret)
else:
ret = getattr(bld, 'retval', 0)
finally:
if cachemode == CACHE_RESULTS:
# cache the results each time
proj = ConfigSet.ConfigSet()
proj['cache_run_c_code'] = ret
proj.store(os.path.join(dir, 'cache_run_c_code'))
else:
shutil.rmtree(dir)
return ret
@conf
def check_cxx(self, *k, **kw):
"""
Same as :py:func:`waflib.Tools.c_config.check` but default to the *c++* programming language
"""
# DO NOT USE
kw['compiler'] = 'cxx'
return self.check(*k, **kw)
@conf
def check_cc(self, *k, **kw):
"""
Same as :py:func:`waflib.Tools.c_config.check` but default to the *c* programming language
"""
# DO NOT USE
kw['compiler'] = 'c'
return self.check(*k, **kw)

View File

@ -37,7 +37,7 @@ int main(int argc, char **argv) {
@before_method('process_source')
def link_lib_test_fun(self):
"""
The configuration test :py:func:`waflib.Tools.ccroot.run_c_code` declares a unique task generator,
The configuration test :py:func:`waflib.Configure.run_build` declares a unique task generator,
so we need to create other task generators from here to check if the linker is able to link libraries.
"""
def write_test_file(task):