Skip to content

Commit 677e123

Browse files
committed
feat(Conan): Add support for Conan lockfiles
Filter output of `conan info` to only include packages listed in lockfile. For now only lockfiles located in the same directory as the definition file are supported. Signed-off-by: Oleksandr Yarosevych <[email protected]>
1 parent 509aeb2 commit 677e123

File tree

3 files changed

+97
-11
lines changed

3 files changed

+97
-11
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"graph_lock": {
3+
"nodes": {
4+
"0": {
5+
"ref": "Poco/1.9.3",
6+
"options": "cxx_14=False\nenable_apacheconnector=False\nenable_cppparser=False\nenable_crypto=True\nenable_data=True\nenable_data_mysql=False\nenable_data_odbc=False\nenable_data_sqlite=True\nenable_json=True\nenable_mongodb=True\nenable_net=True\nenable_netssl=True\nenable_netssl_win=True\nenable_pagecompiler=False\nenable_pagecompiler_file2page=False\nenable_pdf=False\nenable_pocodoc=False\nenable_redis=True\nenable_sevenzip=False\nenable_tests=False\nenable_util=True\nenable_xml=True\nenable_zip=True\nforce_openssl=True\npoco_unbundled=False\nshared=False\nopenssl:386=False\nopenssl:capieng_dialog=False\nopenssl:enable_capieng=False\nopenssl:enable_weak_ssl_ciphers=False\nopenssl:no_aria=False\nopenssl:no_asm=False\nopenssl:no_async=False\nopenssl:no_bf=False\nopenssl:no_blake2=False\nopenssl:no_camellia=False\nopenssl:no_cast=False\nopenssl:no_chacha=False\nopenssl:no_cms=False\nopenssl:no_comp=False\nopenssl:no_ct=False\nopenssl:no_deprecated=False\nopenssl:no_des=False\nopenssl:no_dgram=False\nopenssl:no_dh=False\nopenssl:no_dsa=False\nopenssl:no_dso=False\nopenssl:no_ec=False\nopenssl:no_ecdh=False\nopenssl:no_ecdsa=False\nopenssl:no_engine=False\nopenssl:no_filenames=False\nopenssl:no_fips=False\nopenssl:no_gost=False\nopenssl:no_idea=False\nopenssl:no_legacy=False\nopenssl:no_md2=True\nopenssl:no_md4=False\nopenssl:no_mdc2=False\nopenssl:no_ocsp=False\nopenssl:no_pinshared=False\nopenssl:no_rc2=False\nopenssl:no_rc4=False\nopenssl:no_rc5=False\nopenssl:no_rfc3779=False\nopenssl:no_rmd160=False\nopenssl:no_seed=False\nopenssl:no_sm2=False\nopenssl:no_sm3=False\nopenssl:no_sm4=False\nopenssl:no_sock=False\nopenssl:no_srp=False\nopenssl:no_srtp=False\nopenssl:no_sse2=False\nopenssl:no_ssl=False\nopenssl:no_ssl3=False\nopenssl:no_stdio=False\nopenssl:no_threads=False\nopenssl:no_tls1=False\nopenssl:no_ts=False\nopenssl:no_whirlpool=False\nopenssl:no_zlib=False\nopenssl:openssldir=None\nopenssl:shared=False\nzlib:shared=False",
7+
"requires": [
8+
"1"
9+
],
10+
"path": "conanfile.py",
11+
"context": "host"
12+
},
13+
"1": {
14+
"ref": "openssl/3.0.0",
15+
"options": "386=False\ncapieng_dialog=False\nenable_capieng=False\nenable_weak_ssl_ciphers=False\nno_aria=False\nno_asm=False\nno_async=False\nno_bf=False\nno_blake2=False\nno_camellia=False\nno_cast=False\nno_chacha=False\nno_cms=False\nno_comp=False\nno_ct=False\nno_deprecated=False\nno_des=False\nno_dgram=False\nno_dh=False\nno_dsa=False\nno_dso=False\nno_ec=False\nno_ecdh=False\nno_ecdsa=False\nno_engine=False\nno_filenames=False\nno_fips=False\nno_gost=False\nno_idea=False\nno_legacy=False\nno_md2=True\nno_md4=False\nno_mdc2=False\nno_ocsp=False\nno_pinshared=False\nno_rc2=False\nno_rc4=False\nno_rc5=False\nno_rfc3779=False\nno_rmd160=False\nno_seed=False\nno_sm2=False\nno_sm3=False\nno_sm4=False\nno_sock=False\nno_srp=False\nno_srtp=False\nno_sse2=False\nno_ssl=False\nno_ssl3=False\nno_stdio=False\nno_threads=False\nno_tls1=False\nno_ts=False\nno_whirlpool=False\nno_zlib=False\nopenssldir=None\nshared=False\nzlib:shared=False",
16+
"package_id": "03853c3f3f9c6c4fb48aba875d65902d33efec66",
17+
"requires": [
18+
"2"
19+
],
20+
"context": "host"
21+
},
22+
"2": {
23+
"ref": "zlib/1.2.12",
24+
"options": "shared=False",
25+
"package_id": "7bc8c2c85db7a618e5320dc997f27fc33e1df074",
26+
"context": "host"
27+
}
28+
},
29+
"revisions_enabled": false
30+
},
31+
"version": "0.4",
32+
"profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=6.3\nos=Windows\nos_build=Windows\n[options]\n[build_requires]\n[env]\n"
33+
}

analyzer/src/funTest/kotlin/managers/ConanFunTest.kt

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import java.io.File
2626

2727
import org.ossreviewtoolkit.downloader.VersionControlSystem
2828
import org.ossreviewtoolkit.model.config.AnalyzerConfiguration
29+
import org.ossreviewtoolkit.model.config.PackageManagerConfiguration
2930
import org.ossreviewtoolkit.model.config.RepositoryConfiguration
3031
import org.ossreviewtoolkit.utils.ort.normalizeVcsUrl
3132
import org.ossreviewtoolkit.utils.test.USER_DIR
@@ -55,7 +56,7 @@ class ConanFunTest : StringSpec() {
5556
url = normalizeVcsUrl(vcsUrlTxt)
5657
)
5758

58-
val result = createConan().resolveSingleProject(definitionFile)
59+
val result = createConanDynamicVersions().resolveSingleProject(definitionFile)
5960

6061
patchActualResult(result.toYaml()) shouldBe expectedResult
6162
}
@@ -71,12 +72,44 @@ class ConanFunTest : StringSpec() {
7172
url = normalizeVcsUrl(vcsUrlPy)
7273
)
7374

74-
val result = createConan().resolveSingleProject(definitionFile)
75+
val result = createConanDynamicVersions().resolveSingleProject(definitionFile)
76+
77+
patchActualResult(result.toYaml()) shouldBe expectedResult
78+
}
79+
80+
"Project dependencies are detected correctly with the lockfile" {
81+
val packageFile = projectsDirPy.resolve("conanfile.py")
82+
val vcsPath = vcsDirPy.getPathToRoot(projectsDirPy)
83+
val expectedResult = patchExpectedResult(
84+
projectsDirPy.parentFile.resolve("conan-expected-output-py.yml"),
85+
definitionFilePath = "$vcsPath/conanfile.py",
86+
path = vcsPath,
87+
revision = vcsRevisionPy,
88+
url = normalizeVcsUrl(vcsUrlPy)
89+
)
90+
91+
val result = createConanWithLockFile().resolveSingleProject(packageFile)
7592

7693
patchActualResult(result.toYaml()) shouldBe expectedResult
7794
}
7895
}
7996

80-
private fun createConan() =
81-
Conan("Conan", USER_DIR, AnalyzerConfiguration(), RepositoryConfiguration())
97+
private fun createConanDynamicVersions() =
98+
Conan("Conan", USER_DIR, AnalyzerConfiguration(true), RepositoryConfiguration())
99+
100+
private fun createConanWithLockFile() =
101+
Conan(
102+
"Conan",
103+
USER_DIR,
104+
AnalyzerConfiguration().copy(
105+
packageManagers = mapOf(
106+
"Conan" to PackageManagerConfiguration(
107+
options = mapOf(
108+
"lockfileName" to "lockfile.lock"
109+
)
110+
)
111+
)
112+
),
113+
RepositoryConfiguration()
114+
)
82115
}

analyzer/src/main/kotlin/managers/Conan.kt

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,7 @@ class Conan(
106106

107107
override fun command(workingDir: File?) = "conan"
108108

109-
// TODO: Add support for Conan lock files.
110-
111-
// protected open fun hasLockFile(projectDir: File) = null
109+
private fun hasLockFile(directory: String) = File(directory).isFile
112110

113111
override fun transformVersion(output: String) =
114112
// Conan could report version strings like:
@@ -144,25 +142,47 @@ class Conan(
144142
stashDirectories(directoryToStash).use {
145143
configureRemoteAuthentication(conanConfig)
146144

145+
// TODO: Support lockfiles which are located in a different directory than the definition file.
146+
val lockfileName = analyzerConfig.packageManagers?.get(managerName)?.options?.get("lockfileName")
147+
requireLockfile(workingDir) { lockfileName?.let { hasLockFile(workingDir.resolve(it).path) } ?: false }
148+
147149
val jsonFile = createOrtTempDir().resolve("info.json")
148150
run(workingDir, "info", definitionFile.name, "--json", jsonFile.absolutePath, *DUMMY_COMPILER_SETTINGS)
149151

150152
val pkgInfos = jsonMapper.readTree(jsonFile)
151153
jsonFile.parentFile.safeDeleteRecursively(force = true)
152154

153-
val packageList = removeProjectPackage(pkgInfos, definitionFile.name)
155+
val filteredPkgInfos = if (!analyzerConfig.allowDynamicVersions && lockfileName != null) {
156+
val lockfileNodes = jsonMapper.readTree(definitionFile.resolveSibling(lockfileName))
157+
val lockfilePackages = lockfileNodes["graph_lock"]["nodes"].mapNotNull {
158+
it["ref"]?.textValue()
159+
}
160+
161+
jsonMapper.createArrayNode().addAll(
162+
pkgInfos.filter { jsonNode ->
163+
lockfilePackages.contains(jsonNode["reference"].textValueOrEmpty())
164+
|| jsonNode["reference"].textValueOrEmpty().contains(definitionFile.name)
165+
}
166+
)
167+
} else {
168+
pkgInfos
169+
}
170+
171+
val packageList = removeProjectPackage(filteredPkgInfos, definitionFile.name)
154172
val packages = parsePackages(packageList, workingDir)
155173

156174
val dependenciesScope = Scope(
157175
name = SCOPE_NAME_DEPENDENCIES,
158-
dependencies = parseDependencies(pkgInfos, definitionFile.name, SCOPE_NAME_DEPENDENCIES, workingDir)
176+
dependencies =
177+
parseDependencies(filteredPkgInfos, definitionFile.name, SCOPE_NAME_DEPENDENCIES, workingDir)
159178
)
160179
val devDependenciesScope = Scope(
161180
name = SCOPE_NAME_DEV_DEPENDENCIES,
162-
dependencies = parseDependencies(pkgInfos, definitionFile.name, SCOPE_NAME_DEV_DEPENDENCIES, workingDir)
181+
dependencies =
182+
parseDependencies(filteredPkgInfos, definitionFile.name, SCOPE_NAME_DEV_DEPENDENCIES, workingDir)
163183
)
164184

165-
val projectPackage = parseProjectPackage(pkgInfos, definitionFile, workingDir)
185+
val projectPackage = parseProjectPackage(filteredPkgInfos, definitionFile, workingDir)
166186

167187
return listOf(
168188
ProjectAnalyzerResult(

0 commit comments

Comments
 (0)