1 | # @file LicenseCheck.py
|
---|
2 | #
|
---|
3 | # Copyright (c) 2020, Intel Corporation. All rights reserved.<BR>
|
---|
4 | # SPDX-License-Identifier: BSD-2-Clause-Patent
|
---|
5 | ##
|
---|
6 |
|
---|
7 | import os
|
---|
8 | import shutil
|
---|
9 | import logging
|
---|
10 | import re
|
---|
11 | from io import StringIO
|
---|
12 | from typing import List, Tuple
|
---|
13 | from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
|
---|
14 | from edk2toolext.environment.var_dict import VarDict
|
---|
15 | from edk2toollib.utility_functions import RunCmd
|
---|
16 |
|
---|
17 |
|
---|
18 | class LicenseCheck(ICiBuildPlugin):
|
---|
19 |
|
---|
20 | """
|
---|
21 | A CiBuildPlugin to check the license for new added files.
|
---|
22 |
|
---|
23 | Configuration options:
|
---|
24 | "LicenseCheck": {
|
---|
25 | "IgnoreFiles": []
|
---|
26 | },
|
---|
27 | """
|
---|
28 |
|
---|
29 | license_format_preflix = 'SPDX-License-Identifier'
|
---|
30 |
|
---|
31 | bsd2_patent = 'BSD-2-Clause-Patent'
|
---|
32 |
|
---|
33 | Readdedfileformat = re.compile(r'\+\+\+ b\/(.*)')
|
---|
34 |
|
---|
35 | file_extension_list = [".c", ".h", ".inf", ".dsc", ".dec", ".py", ".bat", ".sh", ".uni", ".yaml",
|
---|
36 | ".fdf", ".inc", "yml", ".asm", ".asm16", ".asl", ".vfr", ".s", ".S", ".aslc",
|
---|
37 | ".nasm", ".nasmb", ".idf", ".Vfr", ".H"]
|
---|
38 |
|
---|
39 | def GetTestName(self, packagename: str, environment: VarDict) -> tuple:
|
---|
40 | """ Provide the testcase name and classname for use in reporting
|
---|
41 | testclassname: a descriptive string for the testcase can include whitespace
|
---|
42 | classname: should be patterned <packagename>.<plugin>.<optionally any unique condition>
|
---|
43 |
|
---|
44 | Args:
|
---|
45 | packagename: string containing name of package to build
|
---|
46 | environment: The VarDict for the test to run in
|
---|
47 | Returns:
|
---|
48 | a tuple containing the testcase name and the classname
|
---|
49 | (testcasename, classname)
|
---|
50 | """
|
---|
51 | return ("Check for license for " + packagename, packagename + ".LicenseCheck")
|
---|
52 |
|
---|
53 | ##
|
---|
54 | # External function of plugin. This function is used to perform the task of the ci_build_plugin Plugin
|
---|
55 | #
|
---|
56 | # - package is the edk2 path to package. This means workspace/packagepath relative.
|
---|
57 | # - edk2path object configured with workspace and packages path
|
---|
58 | # - PkgConfig Object (dict) for the pkg
|
---|
59 | # - EnvConfig Object
|
---|
60 | # - Plugin Manager Instance
|
---|
61 | # - Plugin Helper Obj Instance
|
---|
62 | # - Junit Logger
|
---|
63 | # - output_stream the StringIO output stream from this plugin via logging
|
---|
64 | def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream=None):
|
---|
65 | # Create temp directory
|
---|
66 | temp_path = os.path.join(Edk2pathObj.WorkspacePath, 'Build', '.pytool', 'Plugin', 'LicenseCheck')
|
---|
67 | if not os.path.exists(temp_path):
|
---|
68 | os.makedirs(temp_path)
|
---|
69 | # Output file to use for git diff operations
|
---|
70 | temp_diff_output = os.path.join (temp_path, 'diff.txt')
|
---|
71 | params = "diff --output={} --unified=0 origin/master HEAD".format(temp_diff_output)
|
---|
72 | RunCmd("git", params)
|
---|
73 | with open(temp_diff_output, encoding='utf8') as file:
|
---|
74 | patch = file.read().strip().split("\n")
|
---|
75 | # Delete temp directory
|
---|
76 | if os.path.exists(temp_path):
|
---|
77 | shutil.rmtree(temp_path)
|
---|
78 |
|
---|
79 | ignore_files = []
|
---|
80 | if "IgnoreFiles" in pkgconfig:
|
---|
81 | ignore_files = pkgconfig["IgnoreFiles"]
|
---|
82 |
|
---|
83 | self.ok = True
|
---|
84 | self.startcheck = False
|
---|
85 | self.license = True
|
---|
86 | self.all_file_pass = True
|
---|
87 | count = len(patch)
|
---|
88 | line_index = 0
|
---|
89 | for line in patch:
|
---|
90 | if line.startswith('--- /dev/null'):
|
---|
91 | nextline = patch[line_index + 1]
|
---|
92 | added_file = self.Readdedfileformat.search(nextline).group(1)
|
---|
93 | added_file_extension = os.path.splitext(added_file)[1]
|
---|
94 | if added_file_extension in self.file_extension_list and packagename in added_file:
|
---|
95 | if (self.IsIgnoreFile(added_file, ignore_files)):
|
---|
96 | line_index = line_index + 1
|
---|
97 | continue
|
---|
98 | self.startcheck = True
|
---|
99 | self.license = False
|
---|
100 | if self.startcheck and self.license_format_preflix in line:
|
---|
101 | if self.bsd2_patent in line:
|
---|
102 | self.license = True
|
---|
103 | if line_index + 1 == count or patch[line_index + 1].startswith('diff --') and self.startcheck:
|
---|
104 | if not self.license:
|
---|
105 | self.all_file_pass = False
|
---|
106 | error_message = "Invalid license in: " + added_file + " Hint: Only BSD-2-Clause-Patent is accepted."
|
---|
107 | logging.error(error_message)
|
---|
108 | self.startcheck = False
|
---|
109 | self.license = True
|
---|
110 | line_index = line_index + 1
|
---|
111 |
|
---|
112 | if self.all_file_pass:
|
---|
113 | tc.SetSuccess()
|
---|
114 | return 0
|
---|
115 | else:
|
---|
116 | tc.SetFailed("License Check {0} Failed. ".format(packagename), "LICENSE_CHECK_FAILED")
|
---|
117 | return 1
|
---|
118 |
|
---|
119 | def IsIgnoreFile(self, file: str, ignore_files: List[str]) -> bool:
|
---|
120 | for f in ignore_files:
|
---|
121 | if f in file:
|
---|
122 | return True
|
---|
123 | return False
|
---|