diff --git a/build.py b/build.py index 7b05f1909..3cce77173 100644 --- a/build.py +++ b/build.py @@ -742,6 +742,8 @@ class CustomTarget: else: self.install = False self.build_always = kwargs.get('build_always', False) + if not isinstance(self.build_always, bool): + raise InvalidArguments('Argument build_always must be a boolean.') def get_basename(self): return self.name diff --git a/dependencies.py b/dependencies.py index 2e48ac673..e3dfc74d7 100644 --- a/dependencies.py +++ b/dependencies.py @@ -72,7 +72,8 @@ class Dependency(): class PkgConfigDependency(Dependency): pkgconfig_found = None - def __init__(self, name, required): + def __init__(self, name, kwargs): + required = kwargs.get('required', True) Dependency.__init__(self) self.name = name if PkgConfigDependency.pkgconfig_found is None: @@ -92,9 +93,19 @@ class PkgConfigDependency(Dependency): self.cargs = [] self.libs = [] else: - mlog.log('Dependency', mlog.bold(name), 'found:', mlog.green('YES')) - self.is_found = True self.modversion = out.decode().strip() + mlog.log('Dependency', mlog.bold(name), 'found:', mlog.green('YES'), self.modversion) + version_requirement = kwargs.get('version', None) + if version_requirement is None: + self.is_found = True + else: + if not isinstance(version_requirement, str): + raise DependencyException('Version argument must be string.') + self.is_found = mesonlib.version_compare(self.modversion, version_requirement) + if not self.is_found and required: + raise DependencyException('Invalid version of a dependency, needed %s %s found %s.' % (name, version_requirement, self.modversion)) + if not self.is_found: + return p = subprocess.Popen(['pkg-config', '--cflags', name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out = p.communicate()[0] @@ -521,7 +532,7 @@ class Qt5Dependency(Dependency): if isinstance(mods, str): mods = [mods] for module in mods: - self.modules.append(PkgConfigDependency('Qt5' + module, False)) + self.modules.append(PkgConfigDependency('Qt5' + module, kwargs)) if len(self.modules) == 0: raise DependencyException('No Qt5 modules specified.') if not qt5toolinfo_printed: @@ -795,7 +806,7 @@ def find_external_dependency(name, kwargs): pkg_exc = None pkgdep = None try: - pkgdep = PkgConfigDependency(name, required) + pkgdep = PkgConfigDependency(name, kwargs) if pkgdep.found(): return pkgdep except Exception as e: diff --git a/interpreter.py b/interpreter.py index cd36d2915..75a413541 100644 --- a/interpreter.py +++ b/interpreter.py @@ -1,4 +1,4 @@ -# Copyright 2012-2014 The Meson development team +# Copyright 2012-2015 The Meson development team # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -726,8 +726,7 @@ class Interpreter(): 'get_option' : self.func_get_option, 'subproject' : self.func_subproject, 'pkgconfig_gen' : self.func_pkgconfig_gen, - 'vcs_info' : self.func_vcs_info, - 'vcs_configure' : self.func_vcs_configure, + 'vcs_tag' : self.func_vcs_tag, } def get_build_def_files(self): @@ -931,6 +930,7 @@ class Interpreter(): raise InterpreterException('Subproject directory does not exist and can not be downloaded.') subdir = os.path.join('subprojects', resolved) abs_subdir = os.path.join(self.build.environment.get_source_dir(), 'subprojects', subdir) + os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) self.global_args_frozen = True mlog.log('\nExecuting subproject ', mlog.bold(dirname), '.\n', sep='') subi = Interpreter(self.build, dirname, subdir) @@ -1113,6 +1113,16 @@ class Interpreter(): def func_jar(self, node, args, kwargs): return self.build_target(node, args, kwargs, JarHolder) + def func_vcs_tag(self, node, args, kwargs): + fallback = kwargs.get('fallback', None) + if not isinstance(fallback, str): + raise InterpreterException('Keyword argument must exist and be a string.') + del kwargs['fallback'] + scriptfile = os.path.join(os.path.split(__file__)[0], 'vcstagger.py') + kwargs['command'] = [sys.executable, scriptfile, '@INPUT@', '@OUTPUT@', fallback] + kwargs['build_always'] = True + return self.func_custom_target(node, ['vcstag'], kwargs) + def func_custom_target(self, node, args, kwargs): if len(args) != 1: raise InterpreterException('Incorrect number of arguments') diff --git a/manual tests/2 multiwrap/meson.build b/manual tests/2 multiwrap/meson.build new file mode 100644 index 000000000..81337ada9 --- /dev/null +++ b/manual tests/2 multiwrap/meson.build @@ -0,0 +1,18 @@ +project('multiwrap', 'c') + +# Using multiple downloaded projects for great justice. + +if meson.get_compiler('c').get_id() != 'msvc' + add_global_arguments('-std=c99', language : 'c') + extra_libs = ['-lm'] +else + extra_libs = [] +endif + +luap = subproject('lua') +pngp = subproject('libpng') + +executable('prog', 'prog.c', +include_directories : [pngp.get_variable('incdir'), luap.get_variable('incdir')], +link_with :[pngp.get_variable('libpng'), luap.get_variable('lualib')], +link_args : extra_libs) diff --git a/manual tests/2 multiwrap/prog.c b/manual tests/2 multiwrap/prog.c new file mode 100644 index 000000000..dd0349e2d --- /dev/null +++ b/manual tests/2 multiwrap/prog.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#if !defined(_MSC_VER) +#include +#endif + +static void *l_alloc (void *ud, void *ptr, size_t osize, + size_t nsize) { + (void)ud; + (void)osize; + if (nsize == 0) { + free(ptr); + return NULL; + } else { + return realloc(ptr, nsize); + } +} + +void open_image(const char *fname) { + png_image image; + + memset(&image, 0, (sizeof image)); + image.version = PNG_IMAGE_VERSION; + + if(png_image_begin_read_from_file(&image, fname) != 0) { + png_bytep buffer; + + image.format = PNG_FORMAT_RGBA; + buffer = malloc(PNG_IMAGE_SIZE(image)); + + if(png_image_finish_read(&image, NULL, buffer, 0, NULL) != 0) { + printf("Image %s read failed: %s\n", fname, image.message); + } +// png_free_image(&image); + free(buffer); + } else { + printf("Image %s open failed: %s", fname, image.message); + } +} + +int printer(lua_State *l) { + if(!lua_isstring(l, 1)) { + fprintf(stderr, "Incorrect call.\n"); + return 0; + } + open_image(lua_tostring(l, 1)); + return 0; +} + + +int main(int argc, char **argv) { + lua_State *l = lua_newstate(l_alloc, NULL); + if(!l) { + printf("Lua state allocation failed.\n"); + return 1; + } + lua_register(l, "printer", printer); + lua_getglobal(l, "printer"); + lua_pushliteral(l, "foobar.png"); + lua_call(l, 1, 0); + lua_close(l); + return 0; +} diff --git a/manual tests/2 multiwrap/subprojects/libpng.wrap b/manual tests/2 multiwrap/subprojects/libpng.wrap new file mode 100644 index 000000000..bd6508040 --- /dev/null +++ b/manual tests/2 multiwrap/subprojects/libpng.wrap @@ -0,0 +1,11 @@ +[mesonwrap] + +directory = libpng-1.6.16 + +source_url = ftp://ftp.simplesystems.org/pub/libpng/png/src/libpng16/libpng-1.6.16.tar.gz +source_filename = libpng-1.6.16.tar.gz +source_hash = 02f96b6bad5a381d36d7ba7a5d9be3b06f7fe6c274da00707509c23592a073ad + +patch_url = https://dl.dropboxusercontent.com/u/37517477/libpng-meson.tar.gz +patch_filename = libpng-meson.tar.gz +patch_hash = b91d1abb19711a5aaa4b8581000df0e15420e46d9ce6ecf688e33144ea688f06 diff --git a/manual tests/2 multiwrap/subprojects/lua.wrap b/manual tests/2 multiwrap/subprojects/lua.wrap new file mode 100644 index 000000000..da2ca1468 --- /dev/null +++ b/manual tests/2 multiwrap/subprojects/lua.wrap @@ -0,0 +1,10 @@ +[mesonwrap] +directory = lua-5.3.0 + +source_url = http://www.lua.org/ftp/lua-5.3.0.tar.gz +source_filename = lua-5.3.0.tar.gz +source_hash = ae4a5eb2d660515eb191bfe3e061f2b8ffe94dce73d32cfd0de090ddcc0ddb01 + +patch_url = https://dl.dropboxusercontent.com/u/37517477/lua53-meson.zip +patch_filename = lua53-meson.zip +patch_hash = 076d0d57d33ec996c556722c8eeb624a364c66fe9d2225e590b1bc9ae34fbd6e \ No newline at end of file diff --git a/manual tests/2 multiwrap/subprojects/zlib.wrap b/manual tests/2 multiwrap/subprojects/zlib.wrap new file mode 100644 index 000000000..7ea335428 --- /dev/null +++ b/manual tests/2 multiwrap/subprojects/zlib.wrap @@ -0,0 +1,10 @@ +[mesonwrap] +directory = zlib-1.2.8 + +source_url = http://zlib.net/zlib-1.2.8.tar.gz +source_filename = zlib-1.2.8.tar.gz +source_hash = 36658cb768a54c1d4dec43c3116c27ed893e88b02ecfcb44f2166f9c0b7f2a0d + +patch_url = https://dl.dropboxusercontent.com/u/37517477/zlib128-meson.tar.gz +patch_filename = zlib128-meson.tar.gz +patch_hash = 03c868bf22d7e35c978e8b9572c4aea1181606c15c3dd19f0713a8479fe27edc diff --git a/mesonlib.py b/mesonlib.py index c30057e29..7e1577047 100644 --- a/mesonlib.py +++ b/mesonlib.py @@ -14,7 +14,7 @@ """A library of random helper functionality.""" -import platform, subprocess +import platform, subprocess, operator def is_osx(): return platform.system().lower() == 'darwin' @@ -41,3 +41,31 @@ def exe_exists(arglist): except FileNotFoundError: pass return False + +def version_compare(vstr1, vstr2): + if vstr2.startswith('>='): + cmpop = operator.ge + vstr2 = vstr2[2:] + elif vstr2.startswith('<='): + cmpop = operator.le + vstr2 = vstr2[2:] + elif vstr2.startswith('!='): + cmpop = operator.ne + vstr2 = vstr2[2:] + elif vstr2.startswith('=='): + cmpop = operator.eq + vstr2 = vstr2[2:] + elif vstr2.startswith('='): + cmpop = operator.eq + vstr2 = vstr2[1:] + elif vstr2.startswith('>'): + cmpop = operator.gt + vstr2 = vstr2[1:] + elif vstr2.startswith('<'): + cmpop = operator.lt + vstr2 = vstr2[1:] + else: + cmpop = operator.eq + varr1 = [int(x) for x in vstr1.split('.')] + varr2 = [int(x) for x in vstr2.split('.')] + return cmpop(varr1, varr2) diff --git a/test cases/common/72 build always/main.c b/test cases/common/72 build always/main.c new file mode 100644 index 000000000..f8d9ac971 --- /dev/null +++ b/test cases/common/72 build always/main.c @@ -0,0 +1,7 @@ +#include +#include"version.h" + +int main(int argc, char **argv) { + printf("Version is %s.\n", version_string); + return 0; +} diff --git a/test cases/common/72 build always/meson.build b/test cases/common/72 build always/meson.build new file mode 100644 index 000000000..7cb2e4b0a --- /dev/null +++ b/test cases/common/72 build always/meson.build @@ -0,0 +1,15 @@ +project('run always', 'c') + +version = '1.0.0' + +vgen = find_program('version_gen.py') + +version_src = custom_target('Version string', +input : 'version.c.in', +output : 'version.c', +command : [vgen, '@INPUT@', '@OUTPUT@', version], +build_always : true, +) + +executable('versionprinter', 'main.c', version_src, +include_directories : include_directories('.')) diff --git a/test cases/common/72 build always/version.c.in b/test cases/common/72 build always/version.c.in new file mode 100644 index 000000000..619e51786 --- /dev/null +++ b/test cases/common/72 build always/version.c.in @@ -0,0 +1,3 @@ +#include"version.h" + +const char *version_string = "@VERSION@"; diff --git a/test cases/common/72 build always/version.h b/test cases/common/72 build always/version.h new file mode 100644 index 000000000..d3fe5c695 --- /dev/null +++ b/test cases/common/72 build always/version.h @@ -0,0 +1,3 @@ +#pragma once + +const char *version_string; diff --git a/test cases/common/72 build always/version_gen.py b/test cases/common/72 build always/version_gen.py new file mode 100755 index 000000000..496245540 --- /dev/null +++ b/test cases/common/72 build always/version_gen.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +import sys, os, subprocess + +def generate(infile, outfile, fallback): + workdir = os.path.split(infile)[0] + if workdir == '': + workdir = '.' + p = subprocess.Popen(['git', 'describe'], cwd=workdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdo, _) = p.communicate() + # If we are working off an extracted tarball, git version number is not available. + if p.returncode == 0: + version = stdo.decode().strip() + else: + version = fallback + newdata = open(infile).read().replace('@VERSION@', version) + try: + olddata = open(outfile).read() + if olddata == newdata: + return + except Exception: + pass + open(outfile, 'w').write(newdata) + +if __name__ == '__main__': + infile = sys.argv[1] + outfile = sys.argv[2] + fallback = sys.argv[3] + generate(infile, outfile, fallback) diff --git a/test cases/common/73 vcstag/meson.build b/test cases/common/73 vcstag/meson.build new file mode 100644 index 000000000..23ff487f2 --- /dev/null +++ b/test cases/common/73 vcstag/meson.build @@ -0,0 +1,8 @@ +project('vcstag', 'c') + +version_src = vcs_tag(input : 'vcstag.c.in', +output : 'vcstag.c', +fallback : '1.0.0') + +executable('tagprog', 'tagprog.c', version_src) + diff --git a/test cases/common/73 vcstag/tagprog.c b/test cases/common/73 vcstag/tagprog.c new file mode 100644 index 000000000..34146b43e --- /dev/null +++ b/test cases/common/73 vcstag/tagprog.c @@ -0,0 +1,9 @@ +#include + +const char *vcstag; + +int main(int argc, char **argv) { + printf("Version is %s\n", vcstag); + return 0; +} + diff --git a/test cases/common/73 vcstag/vcstag.c.in b/test cases/common/73 vcstag/vcstag.c.in new file mode 100644 index 000000000..09192d90d --- /dev/null +++ b/test cases/common/73 vcstag/vcstag.c.in @@ -0,0 +1,2 @@ +const char *vcstag = "@VCS_TAG@"; + diff --git a/test cases/linuxlike/1 pkg-config/meson.build b/test cases/linuxlike/1 pkg-config/meson.build index a39e33ca6..8e8e8f256 100644 --- a/test cases/linuxlike/1 pkg-config/meson.build +++ b/test cases/linuxlike/1 pkg-config/meson.build @@ -2,7 +2,7 @@ project('external dependency', 'c') # Zlib is probably on all dev machines. -dep = dependency('zlib') +dep = dependency('zlib', version : '>=1.2.8') exe = executable('zlibprog', 'prog.c', dependencies : dep) test('zlibtest', exe) diff --git a/vcstagger.py b/vcstagger.py new file mode 100755 index 000000000..f754358f5 --- /dev/null +++ b/vcstagger.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +# Copyright 2015 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys, os, subprocess + +def tag(infile, outfile, fallback): + tagid = get_string(infile, fallback) + newdata = open(infile).read().replace('@VCS_TAG@', tagid) + try: + olddata = open(outfile).read() + if olddata == newdata: + return + except Exception: + pass + open(outfile, 'w').write(newdata) + +def get_string(infile, fallback): + absfile = os.path.join(os.getcwd(), infile) + directory = os.path.split(absfile)[0] + segs = directory.replace('\\', '/').split('/') + for i in range(len(segs), -1, -1): + curdir = '/'.join(segs[:i]) + if os.path.isdir(os.path.join(curdir, '.git')): + output = subprocess.check_output(['git', 'describe'], + cwd = directory) + return output.decode().strip() + elif os.path.isdir(os.path.join(curdir, '.hg')): + output = subprocess.check_output(['hg', 'identify'], + cwd=directory) + return output.decode().strip() + elif os.path.isdir(os.path.join(curdir, '.bzr')): + output = subprocess.check_output(['bzr', 'revno'], + cwd=directory) + return output.decode().strip() + elif os.path.isdir(os.path.join(curdir, '.svn')): + output = subprocess.check_output(['svn', 'info'], + cwd=directory) + for line in output.decode().split('\n'): + (k, v) = line.split(':', 1) + if k.strip() == 'Revision': + return v.strip() + raise RuntimeError('Svn output malformed.') + return fallback + +if __name__ == '__main__': + infile = sys.argv[1] + outfile = sys.argv[2] + fallback = sys.argv[3] + tag(infile, outfile, fallback) diff --git a/wrap.py b/wrap.py index 2c3e087b5..af2b213dd 100644 --- a/wrap.py +++ b/wrap.py @@ -79,7 +79,7 @@ class Resolver: (pdata, phash) = self.get_data(purl) expected = p.get('patch_hash') if phash != expected: - raise RuntimeError('Incorrect hash for patch %s:\n %s expected\n %s actual.' % (packagename, expected, phash)) + raise RuntimeError('Incorrect hash for patch %s:\n %s expected\n %s actual' % (packagename, expected, phash)) open(os.path.join(self.cachedir, p.get('patch_filename')), 'wb').write(pdata) else: mlog.log('Package does not require patch.')