# SPDX-FileCopyrightText: 2020 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# SPDX-License-Identifier: Apache-2.0

import argparse
import logging
import os
from pathlib import Path

from strsimpy.sorensen_dice import SorensenDice

# Default values for base license files
APPROVED_LICENSES_PATH = Path(__file__).parent / "_licenses/_approved_licenses"
PROHIBITED_LICENSES_PATH = Path(__file__).parent / "_licenses/_prohibited_licenses"

# Directories, files and file_extensions ignored for license check
IGNORED_DIRS = [".git", ".github", "caravel", "gl", "third_party"]
IGNORED_EXTS = [".cfg", ".csv", ".def", ".drc", ".gds", ".gz", ".hex", ".jpg", ".lef", ".log", ".mag", ".md", ".out", ".pdf", ".png", ".pyc", ".rdb", ".spice", ".svg", ".txt", ".vcd", ".xml"]
IGNORED_FILES = [".git", ".gitignore", ".gitmodules", "info.yaml", "LICENSE", "manifest", "OPENLANE_VERSION", "PDK_SOURCES"]

# Default values for base license files
LICENSE_FILENAME = "LICENSE"
SPDX_COPYRIGHT_HEADER = "SPDX-FileCopyrightText"
SPDX_LICENSE_HEADER = "SPDX-License-Identifier"


def check_license(target_license_path, licenses_path):
    confidence_map = []
    try:
        target_license_file_content = target_license_path.open(encoding="utf-8").read()
    except FileNotFoundError:
        logging.error(f"LICENSE FILE NOT FOUND in {target_license_path.parent}")
        return None

    for license_file in licenses_path.iterdir():
        license_file_content = license_file.open(encoding="utf-8").read()
        confidence = 1 - SorensenDice().distance(license_file_content.strip(), target_license_file_content.strip())
        confidence_map.append({"license_key": license_file.stem, "confidence": confidence * 100})
    license_check_result = max(confidence_map, key=lambda x: x["confidence"])
    return license_check_result["license_key"] if license_check_result["confidence"] > 95 else None


def verify_license_compliance(path):
    path = path / LICENSE_FILENAME
    try:
        prohibited_license = check_license(path, PROHIBITED_LICENSES_PATH)
        if prohibited_license:
            logging.warning(f"A prohibited LICENSE ({prohibited_license}) was found in {path.parent}.")
            return False
        else:
            approved_license = check_license(path, APPROVED_LICENSES_PATH)
            if approved_license:
                logging.info(f"An approved LICENSE ({approved_license}) was found in {path.parent}.")
                return True
            else:
                logging.warning(f"An identifiable LICENSE file was not found in {path.parent}.")
                return False
    except Exception as e:
        logging.fatal(f"VERIFY LICENSE EXCEPTION in ({path}): {e}")
        raise


def check_submodules_licenses(path):
    submodules = []
    for root, dirs, files in os.walk(path):  # note: root = submodule
        if ".git" not in dirs or root == path:
            continue
        submodules.append(verify_license_compliance(path))
    return False if False in submodules else True


def check_third_party_libs_licenses(path):
    libs = []
    for lib_path in next(os.walk(path))[1]:
        lib_path = Path(path) / lib_path
        libs.append(verify_license_compliance(lib_path))
    return False if False in libs else True


def check_dir_spdx_compliance(non_compliant_list, path, license_key=None):
    for root, dirs, files in os.walk(path):
        for file in files:
            file_under_test = Path(root) / file
            if not any(ignored_dir in str(file_under_test.parent) for ignored_dir in IGNORED_DIRS):
                result = check_file_spdx_compliance(file_under_test, license_key)
                if result:
                    non_compliant_list.append(result)
    return non_compliant_list


def check_file_spdx_compliance(file_path, license_key):
    spdx_license_header = SPDX_LICENSE_HEADER if not license_key else f"{'SPDX-License-Identifier'}: {license_key}"
    spdx_compliant = spdx_cp_compliant = spdx_ls_compliant = False

    file_base = file_path.name
    if file_base in IGNORED_FILES:
        return None
    file_ext = file_path.suffix
    if file_ext in IGNORED_EXTS:
        return None

    try:
        with open(file_path, "tr", encoding="utf-8") as f:
            lines = [x.rstrip() for x in f.readlines()]
        if lines and list(filter(None, lines)):
            for line in lines:
                if line:
                    if SPDX_COPYRIGHT_HEADER in line:
                        spdx_cp_compliant = True
                    if spdx_license_header in line:
                        spdx_ls_compliant = True

                if spdx_cp_compliant and spdx_ls_compliant:
                    spdx_compliant = True
                    break
            return file_path if not spdx_compliant else None
    except UnicodeDecodeError as unicode_error:
        logging.error(f"SPDX COMPLIANCE FILE UNICODE DECODE EXCEPTION in ({file_path}): {unicode_error}")
    except FileNotFoundError as file_not_found_error:
        if not Path(file_not_found_error.filename).is_symlink():
            logging.error(f"SPDX COMPLIANCE FILE NOT FOUND in {file_path}")
        else:
            logging.error(f"SPDX COMPLIANCE SYMLINK FILE NOT FOUND in {file_path}")
    return None


if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG, format=f"%(asctime)s | %(levelname)-7s | %(message)s", datefmt='%d-%b-%Y %H:%M:%S')
    default_input_directory = Path(__file__).parents[2] / "_default_content"
    parser = argparse.ArgumentParser(description='Runs a set of license checks on a given directory.')
    parser.add_argument('--input_directory', '-i', required=False, default=default_input_directory, help='Yaml Path')
    args = parser.parse_args()

    if verify_license_compliance(Path(args.input_directory)):
        logging.info("License Clean")
    else:
        logging.warning("License Dirty")

    if check_third_party_libs_licenses(Path(args.input_directory)):
        logging.info("Third Party Libraries Clean")
    else:
        logging.warning("Third Party Libraries Dirty")

    if check_submodules_licenses(Path(args.input_directory)):
        logging.info("Submodules Clean")
    else:
        logging.warning("Submodules Dirty")

    spdx_non_compliant_list = [str(x) for x in check_dir_spdx_compliance([], Path(args.input_directory))]
    if not spdx_non_compliant_list:
        logging.info("Project is compliant with the SPDX Standard")
    else:
        logging.warning(f"Project is not compliant with the SPDX Standard."
                        f" {spdx_non_compliant_list.__len__()} non-compliant files found: {spdx_non_compliant_list}")

# SPDX-FileCopyrightText: 2020 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# SPDX-License-Identifier: Apache-2.0

import argparse
import logging
import os
from pathlib import Path

from strsimpy.sorensen_dice import SorensenDice

# Default values for base license files
APPROVED_LICENSES_PATH = Path(__file__).parent / "_licenses/_approved_licenses"
PROHIBITED_LICENSES_PATH = Path(__file__).parent / "_licenses/_prohibited_licenses"

# Directories, files and file_extensions ignored for license check
IGNORED_DIRS = [".git", ".github", "caravel", "gl", "third_party"]
IGNORED_EXTS = [".cfg", ".csv", ".def", ".drc", ".gds", ".gz", ".hex", ".jpg", ".lef", ".log", ".mag", ".md", ".out", ".pdf", ".png", ".pyc", ".rdb", ".spice", ".svg", ".txt", ".vcd", ".xml"]
IGNORED_FILES = [".git", ".gitignore", ".gitmodules", "info.yaml", "LICENSE", "manifest", "OPENLANE_VERSION", "PDK_SOURCES"]

# Default values for base license files
LICENSE_FILENAME = "LICENSE"
SPDX_COPYRIGHT_HEADER = "SPDX-FileCopyrightText"
SPDX_LICENSE_HEADER = "SPDX-License-Identifier"


def check_license(target_license_path, licenses_path):
    confidence_map = []
    try:
        target_license_file_content = target_license_path.open(encoding="utf-8").read()
    except FileNotFoundError:
        logging.error(f"LICENSE FILE NOT FOUND in {target_license_path.parent}")
        return None

    for license_file in licenses_path.iterdir():
        license_file_content = license_file.open(encoding="utf-8").read()
        confidence = 1 - SorensenDice().distance(license_file_content.strip(), target_license_file_content.strip())
        confidence_map.append({"license_key": license_file.stem, "confidence": confidence * 100})
    license_check_result = max(confidence_map, key=lambda x: x["confidence"])
    return license_check_result["license_key"] if license_check_result["confidence"] > 95 else None


def verify_license_compliance(path):
    path = path / LICENSE_FILENAME
    try:
        prohibited_license = check_license(path, PROHIBITED_LICENSES_PATH)
        if prohibited_license:
            logging.warning(f"A prohibited LICENSE ({prohibited_license}) was found in {path.parent}.")
            return False
        else:
            approved_license = check_license(path, APPROVED_LICENSES_PATH)
            if approved_license:
                logging.info(f"An approved LICENSE ({approved_license}) was found in {path.parent}.")
                return True
            else:
                logging.warning(f"An identifiable LICENSE file was not found in {path.parent}.")
                return False
    except Exception as e:
        logging.fatal(f"VERIFY LICENSE EXCEPTION in ({path}): {e}")
        raise


def check_submodules_licenses(path):
    submodules = []
    for root, dirs, files in os.walk(path):  # note: root = submodule
        if ".git" not in dirs or root == path:
            continue
        submodules.append(verify_license_compliance(path))
    return False if False in submodules else True


def check_third_party_libs_licenses(path):
    libs = []
    for lib_path in next(os.walk(path))[1]:
        lib_path = Path(path) / lib_path
        libs.append(verify_license_compliance(lib_path))
    return False if False in libs else True


def check_dir_spdx_compliance(non_compliant_list, path, license_key=None):
    for root, dirs, files in os.walk(path):
        for file in files:
            file_under_test = Path(root) / file
            if not any(ignored_dir in str(file_under_test.parent) for ignored_dir in IGNORED_DIRS):
                result = check_file_spdx_compliance(file_under_test, license_key)
                if result:
                    non_compliant_list.append(result)
    return non_compliant_list


def check_file_spdx_compliance(file_path, license_key):
    spdx_license_header = SPDX_LICENSE_HEADER if not license_key else f"{'SPDX-License-Identifier'}: {license_key}"
    spdx_compliant = spdx_cp_compliant = spdx_ls_compliant = False

    file_base = file_path.name
    if file_base in IGNORED_FILES:
        return None
    file_ext = file_path.suffix
    if file_ext in IGNORED_EXTS:
        return None

    try:
        with open(file_path, "tr", encoding="utf-8") as f:
            lines = [x.rstrip() for x in f.readlines()]
        if lines and list(filter(None, lines)):
            for line in lines:
                if line:
                    if SPDX_COPYRIGHT_HEADER in line:
                        spdx_cp_compliant = True
                    if spdx_license_header in line:
                        spdx_ls_compliant = True

                if spdx_cp_compliant and spdx_ls_compliant:
                    spdx_compliant = True
                    break
            return file_path if not spdx_compliant else None
    except UnicodeDecodeError as unicode_error:
        logging.error(f"SPDX COMPLIANCE FILE UNICODE DECODE EXCEPTION in ({file_path}): {unicode_error}")
    except FileNotFoundError as file_not_found_error:
        if not Path(file_not_found_error.filename).is_symlink():
            logging.error(f"SPDX COMPLIANCE FILE NOT FOUND in {file_path}")
        else:
            logging.error(f"SPDX COMPLIANCE SYMLINK FILE NOT FOUND in {file_path}")
    return None


if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG, format=f"%(asctime)s | %(levelname)-7s | %(message)s", datefmt='%d-%b-%Y %H:%M:%S')
    default_input_directory = Path(__file__).parents[2] / "_default_content"
    parser = argparse.ArgumentParser(description='Runs a set of license checks on a given directory.')
    parser.add_argument('--input_directory', '-i', required=False, default=default_input_directory, help='Yaml Path')
    args = parser.parse_args()

    if verify_license_compliance(Path(args.input_directory)):
        logging.info("License Clean")
    else:
        logging.warning("License Dirty")

    if check_third_party_libs_licenses(Path(args.input_directory)):
        logging.info("Third Party Libraries Clean")
    else:
        logging.warning("Third Party Libraries Dirty")

    if check_submodules_licenses(Path(args.input_directory)):
        logging.info("Submodules Clean")
    else:
        logging.warning("Submodules Dirty")

    spdx_non_compliant_list = [str(x) for x in check_dir_spdx_compliance([], Path(args.input_directory))]
    if not spdx_non_compliant_list:
        logging.info("Project is compliant with the SPDX Standard")
    else:
        logging.warning(f"Project is not compliant with the SPDX Standard."
                        f" {spdx_non_compliant_list.__len__()} non-compliant files found: {spdx_non_compliant_list}")

# SPDX-FileCopyrightText: 2020 Efabless Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # #      http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # SPDX-License-Identifier: Apache-2.0 # Default values for base license files # Directories, files and file_extensions ignored for license check # Default values for base license files # note: root = submodule