-
Notifications
You must be signed in to change notification settings - Fork 129
Expand file tree
/
Copy path__init__.py
More file actions
200 lines (165 loc) · 8.43 KB
/
__init__.py
File metadata and controls
200 lines (165 loc) · 8.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# -----------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# -----------------------------------------------------------------------------
import os
import sys
import time
import yaml
from knack.help_files import helps
from knack.log import get_logger
from knack.util import CLIError
from azdev.utilities import (
heading, subheading, display, get_path_table, require_azure_cli, filter_by_git_diff)
from azdev.utilities.path import get_cli_repo_path, get_ext_repo_paths
from azdev.operations.style import run_pylint
from .linter import LinterManager, LinterScope, RuleError, LinterSeverity
from .util import filter_modules, merge_exclusion
logger = get_logger(__name__)
CHECKERS_PATH = 'azdev.operations.linter.pylint_checkers'
# pylint:disable=too-many-locals, too-many-statements, too-many-branches
def run_linter(modules=None, rule_types=None, rules=None, ci_exclusions=None,
git_source=None, git_target=None, git_repo=None, include_whl_extensions=False,
min_severity=None, save_global_exclusion=False, base_meta_path=None, diff_meta_path=None):
require_azure_cli()
from azure.cli.core import get_default_cli # pylint: disable=import-error
from azure.cli.core.file_util import ( # pylint: disable=import-error
get_all_help, create_invoker_and_load_cmds_and_args)
heading('CLI Linter')
# allow user to run only on CLI or extensions
cli_only = modules == ['CLI']
ext_only = modules == ['EXT']
if cli_only or ext_only:
modules = None
# process severity option
if min_severity:
try:
min_severity = LinterSeverity.get_linter_severity(min_severity)
except ValueError:
valid_choices = linter_severity_choices()
raise CLIError("Please specify a valid linter severity. It should be one of: {}"
.format(", ".join(valid_choices)))
# needed to remove helps from azdev
azdev_helps = helps.copy()
exclusions = {}
selected_modules = get_path_table(include_only=modules, include_whl_extensions=include_whl_extensions)
if cli_only:
selected_modules['ext'] = {}
if ext_only:
selected_modules['mod'] = {}
selected_modules['core'] = {}
# used to upsert global exclusion
update_global_exclusion = None
if save_global_exclusion and (cli_only or ext_only):
if cli_only:
update_global_exclusion = 'CLI'
if os.path.exists(os.path.join(get_cli_repo_path(), 'linter_exclusions.yml')):
os.remove(os.path.join(get_cli_repo_path(), 'linter_exclusions.yml'))
elif ext_only:
update_global_exclusion = 'EXT'
for ext_path in get_ext_repo_paths():
if os.path.exists(os.path.join(ext_path, 'linter_exclusions.yml')):
os.remove(os.path.join(ext_path, 'linter_exclusions.yml'))
# filter down to only modules that have changed based on git diff
selected_modules = filter_by_git_diff(selected_modules, git_source, git_target, git_repo)
if not any(selected_modules.values()):
logger.warning('No commands selected to check.')
selected_mod_names = list(selected_modules['mod'].keys()) + list(selected_modules['core'].keys()) + \
list(selected_modules['ext'].keys())
selected_mod_paths = list(selected_modules['mod'].values()) + list(selected_modules['core'].values()) + \
list(selected_modules['ext'].values())
if selected_mod_names:
display('Modules: {}\n'.format(', '.join(selected_mod_names)))
# collect all rule exclusions
for path in selected_mod_paths:
exclusion_path = os.path.join(path, 'linter_exclusions.yml')
if os.path.isfile(exclusion_path):
with open(exclusion_path) as f:
mod_exclusions = yaml.safe_load(f)
merge_exclusion(exclusions, mod_exclusions or {})
global_exclusion_paths = [os.path.join(get_cli_repo_path(), 'linter_exclusions.yml')]
try:
global_exclusion_paths.extend([os.path.join(path, 'linter_exclusions.yml')
for path in (get_ext_repo_paths() or [])])
except CLIError:
pass
for path in global_exclusion_paths:
if os.path.isfile(path):
with open(path) as f:
mod_exclusions = yaml.safe_load(f)
merge_exclusion(exclusions, mod_exclusions or {})
start = time.time()
display('Initializing linter with command table and help files...')
az_cli = get_default_cli()
# load commands, args, and help
create_invoker_and_load_cmds_and_args(az_cli)
loaded_help = get_all_help(az_cli)
stop = time.time()
logger.info('Commands and help loaded in %i sec', stop - start)
command_loader = az_cli.invocation.commands_loader
# format loaded help
loaded_help = {data.command: data for data in loaded_help if data.command}
# load yaml help
help_file_entries = {}
for entry_name, help_yaml in helps.items():
# ignore help entries from azdev itself, unless it also coincides
# with a CLI or extension command name.
if entry_name in azdev_helps and entry_name not in command_loader.command_table:
continue
help_entry = yaml.safe_load(help_yaml)
help_file_entries[entry_name] = help_entry
# trim command table and help to just selected_modules
command_loader, help_file_entries = filter_modules(
command_loader, help_file_entries, modules=selected_mod_names, include_whl_extensions=include_whl_extensions)
if not command_loader.command_table:
logger.warning('No commands selected to check.')
# Instantiate and run Linter
linter_manager = LinterManager(command_loader=command_loader,
help_file_entries=help_file_entries,
loaded_help=loaded_help,
exclusions=exclusions,
rule_inclusions=rules,
use_ci_exclusions=ci_exclusions,
min_severity=min_severity,
update_global_exclusion=update_global_exclusion,
git_source=git_source,
git_target=git_target,
git_repo=git_repo,
base_meta_path=base_meta_path,
diff_meta_path=diff_meta_path
)
subheading('Results')
logger.info('Running linter: %i commands, %i help entries',
len(command_loader.command_table), len(help_file_entries))
exit_code = linter_manager.run(
run_params=not rule_types or 'params' in rule_types,
run_commands=not rule_types or 'commands' in rule_types,
run_command_groups=not rule_types or 'command_groups' in rule_types,
run_help_files_entries=not rule_types or 'help_entries' in rule_types,
run_command_test_coverage=not rule_types or 'command_test_coverage' in rule_types,
run_extra_cli_linter=not rule_types or "extra_cli_linter" in rule_types,
)
display(os.linesep + 'Run custom pylint rules.')
exit_code += pylint_rules(selected_modules)
print(exit_code)
sys.exit(exit_code)
def pylint_rules(selected_modules):
# TODO: support severity for pylint rules
from importlib import import_module
my_env = os.environ.copy()
checker_path = import_module('{}'.format(CHECKERS_PATH)).__path__[0]
my_env['PYTHONPATH'] = checker_path
checkers = [os.path.splitext(f)[0] for f in os.listdir(checker_path) if
os.path.isfile(os.path.join(checker_path, f)) and f != '__init__.py']
enable = [s.replace('_', '-') for s in checkers]
pylint_result = run_pylint(selected_modules, env=my_env, checkers=checkers, disable_all=True, enable=enable)
if pylint_result and not pylint_result.error:
display(os.linesep + 'No violations found for custom pylint rules.')
display('Linter: PASSED\n')
if pylint_result and pylint_result.error:
display(pylint_result.error.output.decode('utf-8'))
display('Linter: FAILED\n')
return pylint_result.exit_code
def linter_severity_choices():
return [str(severity.name).lower() for severity in LinterSeverity]