clang_compilation_database.py 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Christoph Koke, 2013
  4. """
  5. Writes the c and cpp compile commands into build/compile_commands.json
  6. see http://clang.llvm.org/docs/JSONCompilationDatabase.html
  7. Usage:
  8. def configure(conf):
  9. conf.load('compiler_cxx')
  10. ...
  11. conf.load('clang_compilation_database')
  12. """
  13. import sys, os, json, shlex, pipes
  14. from waflib import Logs, TaskGen, Task
  15. Task.Task.keep_last_cmd = True
  16. @TaskGen.feature('c', 'cxx')
  17. @TaskGen.after_method('process_use')
  18. def collect_compilation_db_tasks(self):
  19. "Add a compilation database entry for compiled tasks"
  20. try:
  21. clang_db = self.bld.clang_compilation_database_tasks
  22. except AttributeError:
  23. clang_db = self.bld.clang_compilation_database_tasks = []
  24. self.bld.add_post_fun(write_compilation_database)
  25. tup = tuple(y for y in [Task.classes.get(x) for x in ('c', 'cxx')] if y)
  26. for task in getattr(self, 'compiled_tasks', []):
  27. if isinstance(task, tup):
  28. clang_db.append(task)
  29. def write_compilation_database(ctx):
  30. "Write the clang compilation database as JSON"
  31. database_file = ctx.bldnode.make_node('compile_commands.json')
  32. Logs.info('Build commands will be stored in %s', database_file.path_from(ctx.path))
  33. try:
  34. root = json.load(database_file)
  35. except IOError:
  36. root = []
  37. clang_db = dict((x['file'], x) for x in root)
  38. for task in getattr(ctx, 'clang_compilation_database_tasks', []):
  39. try:
  40. cmd = task.last_cmd
  41. except AttributeError:
  42. continue
  43. directory = getattr(task, 'cwd', ctx.variant_dir)
  44. f_node = task.inputs[0]
  45. filename = os.path.relpath(f_node.abspath(), directory)
  46. entry = {
  47. "directory": directory,
  48. "arguments": cmd,
  49. "file": filename,
  50. }
  51. clang_db[filename] = entry
  52. root = list(clang_db.values())
  53. database_file.write(json.dumps(root, indent=2))
  54. # Override the runnable_status function to do a dummy/dry run when the file doesn't need to be compiled.
  55. # This will make sure compile_commands.json is always fully up to date.
  56. # Previously you could end up with a partial compile_commands.json if the build failed.
  57. for x in ('c', 'cxx'):
  58. if x not in Task.classes:
  59. continue
  60. t = Task.classes[x]
  61. def runnable_status(self):
  62. def exec_command(cmd, **kw):
  63. pass
  64. run_status = self.old_runnable_status()
  65. if run_status == Task.SKIP_ME:
  66. setattr(self, 'old_exec_command', getattr(self, 'exec_command', None))
  67. setattr(self, 'exec_command', exec_command)
  68. self.run()
  69. setattr(self, 'exec_command', getattr(self, 'old_exec_command', None))
  70. return run_status
  71. setattr(t, 'old_runnable_status', getattr(t, 'runnable_status', None))
  72. setattr(t, 'runnable_status', runnable_status)