├── .gitignore ├── LICENSE ├── README.md ├── benchmark.sh ├── kninja.py ├── mmh2.py ├── ninja_internal.py ├── requirements.txt └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Rabin Vincent 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kninja 2 | 3 | Kninja is an experimental [Ninja](https://ninja-build.org/) build file 4 | generator for Kbuild, the Linux kernel's build system. It does not really 5 | attempt to understand Kbuild files but instead just processes the output 6 | generated by GNU Make's `--print-data-base`. Configuration changes and changes 7 | to files with special generation rules are not currently handled, but 8 | modifications to most normal source and header files should just work. 9 | 10 | The use of ninja results in vastly improved incremental build times: 11 | 12 | | Change | make -j8 | make -j8 objectname | ninja | 13 | | --------------------------| -------- | ------------------- | ------ | 14 | | No changes | 2.254s | 0.731s | 0.065s | 15 | | One change, compile error | 1.225s | 0.765s | 0.077s | 16 | | One change, full link | 5.915s | NA | 4.482s | 17 | 18 | The link time unsuprisingly dominates when performing small changes, but as can 19 | be seen the time until the start of compilation (and thus the time until any 20 | compile errors are detected) is several times smaller with ninja. 21 | 22 | These numbers were measured with the [benchmarking script](benchmark.sh) 23 | included in the repository. Reading that script should also give you an 24 | example of how to use kninja. 25 | 26 | ## Usage 27 | 28 | Python 3.4+ is required. Install the required packages with: 29 | 30 | pip3 install -r requirements.txt 31 | 32 | Run `kninja.py` to generate Ninja build files. This will be automatically 33 | re-run after any changes to .config or Makefiles. 34 | 35 | $ kninja.py 36 | [INFO] Ensuring full build: make -j 8 37 | CHK include/config/kernel.release 38 | CHK include/generated/uapi/linux/version.h 39 | CHK include/generated/utsrelease.h 40 | CHK include/generated/timeconst.h 41 | CHK include/generated/bounds.h 42 | CHK include/generated/asm-offsets.h 43 | CALL scripts/checksyscalls.sh 44 | CHK include/generated/compile.h 45 | Building modules, stage 2. 46 | MODPOST 18 modules 47 | Kernel: arch/x86/boot/bzImage is ready (#6) 48 | [INFO] Generating make database: make -p 49 | [INFO] Caching make database to .makedb 50 | [INFO] Parsing make database (2635077 lines) 51 | [INFO] Wrote build.ninja (2763 rules, 2744 build statements) 52 | [INFO] Wrote .ninja_deps (2155 targets, 939990 deps) 53 | [INFO] Wrote .ninja_log (2763 commands) 54 | [INFO] Checking ninja status: ninja -d explain -n 55 | [INFO] All OK 56 | -------------------------------------------------------------------------------- /benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | # 3 | # Run in a kernel source git with something like: 4 | # 5 | # $ ../kninja/benchmark.sh >log.txt 2>&1 6 | # 7 | 8 | mypath=${0%/*} 9 | 10 | git reset --hard 11 | make clean 12 | 13 | make defconfig 14 | $mypath/kninja.py 15 | 16 | echo '========== No changes, make' 17 | time make -j8 18 | 19 | echo '========== No changes, make with filename' 20 | time make -j8 arch/x86/kernel/hw_breakpoint.o 21 | 22 | echo '========== No changes, ninja' 23 | time ninja 24 | 25 | echo '========== One file change and error, make' 26 | echo error > arch/x86/kernel/hw_breakpoint.c 27 | time make -j8 || : 28 | 29 | echo '========== One file change and error, make with filename' 30 | echo error > arch/x86/kernel/hw_breakpoint.c 31 | time make arch/x86/kernel/hw_breakpoint.o || : 32 | 33 | echo '========== One file change and error, ninja' 34 | echo error > arch/x86/kernel/hw_breakpoint.c 35 | time ninja || : 36 | 37 | git reset --hard 38 | 39 | echo '========== One file change and link, make' 40 | touch arch/x86/kernel/hw_breakpoint.c 41 | time make -j8 42 | 43 | echo '========== One file change and link, ninja' 44 | touch arch/x86/kernel/hw_breakpoint.c 45 | time ninja 46 | -------------------------------------------------------------------------------- /kninja.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import logging 5 | import os 6 | import re 7 | import shlex 8 | import subprocess 9 | import fnmatch 10 | import sys 11 | import re 12 | 13 | import ninja_syntax 14 | 15 | import ninja_internal 16 | 17 | # These objects either get modified in the final link stage (which is opaque to 18 | # ninja) or generate files that are later used in some rules which are not 19 | # currently handled by ninja. We ignore these to ensure that interchangably 20 | # running make and ninja without any changes to the tree does not cause any 21 | # rebuilds. Use "make V=2" and "ninja -d explain -v" to debug this. 22 | 23 | IGNORES = [ 24 | 'init/version.o', 25 | 'lib/gen_crc32table', 26 | 'vmlinux.o', 27 | 'usr/gen_init_cpio', 28 | ] 29 | 30 | WILDCARD_IGNORES = [ 31 | 'arch/arm/boot/*', 32 | 'arch/*/vdso/*', 33 | 34 | 'arch/x86/boot/*', 35 | 'arch/x86/entry/vdso/*', 36 | 'arch/x86/realmode/*', 37 | 'arch/x86/tools/*', 38 | 39 | '*/lib-ksyms.o', 40 | 41 | # These depend on include/generated/utsrelease.h which make changes 42 | '*.mod.o', 43 | 44 | 'scripts/*', 45 | ] 46 | 47 | 48 | IGNORE_UNHANDLED = [ 49 | 'System.map', 50 | 'Module.symvers', 51 | 'arch/x86/boot/voffset.h', 52 | 'usr/.initramfs_data.cpio.d', 53 | '.version', 54 | ] 55 | 56 | 57 | class Kninja(object): 58 | 59 | def __init__(self): 60 | pass 61 | 62 | def fixname(self, name): 63 | return name.replace('/', '_') 64 | 65 | def fixup_obj(self, obj): 66 | if obj in IGNORES: 67 | logging.debug('Ignoring %s', obj) 68 | return None 69 | 70 | for ign in WILDCARD_IGNORES: 71 | if fnmatch.fnmatch(obj, ign): 72 | logging.debug('Ignoring %s', obj) 73 | return None 74 | 75 | # ARM oprofile objects have .. in their paths 76 | if '..' in obj: 77 | return os.path.relpath(obj) 78 | 79 | return obj 80 | 81 | def _parse_tokens(self, line): 82 | tokens = [] 83 | generated = [] 84 | for token in re.split('\s', line): 85 | if not token: 86 | continue 87 | 88 | if token.endswith(':') and line.startswith(token) and len(line.split(' ')) > 1: 89 | generated.append(token.rstrip(':')) 90 | continue 91 | 92 | if not re.match('^[-_./A-Za-z0-9]+$', token): 93 | continue 94 | 95 | if token[0] == '/' or \ 96 | token[0] == '-' or \ 97 | token.endswith('..') or \ 98 | token.startswith('CONFIG_') or \ 99 | token.startswith('cmd_') or \ 100 | token.startswith('deps_') or \ 101 | token.endswith('.cmd') or \ 102 | token.endswith('modules.order') or \ 103 | token.endswith('.conf') or \ 104 | any(t for t in IGNORE_UNHANDLED if token.endswith(t)) or \ 105 | len(token) < 4: 106 | continue 107 | 108 | tokens.append(token) 109 | 110 | return tokens, generated 111 | 112 | def convert(self, makedb): 113 | gotvmlinux = False 114 | 115 | rulenames = [] 116 | builds = [] 117 | rules = [] 118 | alldeps = [] 119 | cmds = [] 120 | srctree = '' 121 | objtree = '' 122 | tokens = set() 123 | generated = set() 124 | handledfiles = set(['vmlinux']) 125 | 126 | rules.append({'name': 'KNINJA', 127 | 'command': ' '.join(sys.argv), 128 | 'generator': 1, 129 | 'pool': 'console'}) 130 | 131 | for line in makedb: 132 | newtokens, newgenerated = self._parse_tokens(line) 133 | tokens.update(newtokens) 134 | generated.update(newgenerated) 135 | 136 | if not gotvmlinux and line.startswith('vmlinux: '): 137 | deps = line.rstrip().replace('vmlinux: ', '').split(' ') 138 | deps = [d for d in deps 139 | if d not in ('autoksyms_recursive', 'vmlinux_prereq', 'FORCE')] 140 | 141 | # This makes make ignore the dependencies for vmlinux, since 142 | # those are already taken care of by ninja by the time this 143 | # gets run. 144 | makeall = r"cat %s | sed -e '/^$(vmlinux-dirs)/,+1d' " \ 145 | r"| make -f - all" 146 | makefile = 'Makefile' 147 | if srctree and objtree: 148 | makefile = os.path.join(srctree, makefile) 149 | makeall += ' -C%s O=%s' % (srctree, objtree) 150 | 151 | makeall = makeall % makefile 152 | logging.debug('vmlinux make command: %s', makeall) 153 | 154 | rules.append({'name': 'cmd_vmlinux', 155 | 'command': makeall.replace('$', '$$'), 156 | 'pool': 'console'}) 157 | 158 | builds.append({'outputs': 'vmlinux', 159 | 'rule': 'cmd_vmlinux', 160 | 'inputs': deps}) 161 | 162 | cmds.append(('vmlinux', os.stat('vmlinux').st_mtime_ns, makeall)) 163 | gotvmlinux = True 164 | continue 165 | elif line.startswith('KBUILD_SRC = '): 166 | srctree = line.rstrip().replace('KBUILD_SRC =', '').rstrip() 167 | continue 168 | elif line.startswith('O = '): 169 | objtree = line.rstrip().replace('O = ', '') 170 | continue 171 | 172 | # built-in.o and other mod/obj files which just combine obj files 173 | if ('.o: ' in line or '.ko: ' in line or '.a:' in line) \ 174 | and 'FORCE' in line \ 175 | and '%' not in line \ 176 | and '.h' not in line \ 177 | and '.S' not in line \ 178 | and '.c' not in line \ 179 | and 'objcopy.o' not in line: # lkdtm_ro_objcopy.o 180 | obj, deps = line.rstrip().split(': ') 181 | 182 | obj = self.fixup_obj(obj) 183 | if not obj: 184 | continue 185 | 186 | deps = [d for d in deps.split(' ') if d != 'FORCE' and d not in IGNORES] 187 | handledfiles.update(deps) 188 | fixed = self.fixname(obj) 189 | 190 | builds.append({'outputs': obj, 191 | 'rule': 'cmd_' + fixed, 192 | 'inputs': deps}) 193 | continue 194 | 195 | try: 196 | var, val = line.rstrip('\n').split(' := ') 197 | except ValueError: 198 | continue 199 | 200 | if var.startswith('cmd_'): 201 | obj = var.replace('cmd_', '', 1) 202 | if obj in ('files', 'vmlinux'): 203 | continue 204 | obj = self.fixup_obj(obj) 205 | if not obj: 206 | continue 207 | 208 | cmdname = 'cmd_' + self.fixname(obj) 209 | args = shlex.split(val) 210 | md = [arg for arg in args if '-MD' in arg] 211 | if md: 212 | depfile = md[0].split('-MD,')[1] 213 | deps = 'gcc' 214 | else: 215 | depfile = None 216 | deps = None 217 | 218 | if cmdname in rulenames: 219 | logging.debug('Ignoring duplicate rule %s', var) 220 | continue 221 | 222 | cmd = ' '.join(val.split()) 223 | 224 | rulenames.append(cmdname) 225 | rules.append({'name': cmdname, 226 | 'command': cmd, 227 | 'deps': deps, 228 | 'depfile': depfile}) 229 | 230 | if deps: 231 | handledfiles.update(deps) 232 | cmds.append((obj, os.stat(obj).st_mtime_ns, cmd)) 233 | 234 | elif var.startswith('deps_'): 235 | obj = var.replace('deps_', '', 1) 236 | obj = self.fixup_obj(obj) 237 | if not obj: 238 | continue 239 | 240 | try: 241 | mtime = os.stat(obj).st_mtime_ns 242 | except OSError: 243 | continue 244 | 245 | val = re.sub(r'\$\(subst[^)]+\)', '', val) 246 | val = re.sub(r'\$\(wildcard[^)]+\)', '', val) 247 | 248 | deps = [p for p in val.split(' ') 249 | if p and not p.startswith('include/config/')] 250 | handledfiles.update(deps) 251 | alldeps.append((obj, mtime, deps)) 252 | 253 | elif var.startswith('source_'): 254 | obj = var.replace('source_', '', 1) 255 | obj = self.fixup_obj(obj) 256 | if not obj: 257 | continue 258 | 259 | name = self.fixname(obj) 260 | builds.append({'outputs': obj, 261 | 'rule': 'cmd_' + name, 262 | 'inputs': val.split(' ')}) 263 | handledfiles.update(val.split(' ')) 264 | 265 | 266 | logging.info('Finding unhandled files (%d tokens)', len(tokens)) 267 | 268 | tokens = set([os.path.realpath(t) for t in tokens]) 269 | tokens.difference_update([os.path.realpath(f) for f in generated]) 270 | tokens.difference_update([os.path.realpath(f) for f in handledfiles]) 271 | 272 | if objtree: 273 | # Every make invocation results in a regeneration of the Makefile 274 | # in the objdir 275 | tokens.remove(os.path.realpath('Makefile')) 276 | 277 | unhandledfiles = sorted([f for f in tokens if os.path.isfile(f)]) 278 | 279 | builds.append({'outputs': 'build.ninja', 280 | 'rule': 'KNINJA', 281 | 'inputs': unhandledfiles}) 282 | 283 | return rules, builds, alldeps, cmds 284 | 285 | 286 | def main(): 287 | parser = argparse.ArgumentParser() 288 | parser.add_argument('--cache', action='store_true') 289 | parser.add_argument('--verbose', '-v', default=0, action='count') 290 | parser.add_argument('--path', default='') 291 | parser.add_argument('makeargs', nargs='*') 292 | args = parser.parse_args() 293 | 294 | if args.verbose >= 1: 295 | level = logging.DEBUG 296 | else: 297 | level = logging.INFO 298 | 299 | logging.basicConfig(format='[%(levelname)s] %(message)s', level=level) 300 | 301 | makedb = [] 302 | cachefile = os.path.join(args.path, '.makedb') 303 | 304 | if args.cache: 305 | if os.path.exists(cachefile): 306 | logging.info('Using cached make database %s', cachefile) 307 | with open(cachefile, 'r') as f: 308 | makedb = f.readlines() 309 | 310 | if not makedb: 311 | makeargs = ['-C', args.path] if args.path else [] 312 | makeargs.extend(args.makeargs) 313 | 314 | cmd = ['make', '-j', '%d' % os.cpu_count()] + makeargs 315 | logging.info('Ensuring full build: %s', ' '.join(cmd)) 316 | subprocess.check_call(cmd) 317 | 318 | cmd = ['make', '-p'] + makeargs 319 | logging.info('Generating make database: %s', ' '.join(cmd)) 320 | out = subprocess.check_output(cmd).decode('utf-8') 321 | makedb = [l for l in out.splitlines() if l and l[0] != '#'] 322 | 323 | logging.info('Caching make database to %s', cachefile) 324 | with open(cachefile, 'w+') as f: 325 | f.write('\n'.join(makedb)) 326 | 327 | kn = Kninja() 328 | logging.info('Parsing make database (%d lines)', len(makedb)) 329 | rules, builds, alldeps, cmds = kn.convert(makedb) 330 | 331 | ninjafile = os.path.join(args.path, 'build.ninja') 332 | with open(ninjafile, 'w+') as f: 333 | w = ninja_syntax.Writer(output=f) 334 | 335 | for rule in rules: 336 | w.rule(**rule) 337 | 338 | for build in builds: 339 | w.build(**build) 340 | 341 | logging.info('Wrote build.ninja (%d rules, %d build statements)', 342 | len(rules), len(builds)) 343 | 344 | depsfile = os.path.join(args.path, '.ninja_deps') 345 | with open(depsfile, 'wb') as f: 346 | ninja_internal.write_deps(f, alldeps) 347 | 348 | logging.info('Wrote .ninja_deps (%d targets, %d deps)', 349 | len(alldeps), sum([len(d) for _, _, d in alldeps])) 350 | 351 | logfile = os.path.join(args.path, '.ninja_log') 352 | with open(logfile, 'w') as f: 353 | ninja_internal.write_log(f, cmds) 354 | 355 | logging.info('Wrote .ninja_log (%d commands)', len(cmds)) 356 | 357 | cmd = ['ninja', '-d', 'explain', '-n'] 358 | logging.info('Checking ninja status: %s', ' '.join(cmd)) 359 | out = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode('utf-8') 360 | if 'no work' in out: 361 | logging.info('All OK') 362 | else: 363 | logging.error('%s\n...', '\n'.join(out.splitlines()[:25])) 364 | logging.error('ninja should be clean, but has work!') 365 | sys.exit(1) 366 | 367 | if __name__ == '__main__': 368 | main() 369 | -------------------------------------------------------------------------------- /mmh2.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | 3 | 4 | # MurmurHash2 (MurmurHash64A()) by Austin Appleby. Public domain. 5 | def _hash64(key, seed=0): 6 | aligned = (len(key) // 8) * 8 7 | 8 | m = numpy.uint64(0xc6a4a7935bd1e995) 9 | r = numpy.uint64(47) 10 | 11 | h = numpy.uint64(seed) ^ numpy.uint64((numpy.uint64(len(key)) * m)) 12 | 13 | data = numpy.frombuffer(key[:aligned], dtype=numpy.uint64) 14 | for k in data: 15 | k *= m 16 | k ^= k >> r 17 | k *= m 18 | 19 | h ^= k 20 | h *= m 21 | 22 | left = len(key) - aligned 23 | if left: 24 | data2 = key[aligned:] 25 | for i in reversed(range(left)): 26 | h ^= numpy.uint64(data2[i]) << numpy.uint64(i * 8) 27 | h *= m 28 | 29 | h ^= h >> r 30 | h *= m 31 | h ^= h >> r 32 | 33 | return h 34 | 35 | 36 | def hash64(key, seed=0): 37 | with numpy.errstate(over='ignore'): 38 | return _hash64(key, seed) 39 | -------------------------------------------------------------------------------- /ninja_internal.py: -------------------------------------------------------------------------------- 1 | import array 2 | import struct 3 | 4 | import mmh2 5 | 6 | 7 | def write_deps(f, alldeps): 8 | signature = '# ninjadeps\n' 9 | version = 4 10 | 11 | paths = [] 12 | for out, mtime, deps in alldeps: 13 | paths.append(out) 14 | paths.extend(deps) 15 | 16 | paths = set(paths) 17 | pathids = {path: _id for _id, path, in enumerate(paths)} 18 | 19 | f.write(signature.encode('utf-8')) 20 | f.write(struct.pack('i', version)) 21 | 22 | for _id, path in enumerate(paths): 23 | data = path.encode('utf-8') 24 | if len(data) % 4: 25 | data += b'\x00' * (len(data) % 4) 26 | 27 | f.write(struct.pack('I', len(data) + 4)) 28 | f.write(data) 29 | f.write(struct.pack('i', ~_id)) 30 | 31 | for out, mtime, deps in alldeps: 32 | size = (1 + 2 + len(deps)) * 4 33 | f.write(struct.pack('I', size | (1 << 31))) 34 | f.write(struct.pack('iII', pathids[out], mtime & 0xffffffff, (mtime >> 32) & 0xffffffff)) 35 | f.write(array.array('I', [pathids[d] for d in deps]).tobytes()) 36 | 37 | 38 | def write_log(f, cmds): 39 | seed = 0xDECAFBADDECAFBAD 40 | 41 | f.write('# ninja log v5\n') 42 | for obj, mtime, cmd in cmds: 43 | hsh = mmh2.hash64(cmd.encode('utf-8'), seed) 44 | f.write('%d\t%d\t%lu\t%s\t%x\n' % (0, 0, mtime, obj, hsh)) 45 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ninja_syntax 2 | numpy 3 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | fail() { 4 | echo Failed. 5 | exit 1 6 | } 7 | 8 | mypath=${0%/*} 9 | 10 | config=allnoconfig 11 | log=$mypath/testlog 12 | src=$PWD 13 | 14 | runtest() { 15 | $src/scripts/config --enable MODULES 16 | $src/scripts/config --enable BLOCK 17 | $src/scripts/config --module BLK_DEV_NULL_BLK 18 | make olddefconfig 19 | 20 | $mypath/kninja.py | tee $log 21 | [ -e vmlinux ] || fail 22 | 23 | vmlinux_before=$(stat -c%Y vmlinux) 24 | ninja -d explain 25 | vmlinux_after=$(stat -c%Y vmlinux) 26 | [ "$vmlinux_before" = "$vmlinux_after" ] || fail 27 | 28 | ninja -d explain | tee $log 29 | grep -q 'no work' $log || fail 30 | ! grep -q 'kninja' $log || fail 31 | 32 | touch $src/init/calibrate.c 33 | ninja -d explain | tee $log 34 | ! grep -q 'no work' $log || fail 35 | ! grep -q 'kninja' $log || fail 36 | 37 | vmlinux_before=$(stat -c%Y vmlinux) 38 | make 39 | vmlinux_after=$(stat -c%Y vmlinux) 40 | [ "$vmlinux_before" = "$vmlinux_after" ] || fail 41 | 42 | ninja -d explain | tee $log 43 | grep -q 'no work' $log || fail 44 | ! grep -q 'kninja' $log || fail 45 | 46 | touch $obj/.config 47 | ninja -d explain | tee $log 48 | grep -q 'kninja' $log || fail 49 | 50 | touch $src/init/Kconfig 51 | ninja -d explain | tee $log 52 | grep -q 'kninja' $log || fail 53 | 54 | touch $src/arch/arm/boot/compressed/vmlinux.lds.S 55 | touch $src/arch/arm64/kernel/vdso/sigreturn.S 56 | touch $src/arch/x86/realmode/init.c 57 | ninja -d explain | tee $log 58 | grep -q 'kninja' $log || fail 59 | 60 | vmlinux_before=$(stat -c%Y vmlinux) 61 | make 62 | vmlinux_after=$(stat -c%Y vmlinux) 63 | [ "$vmlinux_before" = "$vmlinux_after" ] || fail 64 | 65 | touch $src/drivers/block/null_blk.h 66 | ninja -d explain | tee $log 67 | grep -q 'null_blk' $log || fail 68 | ! grep -q 'no work' $log || fail 69 | ! grep -q 'vmlinux' $log || fail 70 | 71 | before=$(stat -c%Y drivers/block/null_blk.ko) 72 | make 73 | after=$(stat -c%Y drivers/block/null_blk.ko) 74 | [ "$before" = "$after" ] || fail 75 | 76 | make clean 77 | 78 | # Broken after clean 79 | # ninja -d explain | tee $log 80 | # grep -q 'kninja' $log || fail 81 | } 82 | 83 | intree() { 84 | cd $src 85 | git reset --hard 86 | rm -f build.ninja 87 | make mrproper 88 | 89 | make $config 90 | 91 | obj=$src 92 | runtest 93 | } 94 | 95 | outoftree() { 96 | cd $src 97 | git reset --hard 98 | rm -f build.ninja 99 | make mrproper 100 | 101 | obj=$PWD/out 102 | rm -rf $obj 103 | mkdir -p $obj 104 | 105 | make O=$obj $config 106 | cd $obj 107 | runtest 108 | cd $src 109 | } 110 | 111 | unset ARCH CROSS_COMPILE 112 | intree 113 | outoftree 114 | 115 | export ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- 116 | intree 117 | outoftree 118 | 119 | export ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- 120 | intree 121 | outoftree 122 | 123 | echo OK. 124 | --------------------------------------------------------------------------------