Skip to content

Commit d1d1a59

Browse files
committed
Update OCLint setup to use native tools instead of xcpretty (#40)
* Add sonar.apple.jsonCompilationDatabasePath parameter * Fix AppleTestsSensorTest * Add JSON Compilation Database merge logic * Upgrade JProc version to 2.8.0 * Export some logic to a separate object OCLintJSONDatabaseBuilder * Adding OCLintJSONDatabaseBuilderTest * Create OCLintExtractor to run oclint-json-compilation-database * Adding parsing step to OCLintExtractor * Update OCLintReportParser * Merging all the piece into OCLintSensor * Some fixes * Update documentation * Updating documentation * Update OCLintReportParserTest * Cleaning * Add headers * Improve OCLint regex for the include parameter * Update README * Fix error in unit tests * Fix bug (from Sonar Cloud) * Fix issues (from Sonar Cloud) * Fix runtime failure, the file wasn't properly closed before it was read by another ressource
1 parent a2ad617 commit d1d1a59

File tree

22 files changed

+717
-177
lines changed

22 files changed

+717
-177
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,6 @@ build/
3333

3434
### Mac OS ###
3535
.DS_Store
36+
37+
### OCLint Tests ###
38+
objc-lang/src/test/resources/oclint/build

README.md

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -33,52 +33,60 @@ The plugin is designed to support Swift 5 syntax.
3333

3434
## Requirements
3535

36-
### Xcode
36+
### Mandatory
3737

38-
Download Xcode from [Apple Developer](https://developer.apple.com/download/).
38+
#### Xcode
3939

40-
The plugin was tested with Xcode 13+, but should work with older versions.
40+
Xcode is required in order to build the project and run tests.
41+
You can download it from [Apple Developer](https://developer.apple.com/download/), but we strongly recommend to use a version manager such as [xcinfo](https://github.com/xcodereleases/xcinfo).
4142

42-
### sonar-scanner (requires Java)
43+
> **Note**
44+
> The plugin was tested with Xcode 13+, but should work with older versions (down to Xcode 11).
4345
44-
Install sonar-scanner as explained in the [official documentation]((https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/)).
46+
#### sonar-scanner
4547

46-
### xcpretty
48+
sonar-scanner is required to run this plugin, build and send the metrics to Sonar.
49+
You can install it as explained in the [official documentation]((https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/)), but we strongly recommend to install it with [Homebrew](https://github.com/Homebrew/brew).
4750

48-
xcpretty is used to generate a JSON Compilation Database from ``xcodebuild`` log.
51+
> **Note**
52+
> sonar-scanner requires Java.
53+
> We recommend to use a Java environment manager such as `jenv` to install OCLint to control the version.
4954
50-
See install instructions [here](https://github.com/xcpretty/xcpretty).
55+
### Optional
5156

52-
### SwiftLint
57+
#### SwiftLint
5358

5459
SwiftLint is used to analyse Swift source files.
60+
You can install it as explained in [here](https://github.com/realm/SwiftLint).
5561

56-
See install instructions [here](https://github.com/realm/SwiftLint).
62+
> **Warning**
63+
> Without SwiftLint many issues will not be detected. This may decrease the quality of the analysis.
5764
58-
### OCLint
65+
#### OCLint
5966

6067
OCLint is used to analyse Objective-C source files.
68+
You can install it as explained in [here](https://docs.oclint.org/en/stable/intro/homebrew.html).
6169

62-
See install instructions [here](https://docs.oclint.org/en/stable/intro/homebrew.html).
70+
> **Note**
71+
> We recommend to use a `Gemfile` to install OCLint to control the version.
6372
64-
Important: after initial installation, macOS will block usage of OCLint libraries. In order to get rid of the manual verification of each of them, use the following commands:
73+
> **Warning**
74+
> Important: after initial installation, macOS will block usage of OCLint libraries. In order to get rid of the manual verification of each of them, use the following commands:
75+
>
76+
> ```bash
77+
> $ sudo xattr -dr com.apple.quarantine /usr/local/lib/oclint/rules/lib*
78+
> $ sudo xattr -dr com.apple.quarantine /usr/local/lib/oclint/reporters/lib*
79+
> ```
6580
66-
```bash
67-
$ sudo xattr -dr com.apple.quarantine /usr/local/lib/oclint/rules/lib*
68-
$ sudo xattr -dr com.apple.quarantine /usr/local/lib/oclint/reporters/lib*
69-
```
70-
71-
### mobsfscan
81+
#### mobsfscan
7282
7383
mobsfscan is used to analyse Swift & Objective-C source files to find insecure code patterns.
84+
You can install it as explained in [here](https://github.com/MobSF/mobsfscan).
7485
75-
See install instructions [here](https://github.com/MobSF/mobsfscan).
76-
77-
### Periphery
86+
#### Periphery
7887
7988
Periphery is used to analyse Swift source files to find unused code.
80-
81-
See install instructions [here](https://github.com/peripheryapp/periphery).
89+
You can install it as explained in [here](https://github.com/peripheryapp/periphery).
8290
8391
## Installation (on the server)
8492
@@ -111,14 +119,15 @@ sonar.tests=iOSAppTests
111119
# Defaults to build/result.xcresult
112120
# sonar.apple.resultBundlePath=custom/path/to/file.xcresult
113121
114-
# Path to xcodebuild.log file
115-
# Defaults to build
116-
# sonar.apple.xcodebuild.logPath=custom/path/to/file.log
117-
118122
# Path to periphery.log file
119123
# Defaults to build
120124
# sonar.apple.periphery.logPath=custom/path/to/file.log
121125
126+
# Path to the JSON Compilation Database folder
127+
# The path is relative to the project base directory.
128+
# Defaults to build/json_compilation_database
129+
# sonar.apple.jsonCompilationDatabasePath=custom/path/to/folder
130+
122131
# Encoding of the source code. Default is default system encoding.
123132
sonar.sourceEncoding=UTF-8
124133
```
@@ -130,18 +139,20 @@ For a complete list of available options, please refer to the [SonarQube documen
130139
Use the following commands from the root folder to start an analysis:
131140

132141
```bash
133-
134-
# Run tests
135142
# Don't forget to add -workspace to the build command if your project is part of a workspace
136143
# Don't forget to activate 'Gather coverage' option in the app scheme or add '-enableCodeCoverage YES' to the following command
137-
$ xcodebuild \
144+
145+
# Run tests
146+
$ xcrun xcodebuild \
138147
-project MyApp.xcodeproj \
139148
-scheme MyApp \
140149
-sdk iphonesimulator \
141150
-destination 'platform=iOS Simulator,name=iPhone 11 Pro' \
142151
-derivedDataPath ./derivedData \
143152
-resultBundlePath build/result.xcresult \
144-
clean test
153+
OTHER_CFLAGS="\$(inherited) -gen-cdb-fragment-path build/compilation_database" \
154+
-quiet \
155+
clean test
145156

146157
# Saves Periphery log to build/periphery.log (this is necessary for Swift dead code analysis)
147158
# Don't forget to add --workspace to the build command if your project is part of a workspace
@@ -154,15 +165,6 @@ $ periphery scan \
154165
--format xcode \
155166
--quiet | tee build/periphery.log
156167

157-
# This rebuild is required to perform Objective-C issue analysis
158-
# Skip it if your project doe not use Objective-C, or if you do want to report Objective-C issues
159-
$ xcodebuild \
160-
-project MyApp.xcodeproj \
161-
-scheme MyApp \
162-
-sdk iphonesimulator \
163-
-destination 'platform=iOS Simulator,name=iPhone 11 Pro' \
164-
COMPILER_INDEX_STORE_ENABLE=NO clean test | tee build/xcodebuild.log
165-
166168
# Run the analysis and publish to the SonarQube server
167169
# Don't forget to specify `sonar.host.url` and `sonar.login` in `sonar-project.properties` or supply it to the following command.
168170
$ sonar-scanner
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* SonarQube Apple Plugin - Enables analysis of Swift and Objective-C projects into SonarQube.
3+
* Copyright © 2022 inside|app (contact@insideapp.fr)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Lesser General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package fr.insideapp.sonarqube.apple.commons;
19+
20+
import java.io.File;
21+
import java.io.FileFilter;
22+
23+
public final class ExtensionFileFilter implements FileFilter {
24+
25+
private final String extension;
26+
27+
public ExtensionFileFilter(String extension) {
28+
this.extension = extension;
29+
}
30+
31+
@Override
32+
public boolean accept(File pathname) {
33+
return pathname.getPath().toLowerCase().endsWith("." + extension);
34+
}
35+
}

commons/src/test/java/fr/insideapp/sonarqube/apple/commons/tests/AppleTestsSensorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public void executeSuccess() {
105105
private void assertContainer(Container container) {
106106
// update setting to get a custom location for Xcode result bundle path
107107
MapSettings settings = new MapSettings();
108-
settings.setProperty(AppleResultSensor.RESULT_BUNDLE_PATH_KEY, "sensor.xcresult");
108+
settings.setProperty(AppleResultSensor.RESULT_BUNDLE_PATH_KEY, container.resultBundlePath);
109109
context.setSettings(settings);
110110

111111
String fullFileNamePath = container.fileNamePath + "." + EXTENSION;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* SonarQube Apple Plugin - Enables analysis of Swift and Objective-C projects into SonarQube.
3+
* Copyright © 2022 inside|app (contact@insideapp.fr)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Lesser General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package fr.insideapp.sonarqube.objc.issues.oclint;
19+
20+
import com.fasterxml.jackson.core.JsonProcessingException;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.SerializationFeature;
23+
import fr.insideapp.sonarqube.objc.ObjectiveC;
24+
import fr.insideapp.sonarqube.objc.issues.oclint.models.OCLintReport;
25+
import org.buildobjects.process.ProcBuilder;
26+
import org.sonar.api.batch.sensor.SensorContext;
27+
import org.sonar.api.utils.log.Logger;
28+
import org.sonar.api.utils.log.Loggers;
29+
30+
import java.io.File;
31+
import java.util.Arrays;
32+
import java.util.stream.Collectors;
33+
34+
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
35+
36+
public class OCLintExtractor {
37+
38+
private static final Logger LOGGER = Loggers.get(OCLintExtractor.class);
39+
private static final int COMMAND_TIMEOUT = 30 * 60 * 1000;
40+
private static final int COMMAND_EXIT_CODE = 0;
41+
private final SensorContext context;
42+
43+
private ObjectMapper objectMapper;
44+
45+
public OCLintExtractor(SensorContext context) {
46+
this.context = context;
47+
objectMapper = new ObjectMapper()
48+
.disable(FAIL_ON_UNKNOWN_PROPERTIES)
49+
.enable(SerializationFeature.INDENT_OUTPUT);
50+
}
51+
52+
public OCLintReport extract(File compileCommandsFolder) throws JsonProcessingException {
53+
String jsonReport = new ProcBuilder("oclint-json-compilation-database")
54+
.withArgs(buildSourceArguments())
55+
.withArgs("-p", compileCommandsFolder.getAbsolutePath())
56+
.withArgs("--")
57+
.withArgs("-report-type", "json")
58+
.withTimeoutMillis(COMMAND_TIMEOUT)
59+
.withExpectedExitStatuses(COMMAND_EXIT_CODE)
60+
.run()
61+
.getOutputString();
62+
63+
OCLintReport oclintReport = objectMapper.readValue(jsonReport, OCLintReport.class);
64+
LOGGER.info("OCLint found {} violation(s)", oclintReport.violations.length);
65+
return oclintReport;
66+
}
67+
68+
private String[] buildSourceArguments() {
69+
// Retrieve all the sources specified
70+
final String sourcesInput = context.config().get("sonar.sources").orElse(".");
71+
// Retrieve all the file extensions for Objective-C
72+
String fileExtensions = Arrays.stream(ObjectiveC.EXTENSIONS).collect(Collectors.joining("|"));
73+
final String[] sources = sourcesInput.split(",");
74+
final String[] sourceArgs = new String[sources.length * 2];
75+
final File baseDirectory = context.fileSystem().baseDir();
76+
// Build parameters for each source
77+
for (int i = 0; i < sources.length; i++) {
78+
sourceArgs[i * 2] = "--include";
79+
String regexPath = String.format("%s/.*\\.(%s)", sources[i], fileExtensions);
80+
File absoluteRegexPath = new File(baseDirectory, regexPath);
81+
LOGGER.debug("For source '{}', following regex is used: {}", sources[i], absoluteRegexPath.getAbsolutePath());
82+
// we use the absolute path since (same as JSON Compilation Database)
83+
sourceArgs[i * 2 + 1] = absoluteRegexPath.getAbsolutePath();
84+
}
85+
return sourceArgs;
86+
}
87+
88+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* SonarQube Apple Plugin - Enables analysis of Swift and Objective-C projects into SonarQube.
3+
* Copyright © 2022 inside|app (contact@insideapp.fr)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Lesser General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package fr.insideapp.sonarqube.objc.issues.oclint;
19+
20+
import fr.insideapp.sonarqube.apple.commons.ExtensionFileFilter;
21+
import org.apache.commons.io.FileUtils;
22+
import org.apache.commons.lang3.RegExUtils;
23+
import org.sonar.api.utils.log.Logger;
24+
import org.sonar.api.utils.log.Loggers;
25+
26+
import java.io.File;
27+
import java.io.FileFilter;
28+
import java.nio.charset.StandardCharsets;
29+
import java.util.Arrays;
30+
import java.util.Iterator;
31+
import java.util.regex.Pattern;
32+
33+
public final class OCLintJSONDatabaseBuilder {
34+
35+
private static final Pattern CLEAN_PATTERN = Pattern.compile("(\"-index-store-path.*DataStore\", |\"-index-unit-output-path.*\\.o\", )");
36+
37+
private static final Logger LOGGER = Loggers.get(OCLintJSONDatabaseBuilder.class);
38+
39+
public String build(File jsonCompilationDatabaseFolder) {
40+
// Retrieve the JSON Database fragments
41+
FileFilter jsonFileFilter = new ExtensionFileFilter("json");
42+
File[] jsonFiles = jsonCompilationDatabaseFolder.listFiles(jsonFileFilter);
43+
Iterator<File> jsonFilesIterator = Arrays.asList(jsonFiles).iterator();
44+
45+
final StringBuilder compileCommandsBuilder = new StringBuilder();
46+
47+
// Beginning the JSON Database
48+
compileCommandsBuilder.append("[");
49+
50+
// For each JSON Database fragment
51+
while (jsonFilesIterator.hasNext()) {
52+
File jsonFile = jsonFilesIterator.next();
53+
try {
54+
// Getting the content
55+
String json = FileUtils.readFileToString(jsonFile, StandardCharsets.UTF_8);
56+
// Cleaning the JSON Database
57+
json = RegExUtils.removeAll(json, CLEAN_PATTERN);
58+
59+
/* For the last one, we remove the trailing comma
60+
because fragments generated by clang have a comma before EOF
61+
and we want a valid JSON in the end
62+
*/
63+
if (!jsonFilesIterator.hasNext()) {
64+
json = RegExUtils.removeFirst(json, ",$");
65+
}
66+
compileCommandsBuilder.append(json);
67+
} catch (Exception e) {
68+
LOGGER.error(e.getMessage(), e);
69+
}
70+
}
71+
72+
// Ending the JSON Database
73+
compileCommandsBuilder.append("]");
74+
75+
return compileCommandsBuilder.toString();
76+
}
77+
78+
}

0 commit comments

Comments
 (0)