ostree-build-compile-one 9.02 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
#!/usr/bin/env python
# Copyright (C) 2011,2012 Colin Walters <walters@verbum.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

# ostbuild-compile-one-make wraps systems that implement the GNOME build API:
# http://people.gnome.org/~walters/docs/build-api.txt

import os,sys,stat,subprocess,tempfile,re,shutil
from StringIO import StringIO
import json
from multiprocessing import cpu_count
import select,time

def log(x):
    sys.stdout.write('ob: ' + x)
    sys.stdout.write('\n')
    sys.stdout.flush()

def fatal(x):
    log(x)
    sys.exit(1)

def _get_env_for_cwd(cwd=None, env=None):
    # This dance is necessary because we want to keep the PWD
    # environment variable up to date.  Not doing so is a recipie
    # for triggering edge conditions in pwd lookup.
    if (cwd is not None) and (env is None or ('PWD' in env)):
        if env is None:
            env_copy = os.environ.copy()
        else:
            env_copy = env.copy()
        if ('PWD' in env_copy) and (not cwd.startswith('/')):
            env_copy['PWD'] = os.path.join(env_copy['PWD'], cwd)
        else:
            env_copy['PWD'] = cwd
    else:
        env_copy = env
    return env_copy

def run_sync(args, cwd=None, env=None):
    log("running: %s" % (subprocess.list2cmdline(args),))

    env_copy = _get_env_for_cwd(cwd, env)

    stdin_target = open('/dev/null', 'r')
    stdout_target = sys.stdout
    stderr_target = sys.stderr

    proc = subprocess.Popen(args, stdin=stdin_target, stdout=stdout_target, stderr=stderr_target,
                            close_fds=True, cwd=cwd, env=env_copy)
    stdin_target.close()
    returncode = proc.wait()
    if returncode != 0:
        logfn = fatal
    else:
        logfn = None
    if logfn is not None:
        logfn("pid %d exited with code %d" % (proc.pid, returncode))
    return returncode

PREFIX = '/usr'

def _has_buildapi_configure_variable(name):
    var = '#buildapi-variable-%s' % (name, )
    for line in open('configure'):
        if line.find(var) >= 0:
            return True
    return False

def main(args):
85 86 87
    ncpus = cpu_count()
    default_buildapi_jobs = ['-j', '%d' % (ncpus + 1), 
                             '-l', '%d' % (ncpus * 2)]
88 89 90 91 92 93 94 95

    starttime = time.time()
    
    uname=os.uname()
    kernel=uname[0].lower()
    machine=uname[4]
    build_target='%s-%s' % (machine, kernel)

Emmanuele Bassi's avatar
Emmanuele Bassi committed
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
    default_configargs = [
        '--prefix=' + PREFIX,
        '--libdir=' + os.path.join(PREFIX, 'lib'),
        '--sysconfdir=/etc',
        '--localstatedir=/var',
        '--bindir=' + os.path.join(PREFIX, 'bin'),
        '--sbindir=' + os.path.join(PREFIX, 'bin'),
        '--datadir=' + os.path.join(PREFIX, 'share'),
        '--includedir=' + os.path.join(PREFIX, 'include'),
        '--libexecdir=' + os.path.join(PREFIX, 'libexec'),
        '--mandir=' + os.path.join(PREFIX, 'share', 'man'),
        '--infodir=' + os.path.join(PREFIX, 'share', 'info'),
    ]

    autotools_configargs = [
        '--build=' + build_target,
        '--disable-static',
        '--disable-silent-rules',
    ]
    meson_configargs = []

117
    makeargs = ['make']
Emmanuele Bassi's avatar
Emmanuele Bassi committed
118
    ninjaargs = ['ninja']
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139

    ostbuild_resultdir='_ostbuild-results'
    ostbuild_meta_path='_ostbuild-meta.json'

    chdir = None
    opt_install = False

    for arg in args:
        if arg.startswith('--ostbuild-resultdir='):
            ostbuild_resultdir=arg[len('--ostbuild-resultdir='):]
        elif arg.startswith('--ostbuild-meta='):
            ostbuild_meta_path=arg[len('--ostbuild-meta='):]
        elif arg.startswith('--chdir='):
            os.chdir(arg[len('--chdir='):])
        else:
            makeargs.append(arg)
        
    f = open(ostbuild_meta_path)
    metadata = json.load(f)
    f.close()

Emmanuele Bassi's avatar
Emmanuele Bassi committed
140
    configargs = metadata.get('config-opts', [])
141

142 143 144 145 146 147
    requires = metadata.get('requires', {})
    pkgconfig_requires = requires.get('pkgconfig', [])
    if pkgconfig_requires:
        for req in pkgconfig_requires:
            run_sync(['pkg-config', '--exists', req])

148 149 150 151 152
    if metadata.get('rm-configure', False):
        configure_path = 'configure'
        if os.path.exists(configure_path):
            os.unlink(configure_path)

Emmanuele Bassi's avatar
Emmanuele Bassi committed
153
    force_autotools = metadata.get('force-autotools', False)
154

155 156
    builddir = '_build'
    use_builddir = True
Emmanuele Bassi's avatar
Emmanuele Bassi committed
157 158

    meson_build = False
159
    if os.path.exists('meson.build') and not force_autotools:
Emmanuele Bassi's avatar
Emmanuele Bassi committed
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
        meson_build = True
    else:
        autogen_script = None
        if not os.path.exists('configure'):
            log("No 'configure' script found, looking for autogen/bootstrap")
            for name in ['autogen', 'autogen.sh', 'bootstrap']:
                if os.path.exists(name):
                    log("Using bootstrap script '%s'" % (name, ))
                    autogen_script = name
            if autogen_script is None:
                fatal("No configure or autogen script detected; unknown buildsystem")

        if autogen_script is not None:
            env = dict(os.environ)
            env['NOCONFIGURE'] = '1'
            run_sync(['./' + autogen_script], env=env)
        else:
            log("Using existing 'configure' script")

        doesnot_support_builddir = _has_buildapi_configure_variable('no-builddir')
        if doesnot_support_builddir:
            log("Found no-builddir Build API variable; copying source tree to _build")
182 183 184 185 186 187 188 189 190 191
        if os.path.isdir('_build'):
            shutil.rmtree('_build')
        shutil.copytree('.', '_build', symlinks=True,
                        ignore=shutil.ignore_patterns('_build'))
        use_builddir = False
    
    if use_builddir:
        log("Using build directory %r" % (builddir, ))
        if not os.path.isdir(builddir):
            os.mkdir(builddir)
192

Emmanuele Bassi's avatar
Emmanuele Bassi committed
193 194 195 196 197 198 199 200 201
    if meson_build:
        args = ['meson']
        args.extend(meson_configargs)
        args.extend(default_configargs)
        args.extend(configargs)
        args.extend(['..'])
    else:
        config_status = os.path.join(builddir, 'config.status')
        have_config_status = os.path.exists(config_status)
202
            
Emmanuele Bassi's avatar
Emmanuele Bassi committed
203 204 205 206 207 208 209
        if not have_config_status:
            if use_builddir:
                args = ['../configure']
            else:
                args = ['./configure']
        args.extend(autotools_configargs)
        args.extend(default_configargs)
210
        args.extend(configargs)
211

Emmanuele Bassi's avatar
Emmanuele Bassi committed
212 213 214 215
    run_sync(args, cwd=builddir)

    use_make = False
    use_ninja = False
216

Emmanuele Bassi's avatar
Emmanuele Bassi committed
217 218
    buildfile_path = None

Emmanuele Bassi's avatar
Emmanuele Bassi committed
219
    for name in ['build.ninja']:
220 221 222
        file_path = os.path.join(builddir, name)
        if os.path.exists(file_path):
            buildfile_path = file_path
Emmanuele Bassi's avatar
Emmanuele Bassi committed
223
            use_ninja = True
224 225
            break

Emmanuele Bassi's avatar
Emmanuele Bassi committed
226
    for name in ['Makefile', 'makefile', 'GNUmakefile']:
227 228 229
        file_path = os.path.join(builddir, name)
        if os.path.exists(file_path):
            buildfile_path = file_path
Emmanuele Bassi's avatar
Emmanuele Bassi committed
230
            use_make = True
Emmanuele Bassi's avatar
Emmanuele Bassi committed
231 232 233 234 235
            break

    if buildfile_path is None:
        fatal("No build rules file found")

236
    if use_make:
Emmanuele Bassi's avatar
Emmanuele Bassi committed
237
        args = list(makeargs)
238 239 240 241 242 243 244 245 246 247 248 249
        env = os.environ.copy()
    else:
        # While make supports passing variables using something like
        # `make CC=foo`, ninja uses environment variables; this means
        # we need to move make variables into the environment before
        # calling ninja
        args = list(ninjaargs)
        env = os.environ.copy()
        if 'CC' in makeargs:
            env['CC'] = makeargs['CC']
        if 'CXX' in makeargs:
            env['CXX'] = makeargs['CXX']
250
        log("CC = '%s', CXX = '%s'" % (env['CC'], env['CXX'],))
Emmanuele Bassi's avatar
Emmanuele Bassi committed
251

252 253 254 255 256
    user_specified_jobs = False
    for arg in args:
        if arg == '-j':
            user_specified_jobs = True
    
Emmanuele Bassi's avatar
Emmanuele Bassi committed
257
    if not user_specified_jobs and use_make:
258
        has_notparallel = False
Emmanuele Bassi's avatar
Emmanuele Bassi committed
259
        for line in open(buildfile_path):
260 261 262 263 264 265 266 267
            if line.startswith('.NOTPARALLEL'):
                has_notparallel = True
                log("Found .NOTPARALLEL")

        if not has_notparallel:
            log("Didn't find NOTPARALLEL, using parallel make by default")
            args.extend(default_buildapi_jobs)

268
    run_sync(args, cwd=builddir, env=env)
269

Emmanuele Bassi's avatar
Emmanuele Bassi committed
270 271 272 273 274 275 276 277 278
    if use_make:
        args = ['make', 'install', 'DESTDIR=' + ostbuild_resultdir]
        env = os.environ.copy()
    else:
        args = ['ninja', 'install']
        env = os.environ.copy()
        env['DESTDIR'] = ostbuild_resultdir

    run_sync(args, cwd=builddir, env=env)
279

280 281 282 283 284
    endtime = time.time()

    log("Compilation succeeded; %d seconds elapsed" % (int(endtime - starttime),))
    log("Results placed in %s" % (ostbuild_resultdir, ))

285
main(sys.argv[1:])