1# @file CharEncodingCheck.py
2#
3# Copyright (c) Microsoft Corporation.
4# SPDX-License-Identifier: BSD-2-Clause-Patent
5##
6
7
8import os
9import logging
10from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
11from edk2toolext.environment.var_dict import VarDict
12
13##
14# map
15##
16EcodingMap = {
17    ".md": 'utf-8',
18    ".dsc": 'utf-8',
19    ".dec": 'utf-8',
20    ".c": 'utf-8',
21    ".h": 'utf-8',
22    ".asm": 'utf-8',
23    ".masm": 'utf-8',
24    ".nasm": 'utf-8',
25    ".s": 'utf-8',
26    ".inf": 'utf-8',
27    ".asl": 'utf-8',
28    ".uni": 'utf-8',
29    ".py": 'utf-8'
30}
31
32
33class CharEncodingCheck(ICiBuildPlugin):
34    """
35    A CiBuildPlugin that scans each file in the code tree and confirms the encoding is correct.
36
37    Configuration options:
38    "CharEncodingCheck": {
39        "IgnoreFiles": []
40    }
41    """
42
43    def GetTestName(self, packagename: str, environment: VarDict) -> tuple:
44        """ Provide the testcase name and classname for use in reporting
45            testclassname: a descriptive string for the testcase can include whitespace
46            classname: should be patterned <packagename>.<plugin>.<optionally any unique condition>
47
48            Args:
49              packagename: string containing name of package to build
50              environment: The VarDict for the test to run in
51            Returns:
52                a tuple containing the testcase name and the classname
53                (testcasename, classname)
54        """
55        return ("Check for valid file encoding for " + packagename, packagename + ".CharEncodingCheck")
56
57    ##
58    # External function of plugin.  This function is used to perform the task of the ci_build_plugin Plugin
59    #
60    #   - package is the edk2 path to package.  This means workspace/packagepath relative.
61    #   - edk2path object configured with workspace and packages path
62    #   - PkgConfig Object (dict) for the pkg
63    #   - EnvConfig Object
64    #   - Plugin Manager Instance
65    #   - Plugin Helper Obj Instance
66    #   - Junit Logger
67    #   - output_stream the StringIO output stream from this plugin via logging
68    def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream=None):
69        overall_status = 0
70        files_tested = 0
71
72        abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(packagename)
73
74        if abs_pkg_path is None:
75            tc.SetSkipped()
76            tc.LogStdError("No Package folder {0}".format(abs_pkg_path))
77            return 0
78
79        for (ext, enc) in EcodingMap.items():
80            files = self.WalkDirectoryForExtension([ext], abs_pkg_path)
81            files = [Edk2pathObj.GetEdk2RelativePathFromAbsolutePath(x) for x in files]  # make edk2relative path so can process ignores
82
83            if "IgnoreFiles" in pkgconfig:
84                for a in pkgconfig["IgnoreFiles"]:
85                    a = a.replace(os.sep, "/")
86                    try:
87                        tc.LogStdOut("Ignoring File {0}".format(a))
88                        files.remove(a)
89                    except:
90                        tc.LogStdError("CharEncodingCheck.IgnoreInf -> {0} not found in filesystem.  Invalid ignore file".format(a))
91                        logging.info("CharEncodingCheck.IgnoreInf -> {0} not found in filesystem.  Invalid ignore file".format(a))
92
93            files = [Edk2pathObj.GetAbsolutePathOnThisSytemFromEdk2RelativePath(x) for x in files]
94            for a in files:
95                files_tested += 1
96                if(self.TestEncodingOk(a, enc)):
97                    logging.debug("File {0} Passed Encoding Check {1}".format(a, enc))
98                else:
99                    tc.LogStdError("Encoding Failure in {0}.  Not {1}".format(a, enc))
100                    overall_status += 1
101
102        tc.LogStdOut("Tested Encoding on {0} files".format(files_tested))
103        if overall_status != 0:
104            tc.SetFailed("CharEncoding {0} Failed.  Errors {1}".format(packagename, overall_status), "CHAR_ENCODING_CHECK_FAILED")
105        else:
106            tc.SetSuccess()
107        return overall_status
108
109    def TestEncodingOk(self, apath, encodingValue):
110        try:
111            with open(apath, "rb") as fobj:
112                fobj.read().decode(encodingValue)
113        except Exception as exp:
114            logging.error("Encoding failure: file: {0} type: {1}".format(apath, encodingValue))
115            logging.debug("EXCEPTION: while processing {1} - {0}".format(exp, apath))
116            return False
117
118        return True
119