一. 获取插件并加载
1. 获取已有插件列表
请求/api/plugins/installed获取已安装的插件
org.sonar.scanner.bootstrap.ScannerPluginInstaller
InstalledPlugin[] listInstalledPlugins() {
Profiler profiler = Profiler.create(LOG).startInfo("Load plugins index");
GetRequest getRequest = new GetRequest(PLUGINS_WS_URL);
InstalledPlugins installedPlugins;
try (Reader reader = wsClient.call(getRequest).contentReader()) {
installedPlugins = new Gson().fromJson(reader, InstalledPlugins.class);
} catch (IOException e) {
throw new IllegalStateException(e);
}
profiler.stopInfo();
return installedPlugins.plugins;
}
2. 下载插件jar包
org.sonar.scanner.bootstrap.ScannerPluginInstaller
private Map<String, ScannerPlugin> loadPlugins(InstalledPlugin[] remotePlugins) {
Map<String, ScannerPlugin> infosByKey = new HashMap<>(remotePlugins.length);
Profiler profiler = Profiler.create(LOG).startDebug("Load plugins");
for (InstalledPlugin installedPlugin : remotePlugins) {
if (pluginPredicate.apply(installedPlugin.key)) {
File jarFile = download(installedPlugin);
PluginInfo info = PluginInfo.create(jarFile);
infosByKey.put(info.getKey(), new ScannerPlugin(installedPlugin.key, installedPlugin.updatedAt, info));
}
}
profiler.stopDebug();
return infosByKey;
}
3. 加载插件
org.sonar.core.platform.PluginLoader
Map<String, Plugin> instantiatePluginClasses(Map<PluginClassLoaderDef, ClassLoader> classloaders) {
// instantiate plugins
Map<String, Plugin> instancesByPluginKey = new HashMap<>();
for (Map.Entry<PluginClassLoaderDef, ClassLoader> entry : classloaders.entrySet()) {
PluginClassLoaderDef def = entry.getKey();
ClassLoader classLoader = entry.getValue();
// the same classloader can be used by multiple plugins
for (Map.Entry<String, String> mainClassEntry : def.getMainClassesByPluginKey().entrySet()) {
String pluginKey = mainClassEntry.getKey();
String mainClass = mainClassEntry.getValue();
try {
instancesByPluginKey.put(pluginKey, (Plugin) classLoader.loadClass(mainClass).newInstance());
} catch (UnsupportedClassVersionError e) {
throw new IllegalStateException(String.format("The plugin [%s] does not support Java %s",
pluginKey, SystemUtils.JAVA_VERSION_TRIMMED), e);
} catch (Throwable e) {
throw new IllegalStateException(String.format(
"Fail to instantiate class [%s] of plugin [%s]", mainClass, pluginKey), e);
}
}
}
return instancesByPluginKey;
}
4. 加载插件中所有的扩展类
org.sonar.scanner.bootstrap.ExtensionInstaller
public ExtensionInstaller install(ComponentContainer container, ExtensionMatcher matcher) {
// core components
for (Object o : BatchComponents.all(analysisMode)) {
doInstall(container, matcher, null, o);
}
// plugin extensions
for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) {
Plugin plugin = pluginRepository.getPluginInstance(pluginInfo.getKey());
Plugin.Context context = new Plugin.Context(sonarRuntime);
plugin.define(context);
for (Object extension : context.getExtensions()) {
doInstall(container, matcher, pluginInfo, extension);
}
}
List<ExtensionProvider> providers = container.getComponentsByType(ExtensionProvider.class);
for (ExtensionProvider provider : providers) {
Object object = provider.provide();
if (object instanceof Iterable) {
for (Object extension : (Iterable) object) {
doInstall(container, matcher, null, extension);
}
} else {
doInstall(container, matcher, null, object);
}
}
return this;
}
二. 获取规则配置和可用规则
1. 获取规则配置
请求/api/qualityprofiles/search获取规则配置
org.sonar.scanner.repository.DefaultQualityProfileLoader
private List<QualityProfile> loadAndOverrideIfNeeded(@Nullable String profileName, StringBuilder url) {
getOrganizationKey().ifPresent(k -> url.append("&organization=").append(encodeForUrl(k)));
Map<String, QualityProfile> result = call(url.toString());
if (profileName != null) {
StringBuilder urlForName = new StringBuilder(WS_URL + "?profileName=");
urlForName.append(encodeForUrl(profileName));
getOrganizationKey().ifPresent(k -> urlForName.append("&organization=").append(encodeForUrl(k)));
result.putAll(call(urlForName.toString()));
}
if (result.isEmpty()) {
throw MessageException.of("No quality profiles have been found, you probably don't have any language plugin installed.");
}
return new ArrayList<>(result.values());
}
2. 获取每个规则配置下可用的规则
请求/api/rules/search获取每个规则配置下对应的可用的规则
org.sonar.scanner.rule.DefaultActiveRulesLoader
public List<LoadedActiveRule> load(String qualityProfileKey) {
List<LoadedActiveRule> ruleList = new LinkedList<>();
int page = 1;
int pageSize = 500;
int loaded = 0;
while (true) {
GetRequest getRequest = new GetRequest(getUrl(qualityProfileKey, page, pageSize));
SearchResponse response = loadFromStream(wsClient.call(getRequest).contentStream());
List<LoadedActiveRule> pageRules = readPage(response);
ruleList.addAll(pageRules);
loaded += response.getPs();
if (response.getTotal() <= loaded) {
break;
}
page++;
}
return ruleList;
}
三. 执行插件中的规则
1. 获取所有插件的执行类并实例化
从插件扩展类中查找实现org.sonar.api.batch.sensor.Sensor接口的执行类
org.sonar.scanner.bootstrap.ScannerExtensionDictionnary
private <T> List<T> getFilteredExtensions(Class<T> type, @Nullable DefaultInputModule module, @Nullable ExtensionMatcher matcher) {
List<T> result = new ArrayList<>();
List<Object> candidates = new ArrayList<>();
candidates.addAll(getExtensions(type));
if (org.sonar.api.batch.Sensor.class.equals(type)) {
candidates.addAll(getExtensions(Sensor.class));
}
if (org.sonar.api.batch.PostJob.class.equals(type)) {
candidates.addAll(getExtensions(PostJob.class));
}
for (Object extension : candidates) {
if (org.sonar.api.batch.Sensor.class.equals(type) && extension instanceof Sensor) {
extension = new SensorWrapper((Sensor) extension, sensorContext, sensorOptimizer);
}
if (org.sonar.api.batch.PostJob.class.equals(type) && extension instanceof PostJob) {
extension = new PostJobWrapper((PostJob) extension, postJobContext, postJobOptimizer);
}
if (shouldKeep(type, extension, module, matcher)) {
result.add((T) extension);
}
}
return result;
}
使用Pico容器,实例化暂时没具体研究
2. 带入可用规则执行插件的扫描方法
org.sonar.scanner.sensor.SensorWrapper
@Override
public void analyse(Project module, org.sonar.api.batch.SensorContext context) {
wrappedSensor.execute(adaptor);
}
四. 问题记录和报告上传(java为例)
1. 问题记录
问题记录写入pb文件
org.sonar.java.SonarComponents
void reportIssue(AnalyzerMessage analyzerMessage, RuleKey key, InputPath inputPath, Double cost) {
Preconditions.checkNotNull(context);
JavaIssue issue = JavaIssue.create(context, key, cost);
AnalyzerMessage.TextSpan textSpan = analyzerMessage.primaryLocation();
if (textSpan == null) {
// either an issue at file or folder level
issue.setPrimaryLocationOnFile(inputPath, analyzerMessage.getMessage());
} else {
if (!textSpan.onLine()) {
Preconditions.checkState(!textSpan.isEmpty(), "Issue location should not be empty");
}
issue.setPrimaryLocation((InputFile) inputPath, analyzerMessage.getMessage(), textSpan.startLine, textSpan.startCharacter, textSpan.endLine, textSpan.endCharacter);
}
issue.addFlow(inputFromIOFile(analyzerMessage.getFile()), analyzerMessage.flows).save();
}
org.sonar.java.JavaIssue
public void save() {
newIssue.save();
}
......
org.sonar.scanner.protocol.output.ScannerReportWriter
public void appendComponentIssue(int componentRef, ScannerReport.Issue issue) {
File file = fileStructure.fileFor(FileStructure.Domain.ISSUES, componentRef);
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file, true))) {
issue.writeDelimitedTo(out);
} catch (Exception e) {
throw ContextException.of("Unable to write issue", e).addContext("file", file);
}
}
2. 报告上传
压缩为zip包
org.sonar.scanner.report.ReportPublisher
private File generateReportFile() {
try {
long startTime = System.currentTimeMillis();
for (ReportPublisherStep publisher : publishers) {
publisher.publish(writer);
}
long stopTime = System.currentTimeMillis();
LOG.info("Analysis report generated in {}ms, dir size={}", stopTime - startTime, FileUtils.byteCountToDisplaySize(FileUtils.sizeOfDirectory(reportDir.toFile())));
startTime = System.currentTimeMillis();
File reportZip = temp.newFile("scanner-report", ".zip");
ZipUtils.zipDir(reportDir.toFile(), reportZip);
stopTime = System.currentTimeMillis();
LOG.info("Analysis reports compressed in {}ms, zip size={}", stopTime - startTime, FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(reportZip)));
return reportZip;
} catch (IOException e) {
throw new IllegalStateException("Unable to prepare analysis report", e);
}
}
以application/x-protobuf协议上传
String upload(File report) {
LOG.debug("Upload report");
long startTime = System.currentTimeMillis();
PostRequest.Part filePart = new PostRequest.Part(MediaTypes.ZIP, report);
PostRequest post = new PostRequest("api/ce/submit")
.setMediaType(MediaTypes.PROTOBUF)
.setParam("organization", settings.get(ORGANIZATION).orElse(null))
.setParam("projectKey", moduleHierarchy.root().key())
.setParam("projectName", moduleHierarchy.root().getOriginalName())
.setParam("projectBranch", moduleHierarchy.root().getBranch())
.setPart("report", filePart);
String branchName = branchConfiguration.branchName();
if (branchName != null) {
post.setParam(CHARACTERISTIC, "branch=" + branchName);
post.setParam(CHARACTERISTIC, "branchType=" + branchConfiguration.branchType().name());
}
WsResponse response;
try {
response = wsClient.call(post).failIfNotSuccessful();
} catch (HttpException e) {
throw MessageException.of(String.format("Failed to upload report - %d: %s", e.code(), ScannerWsClient.tryParseAsJsonError(e.content())));
}
try (InputStream protobuf = response.contentStream()) {
return WsCe.SubmitResponse.parser().parseFrom(protobuf).getTaskId();
} catch (Exception e) {
throw Throwables.propagate(e);
} finally {
long stopTime = System.currentTimeMillis();
LOG.info("Analysis report uploaded in " + (stopTime - startTime) + "ms");
}
}