Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/central-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Set Up Ballerina
uses: ballerina-platform/setup-ballerina@v1.1.0
with:
version: 2201.12.0
version: 2201.13.0

- name: Build with Gradle
env:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/full_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Set Up Ballerina
uses: ballerina-platform/setup-ballerina@v1.1.1
with:
version: 2201.12.0
version: 2201.13.0

- name: Grant execute permission for gradlew
run: chmod +x gradlew
Expand All @@ -35,7 +35,7 @@ jobs:
env:
packageUser: ${{ github.actor }}
packagePAT: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build --scan
run: unset BALLERINA_HOME && ./gradlew build --scan

- name: Generate Jacoco report
run: ./gradlew createCodeCoverageReport
Expand Down Expand Up @@ -66,10 +66,10 @@ jobs:
- name: Set Up Ballerina
uses: ballerina-platform/setup-ballerina@v1.1.1
with:
version: 2201.12.0
version: 2201.13.0

- name: Build with Gradle
env:
packageUser: ${{ github.actor }}
packagePAT: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build --scan
run: Remove-Item Env:BALLERINA_HOME -ErrorAction SilentlyContinue; ./gradlew build --scan
2 changes: 1 addition & 1 deletion .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Set Up Ballerina
uses: ballerina-platform/setup-ballerina@v1.1.0
with:
version: 2201.12.0
version: 2201.13.0

- name: Set version env variable
run: echo "VERSION=$((grep -w "version" | cut -d= -f2) < gradle.properties | rev | cut --complement -d- -f1 | rev)" >> $GITHUB_ENV
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ enterprisePluginVersion=3.18.1
# Dependency versions
picoCLIVersion=4.7.5
gsonVersion=2.10.1
ballerinaLangVersion=2201.12.0
ballerinaLangVersion=2201.13.0
puppycrawlCheckstyleVersion=10.12.1
apacheCommonsLang3Version=3.0
commonsIoVersion=2.15.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@

import io.ballerina.projects.Project;
import io.ballerina.projects.ProjectEnvironmentBuilder;
import io.ballerina.projects.directory.BuildProject;
import io.ballerina.projects.directory.SingleFileProject;
import io.ballerina.projects.directory.ProjectLoader;
import io.ballerina.projects.environment.Environment;
import io.ballerina.projects.environment.EnvironmentBuilder;
import io.ballerina.scan.internal.ProjectAnalyzer;
Expand Down Expand Up @@ -72,11 +71,7 @@ public class TestScanCmd extends ScanCmd {
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList());
if (projectPath.toFile().isDirectory()) {
project = BuildProject.load(getEnvironmentBuilder(distributionPath), projectPath);
} else {
project = SingleFileProject.load(getEnvironmentBuilder(distributionPath), projectPath);
}
project = ProjectLoader.load(projectPath, getEnvironmentBuilder(distributionPath)).project();
}

@Override
Expand Down
20 changes: 18 additions & 2 deletions scan-command/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,24 @@ build {

// Configuring tests
tasks.test {
systemProperty "ballerina.home", Paths.get(System.getenv("BALLERINA_HOME")).resolve("distributions")
.resolve("ballerina-${ballerinaLangVersion}").toString()
// set ballerina home system property for java based tests
def balHomeOutput = ""
try {
balHomeOutput = new ByteArrayOutputStream()
exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
// Use cmd so Windows can run the .bat reliably
commandLine 'cmd', '/c', 'bal.bat home'
} else {
commandLine 'sh', '-c', 'bal home'
}
standardOutput = balHomeOutput
}
systemProperty 'ballerina.home', balHomeOutput.toString().trim()
} catch (Exception e) {
println "Warning: Could not determine ballerina.home from 'bal home' command: ${e.message}" +
"\nOutput: " + balHomeOutput
}

useTestNG() {
suites 'src/test/resources/testng.xml'
Expand Down
138 changes: 120 additions & 18 deletions scan-command/src/main/java/io/ballerina/scan/internal/ScanCmd.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,26 @@

package io.ballerina.scan.internal;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.ballerina.cli.BLauncherCmd;
import io.ballerina.projects.Project;
import io.ballerina.projects.ProjectKind;
import io.ballerina.projects.ProjectLoadResult;
import io.ballerina.projects.directory.BuildProject;
import io.ballerina.projects.directory.SingleFileProject;
import io.ballerina.projects.directory.ProjectLoader;
import io.ballerina.projects.directory.WorkspaceProject;
import io.ballerina.projects.util.ProjectConstants;
import io.ballerina.projects.util.ProjectPaths;
import io.ballerina.projects.util.ProjectUtils;
import io.ballerina.scan.Issue;
import io.ballerina.scan.PlatformPluginContext;
import io.ballerina.scan.ReportFormat;
import io.ballerina.scan.Rule;
import io.ballerina.scan.StaticCodeAnalysisPlatformPlugin;
import io.ballerina.scan.utils.Constants;
import io.ballerina.scan.utils.DiagnosticCode;
import io.ballerina.scan.utils.DiagnosticLog;
import io.ballerina.scan.utils.ScanTomlFile;
Expand All @@ -46,6 +54,7 @@
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
Expand All @@ -59,6 +68,7 @@

import static io.ballerina.scan.internal.ScanToolConstants.RUNNING_SCANS_LOG;
import static io.ballerina.scan.internal.ScanToolConstants.SCAN_COMMAND;
import static io.ballerina.scan.utils.ScanUtils.convertIssuesToSarifString;

/**
* Represents the "bal scan" command.
Expand Down Expand Up @@ -107,6 +117,7 @@ public class ScanCmd implements BLauncherCmd {
private List<String> platforms = new ArrayList<>();

private final List<Rule> allRules = new ArrayList<>();
private final List<Issue> allIssues;

public ScanCmd() {
this(System.out);
Expand All @@ -115,6 +126,7 @@ public ScanCmd() {
ScanCmd(PrintStream outputStream) {
this.projectPath = Paths.get(System.getProperty(ProjectConstants.USER_DIR));
this.outputStream = outputStream;
this.allIssues = new ArrayList<>();
}

protected ScanCmd(
Expand All @@ -140,6 +152,7 @@ protected ScanCmd(
this.includeRules.addAll(includeRules.stream().map(Rule::id).toList());
this.excludeRules.addAll(excludeRules.stream().map(Rule::id).toList());
this.platforms.addAll(platforms);
this.allIssues = new ArrayList<>();
}

@Override
Expand Down Expand Up @@ -176,20 +189,94 @@ public void execute() {
return;
}

if (project.get().kind() == ProjectKind.WORKSPACE_PROJECT) {
outputStream.println();
outputStream.println("Resolving workspace dependencies");
WorkspaceProject workspaceProject = (WorkspaceProject) project.get();
List<BuildProject> topologicallySortedList =
workspaceProject.getResolution().dependencyGraph().toTopologicallySortedList();
for (BuildProject buildProject : topologicallySortedList) {
if (ProjectUtils.isProjectEmpty(buildProject)) {
outputStream.println(DiagnosticLog.error(DiagnosticCode.EMPTY_PACKAGE));
continue;
}
executeProject(buildProject);
}

outputStream.println();
accumulateWorkspaceReports(topologicallySortedList, workspaceProject);
if (scanReport) {
Path scanReportPath = ScanUtils.generateScanReport(allIssues, project.get(), targetDir);
outputStream.println();
outputStream.println("View scan report at:");
outputStream.println("\t" + scanReportPath.toUri() + System.lineSeparator());
}

return;
}
if (ProjectUtils.isProjectEmpty(project.get())) {
outputStream.println(DiagnosticLog.error(DiagnosticCode.EMPTY_PACKAGE));
return;
}
executeProject(project.get());
}

Optional<ScanTomlFile> scanTomlFile = ScanUtils.loadScanTomlConfigurations(project.get(), outputStream);
private void accumulateWorkspaceReports(List<BuildProject> topologicallySortedList,
WorkspaceProject workspaceProject) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
JsonArray allIssuesArray = new JsonArray();
Path finalReportPath = null;
String finalReportContent = null;
try {
for (BuildProject buildProject : topologicallySortedList) {
Path reportPath = ScanUtils.getTargetPath(buildProject, targetDir).getReportPath();
if (ReportFormat.SARIF.equals(format)) {
reportPath = reportPath.resolve(Constants.RESULTS_SARIF_FILE);
finalReportPath = ScanUtils.getTargetPath(workspaceProject, targetDir).getReportPath()
.resolve(Constants.RESULTS_SARIF_FILE);
String content = Files.readString(reportPath, StandardCharsets.UTF_8);
JsonObject jsonObject = gson.fromJson(content, JsonObject.class);
jsonObject.getAsJsonArray("runs").forEach(runElement -> {
JsonObject runObject = runElement.getAsJsonObject();
JsonArray issuesArray = runObject.getAsJsonArray("results");
if (issuesArray != null) {
issuesArray.forEach(allIssuesArray::add);
}
});
finalReportContent = convertIssuesToSarifString(allIssues, workspaceProject);
} else {
reportPath = reportPath.resolve(Constants.RESULTS_JSON_FILE);
finalReportPath = ScanUtils.getTargetPath(workspaceProject, targetDir).getReportPath()
.resolve(Constants.RESULTS_JSON_FILE);
if (Files.exists(reportPath)) {
String content = Files.readString(reportPath, StandardCharsets.UTF_8);
JsonArray issuesArray = gson.fromJson(content, JsonArray.class);
if (issuesArray != null) {
issuesArray.forEach(allIssuesArray::add);
}
}
finalReportContent = gson.toJson(allIssues);
}
}
Files.writeString(finalReportPath, finalReportContent);
outputStream.println("View scan results at:");
outputStream.println("\t" + finalReportPath.toUri());
outputStream.println();
} catch (IOException e) {
throw new RuntimeException("Error while obtaining report path: " + e.getMessage());
}
}

public void executeProject(Project project) {
Optional<ScanTomlFile> scanTomlFile = ScanUtils.loadScanTomlConfigurations(project, outputStream);
if (scanTomlFile.isEmpty()) {
return;
}

outputStream.println();
outputStream.println(RUNNING_SCANS_LOG);

ProjectAnalyzer projectAnalyzer = getProjectAnalyzer(project.get(), scanTomlFile.get());
ProjectAnalyzer projectAnalyzer = getProjectAnalyzer(project, scanTomlFile.get());
List<Rule> coreRules = CoreRule.rules();
Map<String, List<Rule>> externalAnalyzers;
try {
Expand Down Expand Up @@ -247,25 +334,30 @@ public void execute() {
issues.removeIf(issue -> excludeRules.contains(issue.rule().id()));
}

allIssues.addAll(issues);

if (platforms.isEmpty() && !platformTriggered) {
boolean isSarifFormat = ReportFormat.SARIF.equals(format);
ScanUtils.printToConsole(issues, outputStream, isSarifFormat, project.get());
if (project.get().kind().equals(ProjectKind.BUILD_PROJECT)) {
ScanUtils.printToConsole(issues, outputStream, isSarifFormat, project);
if (project.kind().equals(ProjectKind.BUILD_PROJECT)) {
Path reportPath;
if (isSarifFormat) {
reportPath = ScanUtils.saveSarifToDirectory(issues, project.get(), targetDir);
reportPath = ScanUtils.saveSarifToDirectory(issues, project, targetDir);
} else {
reportPath = ScanUtils.saveToDirectory(issues, project.get(), targetDir);
reportPath = ScanUtils.saveToDirectory(issues, project, targetDir);
}
outputStream.println();
outputStream.println("View scan results at:");
outputStream.println("\t" + reportPath.toUri());
outputStream.println();
if (scanReport) {
Path scanReportPath = ScanUtils.generateScanReport(issues, project.get(), targetDir);

if (project.workspaceProject().isEmpty()) {
outputStream.println();
outputStream.println("View scan report at:");
outputStream.println("\t" + scanReportPath.toUri() + System.lineSeparator());
outputStream.println("View scan results at:");
outputStream.println("\t" + reportPath.toUri());
outputStream.println();
if (scanReport) {
Path scanReportPath = ScanUtils.generateScanReport(issues, project, targetDir);
outputStream.println();
outputStream.println("View scan report at:");
outputStream.println("\t" + scanReportPath.toUri() + System.lineSeparator());
}
}
} else {
if (targetDir != null) {
Expand Down Expand Up @@ -315,10 +407,20 @@ private StringBuilder helpMessage() {

protected Optional<Project> getProject() {
try {
if (projectPath.toFile().isDirectory()) {
return Optional.of(BuildProject.load(projectPath));
if (!ProjectPaths.isBuildProjectRoot(projectPath)
&& !ProjectPaths.isStandaloneBalFile(projectPath)
&& !ProjectPaths.isWorkspaceProjectRoot(projectPath)) {
outputStream.println("The specified path is not a valid Ballerina project: " + projectPath + ". Please "
+ "provide a valid Ballerina project path and try again.");
return Optional.empty();
}

ProjectLoadResult loadResult = ProjectLoader.load(projectPath);
if (loadResult.diagnostics().hasErrors()) {
loadResult.diagnostics().errors().forEach(outputStream::println);
return Optional.empty();
}
return Optional.of(SingleFileProject.load(projectPath));
return Optional.of(loadResult.project());
} catch (RuntimeException ex) {
outputStream.println(ex.getMessage());
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package io.ballerina.scan.utils;


import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
Expand All @@ -28,8 +29,8 @@
* @since 0.1.0
*/
public class Constants {
static final String RESULTS_JSON_FILE = "scan_results.json";
static final String RESULTS_SARIF_FILE = "scan_results.sarif";
public static final String RESULTS_JSON_FILE = "scan_results.json";
public static final String RESULTS_SARIF_FILE = "scan_results.sarif";
static final String RESULTS_HTML_FILE = "index.html";
static final String REPORT_DATA_PLACEHOLDER = "__data__";
static final String SCAN_REPORT_PROJECT_NAME = "projectName";
Expand Down
Loading
Loading