#!/usr/bin/env python # -*- coding: utf-8 -*- """ JuJu - An automagical compiler for the とあるOS userspace. """ import os import subprocess import sys TOOLCHAIN_PATH = os.environ['TOOLCHAIN'] class CCompiler(object): extension = 'c' compiler = 'i686-pc-toaru-gcc' dependency_hints = { '': ('cairo', '-lcairo', ['', '']), '': ('freetype2', '-lfreetype', []), '': ('pixman-1', '-lpixman-1', []), '': (None, '-lz', ['']), '': (None, '-lpng', ['']), '': (None, TOOLCHAIN_PATH + '/lib/libm.a', []), '"lib/decorations.h"': (None, 'lib/decorations.o', ['"lib/shmemfonts.h"', '"lib/graphics.h"', '"lib/window.h"']), '"lib/graphics.h"': (None, 'lib/graphics.o', ['']), '"lib/kbd.h"': (None, 'lib/kbd.o', []), '"lib/list.h"': (None, 'lib/list.o', []), '"lib/pthread.h"': (None, 'lib/pthread.o', []), '"lib/sha2.h"': (None, 'lib/sha2.o', []), '"lib/shmemfonts.h"': (None, 'lib/shmemfonts.o', ['"lib/graphics.h"', '']), '"lib/wcwidth.h"': (None, 'lib/wcwidth.o', []), '"lib/window.h"': (None, 'lib/window.o', ['"lib/pthread.h"', '"lib/list.h"']), } def __init__(self, filename): self.filename = filename self.arguments = ['-std=c99', '-U__STRICT_ANSI__', '-O3', '-m32', '-Wa,--32', '-g', '-I.'] if '/lib/' in filename: self.includes, _ = self._depends() self.libs = [] self.arguments.extend(self.includes) self.arguments.extend(['-c', '-o', self.output_file(), filename]) else: self.includes, self.libs = self._depends() self.arguments.extend(self.includes) self.arguments.extend(['-flto', '-o', self.output_file(), filename]) self.arguments.extend(self.libs) def dependencies(self): return [os.path.abspath(x) for x in self.libs if x.startswith('lib/')] def output_file(self): if '/lib/' in self.filename: return os.path.abspath(self.filename.replace('.c','.o')) else: return os.path.abspath('../hdd/bin/' + os.path.basename(self.filename).replace('.c','')) def _calculate(self, depends, new): for k in new: if not k in depends: depends.append(k) _, _, other = self.dependency_hints[k] depends = self._calculate(depends, other) return depends def _depends(self): lines = [] depends = [] with open(self.filename) as f: lines = f.readlines() for l in lines: if l.startswith('#include'): depends.extend([k for k in self.dependency_hints.keys() if l.startswith('#include ' + k)]) depends = self._calculate([], depends) includes = [] libraries = [] for k in depends: dep = self.dependency_hints[k] if dep[0]: includes.append('-I' + TOOLCHAIN_PATH + '/include/' + dep[0]) if dep[1]: libraries.append(dep[1]) return includes, libraries def notify_start(self): subprocess.call(['../util/mk-beg', 'CC', self.filename]) def notify_done(self): subprocess.call(['../util/mk-end', 'CC', self.filename]) class CXXCompiler(CCompiler): extension = 'cpp' compiler = 'i686-pc-toaru-g++' def __init__(self, filename): self.filename = filename self.arguments = ['-O3', '-m32', '-Wa,--32', '-g', '-I.'] if '/lib/' in filename: self.includes, _ = self._depends() self.libs = [] self.arguments.extend(self.includes) self.arguments.extend(['-c', '-o', self.output_file(), filename]) else: self.includes, self.libs = self._depends() self.arguments.extend(self.includes) self.arguments.extend(['-flto', '-o', self.output_file(), filename]) self.arguments.extend(self.libs) def output_file(self): if '/lib/' in self.filename: return os.path.abspath(self.filename.replace('.cpp','.o')) else: return os.path.abspath('../hdd/bin/' + os.path.basename(self.filename).replace('.cpp','')) def notify_start(self): subprocess.call(['../util/mk-beg', 'CPP', self.filename]) def notify_done(self): subprocess.call(['../util/mk-end', 'CPP', self.filename]) source_extensions = {'c': CCompiler, 'cpp': CXXCompiler} def find_sources(path): sources = [] for directory, subdirectories, files in os.walk(path): for f in files: for k, v in source_extensions.iteritems(): if f.endswith('.' + k): sources.append((directory + '/' + f, v)) return sources sources = {} for source, compiler in find_sources('.'): sources[os.path.abspath(source)] = compiler(source) outputs = {} outputs_r = {} for k,v in sources.iteritems(): outputs[k] = v.output_file() outputs_r[outputs[k]] = k marked = [] rounds = [] remaining = outputs_r.keys() def satisfied(): return len(remaining) == 0 while not satisfied(): this_round = [] for target in [x for x in outputs_r.keys() if x not in marked]: x = sources[outputs_r[target]] if all([(y in marked) for y in x.dependencies()]): marked.append(target) remaining.remove(target) this_round.append(target) else: for y in [dep for dep in x.dependencies() if not dep in marked]: if not y in remaining: remaining.append(y) if not this_round and not satisfied(): raise Exception("Dependency resolution error.") rounds.append(this_round) needs_rebuild = [] for r in rounds: for target in r: source = outputs_r[target] source_obj = sources[source] if not os.path.exists(target): needs_rebuild.append(target) elif os.path.getmtime(target) < os.path.getmtime(source): needs_rebuild.append(target) elif [True for x in source_obj.dependencies() if x in needs_rebuild]: needs_rebuild.append(target) elif [True for x in source_obj.dependencies() if os.path.exists(target) and (os.path.getmtime(target) < os.path.getmtime(x))]: needs_rebuild.append(target) if len(sys.argv) > 1 and sys.argv[1] == 'status': if not needs_rebuild: print "Nothing to do." elif len(needs_rebuild) == 1: print "One file needs rebuilding." else: print len(needs_rebuild), "file(s) need rebuilding." elif len(sys.argv) > 1 and sys.argv[1] == 'clean': for i in outputs_r.keys(): subprocess.call(['../util/mk-beg-rm', 'RM', i]) subprocess.call(['rm', i]) subprocess.call(['../util/mk-end-rm', 'RM', i]) else: if not needs_rebuild: print "Nothing to do." else: for target in needs_rebuild: source_obj = sources[outputs_r[target]] cmd = [source_obj.compiler] cmd.extend(source_obj.arguments) source_obj.notify_start() ret = subprocess.call(cmd) source_obj.notify_done() if ret: print target, ret print cmd raise Exception