VirtualBox

source: vbox/trunk/src/VBox/Devices/EFI/FirmwareNew/.pytool/Plugin/SpellCheck/SpellCheck.py@ 108794

Last change on this file since 108794 was 108794, checked in by vboxsync, 2 weeks ago

Devices/EFI/FirmwareNew: Merge edk2-stable202502 from the vendor branch and make it build for the important platforms, bugref:4643

  • Property svn:eol-style set to native
File size: 9.0 KB
Line 
1# @file SpellCheck.py
2#
3# An edk2-pytool based plugin wrapper for cspell
4#
5# Copyright (c) Microsoft Corporation.
6# SPDX-License-Identifier: BSD-2-Clause-Patent
7##
8import logging
9import json
10import yaml
11from io import StringIO
12import os
13from edk2toolext.environment.plugintypes.ci_build_plugin import ICiBuildPlugin
14from edk2toollib.utility_functions import RunCmd
15from edk2toolext.environment.var_dict import VarDict
16from edk2toollib.gitignore_parser import parse_gitignore_lines
17from edk2toolext.environment import version_aggregator
18
19
20class SpellCheck(ICiBuildPlugin):
21 """
22 A CiBuildPlugin that uses the cspell node module to scan the files
23 from the package being tested for spelling errors. The plugin contains
24 the base cspell.json file then thru the configuration options other settings
25 can be changed or extended.
26
27 Configuration options:
28 "SpellCheck": {
29 "AuditOnly": False, # Don't fail the build if there are errors. Just log them
30 "IgnoreFiles": [], # use gitignore syntax to ignore errors in matching files
31 "ExtendWords": [], # words to extend to the dictionary for this package
32 "IgnoreStandardPaths": [], # Standard Plugin defined paths that should be ignore
33 "AdditionalIncludePaths": [] # Additional paths to spell check (wildcards supported)
34 }
35 """
36
37 #
38 # A package can remove any of these using IgnoreStandardPaths
39 #
40 STANDARD_PLUGIN_DEFINED_PATHS = ("*.c", "*.h",
41 "*.nasm", "*.asm", "*.masm", "*.s",
42 "*.asl",
43 "*.dsc", "*.dec", "*.fdf", "*.inf",
44 "*.md", "*.txt"
45 )
46
47 def GetTestName(self, packagename: str, environment: VarDict) -> tuple:
48 """ Provide the testcase name and classname for use in reporting
49
50 Args:
51 packagename: string containing name of package to build
52 environment: The VarDict for the test to run in
53 Returns:
54 a tuple containing the testcase name and the classname
55 (testcasename, classname)
56 testclassname: a descriptive string for the testcase can include whitespace
57 classname: should be patterned <packagename>.<plugin>.<optionally any unique condition>
58 """
59 return ("Spell check files in " + packagename, packagename + ".SpellCheck")
60
61 ##
62 # External function of plugin. This function is used to perform the task of the CiBuild Plugin
63 #
64 # - package is the edk2 path to package. This means workspace/packagepath relative.
65 # - edk2path object configured with workspace and packages path
66 # - PkgConfig Object (dict) for the pkg
67 # - EnvConfig Object
68 # - Plugin Manager Instance
69 # - Plugin Helper Obj Instance
70 # - Junit Logger
71 # - output_stream the StringIO output stream from this plugin via logging
72
73 def RunBuildPlugin(self, packagename, Edk2pathObj, pkgconfig, environment, PLM, PLMHelper, tc, output_stream=None):
74 Errors = []
75
76 abs_pkg_path = Edk2pathObj.GetAbsolutePathOnThisSystemFromEdk2RelativePath(
77 packagename)
78
79 if abs_pkg_path is None:
80 tc.SetSkipped()
81 tc.LogStdError("No package {0}".format(packagename))
82 return -1
83
84 # check for node
85 return_buffer = StringIO()
86 ret = RunCmd("node", "--version", outstream=return_buffer)
87 if (ret != 0):
88 tc.SetSkipped()
89 tc.LogStdError("NodeJs not installed. Test can't run")
90 logging.warning("NodeJs not installed. Test can't run")
91 return -1
92 node_version = return_buffer.getvalue().strip() # format vXX.XX.XX
93 tc.LogStdOut(f"Node version: {node_version}")
94 version_aggregator.GetVersionAggregator().ReportVersion(
95 "NodeJs", node_version, version_aggregator.VersionTypes.INFO)
96
97 # Check for cspell
98 return_buffer = StringIO()
99 ret = RunCmd("cspell", "--version", outstream=return_buffer)
100 if (ret != 0):
101 tc.SetSkipped()
102 tc.LogStdError("cspell not installed. Test can't run")
103 logging.warning("cspell not installed. Test can't run")
104 return -1
105 cspell_version = return_buffer.getvalue().strip() # format XX.XX.XX
106 tc.LogStdOut(f"CSpell version: {cspell_version}")
107 version_aggregator.GetVersionAggregator().ReportVersion(
108 "CSpell", cspell_version, version_aggregator.VersionTypes.INFO)
109
110 # copy the default as a list
111 package_relative_paths_to_spell_check = list(SpellCheck.STANDARD_PLUGIN_DEFINED_PATHS)
112
113 #
114 # Allow the ci.yaml to remove any of the above standard paths
115 #
116 if("IgnoreStandardPaths" in pkgconfig):
117 for a in pkgconfig["IgnoreStandardPaths"]:
118 if(a in package_relative_paths_to_spell_check):
119 tc.LogStdOut(
120 f"ignoring standard path due to ci.yaml ignore: {a}")
121 package_relative_paths_to_spell_check.remove(a)
122 else:
123 tc.LogStdOut(f"Invalid IgnoreStandardPaths value: {a}")
124
125 #
126 # check for any additional include paths defined by package config
127 #
128 if("AdditionalIncludePaths" in pkgconfig):
129 package_relative_paths_to_spell_check.extend(
130 pkgconfig["AdditionalIncludePaths"])
131
132 #
133 # Make the path string for cspell to check
134 #
135 relpath = os.path.relpath(abs_pkg_path)
136 cpsell_paths = " ".join(
137 # Double quote each path to defer expansion to cspell parameters
138 [f'"{relpath}/**/{x}"' for x in package_relative_paths_to_spell_check])
139
140 # Make the config file
141 config_file_path = os.path.join(
142 Edk2pathObj.WorkspacePath, "Build", packagename, "cspell_actual_config.json")
143 mydir = os.path.dirname(os.path.abspath(__file__))
144 # load as yaml so it can have comments
145 base = os.path.join(mydir, "cspell.base.yaml")
146 with open(base, "r") as i:
147 config = yaml.safe_load(i)
148
149 if("ExtendWords" in pkgconfig):
150 config["words"].extend(pkgconfig["ExtendWords"])
151 with open(config_file_path, "w") as o:
152 json.dump(config, o) # output as json so compat with cspell
153
154 All_Ignores = []
155 # Parse the config for other ignores
156 if "IgnoreFiles" in pkgconfig:
157 All_Ignores.extend(pkgconfig["IgnoreFiles"])
158
159 # spell check all the files
160 ignore = parse_gitignore_lines(All_Ignores, os.path.join(
161 abs_pkg_path, "nofile.txt"), abs_pkg_path)
162
163 # result is a list of strings like this
164 # C:\src\sp-edk2\edk2\FmpDevicePkg\FmpDevicePkg.dec:53:9 - Unknown word (Capule)
165 EasyFix = []
166 results = self._check_spelling(cpsell_paths, config_file_path)
167 for r in results:
168 path, _, word = r.partition(" - Unknown word ")
169 if len(word) == 0:
170 # didn't find pattern
171 continue
172
173 pathinfo = path.rsplit(":", 2) # remove the line no info
174 if(ignore(pathinfo[0])): # check against ignore list
175 tc.LogStdOut(f"ignoring error due to ci.yaml ignore: {r}")
176 continue
177
178 # real error
179 EasyFix.append(word.strip().strip("()"))
180 Errors.append(r)
181
182 # Log all errors tc StdError
183 for l in Errors:
184 tc.LogStdError(l.strip())
185
186 # Helper - Log the syntax needed to add these words to dictionary
187 if len(EasyFix) > 0:
188 EasyFix = sorted(set(a.lower() for a in EasyFix))
189 logging.error(f'SpellCheck found {len(EasyFix)} failing words. See CI log for details.')
190 tc.LogStdOut("\n Easy fix:")
191 OneString = "If these are not errors add this to your ci.yaml file.\n"
192 OneString += '"SpellCheck": {\n "ExtendWords": ['
193 for a in EasyFix:
194 tc.LogStdOut(f'\n"{a}",')
195 OneString += f'\n "{a}",'
196 logging.critical(OneString.rstrip(",") + '\n ]\n}')
197
198 # add result to test case
199 overall_status = len(Errors)
200 if overall_status != 0:
201 if "AuditOnly" in pkgconfig and pkgconfig["AuditOnly"]:
202 # set as skipped if AuditOnly
203 tc.SetSkipped()
204 return -1
205 else:
206 tc.SetFailed("SpellCheck {0} Failed. Errors {1}".format(
207 packagename, overall_status), "CHECK_FAILED")
208 else:
209 tc.SetSuccess()
210 return overall_status
211
212 def _check_spelling(self, abs_file_to_check: str, abs_config_file_to_use: str) -> []:
213 output = StringIO()
214 ret = RunCmd(
215 "cspell", f"--config {abs_config_file_to_use} {abs_file_to_check}", outstream=output)
216 if ret == 0:
217 return []
218 else:
219 return output.getvalue().strip().splitlines()
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette