# encoding: utf-8 # conan.py -- Conan Package Manager integration # Copyright (C) 2019 a1batross # This program 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. # # This program 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. from waflib import Logs, Utils from waflib.Configure import conf import os import sys import subprocess import json def options(opt): grp = opt.add_option_group('Conan options') grp.add_option('--disable-conan', action = 'store_true', default = False, dest = 'NO_CONAN', help = 'completely disable Conan') grp.add_option('--force-conan', action = 'store_true', default = False, dest = 'CONAN_MANDATORY', help = 'require Conan, useful for testing') grp.add_option('--conan-profile', action = 'store', default = None, dest = 'CONAN_PROFILE', help = 'set conan profile') # grp.add_option('') def conan(ctx, args, quiet=False): # print('%s %s' % (ctx.env.CONAN[0], arg_str)) argv = [ctx.env.CONAN[0]] argv += Utils.to_list(args) ret = b'' retval = False ctx.logger.info('argv: {}'.format(argv)) if quiet: try: ret = subprocess.check_output(argv, cwd=ctx.bldnode.abspath()) ctx.logger.info('output: \n{}'.format(ret)) except subprocess.CalledProcessError as e: ret = e.output ctx.logger.info('FAIL!!!\nretval: {}\noutput: \n{}'.format(e.returncode, ret)) else: retval = True else: retval = subprocess.call(argv, cwd=ctx.bldnode.abspath()) if retval != 0: ctx.logger.info('FAIL!!!\nretval: {}'.format(retval)) retval = False else: retval = True if sys.version_info > (3, 0): ret = ret.decode('utf-8') ret = ret.strip().replace('\r\n', '\n') return (retval, ret) @conf def add_conan_remote(ctx, name, url): """ Adds conan remote :param name: name of remote :type name: string :param url: url path :type url: string """ if not ctx.env.CONAN: ctx.fatal("Conan is not installed!") ctx.start_msg('Checking if conan has %s remote' % name) [success,remotes] = conan(ctx, 'remote list --raw', quiet=True) if not success: ctx.end_msg('no') ctx.fatal('conan has failed to list remotes') found = False for v in remotes.splitlines(): a = v.split(' ') if a[0] == name: if a[1] == url: found = True else: ctx.end_msg('no') ctx.fatal('''Conan already has %s remote, but it points to another remote! You can remote it with:\n $ %s remote remove %s''' % (name, ctx.env.CONAN[0], name)) break if not found: ctx.end_msg('no') ctx.start_msg('Adding new %s remote to conan' % name) if conan(ctx, 'remote add %s %s' % (name, url), quiet=True) == False: ctx.end_msg('fail', color='RED') ctx.fatal('conan has failed to add %s remote' % name) ctx.end_msg('done') else: ctx.end_msg('yes') @conf def parse_conan_json(ctx, name): with open(os.path.join(ctx.bldnode.abspath(), 'conanbuildinfo.json')) as jsonfile: cfg = json.load(jsonfile) deps = cfg["dependencies"] ctx.env['HAVE_%s' % name] = True for dep in deps: def to_u8(arr): if sys.version_info > (3, 0): return arr ret = [] for i in arr: ret += [ i.encode('utf8') ] return ret ctx.env['INCLUDES_%s' % name] += to_u8(dep['include_paths']) ctx.env['LIB_%s' % name] += to_u8(dep['libs']) ctx.env['LIBPATH_%s' % name] += to_u8(dep['lib_paths']) ctx.env['DEFINES_%s' % name] += to_u8(dep['defines']) ctx.env['CFLAGS_%s' % name] += to_u8(dep['cflags']) ctx.env['CXXFLAGS_%s' % name] += to_u8(dep['cflags']) ctx.env['LINKFLAGS_%s' % name] += to_u8(dep['sharedlinkflags']) return def conan_update_profile(ctx, settings, profile): args = ['profile', 'update'] for (key, value) in settings.items(): args2 = args + [ 'settings.%s=%s' % (key, value), profile ] if conan(ctx, args2, quiet=True) == False: ctx.fatal('Can\'t update profile') @conf def add_dependency(ctx, pkg, *k, **kw): """ Retrieves and adds depedency during configuration stage :param pkg: package name in conan format :type pkg: string :param remote: remote name, optional :type remote: string :param options: package options, optional :type options: dict :param uselib_store: set uselib name, optional :type uselib_store: string """ if not ctx.env.CONAN: ctx.fatal("Conan is not installed!") name = pkg.split('/')[0] ctx.msg(msg='Downloading dependency %s' % name, result='in process', color='BLUE') args = ['install', pkg, '-g', 'json', '--build=missing', '-pr', ctx.env.CONAN_PROFILE] if 'remote' in kw: args += ['-r', kw['remote']] if 'options' in kw: for (key, value) in kw['options'].items(): args += ['-o', '%s=%s' % (key, value)] if conan(ctx, args): if 'uselib_store' in kw: uselib = kw['uselib_store'] else: uselib = name.upper() # we just use upper names everywhere ctx.parse_conan_json(uselib) ctx.msg(msg='Downloading dependency %s' % name, result='ok', color='GREEN') return ctx.msg(msg='Downloading dependency %s' % name, result='fail', color='RED') ctx.fatal('Conan has failed installing dependency %s' % pkg) def configure(conf): # already configured if conf.env.CONAN: return # respect project settings if not conf.env.CONAN_MANDATORY: conf.env.CONAN_MANDATORY = conf.options.CONAN_MANDATORY # disabled by user request if conf.options.NO_CONAN and not conf.env.CONAN_MANDATORY: conf.env.CONAN = None return conf.find_program('conan', mandatory=conf.env.MANDATORY) if not conf.env.CONAN: return conf.start_msg('Checking conan version') ver = conan(conf, '--version', quiet=True) if not ver: conf.end_msg('fail') if conf.env.CONAN_MANDATORY: conf.fatal('Conan has failed! Check your conan installation') else: Logs.warn('Conan has failed! Check your conan installation. Continuing...') if conf.options.CONAN_PROFILE: conf.env.CONAN_PROFILE = conf.options.CONAN_PROFILE else: profile = conf.env.CONAN_PROFILE = os.path.join(conf.bldnode.abspath(), 'temp_profile') settings = dict() conan(conf, ['profile', 'new', profile, '--detect', '--force'], quiet=True) # NOTE: Conan installs even 32-bit runtime packages on x86_64 for now :( # it may potentially break system on Linux if conf.env.DEST_SIZEOF_VOID_P == 4 and conf.env.DEST_CPU in ['x86', 'x86_64'] and conf.env.DEST_OS != 'linux': settings['arch'] = 'x86' if conf.env.DEST_OS2 == 'android': settings['os'] = 'Android' if conf.env.COMPILER_CC == 'msvc': settings['compiler.runtime'] = 'MT' conan_update_profile(conf, settings, profile) # I think Conan is respecting environment CC/CXX values, so it's not need # to specify compiler here #compiler = conf.env.COMPILER_CC #if conf.env.COMPILER_CC == 'msvc': # compiler = 'Visual Studio' #elif conf.env.DEST_OS == 'darwin' and conf.env.COMPILER_CC == 'clang': # compiler = 'apple-clang' #elif conf.env.COMPILER_CC == 'suncc': # compiler = 'sun-cc' #settings['compiler'] = compiler conf.end_msg(ver)