diff --git a/docs/detectors/conan.md b/docs/detectors/conan.md
new file mode 100644
index 000000000..10c847a8f
--- /dev/null
+++ b/docs/detectors/conan.md
@@ -0,0 +1,11 @@
+# Conan Detection
+## Requirements
+Conan detection relies on a conan.lock file being present.
+
+## Detection strategy
+Conan detection is performed by parsing every conan.lock found under the scan directory.
+
+## Known limitations
+Conan detection will not work if lock files are not being used or not yet generated. So ensure to run the conan build to generate the lock file(s) before running the scan.
+
+Full dependency graph generation is not supported. However, dependency relationships identified/present in the conan.lock file is captured.
diff --git a/src/Microsoft.ComponentDetection.Detectors/conan/ConanLockComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/conan/ConanLockComponentDetector.cs
index 70900dcc3..e182867b6 100644
--- a/src/Microsoft.ComponentDetection.Detectors/conan/ConanLockComponentDetector.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/conan/ConanLockComponentDetector.cs
@@ -46,6 +46,11 @@ protected override async Task OnFileFoundAsync(ProcessRequest processRequest, ID
{
var conanLock = JsonSerializer.Deserialize(conanLockFile.Stream);
this.RecordLockfileVersion(conanLock.Version);
+ if (conanLock.Version != "0.4")
+ {
+ this.Logger.LogWarning("Unsupported conan.lock file version '{ConanLockVersion}'. Failed to process conan.lock file '{ConanLockLocation}'", conanLock.Version, conanLockFile.Location);
+ return;
+ }
if (!conanLock.HasNodes())
{
@@ -58,8 +63,8 @@ protected override async Task OnFileFoundAsync(ProcessRequest processRequest, ID
if (packagesDictionary.ContainsKey("0"))
{
packagesDictionary.Remove("0", out var rootNode);
- explicitReferencedDependencies = rootNode.Requires;
- developmentDependencies = rootNode.BuildRequires;
+ explicitReferencedDependencies = rootNode.Requires ?? Array.Empty();
+ developmentDependencies = rootNode.BuildRequires ?? Array.Empty();
}
foreach (var (packageIndex, package) in packagesDictionary)
diff --git a/src/Microsoft.ComponentDetection.Detectors/conan/Contracts/ConanLockNode.cs b/src/Microsoft.ComponentDetection.Detectors/conan/Contracts/ConanLockNode.cs
index 090ecb74b..91aeb4223 100644
--- a/src/Microsoft.ComponentDetection.Detectors/conan/Contracts/ConanLockNode.cs
+++ b/src/Microsoft.ComponentDetection.Detectors/conan/Contracts/ConanLockNode.cs
@@ -7,9 +7,6 @@ namespace Microsoft.ComponentDetection.Detectors.Conan.Contracts;
public class ConanLockNode
{
- private string[] requires;
- private string[] buildRequires;
-
[JsonPropertyName("context")]
public string Context { get; set; }
@@ -32,18 +29,18 @@ public class ConanLockNode
public string Reference { get; set; }
[JsonPropertyName("requires")]
- public string[] Requires { get => this.requires; set => this.requires = value ?? Array.Empty(); }
+ public string[] Requires { get; set; }
[JsonPropertyName("build_requires")]
- public string[] BuildRequires { get => this.buildRequires; set => this.buildRequires = value ?? Array.Empty(); }
+ public string[] BuildRequires { get; set; }
public override bool Equals(object obj) => obj is ConanLockNode node && this.Context == node.Context && this.Modified == node.Modified && this.Options == node.Options && this.PackageId == node.PackageId && this.Path == node.Path && this.Previous == node.Previous && this.Reference == node.Reference && Enumerable.SequenceEqual(this.Requires, node.Requires);
public override int GetHashCode() => HashCode.Combine(this.Context, this.Modified, this.Options, this.PackageId, this.Path, this.Previous, this.Reference, this.Requires);
- internal string Name() => this.Reference.Split('/', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).FirstOrDefault("Unknown");
+ internal string Name() => this.Reference == null ? string.Empty : this.Reference.Split('/', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).FirstOrDefault("Unknown");
internal TypedComponent ToComponent() => new ConanComponent(this.Name(), this.Version());
- internal string Version() => this.Reference.Split('/', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Skip(1).FirstOrDefault("None");
+ internal string Version() => this.Reference == null ? string.Empty : this.Reference.Split('/', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Skip(1).FirstOrDefault("None");
}
diff --git a/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanPythonFile/conan.lock b/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanPythonFile/conan.lock
new file mode 100644
index 000000000..84b7147e0
--- /dev/null
+++ b/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanPythonFile/conan.lock
@@ -0,0 +1,77 @@
+{
+ "graph_lock": {
+ "nodes": {
+ "0": {
+ "ref": "MyAwesomeConanProject/1.2.5",
+ "options": "",
+ "requires": [
+ "1"
+ ],
+ "build_requires": [
+ "6"
+ ],
+ "path": "conanfile.py",
+ "context": "host"
+ },
+ "1": {
+ "ref": "boost/1.82.0",
+ "options": "",
+ "package_id": "dd7f5f958c7381cfd81e611a16062de0c827160a",
+ "prev": "0",
+ "modified": true,
+ "requires": [
+ "2",
+ "3",
+ "4"
+ ],
+ "build_requires": [
+ "5"
+ ],
+ "context": "host"
+ },
+ "2": {
+ "ref": "zlib/1.2.13",
+ "options": "",
+ "package_id": "240c2182163325b213ca6886a7614c8ed2bf1738",
+ "prev": "0",
+ "modified": true,
+ "context": "host"
+ },
+ "3": {
+ "ref": "bzip2/1.0.8",
+ "options": "",
+ "package_id": "238a93dc813ca1550968399f1f8925565feeff8e",
+ "prev": "0",
+ "modified": true,
+ "context": "host"
+ },
+ "4": {
+ "ref": "libbacktrace/cci.20210118",
+ "options": "",
+ "package_id": "240c2182163325b213ca6886a7614c8ed2bf1738",
+ "prev": "0",
+ "modified": true,
+ "context": "host"
+ },
+ "5": {
+ "ref": "b2/4.9.6",
+ "options": "",
+ "package_id": "a5ad5696abf650a25eea8f377806b3d5fe234e6e",
+ "prev": "0",
+ "modified": true,
+ "context": "host"
+ },
+ "6": {
+ "ref": "gtest/1.8.1",
+ "options": "",
+ "package_id": "fb16a498e820fb09d04ff9374a782b5b21da0601",
+ "prev": "0",
+ "modified": true,
+ "context": "host"
+ }
+ },
+ "revisions_enabled": false
+ },
+ "version": "0.4",
+ "profile_host": "\n"
+}
\ No newline at end of file
diff --git a/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanPythonFile/conanfile.py b/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanPythonFile/conanfile.py
new file mode 100644
index 000000000..6d0e2e714
--- /dev/null
+++ b/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanPythonFile/conanfile.py
@@ -0,0 +1,11 @@
+from conans import ConanFile
+
+class MyAwesome(ConanFile):
+ name = "MyAwesomeConanProject"
+ version = "1.2.5"
+
+ def requirements(self):
+ self.requires("boost/1.82.0")
+
+ def build_requirements(self):
+ self.tool_requires("gtest/1.8.1")
diff --git a/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanTextFile/conan.lock b/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanTextFile/conan.lock
new file mode 100644
index 000000000..560ece630
--- /dev/null
+++ b/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanTextFile/conan.lock
@@ -0,0 +1,60 @@
+{
+ "graph_lock": {
+ "nodes": {
+ "0": {
+ "options": "",
+ "requires": [
+ "1"
+ ],
+ "build_requires": [
+ "5"
+ ],
+ "path": "conanfile.txt",
+ "context": "host"
+ },
+ "1": {
+ "ref": "boost/1.82.0",
+ "options": "",
+ "package_id": "dd7f5f958c7381cfd81e611a16062de0c827160a",
+ "prev": "0",
+ "requires": [
+ "2",
+ "3",
+ "4"
+ ],
+ "context": "host"
+ },
+ "2": {
+ "ref": "zlib/1.2.13",
+ "options": "",
+ "package_id": "240c2182163325b213ca6886a7614c8ed2bf1738",
+ "prev": "0",
+ "context": "host"
+ },
+ "3": {
+ "ref": "bzip2/1.0.8",
+ "options": "",
+ "package_id": "238a93dc813ca1550968399f1f8925565feeff8e",
+ "prev": "0",
+ "context": "host"
+ },
+ "4": {
+ "ref": "libbacktrace/cci.20210118",
+ "options": "",
+ "package_id": "240c2182163325b213ca6886a7614c8ed2bf1738",
+ "prev": "0",
+ "context": "host"
+ },
+ "5": {
+ "ref": "gtest/1.8.1",
+ "options": "",
+ "package_id": "fb16a498e820fb09d04ff9374a782b5b21da0601",
+ "prev": "0",
+ "context": "host"
+ }
+ },
+ "revisions_enabled": false
+ },
+ "version": "0.4",
+ "profile_host": "\n"
+}
\ No newline at end of file
diff --git a/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanTextFile/conanfile.txt b/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanTextFile/conanfile.txt
new file mode 100644
index 000000000..b7eba86a4
--- /dev/null
+++ b/test/Microsoft.ComponentDetection.VerificationTests/resources/conan/conanTextFile/conanfile.txt
@@ -0,0 +1,5 @@
+[requires]
+boost/1.82.0
+
+[tool_requires]
+gtest/1.8.1