#!/usr/bin/env python3
#
# Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.

# Simple tool for verifying that sources from the standalone embedder do not
# directly include sources from the VM or vice versa.

import os
import re
import sys

INCLUDE_DIRECTIVE_RE = re.compile(r'^#include "(.*)"')

PLATFORM_LAYER_RE = re.compile(r'^runtime/platform/')
VM_LAYER_RE = re.compile(r'^runtime/(vm|lib)/')
BIN_LAYER_RE = re.compile(r'^runtime/bin/')

# Tests that don't match the simple case of *_test.cc.
EXTRA_TEST_FILES = [
    'runtime/bin/run_vm_tests.cc',
    'runtime/bin/ffi_unit_test/run_ffi_unit_tests.cc',
    'runtime/vm/libfuzzer/dart_libfuzzer.cc'
]


def CheckFile(sdk_root, path):
    includes = set()
    with open(os.path.join(sdk_root, path), encoding='utf-8') as file:
        for line in file:
            m = INCLUDE_DIRECTIVE_RE.match(line)
            if m is not None:
                header = os.path.join('runtime', m.group(1))
                if os.path.isfile(os.path.join(sdk_root, header)):
                    includes.add(header)

    errors = []
    for include in includes:
        if PLATFORM_LAYER_RE.match(path):
            if VM_LAYER_RE.match(include):
                errors.append(
                    'LAYERING ERROR: %s must not include %s' % (path, include))
            elif BIN_LAYER_RE.match(include):
                errors.append(
                    'LAYERING ERROR: %s must not include %s' % (path, include))
        elif VM_LAYER_RE.match(path):
            if BIN_LAYER_RE.match(include):
                errors.append(
                    'LAYERING ERROR: %s must not include %s' % (path, include))
        elif BIN_LAYER_RE.match(path):
            if VM_LAYER_RE.match(include):
                errors.append(
                    'LAYERING ERROR: %s must not include %s' % (path, include))
    return errors


def CheckDir(sdk_root, dir):
    errors = []
    for file in os.listdir(dir):
        path = os.path.join(dir, file)
        if os.path.isdir(path):
            errors += CheckDir(sdk_root, path)
        elif path.endswith('test.cc') or path in EXTRA_TEST_FILES:
            None  # Tests may violate layering.
        elif path.endswith('.cc') or path.endswith('.h'):
            errors += CheckFile(sdk_root, os.path.relpath(path, sdk_root))
    return errors


def DoCheck(sdk_root):
    return CheckDir(sdk_root, 'runtime')


if __name__ == '__main__':
    errors = DoCheck('.')
    print('\n'.join(errors))
    if errors:
        sys.exit(-1)
