#!/usr/bin/env python

"""Helps to keep BUILD.gn files in sync with the corresponding CMakeLists.txt.

For each BUILD.gn file in the tree, checks if the list of cpp files in
it is identical to the list of cpp files in the corresponding CMakeLists.txt
file, and prints the difference if not.

Also checks that each CMakeLists.txt file below unittests/ folders that define
binaries have corresponding BUILD.gn files.
"""

from __future__ import print_function

import os
import re
import subprocess


def sync_source_lists():
    gn_files = subprocess.check_output(
            ['git', 'ls-files', '*BUILD.gn']).splitlines()

    # Matches e.g. |   "foo.cpp",|, captures |foo| in group 1.
    gn_cpp_re = re.compile(r'^\s*"([^"]+\.(?:cpp|h))",$', re.MULTILINE)
    # Matches e.g. |   foo.cpp|, captures |foo| in group 1.
    cmake_cpp_re = re.compile(r'^\s*([A-Za-z_0-9/-]+\.(?:cpp|h))$',
                              re.MULTILINE)

    for gn_file in gn_files:
        # The CMakeLists.txt for llvm/utils/gn/secondary/foo/BUILD.gn is
        # directly at foo/CMakeLists.txt.
        strip_prefix = 'llvm/utils/gn/secondary/'
        if not gn_file.startswith(strip_prefix):
            continue
        cmake_file = os.path.join(
                os.path.dirname(gn_file[len(strip_prefix):]), 'CMakeLists.txt')
        if not os.path.exists(cmake_file):
            continue

        def get_sources(source_re, text):
            return set([m.group(1) for m in source_re.finditer(text)])
        gn_cpp = get_sources(gn_cpp_re, open(gn_file).read())
        cmake_cpp = get_sources(cmake_cpp_re, open(cmake_file).read())

        if gn_cpp == cmake_cpp:
            continue

        print(gn_file)
        add = cmake_cpp - gn_cpp
        if add:
            print('add:\n' + '\n'.join('    "%s",' % a for a in add))
        remove = gn_cpp - cmake_cpp
        if remove:
            print('remove:\n' + '\n'.join(remove))
        print()


def sync_unittests():
    # Matches e.g. |add_llvm_unittest_with_input_files|.
    unittest_re = re.compile(r'^add_\S+_unittest', re.MULTILINE)

    checked = [ 'clang', 'lld', 'llvm' ]
    for c in checked:
        for root, _, _ in os.walk(os.path.join(c, 'unittests')):
            cmake_file = os.path.join(root, 'CMakeLists.txt')
            if not os.path.exists(cmake_file):
                continue
            if not unittest_re.search(open(cmake_file).read()):
                continue  # Skip CMake files that just add subdirectories.
            gn_file = os.path.join('llvm/utils/gn/secondary', root, 'BUILD.gn')
            if not os.path.exists(gn_file):
                print('missing GN file %s for unittest CMake file %s' %
                      (gn_file, cmake_file))


def main():
    sync_source_lists()
    sync_unittests()


if __name__ == '__main__':
    main()
