Skip to content

Commit 7637e0f

Browse files
nnobelissschuberth
authored andcommitted
feat(conan): Add a parameter to analyze projects with Conan 2
The parameter 'useConan2' of the Conan package manager makes the latter run the 'conan2' command instead of the 'conan' one. It should be used in a setup where ORT must be able to scan both Conan 1 and Conan 2 projects, for instance a CI using the ORT Docker image for compliance check. This commit makes the `ConanFunTest` successful again. Signed-off-by: Nicolas Nobelis <[email protected]>
1 parent 19427ac commit 7637e0f

File tree

5 files changed

+40
-25
lines changed

5 files changed

+40
-25
lines changed

model/src/main/resources/reference.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,14 @@ ort:
117117
disableRegistryCertificateVerification: false
118118

119119
Conan:
120-
# Holds a name of the lockfile. Required if allowDynamicVersions = false.
121-
# The lockfile should be located in the same directory as the conanfile.py or conanfile.txt.
122120
options:
121+
# Holds a name of the lockfile. Required if allowDynamicVersions = false.
122+
# The lockfile should be located in the same directory as the conanfile.py or conanfile.txt.
123123
lockfileName: "lockfile.lock"
124124

125+
# If true, the Conan package manager with call a command called "conan2" instead of "conan".
126+
useConan2: true
127+
125128
advisor:
126129
# A flag to control whether excluded scopes and paths should be skipped when giving the advice.
127130
skipExcluded: true

plugins/package-managers/conan/src/funTest/kotlin/ConanFunTest.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ import org.ossreviewtoolkit.utils.test.matchExpectedResult
3030
import org.ossreviewtoolkit.utils.test.patchActualResult
3131

3232
/**
33-
* This test class performs tests with both Conan 1 and Conan 2.
34-
* TODO: In the current state, both Conan version 2 cannot be run at the same time, because the package manager only
35-
* uses one "conan" command.
33+
* This test class performs tests with both Conan 1 and Conan 2. For it to be successful, it needs both a "conan"
34+
* command for Conan 1 and a "conan2" command for Conan 2 in the PATH environment variable (as in ORT Docker image).
3635
*
3736
* A word of caution about Conan 2 tests: If there is no lockfile, when Conan resolves the dependencies it read its
3837
* cache and relies on the package name, ignoring the version. This means, for instance, that if a test reported
@@ -79,7 +78,8 @@ class ConanFunTest : StringSpec({
7978
val definitionFile = getAssetFile("projects/synthetic/conan-txt/conanfile.txt")
8079
val expectedResultFile = getAssetFile("projects/synthetic/conan2-expected-output-txt.yml")
8180

82-
val result = ConanFactory.create().resolveSingleProject(definitionFile, allowDynamicVersions = true)
81+
val result = ConanFactory.create(useConan2 = true)
82+
.resolveSingleProject(definitionFile, allowDynamicVersions = true)
8383

8484
patchActualResult(result.toYaml()) should matchExpectedResult(expectedResultFile, definitionFile)
8585
}
@@ -89,7 +89,8 @@ class ConanFunTest : StringSpec({
8989
val definitionFile = getAssetFile("projects/synthetic/conan-py/conanfile.py")
9090
val expectedResultFile = getAssetFile("projects/synthetic/conan2-expected-output-py.yml")
9191

92-
val result = ConanFactory.create().resolveSingleProject(definitionFile, allowDynamicVersions = true)
92+
val result = ConanFactory.create(useConan2 = true)
93+
.resolveSingleProject(definitionFile, allowDynamicVersions = true)
9394

9495
patchActualResult(result.toYaml()) should matchExpectedResult(expectedResultFile, definitionFile)
9596
}
@@ -101,7 +102,7 @@ class ConanFunTest : StringSpec({
101102
val definitionFile = getAssetFile("projects/synthetic/conan-py-lockfile/conanfile.py")
102103
val expectedResultFile = getAssetFile("projects/synthetic/conan-expected-output-py-lockfile.yml")
103104

104-
val result = ConanFactory.create(lockfileName = "lockfile_conan2.lock")
105+
val result = ConanFactory.create(lockfileName = "lockfile_conan2.lock", useConan2 = true)
105106
.resolveSingleProject(definitionFile)
106107

107108
patchActualResult(result.toYaml()) should matchExpectedResult(expectedResultFile, definitionFile)

plugins/package-managers/conan/src/main/kotlin/Conan.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import org.ossreviewtoolkit.model.VcsInfo
5252
import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
5353
import org.ossreviewtoolkit.model.config.Excludes
5454
import org.ossreviewtoolkit.plugins.api.OrtPlugin
55+
import org.ossreviewtoolkit.plugins.api.OrtPluginOption
5556
import org.ossreviewtoolkit.plugins.api.PluginDescriptor
5657
import org.ossreviewtoolkit.utils.common.CommandLineTool
5758
import org.ossreviewtoolkit.utils.common.masked
@@ -65,8 +66,8 @@ import org.ossreviewtoolkit.utils.ort.requestPasswordAuthentication
6566
import org.semver4j.RangesList
6667
import org.semver4j.RangesListFactory
6768

68-
internal object ConanCommand : CommandLineTool {
69-
override fun command(workingDir: File?) = "conan"
69+
internal class ConanCommand(private val useConan2: Boolean = false) : CommandLineTool {
70+
override fun command(workingDir: File?) = if (useConan2) "conan2" else "conan"
7071

7172
override fun transformVersion(output: String) =
7273
// Conan could report version strings like:
@@ -84,7 +85,15 @@ data class ConanConfig(
8485
* The name of the lockfile, which is used for analysis if allowDynamicVersions is set to false. The lockfile should
8586
* be located in the analysis root. Currently only one lockfile is supported per Conan project.
8687
*/
87-
val lockfileName: String?
88+
val lockfileName: String?,
89+
90+
/**
91+
* If true, the Conan package manager will call a command called "conan2" instead of "conan". This is required to
92+
* be able to support both Conan major versions in a given environment e.g., the ORT Docker image or a local
93+
* development environment.
94+
*/
95+
@OrtPluginOption(defaultValue = "false")
96+
val useConan2: Boolean
8897
)
8998

9099
/**
@@ -113,10 +122,12 @@ class Conan(
113122
internal const val SCOPE_NAME_DEV_DEPENDENCIES = "build_requires"
114123
}
115124

125+
internal val command by lazy { ConanCommand(config.useConan2) }
126+
116127
override val globsForDefinitionFiles = listOf("conanfile*.txt", "conanfile*.py")
117128

118129
private val handler by lazy {
119-
if (ConanCommand.getVersion().startsWith("1.")) {
130+
if (command.getVersion().startsWith("1.")) {
120131
ConanV1Handler(this)
121132
} else {
122133
ConanV2Handler(this)
@@ -140,7 +151,7 @@ class Conan(
140151
analysisRoot: File,
141152
definitionFiles: List<File>,
142153
analyzerConfig: AnalyzerConfiguration
143-
) = ConanCommand.checkVersion()
154+
) = command.checkVersion()
144155

145156
/**
146157
* Primary method for resolving dependencies from [definitionFile].
@@ -219,7 +230,7 @@ class Conan(
219230
private fun configureRemoteAuthentication(conanConfig: File?) {
220231
// Install configuration from a local directory if available.
221232
conanConfig?.let {
222-
ConanCommand.run("config", "install", it.absolutePath).requireSuccess()
233+
command.run("config", "install", it.absolutePath).requireSuccess()
223234
}
224235

225236
val remotes = handler.listRemotes()
@@ -237,7 +248,7 @@ class Conan(
237248
if (auth != null) {
238249
// Configure Conan's authentication based on ORT's authentication for the remote.
239250
runCatching {
240-
ConanCommand.run(
251+
command.run(
241252
"user",
242253
"-r", remoteName,
243254
"-p", String(auth.password).masked(),

plugins/package-managers/conan/src/main/kotlin/ConanV1Handler.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ internal class ConanV1Handler(private val conan: Conan) : ConanVersionHandler {
5151
val jsonFile = createOrtTempDir().resolve("info.json")
5252
if (lockfileName != null) {
5353
conan.verifyLockfileBelongsToProject(workingDir, lockfileName)
54-
ConanCommand.run(
54+
conan.command.run(
5555
workingDir,
5656
"info", definitionFile.name,
5757
"-l", lockfileName,
5858
"--json", jsonFile.absolutePath
5959
).requireSuccess()
6060
} else {
61-
ConanCommand.run(
61+
conan.command.run(
6262
workingDir,
6363
"info",
6464
definitionFile.name,
@@ -92,7 +92,7 @@ internal class ConanV1Handler(private val conan: Conan) : ConanVersionHandler {
9292

9393
override fun listRemotes(): List<Pair<String, String>> {
9494
val remoteList = runCatching {
95-
ConanCommand.run("remote", "list", "--raw").requireSuccess()
95+
conan.command.run("remote", "list", "--raw").requireSuccess()
9696
}.getOrElse {
9797
logger.warn { "Failed to list remotes." }
9898
return emptyList()
@@ -116,7 +116,7 @@ internal class ConanV1Handler(private val conan: Conan) : ConanVersionHandler {
116116
}
117117

118118
override fun runInspectCommand(workingDir: File, pkgName: String, jsonFile: File) {
119-
ConanCommand.run(workingDir, "inspect", pkgName, "--json", jsonFile.absolutePath).requireSuccess()
119+
conan.command.run(workingDir, "inspect", pkgName, "--json", jsonFile.absolutePath).requireSuccess()
120120
}
121121

122122
/**

plugins/package-managers/conan/src/main/kotlin/ConanV2Handler.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ internal class ConanV2Handler(private val conan: Conan) : ConanVersionHandler {
5353

5454
// Create a default build profile.
5555
if (!getConanHome().resolve("profiles/default").isFile) {
56-
ConanCommand.run(workingDir, "profile", "detect")
56+
conan.command.run(workingDir, "profile", "detect")
5757
}
5858

5959
val jsonFile = createOrtTempDir().resolve("info.json")
6060
if (lockfileName != null) {
6161
conan.verifyLockfileBelongsToProject(workingDir, lockfileName)
62-
ConanCommand.run(
62+
conan.command.run(
6363
workingDir,
6464
"graph",
6565
"info",
@@ -73,7 +73,7 @@ internal class ConanV2Handler(private val conan: Conan) : ConanVersionHandler {
7373
definitionFile.name
7474
).requireSuccess()
7575
} else {
76-
ConanCommand.run(
76+
conan.command.run(
7777
workingDir,
7878
"graph",
7979
"info",
@@ -111,7 +111,7 @@ internal class ConanV2Handler(private val conan: Conan) : ConanVersionHandler {
111111
override fun listRemotes(): List<Pair<String, String>> {
112112
val remoteList = runCatching {
113113
// List configured remotes in JSON format.
114-
ConanCommand.run("remote", "list", "-f", "json").requireSuccess()
114+
conan.command.run("remote", "list", "-f", "json").requireSuccess()
115115
}.getOrElse {
116116
logger.warn { "Failed to list remotes." }
117117
return emptyList()
@@ -134,15 +134,15 @@ internal class ConanV2Handler(private val conan: Conan) : ConanVersionHandler {
134134
pkgName
135135
} else {
136136
// For Conan 2, "conan inspect" need the path of the reference. See https://github.com/conan-io/conan/issues/12532.
137-
ConanCommand.run(
137+
conan.command.run(
138138
workingDir,
139139
"cache",
140140
"path",
141141
pkgName
142142
).requireSuccess().stdout.trim()
143143
}
144144

145-
ConanCommand.run(
145+
conan.command.run(
146146
workingDir,
147147
"inspect",
148148
path,

0 commit comments

Comments
 (0)